All streams
Search
Write a publication
Pull to refresh
88
0
Вадим Дерябкин @Vadimatorikda

Инженер-программист

Send message
Нет потоков и ОС — нет mutex-ов.
А если серьезно, то правильно настроив NVIC — можно получить такой код, когда такой проблемы не возникает.
Насколько я знаю, еще есть средства самого языка для атомарного доступа. Но ими я не пользовался, увы. Не было необходимости. Так что буду знать, если знатоки напомнят. std::...;
Иногда приходилось применять «костыль» с блокировкой всех прерываний и последующим возобновлением. Так как это дело пары команд ассемблера, то ничего ИМХО, страшного.
Ну если в этом смысле, то возможно. Хотя и не факт. Например бытует мнение о том, что в прерываниях нужно делать «минимум работы». А остатки уже в основной программе. Это не совсем справедливо для «больших» микроконтроллеров. Из-за наличия NVIC (о чем было в статье).
Сомневаюсь, что дело в этом. ОСРВ в МК не дает особых преимуществ над железом (если речь о FreeRTOS). Поскольку она дает лишь базовые вещи. Потоки и их средства синхронизации. Другое дело «ОС со встроенным аппаратным уровнем», каких сейчас тоже не мало. Там да. Есть и uart.tx() встроенные и прочие.
К сожалению о Rust знаю лишь по наслышке. По-этому не могу ничего сказать на этот счет. Думаю есть на хабре люди, которые смогу высказать свое мнение по этому вопросу.
Опять же ИМХО, но для себя выбрал путь «Си для драйверов + Lua для логики». Поскольку в моей практике редко встречаются устройства с «золотой серединой». Это либо совсем уж «преобразователи протоколов» (там нет никакой Lua. Драйвера и пара функций). Либо уж совсем монстры (GUI, логика переходов сравнимая с игровыми ботами...). Но к Rust присматриваюсь чисто в академических целях. Может в будущем займусь всерьез.
Ну и про тех кто пишет в CLion + GCC тоже. Вариантов много :)
Почему же? constexpr объект может представлять из себя один get метод для const поля. Вполне себе. При этом переменная будет лежать во flash. Ровно как и метод. Если конечно опциями компоновщика не задано иное.
Вот. Вы предлагаете делать примерно как в HAL-е. Подсовывать структуру при каждом вызове. А в моем случае это скрыто. Внутри есть структура с текущим состоянием и с начальным. К которому можно откатиться, вызвав метод cfg_reset. Таком образом, после инициализации не нужно ничего передавать в вывод.
По поводу реализации. Есть у меня проект-песочница. Там я делал нечто подобное. Когда только отрабатывал все эти штуки:
А восстанавливать исходное состояние как будете? Если нужен метод reset_cfg?
Виноват подход. О чем ясно сказано. А подход идет от возможностей языка. Если полноценно не используются возможности языка, то он выбран не верно. Это все ИМХО, конечно же.
У вас есть HAL, который вы заполняете на основании параметров из конструктора.

Нет. Не в конструкторе. А в методе init. То есть, в реальном времени. Это нужно для того, повторюсь, чтобы после всех манипуляций по ходу работы с структурой HAL-а иметь возможность восстановить ее в исходное состояние.
Суть в том, что структура, которая передается объекту PIN — это не структура HAL Это собственная настройка. По ней в init методе заполнялась структура HAL-а… И когда вызывались методы изменения конфигурации, то менялась структура HAL-а, которая в RAM. А если вызывался метод reset, то это было равносильно удалению объекта и его созданию заново. Только без работы с памятью. Старая структура в RAM перезатиралась данными из структуры, указатель на которую был передан при создании.
В том предприятии это было явно. Ведь это писал сварщик по образованию (хоть и оператор электронно-лучевой установки).
Я уже видел в своей практике (на прошлом предприятии) вот такой участок кода в проекте под stm32f1.
cout << "temp: " << sensor.get();

И вопросы «А почему он вдруг не вмещается в МК?! Я же только вывести хотел!». А то что stdout отжирает 300кб в мк с 128 кб flash — его не беспокоило.
Или snprintf внутри прерывания в буфер в 2 кб при стеке под прерывания в 1 кб…
Еще была цель в будущем переложить задачу создания «однотипных устройств» на программистов, не имеющих опыта работы с железом. Например, когда меняется только протокол (для устройств сопряжения).
Идея звучала интересно. Но разбилась о практику. Такие дела.
Я не соглашусь с использованием HAL-а даже. Он дико избыточен. Инициализировать UART у меня занимает 5 строк кода (запись в регистры). А вот HAL просит структуру какую-то. Ведет свои состояния. Логику и прочее. Это ведь тоже по сути объектно-ореинтированный подход. Что уже по сути своей для вывода избыточно. Причем, еще и игнорируется аппаратная часть. Чтобы записать «1» в вывод — не нужно считывать регистр, накладывать маску и записывать обратно. Нужно просто знать про бит-ендинг. И записать число по нужному адресу. Абсолютная атомарность.
Конечная цель (еще до моего прихода на предприятие) было создание «конструктора». Когда можно «собрать устройство» из готовых блоков на проверенной архитектуре. Поэтому и классов много. Если бы я создавал устройство с нуля, то я бы делал так, как делаю на фрилансах: на Си пишу драйвер под архитектуру (если вдруг нужно что-то новое) и на Lua пишу логику. Которую, в случае чего, может править пользователь.
Так было задумано для того, чтобы потом можно было изменить параметры вывода на лету (скорости, альтернативная функция). А потом, по надобности, восстановить начальные значения отдельной командой. До того, как это понадобилось, было как вы сказали. Передавалась структура и по ней constexpr метод заполнял HAL структуру внутри объекта. По которой в реальном времени инициализировался вывод.
Не понятно, зачем вам иметь отдельно объекты pin_cfg

Для того, чтобы не передавать в конструктор класса 8 параметров. Вместо этого указатель на структуру с этими параметрами.
отдельно объекты pin

Ну вот, например, объект UART хочет себе при инициализации объект класса Pin для управления CS (если не используется аппаратный по какой-то причине. Например, если в МК один SPI на кучу устройств).
отдельно вектор указателей на объекты pin

Для объекта, который управляет всеми выводами разом одной командой.
Ну вот краткая нарезка из одного из проектов (имена изменены, части приведенные ниже разбиты согласно архитектуре проекта по различным файлам в разных директориях. Тут приведено подряд для удобства).
Код
// SPI
#define PIN_SPI_1_MOSI {GPIOA, {GPIO_PIN_7, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_VERY_HIGH, GPIO_AF5_SPI1}}
#define PIN_SPI_1_MISO {GPIOA, {GPIO_PIN_6, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_VERY_HIGH, GPIO_AF5_SPI1}}
#define PIN_SPI_1_CLK {GPIOA, {GPIO_PIN_5, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_VERY_HIGH, GPIO_AF5_SPI1}}
#define PIN_SPI_1_CS {GPIOA, {GPIO_PIN_4, GPIO_MODE_OUTPUT_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_VERY_HIGH, 0}}
...
// SPI
const pin_cfg pin_spi_1_mosi[] = {PIN_SPI_1_MOSI};
const pin_cfg pin_spi_1_miso[] = {PIN_SPI_1_MISO};
const pin_cfg pin_spi_1_clk[] = {PIN_SPI_1_CLK};
const pin_cfg pin_spi_1_cs[] = {PIN_SPI_1_CS};
...
// SPI
pin spi_1_mosi(pin_spi_1_mosi);
pin spi_1_miso(pin_spi_1_miso);
pin spi_1_clk(pin_spi_1_clk);
pin spi_1_cs(pin_spi_1_cs);
...
const pin *project_name_pin_array[PROJ_NAME_PIN_COUNT] = {
...
    &spi_1_mosi,
    &spi_1_miso,
    &spi_1_clk,
    &spi_1_cs,
...
}
...
GlobalProt gb_controller(project_name_pin_array);
...
struct project_name_bsp_controller_cfg {
    const pin **initialization_pins;
    uint32_t initialization_pins_count;
...
}

proj_name_bsp_controller bsp(&bsp_cfg);
prog_name_controller_cfg pr_cfg {
...
&bsp
};

prog_name_controller proj_name(pr_cfg);
...
int main () {
return proj_name.start();
}


Тут приведена «жизнь обычного вывода». Есть структура с описанием его конфигурации (для удобства, конфигурации описываются отдельными определениями в .h файле для каждого пина, а потом этот define используется для инициализации полей структур). Эта структура «скармливается» глобальному объекту пина в качестве параметра инициализации. Далее указатель на этот объект становится частью глобального массива на объекты используемых пинов. Этот массив является структурой инициализации для объекта GlobalProt, через который производится взаимодействие сразу со всеми выводами (инициализировать все, сбросить конфигурацию, изменить параметры порта целиком и прочее). Указатель на этот объект является частью структуры инициализации bsp контроллера (board support package), который управляет всей периферией и дает высокоуровневый интерфейс для взаимодействия с периферией (на уровне протоколов и нематериальных объектов (не таймер, работающий в режиме pwm,, а «подсветка с яркостью в диапазоне от А до Б»). После чего, для использования аппаратки из кода логики, указатель на объект bsp дается уже контроллеру приложения, в котором инкапсулирована бизнес-логика приложения.
Вы можете создать объект класса Rs485Port, который агрегирует UART, GPIO (для управления DE (направление передачи) выводом). Тогда это будет правильно с точки зрения архитектуры проекта. А над ним построить класс, предоставляющий API именно для взаимодействия по требуемому протоколу. Какой-нибудь ModBusContriller. И вот у вас уже 3 уровня абстракции (UART и GPIO, потом инкапсулятор 485 и контроллер протокола). А выше могут быть еще какие-то классы логики. Которые что-то высылают по протоколу при каких-то событиях.
Делайте объекты крупнее.

Тогда нарушается принцип «один объект — одна область ответственности». А это уже не хорошо. Потому что никто не ждет от «функции с именем print запроса данных от пользователя». Я помню, как у меня горело, когда я обновил HAL, а там функция изменения частоты ядра (HAL_RCC..._Config) еще и инициализировала systic, что приводило к тому, что у меня прерывание срабатывало раньше, чем пройдет инициализация всех сущностей проекта и запустится планировщик FreeRTOS.

Information

Rating
Does not participate
Location
Красноярск, Красноярский край, Россия
Date of birth
Registered
Activity

Specialization

Software Developer, Embedded Software Engineer
Lead
From 250,000 ₽
C++
STM32
Linux
Circuitry
Python
Assembler
Programming microcontrollers
Embedded system
Software development
Object-oriented design