1. Тема дня
Три главных механизма связи между задачами FreeRTOS:
Queue - передать данные
Event Group - сообщить набор флагов состояния
Task Notification - быстро разбудить конкретную task или передать 32-битный сигналГлавный вопрос: как задачам общаться без глобальных переменных, гонок, зависаний и потерь событий.
2. Зачем это нужно
Типовые потоки данных:
1. Входы светофора
input_task -> phase_task -> transport_task
2. Модем EC25
UART RX -> modem_task -> transport_task / health_task / CLI
3. Сетевые события
transport_task -> modem_task -> retry/ACK state
4. Диагностика
services -> health_task -> CLI
5. HIL-тестирование
Raspberry Pi -> UART CLI -> services -> ответы/логиГлобальные переменные:
extern bool g_modem_ready;
extern int g_current_phase;
extern bool g_send_now;
extern uint32_t g_error_flags;Поначалу удобны, но потом приводят к гонкам, потере событий и нестабильным HIL-тестам.
3. Шпаргалка выбора механизма
Нужно передать структуру данных? -> Queue
Нужно сообщить флаги состояния? -> Event Group
Нужно быстро разбудить одну task? -> Task Notification
Нужно защитить общий ресурс? -> Mutex
Нужно передавать поток байтов UART? -> Stream/ring buffer/UART driver buffer4. Queue
Queue передаёт сообщения. Примеры:
input_task -> input_snapshot_t -> phase_task
phase_task -> phase_event_t -> transport_task
transport_task -> modem_request_t -> modem_taskСтруктуры:
typedef struct {
uint32_t raw_mask;
int64_t timestamp_us;
} input_snapshot_t;
typedef struct {
uint32_t seq;
uint8_t old_phase;
uint8_t new_phase;
uint32_t raw_mask;
int64_t timestamp_us;
} phase_event_t;Очередь отвечает на вопрос: “какие именно данные произошли?”
5. Выбор длины queue
Не ставь длину “на глаз” слишком большой. Примерные стартовые значения:
input_queue length = 4..16
phase_event_queue length = 8..32
modem_request_queue = 8..32Маленькая очередь быстрее показывает перегрузку. Большая может скрыть проблему.
6. Что делать при переполнении
Для разных данных - разные политики:
Событие переключения фазы:
терять плохо, нужен counter drops и диагностика.
Периодическая телеметрия:
можно потерять старую, важнее свежая.
Текущее состояние модема:
можно хранить последнее значение.
CLI-ответ:
лучше вернуть BUSY или QUEUE_FULL.
UART RX от модема:
терять плохо, нужен нормальный RX buffer/parser.Код:
if (xQueueSend(queue, &event, 0) != pdTRUE) {
diag.queue_drops++;
}7. Event Group
Event Group - набор битов состояния:
bit 0: MODEM_AT_READY
bit 1: MODEM_REGISTERED
bit 2: PDP_ACTIVE
bit 3: MQTT_CONNECTED
bit 4: UDP_READY
bit 5: TIME_SYNCED
bit 6: INPUT_FAULT
bit 7: TRANSPORT_DEGRADEDПример:
#define MODEM_BIT_AT_READY (1 << 0)
#define MODEM_BIT_REGISTERED (1 << 1)
#define MODEM_BIT_PDP_ACTIVE (1 << 2)
#define MODEM_BIT_MQTT_CONNECTED (1 << 3)
#define MODEM_BIT_TIME_SYNCED (1 << 4)
static EventGroupHandle_t modem_event_group;Когда модем готов:
xEventGroupSetBits(modem_event_group, MODEM_BIT_AT_READY);Когда сеть потеряна:
xEventGroupClearBits(modem_event_group,
MODEM_BIT_REGISTERED |
MODEM_BIT_PDP_ACTIVE |
MODEM_BIT_MQTT_CONNECTED);Event group хорош для состояния, но плох для истории событий.
Сверить рассуждение о готовности флагов
Раскрыть решение
Исходно 00001111 AND 00001110 = 00001110 (14): результат совпадает с маской. После выключения бита 2 число становится 11; 11 AND 14 = 10. Флага MODEM_BIT_PDP_ACTIVE нет, поэтому готовности всех трёх флагов нет, хотя результат ненулевой. После выключения только бита 0 число равно 14; 14 AND 14 = 14. Бит MODEM_BIT_AT_READY не входит в выбранную маску и на этот критерий не влияет. Маска 14 — конкретный критерий этой практики, а не универсальная политика готовности модема.
8. Task Notification
Task Notification - прямой сигнал конкретной задаче.
static TaskHandle_t transport_task_handle;
xTaskNotifyGive(transport_task_handle);Получатель:
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);Полезно, если данные уже лежат где-то ещё, а notification нужен только как “звонок в дверь”. Можно использовать биты notification:
#define TRANSPORT_NOTIFY_NEW_EVENT (1 << 0)
#define TRANSPORT_NOTIFY_MODEM_UP (1 << 1)
#define TRANSPORT_NOTIFY_MODEM_DOWN (1 << 2)
#define TRANSPORT_NOTIFY_ACK_TIMEOUT (1 << 3)
xTaskNotify(transport_task_handle,
TRANSPORT_NOTIFY_NEW_EVENT,
eSetBits);9. Ограничения notification
Task notification удобны, но не заменяют queue.
Передать phase_event_t со всеми полями? -> Queue
Разбудить задачу? -> Task Notification
Передать битовые флаги одной задаче? -> Task Notification bits
Передать состояние, которое читают многие? -> Event Group10. Типичные ошибки
- использовать global variable вместо queue;
- использовать event group для событий с данными;
- игнорировать результат xQueueSend();
- блокировать быстрый путь через portMAX_DELAY;
- путать mutex и semaphore;
- делать одну queue для всего;
- заставлять одну task ждать слишком много разных объектов.
11. Практическое задание
Создай RTOS_COMMUNICATION.md:
# RTOS communication map
| From | To | Mechanism | Data | Overflow policy |
|---|---|---|---|---|
| input_task | phase_task | Queue | input_snapshot_t | drop newest + counter |
| phase_task | transport_task | Queue | phase_event_t | drop newest + critical counter |
| transport_task | modem_task | Queue | modem_request_t | reject low-priority telemetry first |
| modem_task | transport_task | Event Group / Notification | modem ready/lost flags | state only, no history |
| cli_task | services | direct API / Queue | command-specific | return BUSY if unavailable |
| services | health_task | counters / Event Group | fault bits | persistent until cleared |Опиши структуры сообщений:
typedef enum {
MODEM_REQ_MQTT_PUBLISH,
MODEM_REQ_UDP_SEND,
MODEM_REQ_TCP_SEND,
MODEM_REQ_RECONNECT,
MODEM_REQ_STATUS
} modem_request_type_t;
typedef struct {
modem_request_type_t type;
uint8_t priority;
uint32_t seq;
uint8_t payload[128];
uint16_t payload_len;
int64_t created_at_us;
} modem_request_t;Добавь counters переполнений.
12. Короткий итог
Queue -> данные
Event Group -> состояние
Task Notification -> быстрый wakeup
Mutex -> защита ресурсаБазовая схема:
input_task
-> input_snapshot_queue
-> phase_task
-> phase_event_queue
-> transport_task
-> modem_request_queue
-> modem_task / EC25Задание
Выберите механизм для четырёх случаев: история переключений фазы с timestamp; текущая готовность модема для нескольких читателей; пробуждение единственного потребителя уже заполненного буфера; защита общего ресурса. Укажите, что нельзя терять молча.
Критерии самопроверки: Назовите четыре разных механизма, место хранения payload уведомления и последствия переполнения.
Показать ответ автора
История фаз — Queue с payload и учётом переполнения. Общая готовность — Event Group. Пробуждение одного потребителя — Task Notification, а данные остаются в буфере с отдельным контрактом владения. Общий ресурс — Mutex в допустимом task-контексте. Переходы фаз и UART-байты нельзя терять молча: нужны политика, диагностика и обработка отказа.