Черга (Queue) у FreeRTOS — це потокобезпечний (thread-safe) буфер типу FIFO (First-In, First-Out — першим прийшов, першим пішов). Вона слугує основним механізмом для передачі даних та синхронізації між різними задачами (Tasks) або між обробниками переривань (ISR) та задачами.
Копіювання за значенням (Pass by Value): Коли ви надсилаєте дані в чергу, вони фізично копіюються байт-за-байтом у буфер черги.
Блокування задач (Blocking):
Якщо задача намагається прочитати з порожньої черги, вона переходить у стан очікування (не споживає CPU) до появи даних або закінчення таймауту.
Якщо задача намагається записати у повну чергу, вона блокується до звільнення місця або закінчення таймауту.
Потокобезпечність: Кілька задач можуть одночасно читати та писати в одну чергу без ризику пошкодити дані (FreeRTOS внутрішньо використовує м'ютекси/критичні секції).
1. Створення та видалення черги
xQueueCreate Створює нову чергу і виділяє під неї пам'ять у купі (Heap).
C
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
uxQueueLength — максимальна кількість елементів у черзі.
uxItemSize — розмір одного елемента в байтах (зазвичай використовують sizeof(тип)).
Повертає: QueueHandle_t (вказівник на чергу) або NULL, якщо бракує пам'яті.
vQueueDelete Видаляє чергу та звільняє виділену під неї пам'ять.
C
void vQueueDelete(QueueHandle_t xQueue);
2. Відправка даних у чергу (Звичайний контекст задач)
xQueueSend / xQueueSendToBack Додає елемент у кінець черги (стандартний FIFO).
C
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
pvItemToQueue — вказівник на дані, які будуть скопійовані в чергу.
xTicksToWait — час очікування (у тіках FreeRTOS), якщо черга повна.
0 — не чекати взагалі.
pdMS_TO_TICKS(100) — чекати 100 мс.
portMAX_DELAY — чекати нескінченно, поки не з'явиться місце.
Повертає: pdPASS (успішно) або errQUEUE_FULL (черга повна).
xQueueSendToFront Додає елемент у початок черги (використовується для термінових/пріоритетних повідомлень).
C
BaseType_t xQueueSendToFront(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
xQueueOverwrite Записує дані у чергу розміром в 1 елемент. Якщо черга вже повна, вона перезаписує існуюче значення.
C
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue);
3. Читання та перегляд даних з черги
xQueueReceive Вичитати (злити) елемент з черги. Зчитаний елемент видаляється з черги.
C
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
pvBuffer — буфер (змінна), куди будуть скопійовані дані.
xTicksToWait — час очікування, якщо черга порожня.
Повертає: pdPASS або pdFAIL.
xQueuePeek Скопіювати елемент з черги без його видалення.
C
BaseType_t xQueuePeek(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
4. Функції для використання всередині переривань (ISR)
Функції із суфіксом FromISR є неблокуючими (не мають аргументу xTicksToWait) та безпечними для виклику в контексті переривань.
xQueueSendFromISR / xQueueSendToBackFromISR
C
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);
pxHigherPriorityTaskWoken — прапорець, який встановлюється у pdTRUE, якщо відправка розблокувала задачу з вищим пріоритетом. Якщо pdTRUE, наприкінці ISR викликають portYIELD_FROM_ISR().
xQueueSendToFrontFromISR — Додати в початок черги з ISR.
xQueueReceiveFromISR — Зчитати з черги з ISR.
5. Допоміжні функції стану черги
uxQueueMessagesWaiting — повертає кількість елементів, що зараз знаходяться в черзі.
uxQueueSpacesAvailable — повертає кількість вільних місць у черзі.
xQueueReset — повністю очищає чергу (повертає її до порожнього стану).
Цей виквал функції створює та закріплює нове завдання (task) в операційній системі реального часу FreeRTOS для мікроконтролерів ESP32. [1, 2]
Розбір параметрів xTaskCreatePinnedToCore:
c
xTaskCreatePinnedToCore(timer_task, "timer", 2048, NULL, 1, NULL, 0);
Будьте обачні, використовуючи код.
timer_task — вказівник на функцію (саме тіло задачі), яка буде виконуватись як потік.
"timer" — текстова назва задачі (використовується переважно для дебагу та моніторингу в FreeRTOS).
2048 — розмір стеку задачі в байтах (кількість пам'яті, що виділяється під локальні змінні та виклики функцій усередині timer_task).
NULL — вхідний параметр (pointer), який передається в функцію задачі (тут нічого не передається).
1 — пріоритет задачі (1 — низький пріоритет, що більше число, то вищий пріоритет).
NULL — дескриптор задачі (TaskHandle_t), куди можна зберегти посилання на створену задачу (тут не зберігається, бо передано NULL).
0 — номер ядра CPU, на якому закріплюється задача (0 — ядро 0, 1 — ядро 1, або tskNO_AFFINITY для виконання на будь-якому вільному ядрі). [1, 2, 3]
1. Створення та видалення задач
// Стандартне створення задачі (динамічна пам'ять)
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pvCreatedTask
);
// Створення задачі з прив'язкою до ядра (розширення ESP32)
BaseType_t xTaskCreatePinnedToCore(
TaskFunction_t pvTaskCode,
const char * const pcName,
const uint32_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pvCreatedTask,
const BaseType_t xCoreID
);
// Створення задачі зі статичним виділенням пам'яті
TaskHandle_t xTaskCreateStatic(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer
);
// Статичне створення з прив'язкою до ядра (розширення ESP32)
TaskHandle_t xTaskCreateStaticPinnedToCore(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer,
const BaseType_t xCoreID
);
// Створення задачі з обмеженням доступу до пам'яті (MPU)
BaseType_t xTaskCreateRestricted(
const TaskParameters_t * const pxTaskDefinition,
TaskHandle_t *pxCreatedTask
);
BaseType_t xTaskCreateRestrictedStatic(
const TaskParameters_t * const pxTaskDefinition,
TaskHandle_t *pxCreatedTask
);
// Видалення задачі (NULL — видалити поточну)
void vTaskDelete(TaskHandle_t xTaskToDelete);
2. Керування часом (затримки)
// Блокування задачі на вказану кількість тіків
void vTaskDelay(const TickType_t xTicksToDelay);
// Точна періодична затримка (старіша версія)
void vTaskDelayUntil(
TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement
);
// Точна періодична затримка (повертає pdTRUE, якщо затримка відбулась)
BaseType_t xTaskDelayUntil(
TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement
);
// Примусове виведення іншої задачі зі стану очікування (Blocked)
BaseType_t xTaskAbortDelay(TaskHandle_t xTask);
3. Керування виконанням та станами
// Призупинення задачі (NULL — призупинити поточну)
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
// Відновлення призупиненої задачі
void vTaskResume(TaskHandle_t xTaskToResume);
// Відновлення задачі з обробника переривання (ISR)
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume);
// Добровільна передача процесорного часу іншій задачі (макрос без параметрів)
void taskYIELD(void);
4. Пріоритети задач
// Отримання поточного пріоритету задачі
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask);
// Отримання пріоритету з переривання (ISR)
UBaseType_t uxTaskPriorityGetFromISR(const TaskHandle_t xTask);
// Зміна пріоритету задачі під час роботи
void vTaskPrioritySet(
TaskHandle_t xTask,
UBaseType_t uxNewPriority
);
5. Прямі сповіщення задач (Task Notifications)
// Надсилання сповіщення з вибором дії (eNotifyAction)
BaseType_t xTaskNotify(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction
);
BaseType_t xTaskNotifyFromISR(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken
);
// Надсилання сповіщення із записом попереднього значення у pulPreviousNotifyValue
BaseType_t xTaskNotifyAndQuery(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue
);
BaseType_t xTaskNotifyAndQueryFromISR(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue,
BaseType_t *pxHigherPriorityTaskWoken
);
// Спрощене сповіщення (інкремент лічильника задачі на 1)
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);
void vTaskNotifyGiveFromISR(
TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken
);
// Очікування сповіщення як лічильника (аналог семафора)
uint32_t ulTaskNotifyTake(
BaseType_t xClearCountOnExit,
TickType_t xTicksToWait
);
// Очікування сповіщення з бітовими масками
BaseType_t xTaskNotifyWait(
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait
);
// Скидання стану сповіщення (на "не отримано")
BaseType_t xTaskNotifyStateClear(TaskHandle_t xTask);
// Очищення конкретних бітів у значенні сповіщення
uint32_t ulTaskNotifyValueClear(
TaskHandle_t xTask,
uint32_t ulBitsToClear
);
6. Глобальне керування планувальником (Scheduler)
// Запуск та зупинка планувальника
void vTaskStartScheduler(void);
void vTaskEndScheduler(void);
// Призупинення та відновлення перемикання всіх задач
void vTaskSuspendAll(void);
BaseType_t xTaskResumeAll(void);
// Отримання поточного стану планувальника
BaseType_t xTaskGetSchedulerState(void);
// Коригування системних тіків після сну (Tickless Idle)
BaseType_t xTaskCatchUpTicks(TickType_t xTicksToCatchUp);
7. Інформація, діагностика та дебаг
// Отримання дескрипторів (TaskHandle_t)
TaskHandle_t xTaskGetCurrentTaskHandle(void);
TaskHandle_t xTaskGetCurrentTaskHandleForCPU(BaseType_t xCoreID);
TaskHandle_t xTaskGetIdleTaskHandle(void);
TaskHandle_t xTaskGetIdleTaskHandleForCPU(UBaseType_t cpuid);
TaskHandle_t xTaskGetHandle(const char *pcNameToQuery);
// Отримання імені, стану, ядра та кількості задач
char *pcTaskGetName(TaskHandle_t xTaskToQuery);
eTaskState eTaskGetState(TaskHandle_t xTask);
BaseType_t xTaskGetAffinity(TaskHandle_t xTask);
UBaseType_t uxTaskGetNumberOfTasks(void);
// Залишок вільного місця в стеку (мінімальний за весь час)
UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask);
configSTACK_DEPTH_TYPE uxTaskGetStackHighWaterMark2(TaskHandle_t xTask);
// Отримання системного часу (в тіках)
TickType_t xTaskGetTickCount(void);
TickType_t xTaskGetTickCountFromISR(void);
// Збір детальної інформації та статистики
void vTaskGetInfo(
TaskHandle_t xTask,
TaskStatus_t *pxTaskStatus,
BaseType_t xGetFreeStackSpace,
eTaskState eState
);
UBaseType_t uxTaskGetSystemState(
TaskStatus_t * const pxTaskStatusArray,
const UBaseType_t uxArraySize,
configRUN_TIME_COUNTER_TYPE * const pulTotalRunTime
);
// Формування текстових звітів у буфер pcWriteBuffer
void vTaskList(char *pcWriteBuffer);
void vTaskGetRunTimeStats(char *pcWriteBuffer);
8. Локальне сховище потоку (TLS) та теги
// Прив'язка власних даних (вказівників) до конкретної задачі
void vTaskSetThreadLocalStoragePointer(
TaskHandle_t xTaskToSet,
BaseType_t xIndex,
void *pvValue
);
void *pvTaskGetThreadLocalStoragePointer(
TaskHandle_t xTaskToQuery,
BaseType_t xIndex
);
// Теги для дебагу та трасування виконання
void vTaskSetApplicationTaskTag(
TaskHandle_t xTask,
TaskHookFunction_t pxHookFunction
);
TaskHookFunction_t xTaskGetApplicationTaskTag(TaskHandle_t xTask);
TaskHookFunction_t xTaskGetApplicationTaskTagFromISR(TaskHandle_t xTask);
передачі даних та синхронізації між задачами
Порівняння механізмів передачі даних
1. Черги (Queues) — універсальний спосіб
Черга працює за принципом FIFO (перший прийшов — перший пішов). Вона сама копіює дані в свою пам'ять, тому вихідну змінну можна безпечно змінювати одразу після відправки.
Створення: QueueHandle_t q = xQueueCreate(10, sizeof(int)); (черга на 10 елементів типу int).
Відправка: xQueueSend(q, &value, portMAX_DELAY); (з переривання — xQueueSendFromISR).
Отримання: xQueueReceive(q, &rx_value, portMAX_DELAY); (задача спить і не витрачає CPU, поки в черзі не з'являться дані).
Приклад 1: Передача структури даних між двома задачами
Цей шаблон використовують, коли одна задача збирає показники з датчиків, а інша відправляє їх по Wi-Fi або виводить на дисплей. Черга сама копіює всю структуру байт за байтом.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
// 1. Описуємо структуру даних, яку будемо передавати
typedef struct {
int sensor_id;
float temperature;
uint32_t timestamp;
} SensorData_t;
// Глобальний дескриптор черги
QueueHandle_t sensorQueue;
// Задача-відправник (читає датчик)
void sender_task(void *pvParameters) {
SensorData_t data_to_send;
int counter = 0;
while (1) {
data_to_send.sensor_id = 1;
data_to_send.temperature = 22.5 + (counter % 5);
data_to_send.timestamp = xTaskGetTickCount();
counter++;
// Відправляємо копію структури в кінець черги.
// Чекаємо максимум 100 мс, якщо черга переповнена.
if (xQueueSend(sensorQueue, &data_to_send, pdMS_TO_TICKS(100)) != pdPASS) {
printf("Помилка: черга переповнена!\n");
}
vTaskDelay(pdMS_TO_TICKS(1000)); // Пауза 1 секунда
}
}
// Задача-отримувач (обробляє дані)
void receiver_task(void *pvParameters) {
SensorData_t received_data;
while (1) {
// Задача переходить у режим сну (не навантажує CPU),
// поки в черзі не з'явиться хоча б один елемент (portMAX_DELAY).
if (xQueueReceive(sensorQueue, &received_data, portMAX_DELAY) == pdPASS) {
printf("Отримано [Датчик %d]: Темп = %.1f C, Час = %lu\n",
received_data.sensor_id,
received_data.temperature,
received_data.timestamp);
}
}
}
void app_main(void) { // В Arduino використовуйте setup()
// 2. Створюємо чергу на 5 елементів розміром із структуру SensorData_t
sensorQueue = xQueueCreate(5, sizeof(SensorData_t));
if (sensorQueue != NULL) {
xTaskCreatePinnedToCore(sender_task, "Sender", 2048, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(receiver_task, "Receiver", 2048, NULL, 2, NULL, 1);
}
}
Приклад 2: Передача даних з переривання (ISR) у задачу
У перериванні (наприклад, натискання кнопки або сигнал таймера) заборонено викликати звичайну xQueueSend(), оскільки переривання не може чекати. Потрібно використовувати спеціальну версію xQueueSendFromISR().
QueueHandle_t buttonQueue;
// Обробник апаратного переривання (наприклад, GPIO)
void IRAM_ATTR gpio_isr_handler(void *arg) {
uint32_t gpio_num = (uint32_t) arg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Безпечна відправка номера піна з переривання
xQueueSendFromISR(buttonQueue, &gpio_num, &xHigherPriorityTaskWoken);
// Якщо задача-отримувач має вищий пріоритет за поточну,
// перемикаємо контекст одразу після виходу з переривання.
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
// Задача, яка обробляє натискання кнопки
void button_task(void *pvParameters) {
uint32_t io_num;
while (1) {
if (xQueueReceive(buttonQueue, &io_num, portMAX_DELAY)) {
printf("Натиснуто кнопку на GPIO %lu\n", io_num);
}
}
}
Корисні функції для роботи з чергою
xQueueOverwrite(queue, &data) — перезаписує значення в черзі довжиною 1 елемент (ідеально для зберігання останнього актуального стану датчика, коли старі значення не важливі).
xQueuePeek(queue, &data, ticks) — читає перший елемент з черги, не видаляючи його звідти.
uxQueueMessagesWaiting(queue) — повертає кількість елементів, які зараз знаходяться в черзі.
xQueueReset(queue) — повністю очищає чергу від усіх накопичених даних.
2. Прямі сповіщення (Task Notifications) — найшвидший спосіб
Кожна задача у FreeRTOS вже має вбудоване 32-бітне значення сповіщення. Якщо вам потрібно передати лише одне число або сигнал, створювати чергу не потрібно.
Відправка значення 42 у задачу worker_handle:
c
xTaskNotify(worker_handle, 42, eSetValueWithOverwrite);
Будьте обачні, використовуючи код.
Отримання в задачі-отримувачі:
c
uint32_t received_val;
xTaskNotifyWait(0, ULONG_MAX, &received_val, portMAX_DELAY);
Будьте обачні, використовуючи код.
Прямі сповіщення (Task Notifications) — це вбудований у кожну задачу FreeRTOS 32-бітний регістр. На відміну від черг чи семафорів, вони не потребують створення окремих об'єктів у пам'яті та працюють на 45% швидше.
Обов'язкова умова для їх використання: задача-відправник повинна знати дескриптор (TaskHandle_t) задачі-отримувача.
Нижче наведено три найпоширеніші сценарії використання.
Приклад 1: Передача 32-бітного значення (заміна черги на 1 елемент)
Використовується, коли одна задача має передати іншій конкретне число (наприклад, результат вимірювання АЦП або код команди).
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// Дескриптор задачі-отримувача (обов'язковий для адресного звернення)
TaskHandle_t receiverTaskHandle = NULL;
void sender_task(void *pvParameters) {
uint32_t sensor_value = 100;
while (1) {
sensor_value += 10;
// Надсилаємо число безпосередньо в задачу receiverTaskHandle.
// eSetValueWithOverwrite перезапише старе значення, якщо задача ще не встигла його прочитати.
xTaskNotify(receiverTaskHandle, sensor_value, eSetValueWithOverwrite);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void receiver_task(void *pvParameters) {
uint32_t received_value = 0;
while (1) {
// Очікуємо на сповіщення (задача спить і не витрачає ресурси CPU).
// 0 — не очищати біти при вході.
// ULONG_MAX (0xFFFFFFFF) — обнулити значення після прочитання.
if (xTaskNotifyWait(0, ULONG_MAX, &received_value, portMAX_DELAY) == pdTRUE) {
printf("Отримано значення: %lu\n", received_value);
}
}
}
void app_main(void) {
// Спочатку створюємо задачу-отримувач і зберігаємо її дескриптор у receiverTaskHandle!
xTaskCreatePinnedToCore(receiver_task, "Receiver", 2048, NULL, 2, &receiverTaskHandle, 1);
// Потім створюємо задачу-відправник
xTaskCreatePinnedToCore(sender_task, "Sender", 2048, NULL, 2, NULL, 0);
}
Приклад 2: Швидкий сигнал із переривання (заміна бінарного семафора)
Ідеально підходить для пробудження задачі-обробника після спрацювання апаратного переривання (натискання кнопки, завершення прийому по UART/SPI).
TaskHandle_t buttonHandlerTask = NULL;
// Обробник апаратного переривання (ISR)
void IRAM_ATTR button_isr_handler(void *arg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Збільшуємо лічильник сповіщень задачі на 1
vTaskNotifyGiveFromISR(buttonHandlerTask, &xHigherPriorityTaskWoken);
// Якщо розбуджена задача має вищий пріоритет за поточну — перемикаємо контекст негайно
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
void button_task(void *pvParameters) {
while (1) {
// Чекаємо на сигнал від переривання.
// pdTRUE означає: скинути лічильник сповіщень в 0 після виходу (режим бінарного семафора).
// Якщо поставити pdFALSE — працюватиме як лічильний семафор (зменшуватиме на 1).
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
printf("Переривання оброблено в задачі!\n");
}
}
Приклад 3: Передача кількох подій через бітові прапорці (заміна Event Groups)
Оскільки сповіщення має розмір 32 біти, кожен біт можна використовувати як окремий прапорець події (наприклад, біт 0 — готовий Wi-Fi, біт 1 — прийшли дані з датчика).
#define EVENT_WIFI_READY (1 << 0) // 0x01
#define EVENT_SENSOR_READY (1 << 1) // 0x02
TaskHandle_t mainWorkerHandle = NULL;
// Десь у задачі Wi-Fi:
// Встановлюємо біт 0, не зачіпаючи інші біти (eSetBits)
xTaskNotify(mainWorkerHandle, EVENT_WIFI_READY, eSetBits);
// Десь у задачі датчика:
// Встановлюємо біт 1
xTaskNotify(mainWorkerHandle, EVENT_SENSOR_READY, eSetBits);
// У головній задачі mainWorkerHandle:
void worker_task(void *pvParameters) {
uint32_t ulNotifiedValue;
while (1) {
// Чекаємо на будь-який біт і очищаємо тільки ті біти, які спрацювали
xTaskNotifyWait(0, ULONG_MAX, &ulNotifiedValue, portMAX_DELAY);
if (ulNotifiedValue & EVENT_WIFI_READY) {
printf("Подія: Wi-Fi підключено!\n");
}
if (ulNotifiedValue & EVENT_SENSOR_READY) {
printf("Подія: Дані з датчика готові!\n");
}
}
}
Режими дії (eNotifyAction) для xTaskNotify()
eNoAction — просто розбудити задачу, не змінюючи її 32-бітне значення сповіщення.
eSetBits — виконати побітове АБО (|) нового значення з поточним (використовується для прапорців подій).
eIncrement — збільшити поточне значення сповіщення на 1 (аналог xTaskNotifyGive).
eSetValueWithOverwrite — записати нове число поверх старого, навіть якщо попереднє ще не прочитали.
eSetValueWithoutOverwrite — записати нове число лише тоді, коли попереднє вже прочитане (інакше функція поверне pdFAIL).
Якщо у вашому проєкті одна подія має розбудити одразу кілька різних задач, прямі сповіщення не підійдуть (вони працюють лише для одного отримувача). Чи потрібні приклади роботи з групами подій (Event Groups) або м'ютексами для таких випадків?
3. Буфери повідомлень (Message Buffers) — для пакетів різної довжини
Якщо черги вимагають фіксованого розміру елемента, то MessageBuffer дозволяє відправити спочатку рядок на 5 байт, а потім структуру на 100 байт.
Створення: MessageBufferHandle_t mb = xMessageBufferCreate(512); (буфер на 512 байт).
Відправка: xMessageBufferSend(mb, data_ptr, data_len, portMAX_DELAY);
Отримання: size_t len = xMessageBufferReceive(mb, rx_buf, sizeof(rx_buf), portMAX_DELAY);
4. Глобальна пам'ять + М'ютекс (Mutex) — для великих даних
Копіювати через чергу масив на кілька кілобайт повільно і затратно по пам'яті. У такому разі створюють глобальну структуру, а доступ до неї захищають м'ютексом.
Створення: SemaphoreHandle_t mutex = xSemaphoreCreateMutex();
Запис або читання даних:
c
if (xSemaphoreTake(mutex, portMAX_DELAY) == pdTRUE) {
shared_data.temperature =
25.4; // Безпечна робота з даними
xSemaphoreGive(mutex); // Обов'язково звільнити м'ютекс
}
Будьте обачні, використовуючи код.
5. Групи подій (Event Groups) — для синхронізації станів
Використовуються, коли одній задачі потрібно дочекатися настання одразу кількох умов від різних задач (наприклад: "підключено Wi-Fi" І "отримано час по NTP").
Встановлення біта: xEventGroupSetBits(eg, BIT_WIFI_CONNECTED);
Очікування бітів: xEventGroupWaitBits(eg, BIT_WIFI_CONNECTED | BIT_NTP_READY, pdTRUE, pdTRUE, portMAX_DELAY);
Які саме дані (одне число, структуру з датчиків чи масив байтів) і в якому напрямку (з переривання в задачу чи між двома задачами) ви плануєте передавати, щоб я навів готовий робочий приклад коду для вашого випадку?