Целью этой статьи является изучение базовых возможностей очередей в операционной системе FreeRTOS и ядре Arduino. Тестирование будет проводиться на микроконтроллере ESP32 DevKit V4.
Введение
Очереди (далее queues) являются отличным иснтрументом для коммуникации между задачами и позволяют консистентно обмениваться сообщениями между ними. Они работают по принципу FIFO (первый вошёл, первый вышел). FreeRTOS имеет богатый Queue API, и в этой статье я хочу описать базовые методы работы с ним.
Есть несколько вещей, которые необходимо знать, перед работой с queues.
- Данные, которые будут добавляться в очередь копируются в память, даже если были переданы по ссылке. Другими словами, если мы передадим переменную типа integer в очередь, её значение будет скопировано, поэтому даже если её значение потом изменится, в очереди будет храниться её старое значение и никаких неожиданностей не возникнет.
На самом деле, мы можем продолжать передавать в очередь значение по ссылке, особенно когда его размер большой. В этом случае, указатель будет добавлен в очередь, а не само значение. И мы должны гарантировать, что данные не изменятся. Но это уже более сложная ситуация, которая не будет описана в этой статье.
- Другим немаловажным поведением очереди, является то, что попытка добавления в полную очередь или попытка чтения из пустой очереди может привести к блокировке кода на определённое время. Это время задаётся параметром в методах Queue API.
Хоть очереди и предназначены для межзадачной коммуникации, в этой статье будет описан простейший пример работы с очередью прямо в главном цикле Arduino main
, чтобы сфокусироваться на понимании работы Queue API.
Работа с Queue API
Для работы с Queues не потребуются какие-либо дополнительные библиотеки. Для начала необходимо объявить переменную типа QueueHandle_t
.
QueueHandle_t queue;
В Arduino функции setup
запустим Serial connection, для того, чтобы иметь возможность наглядно выводить результаты.
Далее, нужно создать queue с помозью функции xQueueCreate
. Первым аргументом она принимает максимальное количество элементов в очереди, вторым - размер в байтах каждого элемента. Соотвественно размер всех элементов в очереди должно быть одинаковым. Мы создадим очередь, которая может вмещать в себя максимум 10 integers, размер которых мы можем получить с помощью функции sizeof
.
После успешной инициализации очереди, функция xQueueCreate
возвращает handler типа QueueHandle_t
, который мы и присвоим ранее объявленной переменной. Если при инициализации очереди возникнут проблемы, например с выделением памяти, функция вернёт NULL
. Необходимо выполнять проверку на этот случай.
void setup() {
Serial.begin(115200);
queue = xQueueCreate(10, sizeof(int));
if (queue == NULL) {
Serial.println("Error creating the queue");
}
}
В главной функции main
будем вставлять элементы в очередь. Для этого используется функция xQueueSend
. Еапомню, что она вставляет новый элемент в конец очереди.
Первым аргументом функция принимает handler, который был получен на этапе инициализации очереди. Вторым аргументом является элемент, который необходимо поместить в очередь. И третьим аргументом является максимальное значение времени, на которое задача будет заблокирована в случае, если очередь переполнена.
Время блокировки указывается в тиках. Если передать константу portMAX_DELAY
, то ожидание будет бесконечным в случае переполнения очереди. Но мы напишем код так, что переполнения не будет.
void loop() {
if (queue == NULL) {
return;
}
for (int i = 0; i < 10; i++) {
xQueueSend(queue, &i, portMAX_DELAY);
}
int element;
for (int i = 0; i < 10; i++) {
xQueueReceive(queue, &element, portMAX_DELAY);
Serial.print(element);
Serial.print("|");
}
Serial.println();
delay(1000);
}
Следует обратить внимание, что хоть мы и передаём переменную i
по ссылке в цикле, на каждой итерации её значение будет скопировано, поэтому проблем при работе очереди не возникает.
Получать элементы из очереди будет прямо тут, в главной функции main
, сразу после добавления элементов. Чтобы извлечь элемент из очереди, необходимо вызвать функцию xQueueReceive
и передать первым аргументом hander на очередь, вторым - буфер, в который будет помещено значение извлечённого элемента. Третий аргумент - количество тиков ожидания, если очередь будет пустой.
Напомню, что после получения элемента, он будет удалён из очереди. Если удалять не нужно, то используйте функцию xQueuePeek
.
Итак, код для извлечения элемента следующий:
int element;
for (int i = 0; i < 10; i++) {
xQueueReceive(queue, &element, portMAX_DELAY);
Serial.print(element);
Serial.print("|");
}
Приведу листинг кода
QueueHandle_t queue;
void setup() {
Serial.begin(115200);
queue = xQueueCreate(10, sizeof(int));
if (queue == NULL) {
Serial.println("Error creating the queue");
}
}
void loop() {
if (queue == NULL) {
return;
}
for (int i = 0; i < 10; i++) {
xQueueSend(queue, &i, portMAX_DELAY);
}
int element;
for (int i = 0; i < 10; i++) {
xQueueReceive(queue, &element, portMAX_DELAY);
Serial.print(element);
Serial.print("|");
}
Serial.println();
delay(1000);
}
Проверка
Если загрузить приведённый выше код в микроконтроллер и открыть монитор порта, то мы увидим значения, извлечённые из очереди.
Вольный перевод статьи: https://techtutorialsx.com/2017/08/20/esp32-arduino-freertos-queues/