Очереди и семафоры - это базовые функции, предоставляемые всеми операционными системами. Разработчики, впервые использующие FreeRTOS, применяют их, потому что они им знакомы. Однако в большинстве случаев прямые уведомления о задачах FreeRTOS обеспечивают более компактную и на 45% более быструю альтернативу семафорам, а буферы сообщений и потоковые буферы FreeRTOS обеспечивают более компактную и быструю альтернативу очередям.
Ричард Барри основал проект FreeRTOS в 2003 году, более десяти лет занимался разработкой и продвижением FreeRTOS в своей компании Real Time Engineers Ltd, а сейчас продолжает работать над FreeRTOS в составе большой команды в качестве главного инженера в Amazon Web Services.
Каждая задача RTOS имеет массив уведомлений о задачах. Каждое уведомление о задаче может находиться в одном из двух состояний: “ожидание” или “не ожидание”, а также имеет 32-битное значение уведомления. Константа configTASK_NOTIFICATION_ARRAY_ENTRIES в файле конфигурации FreeRTOS FreeRTOSConfig.h устанавливает количество индексов в массиве уведомлений о задачах. До версии FreeRTOS V10.4.0 задачи имели только одно уведомление, а не массив уведомлений.
Оповещения задач осуществляются через DMA следующим образом: задача RTOS вызывает передающую функцию, затем ждёт в блокированном состоянии (не потребляя время CPU), пока не получит оповещение о завершении передачи. Передача происходит под управлением DMA, а прерывание завершения DMA используется для оповещения задачи.
Для отправки оповещений используются API-вызовы группы TaskNotify или TaskNotifyGive. Оповещения остаются в состоянии ожидания, пока принимающая задача не вызовет их по TaskNotifyWait или TaskNotifyTake.
Прямое уведомление о задаче — это событие, отправляемое непосредственно задаче, а не косвенно через промежуточный объект, такой как очередь, группа событий или семафор. При отправке прямого уведомления о задаче состояние уведомления в принимающей задаче устанавливается в «ожидание». Точно так же, как задача может блокироваться на промежуточном объекте, таком как семафор, в ожидании доступности этого семафора, задача может блокироваться на уведомлении о задаче в ожидании перехода этого уведомления в состояние ожидания.
При отправке прямого уведомления о задаче в задачу можно дополнительно обновить его значение на приёме одним из следующих способов:
перезаписать значение независимо от того, прочитало ли его получающее задание;
перезаписать значение, но только если получающая задача прочитала перезаписываемое значение;
установить один или несколько битов в значении;
увеличить (добавить единицу) значение.
Вызов xTaskNotifyWait()/xTaskNotifyWaitIndexed() для считывания значения уведомления приводит к тому, что состояние уведомления становится равным не ожидающее. Состояние уведомления также можно явно установить равным не ожидающее с помощью вызова xTaskNotifyStateClear()/xTaskNotifyStateClearIndexed().
Примечание: каждое уведомление в массиве работает независимо - задача может быть заблокирована только одним уведомлением в массиве за раз и не будет разблокирована уведомлением, отправленным в любой другой индекс массива.
Функция уведомлений о задачах RTOS включена по умолчанию и может быть исключена из сборки (для экономии 5 байт на индекс массива для каждой задачи) путем установки configUSE_TASK_NOTIFICATIONS в значение 0 в FreeRTOSConfig.h.
ВАЖНОЕ ПРИМЕЧАНИЕ: для буферов потоков и сообщений FreeRTOS использует уведомление о задаче по индексу массива 0. Если требуется сохранить состояние уведомления о задаче при вызове функции API буфера потока или сообщения, следует использовать уведомление о задаче по индексу массива больше 0.
Гибкость уведомлений о задачах позволяет использовать их там, где в противном случае пришлось бы создавать отдельную очередь, двоичный семафор, счетный семафор или группу событий. Разблокировка задачи RTOS с помощью прямого уведомления выполняется на 45% быстрее и требует меньше оперативной памяти, чем разблокировка задачи с помощью промежуточного объекта.
Как и следовало ожидать, эти преимущества в производительности требуют некоторых ограничений в использовании:
уведомления о задачах RTOS можно использовать только в том случае, если существует только одна задача, которая может быть получателем события. Однако это условие выполняется в большинстве реальных случаев использования, например, при прерывании, разблокирующем задачу, которая будет обрабатывать данные, полученные в результате прерывания.
“В FreeRTOS, выпущенной в 2002 году, была добавлена функция семафоров за счёт реализации API семафоров в виде набора макросов, которые вызывают API очередей. Преимущество такого подхода заключалось в том, что функция семафоров была добавлена без увеличения размера кода (что было важно, когда объём флэш-памяти обычно был меньше, чем сегодня), но недостатком было то, что семафоры были нетипично тяжёлыми объектами, поскольку они наследовали все функции очереди.
Например, очереди по-настоящему учитывают потоки и приоритеты, включают в себя механизм событий и упорядоченные по приоритету списки задач, ожидающих отправки в очередь и получения из очереди. В некоторых случаях использования семафоров эта комплексная функциональность полезна, но в большинстве случаев она не требуется.
Поэтому, когда мы искали простой механизм событий для использования в библиотеках драйверов, мы решили не переписывать код семафоров, а создать новый примитив, специально предназначенный для этих наиболее распространённых случаев использования. Этот примитив - прямые уведомления о задачах - с этого момента будет называться просто уведомления“.
Большинство методов межзадачной связи проходят через промежуточные объекты, такие как очередь, семафор или группа событий. Отправляющая задача записывает данные в объект связи, а принимающая задача считывает данные из объекта связи. При использовании прямого уведомления о задаче, как следует из названия, отправляющая задача отправляет уведомление непосредственно принимающей задаче без использования промежуточного объекта.


Начиная с FreeRTOS V10.4.0, каждая задача имеет массив уведомлений. До этого у каждой задачи было по одному уведомлению. Каждое уведомление состоит из 32-битного значения и логического состояния, которые вместе занимают всего 5 байт оперативной памяти.
Точно так же, как задача может блокироваться на двоичном семафоре в ожидании, пока этот семафор станет «доступным», задача может блокироваться на уведомлении в ожидании, пока состояние этого уведомления «ожидающее». Точно так же, как задача может блокироваться на счётном семафоре в ожидании, пока счёт этого семафора ненулевой, задача может блокироваться на уведомлении в ожидании, пока значение этого уведомления ненулевое.
Узнать версию FreeRTOS можно по определениям, доступным в файле task.h. Они определяют версию ядра по следующим параметрам: tskKERNEL_VERSION_NUMBER (версия ядра), tskKERNEL_VERSION_MAJOR (основная версия), tskKERNEL_VERSION_MINOR (минорная версия), tskKERNEL_VERSION_BUILD (версия сборки).
#include "task.h"
/*-----------------------------------------------------------
* MACROS AND DEFINITIONS
*----------------------------------------------------------*/
#define tskKERNEL_VERSION_NUMBER "V10.4.3"
#define tskKERNEL_VERSION_MAJOR 10
#define tskKERNEL_VERSION_MINOR 4
#define tskKERNEL_VERSION_BUILD 3
task.h
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);
BaseType_t xTaskNotifyGiveIndexed(TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify);
xTaskNotifyGive() - это макрос, когда уведомление о задаче используется в качестве облегченной и более быстрой альтернативы двоичному коду или счетному семафору. Семафоры FreeRTOS задаются с помощью API-функции xSemaphoreGive(). xTaskNotifyGive() - это эквивалент, который использует одно из значений уведомления принимающей задачи RTOS вместо семафора.
xTaskNotifyGive() и xTaskNotifyGiveIndexed() - это эквивалентные макросы. Разница лишь в том, что xTaskNotifyGiveIndexed() может работать с любым уведомлением о задаче в массиве, а xTaskNotifyGive() всегда работает с уведомлением о задаче по индексу 0 в массиве.
Если значение уведомления о задаче используется в качестве эквивалента двоичного или счётного семафора, то задача, получающая уведомление, должна ожидать уведомления с помощью функции ulTaskNotifyTake() API.
Примечание: каждое уведомление в массиве работает независимо - задача может быть заблокирована только одним уведомлением в массиве и не будет разблокирована уведомлением, отправленным в любой другой индекс массива.
Функция xTaskNotifyGive() не должна вызываться из подпрограммы обработки прерываний. Вместо этого используйте vTaskNotifyGiveFromISR().
configUSE_TASK_NOTIFICATIONS должно быть равно 1 в FreeRTOSConfig.h (или не определён), чтобы эти макросы были доступны. Константа configTASK_NOTIFICATION_ARRAY_ENTRIES задает количество индексов в массиве уведомлений каждой задачи.
До FreeRTOS V10.4.0 у каждой задачи было одно «значение уведомления», и все функции API уведомлений задач работали с этим значением. Замена одного значения уведомления массивом значений уведомлений потребовала создания нового набора функций API, которые могли бы обращаться к конкретным уведомлениям в массиве. xTaskNotifyGive() — это исходная функция API, которая остаётся обратно совместимой, всегда работая со значением уведомления по индексу 0 в массиве. Вызов xTaskNotifyGive() эквивалентен вызову xTaskNotifyGiveIndexed() с параметром uxIndexToNotify, равным 0.
Параметры:
xTaskToNotify, дескриптор (заголовок) задачи, получающей уведомления. Данный дескриптор определяется при создании задачи с помощью xTaskCreate() параметром pxCreatedTask или возвращается при создании задачи с помощью xTaskCreateStatic(). Также заголовок может быть получен при вызове xTaskGetHandle(). Обработчик выполняемой в данный момент задачи RTOS возвращается функцией xTaskGetCurrentTaskHandle() API;
uxIndexToNotify, индекс в массиве значений уведомлений целевой задачи, на который должно быть отправлено уведомление. uxIndexToNotify должен быть меньше, чем configTASK_NOTIFICATION_ARRAY_ENTRIES. Функция xTaskNotifyGive() не имеет этого параметра и всегда отправляет уведомления на индекс 0.
task.h
void vTaskNotifyGiveFromISR(
TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken);
void vTaskNotifyGiveIndexedFromISR(
TaskHandle_t xTaskHandle,
UBaseType_t uxIndexToNotify,
BaseType_t *pxHigherPriorityTaskWoken);
Это версии xTaskNotifyGive() и xTaskNotifyGiveIndexed(), которые нужно использовать из подпрограмм обслуживания прерываний (ISR).
Параметры:
xTaskToNotify, дескриптор (заголовок) задачи, получающей уведомления. Данный дескриптор определяется при создании задачи с помощью xTaskCreate() параметром pxCreatedTask или возвращается при создании задачи с помощью xTaskCreateStatic(). Также заголовок может быть получен при вызове xTaskGetHandle(). Обработчик выполняемой в данный момент задачи RTOS возвращается функцией xTaskGetCurrentTaskHandle() API;
uxIndexToNotify, индекс в массиве значений уведомлений целевой задачи, на который должно быть отправлено уведомление. uxIndexToNotify должен быть меньше, чем configTASK_NOTIFICATION_ARRAY_ENTRIES. Функция vTaskNotifyGiveFromISR() не имеет этого параметра и всегда отправляет уведомления на индекс 0;
pxHigherPriorityTaskWoken: pxHigherPriorityTaskWoken должен быть инициализирован pdFALSE. vTaskNotifyGiveFromISR() установит pxHigherPriorityTaskWoken в значение pdTRUE, если отправка уведомления привела к разблокировке задачи, а у разблокированной задачи приоритет выше, чем у текущей запущенной задачи. Если vTaskNotifyGiveFromISR() устанавливает это значение в pdTRUE, то после этого перед завершением прерывания следует запросить переключение контекста - portYIELD_FROM_ISR(xHigherPriorityTaskWoken. pxHigherPriorityTaskWoken является необязательным параметром и может иметь значение NULL.
task.h
uint32_t ulTaskNotifyTake(
BaseType_t xClearCountOnExit,
TickType_t xTicksToWait
);
uint32_t ulTaskNotifyTakeIndexed(
UBaseType_t uxIndexToWaitOn,
BaseType_t xClearCountOnExit,
TickType_t xTicksToWait
);
Значение уведомления, напрямую поступающее в задачу может быть изменено принимающей задачей несколькими различными способами. Например, уведомление может перезаписать одно из значений или просто установить один или несколько битов в одном из значений уведомления в принимающей задаче.
ulTaskNotifyTake() является макросом, предназначенным для использования в качестве более быстрой и легковесной альтернативы двоичному коду или счетному семафору. Семафоры FreeRTOS берутся с помощью xSemaphoreTake(). Функция API ulTaskNotifyTake() это эквивалент, который использует значение уведомления вместо семафора.
ulTaskNotifyTake() и ulTaskNotifyTakeIndexed() являются эквивалентными макросами - с той лишь разницей, что ulTaskNotifyTakeIndexed() может работать с любым уведомлением о задаче внутри массива, а ulTaskNotifyTake() всегда работает с уведомлением о задаче с индексом массива 0.
Когда задача использует значение уведомления в качестве двоичного или счётного семафора, другие задачи и прерывания должны отправлять ей уведомления с помощью макроса xTaskNotifyGive() или функции xTaskNotify() с параметром eAction, установленным в eIncrement (эти два варианта эквивалентны).
ulTaskNotifyTake() может либо обнулить значение уведомления задачи при выходе, и в этом случае значение уведомления действует как двоичный семафор, либо уменьшить значение уведомления задачи при выходе, и в этом случае значение уведомления действует больше как счетный семафор.
Задача RTOS использует ulTaskNotifyTake() для блокирования в ожидании значения уведомления о задаче. Это действие не потребляет процессорного времени, пока задача находится в заблокированном состоянии.
Примечание: каждое уведомление в массиве работает независимо — задача может быть заблокирована только одним уведомлением в массиве и не будет разблокирована уведомлением, отправленным в любой другой индекс массива.
configUSE_TASK_NOTIFICATIONS должен быть равно 1 в FreeRTOSConfig.h (или не определён), чтобы эти макросы были доступны. Константа configTASK_NOTIFICATION_ARRAY_ENTRIES задает количество индексов в массиве уведомлений каждой задачи.
До FreeRTOS версии 10.4.0 каждая задача имела единственное “значение уведомления” и все функции API уведомлений о задачах работали с этим значением. Замена одного значения уведомления массивом значений уведомлений потребовала создания нового набора функций API, которые могли бы обращаться к конкретным уведомлениям внутри массива. ulTaskNotifyTake() остаётся обратно совместимой, всегда работая со значением уведомления по индексу 0 в массиве. Вызов ulTaskNotifyTake() эквивалентен вызову ulTaskNotifyTakeIndexed() с помощью uxIndexToWaitOn равным 0.
Параметры:
uxIndexToWaitOn - индекс в массиве значений уведомлений вызывающей задачи, по которому вызывающая задача будет ожидать ненулевого значения уведомления. uxIndexToWaitOn должен быть меньше, чем - configTASK_NOTIFICATION_ARRAY_ENTRIES. ulTaskNotifyTake() не имеет этого параметра и всегда ожидает уведомлений по индексу 0;
xClearCountOnExit - если получено уведомление о задаче RTOS и для параметра xClearCountOnExit установлено значение pdFALSE, то значение уведомления задачи RTOS уменьшается перед завершением работы ulTaskNotifyTake(). Это эквивалентно уменьшению значения счетного семафора при успешном вызове функции xSemaphoreTake().
Если получено уведомление о задаче RTOS и для параметра xClearCountOnExit установлено значение pdTRUE, то значение уведомления задачи RTOS сбрасывается на 0 перед завершением работы ulTaskNotifyTake(). Это эквивалентно тому, что значение двоичного семафора становится равным нулю (или пустым, или “недоступным”) после успешного вызова функции xSemaphoreTake();
Возврат:
Значение уведомления о задаче до его уменьшения или очистки.
В листинге ниже показано приложение с задачей, которая блокируется на уведомлении. Если бы задача блокировалась на семафоре, она бы вызывала функцию xSemaphoreTake() API, но поскольку задача использует уведомление, она вместо этого вызывает функцию ulTaskNotifyTake() API. Функция ulTaskNotifyTake() всегда использует уведомление с индексом 0. Рекомендуется использовать ulTaskNotifyTakeIndexed() вместо ulTaskNotifyTake(), чтобы использовать уведомление по любому конкретному индексу массива.
/** Arduino-Esp32-CAM *** NotificForSynchro.ino ***
*
* Использовать уведомления для синхронизации прерывания с задачей
* и выполнения некоторой обработки в блокированном состоянии
*
* v1.0.1, 09.02.2025 Автор: Труфанов В.Е.
* Copyright © 2025 tve Дата создания: 08.02.2025
*
* SETUP отработал!
* Задача запустилась 1 раз
* Задача получила уведомление!
* Задача запустилась 2 раз
* Задача получила уведомление!
* Задача запустилась 3 раз
* Задача получила уведомление!
* ...
**/
// Определяем заголовок для объекта таймерного прерывания
hw_timer_t *timer = NULL;
// Определяем заголовок задачи, играющей роль блокированных действий. Это
// пример функции передачи в универсальном периферийном драйвере. Задача RTOS
// вызовет функцию оповещения, а затем будет ожидать его в заблокированном
// состоянии (чтобы не использовать процессорное время) до тех пор, пока не
// будет получено уведомление о завершении передачи. Передача осуществляется с
// помощью DMA и в завершении прерывания также DMA используется для отправки
// уведомления задаче
static TaskHandle_t xTaskToNotify = NULL;
// ****************************************************************************
// * Инициировать приложение *
// ****************************************************************************
void setup()
{
Serial.begin(115200);
// Определяем задачу
xTaskCreatePinnedToCore (
vNotifiedTask, // название функции, которая будет запускаться, как задача
"Сообщение", // название задачи
1024, // размер стека в байтах
NULL, // указатель параметра, который будет передан задаче (NULL, если параметр не передаётся)
0, // приоритет задачи
&xTaskToNotify, // дескриптор (указатель или заголовок) на задачу
0 // ядро процессора, на котором требуется запустить задачу
);
// Создаём объект таймера, устанавливаем его частоту отсчёта (1Mhz)
timer = timerBegin(1000000);
// Подключаем функцию обработчика прерывания от таймера - onTimer
timerAttachInterrupt(timer, &onTimer);
// Настраиваем таймер: интервал перезапуска - 3 секунды (3000000 микросекунд),
// всегда повторяем перезапуск (третий параметр = true), неограниченное число
// раз (четвертый параметр = 0)
timerAlarm(timer, 3000000, true, 0);
Serial.println("SETUP отработал!");
}
// ****************************************************************************
// * Отправить уведомление задаче из прерывания *
// ****************************************************************************
void ARDUINO_ISR_ATTR onTimer()
{
// Параметр xHigherPriorityTaskWoken инициализируем значением pdFALSE,
// поскольку он будет установлен в значение pdTRUE внутри прерывания с
// помощью функции API. Ситуация будет безопасной для прерывания, если вызов
// функции API разблокирует задачу, имеющую более высокий приоритет, чем
// задача, которую прервала эта ISR
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
// Отправляем уведомление непосредственно задаче, которая выполняет
// некоторую обработку, связанную с этим прерыванием. В первом параметре
// указываем дескриптор задачи, которой отправляется уведомление
vTaskNotifyGiveFromISR(xTaskToNotify, &xHigherPriorityTaskWoken);
// Так как xHigherPriorityTaskWoken теперь pdTRUE, то вызов
// portYIELD_FROM_ISR() приведет к переключению контекста и это прерывание
// вернется непосредственно к разблокированной задаче
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// ****************************************************************************
// * Выполнить основной цикл приложения *
// ****************************************************************************
void loop()
{
delay(1300);
}
// ****************************************************************************
// * Задача, которая блокируется на уведомлении. Если бы задача блокировалась *
// * на семафоре, она бы вызывала функцию API xSemaphoreTake(), но поскольку *
// * задача использует уведомление, она вместо этого вызывает функцию API *
// * ulTaskNotifyTake(). Функция ulTaskNotifyTake() всегда использует *
// * уведомление с индексом 0. Чтобы использовать уведомление с любым *
// * конкретным индексом массива, используйте функцию *
// * ulTaskNotifyTakeIndexed() вместо ulTaskNotifyTake() *
// ****************************************************************************
int i=0;
static void vNotifiedTask(void *pvParameters)
{
for( ;; )
{
i++;
Serial.print("Задача запустилась "); Serial.print(i); Serial.println(" раз");
// Ждём получения уведомления, которое будет отправлено непосредственно в эту задачу.
// Для первого параметра задано значение false, что позволяет выполнить
// вызов, повторяющий поведение счетного семафора. Следует установить для
// параметра значение true для воспроизведения поведения двоичного семафора
// Второму параметру присвоено значение port MAX_DELAY, что заставляет задачу
// блокироваться на неопределенный срок в ожидании уведомления.
// Это сделано для упрощения примера, реальные приложения не должны
// блокироваться на неопределенный срок, поскольку это не даёт задаче
// восстанавливается после ошибок.
if (ulTaskNotifyTake(pdFALSE, portMAX_DELAY) != 0)
{
// Обрабатываем заданное событие, как только задача получит уведомление
DoSomething();
}
}
}
// ****************************************************************************
// * Обрабатываем событие, заблокированное уведомлением *
// ****************************************************************************
void DoSomething()
{
Serial.println("Задача получила уведомление!");
}
// ************************************************** NotificForSynchro.ino ***
task.h
BaseType_t xTaskNotify(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction
);
BaseType_t xTaskNotifyIndexed(
TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction
);
Функция xTaskNotify() используется для отправки события непосредственно в задачу RTOS и потенциальной разблокировки задачи, а также для дополнительного обновления одного из значений уведомления задачи-получателя одним из следующих способов: передачей 32-разрядного числа в значение уведомления, добавлением единицы (увеличения) к значению уведомления, манипуляцией одного или нескольких разрядов в значении уведомления, оставлением значения уведомления без изменений.
xTaskNotify() и xTaskNotifyIndexed() являются эквивалентными функциями с той лишь разницей, что xTaskNotifyIndexed() может работать с любым уведомлением о задаче в массиве, а xTaskNotify() всегда работает с уведомлением о задаче с индексом в массиве, равным 0.
Эти функции не должны вызываться из процедуры обслуживания прерываний (ISR). Вместо них следует использовать xTaskNotifyFromISR() и xTaskNotifyIndexedFromISR().
Параметры:
xTaskToNotify - дескриптор задачи RTOS, получающей уведомление. Дескриптор становится определенным, после создания задачи. Кроме этого получить дескриптор задачи можно по имени задачи с помощью функции xTaskGetHandle(). Функция API xTaskGetCurrentTaskHandle() возвращает дескриптор текущей выполняемой задачи RTOS;
uxIndexToNotify - индекс в массиве значений уведомлений целевой задачи, на который должно быть отправлено уведомление. Значение uxIndexToNotify должно быть меньше значения configTASK_NOTIFICATION_ARRAY_ENTRIES. Функция xTaskNotify() не имеет этого параметра и всегда отправляет уведомления с индексом 0;
ulValue - переданное значение уведомления целевой задачи, которое при приёме может быть изменено в соответствии со значением параметра eAction;
eAction - перечисляемый тип, который может принимать одно из значений, описанных ниже, для выполнения соответствующего действия:
eNoAction - целевая задача получает событие, но значение ее уведомления не обновляется. В этом случае ulValue не используется;
eSetBits - значение уведомления целевой задачи будет побитово объединено с ulValue. Например, если значение ulValue равно 0x01, то в значении уведомления целевой задачи будет установлен бит 0. Аналогично, если значение ulValue равно 0x04, то в значении уведомления целевой задачи будет установлен бит 2. Таким образом, механизм уведомлений задач RTOS можно использовать в качестве облегчённой альтернативы группе событий;
eIncrement - значение уведомления для целевой задачи будет увеличено на единицу, что сделает вызов функции xTaskNotify() эквивалентным вызову функции xTaskNotifyGive(). В этом случае ulValue не используется.
eSetValueWithOverwrite - значение уведомления для целевой задачи обязательно устанавливается равным ulValue. Таким образом, механизм уведомления о задачах RTOS используется в качестве упрощенной альтернативы xQueueOverwrite();
eSetValueWithoutOverwrite - если у целевой задачи еще нет ожидающего уведомления, то значения её уведомления будет установлено, как ulValue. Если у целевой задачи уже есть ожидающее уведомление, то значение уведомления не обновляется, поскольку это привело бы к перезаписи предыдущего значения до его использования. В этом случае вызов функции xTaskNotify() завершается ошибкой и возвращается pdFALSE.
Таким образом, механизм уведомления о задачах RTOS используется в качестве упрощенной альтернативы xQueueSend() для очереди длиной 1.
Возврат:
pdPASS возвращается во всех случаях, кроме случаев, когда для параметра eAction установлено значение eSetValueWithoutOverwrite, а значение уведомления целевой задачи не может быть обновлено, поскольку для целевой задачи уже было получено ожидающее уведомление.
task.h
BaseType_t xTaskNotifyFromISR(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken
);
BaseType_t xTaskNotifyIndexedFromISR(
TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken
);
Эти функции являются аналогами xTaskNotify() и xTaskNotifyIndexed(), но которые следует использовать в подпрограммах обслуживания прерываний (ISR). Все параметры xTaskNotifyFromISR, xTaskNotifyIndexedFromISR идентичны аналогам, но добавляется еще один параметр:
Возврат:
pdPASS возвращается во всех случаях, кроме тех, когда для eAction установлено значение eSetValueWithoutOverwrite, а значение уведомления целевой задачи не может быть обновлено, поскольку у целевой задачи уже есть ожидающее выполнения уведомление.
task.h
BaseType_t xTaskNotifyWait(
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait
);
BaseType_t xTaskNotifyWaitIndexed(
UBaseType_t uxIndexToWaitOn,
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait
);
Функция xTaskNotifyWait() ожидает, с необязательным таймаутом, получения уведомления от вызывающей задачи. Если принимающая задача RTOS уже была заблокирована в ожидании уведомления, то при получении ожидаемого уведомления принимающая задача RTOS будет выведена из заблокированного состояния, а уведомление удалено.
xTaskNotifyWait() и xTaskNotifyWaitIndexed() являются эквивалентными макросами с той лишь разницей, что xTaskNotifyWaitIndexed() может работать с любым уведомлением о задаче в массиве, а xTaskNotifyWait() всегда работает с уведомлением о задаче с индексом массива 0.
Параметры:
uxIndexToWaitOn - индекс в массиве значений уведомлений вызывающей задачи, по которому вызывающая задача будет ожидать получения уведомления;
ulBitsToClearOnEntry - вход в систему, все биты, заданные в ulBitsToClearOnEntry, будут удалены в значении уведомления вызывающей задачи при входе в функцию xTaskNotifyWait() (до того, как задача начнет ожидать нового уведомления) при условии, что уведомление еще не находится в ожидании при вызове функции xTaskNotifyWait().
Например, если ulBitsToClearOnEntry равен 0x01, то бит 0 значения уведомления задачи будет очищен при входе в функцию. Установка значения ulBitsToClearOnEntry в 0xffffffffff (ULONG_MAX) очистит все биты в значении уведомления о задаче, фактически установив значение равным 0;
Например, если значение ulBitsToClearOnExit равно 0x03, то биты 0 и 1 значения уведомления задачи будут удалены перед завершением работы функции. Установка значения ulBitsToClearOnExit равным 0xffffffff (ULONG_MAX) очистит все биты в значении уведомления о задаче, фактически установив значение равным 0;
pulNotificationValue используется для передачи значения уведомления задачи RTOS. Значение, скопированное в *pulNotificationValue, является значением уведомления задачи RTOS в том виде, в каком оно было до того, как какие-либо биты были удалены из-за установки ulBitsToClearOnExit. Если значение уведомления не требуется, то значение pulNotificationValue устанавливается равным NULL;
xTicksToWait - максимальное время ожидания получения уведомления в заблокированном состоянии, если уведомление еще не находится на рассмотрении при вызове функции xTaskNotifyWait(). Задача RTOS не потребляет процессорного времени, когда она находится в заблокированном состоянии. Время указано в тиках CPU. Макрос pdMS_TO_TICKS() может использоваться для преобразования времени, указанного в миллисекундах, во время, указанное в тиках.
Возврат:
pdTRUE, если уведомление было получено, или уведомление уже находилось на рассмотрении, когда была вызвана функция xTaskNotifyWait(). pdFALSE, если время ожидания вызова функции xTaskNotifyWait() истекло до получения уведомления.
В следующем примере использование уведомлений выходит за рамки простого повторения поведения семафора и демонстрирует, как отправлять данные с помощью уведомлений. Отправка данных требует минимальных дополнительных затрат.
В листинге показана структура функции, которая возвращает результат аналого-цифрового преобразования (АЦП). Задача, которая вызывает функцию, ожидает результат преобразования в заблокированном состоянии, поэтому она не расходует ресурсы процессора. Результат отправляется ей из подпрограммы обработки прерываний (ISR) в конце преобразования. В этом сценарии необходимо использовать несколько более сложные функции API xTaskNotify() и xTaskNotifyWait(). Как и прежде, xTaskNotify() и xTaskNotifyWait() работают с уведомлением по индексу 0 в массиве уведомлений. Используйте xTaskNotifyIndexed() и xTaskNotifyWaitIndexed() для работы с любым конкретным индексом в массиве.
/** Arduino-Esp32-CAM *** NotificValueFromISR.ino ***
*
* Использовать уведомления для отправки значения из ISR в задачу
*
* v1.0.0, 09.02.2025 Автор: Труфанов В.Е.
* Copyright © 2025 tve Дата создания: 09.02.2025
*
**/
// Определяем заголовок для объекта таймерного прерывания
hw_timer_t *timer = NULL;
// Определяем заголовок задачи, играющей роль блокированных действий. Это
// пример функции передачи в универсальном периферийном драйвере. Задача RTOS
// вызовет функцию оповещения, а затем будет ожидать его в заблокированном
// состоянии (чтобы не использовать процессорное время) до тех пор, пока не
// будет получено уведомление о завершении передачи. Передача осуществляется с
// помощью DMA и в завершении прерывания также DMA используется для отправки
// уведомления задаче
static TaskHandle_t xHandlingTask = NULL;
// ****************************************************************************
// * Инициировать приложение *
// ****************************************************************************
void setup()
{
Serial.begin(115200);
// Определяем задачу
xTaskCreatePinnedToCore (
vNotifiedTask, // название функции, которая будет запускаться, как задача
"Сообщение", // название задачи
1024, // размер стека в байтах
NULL, // указатель параметра, который будет передан задаче (NULL, если параметр не передаётся)
0, // приоритет задачи
&xHandlingTask, // дескриптор (указатель или заголовок) на задачу
0 // ядро процессора, на котором требуется запустить задачу
);
// Создаём объект таймера, устанавливаем его частоту отсчёта (1Mhz)
timer = timerBegin(1000000);
// Подключаем функцию обработчика прерывания от таймера - onTimer
timerAttachInterrupt(timer, &onTimer);
// Настраиваем таймер: интервал перезапуска - 3 секунды (3000000 микросекунд),
// всегда повторяем перезапуск (третий параметр = true), неограниченное число
// раз (четвертый параметр = 0)
timerAlarm(timer, 3000000, true, 0);
Serial.println("SETUP отработал!");
}
// ****************************************************************************
// * Отправить уведомление cо значением задаче из прерывания *
// ****************************************************************************
int i=0; // счётчик прерываний
void ARDUINO_ISR_ATTR onTimer()
{
// Резервируем переменную для значения
uint32_t ulStatusRegister;
// Резервируем регистратор приоритета разблокированной задачи
BaseType_t xHigherPriorityTaskWoken;
i++;
Serial.print("Прерывание сработало "); Serial.print(i); Serial.println(" раз");
// Готовим значение для передачи с оповещением
ulStatusRegister = i;
// Инициализируем xHigherPriorityTaskWoken значением false. При вызове
// функция xTaskNotifyFromISR() разблокирует задачу обработки, и если приоритет
// задачи обработки выше приоритета текущей запущенной задачи, то для функции
// xHigherPriorityTaskWoken автоматически будет установлено значение pdTRUE
xHigherPriorityTaskWoken = pdFALSE;
// Передаём сообщение в задачу обработки от прерывания. xHandlingTask - это дескриптор задачи,
// который был получен когда задача была создана
//xTaskNotifyIndexedFromISR(xHandlingTask,2,ulStatusRegister,eSetValueWithOverwrite,&xHigherPriorityTaskWoken);
xTaskNotifyFromISR(xHandlingTask,ulStatusRegister,eSetValueWithOverwrite,&xHigherPriorityTaskWoken);
// Принудительно переключаеме контекст на задачу xHandlingTask, так как теперь xHigherPriorityTaskWoken = pdTRUE
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
// ****************************************************************************
// * Выполнить основной цикл приложения *
// ****************************************************************************
void loop()
{
delay(1300);
}
// ****************************************************************************
// * Задача, которая блокируется на уведомлении. Если бы задача блокировалась *
// * на семафоре, она бы вызывала функцию API xSemaphoreTake(), но поскольку *
// * задача использует уведомление, она вместо этого вызывает функцию API *
// * ulTaskNotifyTake(). Функция ulTaskNotifyTake() всегда использует *
// * уведомление с индексом 0. Чтобы использовать уведомление с любым *
// * конкретным индексом массива, используйте функцию *
// * ulTaskNotifyTakeIndexed() вместо ulTaskNotifyTake() *
// ****************************************************************************
static void vNotifiedTask(void *pvParameters)
{
uint32_t ulInterruptStatus;
for( ;; )
{
// Ждём получения уведомления, которое будет отправлено непосредственно в эту задачу.
xTaskNotifyWait (
0,
0,
&ulInterruptStatus, // для приёма значения из прерывания
portMAX_DELAY // блокировка до приема на неопределённый срок
);
// Выполняем обработку уведомления
DoSomething(ulInterruptStatus);
}
}
// ****************************************************************************
// * Обрабатываем событие, заблокированное уведомлением *
// ****************************************************************************
void DoSomething(uint32_t value)
{
Serial.print("Задача получила уведомление со значением: ");
Serial.println(value);
}
// ************************************************ NotificValueFromISR.ino ***
FreeRTOS — это устоявшийся продукт, который развивался на протяжении почти двух десятилетий и продолжает развиваться, включая дополнительные функции, адаптированные к наиболее распространённым сценариям использования. Эти функции включают уведомления о задачах, буферы сообщений и потоковые буферы.
Разработчикам следует использовать эти адаптированные функции вместо более старых функций FreeRTOS, потому что они меньше по размеру и работают быстрее, но новые разработчики FreeRTOS часто упускают их из виду, потому что эти концепции не описаны в стандартных текстах по ОС.
Оригинальные функции FreeRTOS по-прежнему будут всегда доступны для покрытия всех вариантов использования, но в большинстве приложений использование комплексных функций, таких как очереди и семафоры, может быть скорее исключением, чем нормой.