1 、函数 xQueueSend() 、xQueueSendToBack()和 和 xQueueSendToFront()
这三个函数都是用于向队列中发送消息的,这三个函数本质都是
宏,
其中函数 xQueueSend()
和 xQueueSendToBack()是一样的,都是后向入队,即将新的消息插入到队列的后面。函数
xQueueSendToToFront()是前向入队,即将新消息插入到队列的前面。然而!这三个函数最后都
是调用的同一个函数:xQueueGenericSend()。这三个函数只能用于任务函数中,不能用于中断
服务函数,中断服务函数有专用的函数,它们以“FromISR”结尾,这三个函数的原型如下:
BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
const void* pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToToFront(QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
队列句柄。
pvItemToQueue: 指向要发送的消息,发送时候会将这个消息拷贝到队列中。
xTicksToWait: 阻塞时间,此参数指示当队列满的时候任务进入阻塞态等待队列空闲的最大
时间。如果为 0 的话当队列满的时候就立即返回;当为 portMAX_DELAY 的
话就会一直等待,直到队列有空闲的队列项,也就是死等,但是宏INCLUDE_vTaskSuspend 必须为 1。
返回值:
pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
2 、函数 xQueueOverwrite()
此函数也是用于向队列发送数据的,当队列满了以后会覆写掉旧的数据,不管这个旧数据
有没有被其他任务或中断取走。这个函数常用于向那些长度为 1 的队列发送消息,此函数也是
一个宏,最终调用的也是函数 xQueueGenericSend(),函数原型如下:
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,
const void * pvItemToQueue);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
队列句柄。
pvItemToQueue:指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
返回值:
pdPASS: 向队列发送消息成功,此函数也只会返回 pdPASS!因为此函数执行过程中不
在乎队列满不满,满了的话我就覆写掉旧的数据,总之肯定能成功。
3 、函数 xQueueGenericSend()
此函数才是真正干活的,上面讲的所有的任务级入队函数最终都是调用的此函数,此函数
也是我们后面重点要讲解的,先来看一下函数原型:
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
队列句柄。
pvItemToQueue:指向要发送的消息,发送的过程中会将这个消息拷贝到队列中。
xTicksToWait : 阻塞时间。
xCopyPosition: 入队方式,有三种入队方式:
queueSEND_TO_BACK: 后向入队
queueSEND_TO_FRONT: 前向入队
queueOVERWRITE: 覆写入队。
上面讲解的入队 API 函数就是通过此参数来决定采用哪种入队方式的。
返回值:
pdTRUE: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
4 、函数 xQueueSendFromISR() 、
xQueueSendToBackFromISR() 、
xQueueSendToFrontFromISR()
这三个函数也是向队列中发送消息的,这三个函数用于中断服务函数中。这三个函数本质
也宏,其中函数 xQueueSendFromISR ()和 xQueueSendToBackFromISR ()是一样的,都是后向入
队,即将新的消息插入到队列的后面。函数 xQueueSendToFrontFromISR ()是前向入队,即将新
消息插入到队列的前面。这三个函数同样调用同一个函数 xQueueGenericSendFromISR ()。这三
个函数的原型如下:
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
队列句柄。
pvItemToQueue:指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
三个函数来设置的,用户不用进行设置,用户只需要提供一
个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退
出中断服务函数之前一定要进行一次任务切换。
返回值:
pdTRUE: 向队列中发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
我们注意观察,可以看出这些函数都没有设置阻塞时间值。原因很简单,这些函数都是在
中断服务函数中调用的,并不是在任务中,所以也就没有阻塞这一说了!
5 、函数 xQueueOverwriteFromISR()
此函数是 xQueueOverwrite()的中断级版本,用在中断服务函数中,在队列满的时候自动覆
写掉旧的数据,此函数也是一个宏,实际调用的也是函数 xQueueGenericSendFromISR(),此函
数原型如下:
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
此函数的参数和返回值同上面三个函数相同。
6 、函数 xQueueGenericSendFromISR()
上面说了 4 个中断级入队函数最终都是调用的函数 xQueueGenericSendFromISR(),这是真
正干活的主啊,也是我们下面会详细讲解的函数,先来看一下这个函数的原型,如下:
BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue,
const void* pvItemToQueue,
BaseType_t* pxHigherPriorityTaskWoken,
BaseType_t xCopyPosition);
参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的
队列句柄。
pvItemToQueue:指向要发送的消息,发送的过程中会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
三个函数来设置的,用户不用进行设置,用户只需要提供一
个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退
出中断服务函数之前一定要进行一次任务切换。
xCopyPosition: 入队方式,有三种入队方式:
queueSEND_TO_BACK: 后向入队
queueSEND_TO_FRONT: 前向入队
queueOVERWRITE: 覆写入队。
返回值:
pdTRUE: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
3.2 任务级通用入队函数
不 管 是 后 向 入 队 、 前 向 入 队 还 是 覆 写 入 队 , 最 终 调 用 的 都 是 通 用 入 队 函 数
xQueueGenericSend(),这个函数在文件 queue.c 文件中由定义,缩减后的函数代码如下:
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 ) ||\ (1)
( xCopyPosition == queueOVERWRITE ) )
{
traceQUEUE_SEND( pxQueue );
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue,\ (2)
xCopyPosition );
/**************************************************************************/
/**************************省略掉与队列集相关代码**************************/
/**************************************************************************/
{
//检查是否有任务由于等待消息而进入阻塞态
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) ==\(3)
pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->\ (4)
xTasksWaitingToReceive ) ) != pdFALSE )
{
//解除阻塞态的任务优先级最高,因此要进行一次任务切换
queueYIELD_IF_USING_PREEMPTION(); (5)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else if( xYieldRequired != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS; (6)
}
else
{
if( xTicksToWait == ( TickType_t ) 0 ) (7)
{
//队列是满的,并且没有设置阻塞时间的话就直接返回
taskEXIT_CRITICAL();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL; (8)
}
else if( xEntryTimeSet == pdFALSE ) (9)
{
//队列是满的并且指定了任务阻塞时间的话就初始化时间结构体
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
//时间结构体已经初始化过了,
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL(); //退出临界区
vTaskSuspendAll(); (10)
prvLockQueue( pxQueue ); (11)
//更新时间壮态,检查是否有超时产生
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) (12)
{
if( prvIsQueueFull( pxQueue ) != pdFALSE ) (13)
{
traceBLOCKING_ON_QUEUE_SEND( pxQueue );
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), \ (14)
xTicksToWait );
prvUnlockQueue( pxQueue ); (15)
if( xTaskResumeAll() == pdFALSE ) (16)
{
portYIELD_WITHIN_API();
}
}
else
{
//重试一次
prvUnlockQueue( pxQueue ); (17)
( void ) xTaskResumeAll();
}
}
else
{
//超时产生
prvUnlockQueue( pxQueue ); (18)
( void ) xTaskResumeAll();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL; (19)
}
}
}
(1)、要向队列发送数据,肯定要先检查一下队列是不是满的,如果是满的话肯定不能发送
的。当队列未满或者是覆写入队的话就可以将消息入队了。
(2)、调用函数 prvCopyDataToQueue()将消息拷贝到队列中。前面说了入队分为后向入队、
前向入队和覆写入队,他们的具体实现就是在函数 prvCopyDataToQueue()中完成的。如果选择
后向入队 queueSEND_TO_BACK 的话就将消息拷贝到队列结构体成员 pcWriteTo 所指向的队
列项,拷贝成功以后 pcWriteTo 增加 uxItemSize 个字节,指向下一个队列项目。当选择前向入
队 queueSEND_TO_FRONT 或者 queueOVERWRITE 的话就将消息拷贝到 u.pcReadFrom 所指向
的队列项目,同样的需要调整 u.pcReadFrom 的位置。当向队列写入一个消息以后队列中统计当
前消息数量的成员 uxMessagesWaiting 就会加一,但是选择覆写入队 queueOVERWRITE 的话还
会将 uxMessagesWaiting 减一,这样一减一加相当于队列当前消息数量没有变。
(3) 、 检 查 是 否 有 任 务 由 于 请 求 队 列 消 息 而 阻 塞 , 阻 塞 的 任 务 会 挂 在 队 列 的
xTasksWaitingToReceive 列表上。
(4)、有任务由于请求消息而阻塞,因为在(2)中已将向队列中发送了一条消息了,所以调用
函数 xTaskRemoveFromEventList()将阻塞的任务从列表 xTasksWaitingToReceive 上移除,并且把
这个任务添加到就绪列表中,如果调度器上锁的话这些任务就会挂到列表 xPendingReadyList 上。
如果取消阻塞的任务优先级比当前正在运行的任务优先级高还要标记需要进行任务切换。当函
数 xTaskRemoveFromEventList()返回值为 pdTRUE 的话就需要进行任务切换。
(5)、进行任务切换。
(6)、返回 pdPASS,标记入队成功。
(7)、(2)到(6)都是非常理想的效果,即消息队列未满,入队没有任何障碍。但是队列满了以
后呢?首先判断设置的阻塞时间是否为 0,如果为 0 的话就说明没有阻塞时间。
(8)、由(7)得知阻塞时间为 0,那就直接返回 errQUEUE_FULL,标记队列已满就可以了。
(9)、如果阻塞时间不为0并且时间结构体还没有初始化的话就初始化一次超时结构体变量,
调用函数 vTaskSetTimeOutState()完成超时结构体变量 xTimeOut 的初始化。其实就是记录当前
的系统时钟节拍计数器的值 xTickCount 和溢出次数 xNumOfOverflows。
(10)、任务调度器上锁,代码执行到这里说明当前的状况是队列已满了,而且设置了不为 0
的阻塞时间。那么接下来就要对任务采取相应的措施了,比如将任务加入到队列的
xTasksWaitingToSend 列表中。
(11)、调用函数 prvLockQueue()给队列上锁,其实就是将队列中的成员变量 cRxLock 和
cTxLock 设置为 queueLOCKED_UNMODIFIED。
(12)、调用函数 xTaskCheckForTimeOut()更新超时结构体变量 xTimeOut,并且检查阻塞时
间是否到了。
(13)、阻塞时间还没到,那就检查队列是否还是满的。
(14)、经过(12)和(13)得出阻塞时间没到,而且队列依旧是满的,那就调用函数
vTaskPlaceOnEventList()将任务添加到队列的 xTasksWaitingToSend 列表中和延时列表中,并且
将 任 务 从 就 绪 列 表 中 移 除 。 注 意 ! 如 果 阻 塞 时 间 是 portMAX_DELAY 并 且 宏
INCLUDE_vTaskSuspend 为 1 的话,函数 vTaskPlaceOnEventList()会将任务添加到列表
xSuspendedTaskList 上。
(15)、操作完成,调用函数 prvUnlockQueue()解锁队列。
(16)、调用函数 xTaskResumeAll()恢复任务调度器
(17)、阻塞时间还没到,但是队列现在有空闲的队列项,那么就在重试一次。
(18)、相比于第(12)步,阻塞时间到了!那么任务就不用添加到那些列表中了,那就解锁队
列,恢复任务调度器。
(19)、返回 errQUEUE_FULL,表示队列满了。
3.3 中断级 通用 入队函数
讲完任务级入队函数再来看一下中断级入队函数 xQueueGenericSendFromISR(),其他的中
断级入队函数都是靠此函数来实现的。中断级入队函数和任务级入队函数大同小异,函数代码
如下:
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue,
const void * const pvItemToQueue,
BaseType_t * const pxHigherPriorityTaskWoken,
const BaseType_t xCopyPosition )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) ||\ (1)
( xCopyPosition == queueOVERWRITE ) )
{
const int8_t cTxLock = pxQueue->cTxLock; (2)
traceQUEUE_SEND_FROM_ISR( pxQueue );
( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); (3)
//队列上锁的时候就不能操作事件列表,队列解锁的时候会补上这些操作的。
if( cTxLock == queueUNLOCKED ) (4)
{
/**************************************************************************/
/**************************省略掉与队列集相关代码**************************/
/**************************************************************************/
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == \ (5)
pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->\ (6)
xTasksWaitingToReceive ) ) != pdFALSE )
{
//刚刚从事件列表中移除的任务对应的任务优先级更高,所以
标记要进行任务切换
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE; (7)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
//cTxLock 加一,这样就知道在队列上锁期间向队列中发送了数据
pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 ); (8)
}
xReturn = pdPASS; (9)
}
else
{
traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
xReturn = errQUEUE_FULL; (10)
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
(1)、队列未满或者采用的覆写的入队方式,这是最理想的壮态。
(2)、读取队列的成员变量 xTxLock,用于判断队列是否上锁。
(3)、将数据拷贝到队列中。
(4)、队列上锁了,比如任务级入队函数在操作队列中的列表的时候就会对队列上锁。
(5)、判断队列列表 xTasksWaitingToReceive 是否为空,如果不为空的话说明有任务在请求
消息的时候被阻塞了。
(6)、将相应的任务从列表 xTasksWaitingToReceive 上移除。跟任务级入队函数处理过程一
样。
(7)、如果刚刚从列表 xTasksWaitingToReceive 中移除的任务优先级比当前任务的优先级高,
那么标记 pxHigherPriorityTaskWoken 为 pdTRUE,表示要进行任务切换。如果要进行任务切换
的话就需要在退出此函数以后,退出中断服务函数之前进行一次任务切换。
(8)、如果队列上锁的话那就将队列成员变量 cTxLock 加一,表示进行了一次入队操作,在
队列解锁(prvUnlockQueue())的时候会对其做相应的处理。
(9)、返回 pdPASS,表示入队完成。
(10)、如果队列满的话就直接返回 errQUEUE_FULL,表示队列满。
4 队列上锁和解锁
在上面讲解任务级通用入队函数和中断级通用入队函数的时候都提到了队列的上锁和解锁,
队列的上锁和解锁是两个 API 函数:prvLockQueue()和 prvUnlockQueue()。首先来看一下队列上
锁函数 prvLockQueue(),此函数本质上就是一个宏,定义如下:
#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{ \
if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
} \
if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
} \
} \
taskEXIT_CRITICAL()
prvLockQueue()函数很简单,就是将队列中的成员变量 cRxLock 和 cTxLock 设置为
queueLOCKED_UNMODIFIED 就行了。
再来看一下队列的解锁函数 prvUnlockQueue(),函数如下:
static void prvUnlockQueue( Queue_t * const pxQueue )
{
//上锁计数器(cTxLock 和 cRxLock)记录了在队列上锁期间,入队或出队的数量,当队列
//上锁以后队列项是可以加入或者移除队列的,但是相应的列表不会更新。
taskENTER_CRITICAL();
{
//处理 cTxLock。
int8_t cTxLock = pxQueue->cTxLock;
while( cTxLock > queueLOCKED_UNMODIFIED ) (1)
{
/**************************************************************************/
/ **************************省略掉与队列集相关代码**************************/
/**************************************************************************/
{
//将任务从事件列表中移除
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == \ (2)
pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->\ (3)
xTasksWaitingToReceive ) ) != pdFALSE )
{
//从列表中移除的任务优先级比当前任务的优先级高,因此要
//进行任务切换。
vTaskMissedYield(); (4)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
break;
}
}
--cTxLock; (5)
}
pxQueue->cTxLock = queueUNLOCKED; (6)
}
taskEXIT_CRITICAL();
//处理 cRxLock。
taskENTER_CRITICAL();
{
int8_t cRxLock = pxQueue->cRxLock;
while( cRxLock > queueLOCKED_UNMODIFIED ) (7)
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) !=\
pdFALSE )
{
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--cRxLock;
}
else
{
break;
}
}
pxQueue->cRxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
}
(1)、判断是否有中断向队列发送了消息,在 13.2.3 小节讲解中断级通用入队函数的时候说
了,如果当队列上锁的话那么向队列发送消息成功以后会将入队计数器 cTxLock 加一。
(2)、判断列表 xTasksWaitingToReceive 是否为空,如果不为空的话就要将相应的任务从列
表中移除。
(3)、将任务从列表 xTasksWaitingToReceive 中移除。
(4)、如果刚刚从列表 xTasksWaitingToReceive 中移除的任务优先级比当前任务的优先级高,
那么就要标记需要进行任务切换。这里调用函数 vTaskMissedYield()来完成此任务,函数
vTaskMissedYield()只是简单的将全局变量 xYieldPending 设置为 pdTRUE。那么真正的任务切换
是在哪里完成的呢?在时钟节拍处理函数 xTaskIncrementTick()中,此函数会判断 xYieldPending
的值,从而决定是否进行任务切换,具体内容可以参考 12.2 小节。
(5)、每处理完一条就将 cTxLock 减一,直到处理完所有的。
(6)、当处理完以后标记 cTxLock 为 queueUNLOCKED,也就说 cTxLock 是没有上锁的了。
(7)、处理完 cTxLock 以后接下来就要处理 xRxLock 了,处理过程和 xTxLock 很类似
。
5 从队列 读取 消息
有入队就有出队,出队就是从队列中获取队列项(消息),FreeRTOS 中出队函数如表 13.6.1.1
所示:
1 、函数 xQueueReceive()
此函数用于在任务中从队列中读取一条(请求)消息,读取成功以后就会将队列中的这条数
据删除,此函数的本质是一个宏,真正执行的函数是 xQueueGenericReceive()。此函数在读取消
息的时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读
取的数据长度是创建队列的时候所设定的每个队列项目的长度,函数原型如下:
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void * pvBuffer,
TickType_t xTicksToWait);
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最
大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏
INCLUDE_vTaskSuspend 必须为 1。
返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。
2 、函数 xQueuePeek()
此函数用于从队列读取一条(请求)消息,只能用在任务中!此函数在读取成功以后不会将
消息删除,此函数是一个宏,真正执行的函数是 xQueueGenericReceive()。此函数在读取消息的
时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读取的
数据长度是创建队列的时候所设定的每个队列项目的长度,函数原型如下:
BaseType_t xQueuePeek(QueueHandle_t xQueue,
void * pvBuffer,
TickType_t xTicksToWait);
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最
大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏
INCLUDE_vTaskSuspend 必须为 1。
返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。
3、数 函数 xQueueGenericReceive()
不 管 是 函 数 xQueueReceive() 还 是 xQueuePeek() , 最 终 都 是 调 用 的 函 数
xQueueGenericReceive(),此函数是真正干事的,函数原型如下:
BaseType_t xQueueGenericReceive(QueueHandle_t xQueue,
void* pvBuffer,
TickType_t xTicksToWait
BaseType_t xJustPeek)
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最
大时间。如果为 0 的话当队列空的时候就立即返回;当为 portMAX_DELAY
的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏
INCLUDE_vTaskSuspend 必须为 1。
xJustPeek : 标记当读取成功以后是否删除掉队列项,当为 pdTRUE 的时候就不用删除,
也就是说你后面再调用函数 xQueueReceive()获取到的队列项是一样的。当为
pdFALSE 的时候就会删除掉这个队列项。
返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。
4 、函数 xQueueReceiveFromISR()
此函数是 xQueueReceive()的中断版本,用于在中断服务函数中从队列中读取(请求)一条消
息,读取成功以后就会将队列中的这条数据删除。此函数在读取消息的时候是采用拷贝方式的,
所以需要用户提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时
候所设定的每个队列项目的长度,函数原型如下:
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,
void* pvBuffer,
BaseType_t * pxTaskWoken);
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
中。
pxTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值是由函数来设置的,
用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值
为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。
5 、函数 xQueuePeekFromISR()
此函数是 xQueuePeek()的中断版本,此函数在读取成功以后不会将消息删除,此函数原型
如下:
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,
void * pvBuffer)
参数:
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的
队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
中。
返回值:
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。
出队函数的具体过程和入队函数类似,具体的过程就不在详细的分析了,有兴趣的,大家
自行对照着源码看一下就可以了。