13. 消息队列
消息队列是一种常用于任务键通信的数据结构,队列可以在任务与任务间、中断与任务间传递信息,实现了任务接收来自其他任务或者中断的不定长数据。
任务能从队列中读取信息,当队列中的消息为空时,读取消息的任务将被阻塞。用户还可以指定阻塞的任务时间xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效,当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息。当等待的时间超过指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。
13.1 消息队列运作机制
FreeRTOS的消息队列控制块由多个元素组成,当消息队列被创建时系统会为控制块分配对应的内存空间,用于保存消息队列的一些信息,如信息的存储位置、头指针pcHead、尾指针pcTail、消息大小uxItemSize以及队列长度uxLength等
任务或者中断服务程序都可以给消息队列发送消息,发送消息时如果队列未满或者允许覆盖入队,FreeRTOS会将消息复制到消息队列队尾,否则会根据用户指定的阻塞超时时间进行阻塞,如果时间超时到了,那么任务会从阻塞态转换为就绪态,并且此时发送信息的任务或者中断会收到一个错误代码“errQUEUE_FULL”。如果因为队列满了被阻塞,那么在等待的时候如果有别的任务读取了队列中的信息,那么被阻塞的任务将会有空间来发送信息,同时解除阻塞态进入就绪态。
任务同样也可以读取信息,当队列中没有信息的时候进行信息的读取,那么任务会被阻塞,我们同样可以指定超时时间。如果在阻塞的时候有任务向队列中发送了消息,那么阻塞会被解除。
队列还可以发送紧急消息,发送紧急消息时发送的位置是队列的队头而非队尾,这样接受者就能够优先接收紧急消息,从而及时进行消息处理
当消息队列不再被使用时,应该将其删除以释放系统资源,一旦操作完成,队列将被永久删除。
假如有多个任务阻塞在一个消息队列中,那么这些阻塞的任务将按照任务优先级进行排序,优先级高的任务将优先获得队列的访问权。
只有在任务中发送消息才允许进入阻塞态,而在中断中发送消息不允许带有阻塞机制,需要调用在中断中发送消息的API函数接口。
13.2 消息队列控制块
typedef struct QueueDefinition
{
int8_t *pcHead; /* 指向队列消息存储区起始位置,即第一个消息空间 */
int8_t *pcTail; /* 指向队列消息存储区结束位置地址 */
int8_t *pcWriteTo; /* 指向队列消息存储区下一个可用消息空间 */
union /* pcReadFrom与uxRecursiveCallCount是一对互斥变量,使用联合体来确保两个互斥的结构体成员不会同时出现。当结构体用于队列式,pcReadFrom指向出队消息空间的最后一个,也就是读取消息时是从pcReadFrom指向的空间读取消息内容 */
{
int8_t *pcReadFrom; /*< Points to the last place that a queued item was read from when the structure is used as a queue. */
UBaseType_t uxRecursiveCallCount; /* 当结构体用于互斥量时,uxRecursiveCallCount用于计数,记录递归互斥量被调用的次数 */
} u;
List_t xTasksWaitingToSend; /* 发送消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序。 */
List_t xTasksWaitingToReceive; /* 获取消息阻塞列表,用于保存阻塞在此的队列任务,任务按照优先级进行排序。 */
volatile UBaseType_t uxMessagesWaiting; /* 用于记录当前消息队列的消息个数,如果消息队列用于信号量,这个值表示有效信号量个数 */
UBaseType_t uxLength; /* 表示队列的长度,也就是能存放多少消息 */
UBaseType_t uxItemSize; /* 单个消息的大小 */
volatile int8_t cRxLock; /* 队列上锁后,存储从队列收到的列表项数目,也就是出队的数量;如果队列没有上锁,则设置为queueUNLOCKED */
volatile int8_t cTxLock; /* 队列上锁后,存储发送到队列的列表项数目,也就是入队的数量;如果队列没有上锁,则设置为queueUNLOCKED*/
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition *pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
/* The old xQUEUE name is maintained above then typedefed to the new Queue_t
name below to enable the use of older kernel aware debuggers. */
typedef xQUEUE Queue_t;
13.3 相关函数
13.3.1 队列创建与删除
13.3.1.1 xQueueCreate() 动态创建消息队列
创建队列时真正使用的函数是xQueueGenericCreate()
QueueHandle_t xQueueCreate( uxQueueLength, uxItemSize )
参数: uxQueueLength:队列中能够存储的最大消息单元数目,即队列长度
uxItemSize:队列中消息单元的大小,以字节为单位
返回值:如果创建成功,则返回一个队列句柄,用于访问创建的队列;如果创建不成功,则返回NULL,可能的原因是创建队列需要的RAM无法分配成功。
注意事项:
- 要想用该函数,必须在FreeRTOSConfig.h中把configSUPPORT_DYNAMIC_ALLOCATION定义为1来启用,这是一个用于启用动态内存分配的宏,通常情况下,在FreeRTOS中凡是创建任务、队列、信号量和互斥量等内核对象都需要用动态内存分配,所以这个宏默认在FreeRTOS.h头文件中已经启用。
13.3.1.2 xQueueCreaetStatic() 静态创建消息队列
QueueHandle xQueueCreateStatic( UBaseType_t uxQueueLength, /* 队列能够存储的最大单元数目,即队列函数 */
UBaseType_t uxItemSize, /* 队列中数据单元的长度,以字节为单位 */
uint8_t *pucQueueStorageBuffer, /* 指针,指向一个uint8_t类型的数组,数组的大小至少有uxQueueLength* uxItemSize个字节,当uxItemSize为0时,pucQueueStorageBuffer可以为NULL */
StaticQueue_t *pxQueueBuffer /* 指向StaticQueue_t类型的变量,该变量用于存储队列的数据结构 */
)
返回值:如果创建成功,则返回一个队列句柄,用于访问创建的队列;如果创建不成功,则返回NULL,可能的原因是创建队列需要的RAM无法分配成功。
注意事项:
- 要想用该函数,必须在FreeRTOSConfig.h中把configSUPPORT_STATIC_ALLOCATION定义为1
13.3.1.3 vQueueDelete() 删除消息队列
void vQueueDelete(QueueHandle_t xQueue)
13.3.2 消息的发送
13.3.2.1 xQueueSend() 发送队列消息
BaseType_t xQueueSend(QueueHandle_t xQueue, /* 队列句柄 */
const void * pvItemToQueue, /* 指针,指向要发送到的队列尾部的队列消息 */
TickType_t xTicksToWait /* 队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait被设置成0,函数立刻返回。如果INCLUDE_vTaskSuspend设置成1,并且指定延时为portMAX_DELAY,将一直等待等到空闲 */
)
返回值:发送成功:pdTURE
发送失败:errQUEUE_FULL
注意事项:
- 该函数消息以复制的形式入队,而不是以引用的形式。该函数决不能在中断服务程序中被调用,中断中必须使用带有中断保护功能的xQueueSendFromISR来代替。
13.3.2.2 xQueueSendFromISR() 中断中发送消息队列
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, /* 队列句柄*/
const void *pvItemToQueue, /* 指针,指向要发送到队列尾部的消息 */
BaseType_t *pxHigherPriorityTaskWoken /* 如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将*pxHigherPriorityTaskWoken设置成pdTRUE,然后在中断退出前进行一次上下文切换,去执行比唤醒任务的优先级更高的任务,从FreeRTOS V7.3.0起,pxHigherPriorityTaskWorken作为一个可选参数,可以设置为NULL */
)
返回值: 发送成功:pdTRUE
发送失败:errQUEUE_FULL
注意事项:
- 在中断服务函数中pxHigherPriorityTaskWoken是要我们自己定义然后传入,然后自己判断是否需要上下文转换,我们要手动转换。
13.3.2.2.1 示例
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();
}
}
13.3.2.3 xQueueSendToFront() 向队列队首发送消息
BaseType_t xQueueSendToFront( QueueHandle_t xQueue, /* 队列句柄 */
const void * pvItemToQueue, /* 指针,指向要发送到队首的消息 */
TickType_t xTicksToWait /* 队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait被设置成0,函数立刻返回。如果INCLUDE_vTaskSuspend设置成1,并且指定延时为portMAX_DELAY,将一直等待等到空闲 */
)
返回值: 发送成功:pdTRUE
发送失败:errQUEUE_FULL
13.3.2.4 xQueueSendToFrontFromISR() 中断中向队列队首发送消息
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, /* 队列句柄*/
const void *pvItemToQueue, /* 指针,指向要发送到队列尾部的消息 */
BaseType_t *pxHigherPriorityTaskWoken /* 如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将*pxHigherPriorityTaskWoken设置成pdTRUE,然后在中断退出前进行一次上下文切换,去执行比唤醒任务的优先级更高的任务,从FreeRTOS V7.3.0起,pxHigherPriorityTaskWorken作为一个可选参数,可以设置为NULL */
)
返回值: 发送成功:pdTRUE
发送失败:errQUEUE_FULL
13.3.3 消息的接收
13.3.3.1 xQueueReceive() 任务接收消息并删除消息
BaseType_t xQueueReceive( QueueHandle_t xQueue, /* 队列句柄 */
void *pvBuffer, /* 指针,指向收到的要保存的数据 */
TickType_t xTicksToWait ) /* 队列为空时,阻塞超时的最大时间 */
返回值: 接收成功:pdTRUE
接收失败:pdFALSE
注意事项:
关于阻塞时间:如果该参数设置为0,函数立即返回,超时时间的单位为系统单位周期,常量portTICK_PERIOD_MS()用于辅助计算真是的时间,单位为ms。如果INCLUDE_vTaskSuspend设置为1,并且指定延时为portMAX_DELAY,将导致任务无限期阻塞
起始xQueueReceive是一个宏定义函数
#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE )
13.3.3.2 xQueuePeek() 任务接收消息但不删除消息
#define xQueuePeek( xQueue, pvBuffer, xTicksToWait )
用法和xQueueReceive是一样的,只是不会删除消息。
13.3.3.3 xQueueReceiveFromISR() 中断接收消息并删除消息
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, /* 队列句柄 */
void * const pvBuffer, /* 指针,指向收到的要保存的数据 */
BaseType_t * const pxHigherPriorityTaskWoken )/* 如果xQueueReceiveFromISR函数导致一个任务解除阻塞,那么 *pxHigherPriorityTaskWoken将被设置成pdTRUR,否则*pxHigherPriorityTaskWoken的值将不变。从FreeRTOS V7.3.0起,pxHigherPriorityTaskWorken作为一个可选参数,可以设置为NULL */
返回值: 接收成功:pdTRUE
接收失败:pdFALSE
13.3.3.4 xQueuePeekFromISR() 中断接收消息但不删除消息
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, /* 队列句柄 */
void * pvBuffer) /* 指针,指向接收到要保存的数据 */
13.4 示例
13.4.1 消息队列发送消息示例
13.4.1.1 从任务中发送消息
static void Send_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;
uint32_t send_data = 1;
while(1)
{
if(判断条件)
{
xReturn = xQueueSend( Test_Queue, /*消息队列句柄*/
&send_data, /* 发送的消息内容 */
0 /* 等待时间0 */
)
}
if(xReturn == pdPASS)
{
printf("消息发送成功");
}
}
}
13.4.1.2 从中断中发送消息
void vBufferISR(void)
{
char cIn;
BaseType_t xHigherPriorityTaskWoken; //这里是一个重点要注意的
xHigherPriorityTaskWoken = pdFALSE;
cIn = 获取的值;
/* 发送这个数据 */
xQueueSendFromISR(xRxQueue,&cIn,&xHigherPriorityTaskWoken);
/* 手动判断是否需要上下文切换*/
if(xHigherPriorityTaskWoken)
{
taskYIELD(); //任务调度是调用这个函数来进行的。
}
}
13.4.2 消息队列接收消息示例
13.4.2.1 从任务中接收消息
static void Receive_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;
uint32_t r_queue;
while(1)
{
xReturn = xQueueReceive( Test_Queue,
&r_queue,
portMAX_DELAY
);
if(xReturn == pdTRUE)
{
printf("成功");
}
else
{
printf("出错");
}
}
}
如果不想删除数据 就将xQueueReceive函数改用xQueuePeek即可
13.4.2.2 从中断中接收消息
QueueHandle_t xQueue;
char cValueToPost = 'a';
const TickType_t xTicksToWait = (TickType_T)0xff;
xQueue = xQueueCreate(10,sizeof(char));
xQueueSend(xQueue,(void*)&cValueToPost,xTicksToWait);
void vISR_Routine(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
char cRxedChar;
xQueueReceiveFromISR(xQueue,
(void*) &cRxedChar,
&xHigherPriorityTaskWoken
)
输出这个值...
if(xHigherPriorityTaskWoken!=pdFAALSE)
{
taskYIELD();
}
}