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

Добрый день, уважаемый читатель! Сегодня поговорим об ещё одном очень полезном объекте FreeRTOS – группах событий или EventGroup.

Принцип работы группы событий

Группа событий — это набор бит, которым ваше приложение может присваивать значение “0” (бит не установлен) или “1” (бит установлен). На первый взгляд, того же самого можно достичь, просто устанавливая отдельные биты в целом числе, но есть большое “но” – битовые операции не годятся для многопоточных систем.

Лично на мой взгляд, это не очень удачное название, правильнее было бы “группа состояний” или “группа флагов”, но не я это придумал…

Кроме функции потокобезопасного переключения событий, группы событий несут в себе ещё одну очень важную функцию – ожидание событий. Группы событий позволяют одновременно различным задачам ожидать одного или сразу нескольких событий в заблокированном (приостановленном) состоянии. Когда ожидаемое событие таки происходит, оно разблокирует сразу все ожидающие его задачи. Этим группы отличаются от очередей – в случае с очередью разблокирована будет только одна задача. Иногда так же полезно заменить несколько семафоров одной группой событий, чтобы уменьшить использование оперативной памяти.

Варианты практического применения

Группы событий ну очень удобно использовать для хранения различных состояний устройства:

  • есть ли подключение к WiFi роутеру
  • состояние ping-а “до интернета”
  • подключение к MQTT-брокеру
  • подключение к внешним сервисам
  • работает или нет синхронизация времени, либо установлены аппаратные часы
  • состояние подключенных сенсоров и датчиков
  • и т.д. и т.п.

При этом очень легко изменять и проверять флаги состояний из различных задач, никак не связанных друг с другом, без опасения получить ошибочные данные, вызванные переключением контекста.

Кроме этого, группы идеально подходят для случаев, когда необходимо из одной задачи поручить выполнение какой – либо функции другой задаче с ожиданием её завершения. Вы просто запускаете другую задачу и ждете установки флага завершения. Пример: пинг сервера в локальной сети или интернет. Когда пинг будет завершен, будет установлен флаг наличия доступа к серверу или флаг его отсутствия.

 


Емкость группы событий

Группа событий, применительно к ESP-IDF, может хранить от 1 до 24 бит (состояний) – это биты от BIT0 до BIT23. “Внутри”, для хранения состояний используется как минимум 32-битное число, но старшие биты (от 24 до 31) зарезервированы под “служебные надобности” и вы не можете их использовать.

Для объявления флагов (другими словами: состояний, бит) очень удобно использовать предопределенные макросы BIT0BIT1 ~ BIT24 из esp_bit_defs.h, но вполне можно использовать и побитовый сдвиг: 1<<01<<1 и т.д.

Проверить состояние бита можно в любой момент, но вот ожидать задачи могут только установку флага, поэтому если вам потребуется в вашей программе дождаться “сброса” какого-либо флага (установки в состояние “0”), этот флаг придется инвертировать.


Создание и удаление группы событий

Как и для большинства других объектов FreeRTOS, прежде чем начать работу с группой событий, её необходимо создать и получить хендл (указатель) на неё. Это делается с помощью двух функций:

  • EventGroupHandle_txEventGroupCreate(void) – динамическое создание группы с выделением буфера под хранение данных из общей оперативной памяти (кучи). Никаких параметров в этом случае передавать не требуется, “в ответ” мы получим хендл группы событий. Обязательно проверьте его на NULL – если внутри 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 xEventGroupconst 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 xEventGroupconst EventBits_t uxBitsToSet)

Вы можете передать через аргумент uxBitsToSet один или сразу несколько бит, объединив их с помощью ИЛИ |.

В данном примере statesSet() и statesClear() всего-лишь “фантики” для стандартных функций, дабы не указывать каждый раз хэндл группы

Эту функцию также нельзя использовать из обработчиков прерываний!


Ожидание одного или нескольких событий

Для ожидания событий | бит | флагов в группе существует такая функция:

EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroupconst EventBits_t uxBitsToWaitForconst BaseType_t xClearOnExitconst 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


Полезные ссылки

  1. ESP-IDF :: Event Group API
  2. Пример на GitHub
  3. Библиотека reStates, в которой используются группы событий

На этом пока всё, до встречи на сайте и на dzen-канале!


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

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

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