Как работать с очередями во FreeRTOS [2024-11-29]

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

У FreeRTOS очень богатый API очередей, который предлагает большое число возможностей.

Запись в очередь для хранения 5 элементов и чтение из нее.
Запись в очередь для хранения 5 элементов и чтение из нее.

Введение - быстрое знакомство

xQueueCreate - coздать новую очередь

xQueueSend - пoместить элемент в очередь

xQueueReceive - выбрать элемент из oчереди

xQueueSendFromISR и xQueueReceiveFromISR

Скетч “Передача сообщения из задачи и прерывания с приемoм в основном цикле”

Библиoграфия


Введение

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

Тем не менее, при вызове функций с очередями следует ссылаться на данные через указатели (это особенно полезно, если сообщения для обмена большие).

Следует также учитывать, что при вставке в заполненную очередь или извлечении из пустой очереди могут выполняться блокирующие вызовы в течение заданного периода времени (этот период времени является параметром API).

В первом, ознакомительном, примере работа с очередью из 10 целых чисел показана только в основном цикле приложения loop, для того чтобы сосредоточиться на базовых вызовах API, которые нужно выполнять.

Для работы не требуется дополнительных библиотек. При подключении к IDE Arduino плат разработки для Esp32 операционная система FreeRTOS подключается автоматически вместе с библиотекой очередей - queue.h.

Таким образом, код начинается с объявления глобальной переменной типа QueueHandle_t, который нужен для ссылки на очередь FreeRTOS.

QueueHandle_t queue;

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

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

При успешном выполнении функция xQueueCreate вернёт дескриптор очереди, который имеет тип QueueHandle_t, как и переменная, которая объявлена глобально. В случае возникновения проблем с выделением очереди будет возвращён NULL. Таким образом, после проверки возвращённого результата функцией можно предупредить пользователя о возникновении проблемы.

void setup() 
{
   Serial.begin(115200);
   queue = xQueueCreate(10, sizeof(int));
   if (queue == NULL)
   {
      Serial.println("Error creating the queue");
   }
}

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

Эта функция получает в качестве первого параметра дескриптор объявленной очереди. В качестве второго параметра она получает указатель на элемент, который следует вставить (хотя передаётся указатель, но в очередь копируется элемент), а в качестве последнего аргумента - максимальное время ожидания задачи в случае, если очередь заполнена.

Для последнего аргумента значение указывается в тиках (квантах FreeRTOS). Передача значения portMAX_DELAY означает, что ожидание будет продолжаться бесконечно, если очередь заполнена.

Поскольку очередь может содержать максимум 10 элементов, организуется простой цикл for и на каждой итерации вставляется текущее значение.

for (int i = 0; i<10; i++)
{
   xQueueSend(queue, &i, portMAX_DELAY);
}

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

Аналогичный цикл используется для извлечения элементов очереди. Для того, чтобы обработать элемент, следует просто вызвать функцию xQueueReceive. Она получит в качестве первого параметра дескриптор очереди, в качестве второго параметра - указатель на буфер, в который будет скопирован полученный элемент, и, наконец, количество тиков для ожидания, если очередь пуста.

Как и прежде, в качестве первого аргумента передаётся глобальная переменная - дескриптор очереди, а в качестве последнего - значение portMAX_DELAY. Что касается буфера, в который копируется элемент, то перед использованием элемента объявляется переменная целочисленного типа, в которой сохраняется полученный элемент.

Следует заметить, что при выборке элемента он будет удалён из очереди. Если не требуется удалять элемент при извлечении, можно использовать функцию 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);
}
к содержанию

xQueueCreate - cоздать новую очередь

Функция xQueueCreate создаёт новую очередь и возвращает дескриптор, с помощью которого можно ссылаться на эту очередь.

 QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);

Перед первым использованием очередей следует просмотреть заголовочный файл FreeRTOSConfig.h. Определение configSUPPORT_DYNAMIC_ALLOCATION должно быть установлено в 1 или не определено (в этом случае по умолчанию будет равно 1), чтобы эта функция RTOS API была доступна.

#define configSUPPORT_DYNAMIC_ALLOCATION             1

Для каждой очереди требуется оперативная память, которая используется для хранения состояния очереди и элементов, содержащихся в очереди (область хранения очереди). Если очередь создаётся с помощью xQueueCreate(), тогда необходимая оперативная память автоматически выделяется из кучи FreeRTOS.

Если очередь создаётся с помощью xQueueCreateStatic(), то оперативная память предоставляется разработчиком приложения, что приводит к увеличению количества параметров, но позволяет выделить оперативную память статически во время компиляции.

Параметры:

Возврат:

если очередь создана успешно, то возвращается дескриптор созданной очереди. Если память, необходимая для создания очереди, не может быть выделена, то возвращается NULL.

struct AMessage
{
   char ucMessageID;
   char ucData[20];
};

void vATask(void *pvParameters)
{
   QueueHandle_t xQueue1, xQueue2;
   // Создаём очередь, способную содержать до 10 чисел типа unsigned long
   xQueue1 = xQueueCreate(10, sizeof(unsigned long));
   if(xQueue1==NULL)
   {
      /* Очередь не была создана и не должна использоваться */
   }
   // Создаем очередь, способную содержать 10 структур, которые должны
   // быть помещены в очередь с помощью указателей, поскольку представляют
   // собой относительно большие данные
   xQueue2 = xQueueCreate(10, sizeof(struct AMessage));
   if( xQueue2 == NULL )
   {
      /* Очередь не была создана и не должна использоваться */
   }
   /* ... остальная часть кода задачи */
}
к содержанию

xQueueSend - поместить элемент в очередь

Функция xQueueSend отправляет элемент в очередь. Хотя элемент, передаваемый в функцию, указывается как параметр по ссылке (через указатель) фактически в очередь укладывается копия элемента.

Эту функцию нельзя вызывать из подпрограммы обслуживания прерываний. Для этого используется функция xQueueSendFromISR().

Формат:

BaseType_t xQueueSend(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);

По сути это макрос, который вызывает xQueueGenericSend(). Он включён для обратной совместимости с более ранними версиями FreeRTOS, в которых не было xQueueSendToFront() и xQueueSendToBack().

Параметры:

Вызов функции будет повторен немедленно, если очередь заполнена, а значение параметра xTicksToWait равно 0.

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

Если для INCLUDE_vTaskSuspend во FreeRTOSConfig.h установлено значение «1» (это по умолчанию), а время блокировки указано как portMAX_DELAY, то это приведет к блокировке задачи на неопределенный срок (без истечения времени ожидания).

Возврат:

Пример использования:

struct AMessage
{
   char ucMessageID;
   char ucData[20];
 } xMessage;
 
unsigned long ulVar = 10UL;

void vATask(void *pvParameters)
{
   QueueHandle_t xQueue1, xQueue2;
   struct AMessage *pxMessage;
   
   // Создаём очередь, способную содержать 10 длинных целых без знака
   xQueue1 = xQueueCreate(10, sizeof(unsigned long));
   // Создаём очередь, способную содержать 10 указателей на структуры
   // сообщений. Они должны передаваться по указателю, поскольку содержат
   // длинные строки
   xQueue2 = xQueueCreate(10, sizeof(struct AMessage *));
   
   /* ... */
   
   if(xQueue1 != 0)
   {
      // Посылаем беззнаковые целые. Ждем 10 секунд, пока освободится 
      // место, при необходимости освобождаем его
      if (xQueueSend(xQueue1, (void *) &ulVar, (TickType_t) 10) != pdPASS)
      {
         /* Не удалось отправить сообщение даже после 10 тиков */
      }
   }
   
   if (xQueue2 != 0)
   {
      // Отправляем указатель на структуру AMessage (не блокируем, если
      // очередь уже заполнена
      pxMessage = & xMessage;
      xQueueSend(xQueue2, ( void *) &pxMessage, (TickType_t) 0);
   }
   /* ... остальная часть кода задачи */
}

Блокировка при записи в очередь

Как и при чтении из очереди, “записывающая” задача может быть заблокирована для ожидания возможности записи в очередь. Это происходит, когда очередь полностью заполнена и в ней нет свободного места для записи нового элемента данных. До тех пор пока какая-либо другая задача не прочитает данные из очереди, задача, которая пишет в очередь, будет «ожидать», находясь в заблокированном состоянии. В одну очередь могут писать сразу несколько задач, поэтому возможна ситуация, когда несколько задач находятся в блокированном состоянии, ожидая завершения операции записи в одну очередь. Когда в очереди появится свободное место, получит управление задача с наивысшим приоритетом. В случае если запись в очередь ожидали равноприоритетные задачи, управление получит та, которая находилась в блокированном состоянии дольше остальных.

Поэтому есть, как минимум, два варианта дальнейших действий в случае, если не удаётся поместить данные в очередь мгновенно (или за некоторое время для надежности): либо отказаться от этого, либо побыстрее выбрать данные из очереди.

к содержанию

xQueueReceive - выбрать элемент из очереди

Функция xQueueReceive извлекает элемент из очереди. Элемент получается путём копирования, поэтому необходимо предоставить буфер подходящего размера. Количество байт, скопированных в буфер, должно соответствовать значению, которое было определено при создании очереди.

Эту функцию нельзя использовать в подпрограмме обслуживания прерываний, альтернативой должно служить xQueueReceiveFromISR.

Формат:

BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);

xQueueReceive - это макрос, который вызывает xQueueGenericReceive().

Параметры:

Время ожидания определяется в тактовых периодах, поэтому для определения реального времени следует использовать в пересчете значение константы portTICK_PERIOD_MS.

Если для INCLUDE_vTaskSuspend установлено значение ‘1’ (а это по умолчанию), то время блокировки, указанное как portMAX_DELAY приведет к блокировке задачи на неопределенный срок (без истечения времени ожидания).

Возврат: pdTRUE, если элемент был успешно получен из очереди, в противном случае pdFALSE.

Пример использования:

// Определяем переменную-структуру AMessage
struct AMessage
{
   char ucMessageID;
   char ucData[20];
}  xMessage;

// Определяем очередь, которая будет использоваться для отправки 
// и получения сообщений в виде структуры типа AMessage
QueueHandle_t xStructQueue = NULL;

// Определяем очередь, которая будет использоваться для отправки
// и получения указателей на структуры
QueueHandle_t xPointerQueue = NULL;

void vCreateQueues(void) 
{
   xMessage.ucMessageID = 0xab;
   memset(&(xMessage.ucData), 0x12, 20);
   
   // Создаём очередь для отправки полных сообщений-структур. Ее также
   // можно создать и позже, но необходимо позаботиться о том, чтобы
   // очередь не использовалась до тех пор, пока она не будет создана
   xStructQueue  =  xQueueCreate(10, sizeof(xMessage));
   
   // Создаём очередь для отправки указателей на структуры AMessage
   xPointerQueue = xQueueCreate(10, sizeof(&xMessage));
   
   if ((xStructQueue == NULL) || (xPointerQueue == NULL))
   {
      // "Не удалось создать одну или несколько очередей из-за нехватки
      // доступной памяти в куче. Исправьте ошибку здесь. 
      // Очереди также могут быть созданы  статически
   }
}

// Задача, которая выполняет запись в очередь
void vATask(void *pvParameters)
{
   struct AMessage *pxPointerToxMessage;
   
   // Отправляем всю структуру в очередь, созданную для хранения 10 структур
   xQueueSend( 
      // Дескриптор очереди
      xStructQueue,
      // Адрес переменной xMessage  sizeof(struct AMessage), откуда
      // байты копируются в очередь
      (void *) &xMessage,
      // Время блокировки, равное 0, означающее, что не нужно блокировать 
      // задачу, если очередь уже заполнена. Следует проверять значение,
      // возвращаемое функцией xQueueSend(), чтобы узнать, было ли
      // сообщение успешно отправлено в очередь
      (TickType_t  0)
   );
                             
   // Сохраняем адрес переменной xMessage в переменной-указателе
   pxPointerToxMessage = &xMessage;
   
   // Отправляем адрес сообщения в очередь, созданную 
   // для хранения 10 указателей
   xQueueSend (xPointerQueue, (void *) &pxPointerToxMessage,  (TickType_t) 0);
                
   /* ... некоторая часть кода */
}

// Задача, которая выполняет чтение из очереди
void vADifferentTask(void *pvParameters)
{
   struct AMessage xRxedStructure, *pxRxedPointer;
   if (xStructQueue != NULL)
   {
      // Получаем сообщение из созданной очереди для хранения сложного
      // структурного сообщения. Блокировка на 10 тиков, если сообщение
      // недоступно немедленно. Значение считывается в структурную
      // переменную AMessage, поэтому после вызова xQueueReceive()  
      // xRxedStructure будет содержать копию сообщения
      
      if (xQueueReceive(xStructQueue, &(xRxedStructure), (TickType_t) 10) == pdPASS)
      {
         /* xRxedStructure теперь содержит копию xMessage. */
      }
   }
   
   if (xPointerQueue != NULL)
   {
      // Получаем сообщение из созданной очереди для хранения указателей.
      // Блокируем на 10 тиков, если сообщение недоступно - немедленно.
      // Значение считывается в переменную указателя, и полученное значение
      // является адресом сообщения. Переменная, после этого вызова
      // pxRxedPointer будет указывать на xMessage.
      
      if (xQueueReceive(xPointerQueue,  &(pxRxedPointer), (TickType_t) 10) == pdPASS)
      {
         /* *pxRxedPointer теперь указывает на xMessage. */
      }
   }
   /* ... остальная часть кода задачи */
}  

Блокировка задачи при чтении из очереди

Когда какая-либо задача пытается прочитать данные из очереди, которая не содержит ни одного элемента, то задача приостанавливается – то есть переходит в блокированное состояние. Приостановленная задача вернется в состояние готовности к выполнению, когда другая задача (или прерывание) поместит данные в очередь. После этого планировщик сможет передать управление ожидающей задаче (но если нет задач с более высоким приоритетом).

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

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

к содержанию

xQueueSendFromISR - xQueueReceiveFromISR

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

Эти функции очень похожи на обычные версии - xQueueSend и xQueueReceive, но имеются некоторые отличия:

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

Появился новый аргумент pxHigherPriorityTaskWoken. Функция может вернуть в данном аргументе два состояния - pdTRUE или pdFALSE. Если вернулось pdTRUE, то это указывает, что необходимо досрочно отдать управление планировщику задач FreeRTOS. Допустим, в текущий момент выполняется задача с низким приоритетом, а высокоприоритетная ожидает сообщения из очереди и приостановлена. Далее происходит прерывание, из которого отправляется какое-либо сообщение в очередь. Но по окончании работы обработчика прерываний выполнение возвращается к текущей низкоприоритетной задаче, а высокоприоритетная все ещё ожидает, пока закончится текущий квант времени, и очередь всё ещё не обрабатывается. Однако если после этого передать управление планировщику досрочно через portYIELD_FROM_ISR, то он передаст управление ожидающей высокоприоритетной задаче. Это позволяет значительно сократить время реакции системы на прерывание.

Передача сообщения из задачи и прерывания с приемом в основном цикле

/** Arduino-Esp32-CAM                              *** QueueHandlMulti2.ino ***
 * 
 *                        Пример передачи сообщения из задачи и из прерывания с
 *                                                     приемом в основном цикле
 * 
 * v1.1, 29.11.2024                                   Автор:      Труфанов В.Е.
 * Copyright © 2024 tve                               Дата создания: 21.11.2024
**/


// Определяем структуру передаваемого сообщения
struct AMessage
{
   int  ucSize;        // Длина сообщения (максимально 256 байт)
   char ucData[256];   // Текст сообщения
};
// Создаем структуру для передачи сообщения из задачи и указатель на нее   
struct AMessage xMessage, *pxMessage;
// Создаем структуру для приёма сообщений   
struct AMessage xRxedStructure;
// Определяем очередь сообщений
QueueHandle_t xQueue;
// Инициируем счетчик циклов дополнительной задачи
unsigned long nLoop = 0UL;
// ****************************************************************************
// *  Сформировать сообщение о прошедшем времени с начала запуска приложения  *
// *                        И ПЕРЕДАТЬ ЧЕРЕЗ ПРЕРЫВАНИЕ                       *
// ****************************************************************************
// Определяем заголовок для объекта таймера
hw_timer_t *timer = NULL;
// Выделяем и инициируем переменную для прошлого момента времени
int lastMillis = millis(); 

void ARDUINO_ISR_ATTR onTimer() 
{
   // Размещаем структуру для сообщения в статической памяти для того,
   // чтобы уменьшить фрагментацию кучи 
   static DRAM_ATTR struct AMessage xiMessage;
   // Выделяем переменную планировщикe задач FreeRTOS для указания
   // необходимости переключения после прерывания на более приоритетную 
   // задачу, связанную с очередью
   static DRAM_ATTR BaseType_t xHigherPriorityTaskWoken;
   // Выделяем переменную для текущего момента времени
   static DRAM_ATTR int currMillis;
   // Выделяем переменную для прошедшего времени с начала запуска приложения
   static DRAM_ATTR int timeMillis;
    
   // Если в очереди есть место, будем размещать сообщение
   if (xQueue!=0)
   {
      // Сбрасываем признак переключения на более приоритетную задачу
      // после прерывания 
      xHigherPriorityTaskWoken = pdFALSE;
      // Определяем время, прошедшее с начала запуска приложения
      currMillis = millis(); 
      if (currMillis < lastMillis) lastMillis=0;
      timeMillis=currMillis-lastMillis;
      // Формируем сообщение для передачи в очередь
      sprintf(xiMessage.ucData, "Прошло %d миллисекунд",timeMillis);
      xiMessage.ucSize = 0;
      while (xiMessage.ucData[xiMessage.ucSize]>0) 
      {
         xiMessage.ucSize++;
      }
      // Отправляем сообщение в структуре AMessage 
      if (xQueueSendFromISR(xQueue, &xiMessage, &xHigherPriorityTaskWoken) != pdPASS)
      {
         Serial.println("ISR: Не удалось отправить структуру!");
      }
   }
   else 
   {
      Serial.println("ISR: Очередь для структур не создана!");
   }
   // Если требуется отдать управление планировщику на переключение 
   // после прерывания на более приоритетную задачу, делаем это 
   if (xHigherPriorityTaskWoken)
   {
      Serial.println("ISR: Управление передаётся планировщику!");
      portYIELD_FROM_ISR();
   }
}
// ****************************************************************************
// *                          Инициировать приложение                         *
// ****************************************************************************
void setup() 
{
   Serial.begin(115200);

   // Определяем дополнительную задачу
   xTaskCreatePinnedToCore (
      vATask,         // название функции, которая будет запускаться, как параллельная задача
      "Сообщение",    // название задачи
      1024,           // размер стека в байтах
      NULL,           // указатель параметра, который будет передан задаче (NULL, если параметр не передаётся)
      0,              // приоритет задачи
      NULL,           // дескриптор или указатель на задачу
      0               // ядро процессора, на котором требуется запустить задачу 
   );
   // Создаём объект таймера, устанавливаем его частоту отсчёта (1Mhz)
   timer = timerBegin(1000000);
   // Подключаем функцию обработчика прерывания от таймера - onTimer
   timerAttachInterrupt(timer, &onTimer);
   // Настраиваем таймер: интервал перезапуска - 1 секунда (1000000 микросекунд),
   // всегда повторяем перезапуск (третий параметр = true), неограниченное число 
   // раз (четвертый параметр = 0) 
   timerAlarm(timer, 1400000, true, 0);
   // Создаем очередь из 10 структур
   xQueue = xQueueCreate(10, sizeof(struct AMessage));
   if(xQueue==NULL)
   {
      Serial.println("SETUP: Очередь не была создана и не может использоваться!");
   }
   Serial.println("SETUP: Очередь сформирована!");
}
// ****************************************************************************
// *           Выполнять ПЕРЕДАЧУ СООБЩЕНИЯ ИЗ ЗАДАЧИ в бесконечном цикле     *
//  (если задача завершится - не будет циклится, то контроллер перезагрузится)
// ****************************************************************************
void vATask (void* pvParameters) 
{
   while (1) 
   {
      nLoop++;
      if (xQueue!=0)
      {
         // Формируем сообщение для передачи в очередь
         sprintf(xMessage.ucData, "Передано %d сообщение из задачи", nLoop);
         xMessage.ucSize = 0;
         while (xMessage.ucData[xMessage.ucSize]>0) 
         {
            xMessage.ucSize++;
         }
         pxMessage = &xMessage;
         if (xQueueSend(xQueue,pxMessage,5) != pdPASS)
         {
            Serial.println("TASK: Не удалось отправить структуру даже после 5 тиков!");
         }
      }
      else 
      {
         Serial.println("TASK: Очередь для структур не создана!");
      }
      delay (1600)
   }
}
// ****************************************************************************
// *                    Выполнить основной цикл приложения                    *
// ****************************************************************************
void loop() 
{
   if (xQueue != NULL)
   {
      // Получаем сообщение из созданной очереди для хранения сложного
      // структурного сообщения. Блокировка на 10 тиков, если сообщение
      // недоступно немедленно. Значение считывается в структурную
      // переменную AMessage, поэтому после вызова xQueueReceive()  
      // xRxedStructure будет содержать копию сообщения
      if (xQueueReceive(xQueue,&xRxedStructure,10) != pdPASS)
      {
         Serial.println("LOOP: Не удалось принять структуру даже после 10 тиков!");
      }
      else
      {
         Serial.print(xRxedStructure.ucSize);
         Serial.print(": ");
         Serial.println(xRxedStructure.ucData);
      }
   }
   else
   {
      Serial.println("LOOP: Нет очереди!");
   }
   delay(1300);
}

// *************************************************** QueueHandlMulti2.ino ***

В этом примере строится очередь для хранения структур в двумя полями: первое - это строка с, максимум, 256 символами; второе количество байт с символами в строке. Прерывание вызывается по таймеру и отправляет сообщение в очередь с указанием времени в миллисекундах с момента запуска приложения. Также в приложении периодически запускается задача, которая отправляет сообщение в очередь с указанием количества раз запуска задачи. Все сообщения принимаются в основном цикле и передаются в последовательный порт.

Ниже представлен протокол работы приложения:

TASK: Очередь для структур не создана!
SETUP: Очередь сформирована!
LOOP: Не удалось принять структуру даже после 10 тиков!
LOOP: Не удалось принять структуру даже после 10 тиков!
40: Прошло 1893 миллисекунд
55: Передано 2 сообщение из задачи
40: Прошло 3293 миллисекунд
55: Передано 3 сообщение из задачи
40: Прошло 4693 миллисекунд
55: Передано 4 сообщение из задачи
40: Прошло 6093 миллисекунд
55: Передано 5 сообщение из задачи
40: Прошло 7493 миллисекунд
55: Передано 6 сообщение из задачи
40: Прошло 8893 миллисекунд
ISR: Не удалось отправить структуру!
55: Передано 7 сообщение из задачи
ISR: Не удалось отправить структуру!
41: Прошло 10293 миллисекунд
41: Прошло 11693 миллисекунд
TASK: Не удалось отправить структуру даже после 5 тиков!
55: Передано 8 сообщение из задачи
41: Прошло 13093 миллисекунд
TASK: Не удалось отправить структуру даже после 5 тиков!
55: Передано 9 сообщение из задачи
TASK: Не удалось отправить структуру даже после 5 тиков!
41: Прошло 14493 миллисекунд
TASK: Не удалось отправить структуру даже после 5 тиков!
56: Передано 10 сообщение из задачи
TASK: Не удалось отправить структуру даже после 5 тиков!
41: Прошло 15893 миллисекунд
56: Передано 11 сообщение из задачи
ISR: Не удалось отправить структуру!
56: Передано 12 сообщение из задачи
ISR: Не удалось отправить структуру!
56: Передано 13 сообщение из задачи
ISR: Не удалось отправить структуру!
41: Прошло 20093 миллисекунд
TASK: Не удалось отправить структуру даже после 5 тиков!
41: Прошло 21493 миллисекунд
41: Прошло 22893 миллисекунд
...

Библиография

Queue Management

FreeRTOS queues

Очереди FreeRTOS

ESP32 Arduino: FreeRTOS Queues

к содержанию