根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。
配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!
队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息。
任务能够从队列里面读取消息,当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定阻塞的任务时间 xTicksToWait。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。
当队列中有新消息时, 被阻塞的任务会被唤醒并处理新消息;
当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。
消息队列是一种异步的通信方式。
通过消息队列服务,任务或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个任务可以从消息队列中获得消息。
当有多个消息发送到消息队列时,默认先进先出原则(FIFO),但是也支持后进先出原则(LIFO)。
FreeRTOS 中使用队列数据结构实现任务异步通信工作,具有如下特性:
消息支持先进先出方式排队,支持异步读写工作方式。
读写队列均支持超时机制。
消息支持后进先出方式排队, 往队首发送消息(LIFO) 。
可以允许不同长度(不超过队列节点最大值)的任意类型消息。
一个任务能够从任意一个消息队列接收和发送消息。
多个任务能够从同一个消息队列接收和发送消息。
当队列使用结束后,可以通过删除队列函数进行删除。
typedef struct QueueDefinition
{
int8_t *pcHead; //头指针,指向队列消息存储区起始位置地址
int8_t *pcTail; //尾指针,指向队列消息存储区结束位置地址
int8_t *pcWriteTo; //指向队列消息存储区下一个可用消息空间
//pcReadFrom 与 uxRecursiveCallCount 是一对互斥变量, 使用联合体用来确保两个互斥的结构体成员不会同时出现。
//当结构体用于队列时, pcReadFrom 指向出队消息空间的最后一个,即读取消息时候是从 pcReadFrom 指向的空间读取消息内容
//当结构体用于互斥量时, uxRecursiveCallCount 用于计数,记录递归互斥量被“调用” 的次数。
union
{
int8_t *pcReadFrom;
UBaseType_t uxRecursiveCallCount;
}u;
List_t xTasksWaitingToSend; //发送消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序,由于队列已满,想要发送消息的任务无法发送消息。
List_t xTasksWaitingToReceive; //获取消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序,由于队列是空的,想要获取消息的任务无法获取到消息。
volatile UBaseType_t uxMessagesWaiting; //记录当前消息队列的消息个数, 如果消息队列被用于信号量的时候,这个值就表示有效信号量个数
UBaseType_t uxLength; //队列长度
UBaseType_t uxItemSize; //单个消息大小
//这两个成员变量为 queueUNLOCKED 时,表示队列未上锁;当这两个成员变量为 queueLOCKED_UNMODIFIED 时,表示队列上锁。
volatile int8_t cRxLock; //队列上锁后,储存从队列收到的列表项数目,也就是出队的数量,如果队列没有上锁,设置为 queueUNLOCKED。
volatile int8_t cTxLock; //队列上锁后,储存发送到队列的列表项数目,也就是入队的数量,如果队列没有上锁,设置为 queueUNLOCKED。
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated;
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition *pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
typedef xQUEUE Queue_t;
使用队列模块的典型流程如下:
创建消息队列。
写队列操作。
读队列操作。
删除队列。
xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的队列句柄。 队列句柄其实就是一个指向队列数据结构类型的指针。
xQueueCreate()函数如下
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
//uxQueueLength为队列长度。uxItemSize为队列中消息单元的大小,以字节为单位。
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
size_t xQueueSizeInBytes; //消息存储空间大小
uint8_t *pucQueueStorage; //消息存储空间起始地址
configASSERT( uxQueueLength > ( UBaseType_t ) 0 );
if( uxItemSize == ( UBaseType_t ) 0 )
{
xQueueSizeInBytes = ( size_t ) 0;
}
else
{
//分配足够存储消息的空间,消息存储空间大小=队列长度*单个消息大小
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
}
//向系统申请内存,内存大小为消息队列控制块大小+消息存储空间大小
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
if( pxNewQueue != NULL )
{
//计算出消息存储空间的起始地址
pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
//初始化消息队列
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
}
else
{
traceQUEUE_CREATE_FAILED( ucQueueType );
}
return pxNewQueue;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
上述函数用到了初始化消息队列函数,prvInitialiseNewQueue()。
prvInitialiseNewQueue()函数如下
/*
消息队列类型:
queueQUEUE_TYPE_BASE:表示队列。
queueQUEUE_TYPE_SET:表示队列集合。
queueQUEUE_TYPE_MUTEX:表示互斥量。
queueQUEUE_TYPE_COUNTING_SEMAPHORE:表示计数信号量。
queueQUEUE_TYPE_BINARY_SEMAPHORE:表示二进制信号量。
queueQUEUE_TYPE_RECURSIVE_MUTEX:表示递归互斥量。
*/
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, //队列长度
const UBaseType_t uxItemSize, //单个消息大小
uint8_t *pucQueueStorage, //存储消息起始地址
const uint8_t ucQueueType, //消息队列类型
Queue_t *pxNewQueue ) //消息队列控制块
{
( void ) ucQueueType;
if( uxItemSize == ( UBaseType_t ) 0 )
{
/* 没有为消息存储分配内存,但是 pcHead 指针不能设置为 NULL,
因为队列用作互斥量时,pcHead 要设置成 NULL。
这里只是将 pcHead 指向一个已知的区域 */
pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else
{
//设置 pcHead 指向存储消息的起始地址
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
}
/* 初始化消息队列控制块的其他成员 */
pxNewQueue->uxLength = uxQueueLength;
pxNewQueue->uxItemSize = uxItemSize;
//重置消息队列
( void ) xQueueGenericReset( pxNewQueue, pdTRUE );
#if ( configUSE_TRACE_FACILITY == 1 )
{
pxNewQueue->ucQueueType = ucQueueType;
}
#endif /* configUSE_TRACE_FACILITY */
#if( configUSE_QUEUE_SETS == 1 )
{
pxNewQueue->pxQueueSetContainer = NULL;
}
#endif /* configUSE_QUEUE_SETS */
traceQUEUE_CREATE( pxNewQueue );
}
上述函数用到了重置消息队列函数,xQueueGenericReset()。
xQueueGenericReset()如下
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
taskENTER_CRITICAL();
{
pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize ); //pcTail 指向存储消息内存空间的结束地址
pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U; //当前消息队列中的消息个数
pxQueue->pcWriteTo = pxQueue->pcHead; //pcWriteTo 指向队列消息存储区下一个可用消息空间
pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize ); //pcReadFrom 指向消息队列最后一个消息空间
pxQueue->cRxLock = queueUNLOCKED;
pxQueue->cTxLock = queueUNLOCKED;
if( xNewQueue == pdFALSE )
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
至此,消息队列的创建内容就已经结束,多数函数都已经被官方封装好,我们直接用就行。
xQueueCreate()。
如果删除消息队列时,有任务正在等待消息, 则不应该进行删除操作。
vQueueDelete()函数如下
void vQueueDelete( QueueHandle_t xQueue ) //删除消息队列和信号量
{
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
traceQUEUE_DELETE( pxQueue ); //删除消息队列
#if ( configQUEUE_REGISTRY_SIZE > 0 )
{
vQueueUnregisterQueue( pxQueue ); //将消息队列从注册表中删除
}
#endif
#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
{
vPortFree( pxQueue ); //释放消息队列的内存
}
#elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )
{
if( pxQueue->ucStaticallyAllocated == ( uint8_t ) pdFALSE )
{
vPortFree( pxQueue );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#else
{
( void ) pxQueue;
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}
至此,消息队列的删除内容就已经结束,多数函数都已经被官方封装好,我们直接用就行。
vQueueDelete()。
任务或者中断服务程序都可以给消息队列发送消息。
当发送消息时,如果队列未满或者允许覆盖入队, FreeRTOS 会将消息拷贝到消息队列队尾,否则,会根据用户指定的阻塞超时时间进行阻塞。
在这段时间中,如果队列一直不允许入队,该任务将保持阻塞状态以等待队列允许入队。
当其它任务从其等待的队列中读取了数据(队列未满),该任务将自动由阻塞态转为就绪态。
当任务等待的时间超过了指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或者中断程序会收到一个错误码 errQUEUE_FULL。
发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,发送的位置是消息队列队头而非队尾,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理
xQueue, 队列句柄,
pvItemToQueue, 指向要发送到队列尾部/首部的队列消息的指针
xTicksToWait为队列满时,等待队列空闲的最大超时时间。如果队列满并且 xTicksToWait 被设置成 0,函数立刻返回。
消息发送成功返回pdTRUE,否则返回errQUEUE_FULL。
//向队列尾部发送一个队列消息,该函数绝对不能在中断服务程序里面被调用,而是必须使用带有中断保护功能的 xQueueSendFromISR()来代替。
//向队列首部发送一个队列消息,该函数绝对不能在中断服务程序里面被调用,而是必须使用带有中断保护功能的 xQueueSendToFrontFromISR ()来代替。
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
xQueueSend()函数实例
static void Send_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
uint32_t send_data1 = 1;
uint32_t send_data2 = 2;
while (1)
{
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) /* K1 被按下 */
{
printf("发送消息 send_data1! \n");
xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
&send_data1,/* 发送的消息内容 */
0 ); /* 等待时间 0 */
if (xReturn == pdPASS)
printf("消息 send_data1 发送成功!\n\n");
}
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) /* K2 被按下 */
{
printf("发送消息 send_data2! \n");
xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
&send_data2,/* 发送的消息内容 */
0 ); /* 等待时间 0 */
if (pdPASS == xReturn)
printf("消息 send_data2 发送成功!\n\n");
}
vTaskDelay(20);/* 延时 20 个 tick */
}
}
xQueue, 队列句柄,
pvItemToQueue, 指向要发送到队列首部/尾部的队列消息的指针
pxHigherPriorityTaskWoken,如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将*pxHigherPriorityTaskWoken设置成 pdTRUE,然后在中断退出前需要进行一次上下文切换,去执行被唤醒的优先级更高的任务 。
消息发送成功返回pdTRUE,否则返回errQUEUE_FULL。
//xQueueSendToFront的中断保护版本,在中断服务程序中向队列首部发送一个队列消息
// xQueueSend的中断保护版本,在中断服务程序中向队列尾部发送一个队列消息
#define xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )
//xQueueSendToBackFromISR和xQueueSendFromISR等同
#define xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
xQueueSendFromISR()函数实例
void vBufferISR( void )
{
char cIn;
BaseType_t xHigherPriorityTaskWoken;
/* 在 ISR 开始的时候,我们并没有唤醒任务 */
xHigherPriorityTaskWoken = pdFALSE;
/* 直到缓冲区为空 */
do
{
/* 从缓冲区获取一个字节的数据 */
cIn = portINPUT_BYTE( RX_REGISTER_ADDRESS );
/* 发送这个数据 */
xQueueSendFromISR( xRxQueue, &cIn, &xHigherPriorityTaskWoken );
}while( portINPUT_BYTE( BUFFER_COUNT ) );
/* 这时候 buffer 已经为空,如果需要则进行上下文切换 */
if ( xHigherPriorityTaskWoken )
{
/* 上下文切换,这是一个宏,不同的处理器,具体的方法不一样 */
taskYIELD_FROM_ISR ();
}
}
至此,消息队列的发送消息内容就已经结束,多数函数都已经被官方封装好,我们直接用就行。
消息队列发送函数
xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) 向队列尾部发送一个队列消息
xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueSend函数的中断保护版本
xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) 向队列首部发送一个队列消息
xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueSendToFront函数的中断保护版本
xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueSendToFront函数的中断保护版本
上述都是在两个函数内展开的,xQueueGenericSend()函数和xQueueGenericSendFromISR()函数。
xQueueGenericSend()函数如下
/*
xCopyPosition参数选择:
#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) //发送到队尾
#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) //发送到队首
#define queueOVERWRITE ( ( BaseType_t ) 2 ) //以覆盖的方式发送,无论队列满还是不满,都可以发送。
*/
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, //消息队列句柄
const void * const pvItemToQueue, //指向要发送的信息的指针
TickType_t xTicksToWait, //阻塞超时时间
const BaseType_t xCopyPosition ) //发送数据到消息队列的位置
{
BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
/* 省略断言代码 */
for( ;; )
{
taskENTER_CRITICAL();
{
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ) //队列未满或者允许覆盖
{
traceQUEUE_SEND( pxQueue );
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); //将消息拷贝到消息队列中
#if ( configUSE_QUEUE_SETS == 1 ) //使用队列集
{
//没用到,省略代码
}
#else /* configUSE_QUEUE_SETS */
{
/* 如果有任务在等待获取此消息队列 */
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 /* configUSE_QUEUE_SETS */
taskEXIT_CRITICAL();
return pdPASS;
}
else //队列已满
{
if( xTicksToWait == ( TickType_t ) 0 ) //用户不指定阻塞超时时间,退出
{
taskEXIT_CRITICAL();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
else if( xEntryTimeSet == pdFALSE )
{
vTaskInternalSetTimeOutState( &xTimeOut ); //初始化阻塞超时结构体变量,初始化进入阻塞的时间 xTickCount 和溢出次数 xNumOfOverflows
xEntryTimeSet = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll(); //挂起调度器
prvLockQueue( pxQueue ); //队列上锁
/* 检查超时时间是否已经过去了 */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
if( prvIsQueueFull( pxQueue ) != pdFALSE ) // 如果队列还是满的
{
/* 将当前任务添加到队列的等待发送列表中以及阻塞延时列表,延时时间为用户指定的超时时间 xTicksToWait */
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;
}
}
}
xQueueGenericSendFromISR()函数只能用于中断中执行,是不带阻塞机制的。
xQueueGenericSendFromISR()函数如下
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, //消息队列句柄
const void * const pvItemToQueue, //指向要发送的信息的指针
BaseType_t * const pxHigherPriorityTaskWoken, //如果入队导致一个任务解锁,并且解锁的任务优先级高于当前运行的任务,则该函数将*pxHigherPriorityTaskWoken设置成pdTRUE。如果值为 pdTRUE,则中断退出前需要一次上下文切换。
const BaseType_t xCopyPosition ) //发送数据到消息队列的位置
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
/* 省略断言代码 */
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ) //队列未满或者允许覆盖
{
const int8_t cTxLock = pxQueue->cTxLock;
traceQUEUE_SEND_FROM_ISR( pxQueue );
( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); //将消息拷贝到消息队列中
if( cTxLock == queueUNLOCKED ) //判断队列是否上锁
{
#if ( configUSE_QUEUE_SETS == 1 ) //使用队列集
{
//没用到,省略代码
}
#else /* configUSE_QUEUE_SETS */
{
/* 如果有任务在等待获取此消息队列 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) //将等待接收队列删除,即将任务从阻塞中恢复
{
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE; /* 解除阻塞的任务优先级比当前任务高,记录上下文切换请求,等返回中断服务程序后,就进行上下文切换 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_QUEUE_SETS */
}
else //队列上锁
{
pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 ); //记录上锁次数,等到任务解除队列锁时,使用这个计录数就可以知道有多少数据入队
}
xReturn = pdPASS;
}
else //队列已满
{
traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue ); //因为 API 执行的上下文环境是中断,所以不能阻塞,直接返回队列已满错误代码 errQUEUE_FULL
xReturn = errQUEUE_FULL;
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
当任务试图读队列中的消息时,可以指定一个阻塞超时时间,当且仅当消息队列中有消息的时候,任务才能读取到消息。
在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转为就绪态。
当任务等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
当任务试图读队列中的消息时,可以指定一个阻塞超时时间,当且仅当消息队列中有消息的时候,任务才能读取到消息。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
xQueue, 队列句柄,
pvItemToQueue, 指向接收到要保存的数据的指针
xTicksToWait, 队列空时,阻塞超时的最大时间。如果 xTicksToWait 被设置成 0,函数立刻返回。
消息发送成功返回pdTRUE,否则返回pdFALSE。
//xQueueReceive()函数用于从一个队列中接收消息并把消息从队列中删除。
//xQueuePeek() 函数用于从一个队列中接收消息但不从队列中删除消息。
#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE )
#define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdTRUE )
接收的消息是以拷贝的形式进行的,所以我们必须提供一个足够大空间的缓冲区。具体能够拷贝多少数据到缓冲区,这个在队列创建的时候已经设定。
xQueueReceive()函数实例
static void Receive_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE; /* 定义一个创建信息返回值,默认为 pdTRUE */
uint32_t r_queue; /* 定义一个接收消息的变量 */
while (1)
{
xReturn = xQueueReceive( Test_Queue, /* 消息队列的句柄 */
&r_queue, /* 发送的消息内容 */
portMAX_DELAY); /* 等待时间 一直等 */
if (pdTRUE== xReturn)
printf("本次接收到的数据是:%d\n\n",r_queue);
else
printf("数据接收出错,错误代码: 0x%lx\n",xReturn);
}
}
该函数绝不能在中断服务程序里面被调用,而是必须使用带有中断保护功能的 xQueueReceiveFromISR ()来代替。
xQueueReceiveFromISR()是 xQueueReceive ()的中断版本。
xQueuePeekFromISR() 是 xQueuePeek() 的中断版本。
这两个函数只能用于中断,是不带有阻塞机制的,并且是在中断中可以安全调用。
xQueueReceiveFromISR( QueueHandle_t xQueue, void * const pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken )函数简介
xQueue, 队列句柄,
pvBuffer, 指向接收到要保存的数据的指针
pxHigherPriorityTaskWoken , 任务在往队列发送信息时,如果队列满,则任务将阻塞在该队列上。
如果 xQueueReceiveFromISR() 到账了一个任务解锁了,则将 *pxHigherPriorityTaskWoken 设置为 pdTRUE ,否则 *pxHigherPriorityTaskWoken 的值将不变。
消息发送成功返回pdTRUE,否则返回pdFALSE。
xQueuePeekFromISR( QueueHandle_t xQueue, void * const pvBuffer )函数简介
xQueue, 队列句柄,
pvBuffer, 指向接收到要保存的数据的指针
消息发送成功返回pdTRUE,否则返回pdFALSE。
xQueueReceiveFromISR()函数实例
QueueHandle_t xQueue;
/* 创建一个队列,并往队列里面发送一些数据 */
void vAFunction( void *pvParameters )
{
char cValueToPost;
const TickType_t xTicksToWait = ( TickType_t )0xff;
/* 创建一个可以容纳 10 个字符的队列 */
xQueue = xQueueCreate( 10, sizeof( char ) );
if ( xQueue == 0 )
{
/* 队列创建失败 */
}
/* ... 任务其他代码 */
/* 往队列里面发送两个字符,如果队列满了则等待 xTicksToWait 个系统节拍周期*/
cValueToPost = 'a';
xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
cValueToPost = 'b';
xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
cValueToPost = 'c';
xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
}
/* 中断服务程序:输出所有从队列中接收到的字符 */
void vISR_Routine( void )
{
BaseType_t xTaskWokenByReceive = pdFALSE;
char cRxedChar;
while ( xQueueReceiveFromISR( xQueue, ( void * ) &cRxedChar, &xTaskWokenByReceive) )
{
/* 接收到一个字符,然后输出这个字符 */
vOutputCharacter( cRxedChar );
/* 如果从队列移除一个字符串后唤醒了向此队列投递字符的任务,那么参数 xTaskWokenByReceive 将会设置成 pdTRUE,
这个循环无论重复多少次,仅会有一个任务被唤醒 */
}
if ( xTaskWokenByReceive != pdFALSE )
{
/* 我们应该进行一次上下文切换,当 ISR 返回的时候则执行另外一个任务 */
/* 这是一个上下文切换的宏,不同的处理器,具体处理的方式不一样 */
taskYIELD ();
}
}
至此,消息队列的发送消息内容就已经结束,多数函数都已经被官方封装好,我们直接用就行。
xQueueReceive() 从一个队列中接收消息并把消息从队列中删除。
xQueuePeek() 从一个队列中接收消息但不从队列中删除消息。
xQueueReceiveFromISR() xQueueReceive()函数的中断保护版本。
xQueuePeekFromISR() xQueuePeek()函数的中断保护版本。
xQueueReceive()和xQueuePeek()都是在同一个函数内展开的,从队列读取消息函数,xQueueGenericReceive()。
xQueueGenericReceive()函数如下
BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, //消息队列句柄
void * const pvBuffer, //指向接收到要保存的数据的指针
TickType_t xTicksToWait, //队列空时,用户指定的阻塞超时时间。如果该参数设置为 0,函数立刻返回。
const BaseType_t xJustPeeking ) //标记消息是否需要出队。pdFALSE,读取消息后会进行出队操作。pdTRUE,读取消息后不会进行出队操作。
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
/* 已删除一些断言 */
for ( ;; )
{
taskENTER_CRITICAL();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting; //uxMessagesWaiting为消息个数
/* 看看队列中有没有消息 */
if ( uxMessagesWaiting > ( UBaseType_t ) 0 ) //当前队列有消息
{
/*记录读消息位置,防止仅仅是读取消息,而不进行消息出队操作*/
pcOriginalReadPosition = pxQueue->u.pcReadFrom;
/* 拷贝消息到用户指定存放区域 pvBuffer */
prvCopyDataFromQueue( pxQueue, pvBuffer );
if ( xJustPeeking == pdFALSE ) //读取消息后会进行出队操作
{
/* 读取消息并且消息出队 */
traceQUEUE_RECEIVE( pxQueue );
/* 获取了消息,当前消息队列的消息个数需要减一 */
pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
/* 判断一下消息队列中是否有等待发送消息的任务 */
if ( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) /* 消息队列中有等待发送消息的任务,就要将任务从阻塞中恢复 */
{
//将等待的任务从队列的等待发送列表xTasksWaitingToSend中删除,并且添加到就绪列表中。
if ( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
/* 将任务从阻塞中恢复,如果被恢复的任务优先级比当前任务高,会进行一次任务切换 */
queueYIELD_IF_USING_PREEMPTION(); (13)
} else
{
mtCOVERAGE_TEST_MARKER();
}
} else
{
mtCOVERAGE_TEST_MARKER();
}
} else //读取消息后不会进行出队操作
{
traceQUEUE_PEEK( pxQueue );
/* 因为是只读消息 所以还要还原读消息位置指针 */
pxQueue->u.pcReadFrom = pcOriginalReadPosition;
/* 判断一下消息队列中是否还有等待获取消息的任务 */
if ( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
/* 将任务从阻塞中恢复 */
if ( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* 如果被恢复的任务优先级比当前任务高,会进行一次任务切换 */
queueYIELD_IF_USING_PREEMPTION();
}else
{
mtCOVERAGE_TEST_MARKER();
}
}else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}else //当前队列没有消息
{
/* 消息队列中没有消息可读 */
if ( xTicksToWait == ( TickType_t ) 0 )
{
/* 不等待,直接返回 */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}else if ( xEntryTimeSet == pdFALSE )
{
/* 初始化阻塞超时结构体变量,初始化进入阻塞的时间 xTickCount 和溢出次数 xNumOfOverflows */
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
//接下来的操作系统不允许其他任务访问队列,简单粗暴挂起调度器就不会进行任务切换,但是挂起调度器并不会禁止中断的发生,所以还需给队列上锁。
//因为系统不希望突然有中断操作这个队列的 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表。
vTaskSuspendAll();
prvLockQueue( pxQueue );
/* 检查超时时间是否已经过去了*/
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
/* 如果队列还是空的 */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
/* 将当前任务添加到队列的等待接收列表中以及阻塞延时列表,阻塞时间为用户指定的超时时间 xTicksToWait */
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
prvUnlockQueue( pxQueue );
if( xTaskResumeAll() == pdFALSE )
{
/* 如果有任务优先级比当前任务高,会进行一次任务切换 */
portYIELD_WITHIN_API();
}else
{
mtCOVERAGE_TEST_MARKER();
}
}else
{
/* 如果队列有消息了,就再试一次获取消息 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}else
{
/* 超时时间已过,退出 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
if ( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
/* 如果队列还是空的,返回错误代码 errQUEUE_EMPTY */
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数据区域大小不小于消息大小,否则,很可能引发地址非法的错误。
无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将消息的地址作为消息进行发送、接收。
队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同一队列写入和读出。
一个队列由多任务或中断写入是经常的事,但由多个任务读出倒是用的比较少。
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t Receive_Task_Handle = NULL; /* LED 任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL; /* KEY 任务句柄 */
QueueHandle_t Test_Queue =NULL;
#define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
#define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) */
int main(void)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
BSP_Init();
printf("按下 KEY1 或者 KEY2 发送队列消息\n");
printf("Receive 任务接收到消息在串口回显\n\n");
/* 创建 AppTaskCreate 任务 */
xReturn = xTaskCreate( (TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle); /* 任务控制块指*/
/* 启动任务调度 */
if (pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while (1); /* 正常不会执行到这里 */
}
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建 Test_Queue */
Test_Queue = xQueueCreate( (UBaseType_t ) QUEUE_LEN, /* 消息队列的长度 */
(UBaseType_t ) QUEUE_SIZE); /* 消息的大小 */
if ( Test_Queue != NULL )
printf("创建 Test_Queue 消息队列成功!\r\n");
/* 创建 Receive_Task 任务 */
xReturn = xTaskCreate( (TaskFunction_t )Receive_Task, /* 任务入口函数 */
(const char* )"Receive_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&Receive_Task_Handle); /*任务控制块指针*/
if (xReturn == pdPASS)
printf("创建 Receive_Task 任务成功!\r\n");
/* 创建 Send_Task 任务 */
xReturn = xTaskCreate( (TaskFunction_t )Send_Task, /* 任务入口函数 */
(const char* )"Send_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t* )&Send_Task_Handle); /*任务控制块指针 */
if (xReturn == pdPASS)
printf("创建 Send_Task 任务成功!\n\n");
vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
taskEXIT_CRITICAL(); //退出临界区
}
static void Receive_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE; /* 定义一个创建信息返回值,默认为 pdTRUE */
uint32_t r_queue; /* 定义一个接收消息的变量 */
while (1)
{
xReturn = xQueueReceive( Test_Queue, /* 消息队列的句柄 */
&r_queue, /* 发送的消息内容 */
portMAX_DELAY); /* 等待时间 一直等 */
if (pdTRUE == xReturn)
printf("本次接收到的数据是%d\n\n",r_queue);
else
printf("数据接收出错,错误代码: 0x%lx\n",xReturn);
}
}
static void Send_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
uint32_t send_data1 = 1;
uint32_t send_data2 = 2;
while (1)
{
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
{
/* KEY1 被按下 */
printf("发送消息 send_data1!\n");
xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
&send_data1, /* 发送的消息内容 */
0 ); /* 等待时间 0 */
if (pdPASS == xReturn)
printf("消息 send_data1 发送成功!\n\n");
}
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
{
/* KEY2 被按下 */
printf("发送消息 send_data2!\n");
xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
&send_data2, /* 发送的消息内容 */
0 ); /* 等待时间 0 */
if (pdPASS == xReturn)
printf("消息 send_data2 发送成功!\n\n");
}
vTaskDelay(20);/* 延时 20 个 tick */
}
}