CDEblog
Обновил версию до 0.0.2
Заменил бубликовые диаграммы на стековую, теперь меньше места тратится впустую.
Отключать ненужные куски на диаграмме нельзя, зато при нажатии сразу фильтруется до нужного символа.
Добавил нормальные тултипы для диаграм
Заменил бубликовые диаграммы на стековую, теперь меньше места тратится впустую.
Отключать ненужные куски на диаграмме нельзя, зато при нажатии сразу фильтруется до нужного символа.
Добавил нормальные тултипы для диаграм
Представим, что мы пишем некую обобщенную инициализацию с использованием макроса generic.
void mod1_init(inst1_t*, cfg_t*);
void mod2_init(inst2_t*, cfg_t*);
#define mod_init(x,c) _Generic((x),\
inst1_t*: mod1_init, \
inst2_t*: mod2_init)(x, c)
Далее когда будем пользоваться просто вызываем наш макрос с нужными параметрами и всё само подставляется.
cfg_t cfg = {
.f=1,
.d=3,
};
mod_init(&inst, &cfg);Ровно до тех пор, пока мы не захотим передать указатель на составной литерал.
mod_init(&inst, &(cfg_t){
.f=1,
.d=3,
});Код собираться не будет, а ведь это могла быть вполне валидная конструкция, будь здесь не макрос, а простая функция.
Что бы исправить ситуацию введём в макрос вариадик аргумент, но без всяких "
__VA_ARGS__"#define mod_init(x,c...) _Generic((x),\
inst1_t*: mod1_init,\
inst2_t*: mod2_init)(x, c)
Заметили три точки после параметра "
c"?Есть только одно НО: это не стандарт, а лишь расширение GNU 🤷♂️
https://cdeblog.ru/variadic-macro-with-generic
#c #cpp
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🤔1🤩1👌1
Хотелось бы озвучить свое мнение о данном вопросе.
Речь пойдет о передачи каких-либо параметров в функцию и возврате указателей различных типов.
Например такие:
// Пример (1)
uint32_t * p = (uint32_t *) malloc(1245);
// Пример (2)
uint32_t (* arr)[32] = malloc(sizeof(* arr));
void * init_fq(void* init_arr);
typedef struct data_t {
void * f1;
data_item_t * f2;
volatile void * f3;
} data_t;
data_t var_s = {
.f1 = (void *)init_fq((void *)(*arr)),
.f2 = (data_item_t *)malloc(sizeof(data_item_t)),
.f3 = &volatile_array,
};
// Пример (3) с потерей квалификаторов
uint32_t * p1 = (void *)var_s.f3; // теряем volatile
С одной стороны кажется, что ни чего плохого в данном коде нет, возможно кроме некоторых специфичных конструкций, ок которых писал ранее.
При возврате из аллокатора нет смысла кастовать, так как зачастую все аллокаторы возвращают именно
void*, а все дополнительные махинации лишь добавляют в код грязи (Пример 1)Случай 2 уже не так безобиден. Пока у нас поле
f1 структуры имеет тип void* всё хорошо и работает так же как предыдущий случай. Но может настать момент когда тип поля структуры изменится, а возвращаемое значение изменится на что-то иное, например перестанет быть указателем, или станет указателем совсем другого типа, в таком случае не исключается вариант конфликта типов, который не будет подсвечен из-за принудительного каста.Другим нехорошим обстоятельством (Пример 3) может быть потеря квалификаторов
volatile и const, а вот это уже может быть большой проблемой. Начиная от неопределенного поведения и заканчивая падениями в рантайме, не понятно что хуже.Перепишем код и избавимся от лишних вещей:
uint32_t * p = malloc(1245);
data_t var_s = {
.f1 = init_fq(*arr),
.f2 = malloc(sizeof(data_item_t)),
.f3 = &volatile_array,
};
volatile uint32_t * p1 = var_s.f3; // теряем volatile
Стоит сказать, что кастование одних типов к другим весьма нехорошая практика, которая потенциально приносит проблемы и грязь в код.
Есть еще одна мысль на счет универсальных функций вроде аллокаторов или функций вида
send() возвращаемые значения и типы входных аргументов оформлять как void*. Таким образом и код будет чище и сопровождать его проще.// Пример с приемом и возвратом uint8_t*
size_t sent_data_u8(uint8_t * data, size_t size);
size_t sent_data_pv(void * data, size_t size);
data_t data_s = { ... };
uint8_t data_arr[32] = { ... };
uint32_t data_32 = 42;
sent_data_u8((uint8_t *)&data_s, sizeof(data_s));
sent_data_u8((uint8_t *)data_arr, sizeof(data_arr));
sent_data_u8((uint8_t *)&data_32, sizeof(data_32));
sent_data_pv(&data_s, sizeof(data_s));
sent_data_pv(data_arr, sizeof(data_arr));
sent_data_pv(&data_32, sizeof(data_32));
Мне кажется выводы сделать просто.
https://cdeblog.ru/void-pointers-casts
Если у вас другое мнение, готов обсудить и даже принять
#c #cpp
Please open Telegram to view this post
VIEW IN TELEGRAM
Иногда вместе со списком элементов необходимо иметь их количество и значение последнего элемента.
Подобное часто применяется для перечисления допустимых индексов каких-либо аппаратных блоков или каких-то статусов.
Не редно видел конструкции вида:
enum dma_instance_e
{
DMA_INST_0,
DMA_INST_1,
DMA_INST_2,
DMA_INST_LAST = DMA_INST_2,
DMA_INST_QTY = DMA_INST_LAST + 1,
};
На этапе разработки постоянно требуется меняеть количество этих элементов.
Но в коде выше есть проблема: решили добавить дма - необходимо изменить ещё и ласт. Одна правка, а изменяет две строчки, что не только не красиво, но и потенциальная ошибка из-за невнимательности.
Намного проще поменять порядок элементов и возложить эту задачу на компилятор:
enum dma_instance_e
{
DMA_INST_0, // 0
DMA_INST_1, // 1
DMA_INST_2, // 2
// <== добавлять тут
DMA_INST_QTY, // 3
DMA_INST_LAST=DMA_INST_QTY-1, // 2
};
#си #перечисление #enum
Please open Telegram to view this post
VIEW IN TELEGRAM
Очередное расширение GNU.
Допустим у нас ситуация... с такими условиями:
void func(uint32_t var)
{
if (var >= VALUE_A_START && var <= VALUE_A_END)
{ /* CODE */}
else if (var >= VALUE_B_START && var <= VALUE_B_END)
{ /* CODE */}
else if (var >= VALUE_C_START && var <= VALUE_C_END)
{ /* CODE */}
else if (var >= VALUE_D_START && var <= VALUE_D_END)
{ /* CODE */}
else
{ /* CODE */}
}
В зависимости от попадания в нужный диапазон выполняется то или иное действие.
Попробуем переписать при помощи switch:
void func(uint32_t var)
{
switch (var)
{
case VALUE_A_START:
case VALUE_A_1:
case VALUE_A_2:
// ... здесь куча других case
case VALUE_A_30:
case VALUE_A_31:
case VALUE_A_END:
/* CODE */
break;
// Остальные секции: D, C, D
...
default: break;
}
}
Выглядит как-то не красиво и это не кажется. Если у нас будет очень большой диапазон проверяемых значений, код станет совсем нечитаем.
Попробуем использовать расширения GNU и переписать код в более удобном для восприятия виде:
void func(uint32_t var)
{
switch (var)
{
case VALUE_A_START ... VALUE_A_END:
/* CODE */
break;
case VALUE_B_START ... VALUE_B_END:
/* CODE */
break;
case VALUE_C_START ... VALUE_C_END:
/* CODE */
break;
case VALUE_D_START ... VALUE_D_END:
/* CODE */
break;
default: break;
}
}
В итоге код стал проще выглядеть и восприниматься. Мы сохранили читаемость такую же как у "if", но приобрели скорость выполнения конструкции "switch"
Пусть это и расширение GNU, но clang собирает без ругани даже если указать
-std=c11 , а не -std=gnu11#c #switchcase #switch
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Паттерн "шина событий" (Event Bus) - это архитектурный шаблон, который позволяет компонентам системы взаимодействовать друг с другом, обмениваясь событиями, без необходимости явного знания о существовании друг друга.Казалось бы к чему это.
Начнем рассказ с архитектуры ПО, которое работает на нашем видеопроцессоре.
Существует тракт обработки видео и множество модулей, которые выполняют действия в какой-то момент обработки потока кадров:
Для того, что бы не нагружать центральный модуль (тракт) вызовом кучей различных функций и не менять постоянно его струтуру при изменении и добавлении функционала было решено реализовать шину событий.
Основная идея в подписи задач на события и последующее их ожидание. Другие же задачи могут формировать эти события. Таким образом, даже не зная устройство друг друга, задачи взаимодействуют друг с другом и синхронизируются.
Первой попыткой реализации было использование event flags и event groups. Однако у этого подхода есть ограничения в невозможности одновременной подписки нескольких задач на одно событие. Флаг сбрасывает первая разблокированная задача.
Вторая попытка с использованием нотификаций, путь даже и с небольшим оверхедом, прижилась лучше.
Реализация заключается в использовании двухмерного массива хэндлов задач, ожидающих событий.
/// \brief Массив очередей событий
/// \note events[ количество событий ][ глубина очереди ]
static event_task_handler_t events
[ EVENT_BUS_QTY ]
[ EVENT_LIST_DEPHT ] = {0};
Подписываясь на событие задача вписывает свой хендл в свободную ячейку требуемого события, отписываюсь - стирает.
Задача которая генерирует событие посылает уведомление каждой задаче из списка.
Таким образом каждая ожидающая задача гарантированно получает уведомление и разблокируется для выполнения, заодно и решается проблема связанная со сбросом флагов в event flags.
Небольшой пример:
Источник событий
static void task_1(void * arg)
{
/* INIT TASK CODE */
for (;;){
/* CODE */
event_bus_push(EVENT_COMPETE_1);
}
}
Потребитель событий 1
static void task_2(void * arg)
{
/* INIT TASK CODE */
// Подписать текущую задачу
event_bus_subscribe(EVENT_COMPETE_1);
// Подписать задачу 3
event_bus_unsubscribe_ex(EVENT_COMPETE_1, task_3_handler);
for (;;){
if (event_bus_wait(EVENT_COMPETE_1, 100) == true){
/* CODE */
}
}
}
Потребитель событий 2
static void task_3(void * arg)
{
for (;;){
if (event_bus_wait(EVENT_COMPETE_1, 100) != true)
{
continue;
}
/* CODE */
}
}
Полный код реализации можно найти в репозитории:
https://github.com/devprodest/event-bus-freertos/tree/main
#eventbus #freertos #шинасобытий
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4 1
Связь модулей "один ко многим" на примере централизованного хранилища
Представьте ситуацию, где одна задача которая собирает статистику дергает по одной функции из каждого модуля, а модулей может быть не 3 и не 10, а много больше. Таким образом мы сразу привязываем логику работы модуля сбора статистики к реализациям остальных модулей. Всё усложнится если таких модулей сбора данных будет несколько.
Вместо зависимости от всех модулей правильнее реализовать зависимость всех модулей от одного, который и будет являться централизованным хранилищем.
Подобная реализация позволяет уменьшить связность кода за счет изменения связки многие ко многим на связку многие к одному, что позволяет избежать ненужных зависимостей.
В рассматриваемом примере хранилище является небольшим словарем.
Ключом выступает тип данных.
В качестве самих данных используется указатель на хранимую сущность.
Так же каждая запись имеет уникальное текстовое имя для идентификации записей одного типа.
Поддерживает следующие типы данных:
-
-
-
-
-
Любое приложение или сервис может зарегистрировать ячейку с нужным именем и типом данных, передав в неё указатель на сами данные. При необходимости ячейка может быть освобождена.
Регистрация и освобождение/удаление ячейки выполняется следующим образом. Важно, что бы параметры при регистрации и удалении были одинаковыми.
Получение данных может быть выполнено вызовом функций
Аналогично выполняется работа и с другими типами
Полный код реализации можно найти в репозитории:
https://github.com/devprodest/statistic
#c #cpp
Представьте ситуацию, где одна задача которая собирает статистику дергает по одной функции из каждого модуля, а модулей может быть не 3 и не 10, а много больше. Таким образом мы сразу привязываем логику работы модуля сбора статистики к реализациям остальных модулей. Всё усложнится если таких модулей сбора данных будет несколько.
Вместо зависимости от всех модулей правильнее реализовать зависимость всех модулей от одного, который и будет являться централизованным хранилищем.
Подобная реализация позволяет уменьшить связность кода за счет изменения связки многие ко многим на связку многие к одному, что позволяет избежать ненужных зависимостей.
В рассматриваемом примере хранилище является небольшим словарем.
Ключом выступает тип данных.
В качестве самих данных используется указатель на хранимую сущность.
Так же каждая запись имеет уникальное текстовое имя для идентификации записей одного типа.
Поддерживает следующие типы данных:
-
STATISTIC_TYPE_EMPTY - пустая ячейка-
STATISTIC_TYPE_FPS - ячейка с частотой кадров-
STATISTIC_TYPE_U32_VALUE - ячейка с целым числом-
STATISTIC_TYPE_STRING - ячейка со строкой-
STATISTIC_TYPE_PROGRESS - ячейка с прогрессомЛюбое приложение или сервис может зарегистрировать ячейку с нужным именем и типом данных, передав в неё указатель на сами данные. При необходимости ячейка может быть освобождена.
Регистрация и освобождение/удаление ячейки выполняется следующим образом. Важно, что бы параметры при регистрации и удалении были одинаковыми.
statistic_register(STATISTIC_TYPE_FPS, &ctx->fps, "rtp:h264");
...
/* Код модуля */
...
statistic_remove(STATISTIC_TYPE_FPS, &ctx->fps, "rtp:h264");
Получение данных может быть выполнено вызовом функций
// Получаем указатель на первую ячейку типа STATISTIC_TYPE_FPS
statistic_record_s * fpsrec = statistic_record_get(STATISTIC_TYPE_FPS);
while (fpsrec != NULL)
{
fps_s * fps = fpsrec->data;
...
/* Код обработки данных */
...
// Получаем указатель на следующую ячейку
fpsrec = statistic_record_get_next(fpsrec, STATISTIC_TYPE_FPS);
}
Аналогично выполняется работа и с другими типами
Полный код реализации можно найти в репозитории:
https://github.com/devprodest/statistic
#c #cpp
vscode-svnlens-1.0.4.vsix
79.8 KB
Очередная версия плагинчика для SVN
Много воды с тех пор утекло и много изменений было сделано для стабильной работы.
Что нового:
🟡 Улучшилась стабильность. Теперь меньше ошибок в консоли.
🟡 Код довольно сильно отрефакторен
🟡 Добавлено всплывающее окно с дополнительной информацией по комиту (в том числе и сообщение комита)
🟡 Исправлена первая проблема на Github от пользователя плагина: информация о последнем открытом отслеживаемом файле отображалась в файле, который не отслеживается, а так же в других окнах, таких как Chat Copilot
🟡 Добавлен канал вывода логов "SVN Lens".
🟡 Исправлена ошибка с пустой последней строкой. SVN blame её не отображает. Добавлена заглушка "Last empty line is missing from blame".
Github: https://github.com/devprodest/vscode-svn-lens
vscode matketplace: https://marketplace.visualstudio.com/items?itemName=ZaikinDenis.vscode-svnlens
Много воды с тех пор утекло и много изменений было сделано для стабильной работы.
Что нового:
Github: https://github.com/devprodest/vscode-svn-lens
vscode matketplace: https://marketplace.visualstudio.com/items?itemName=ZaikinDenis.vscode-svnlens
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
В этой статье рассмотривается, как настроить контейнерную среду разработки для проектов на языке C. Также настройку системы сборки с помощью CMake, среды тестирования с помощью Unity и даже то, как использовать контейнерную среду в конвейере непрерывной интеграции!
Статья старая, но не бесполезная. Язык английский.
https://interrupt.memfault.com/blog/a-modern-c-dev-env
#memfault #c #docker #vscode
Please open Telegram to view this post
VIEW IN TELEGRAM
Interrupt
A Modern C Development Environment
Spinning up a C development environment using CMake, Docker, Unity, and GitHub Actions.
👍1
Мы тут на работе несколько дней назад обсуждали различные варианты включения бинарных данных в прошивку.
Сравнивали варианты с ассемлерными вставками, которые применяли раньше, когда пользовались c11 и gcc 8.1, и новой директивой
#embed которую только месяц назад втащил в проект вместе с c23Примерно так может выглядеть код:
const uint8_t font_data[] = {
#embed "font.bin"
};Плюсы:
extern переменных со странными именами.Теперь у области с данными есть имя (массив) и его видно при анализе эльфа.
#include образом.Минусы:
Дополнительные примочки которые не используем, но могут пригодится:
Ну и напоследок, проверить доступность директивы можно
макросом __has_embed#c #с23 #embed
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
Это объявление сущности без указания подробностей её внутренностях. По сути позволяет компилятору понять размер и разрешить ссылки при сборке.
Типичным примером является прототип функции
int func(int val);
Таким же образом можно указывать и типы
typedef struct st_type_t st_type_t;
typedef enum e_type_t e_type_t;
Для чего это может быть полезно.
1. Уменьшение связности модулей программы
Представим ситуацию что есть несколько заголовочников.
Файл
a.h содержит описание типов структуры sta_t и перечисления eta_t. Файл
b.h - stb_t и etb_t.Представим, что нам нужен ещё файл
c.h, в котором функция будет принимать указатели на sta_t и stb_t. Можно написать два инклюда a.h и b.h. Но тогда получается что файл который будет инклюдить c.h получает все зависимости всех файлов. Если у нас не только три файла, а большое дерево зависимостей, то в итоге весь код становиться просто спутанным клубком.
Для того что бы развязать можно использовать инклюды только в файлах
.c и ограничить их использование в .h, тогда будут появляться только нужные зависимости. 2. Ускорение сборки
Данный пункт напрямую вытекает из предыдущего. Объясняется это тем, что компилируемый файл имеет меньший размер, так как директива
include просто вставляет содержимое файла.В моём текущем проекте это позволило снизить размеры файлов после препроцессора с 4-6 мегабайт до 500-800 килобайт. Что в общем случае ускорило сборку проекта на 30%.
Ограничения
Для тог что бы это всё работало, компилятор должен смочь вывести размер сущности и то как её использовать. Для указателей (функции в том числе), стандартных типов, и перечислений (не всегда) это делается очень просто и очевидно.
Для структур не получится и в таком случае
include внутри хедера будет оправданPlease open Telegram to view this post
VIEW IN TELEGRAM
👍1
Каркас для прошивки: почему архитектура решает все
Запустить светодиод на микроконтроллере можно и простым скетчем. Но когда ваш проект превращается в сложное устройство с дисплеем, кнопками, датчиками, связью и логикой, код без продуманной структуры становится кошмаром. Он превращается в хрупкое полотно из тысяч строк, где изменение в одном месте ломает три других.
Решение - выбрать архитектурный подход, то есть фундаментальный принцип организации кода. Это не про библиотеки или конкретные функции. Это решение о том, как разные части программы будут общаться, в каком порядке выполняться и кто будет принимать решения.
Почему это критически важно?
Выбор архитектуры определяет:
🟡 Скорость реакции на события. Мгновенно или когда дойдут руки?
🟡 Предсказуемость. Можно ли точно рассчитать время отклика?
🟡 Стоимость железа. Будет ли достаточно дешевого 8-битного МК или нужен мощный ARM?
🟡 Сроки разработки и поддержки. Можно ли легко добавлять фичи через полгода или каждый раз всё переписывать?
🟡 Надежность. Насколько система устойчива к ошибкам и сложным внешним условиям?
Основных пути, о которых пойдет речь:
🔴 Суперлуп (Super Loop) - фундамент. Бесконечный цикл, где задачи выполняются строго по очереди. Простота, но беспощадная ограниченность.
🔴 Прерывания (Interrupt-Driven) - мир асинхронности. Позволяет мгновенно реагировать на внешние события, прорывая основной цикл.
🔴 Планировщик на таймере (Timer-Based Scheduler) - вводит порядок и периодичность. Шаг от хаоса к системе.
🔴 RTOS (Real-Time Operating System) - промышленная мощь. Полноценная многозадачность с приоритетами, синхронизацией и четким планированием.
🔴 Событийно-ориентированная архитектура (Event-Driven) - философия слабой связанности. Компоненты общаются через сообщения, не зная друг о друге.
Как выбирать?
🟢 Насколько сложна моя система? (одна задача vs. множество независимых процессов)
🟢 Критично ли время отклика? (мгновенно vs. в течение секунды)
🟢 Каковы ресурсы моего МК? (память, частота)
🟢 Будет ли проект масштабироваться?
🟢 Какой опыт у команды?
В следующих постах разберу каждый архитектурный паттерн в деталях: как он устроен изнутри, где сияет, а где терпит поражение, и для какого типа проектов создан.
Необходимо понимать, что выбор в начале пути сэкономит месяцы работы в будущем. Это решение, которое отделяет любительский проект от профессионального продукта.
#встраиваемыесистемы #архитектурапо #прошивка #embedded #разработка #микроконтроллеры #инженерия #впо
Запустить светодиод на микроконтроллере можно и простым скетчем. Но когда ваш проект превращается в сложное устройство с дисплеем, кнопками, датчиками, связью и логикой, код без продуманной структуры становится кошмаром. Он превращается в хрупкое полотно из тысяч строк, где изменение в одном месте ломает три других.
Решение - выбрать архитектурный подход, то есть фундаментальный принцип организации кода. Это не про библиотеки или конкретные функции. Это решение о том, как разные части программы будут общаться, в каком порядке выполняться и кто будет принимать решения.
Почему это критически важно?
Выбор архитектуры определяет:
Основных пути, о которых пойдет речь:
Как выбирать?
В следующих постах разберу каждый архитектурный паттерн в деталях: как он устроен изнутри, где сияет, а где терпит поражение, и для какого типа проектов создан.
Необходимо понимать, что выбор в начале пути сэкономит месяцы работы в будущем. Это решение, которое отделяет любительский проект от профессионального продукта.
#встраиваемыесистемы #архитектурапо #прошивка #embedded #разработка #микроконтроллеры #инженерия #впо
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Суперлуп: первый и самый простой каркас для прошивки
Представьте, что вы учитесь готовить. Вы не начинаете со сложного торта Прага. Вы начинаете с бутерброда: взял хлеб, намазал масло, положил колбасу. Суперлуп (Super Loop) - это тот самый бутерброд в мире встраиваемого ПО. Это фундамент, на котором всё начинается.
Технически, это бесконечный цикл while(1), в котором ваши функции (задачи) вызываются строго одна за другой, по кругу. Никакой магии, только четкая последовательность.
Как это работает на практике?
Весь код существует в одном потоке выполнения. Микроконтроллер последовательно переходит от одной функции к другой, и когда доходит до конца цикла - начинает сначала. Порядок выполнения жёстко прописан разработчиком.
Ключевой принцип: Синхронность. Система не реагирует на внешние события мгновенно, а обрабатывает их только когда дойдёт до соответствующей строки в цикле.
Когда выбирать Суперлуп?
🟡 Система тривиальна? (Мигание светодиодом, простейший термометр).
🟡 Задачи всегда выполняются быстро? (Ни одна функция не занимает больше пары миллисекунд).
🟡 Ресурсы микроконтроллера исчезающе малы? (Мало памяти, низкая частота).
🟡 Время отклика не критично? (Не страшно, если система отреагирует на кнопку через 100 мс, а не через 1 мс).
Идеальные кандидаты: Электронные таймеры, простые термостаты, мигалки, детские игрушки, системы с одним основным действием.
Сильные стороны:
➕ Предельная простота. Понять и написать может даже новичок. Это главный обучающий инструмент.
➕ Нулевые накладные расходы. Нет планировщика, переключения контекста, стеков для задач. Весь ресурс МК - на полезную работу.
➕ Абсолютная предсказуемость. Время отклика системы = сумме времен выполнения всех функций до нужной. Всё считается на бумажке.
➕ Никаких проблем синхронизации. Нет прерываний, нет разделяемых ресурсов между задачами - значит, нет гонок данных (race condition) и deadlock'ов.
➕ Полный контроль. Разработчик точно знает, что и в какой момент выполняется.
Слабые стороны:
➖ Катастрофическая отзывчивость. Если функция read_sensors() внезапно займет 500 мс на измерение, всё встанет. Нажатия кнопок не обработаются, дисплей не обновится.
➖ Спагетти-код. При добавлении новых функций код быстро превращается в запутанный ком, где всё зависит от всего. Сопровождать такой код - мучение.
➖ Неэффективное использование процессора. Пока система ждет события (например, нажатия кнопки), процессор бесполезно крутит пустой цикл, тратя энергию, вместо того чтобы уйти в режим сна.
➖ Нет реального параллелизма. Полная неспособность обрабатывать несколько внешних событий, требующих внимания одновременно.
➖ Жёсткая связанность. Изменение в одной функции часто влечёт за собой правки в других.
Суперцикл - это не плохая архитектура. Это правильная архитектура для очень простых задач. Она даёт бесценный опыт понимания работы МК на голом железе. Но как только ваши амбиции и требования к системе перерастают уровень мигающего светодиода, пора смотреть в сторону более мощных инструментов.
#суперлуп #superloop #архитектурапрошивки #embedded #встраиваемыесистемы #программированиемк #новичкам
Представьте, что вы учитесь готовить. Вы не начинаете со сложного торта Прага. Вы начинаете с бутерброда: взял хлеб, намазал масло, положил колбасу. Суперлуп (Super Loop) - это тот самый бутерброд в мире встраиваемого ПО. Это фундамент, на котором всё начинается.
Технически, это бесконечный цикл while(1), в котором ваши функции (задачи) вызываются строго одна за другой, по кругу. Никакой магии, только четкая последовательность.
while(1) // Вечный двигатель вашей прошивки
{
check_buttons(); // 1. Опрос кнопок
read_sensors(); // 2. Чтение датчиков
process_logic(); // 3. Вычисление логики
update_display();// 4. Вывод на экран
}
Как это работает на практике?
Весь код существует в одном потоке выполнения. Микроконтроллер последовательно переходит от одной функции к другой, и когда доходит до конца цикла - начинает сначала. Порядок выполнения жёстко прописан разработчиком.
Ключевой принцип: Синхронность. Система не реагирует на внешние события мгновенно, а обрабатывает их только когда дойдёт до соответствующей строки в цикле.
Когда выбирать Суперлуп?
Идеальные кандидаты: Электронные таймеры, простые термостаты, мигалки, детские игрушки, системы с одним основным действием.
Сильные стороны:
Слабые стороны:
Суперцикл - это не плохая архитектура. Это правильная архитектура для очень простых задач. Она даёт бесценный опыт понимания работы МК на голом железе. Но как только ваши амбиции и требования к системе перерастают уровень мигающего светодиода, пора смотреть в сторону более мощных инструментов.
#суперлуп #superloop #архитектурапрошивки #embedded #встраиваемыесистемы #программированиемк #новичкам
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Прерывания: как заставить систему реагировать мгновенно
Чтобы система реагировала на события мгновенно, переходим к архитектуре на основе Прерываний (Interrupt-Driven).
Основная логика переносится в обработчики прерываний (ISR), которые асинхронно вызываются аппаратурой микроконтроллера.
Суперлуп часто остается для фоновых, не срочных задач.
Как это работает на практике?
Аппаратура МК генерирует сигнал прерывания при событии. Процессор немедленно сохраняет контекст, выполняет ISR и возвращается к прерванному коду. Ключевой принцип - асинхронность и приоритет.
Когда выбирать архитектуру на прерываниях?
🟢 Требуется ли мгновенная реакция? (Нажатие кнопки, аварийный датчик)
🟢 Нужна ли максимальная энергоэффективность? (Устройство на батарейке)
🟢 Система в основном "спит и ждет" событий? (Пульт ДУ, датчик двери)
🟢 Готовы ли вы к сложной отладке асинхронного кода?
Идеальные кандидаты: Пульты, коммуникационные шлюзы (UART, SPI), системы аварийного останова.
Сильные стороны:
➕ Максимальная отзывчивость. Реакция за микросекунды
➕ Энергоэффективность. Основной цикл может спать (WFI)
➕ Реальная асинхронность. Система живет в ритме внешнего мира
➕ Четкое разделение. Код реакции изолирован в ISR
Слабые стороны:
😀 Кошмар отладки. Ошибки трудно воспроизвести
😀 Проблемы синхронизации. Общие данные требуют защиты
😀 Риск переполнения стека. Длинные или вложенные ISR опасны
😀 Непредсказуемость. При высокой нагрузке время отклика неопределенно
😀 "Размазанная" логика. Код разбросан по множеству ISR
Прерывания - мощнейший инструмент для создания отзывчивых и энергоэффективных систем, требующий высочайшей дисциплины и готовности к сложной отладке.
#прерывания #isr #архитектурапрошивки #embedded #встраиваемыесистемы #realtime
Чтобы система реагировала на события мгновенно, переходим к архитектуре на основе Прерываний (Interrupt-Driven).
Основная логика переносится в обработчики прерываний (ISR), которые асинхронно вызываются аппаратурой микроконтроллера.
Суперлуп часто остается для фоновых, не срочных задач.
volatile bool button_pressed = false;
void button_isr(void) {
button_pressed = true;
}
int main(void) {
setup_hardware();
enable_interrupts();
while(1) {
update_clock();
if(button_pressed) {
button_pressed = false;
handle_button_action();
}
sleep_mode();
}
}
Как это работает на практике?
Аппаратура МК генерирует сигнал прерывания при событии. Процессор немедленно сохраняет контекст, выполняет ISR и возвращается к прерванному коду. Ключевой принцип - асинхронность и приоритет.
Когда выбирать архитектуру на прерываниях?
Идеальные кандидаты: Пульты, коммуникационные шлюзы (UART, SPI), системы аварийного останова.
Сильные стороны:
Слабые стороны:
Прерывания - мощнейший инструмент для создания отзывчивых и энергоэффективных систем, требующий высочайшей дисциплины и готовности к сложной отладке.
#прерывания #isr #архитектурапрошивки #embedded #встраиваемыесистемы #realtime
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1