队列的概念
队列用于任务间通信的数据结构,通过消息队列服务,任务或中断服务将消息放入消息队列中。其他任务或者自身从消息队列中获得消息。实现队列可以在任务与任务间、中断和任务间传递信息。队列操作支持阻塞等待,向已经填满的队列发送数据或者从空队列读出数据,都会导致阻塞,时间自定义。消息队列的运作过程具如下:
队列创建
xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的句柄。队列句柄其实就是一个指向队列数据结构类型的指针。
-
master_queue = xQueueCreate(50, sizeof(track_task_message_struct_t));
创建队列,占用50个单元,每个单元为sizeof(track_task_message_struct_t)字节,和 malloc比较类似。
其最终使用的函数是 xQueueGenericCreate(),后续信号量等也是使用它创建,只是最后的队列类型不同。
申请内存后,xQueueGenericReset再对其进行初始化,队列的结构体xQUEUE成员:
-
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
-
{
-
int8_t * pcHead; /*< Points to the beginning of the queue storage area. */
-
int8_t * pcWriteTo; /*< Points to the free next place in the storage area. */
-
//类型
-
union
-
{
-
QueuePointers_t xQueue; /*< Data required exclusively when this structure is used as a queue. */
-
SemaphoreData_t xSemaphore; /*< Data required exclusively when this structure is used as a semaphore. */
-
} u;
-
//当前向队列写数据阻塞的任务列表或者从队列取数阻塞的链表
-
List_t xTasksWaitingToSend;
-
List_t xTasksWaitingToReceive;
-
//队列里有多少个单元被占用,应用中需要
-
volatile UBaseType_t uxMessagesWaiting;
-
UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */
-
UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. */
-
/******/
-
} xQUEUE;
队列删除
队列删除函数 vQueueDelete()需传入要删除的消息队列的句柄即可,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了。实际应用中很少使用。
向队列发送消息
任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队,FreeRTOS 会将消息拷贝到消息队列队尾,否则,会根据用户指定的超时时间进行阻塞,消息发送接口很多,最简单的是 xQueueSend(),用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,该函数绝对不能在中断服务程序里面被调用,中断中必须使用带有中断保护功能的 xQueueSendFromISR()来代替。
BaseType_t xQueueSend(QueueHandle_t xQueue,const void* pvItemToQueue, TickType_t xTicksToWait);
用于向队列尾部发送一个队列消息
参数
xQueue 队列句柄
pvItemToQueue 指针,指向要发送到队列尾部的队列消息。
xTicksToWait 队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait 被设置成 0,函数立刻返回。超时时间的单位为系统节拍周期 tick,延时为 portMAX_DELAY 将导致任务挂起(没有超时)。
返回值
消息发送成功成功返回 pdTRUE,否则返回 errQUEUE_FULL
xQueueSendToBack与xQueueSend完全相同, xQueueSendFromISR()与 xQueueSendToBackFromISR(),带FromISR表示只能在中断中使用,freeRTOS所以带这个后缀的都是这个含义。
xQueueSendToFront()和QueueSendToFrontFromISR()用于向队列队首发送一个消息。 这些在任务中发送消息的函数都是 xQueueGenericSend()展开的宏定义。
-
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, //任务句柄
-
const void * const pvItemToQueue, //指向要发送到队列的数据项的指针
-
TickType_t xTicksToWait, //函数将等待队列有空位的最长时间(以系统节拍为单位)
-
const BaseType_t xCopyPosition ) //发送数据到消息队列的位置
一般使用xQueueSend和xQueueSendFromISR,如不确定当前运行的是系统服务,还是中断服务,一般ARM都支持查询中断状态寄存器判断,可以封装一层接口,只管发消息,内部判断是否使用支持中断嵌套的版本,UIS8910就是如此。
特殊情况下,如发送网络数据包未收到服务器响应,期望立刻入队再次发送它,可以xQueueSendToFront向队头发消息。
从队列读取消息
当任务试图读队列中的消息时,可以指定一个阻塞超时时间,当且仅当消息队列中有消息的时候,任务才能读取到消息。如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。所有的task主入口while循环体都是按这个执行。例如:
-
static void track_master_task_main()
-
{
-
track_task_message_struct_t queue_item = {0};
-
/****/
-
while(1)
-
{
-
if(xQueueReceive(master_queue, &queue_item, portMAX_DELAY))//阻塞等待
-
{
-
track_master_task_msg_handler(&queue_item);
-
}
-
}
-
}
xQueueReceive()用于从一个队列中接收消息并把消息从队列中删除。
如果不想删除消息的话,就调用 xQueuePeek()函数。
xQueueReceiveFromISR()与xQueuePeekFromISR()是中断版本,用于在中断服务程序中接收一个队列消息并把消息。这两个函数只能用于中断,是不带有阻塞机制的,实际项目没有使用。
查询队列使用情况
uxQueueMessagesWaiting()查询队列中存储的信息数目,具有中断保护的版本为uxQueueMessagesWaitingFromISR()。
查询队列的空闲数目uxQueueSpacesAvailable()。
队列使用注意点
使用队列函数需要注意以下几点:
1、中断中必须使用带FromISR后缀的接口;
2、发送或者是接收消息都是以拷贝的方式进行,如果消息内容过于庞大,可以将消息的地址作为消息进行发送、接收。
-
typedef struct
-
{
-
TaskHandle_t src_mod_id;
-
int message_id;
-
int32_t param;
-
union
-
{
-
int32_t result;
-
int32_t socket_id;
-
};
-
void* pvdata; //大数据使用动态申请内存保存,队列只传递指针
-
} track_task_message_struct_t;
3、队列并不属于任何任务,所有任务都可以向同一队列写入和读出,一个队列可以由多任务或中断读写。 4、队列的深度要结合实际,可以多申请点,前提是每个队列单元尽可能小。 5、队列存在一定限制,在队头没有取出来之前,是无法取出第二个,和以前使用的STL存在差异。
创建任务和队列
#define UART_TASK_PRIO 1 //任务优先级
#define UART_TASK_STK_SIZE 80 //任务堆栈大小
TaskHandle_t UARTTaskHandler; //任务句柄
void UARTTaskFunc(void *pvParameters); //任务函数
#define TEST_TASK0_PRIO 2 //任务优先级
#define TEST_TASK0_STK_SIZE 50 //任务堆栈大小
TaskHandle_t TestTask0Handler; //任务句柄
void TestTask0Func(void *pvParameters); //任务函数
#define TEST_TASK1_PRIO 3 //任务优先级
#define TEST_TASK1_STK_SIZE 80 //任务堆栈大小
TaskHandle_t TestTask1Handler; //任务句柄
void TestTask1Func(void *pvParameters); //任务函数
#define QUEUE_TASK_PRIO 4 //任务优先级
#define QUEUE_TASK_STK_SIZE 80 //任务堆栈大小
TaskHandle_t QueueTaskHandler; //任务句柄
void QueueTaskFunc(void *pvParameters); //任务函数
//LCD显示任务当前计数,并把当前计数写入TaskToTaskQueue队列函数
void UARTTaskFunc(void *pvParameters);
//Test0任务函数,读取 TaskToTaskQueue的数据并打印
void TestTask0Func(void *pvParameters);
//Test1任务函数
void TestTask1Func(void *pvParameters);
//从队列读取串口收到的数据并打印任务函数
void QueueTaskFunc(void *pvParameters);
QueueHandle_t TaskToIrqQueue; //信息队列句柄
QueueHandle_t TaskToTaskQueue; //信息队列句柄
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init /
* /* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX /
* /* add mutexes, ... /
* /* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES /
* /* add semaphores, ... /
* /* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS /
* /* start timers, add new ones, ... /
* /* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES /
* /* add queues, ... /
* /* USER CODE END RTOS_QUEUES */
/* Create the thread(s) /
* /* creation of defaultTask */
//defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS /
* /* add threads, ... */
BaseType_t ret;
TaskToIrqQueue=xQueueCreate(1,255); //创建一个队列,队列有1项,每个项长为255byte。
TaskToTaskQueue=xQueueCreate(5,2); //创建一个队列,队列有5项,每个项长为2byte
ret=xTaskCreate((TaskFunction_t )UARTTaskFunc, (const char* )"lcdtask", (uint16_t )UART_TASK_STK_SIZE,
(void* )NULL, (UBaseType_t )UART_TASK_PRIO, (TaskHandle_t* )&UARTTaskHandler);
ret=xTaskCreate((TaskFunction_t )TestTask0Func, (const char* )"testtask0", (uint16_t )TEST_TASK0_STK_SIZE,
(void* )NULL, (UBaseType_t )TEST_TASK0_PRIO, (TaskHandle_t* )&TestTask0Handler);
ret=xTaskCreate((TaskFunction_t )TestTask1Func, (const char* )"testtask1", (uint16_t )TEST_TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TEST_TASK1_PRIO,
(TaskHandle_t* )&TestTask1Handler);
ret=xTaskCreate((TaskFunction_t )QueueTaskFunc, (const char* )"queue task", (uint16_t )QUEUE_TASK_STK_SIZE,
(void* )NULL,
(UBaseType_t )QUEUE_TASK_PRIO,
(TaskHandle_t* )&QueueTaskHandler);
xTaskCreate(LED_Test,"LED",254,NULL,osPriorityNormal,&LED123);
//xTaskCreate(Key_Test,"KEY",254,NULL,osPriorityNone,&KEY123);
vTaskStartScheduler();
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS /
* /* add events, ... /
* /* USER CODE END RTOS_EVENTS */
}
各个任务函数:
//显示任务当前计数,并把当前计数写入TaskToTaskQueue队列函数
void UARTTaskFunc(void *pvParameters)
{
static uint8_t i=0;
for(;;)
{
i++; xQueueSend(TaskToTaskQueue,&i,20);
vTaskDelay(500); //延时500ms,也就是500个时钟节拍 }
}
//Test0任务函数,读取 TaskToTaskQueue的数据并通过串口打印出来
void TestTask0Func(void *pvParameters)
{
uint8_t i;
char a[20];
while(1)
{ if(TaskToTaskQueue!=NULL) { xQueueReceive(TaskToTaskQueue,&i,portMAX_DELAY);
sprintf((char *)a,"%d",i);
printf("%s\r\n",a); }
}
}
//Test1任务函数
void TestTask1Func(void *pvParameters)
{
while(1)
{
printf("task1\r\n");
vTaskDelay(1000);
}
}
//从队列读取串口收到的数据并打印任务函数
void QueueTaskFunc(void *pvParameters)
{
for(;;)
{
if(TaskToIrqQueue!=NULL)
{
if(xQueueReceive(TaskToIrqQueue,rx_buf,10))
{
printf("serial:%s\r\n",rx_buf);
}
}
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
HAL_UART_Receive_IT(&huart1,&rx_data,1);
rx_buf[rx_pointer++] = rx_data;
BaseType_t pxHigherPriorityTaskWoken=pdFALSE;
xQueueSendFromISR(TaskToIrqQueue,rx_buf,&pxHigherPriorityTaskWoken);
//向队列写入数据,如果写队列使某个任务就绪,并且这个任务是当前所有任务中优先级最高的,那么pxHigherPriorityTaskWoken将会被设置为pdTRUE
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
//如果pxHigherPriorityTaskWoken为真将进行任务切换
}