Найти в Дзене
Что куда ставить
? Итак, у нас есть 5 основных архитектур. Как выбрать? Давайте сравним их по ключевым критериям. По простоте и порогу входа 🟢Суперлуп - начать может любой новичок 🟢Прерывания - требуется понимание работы МК 🟢Планировщик - нужны знания таймеров и прерываний 🟢RTOS - серьезный скачок в сложности 🟢Событийно-ориентированная - самый сложный для проектирования По отзывчивости и времени отклика 🟡Прерывания - абсолютный лидер (почти мгновенно) 🟡RTOS - отличная отзывчивость для приоритетных задач 🟡Событийно-ориентированная - зависит от реализации 🟡Планировщик - задержка до следующего "тика"...
4 недели назад
Событийно-ориентированная архитектура - общение сообщениями
Самый высокоуровневый подход - Событийно-ориентированная архитектура (Event-Driven Architecture, EDA). Здесь нет центрального цикла. Компоненты системы общаются через сообщения (события): одни модули их "издают" (publish), другие - "обрабатывают" (subscribe). Часто строится поверх RTOS или как большая state-машина. // Издатель события (например, драйвер кнопки) void button_isr(void) { event_t btn_event = { .type = EVENT_BUTTON_PRESSED, .data = 1 }; event_bus_publish(&btn_event); // Отправка в шину событий } // Подписчик события (обработчик интерфейса) void ui_handler(const event_t *event) {...
4 недели назад
RTOS - мощь многозадачности
Для сложных систем требуется полноценная многозадачность. Здесь на сцену выходит RTOS (Real-Time Operating System), например, FreeRTOS, Zephyr или Azure RTOS. Её ядро - это вытесняющий планировщик, который на основе приоритетов решает, какую задачу (поток) выполнять прямо сейчас. // Поток 1: Высокоприоритетный, обрабатывает прерывания void high_priority_task(void *params) { while(1) { xQueueReceive(irq_queue, &data, portMAX_DELAY); process_critical_data(data); } } // Поток 2: Средний приоритет, управление интерфейсом void ui_task(void *params) { while(1) { update_display(); vTaskDelay(pdMS_TO_TICKS(100));...
4 недели назад
Планировщик на таймере - порядок во времени
Следующий шаг к структурированию - Планировщик на таймере (Timer-Based Scheduler). Это эволюция суперлупа, где системный таймер периодически генерирует прерывание (тик), которое проверяет, каким задачам пора выполняться, и выставляет флаги. Основной цикл проверяет эти флаги и запускает соответствующие задачи. volatile uint32_t tick_count = 0; #define TASK1_PERIOD 100 // Выполнять каждые 100 мс #define TASK2_PERIOD 500 // Выполнять каждые 500 мс void systick_isr(void) { tick_count++; } int main(void) { uint32_t last_task1 = 0, last_task2 = 0; setup_timer(); enable_interrupts(); while(1) { uint32_t...
4 недели назад
Прерывания: как заставить систему реагировать мгновенно
Чтобы система реагировала на события мгновенно, переходим к архитектуре на основе Прерываний (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(); } } Как это работает на практике? Аппаратура МК генерирует сигнал прерывания при событии...
4 недели назад
Суперлуп: первый и самый простой каркас для прошивки
Представьте, что вы учитесь готовить. Вы не начинаете со сложного торта Прага. Вы начинаете с бутерброда: взял хлеб, намазал масло, положил колбасу. Суперлуп (Super Loop) - это тот самый бутерброд в мире встраиваемого ПО. Это фундамент, на котором всё начинается. Технически, это бесконечный цикл while(1), в котором ваши функции (задачи) вызываются строго одна за другой, по кругу. Никакой магии, только четкая последовательность. while(1) // Вечный двигатель вашей прошивки { check_buttons(); // 1. Опрос кнопок read_sensors(); // 2. Чтение датчиков process_logic(); // 3. Вычисление логики update_display();// 4...
4 недели назад
Каркас для прошивки: почему архитектура решает все
Запустить светодиод на микроконтроллере можно и простым скетчем. Но когда ваш проект превращается в сложное устройство с дисплеем, кнопками, датчиками, связью и логикой, код без продуманной структуры становится кошмаром. Он превращается в хрупкое полотно из тысяч строк, где изменение в одном месте ломает три других. Решение - выбрать архитектурный подход, то есть фундаментальный принцип организации кода. Это не про библиотеки или конкретные функции. Это решение о том, как разные части программы будут общаться, в каком порядке выполняться и кто будет принимать решения. Почему это критически важно? Выбор архитектуры определяет: 🟡Скорость реакции на события...
4 недели назад
👩‍💻👩‍💻👩‍💻 Современная среда разработки на языке C
👩‍💻👩‍💻👩‍💻 Современная среда разработки на языке C В этой статье рассмотривается, как настроить контейнерную среду разработки для проектов на языке C. Также настройку системы сборки с помощью CMake, среды тестирования с помощью Unity и даже то, как использовать контейнерную среду в конвейере непрерывной интеграции! Статья старая, но не бесполезная...
5 месяцев назад
https://youtu.be/F0Eh3XYMq7M?si=BTFAKFK0CYZqgtWz
5 месяцев назад
👩‍💻 Шина событий на костылях
👩‍💻 Шина событий на костылях. Реализация в FreeRTOS Паттерн "шина событий" (Event Bus) - это архитектурный шаблон, который позволяет компонентам системы взаимодействовать друг с другом, обмениваясь событиями, без необходимости явного знания о существовании друг друга. Казалось бы к чему это. Начнем рассказ с архитектуры ПО, которое работает на нашем видеопроцессоре. Существует тракт обработки видео и множество модулей, которые выполняют действия в какой-то момент обработки потока кадров: 🔴перед кадром 🔴после кадра 🔴во время обработки кадра 🔴после сжатия 🔴и других событиях. Для того,...
5 месяцев назад
👩‍💻 Switch statement с диапазонами
👩‍💻 Switch statement с диапазонами Очередное расширение 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: // ...
5 месяцев назад
👩‍💻 Перечисление (enum
👩‍💻 Перечисление (enum) Иногда вместе со списком элементов необходимо иметь их количество и значение последнего элемента. Подобное часто применяется для перечисления допустимых индексов каких-либо аппаратных блоков или каких-то статусов. Не редно видел конструкции вида: 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, }; На этапе разработки постоянно требуется меняеть количество этих элементов. Но в коде выше есть проблема: решили добавить дма - необходимо изменить ещё и ласт. Одна правка, а изменяет две строчки, что не только не красиво, но и потенциальная ошибка из-за невнимательности...
5 месяцев назад