На данном занятии мы познакомимся ещё с одним видом межпроцессного взаимодействия и синхронизации потоков — Event Groups (группами событий). Для этого также существуют очереди, семафоры и мьютексы. Но группы событий от них отличаются тем, что во-первых здесь идёт экономия ресурсов, так как за каждое событие отвечает не отдельный объект, а всего лишь один бит, а также существует встроенный механизм отслеживания того момента, когда произойдёт одно из событий либо когда произойдут все события, а это также экономия ресурсов. Немаловажным отличием также является то, что группы событий позволяют отследить наступления событий одновременно в нескольких событий, что мы себе не можем позволить, пользуясь семафорами, очередями и мьютексами. Один семафор нельзя забрать несколько раз и очередь тоже.
Группы событий (Event Groups) оперируют битами (также их называют флагами) событий. Флаг события представляет собой логическое значение (1 или 0), используемое для указания того, произошло ли событие. Это позволяет хранить флаги событий в одном бите, а состояние всех флагов событий в группе событий — в одной переменной, где каждый флаг события EvenBits_t. Если бит в переменной EvenBits_t равен 1, это значит, что произошло событие, представленное битом. Если бит в переменной EvenBits_t равен 0, это значит, что событие, указанное этим битом, не произошло
Например, на рисунке ниже мы видим, что значение группы событий равно 0x92, то есть биты событий 1, 4 и 7 равны 1, следовательно произошли только события, представленные битами 1, 4 и 7
Максимальное количество битов, которые могут быть использованы для отслеживания событий в группах, регулируется переменной configUSE_16_BIT_TICKS в файле FreeRTOSConfig.h. Если данная переменная равна единице, то мы можем следить только за восемью событиями, а если равна 0, каждая группа событий может содержать 24 доступных бита события.
Теперь немного об используемых функциях для групп событий.
Создать группу событий мы можем при помощи следующей функции
Возвращаемое значение:
NULL – не удалось создать группу
Not NULL – группа событий создана успешно, возвращаемое значение – дескриптор созданной группы событий
Функция установки битов событий
где:
xEventGroup – дескриптор группы событий
uxBitsToSet – битовая маска, которая указывает, какие биты в группе событий установлены в 1, а какие в 0
Возвращаемое значение типа EventBits_t – возвращаемое значение не обязательно содержит биты, установленные uxBitsToSet, потому что эти биты могут быть очищены другими задачами.
Функция проверки битов событий
где:
xEventGroup – дескриптор группы событий.
uxBitsToWaitFor – битовая маска, которая определяет бит(ы) события, которые должны быть протестированы в группе событий. Например, если вызывающая задача хочет дождаться установки бита события 0 и(или) бита события 2 в группе событий, устанавливаем для uxBitsToWaitFor значение 0x05.
xClearOnExit – если вызывающая задача удовлетворяет условию разблокирования и xClearOnExit имеет значение pdTRUE, бит события, указанный в uxBitsToWaitFor, очищается перед выходом из данной функции. Если данный параметр равен pdFALSE, данная функция не будет изменять биты событий группы событий.
xWaitForAllBits – если pdFALSE, то если установлен любой бит в uxBitsToWaitFor, то функция завершится со сбросом данного бита. Если установим pdTRUE, то если все биты, установленные в uxBitsToWaitFor, устанавливаются, то только тогда мы выйдем из функции и все биты событий, присутствующие в маске, также сбросятся.
xTicksToWait – тайм-аут ожидания. Если вышеуказанные требования не будут выполнены в течение установленного времени, функция будет завершена.
Возвращаемое значение типа EventBits_t – если бит события установлен и условие разблокирования вызывающей задачи выполнено, возвращаемое значение является значением группы событий, которая удовлетворяет вызывающей задаче для выхода из состояния блокировки. Если выход вызван тайм-аутом, возвращаемое значение не будет соответствовать значению группы событий, которая вызвала задачу для выхода из заблокированного состояния.
А теперь, когда мы немного познакомились с группами событий, можно и попрактиковаться, иначе без этого материал не усваивается.
Схема со времён предыдущего урока не изменилась
Проект был сделан из проекта урока 13 по программным таймерам с именем SOFT_TIMER и был назван EVENT_GROUP_LCD.
Откроем проект в Espressif IDE и сразу в файле main.h подключим библиотеку для групп событий
Перейдём в файл main.c и объявим глобальный указатель для группы событий
Сразу же объявим макросы для битов событий
Логика будет простая. Мы создадим три программных таймера. Как только сработает первый таймер — установится бит, второй таймер — другой бит, третий таймер — третий бит.
Функции periodic_timer_callback и oneshot_timer_callback пока удалим вместе с телами, мы потом добавим другие функции обратного вызова.
В функции app_main пока удалим весь код, начиная со строки
LCD_ini();
до бесконечного цикла.
В этой же функции создадим группу событий
Для каждого таймера из трёх создадим переменную типа структуры параметров таймера с указателем на функцию обратного вызова
Выше функции app_main добавим данные функции, иначе у нас код перестанет собираться
В каждой функции мы пытаемся установить бит события, соответствующего данному таймеру.
Вернёмся в функцию app_main и создадим, а затем запустим каждый из таймеров с периодом между запусками в 1 секунду
Выше функции app_main добавим функцию задачи, в которой мы будем следить за нашими событиями
В данной задаче мы в бесконечном с помощью функции xEventGroupWaitBits ждём установки событий, добавив их с помощью маски в параметры функции. Так как третий аргумент у нас pdTRUE, то биты событий у нас после того, как мы дождёмся событий, сбросятся и у нас код не зациклится, иначе мы будем в следующих итерациях постоянно проваливаться вниз. А так как четвёртый параметр у нас pdFALSE, то мы ждём наступления любого из трёх событий.
Затем мы проверяем, какое именно у нас событие произошло и отображаем в соответствующей строке дисплея проинкрементированное число.
Также прежде чем у нас код нормально заработал, нам нужно данную задачу создать. Для этого идём в app_main и создаём её
Соберём код и посмотрим результат на дисплее
Число число появляется примерно раз в 1 секунду в каждой строке, значит у нас происходит отслеживание каждого события, так как таймеры запущены с разницей во времени в 1 секунду, а период срабатывания у них одинаковый.
Теперь попробуем в функции task1 четвёртый параметр поставить в pdTrue
EventBits_t bits = xEventGroupWaitBits(timer_event_group,
TIMER1_EVT | TIMER2_EVT | TIMER3_EVT,
pdTRUE,
pdTRUE,
portMAX_DELAY);
После выход из функции должен будет происходить только при условии, когда все 3 события произойдут.
Проверим это, собрав код и прошив контроллер
Теперь у нас инкрементирование происходит только раз в три секунды и только в третьей строке, так как именно тогда, когда сработают все три таймера, мы выйдем из функции. А в третьей строке потому, что у нас проверка идёт с обычным if, а не else if, а так как в последней строчке мы проверяем третье событие, то соответствующая строка и присваивается последней.
Итак, на данном уроке мы познакомились ещё с одной возможностью синхронизации потоков — с использованием групп событий (Event Group). Теперь, я думаю, настало время поработать с беспроводной связью, так как там широко используются разные типы межпроцессного взаимодействия, и, не познакомившись с ними заранее, тяжело будет понять логику работы с WiFi.
Всем спасибо за внимание!
Оригинал статьи находится здесь.
<<Предыдущий урок | Следующий урок>>
Недорогие отладочные платы ESP32 можно купить здесь
Логический анализатор 16 каналов можно приобрести здесь
Дисплей LCD 20x4 можно приобрести тут
Дисплей LCD 16x2 можно приобрести тут
Переходник I2C to LCD1602 2004 можно приобрести здесь
Видео в RuTube
Видео в Дзен
Видео в Youtube