Перейти к содержимому

FreeRTOS Task Notifications – легкий способ синхронизации задач

Метки:

Доброго здравия, уважаемые читатели!

Все, кто писал программы для FreeRTOSESP-IDF в частности), наверняка уже знакомы с объектами синхронизации задач: очередь, группа событий, мьютекс и т.д. Все эти объекты позволяют передавать в одну или несколько задач какие-то данные, события или сигналы управления.

Но FreeRTOS имеет ещё один механизм оповещения задач, который я и предлагаю вам обсудить. Этот механизм называется Task Notifications или [прямые] уведомления. Уведомления позволяют задаче FreeRTOS отправлять и получать сигналы без дополнительных затрат, связанных с традиционными механизмами синхронизации (очереди, группы событий или семафоры).

Это дает следующие преимущества:

  • Более быстрое выполнение по сравнению с очередями или семафорами.
  • Меньший объём занимаемой памяти, поскольку не требуется создавать в памяти дополнительные объекты.
  • Прямая связь между задачами и обработчиками прерываний.
  • Поддержка нескольких операций, таких как инкрементирование, установка битов или перезапись значений.

Разблокировка задачи FreeRTOS прямым оповещением работает на 45% быстрее, и использует при этом меньший объем RAM, чем разблокировка задачи на промежуточном объекте (например через двоичный семафор).

Как и следовало бы ожидать, этот выигрыш в производительности влечет за собой некоторые ограничения в использовании – они подходят только для простых событий и небольших данных. Также task notifications можно использовать, если только одна задача должна быть получателем события. Однако это условие выполняется во многих случаях, например как обработка сигналов от обработчиков прерывания.

Уведомления не подойдут вам, если нужно:

  • Передавать задаче большие данные и сложные структуры.
  • Передавать много элементов.
  • Иметь очередь сообщений из нескольких элементов.
  • Синхронизировать одновременно несколько задач.

Есть только один сценарий, когда вместо очереди может использоваться task notification: когда принимающая задача может ожидать оповещения в состоянии blocked (не расходуя время CPU), а отправляющая задача или прерывание может не ждать завершения отправки, если отправка оповещения не может быть завершена немедленно. 

 


Что это такое и как это работает

Каждая задача FreeRTOS имеет собственный массив notification values «значений уведомлений» (или просто «уведомлений»), каждое из которых представляет собой 32-битное беззнаковое целое число (uint32_t). Константа configTASK_NOTIFICATION_ARRAY_ENTRIES задает количество индексов в массиве и по умолчанию равна 1, если не определена. Сделано это для обратной совместимости, так как до версии FreeRTOS V10.4.0 для каждой задачи существовало только одно значение уведомления. [ESP32 FreeRTOS]

Механизм работы уведомлений следующий:

  • С каждым элементом массива notification values связан особый флаг pending (то есть «уведомление пришло»). Именно этот флаг и является основным механизмом разблокировки ожидающей уведомления задачи и её пробуждения из зимней спячки (не будите спящего медведя)
  • С помощью массива notification values (или одного значения) можно передавать некоторые данные задаче, но это не точно обязательно. В некоторых случаях значение – это только «дополнительная информация», которая может не иметь никакого значения (а может и иметь).
  • Отправить уведомление из другой задачи можно с помощью функций xTaskNotifyIndexed() (для любого индекса массива уведомлений)  или xTaskNotify() для нулевого индекса по умолчанию. Можно также воспользоваться упрощённым вариантомxTaskNotifyGiveIndexed() / xTaskNotifyGive(), который всегда делает eIncrement, и используется как быстрый счётный/бинарный семафор. Если необходимо отправить уведомление из обработчика прерываний, необходимо воспользоваться специальными версиями функций xTaskNotifyIndexedFromISR() / xTaskNotifyFromISR().
  • Целевая задача должна использовать функции xTaskNotifyWaitIndexed() или ulTaskNotifyWait()  для блокировки в состоянии ожидания уведомления. Кроме этого, можно использовать функции ulTaskNotifyTakeIndexed() или ulTaskNotifyTake() — ждать, пока значение уведомления станет ненулевым, и «забрать» его (аналог xSemaphoreTake() для счётного/бинарного семафора).  В состоянии «Заблокировано» целевая задача не потребляет процессорное время.
  • Уведомление, отправленное задаче, будет оставаться в состоянии ожидания pending до тех пор, пока задача не отменит его, вызвав указанные выше функции xTaskNotifyWaitIndexed() или ulTaskNotifyTakeIndexed() или их аналоги. Если задача находилась в состоянии «Заблокировано» и ожидала уведомления, то при его получении задача автоматически будет выведена из состояния «Заблокировано», а уведомление отменено.
  • Каждое уведомление в массиве работает независимо — задача может блокироваться только на одном уведомлении в массиве и не будет разблокирована уведомлением, отправленным на любой другой индекс массива. То есть если задача “слушает” канал 0, то уведомление в канале 1 её не разбудит. Задача-получатель может по очереди ждать разные индексы (в разные моменты времени), зная, что уведомления по другому индексу её не разблокируют.

Ещё раз иными словами: важен сам факт отправки уведомления, при котором отправляемое уведомление устанавливается в состояние pending. Значение notification value — «доп. информация» или счётчик или набор флагов, который задача может использовать, но не обязана.

Есть только один сценарий, когда вместо очереди может использоваться task notification: когда принимающая задача может ожидать оповещения в состоянии blocked (не расходуя время CPU), а отправляющая задача или прерывание может не ждать завершения отправки, если отправка оповещения не может быть завершена немедленно.

 

Уведомление, отправленное задаче, может дополнительно выполнять некоторые действия, например обновлять, перезаписывать или увеличивать одно из значений массива notification values:

  • eNoAction — задача получает уведомление, но значение уведомления не обновляется, ulValue не используется. В этом случае xTaskNotifyIndexed() или её аналог всегда возвращает pdPASS.
  • eSetBits — целевое значение уведомления побитово суммируется (ИЛИ) с указанным ulValue. В этом случаеxTaskNotifyIndexed()или её аналог всегда возвращает pdPASS.
  • eIncrement — целевое значение уведомления увеличивается на единицу, ulValue не используется. В этом случае xTaskNotifyIndexed() или её аналог всегда возвращает pdPASS. Это работает как счётный семафор.
  • eSetValueWithOverwrite — целевому значению уведомления присваивается значение ulValue, даже если задача, получающая уведомления, ещё не обработала предыдущее уведомление по тому же индексу массива (у задачи уже было ожидающее уведомления по этому индексу). В этом случае xTaskNotifyIndexed() или её аналог всегда возвращает pdPASS.
  • eSetValueWithoutOverwrite — если у задачи, получающей уведомление, ещё не было ожидающего уведомления по тому же индексу массива, то целевому значению уведомления присваивается ulValue, а функция xTaskNotifyIndexed() возвращает pdPASS. Если же у задачи, получающей уведомление, уже было ожидающее уведомление по тому же индексу массива, то никаких действий не выполняется и возвращается pdFAIL.

 

В каких случаях можно использовать Task Notifications

Из описания ESP-IDF/FreeRTOS можно выделить типичные сценарии применения API Task Notifications:

1. Замена бинарного/счётного семафора

Используйте функции xTaskNotifyGiveIndexed() или vTaskNotifyGiveIndexedFromISR() для отправки уведомления и ulTaskNotifyTakeIndexed() для ожидания уведомления в целевой задаче.

Это быстрее и легче, чем создавать отдельный объект семафора: нет отдельной структуры, всё хранится в TCB задачи.

2. Сигнал «событие произошло» от обработчика прерываний ISR к задаче

В этом случае обработчик ISR вызывает функции xTaskNotifyIndexedFromISR() или vTaskNotifyGiveIndexedFromISR(). Задача блокируется на xTaskNotifyWaitIndexed() или ulTaskNotifyTakeIndexed().

Это типичный паттерн «ISR → worker task» без использования очереди.

3. Передача небольших данных или флагов

С помощью функции xTaskNotifyIndexed() с опцией eSetBits можно использовать notification value как набор битовых флагов событий. Либо с опцией eSetValueWithOverwrite / eSetValueWithoutOverwrite можно передавать одно 32‑битное значение (например, код события или счётчик).

Когда нужно иметь несколько независимых «каналов» уведомлений на задачу

За счёт массива notification values (при configTASK_NOTIFICATION_ARRAY_ENTRIES > 1) каждая задача может иметь несколько независимых «встроенных семафоров/флагов» с доступом по индексам. Но задача может блокироваться только на одном индексе одновременно; уведомления по другим индексам её не разблокируют.

 

Когда лучше не использовать Task Notifications

В документации прямо не перечислены «анти‑кейсы», но по описанию видно, что Task Notifications подходят для простых событий и небольших данных. Если нужно:

  • передавать много элементов,
  • иметь очередь сообщений между несколькими задачами одновременно,
  • хранить сложные структуры или данные длиннее 32 бит,

то лучше использовать обычные очереди, группы событий или другие объекты синхронизации.

Дополнительная информация в справочной системе Espressif: ESP32 FreeRTOS. А мы переходим к практическим упражнениям.

 


Краткий справочник по Task Notification API

Все функции, перечисленные ниже, имеются в двух вариантах: индексном (Indexed) и безиндексном. Безиндексный вариант оставлен для совместимости со старыми версиями FreeRTOS и всегда работает с индексом 0.

Функции отправки уведомления из внешней задачи или ISR

xTaskNotify() / xTaskNotifyIndexed()

Базовая функция xTaskNotifyIndexed() для отправки уведомлений по заданному индексу массива notification values (на самом деле это макрос). xTaskNotify() – исходная версия той же самой функции, которая всегда работает с индексом 0. Вызов xTaskNotify() эквивалентен вызову xTaskNotifyIndexed() с параметром uxIndexToNotify, равным 0.

xTaskNotifyIndexed(xTaskToNotify, uxIndexToNotify, ulValue, eAction)
xTaskNotify(xTaskToNotify, ulValue, eAction)

Параметры:

  • xTaskToNotify — дескриптор задачи, о которой нужно сообщить. Дескриптор задачи можно получить с помощью функции API xTaskCreate(), которая используется для создания задачи, а дескриптор текущей задачи можно получить с помощью вызова xTaskGetCurrentTaskHandle().
  • uxIndexToNotify — индекс в массиве значений уведомлений целевой задачи, которому должно быть отправлено уведомление. Значение uxIndexToNotify должно быть меньше, чем configTASK_NOTIFICATION_ARRAY_ENTRIES. Функция xTaskNotify() не имеет этого параметра и всегда отправляет уведомления по индексу 0.
  • ulValue — данные, которые можно отправить вместе с уведомлением. Способ использования данных зависит от значения следующего параметра eAction.
  • eAction — указывает, как уведомление обновляет значение уведомления задачи, если вообще обновляет.

Возвращаемое значение зависит от значения eAction, см. описание выше.

 

xTaskNotifyGive() / xTaskNotifyGiveIndexed()

Отправляет прямое уведомление задаче аналогично использованию лёгкого бинарного или счётного семафора. Задача, получающая уведомление, должна ожидать его с помощью функции API ulTaskNotifyTakeIndexed(), а не функции API xTaskNotifyWaitIndexed(). Вызов xTaskNotifyGive() эквивалентен вызову xTaskNotifyGiveIndexed() с параметром uxIndexToNotify, равным 0.

xTaskNotifyGiveIndexed(xTaskToNotify, uxIndexToNotify)
xTaskNotifyGive(xTaskToNotify)

Параметры:

  • xTaskToNotify — дескриптор задачи, о которой нужно сообщить. Дескриптор задачи можно получить с помощью функции API xTaskCreate(), которая используется для создания задачи, а дескриптор текущей задачи можно получить с помощью вызова xTaskGetCurrentTaskHandle().
  • uxIndexToNotify — индекс в массиве значений уведомлений целевой задачи, которому должно быть отправлено уведомление. Значение uxIndexToNotify должно быть меньше, чем configTASK_NOTIFICATION_ARRAY_ENTRIES. Функция xTaskNotify() не имеет этого параметра и всегда отправляет уведомления по индексу 0.

xTaskNotifyGive() — это макрос, который вызывает xTaskNotify() с параметром eAction = eIncrement, поэтому всегда возвращается pdPASS.

 

xTaskNotifyAndQuery() / xTaskNotifyAndQueryIndexed()

Функция xTaskNotifyAndQueryIndexed() выполняет ту же операцию, что и xTaskNotifyIndexed(), но с дополнительным параметром pulPreviousNotifyValue, в котором возвращается предыдущее значение уведомления для задачи (значение уведомления на момент вызова функции, а не на момент её возврата). Это можно быть удобно для получения значения счетчика.

xTaskNotifyAndQueryIndexed(xTaskToNotify, uxIndexToNotify, ulValue, eAction, pulPreviousNotifyValue)
xTaskNotifyAndQuery(xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue)

Параметры:

  • xTaskToNotify — дескриптор задачи, о которой нужно сообщить. Дескриптор задачи можно получить с помощью функции API xTaskCreate(), которая используется для создания задачи, а дескриптор текущей задачи можно получить с помощью вызова xTaskGetCurrentTaskHandle().
  • uxIndexToNotify — индекс в массиве значений уведомлений целевой задачи, которому должно быть отправлено уведомление. Значение uxIndexToNotify должно быть меньше, чем configTASK_NOTIFICATION_ARRAY_ENTRIES. Функция xTaskNotifyAndQuery() не имеет этого параметра и всегда отправляет уведомления по индексу 0.
  • ulValue — данные, которые можно отправить вместе с уведомлением. Способ использования данных зависит от значения следующего параметра eAction.
  • eAction — указывает, как уведомление обновляет значение уведомления задачи, если вообще обновляет.
  • pulPreviousNotifyValue — значение уведомления, которое было сохранено в массиве на момент вызова данной функции.

Возвращаемое значение зависит от значения eAction, см. описание выше.

 

xTaskNotifyFromISR() / xTaskNotifyIndexedFromISR()

Функция xTaskNotifyIndexedFromISR() и её безиндексный аналог xTaskNotifyFromISR() также используются для отправки уведомлений, но уже из обработчиков прерываний ISR.

xTaskNotifyIndexedFromISR(xTaskToNotify, uxIndexToNotify, ulValue, eAction, pxHigherPriorityTaskWoken)
xTaskNotifyFromISR(xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken)

Параметры:

  • xTaskToNotify — дескриптор задачи, о которой нужно сообщить. Дескриптор задачи можно получить с помощью функции API xTaskCreate(), которая используется для создания задачи, а дескриптор текущей задачи можно получить с помощью вызова xTaskGetCurrentTaskHandle().
  • uxIndexToNotify — индекс в массиве значений уведомлений целевой задачи, которому должно быть отправлено уведомление. Значение uxIndexToNotify должно быть меньше, чем configTASK_NOTIFICATION_ARRAY_ENTRIES. Функция xTaskNotify() не имеет этого параметра и всегда отправляет уведомления по индексу 0.
  • ulValue — данные, которые можно отправить вместе с уведомлением. Способ использования данных зависит от значения следующего параметра eAction.
  • eAction — указывает, как уведомление обновляет значение уведомления задачи, если вообще обновляет.
  • pxHigherPriorityTaskWoken — функция установит для *pxHigherPriorityTaskWoken значение pdTRUE, если отправка уведомления привела к тому, что задача, которой было отправлено уведомление, вышла из состояния блокировки, и её приоритет выше, чем у выполняющейся в данный момент задачи. Если xTaskNotifyFromISR() устанавливает для этого значения значение pdTRUE, то перед выходом из прерывания необходимо запросить переключение контекста для досрочной передачи управления ожидающей задаче.

Возвращаемое значение зависит от значения eAction, см. описание выше.

 

xTaskNotifyAndQueryFromISR() / xTaskNotifyAndQueryIndexedFromISR()

Функция xTaskNotifyAndQueryIndexedFromISR() выполняет ту же операцию, что и xTaskNotifyIndexedFromISR(), но с дополнительным параметром pulPreviousNotifyValue, в котором возвращается предыдущее значение уведомления для задачи (значение уведомления на момент вызова функции, а не на момент её возврата). Это можно быть удобно для получения значения счетчика.

xTaskNotifyAndQueryIndexedFromISR(xTaskToNotify, uxIndexToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken)
xTaskNotifyAndQueryFromISR(xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken)

Параметры:

  • xTaskToNotify — дескриптор задачи, о которой нужно сообщить. Дескриптор задачи можно получить с помощью функции API xTaskCreate(), которая используется для создания задачи, а дескриптор текущей задачи можно получить с помощью вызова xTaskGetCurrentTaskHandle().
  • uxIndexToNotify — индекс в массиве значений уведомлений целевой задачи, которому должно быть отправлено уведомление. Значение uxIndexToNotify должно быть меньше, чем configTASK_NOTIFICATION_ARRAY_ENTRIES. Функция xTaskNotify() не имеет этого параметра и всегда отправляет уведомления по индексу 0.
  • ulValue — данные, которые можно отправить вместе с уведомлением. Способ использования данных зависит от значения следующего параметра eAction.
  • eAction — указывает, как уведомление обновляет значение уведомления задачи, если вообще обновляет.
  • pulPreviousNotifyValue — значение уведомления, которое было сохранено в массиве на момент вызова данной функции.
  • pxHigherPriorityTaskWoken — функция установит для *pxHigherPriorityTaskWoken значение pdTRUE, если отправка уведомления привела к тому, что задача, которой было отправлено уведомление, вышла из состояния блокировки, и её приоритет выше, чем у выполняющейся в данный момент задачи. Если xTaskNotifyFromISR() устанавливает для этого значения значение pdTRUE, то перед выходом из прерывания необходимо запросить переключение контекста для досрочной передачи управления ожидающей задаче.

Возвращаемое значение зависит от значения eAction, см. описание выше.

 

Функции ожидания и получения уведомлений в целевой задаче

xTaskNotifyWait() / xTaskNotifyWaitIndexed()

Задача может использовать функцию xTaskNotifyWaitIndexed() для блокировки в ожидании уведомления вне зависимости от значения уведомления. Задача может использовать полученное значение, а может и не использовать.

xTaskNotifyWaitIndexed(uxIndexToWaitOn, ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait)
xTaskNotifyWait(ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait)

Параметры:

  • uxIndexToWaitOn — индекс в массиве значений уведомлений вызывающей задачи, по которому вызывающая задача будет ожидать получения уведомления. uxIndexToWaitOn должен быть меньше, чем configTASK_NOTIFICATION_ARRAY_ENTRIESxTaskNotifyWait() не имеет этого параметра и всегда ожидает получения уведомлений по индексу 0.
  • ulBitsToClearOnEntry — биты, установленные в значении ulBitsToClearOnEntry, будут сброшены в значении уведомления вызывающей задачи до того, как задача проверит наличие ожидающих уведомлений и, при необходимости, заблокируется, если ожидающих уведомлений нет. Установка значения ulBitsToClearOnEntry в ULONG_MAX или 0xffffffffUL приведет к сбросу значения уведомления задачи до 0. Установка ulBitsToClearOnEntry в 0 не изменит значение уведомления задачи.
  • ulBitsToClearOnExit — Если уведомление ожидает обработки или было получено до того, как вызывающая задача завершила работу функции xTaskNotifyWait(), то значение уведомления задачи (см. функцию API xTaskNotify()) передается с помощью параметра *pulNotificationValue. Затем все биты, установленные в ulBitsToClearOnExit, будут сброшены в значении уведомления задачи (обратите внимание, что значение *pulNotificationValue устанавливается до сброса битов). Установка ulBitsToClearOnExit в значение ULONG_MAX или 0xffffffffUL приведет к сбросу значения уведомления задачи до 0 перед выходом из функции. Установка ulBitsToClearOnExit в значение 0 оставит значение уведомления задачи неизменным при выходе из функции (в этом случае значение, переданное в pulNotificationValue, будет соответствовать значению уведомления задачи).
  • pulNotificationValue — используется для передачи значения уведомления о задаче за пределы функции. Обратите внимание, что на передаваемое значение не повлияет очистка каких-либо битов, вызванная ненулевым значением ulBitsToClearOnExit.
  • xTicksToWait — максимальное время, в течение которого задача должна находиться в заблокированном состоянии в ожидании уведомления, если на момент вызова xTaskNotifyWait() уведомление ещё не было ожидающим. Пока задача находится в заблокированном состоянии, она не расходует процессорное время. Это значение указывается в тиках ядра. Для преобразования времени, указанного в миллисекундах, во время, указанное в тиках, можно использовать макрос pdMS_TO_TICKS(value_in_ms).

Возвращаемое значение: Если уведомление было получено (включая уведомления, которые уже находились в очереди на момент вызова xTaskNotifyWait(), возвращается значение pdPASS. В противном случае возвращается значение pdFAIL.

 

ulTaskNotifyTake() / ulTaskNotifyTakeIndexed()

Функция ulTaskNotifyTakeIndexed() предназначена для использования в тех случаях, когда уведомление о задаче используется в качестве более быстрой и лёгкой альтернативы двоичному семафору или счётному семафору.

ulTaskNotifyTakeIndexed(uxIndexToWaitOn, xClearCountOnExit, xTicksToWait)
ulTaskNotifyTake(xClearCountOnExit, xTicksToWait)

Параметры:

  • uxIndexToWaitOn — индекс в массиве значений уведомлений вызывающей задачи, по которому вызывающая задача будет ожидать получения уведомления. uxIndexToWaitOn должен быть меньше, чем configTASK_NOTIFICATION_ARRAY_ENTRIESxTaskNotifyTake() не имеет этого параметра и всегда ожидает получения уведомлений по индексу 0.
  • xClearCountOnExit — если xClearCountOnExit имеет значение pdFALSE, то при выходе из функции значение уведомления задачи уменьшается на единицу – таким образом, значение уведомления действует как счётный семафор. Если xClearCountOnExit имеет значение, отличное от pdFALSE, то при выходе из функции значение уведомления задачи обнуляется – таким образом, значение уведомления действует как двоичный семафор.
  • xTicksToWait — максимальное время, в течение которого задача должна находиться в заблокированном состоянии в ожидании уведомления, если на момент вызова xTaskNotifyWait() уведомление ещё не было ожидающим. Пока задача находится в заблокированном состоянии, она не расходует процессорное время. Это значение указывается в тиках ядра. Для преобразования времени, указанного в миллисекундах, во время, указанное в тиках, можно использовать макрос pdMS_TO_TICKS(value_in_ms).
Возвращает количество уведомлений до того, как оно будет обнулено или уменьшено (см. параметр xClearCountOnExit).
 

Функции очистки состояния или значения уведомления

xTaskNotifyStateClear() / xTaskNotifyStateClearIndexed()

xTaskNotifyStateClearIndexed() — это функция, которая удаляет ожидающее уведомление без считывания его значения. Значение уведомления по тому же индексу массива не изменяется. Установите xTask равным NULL, чтобы очистить состояние уведомления вызывающей задачи.

xTaskNotifyStateClearIndexed(xTask, uxIndexToClear)
xTaskNotifyStateClear(xTask)

Параметры:

  • xTask — дескриптор задачи RTOS, для которой будет очищено состояние уведомления. Установите для xTask значение NULL, чтобы очистить состояние уведомления в вызывающей задаче. Чтобы получить дескриптор задачи, создайте задачу с помощью xTaskCreate() и используйте параметр pxCreatedTask, или создайте задачу с помощью xTaskCreateStatic() и сохраните возвращаемое значение, или используйте имя задачи в вызове xTaskGetHandle().
  • uxIndexToClear — индекс в массиве значений уведомлений целевой задачи, на который нужно воздействовать. Например, если установить для uxIndexToClear значение 1, будет очищено состояние уведомления с индексом 1 в массиве. Значение uxIndexToClear должно быть меньше configTASK_NOTIFICATION_ARRAY_ENTRIES. Функция ulTaskNotifyStateClear() не имеет этого параметра и всегда воздействует на уведомление с индексом 0.

Возвращает pdTRUE, если для состояния уведомления задачи было установлено значение eNotWaitingNotification, в противном случае — pdFALSE.

 

ulTaskNotifyValueClear() / ulTaskNotifyValueClearIndex()

Функция ulTaskNotifyValueClearIndexed() очищает биты, указанные в битовой маске ulBitsToClear, в значении уведомления по индексу массива uxIndexToClear задачи, на которую ссылается xTask.

ulTaskNotifyValueClearIndexed(xTask, uxIndexToClear, ulBitsToClear)
ulTaskNotifyValueClear(xTask, ulBitsToClear)

Параметры:

  • xTask — дескриптор задачи RTOS, в одном из значений уведомлений которой будут очищены биты. Чтобы очистить биты в значении уведомления вызывающей задачи, установите для xTask значение NULL. Чтобы получить дескриптор задачи, создайте задачу с помощью xTaskCreate() и используйте параметр pxCreatedTask, или создайте задачу с помощью xTaskCreateStatic() и сохраните возвращаемое значение, или используйте имя задачи в вызове xTaskGetHandle().
  • uxIndexToClear — индекс в массиве значений уведомлений целевой задачи, в котором нужно очистить биты. uxIndexToClear должен быть меньше, чем configTASK_NOTIFICATION_ARRAY_ENTRIES. Функция ulTaskNotifyValueClear() не имеет этого параметра и всегда очищает биты в значении уведомления с индексом 0.
  • ulBitsToClear — битовая маска для очистки битов в значении уведомления xTask. Установите бит в значение 1, чтобы очистить соответствующие биты в значении уведомления задачи. Установите ulBitsToClear в значение 0xffffffff (UINT_MAX в 32-битных архитектурах), чтобы очистить значение уведомления до 0. Установите ulBitsToClear в значение 0, чтобы запросить значение уведомления задачи без очистки каких-либо битов.

Возвращает значение уведомления целевой задачи до очистки битов, указанных в ulBitsToClear.

 


Примеры использования

В заключение приведу несколько примеров использования данного FreeRTOS API. Пока три, если найдутся более интересные примеры, возможно я дополню статью.

1. Использование task notifications в режиме семафора для периодического запуска задачи по команде извне

Допустим, мы создаем какую-то задачу worker_task, которая должна по команде свыше извне: проснуться, выполнить полезную работу, и снова уйти в спячку до следующей команды.

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// Задача, которая выполняет работу по командам извне
static TaskHandle_t worker_task_handle = NULL;

static void worker_task(void *arg)
{
    for (;;) {
        // Ждём нотификацию как бинарный семафор
        uint32_t notified = ulTaskNotifyTakeIndexed(
                0,              // Используемый индекс ("канал") для нотификации
                pdTRUE,         // Очистить значение при выходе
                portMAX_DELAY); // Ждать бесконечно

        if (notified > 0) {
            // Полезная работа
            // ...
        }
    }
}

void app_main(void)
{
    // Создаем задачу
    xTaskCreate(worker_task, "worker", 4096, NULL, 5, &worker_task_handle);

    // Периодическая отправка уведомлений задаче
    for (;;) {
        // Ожидание 1 с
        vTaskDelay(pdMS_TO_TICKS(1000)); 

        // Дать «семафор» через task notification
        xTaskNotifyGiveIndexed(worker_task_handle, 0);
    }
}

 

2. Использование task notifications для завершения работы задачи

Другой пример: создаем задачу, которая работает себе и работает, но по команде извне должна корректно остановиться, завершиться и аннигилироваться. Реальный пример: esp_ping API, про  который я как-нибудь тоже расскажу (где, я собственно, в первый раз и обнаружил этот самый механизм когда-то давно).

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// Идентификатор управляемой задачи
static TaskHandle_t worker_task_handle = NULL;

/* Рабочая задача: периодически выполняется, пока не придёт команда "стоп" */
static void worker_task(void *arg)
{
    while (1) {
        // Ожидание внешней команды на остановку
        uint32_t stop_cmd = ulTaskNotifyTakeIndexed(
                0,                    // Индекс массива уведомлений
                pdTRUE,               // Очистить значение при выходе
                pdMS_TO_TICKS(1000)); // Ждем 1 секунду (для примера)

        // Получена команда "стоп"
        if (stop_cmd > 0) {
            printf("Worker: stop command received, exiting task\n");
            // Корректное завершение: освободить ресурсы и удалить себя
            vTaskDelete(NULL); 
        }
        
        // Полезная периодическая работа
        // ...
    }
}

void app_main(void)
{
    // Запускаем задачу
    xTaskCreate(worker_task, "worker", 4096, NULL, 5, &worker_task_handle);

    // Ждём 60 секунд, потом останавливаем worker
    vTaskDelay(pdMS_TO_TICKS(60000));

    // Даём "семафор" как команду стоп
    printf("Controller: sending stop command\n");
    xTaskNotifyGiveIndexed(worker_task_handle, 0); 
}

 

3. Используем task notifications для передачи событий от обработчиков прерываний ISR

Допустим, мы имеем четыре кнопки, подключенные к разным GPIO, состояние которых считывается в обработчике прерываний. Необходимо передать в задачу worker номер нажатой кнопки. Для простоты понимания инициализацию кнопок, настройку прерываний и устранение дребезга контактов я опустил из данного примера. 

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"

static TaskHandle_t s_worker_task = NULL;

// Пусть у нас 4 кнопки на этих GPIO
#define BTN1_GPIO   GPIO_NUM_4
#define BTN2_GPIO   GPIO_NUM_5
#define BTN3_GPIO   GPIO_NUM_18
#define BTN4_GPIO   GPIO_NUM_19

// Биты в notification value для каждой кнопки
#define BTN1_BIT    (1U << 0)
#define BTN2_BIT    (1U << 1)
#define BTN3_BIT    (1U << 2)
#define BTN4_BIT    (1U << 3)

// Один индекс notification (0)
#define NOTIF_INDEX 0

// Простейшее сопоставление GPIO -> бит
static inline uint32_t gpio_to_bit(gpio_num_t gpio)
{
    switch (gpio) {
        case BTN1_GPIO: return BTN1_BIT;
        case BTN2_GPIO: return BTN2_BIT;
        case BTN3_GPIO: return BTN3_BIT;
        case BTN4_GPIO: return BTN4_BIT;
        default:        return 0;
    }
}

// ISR GPIO: вызывается при любом фронте (нажатие/отпускание)
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
    // Определяем, по какому GPIO пришло прерывание
    gpio_num_t gpio = (gpio_num_t)(uintptr_t)arg;
    uint32_t bit = gpio_to_bit(gpio);
    if (bit == 0) {
        return;
    }

    // Считываем логический уровень на входе
    int level = gpio_get_level(gpio);

    // Отправляем сообщение о нажатии на кнопку
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    if (level) {
        // Нажатие на кнопку: установить соответствующий бит
        xTaskNotifyIndexedFromISR(
            s_worker_task,
            NOTIF_INDEX,
            bit,
            eSetBits,  // OR с текущим notification value
            &xHigherPriorityTaskWoken);
    }

    // Переключаем контекст при необходимости
    if (xHigherPriorityTaskWoken == pdTRUE) {
        portYIELD_FROM_ISR();
    }
}

// Задача, которая ждёт события от кнопок
static void worker_task(void *arg)
{
    uint32_t notif_value;

    for (;;) {
        // Ждём любое событие
        if (xTaskNotifyWaitIndexed(
                NOTIF_INDEX,
                0,              // ulBitsToClearOnEntry
                0xffffffff,     // Очистить биты при выходе, чтобы начать "с чистого листа" для получения следующих событий
                &notif_value,   // Сюда придёт текущее значение битов
                portMAX_DELAY   // Ждать бесконечно
                ) == pdPASS) {  // Получили уведомление

            // Разбираем, какие кнопки сейчас "нажаты" по битам
            if (notif_value & BTN1_BIT) {
                printf("BTN1 pressed (bit set)\n");
            }
            if (notif_value & BTN2_BIT) {
                printf("BTN2 pressed (bit set)\n");
            }
            if (notif_value & BTN3_BIT) {
                printf("BTN3 pressed (bit set)\n");
            }
            if (notif_value & BTN4_BIT) {
                printf("BTN4 pressed (bit set)\n");
            }
        }
    }
}

void app_main(void)
{
    // Здесь должна быть инициализация GPIO, как в generic_gpio:
    // - конфигурация входов
    // - включение pull-up
    // - настройка прерываний по любому фронту
    // - установка ISR и привязка gpio_isr_handler() к каждому пину
    // [Generic GPIO main](https://docs.espressif.com/projects/esp-techpedia/en/latest/esp-friends/get-started/case-study/peripherals-examples/gpio-examples/generic-gpio-example.html#main-function-description)

    xTaskCreate(worker_task, "worker", 4096, NULL, 5, &s_worker_task);
}

Как вы поняли, данный пример реагирует только на нажатие кнопок, и игнорирует их отпускание. Решить эту проблему можно как минимум двумя способами:

  • Можно добавить ещё четыре бита, например BTN1_ON_BIT  и BTN1_OFF_BIT, и отправлять их отдельно. Тогда программа сможет реагировать на состояние каждой кнопки отдельно.
  • Можно его усложнить, дабы ISR мог считывать и передавать состояние всех входов одновременно в виде комбинации бит. Тогда реагировать на “отпускание” отдельным событием не понадобится.

Будем считать это вашей “домашней работой”.


Ну а на этом пока всё, позвольте откланяться, с вами был Александр aka kotyara12.


Пожалуйста, оцените статью:
[ 0 из 5, всего 0 оценок ]

-= Каталог статей (список по разделам) =-   -= Архив статей (плитки, все подряд) =-

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *