Синхронизация задач FreeRTOS с использованием семафоров в ESP32 с помощью ESP-IDF


В данной статье мы научимся использовать семафоры FreeRTOS в ESP32 с помощью ESP-IDF. Семафоры используются для отправки уведомлений другим задачам или для синхронизации различных задач, тогда как очереди используются для межзадачной связи. Сначала мы рассмотрим пример кода, чтобы узнать, как использовать бинарные семафоры для синхронизации задач. Затем мы увидим, как освободить семафор из обработчика прерывания для синхронизации задач.

Ранее мы изучили создание задач FreeRTOS и использование очередей FreeRTOS на ESP32 с помощью ESP-IDF. Также на нашем сайте вы можете прочитать статью про работу с FreeRTOS и памятью в ESP32-S3 с помощью ESP-IDF - возможно, некоторые аспекты в ней рассмотрены более понятным для вас языком.

Прежде чем продолжить, убедитесь, что у вас установлена ​​последняя версия VS Code и настроено расширение ESP-IDF:

Также на нашем сайте вы можете прочитать статью про начало работы с ESP32 с использованием ESP-IDF.

Введение в семафоры

Семафор — это объект ядра, который задача может получить для целей синхронизации задач. Это процесс сигнализации, в ходе которого ожидающая задача получает сигнал от другой задачи о продолжении выполнения. Например, у нас есть две задачи: Demo_Task и Demo_Task2. Когда Demo_Task завершает определенную работу с данными, она выдает флаг (семафор), который является целым числом и увеличивается на 1. Этот флаг принимает Demo_Task2, которая ожидала его. Теперь она начинает свое выполнение. После завершения выполнения Demo_Task2 флаг уменьшается до 1.

Одна задача предоставляет семафор, а другие задачи получают его для перехода из блокирующего состояния в работающее. Если две или более задач ожидают семафора, то в случае бинарного семафора его получает задача с наивысшим приоритетом.

Существует два типа семафоров: бинарный семафор (Binary Semaphore) и счетный семафор (Counting semaphore).

Принцип работы семафора

Бинарный семафор

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

В рамках одного приложения бинарный семафор может использоваться несколькими задачами. Однако одновременно его может получить только одна задача. Любая задача, включая ту, которая изначально не получила семафор, может вернуть его, сделав семафор глобальным ресурсом. Предположим, задача 2 пытается получить доступ к бинарному семафору, который задача 1 получила ранее, но он уже используется. Следовательно, пока задача 1 не освободит бинарный семафор, задача 2 перейдет в блокирующее состояние и останется в нем.

Счётный семафор

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

Задачи, выполняемые счётным семафором

Типы счетных семафоров

Существует два типа таких устройств. Обычно они используются для двух целей: подсчета событий и управления ресурсами. 

Подсчет событий 

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

Например, мы создали счетный семафор с максимальным значением 4 и начальным значением, равным нулю. В этом случае обработчик событий будет «выдавать» семафор каждый раз, когда происходит событие, но максимальное количество событий, которое он может обработать, составляет до четырех. 

Управление ресурсами 

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

Например, есть две задачи FreeRTOS, которые хотят записывать данные в последовательный монитор Arduino через модуль связи UART. Но, как известно, Arduino IDE имеет только один последовательный монитор. Кроме того, Arduino Uno имеет только один встроенный модуль UART. Поэтому нам необходимо управлять ресурсами UART и последовательного монитора Arduino IDE между двумя задачами, используя счетный семафор для управления ресурсами. Сначала необходимо инициализировать счетный семафор, присвоив ему значение, равное количеству ресурсов. После этого задача, которая хочет использовать ресурс, должна получить семафор, уменьшив значение счетчика семафора. 

Пример кода, демонстрирующий работу с семафором в FreeRTOS (отправка уведомлений от одной задачи к другой)

Рассмотрим пример, чтобы изучить бинарный семафор для обычных задач. Мы будем работать с кодом, который использовали ранее при создании двух задач, и интегрируем в него семафор. Перед тем, как продолжить, обратитесь к статье «Задачи FreeRTOS с ESP-IDF». Создание задач уже обсуждалось в этой статье. Здесь мы покажем, как использовать семафор.

Это полный код, который мы используем.

Как работает код?

Чтобы использовать функциональные возможности библиотеки Semaphore, предоставляемой FreeRTOS, сначала подключим её заголовочный файл:

Далее создайте дескриптор семафора, как показано ниже:

В этом коде мы отправляем семафор из Demo_Task в Demo_Task2. Поэтому в функции Demo_Task() мы используем xSemaphoreGive() для передачи семафора. Она принимает один аргумент — дескриптор используемого семафора. В нашем случае это 'xSemaphore'. Эта функция вызывается из любой задачи, которую необходимо синхронизировать. Функция printf() будет использоваться для отображения того, что семафор успешно отправлен. Кроме того, xTaskGetTickCount() отобразит время, прошедшее с момента запуска приложения. Мы добавляем задержку в 1 секунду после повторного выполнения цикла while.

В задаче Demo_Task2 мы используем функцию xSemaphoreTake() , которая используется для получения семафора. Она принимает два аргумента. Первый аргумент — это дескриптор семафора, а второй — время блокировки. Это максимальное время, в течение которого задача будет ожидать в заблокированном состоянии, пока семафор не станет доступным. Мы указали время блокировки как 'portMAX_DELAY'. Это максимальное значение, которое можно задать в качестве типа тика. Следовательно, задача будет ждать, пока не получит флаг от семафора. Функция print() будет использоваться для отображения того, что семафор успешно получен. Кроме того, функция xTaskGetTickCount() отобразит время, прошедшее с момента запуска приложения.

Внутри основной функции мы создадим бинарный семафор с помощью функции xSemaphoreCreateBinary(). Она не принимает никаких аргументов и возвращает переменную типа SemaphoreHandle_t. xSemaphore — это глобальная переменная, которую мы используем для хранения семафора. Изначально мы установили её значение равным NULL.

Демонстрация работы кода

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

После успешной прошивки кода в последовательный терминал выводится следующий результат. Demo_Task сообщает Demo_Task2 каждую секунду. Когда выполняются функции printf и semaphore give, функция semaphore take запускается немедленно, поскольку время выполнения одинаково.

Демонстрация работы семафора в FreeRTOS на ESP32

Пример использования семафора в FreeRTOS в качестве прерывания

В этом примере мы будем использовать семафор для создания прерывания. Для демонстрации мы будем использовать пример из статьи про создание задач FreeRTOS в ESP32, где прерывание использовалось для переключения светодиода, подключенного к плате ESP32, с помощью кнопки.

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

Это полный код, который мы будем использовать.

Как работает код?

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

Для начала мы подключим необходимые библиотеки для этого проекта. Это включает библиотеку driver/gpio.h, поскольку нам нужно работать с контактами GPIO, и библиотеки FreeRTOS, так как мы хотим добавлять задержки, создавать задачи и использовать семафор.

Определите дескриптор семафора и установите для него значение NULL.

Внутри функции button_isr_handler() мы используем функцию xSemaphoreGiveFromISR() для передачи семафора для прерывания. Она принимает два аргумента. Первый аргумент — это дескриптор семафора, а второй — приоритет. Эта функция будет использоваться для синхронизации обработчика прерываний и задачи.

Компиляция скетча

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

После успешной прошивки кода вы можете просмотреть вывод на последовательном терминале. Обе задачи отправляют уведомления каждые 5 секунд. При нажатии кнопки срабатывает прерывание, и светодиод, подключенный к GPIO27, загорается. Повторное нажатие кнопки выключает его. Более того, функция xSemaphoreGiveFromISR() будет вызываться из функции ISR (обработки прерывания), а xSemaphoreTake() — из функции Demo_Task2(). Таким образом, при каждом нажатии кнопки функция Demo_Task() также выводит свое сообщение на последовательный терминал.

Демонстрация использования семафора в FreeRTOS в качестве прерывания

Вам также может быть интересно прочитать:

(Проголосуй первым!)
Загрузка...
3 просмотров

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

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