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

Также на нашем сайте вы можете прочитать статью про работу с FreeRTOS и памятью в ESP32-S3 с помощью ESP-IDF - возможно, некоторые аспекты в ней рассмотрены более понятным для вас языком.
Прежде чем продолжить, убедитесь, что у вас установлена последняя версия VS Code и настроено расширение ESP-IDF:
- Установка ESP32 ESP-IDF на Windows и его интеграция с Visual Studio Code
- Установка ESP32 ESP-IDF на Linux Ubuntu
Также на нашем сайте вы можете прочитать статью про начало работы с ESP32 с использованием ESP-IDF.
Введение в очереди FreeRTOS
Очередь сообщений — это тип буфера FIFO, в котором хранятся элементы данных фиксированного размера. Кроме того, после инициализации очереди количество элементов, которые она может содержать, фиксировано. Как правило, задачи считывают данные из начала буфера и записывают их в конец буфера. Но возможна и запись с начала. Буфер может использоваться несколькими записывающими и читающими процессами одновременно.
Однако буфер может использоваться только одной задачей записи или чтения одновременно, а другие процессы блокируются. В результате блокировка возможна как для записи, так и для чтения из буфера.
Например, задача 1 отправляет данные в буфер. Задача 2 считывает эти данные из буфера и удаляет их значение из буфера. Каждый блок данных в очереди FreeRTOS может иметь любой тип данных или размер.
Блокировка задач при чтении из очереди
Блокировка при чтении в задаче FreeRTOS вероятна в следующих ситуациях:
- Если несколько задач хотят прочитать данные из очереди сообщений, то задача с наивысшим приоритетом читает данные первой, а задача с наименьшим приоритетом — в конце. При этом остальные задачи остаются заблокированными. Мы также можем установить максимальное время блокировки задачи при отправке запроса на чтение. Однако различные задачи FreeRTOS могут иметь различное время блокировки.
- Другой возможный случай — когда очередь пуста. В этом случае все запросы на чтение переходят в блокирующее состояние. Как только данные становятся доступны из очереди сообщений (когда другая задача помещает данные в очередь готовности), все читатели переводятся в состояние готовности, и задача с наивысшим приоритетом получает возможность читать из очереди первой.
Блокировка задач при записи в очередь
Подобно чтению из очереди, задача записи также может дополнительно задать время блокировки при записи в очередь. Например, задача записи хочет записать данные в уже заполненную очередь. В этом случае время блокировки — это максимальное время, в течение которого задача должна находиться в заблокированном состоянии, ожидая освобождения места в очереди.
В одной очереди может находиться несколько задач записи. Поэтому существует вероятность того, что в уже заполненной очереди будет заблокировано более одной задачи, ожидающей завершения операции записи. В этом случае, когда освободится место в очереди, будет разблокирована только одна задача.
Разблокированной всегда будет задача с наивысшим приоритетом, ожидавшая освобождения места. Если заблокированные задачи имеют одинаковый приоритет, то разблокированной будет задача, которая дольше всего ожидала освобождения места.
Данная симуляция демонстрирует последовательность операций чтения/записи:

Пример кода для работы с очередью FreeRTOS в ESP32 с помощью ESP-IDF
Давайте поработаем над кодом, который мы использовали ранее при создании двух задач, и интегрируем в него Queue (очередь). Прежде чем продолжить, обратитесь к статье «Задачи FreeRTOS в ESP32 с ESP-IDF». Создание задач уже обсуждалось в данной статье. Здесь мы покажем, как использовать очереди FreeRTOS.
Чтобы использовать функциональные возможности библиотеки Queue, предоставляемой FreeRTOS, сначала подключим её заголовочный файл:
|
1 |
#include freertos/queue.h |
Далее создайте обработчик очереди, как показано ниже:
|
1 |
QueueHandle_t queue; |
Первая задача (функция)
Затем мы определим функцию Demo_Task. Это позволит нам отправлять данные в буфер.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void Demo_Task(void *arg) { char txBuffer[50]; queue = xQueueCreate(5, sizeof(txBuffer)); if (queue == 0) { printf("Failed to create queue= %p\n", queue); } sprintf(txBuffer, "Hello from Demo_Task 1"); xQueueSend(queue, (void*)txBuffer, (TickType_t)0); sprintf(txBuffer, "Hello from Demo_Task 2"); xQueueSend(queue, (void*)txBuffer, (TickType_t)0); sprintf(txBuffer, "Hello from Demo_Task 3"); xQueueSend(queue, (void*)txBuffer, (TickType_t)0); while(1){ vTaskDelay(1000/ portTICK_RATE_MS); } } |
Во-первых, мы создадим массив символов для хранения данных. Он называется 'txBuffer'.
|
1 |
char txBuffer[50]; |
Создание очереди
Далее мы создадим очередь с помощью функции xQueueCreate(). Первый аргумент — это количество символов в буфере очереди, а второй параметр — размер каждого символа в буфере. В нашем случае это выглядит следующим образом:
|
1 |
queue = xQueueCreate(5, sizeof(txBuffer)); |
Поскольку размер txBuffer установлен на 50, каждый блок будет иметь ограничение в 50 символов.
Кроме того, мы добавим условное выражение для проверки того, успешно ли создана очередь.
|
1 2 3 |
if (queue == 0){ printf("Failed to create queue= %p\n", queue); } |
Далее мы определяем строку 'Hello from Demo_Task 1', которую хотим отправить в очередь, используя функцию sprintf().
|
1 |
sprintf(txBuffer, "Hello from Demo_Task 1"); |
Запись данных в очередь
Для отправки этого буфера в очередь мы будем использовать функцию xQueueSend(). Первый аргумент этой функции — это используемая очередь. В нашем случае это 'queue'. Второй аргумент — это указатель на отправляемые данные. В нашем случае это '(void*)txBuffer'. Третий аргумент — это время блокировки. Поскольку мы не хотим добавлять блоки, мы установим его равным нулю.
|
1 |
xQueueSend(queue, (void*)txBuffer, (TickType_t)0); |
Добавим ещё несколько строк в txBuffer и отправим их в очередь следующим образом:
|
1 2 3 4 5 6 7 8 |
sprintf(txBuffer, "Hello from Demo_Task 1"); xQueueSend(queue, (void*)txBuffer, (TickType_t)0); sprintf(txBuffer, "Hello from Demo_Task 2"); xQueueSend(queue, (void*)txBuffer, (TickType_t)0); sprintf(txBuffer, "Hello from Demo_Task 3"); xQueueSend(queue, (void*)txBuffer, (TickType_t)0); |
Вторая функция задачи
Во второй функции задачи, Demo_Task2(), мы получим данные из буфера очереди.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
void Demo_Task2(void *arg) { char rxBuffer[50]; while(1){ if( xQueueReceive(queue, &(rxBuffer), (TickType_t)5)) { printf("Received data from queue == %s/n", rxBuffer); vTaskDelay(1000/ portTICK_RATE_MS); } } } |
Во-первых, мы создадим массив символов для хранения данных, которые будут получены из буфера очереди. Он называется 'rxBuffer'.
|
1 |
char rxBuffer[50]; |
Чтение данных из очереди
Внутри непрерывного цикла while мы будем использовать функцию xQueueReceive() для постоянного чтения буфера. Ее первый аргумент — это очередь, которую мы будем проверять в функции xQueueSend(). В нашем случае это очередь. Второй аргумент — это указатель на данные, которые мы получаем. В нашем случае это '&(rxBuffer)'. Третий аргумент — это время блокировки. Мы будем добавлять время блокировки при получении данных из буфера очереди, чтобы не потерять данные.
|
1 |
xQueueReceive(queue, &(rxBuffer), (TickType_t)5) |
Кроме того, мы добавим оператор print для проверки данных, полученных через последовательный монитор.
|
1 2 3 4 5 6 |
if( xQueueReceive(queue, &(rxBuffer), (TickType_t)5)) { printf("Received data from queue == %s\n", rxBuffer); vTaskDelay(1000/ portTICK_RATE_MS); } |
Полный код
Это полный код, который мы используем, и в котором мы объяснили части, относящиеся к очереди FreeRTOS.
|
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 50 |
#include <stdio.h> #include <freertos/FreeRTOS.h> #include <freertos/task.h> #include "freertos/queue.h" TaskHandle_t myTaskHandle = NULL; TaskHandle_t myTaskHandle2 = NULL; QueueHandle_t queue; void Demo_Task(void *arg) { char txBuffer[50]; queue = xQueueCreate(5, sizeof(txBuffer)); if (queue == 0) { printf("Failed to create queue= %p\n", queue); } sprintf(txBuffer, "Hello from Demo_Task 1"); xQueueSend(queue, (void*)txBuffer, (TickType_t)0); sprintf(txBuffer, "Hello from Demo_Task 2"); xQueueSend(queue, (void*)txBuffer, (TickType_t)0); sprintf(txBuffer, "Hello from Demo_Task 3"); xQueueSend(queue, (void*)txBuffer, (TickType_t)0); while(1){ vTaskDelay(1000/ portTICK_RATE_MS); } } void Demo_Task2(void *arg) { char rxBuffer[50]; while(1){ if( xQueueReceive(queue, &(rxBuffer), (TickType_t)5)) { printf("Received data from queue == %s\n", rxBuffer); vTaskDelay(1000/ portTICK_RATE_MS); } } } void app_main(void) { xTaskCreate(Demo_Task, "Demo_Task", 4096, NULL, 10, &myTaskHandle); xTaskCreatePinnedToCore(Demo_Task2, "Demo_Task2", 4096, NULL,10, &myTaskHandle2, 1); } |
Демонстрация
Для прошивки микросхемы введите следующую команду в последовательном терминале. Не забудьте заменить COM-порт на тот, через который подключена ваша плата.
|
1 |
idf.py -p COMX flash monitor |
После успешной прошивки кода этот вывод отображается в последовательном терминале. Как видно из вывода монитора последовательного порта, первая задача записывает три строки в очередь, а вторая задача считывает их, как только появляется доступная запись в очереди.
Таким образом, мы можем читать и записывать данные в очереди, используя FreeRTOS, и осуществлять межзадачное взаимодействие для обмена данными между независимыми задачами. Очереди обеспечивают безопасный и эффективный способ обмена данными между задачами.
Вам также может быть интересно прочитать:
2 просмотров




