ESP32 Arduino: Работа с очередями в FreeRTOS

Целью этой статьи является изучение базовых возможностей очередей в операционной системе FreeRTOS и ядре Arduino. Тестирование будет проводиться на микроконтроллере ESP32 DevKit V4.

Введение

Очереди (далее queues) являются отличным иснтрументом для коммуникации между задачами и позволяют консистентно обмениваться сообщениями между ними. Они работают по принципу FIFO (первый вошёл, первый вышел). FreeRTOS имеет богатый Queue API, и в этой статье я хочу описать базовые методы работы с ним.

Есть несколько вещей, которые необходимо знать, перед работой с queues.

  1. Данные, которые будут добавляться в очередь копируются в память, даже если были переданы по ссылке. Другими словами, если мы передадим переменную типа integer в очередь, её значение будет скопировано, поэтому даже если её значение потом изменится, в очереди будет храниться её старое значение и никаких неожиданностей не возникнет.

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

  1. Другим немаловажным поведением очереди, является то, что попытка добавления в полную очередь или попытка чтения из пустой очереди может привести к блокировке кода на определённое время. Это время задаётся параметром в методах 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/