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

Также вы можете посмотреть другие статьи на нашем сайте про использование FreeRTOS на ESP32:
- создание задач FreeRTOS в ESP32 с помощью ESP-IDF;
- работа с очередями FreeRTOS в ESP32 с помощью ESP-IDF;
- синхронизация задач FreeRTOS с использованием семафоров в ESP32 с помощью ESP-IDF;
- таймеры и задержки в FreeRTOS на ESP32 с помощью ESP-IDF.
Что такое мьютекс?
Мьютекс (mutex) — это особый тип бинарного семафора, который используется для управления ресурсами между задачами FreeRTOS. Управление ресурсами означает предоставление доступа к общим ресурсам двум или более задачам последовательно. Слово «мьютекс» также известно как «взаимное исключение».

Мьютекс означает взаимное исключение. Он гарантирует, что только одна задача имеет доступ к ресурсу в определенный момент времени, тем самым предотвращая взаимный доступ нескольких задач к одному и тому же ресурсу. Он действует как флаг, цель которого — предоставить потоку доступ к определенной части кода за раз. Остальные потоки блокируются от получения доступа к этому конкретному коду/ресурсу. Таким образом, мьютекс гарантирует, что никакие другие потоки не повредят информацию, и выполнение этого кода/ресурса будет потокобезопасным. Мьютекс действует как механизм блокировки, ограничивая одновременное чтение и изменение общего ресурса несколькими потоками. Одному потоку разрешено работать с ресурсом, а остальные ждут возврата ключа (мьютекса), который может быть использован другим потоком впоследствии.
Зачем нам нужен мьютекс?
В многозадачных системах часто встречаются ситуации, когда более одной задачи используют один и тот же ресурс для выполнения. Следовательно, использование одного общего ресурса несколькими задачами может привести к многочисленным проблемам, таким как взаимоблокировки, повреждение данных, инверсия приоритетов и т. д.
Предположим, в нашем приложении FreeRTOS используются две задачи: Task_A и Task_B. Обе задачи выводят строки на ЖК-дисплей. Более того, приоритет Task_B выше, чем у Task_A. Это означает, что Task_B может вытеснить Task_A.
Например, если задача начинает свое выполнение, обращаясь к общему ресурсу, но не сможет завершить его до того, как задача с более высоким приоритетом займет этот ресурс, и оставляет ресурс в несогласованном состоянии, то доступ к тому же ресурсу любой другой задачей или прерывание могут привести к повреждению данных или другим подобным проблемам.

Рассмотрим пример, показанный на приведенной выше диаграмме, где задачам Task_B и Task_A требуется доступ к внешнему периферийному ЖК-дисплею для отображения строки. Например, задача Task_A находится в состоянии выполнения и начинает выводить на ЖК-дисплей текст «ESP32 FreeRTOS Mutex». В середине выполнения задачи Task_A задача Task_B (задача с высоким приоритетом) прерывает выполнение задачи Task_A, и Task_A выводит на ЖК-дисплей только «ESP32 FreeRTOS Mutex». Но задача Task_B получает доступ к общему ресурсу ЖК-дисплея. Задача Task_B начинает выполнение и отображает на ЖК-дисплее «LAB_1». После завершения выполнения задачи Task_B задача Task_A снова начинает выполнение с того места, где ее прервала задача Task_B, и выводит на ЖК-дисплей «tex».
Теперь на ЖК-дисплее отобразится «ESP32 FreeRTOS MuLAB_1tex». Но это не то, что мы хотели отобразить. Вместо этого нам нужно отобразить «FreeRTOS Mutex LAB_1». Следовательно, это проблема повреждения данных. Мы можем решить эту проблему, обеспечив последовательный доступ к общему ресурсу (ЖК-дисплею) с помощью семафора мьютекса FreeRTOS.
Как использовать мьютекс для сериализации доступа к общим ресурсам?
В последнем примере мы столкнулись с проблемой повреждения данных, когда две задачи обращаются к общему ресурсу неожиданным образом. Мы исправим эту проблему с помощью семафора мьютекса в FreeRTOS.
Рассмотрим мьютекс как токен, и только процесс, обладающий этим токеном, может получить доступ к общему ресурсу, независимо от приоритета задачи. Например, в этом примере общим ресурсом является ЖК-дисплей (LCD). Любая задача, желающая получить доступ к LCD для записи строки, сначала должна получить доступ к токену. Другими словами, она должна стать держателем токена (мьютекса). Никакая другая задача не сможет получить доступ к LCD, поскольку она не владеет токеном. После того, как держатель токена завершит свое выполнение и использование ресурса, он должен освободить токен, вернув его ресурсу. Только после возврата токена другая задача сможет успешно получить доступ к нему и затем безопасно получить доступ к тому же общему ресурсу. Задача не имеет права доступа к общему ресурсу, если она не владеет мьютексом.

Пример мьютекса
На следующем рисунке показан пример его использования. Задачи Task_1 и Task_2 должны получить токен мьютекса перед отправкой данных на ЖК-дисплей. Например, если Task_1 имеет этот токен мьютекса, Task_2 не сможет вытеснить Task_1, даже если Task_2 имеет более высокий приоритет, чем Task_1. Это связано с механизмом наследования приоритетов в мьютексах FreeRTOS, и именно это отличает мьютексы от бинарных и счетных семафоров.

Наследование приоритетов означает, что если задача с более низким приоритетом владеет токеном мьютекса, а задача с более высоким приоритетом пытается вытеснить задачу с более низким приоритетом, то приоритет задачи с более низким приоритетом, владеющей мьютексом, повысится до приоритета задачи с более высоким приоритетом. Это предотвратит вытеснение задачи с более низким приоритетом задачей с более высоким приоритетом, а также задача с более высоким приоритетом перейдет в состояние блокировки, если задача с более низким приоритетом не вернет мьютекс. Наследование приоритетов используется для минимизации эффекта инверсии приоритетов.
Библиотека мьютексов FreeRTOS в ESP-IDF
ESP-IDF предоставляет библиотеку freertos/semphr.h для использования мьютекса-семафора. Это особый тип бинарного семафора, который включает в себя безопасность удаления задач, управление правами доступа, рекурсивный доступ и обработку проблем, возникающих при использовании мьютекса. Ключевое различие между мьютексом и семафором заключается в том, что в случае мьютекса задача, имеющая к нему доступ, должна также вернуть его, чтобы другие задачи могли использовать тот же мьютекс. Однако в случае семафора это не так, поскольку любая задача может как принимать, так и отдавать семафор.
Хотя бинарные семафоры и мьютексы похожи, они не идентичны. В отличие от мьютексов, которые включают механизм наследования приоритетов, бинарные семафоры его не имеют. Это делает мьютексы лучшим вариантом при реализации простого взаимного исключения, тогда как бинарные семафоры лучше подходят для синхронизации задач.
Мьютекс FreeRTOS для ESP32 с помощью ESP-IDF
В этом разделе мы продемонстрируем работу мьютекса, используя API библиотеки семафоров FreeRTOS.
Создание проекта
Откройте VS Code и перейдите в меню View > Command Palette («Вид» > «Палитра команд»). Введите в строке поиска ESP-IDF: New Project и нажмите Enter.
Укажите название проекта и каталог. Мы назвали наш проект «ESP_IDF_FREERTOS_MUTEX». Для платы ESP-IDF мы выбрали опцию custom board («Пользовательская плата»). В качестве целевой платформы ESP-IDF мы выбрали модуль ESP32. Нажмите кнопку ‘Choose Template’ («Выбрать шаблон»), чтобы продолжить.
В разделе Extension («Расширение») выберите опцию ESP-IDF:
На вкладке get-started («Начало работы») мы щелкнем по пункту «sample_project». Затем нажмем ‘Create project using template sample_project’) («Создать проект, используя шаблон sample_project»).
Вы получите уведомление о создании проекта. Чтобы открыть проект в новом окне, нажмите «Да».
Это открывает наш проект, который мы создали во вкладке EXPLORER («Проводник»). Внутри папки проекта находится несколько папок. Это справедливо для каждого проекта, который вы будете создавать с помощью ESP-IDF Explorer.
Код мьютекса FreeRTOS для ESP32
В папке main находится исходный код, то есть файл main.c будет расположен именно здесь. Перейдите в папку main > main.c и откройте его. Скопируйте приведенный ниже код в свой файл main.c и сохраните его.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" xSemaphoreHandle xMutex; //shared resourse (printf) function void write_message_console(char *message) { printf(message); } void temperature_task(void *params) { while (true) { printf("Inside temperature_task \n"); if (xSemaphoreTake(xMutex, portMAX_DELAY)) { write_message_console("temperature is 35'c\n"); xSemaphoreGive(xMutex); } vTaskDelay(1000 / portTICK_PERIOD_MS); } } void humidity_task(void *params) { while (true) { printf("Inside humidity_task\n"); if (xSemaphoreTake(xMutex, portMAX_DELAY)) { write_message_console("humidity is 48% \n"); xSemaphoreGive(xMutex); } vTaskDelay(2000 / portTICK_PERIOD_MS); } } void app_main(void) { xMutex = xSemaphoreCreateMutex(); xTaskCreate(&temperature_task, "temperature_task", 2048, NULL, 2, NULL); xTaskCreate(&humidity_task, "humidity_task", 2048, NULL, 2, NULL); } |
Как работает код?
Чтобы использовать функциональные возможности библиотеки Semaphore, предоставляемой FreeRTOS, для создания мьютекса, сначала подключим ее заголовочный файл вместе с другими библиотеками FreeRTOS для генерации задержек и создания задач.
|
1 2 3 4 5 6 |
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" |
Далее создайте дескриптор семафора с именем 'xMutex', как показано ниже.
|
1 |
xSemaphoreHandle xMutex; |
Функция write_message_console() отвечает за вывод сообщения на терминал путем вызова функции printf().
|
1 2 3 4 |
void write_message_console(char *message) { printf(message); } |
Внутри функции temperature_task мы вызываем функцию xSemaphoreTake(), если мьютекс получен. Эта функция используется для получения семафора и принимает два аргумента. Первый аргумент — это дескриптор семафора, в нашем случае 'xMutex'. Второй аргумент — это время блокировки. Это максимальное время, в течение которого задача будет ожидать в заблокированном состоянии, пока семафор не станет доступным. Мы указали время блокировки как 'portMAX_DELAY'. Это максимальное значение, которое можно определить как тип такта. Следовательно, задача будет ждать, пока не получит флаг от семафора. Функция write_message_console() будет использоваться для отображения показаний температуры 35°C, а функция xSemaphoreGive() будет вызвана для освобождения мьютекса. Она принимает один аргумент — дескриптор используемого семафора, 'xMutex'. Эта функция вызывается из любой задачи, которую необходимо синхронизировать. После этого добавляется задержка в одну секунду, прежде чем процесс продолжится.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void temperature_task(void *params) { while (true) { printf("Inside temperature_task \n"); if (xSemaphoreTake(xMutex, portMAX_DELAY)) { write_message_console("temperature is 35'c\n"); xSemaphoreGive(xMutex); } vTaskDelay(1000 / portTICK_PERIOD_MS); } } |
Аналогично, внутри функции humidity_task() мы вызываем xSemaphoreTake(), если мьютекс был получен. Функция write_message_console() будет использоваться для отображения показаний влажности, а функция xSemaphoreGive() будет вызвана для освобождения мьютекса. После этого добавляется задержка в две секунды, прежде чем процесс продолжится.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void humidity_task(void *params) { while (true) { printf("Inside humidity_task\n"); if (xSemaphoreTake(xMutex, portMAX_DELAY)) { write_message_console("humidity is 48% \n"); xSemaphoreGive(xMutex); } vTaskDelay(2000 / portTICK_PERIOD_MS); } } |
app_main()
Внутри функции app_main() сначала создаётся семафор типа мьютекс с помощью функции xSemaphoreCreateMutex(). Эта функция возвращает дескриптор, через который осуществляется ссылка на мьютекс. Это 'xMutex', который представляет собой дескриптор семафора, определённый нами ранее.
|
1 |
xMutex = xSemaphoreCreateMutex(); |
Затем мы создадим задачи для temperature_task и humidity_task. Для создания задачи мы используем функцию xTaskCreate(). Эта функция принимает несколько аргументов.
- Первый аргумент — это имя функции.
- Второй аргумент — это название задачи, используемое в описательных целях.
- Третий аргумент задаёт размер стека задачи. Это указывает объём памяти, который мы хотим зарезервировать для конкретной задачи.
- Четвертый аргумент — это параметр. Это значение, которое передается в качестве параметра создаваемой задаче. Если это свойство не используется, оно устанавливается в значение NULL.
- Пятый аргумент — это приоритет задачи.
- Последний аргумент — это дескриптор, используемый для изменения функции задачи, например, приостановки, удаления, возобновления, получения или установки конфигурации задачи. Он необязателен, поэтому может быть установлен как NULL.
|
1 2 |
xTaskCreate(&temperature_task, "temperature_task", 2048, NULL, 2, NULL); xTaskCreate(&humidity_task, "humidity_task", 2048, NULL, 2, NULL); |
Демонстрация работы проекта
Для прошивки микросхемы введите следующую команду в последовательном терминале. Не забудьте заменить COM-порт на тот, через который подключена ваша плата.
|
1 |
idf.py -p COMX flash monitor |
После успешной прошивки кода в последовательный терминал выводится следующее. Каждую секунду мьютекс в задаче temperature_task захватывается и освобождается, и показания температуры выводятся в терминал ESP-IDF. Аналогично, каждые 2 секунды мьютекс в задаче humidity_task захватывается и освобождается, и показания влажности выводятся в терминал ESP-IDF.
3 просмотров







