Добрый день, уважаемый читатель! Сегодня поговорим об ещё одном очень полезном объекте FreeRTOS – группах событий или EventGroup.
Принцип работы группы событий
Группа событий — это набор бит, которым ваше приложение может присваивать значение “0
” (бит не установлен) или “1
” (бит установлен). На первый взгляд, того же самого можно достичь, просто устанавливая отдельные биты в целом числе, но есть большое “но” – битовые операции не годятся для многопоточных систем.
Лично на мой взгляд, это не очень удачное название, правильнее было бы “группа состояний” или “группа флагов”, но не я это придумал…
Кроме функции потокобезопасного переключения событий, группы событий несут в себе ещё одну очень важную функцию – ожидание событий. Группы событий позволяют одновременно различным задачам ожидать одного или сразу нескольких событий в заблокированном (приостановленном) состоянии. Когда ожидаемое событие таки происходит, оно разблокирует сразу все ожидающие его задачи. Этим группы отличаются от очередей – в случае с очередью разблокирована будет только одна задача. Иногда так же полезно заменить несколько семафоров одной группой событий, чтобы уменьшить использование оперативной памяти.
Варианты практического применения
Группы событий ну очень удобно использовать для хранения различных состояний устройства:
- есть ли подключение к WiFi роутеру
- состояние ping-а “до интернета”
- подключение к MQTT-брокеру
- подключение к внешним сервисам
- работает или нет синхронизация времени, либо установлены аппаратные часы
- состояние подключенных сенсоров и датчиков
- и т.д. и т.п.
При этом очень легко изменять и проверять флаги состояний из различных задач, никак не связанных друг с другом, без опасения получить ошибочные данные, вызванные переключением контекста.
Кроме этого, группы идеально подходят для случаев, когда необходимо из одной задачи поручить выполнение какой – либо функции другой задаче с ожиданием её завершения. Вы просто запускаете другую задачу и ждете установки флага завершения. Пример: пинг сервера в локальной сети или интернет. Когда пинг будет завершен, будет установлен флаг наличия доступа к серверу или флаг его отсутствия.
Емкость группы событий
Группа событий, применительно к ESP-IDF, может хранить от 1 до 24 бит (состояний) – это биты от BIT0
до BIT23
. “Внутри”, для хранения состояний используется как минимум 32-битное число, но старшие биты (от 24 до 31) зарезервированы под “служебные надобности” и вы не можете их использовать.
Для объявления флагов (другими словами: состояний, бит) очень удобно использовать предопределенные макросы BIT0
, BIT1
~ BIT24
из esp_bit_defs.h
, но вполне можно использовать и побитовый сдвиг: 1<<0
, 1<<1
и т.д.
Проверить состояние бита можно в любой момент, но вот ожидать задачи могут только установку флага, поэтому если вам потребуется в вашей программе дождаться “сброса” какого-либо флага (установки в состояние “0”), этот флаг придется инвертировать.
Создание и удаление группы событий
Как и для большинства других объектов FreeRTOS, прежде чем начать работу с группой событий, её необходимо создать и получить хендл (указатель) на неё. Это делается с помощью двух функций:
EventGroupHandle_txEventGroupCreate(void)
xEventGroupCreate()
не удастся выделить память под данные группы, иначе вы почти гарантировано нарветесь на исключение и перезагрузку контроллера.EventGroupHandle_txEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer)
– создание группы с использованием статического буфераStaticEventGroup_t
. Этот способ чуть сложнее, но позволяет заранее (на этапе компиляции) выделить необходимый общем памяти под группу событий и, тем самым, уменьшить фрагментацию кучи.xEventGroupCreateStatic()
в любом случае вернет действующий хэндл (ведь память фактически уже была выделена, её нужно только подобающим образом “оформить”), поэтому проверка на NULL в данном случае не требуется. Пример из справочной системы прекрасно иллюстрирует данный способ:
Удаление группы событий
Удаление группы имеет смысл только в случае её динамического создания, так как в случае статического создания особого смысла в этом нет. Сделать это можно с помощью функции vEventGroupDelete()
, передав ей хендл ранее созданной группы событий:
void vEventGroupDelete(EventGroupHandle_t xEventGroup)
Чтение состояния группы событий
Получить текущее состояние группы (без какого-либо ожидания) можно с помощью простой функции-макроса:
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup)
Вы передаете этой функции хэндл группы, а в ответ получаете 32-битное число, отражающее текущее состояние всех рабочих бит (с 0 по 23) группы. Затем, с помощью битовых операций вы должны выяснить, “взведён” нужный вам бит или нет, например так:
Как вы понимаете, за одну операцию читается сразу вся группа, нет необходимости вызывать её для каждого бита отдельно.
Чтение состояния со сбросом указанных флагов
Если необходимо сбросить некоторые биты сразу после прочтения их текущего статуса, воспользуйтесь другой функцией:
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear)
Эта функция, так же как и xEventGroupGetBits()
, вернет вам состояние группы событий перед операцией сброса в виде 32-битного числа, но и дополнительно сбросит те биты, которые вы перечислите в аргументе uxBitsToClear, остальные биты не будут затронуты. Если передать uxBitsToClear = 0, то брюки превращаются, брюки превращаются эта функция плавно превращается в xEventGroupGetBits()
.
Эту же функцию можно использовать и просто для сброса одного или нескольких флагов, в этом случае вы просто игнорируете возвращаемый результат.
За одну операцию можно сбросить сразу несколько флагов / бит, для этого сложите несколько отдельных бит с помощью логического ИЛИ:

Сброс сразу нескольких флагов при отключении от WiFi
Важное замечание: xEventGroupClearBits()
, как и xEventGroupGetBits()
, нельзя использовать из прерываний. Для чтения группы событий и сброса флагов следует воспользоваться функцией
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup)
или
EventBits_t xEventGroupClearBitsFromISR(xEventGroup, uxBitsToClear)
Более подробная информация о работе с группами из прерываний будет чуть ниже.
Установка одного или нескольких бит
Для установки флагов (бит) воспользуйтесь другой функцией:
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet)
Вы можете передать через аргумент uxBitsToSet
один или сразу несколько бит, объединив их с помощью ИЛИ |
.

В данном примере statesSet() и statesClear() всего-лишь “фантики” для стандартных функций, дабы не указывать каждый раз хэндл группы
Эту функцию также нельзя использовать из обработчиков прерываний!
Ожидание одного или нескольких событий
Для ожидания событий | бит | флагов в группе существует такая функция:
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait)
где:
- EventGroupHandle_t xEventGroup – указатель на группу событий
- constEventBits_t uxBitsToWaitFor – один или несколько бит, которые необходимо ожидать, их можно сложить с помощью | (как и в предыдущих случаях)
- constBaseType_t xClearOnExit – если для xClearOnExit задано значение
pdTRUE
, то любые биты в uxBitsToWaitFor, установленные в группе событий, будут очищены до выхода изxEventGroupWaitBits()
, но только если условие ожидания было выполнено (если функция возвращается по причине, отличной от таймаута). Если для xClearOnExit установлено значениеpdFALSE
, биты, установленные в группе событий, не изменяются при возврате вызоваxEventGroupWaitBits()
. - constBaseType_t xWaitForAllBits – если для xWaitForAllBits задано значение
pdTRUE
, тогда функцияxEventGroupWaitBits()
завершится только когда либо будут установлены все биты в uxBitsToWaitFor, либо истечет указанное время xTicksToWait. Если для xWaitForAllBits установлено значениеpdFALSE
, тогдаxEventGroupWaitBits()
завершится, когда будет установлен любой из битов, установленных в uxBitsToWaitFor, или по таймауту. - TickType_t xTicksToWait – максимальное количество времени (указанное в тиках FreeRTOS) для ожидания установки одного или всех (в зависимости от значения xWaitForAllBits) битов, указанных в uxBitsToWaitFor. Укажите
portMAX_DELAY
для бесконечного ожидания, либо используйте макросыpdMS_TO_TICKS ( время в мс )
иливремя в мс / portTICK_PERIOD_MS
для пересчета миллисекунд в тики RTOS.
Функция вернет состояние группы событий на момент установки ожидаемых битов или истечения времени блока. Обязательно проверьте возвращаемое значение, чтобы узнать, какие биты были установлены. Если функция xEventGroupWaitBits()
возвращается из-за истечения времени ожидания, то не все ожидаемые биты будут установлены.
Если функция xEventGroupWaitBits()
возвратила значение из-за того, что биты, которых она ожидала, были установлены, то возвращаемое значение является состоянием группы событий до того, как какие-либо биты были автоматически очищены в случае, если для параметра xClearOnExit было установлено значение pdTRUE
.

Пример: ожидание подключения к WiFi в миллисекундах
Работа с группой событий из обработчиков прерываний
Чтение состояния группы
Для чтения состояния группы бит из обработчиков прерываний воспользуйтесь специальной версией функции:
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup)
Запись или очистка бит
Установка битов в группе событий не является детерминированной операцией, поскольку существует неизвестное количество задач, которые могут ожидать установки заданных бита или битов. FreeRTOS не позволяет выполнять недетерминированные операции при отключенных прерываниях, поэтому защищает группы событий, к которым осуществляется доступ из задач, приостанавливая планировщик, а не отключая прерывания. В результате доступ к группам событий невозможен непосредственно из процедуры обслуживания прерывания.
Поэтому специальные версии функций xEventGroupClearBitsFromISR()
и xEventGroupSetBitsFromISR()
отправляют сообщение задаче таймера, чтобы операция очистки или установки бит выполнялась в контексте задачи таймера. Если запрос на выполнение очистки или установки бит был отправлен в очередь таймера успешно, то возвращается pdPASS
, в противном случае возвращается pdFALSE
.
Демонстрационный проект
Для примера набросал небольшой проект, который состоит из группы событий и одной ожидающей задачи.
Все объекты создаются статическим способом.
Скачать проект, как обычно, можно на GitHub
Полезные ссылки
- ESP-IDF :: Event Group API
- Пример на GitHub
- Библиотека reStates, в которой используются группы событий
На этом пока всё, до встречи на сайте и на dzen-канале!