01.初识freeRTOS
02.FreeRTOS的移植
03.FreeRTOS系统配置
04.FreeRTOS任务创建与删除
05.FreeRTOS任务挂起与恢复
06.FreeRTOS中断管理
07.FreeRTOS列表与列表项
08.FreeRTOS任务调度与任务切换
09.FreeRTOS时间片调度与任务相关函数
10.FreeRTOS队列操作
11.FreeRTOS信号量操作
12.FreeRTOS队列集和事件标志组
13.FreeRTOS任务通知
14.FreeRTOS软件定时器
15.FreeRTOS低功耗
16.FreeRTOS内存管理
10. FreeRTOS队列操作
1. 队列简介
- 队列的简介:
队列是一种任务到任务、任务到中断、中断到任务数据交流的一种机制。在队列中可以存储数量有限、大小固定的多个数据,队列中的每一个数据叫做队列项目,队列能够存储队列项目的最大数量称为队列的长度,在创建队列的时候,就需要指定所创建队列的长度及队列项目的大小。因为队列是用来在任务与任务或任务于中断之间传递消息的一种机制,因此队列也叫做消息队列。
-
队列的特点:
-
数据存储
队列通常采用FIFO(先进先出)的存储缓冲机制,当有新的数据被写入队列中时,永远都是写入到队列的尾部,而从队列中读取数据时,永远都是读取队列的头部数据。但同时FreeRTOS的队列也支持将数据写入到队列的头部,并且还可以指定是否覆盖先前己经在队列头部的数据。
-
多任务访问
队列不属于某个特定的任务,可以在任何的任务或中断中往队列中写入消息,或者从队列中读取消息。
-
队列读取阻塞
在任务从队列读取消息时,可以指定一个阻塞超时时间。如果任务在读取队列时,队列为空,这时任务将被根据指定的阻塞超时时间添加到阻塞态任务列表中进行阻塞,以等待队列中有可用的消息。当有其他任务或中断将消息写入队列中,因等待队列而阻塞任务将会被添加到就绪态任务列表中,并读取队列中可用的消息。如果任务因等待队列而阻塞的时间超过指定的阻塞超时时间,那么任务也将自动被转移到就绪态任务列表中,但不再读取队列中的数据。
-
队列写入阻塞
与队列读取一样,在任务往队列写入消息时,也可以指定一个阻塞超时时间。如果任务在写入队列时,队列已经满了,这时任务将被根据指定的阻塞超时时间添加到阻塞态任务列表中进行阻塞,以等待队列有空闲的位置可以写入消息。指定的阻塞超时时间为任务阻塞的最大时间,如果在阻塞超时时间到达之前,队列有空闲的位置,那么队列写入阻塞任务将会解除阻塞,并往队列中写入消息,如果达到指定的阻塞超时时间,队列依旧没有空闲的位置写入消息,那么队列写入阻塞任务将会自动转移到就绪态任务列表中,但不会往队列中写入消息。
-
-
队列操作基本过程
-
创建队列
创建了一个用于任务A与任务B之间“沟通交流”的队列,这个队列最大可容纳5个队列项目,即队列的长度为5。刚创建的队列是不包含内容的,因此这个队列为空。
-
往队列写入第一个消息
任务A将一个私有变量写入队列的尾部。由于在写入队列之前,队列是空的,因此新写入的消息,既是是队列的头部,也是队列的尾部。
-
往队列写入第二个消息
任务A改变了私有变量的值,并将新值写入队列。现在队列中包含了队列A写入的两个值,其中第一个写入的值在队列的头部,而新写入的值在队列的尾部。这时队列还有3个空闲的位置。
-
从队列读取第一个消息
任务B从队列中读取消息,任务B读取的消息是处于队列头部的消息,这是任务A第一次往队列中写入的消息。在任务B从队列中读取消息后,队列中任务A第二次写入的消息,变成了队列的头部,因此下次任务B再次读取消息时,将读取到这个消息。此时队列中剩余4个空闲的位置。
-
2. 队列结构体介绍
-
队列结构体定义:
-
队列结构体整体示意图:
3. 队列相关API函数介绍
-
创建队列:
函数的参数与返回值:
函数的其他功能:
# xQueueGenericCreate()的其他功能 #define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) /* 队列 */ #define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U ) /* 队列集 */ #define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) /* 互斥信号量 */ #define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) /* 计数型信号量 */ #define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) /* 二值信号量 */ #define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) /* 递归互斥信号量 */
函数的执行过程:
-
写入队列相关函数
函数 描述 xQueueSend() 往队列的尾部写入消息 xQueueSendToBack() 同 xQueueSend() xQueueSendToFront() 往队列的头部写入消息 xQueueOverwrite() 覆写队列消息(只用于队列长度为 1 的情况) xQueueSendFromISR() 在中断中往队列的尾部写入消息 xQueueSendToBackFromISR() 同 xQueueSendFromISR() xQueueSendToFrontFromISR() 在中断中往队列的头部写入消息 xQueueOverwriteFromISR() 在中断中覆写队列消息(只用于队列长度为 1 的情况) 在任务中往队列写入消息的函数有四个,它们均为宏定义,定义如下:
调用的函数都为xQueueGenericSend(),只是写入的位置不同,队列的三种写入方式为:
函数xQueueGenericSend()的入口参数与返回值:
函数执行过程:
- 参数验证
configASSERT(pxQueue); configASSERT(!( (pvItemToQueue == NULL) && (pxQueue->uxItemSize != (UBaseType_t) 0U))); configASSERT(!( (xCopyPosition == queueOVERWRITE) && (pxQueue->uxLength != 1) )); #if ( (INCLUDE_xTaskGetSchedulerState == 1) || (configUSE_TIMERS == 1) ) { configASSERT(!( (xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) && (xTicksToWait != 0) )); } #endif
configASSERT(pxQueue)
: 确保队列句柄pxQueue
不为空。configASSERT(!( (pvItemToQueue == NULL) && (pxQueue->uxItemSize != (UBaseType_t) 0U)))
: 确保如果队列项的大小大于0,则消息指针不能为NULL
。configASSERT(!( (xCopyPosition == queueOVERWRITE) && (pxQueue->uxLength != 1) ))
: 如果使用queueOVERWRITE
模式,则队列长度必须为1。configASSERT(!( (xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) && (xTicksToWait != 0) ))
: 如果调度器被挂起并且xTicksToWait
不为0,则检查调度器状态。
- 进入临界区
taskENTER_CRITICAL();
taskENTER_CRITICAL()
: 关闭中断,以保护对队列的操作,确保线程安全。
- 检查队列状态
if ((pxQueue->uxMessagesWaiting < pxQueue->uxLength) || (xCopyPosition == queueOVERWRITE)) { traceQUEUE_SEND(pxQueue); #if (configUSE_QUEUE_SETS == 1) { const UBaseType_t uxPreviousMessagesWaiting = pxQueue->uxMessagesWaiting; xYieldRequired = prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition); if (pxQueue->pxQueueSetContainer != NULL) { if ((xCopyPosition == queueOVERWRITE) && (uxPreviousMessagesWaiting != (UBaseType_t) 0)) { mtCOVERAGE_TEST_MARKER(); } else if (prvNotifyQueueSetContainer(pxQueue) != pdFALSE) { queueYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } else { if (listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE) { if (xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive)) != pdFALSE) { queueYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } else if (xYieldRequired != pdFALSE) { queueYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } } #else { xYieldRequired = prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition); if (listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE) { if (xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive)) != pdFALSE) { queueYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } else if (xYieldRequired != pdFALSE) { queueYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif taskEXIT_CRITICAL(); return pdPASS; }
if ((pxQueue->uxMessagesWaiting < pxQueue->uxLength) || (xCopyPosition == queueOVERWRITE))
: 检查队列是否有空间,或是否允许覆盖写入。traceQUEUE_SEND(pxQueue)
: 记录队列发送的跟踪信息。prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition)
: 将数据复制到队列中。if (pxQueue->pxQueueSetContainer != NULL)
: 如果队列属于队列集,则通知队列集。xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive))
: 如果有任务在等待接收数据,则解除其阻塞。queueYIELD_IF_USING_PREEMPTION()
: 根据任务的优先级决定是否进行任务切换。
- 处理队列满的情况
else { if (xTicksToWait == (TickType_t) 0) { taskEXIT_CRITICAL(); traceQUEUE_SEND_FAILED(pxQueue); return errQUEUE_FULL; } else if (xEntryTimeSet == pdFALSE) { vTaskInternalSetTimeOutState(&xTimeOut); xEntryTimeSet = pdTRUE; } }
if (xTicksToWait == (TickType_t) 0)
: 如果队列满且不允许阻塞,退出临界区并返回errQUEUE_FULL
。else if (xEntryTimeSet == pdFALSE)
: 如果允许阻塞且超时未设置,配置超时结构。
- 处理阻塞和超时
taskEXIT_CRITICAL(); vTaskSuspendAll(); prvLockQueue(pxQueue); if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait) == pdFALSE) { if (prvIsQueueFull(pxQueue) != pdFALSE) { traceBLOCKING_ON_QUEUE_SEND(pxQueue); vTaskPlaceOnEventList(&(pxQueue->xTasksWaitingToSend), xTicksToWait); prvUnlockQueue(pxQueue); if (xTaskResumeAll() == pdFALSE) { portYIELD_WITHIN_API(); } } else { prvUnlockQueue(pxQueue); (void) xTaskResumeAll(); } } else { prvUnlockQueue(pxQueue); (void) xTaskResumeAll(); traceQUEUE_SEND_FAILED(pxQueue); return errQUEUE_FULL; }
vTaskSuspendAll()
: 暂停任务调度器,确保任务调度不影响阻塞。prvLockQueue(pxQueue)
: 锁定队列,防止其他任务对其进行操作。if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait) == pdFALSE)
: 检查超时是否到期。如果未到期,继续处理。if (prvIsQueueFull(pxQueue) != pdFALSE)
: 如果队列仍然满,将当前任务放入事件列表,进行阻塞。vTaskPlaceOnEventList(&(pxQueue->xTasksWaitingToSend), xTicksToWait)
: 将当前任务放到等待发送事件的列表中。prvUnlockQueue(pxQueue)
: 解锁队列。if (xTaskResumeAll() == pdFALSE)
: 恢复任务调度器。如果需要,进行任务切换。else
: 如果队列不再满,尝试重新发送消息。else
: 如果超时已过,解除锁定,恢复任务调度器,返回errQUEUE_FULL
。
-
队列读取相关函数
函数 描述 xQueueReceive() 从队列头部读取消息,并删除消息 xQueuePeek() 从队列头部读取消息 xQueueReceiveFromISR() 在中断中从队列头部读取消息,并删除消息 xQueuePeekFromISR() 在中断中从队列头部读取消息 函数 xQueueReceive()的入口参数与返回值:
函数 xQueuePeek()的入口参数与返回值:
函数执行过程:
-
参数检查:
configASSERT( ( pxQueue ) ); configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );
- 确保
pxQueue
不为空。 - 如果
pvBuffer
为空,则只有在uxItemSize
为0的情况下才允许(表示队列中没有实际的数据存储)。
- 确保
-
调度器状态检查:
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ) configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) ); #endif
- 如果调度器被挂起且
xTicksToWait
不为0,则会产生断言失败,因为在这种状态下任务不能阻塞。
- 如果调度器被挂起且
-
主循环:
for( ; ; )
进入无限循环来处理队列数据接收和等待超时。
-
临界区保护:
taskENTER_CRITICAL();
进入临界区以防止中断和其他任务对队列的访问。
-
检查队列数据:
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting; if (uxMessagesWaiting > (UBaseType_t) 0)
- 检查队列中是否有数据。如果有数据,则:
- 从队列中复制数据到
pvBuffer
。 - 记录接收事件。
- 减少队列中的消息计数。
- 检查是否有任务在等待发送数据到队列,如果有,解除这些任务的阻塞。
- 退出临界区并返回
pdPASS
表示成功接收数据。
- 从队列中复制数据到
prvCopyDataFromQueue( pxQueue, pvBuffer ); traceQUEUE_RECEIVE( pxQueue ); pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;
- 检查队列中是否有数据。如果有数据,则:
-
队列为空时处理:
if (xTicksToWait == (TickType_t) 0)
- 如果
xTicksToWait
为0且队列为空,退出并返回errQUEUE_EMPTY
。
traceQUEUE_RECEIVE_FAILED( pxQueue ); return errQUEUE_EMPTY;
- 否则,设置超时状态并进入等待。
if (xEntryTimeSet == pdFALSE) { vTaskInternalSetTimeOutState( &xTimeOut ); xEntryTimeSet = pdTRUE; }
- 如果
-
退出临界区:
taskEXIT_CRITICAL();
- 退出临界区,允许其他任务和中断对队列进行操作。
-
挂起任务调度器:
vTaskSuspendAll();
- 挂起调度器,以确保当前任务在等待过程中不会被调度出去。
-
队列锁定:
prvLockQueue( pxQueue );
- 锁定队列,防止其他任务在此期间对队列进行操作。
-
检查超时状态并处理:
if (xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE)
- 检查超时状态。如果超时未过,继续等待:
- 如果队列仍然为空,将当前任务添加到等待接收的任务列表中。
- 解锁队列并恢复任务调度器。
if (prvIsQueueEmpty( pxQueue ) != pdFALSE) { traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue ); vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait ); prvUnlockQueue( pxQueue ); if (xTaskResumeAll() == pdFALSE) { portYIELD_WITHIN_API(); } } else { prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); }
- 检查超时状态。如果超时未过,继续等待:
-
处理超时情况:
- 如果超时已经到达,但队列仍然为空,解锁队列,恢复任务调度器,并返回
errQUEUE_EMPTY
。
if (prvIsQueueEmpty( pxQueue ) != pdFALSE) { traceQUEUE_RECEIVE_FAILED( pxQueue ); return errQUEUE_EMPTY; }
- 如果超时已经到达,但队列仍然为空,解锁队列,恢复任务调度器,并返回
-
重复尝试:
- 如果队列现在有数据,再次尝试接收数据。
内部函数调用时机
-
prvCopyDataFromQueue
:- 在队列中有数据时调用,从队列中复制数据到缓冲区。
-
vTaskInternalSetTimeOutState
:- 当队列为空且需要阻塞等待数据时,设置超时状态。
-
xTaskRemoveFromEventList
:- 在队列中有数据并且有任务在等待发送数据时,解除这些任务的阻塞。
-
vTaskPlaceOnEventList
:- 当队列仍然为空且需要阻塞等待数据时,将当前任务放到等待接收任务列表中。
-
prvLockQueue
和prvUnlockQueue
:- 在等待数据和处理超时时,锁定和解锁队列以确保数据的一致性。
-
xTaskCheckForTimeOut
:- 检查超时状态,确定是否需要继续阻塞或解除阻塞。
-
portYIELD_WITHIN_API
:- 在任务调度恢复后,执行任务切换,以确保高优先级任务能够被调度。
-
4. 队列操作实验
创建的任务以及任务的功能:
相关任务:
队列创建:
//队列创建
key_queue = xQueueCreate(2, sizeof(uint8_t));
if(key_queue != NULL)
{
printf("key_queue队列创建成功!\r\n");
}
else{
printf("key_queue队列创建失败!\r\n");
}
big_date_queue = xQueueCreate(1, sizeof(char *));
if(big_date_queue != NULL)
{
printf("big_date_queue队列创建成功!\r\n");
}
else{
printf("big_date_queue队列创建失败!\r\n");
}
任务1:入队操作
/*任务一:入队操作*/
void task1(void* pvParamter)
{
uint8_t key = 0;
char * buf;
buf = &buff[0];
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES || key == KEY1_PRES)
{
if(xQueueSend(key_queue, &key, portMAX_DELAY) != pdTRUE)
{
printf("key_queue队列发送失败\r\n");
}
}
else if(key == WKUP_PRES)
{
if(xQueueSend(big_date_queue, &buf, portMAX_DELAY) != pdTRUE)
{
printf("big_date_queue队列发送失败\r\n");
}
}
vTaskDelay(20);
}
}
任务2:小数据出队
/*任务二:小数据出队*/
void task2(void* pvParamter)
{
uint8_t key = 0;
while(1)
{
if(xQueueReceive(key_queue, &key, portMAX_DELAY) != pdTRUE)
{
printf("key_queue队列读取失败\r\n");
}
else
{
printf("读取队列成功,数据%d\r\n",key);
}
}
}
任务3:大数据出队
/*任务三:大数据出队*/
void task3(void* pvParamter)
{
char * buf;
while(1)
{
if(xQueueReceive(big_date_queue, &buf, portMAX_DELAY) != pdTRUE)
{
printf("big_date_queue队列读取失败\r\n");
}
else
{
printf("读取队列成功,数据%s\r\n",buf);
}
}
}
实验结果: