什么是队列
队列,也叫消息队列,就是把消息一条一条的排个队。
比如创建了一个消息队列,这个消息队列可以存10条消息。任务A可以往里存消息,任务B也可以往里存。这个存的消息是要讲先来后到的,存满了10条消息就不能存了。假设任务C从里面去消息,也是一条一条的取,按照先后顺序取,先存的先取,后存的后取。信息取一个,队列里面就少一个,取10个之后,消息队列就空了。
往队列里面存消息,如果队列已经满了,可以一直等着,直到队列里面有空位置,也可以等一会,没空位离开。
从队列里面取消息,如果队列已经空了,可以一直等着,直到队列里面有消息再取,也可以等一会,没消息就离开。
跟队列相关的几个函数
//1、创建消息队列
osMessageQId osMessageCreate (const osMessageQDef_t *queue_def, osThreadId thread_id);
//2、往消息队列里面写消息
osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec);
//3、从消息队列中获取消息,把消息拿走
osEvent osMessageGet (osMessageQId queue_id, uint32_t millisec);
//4、从消息队列中获取一个消息,但是不拿走,只是看一眼
osEvent osMessagePeek (osMessageQId queue_id, uint32_t millisec);
//5、获取消息队列中的消息数量
uint32_t osMessageWaiting(osMessageQId queue_id);
//6、获取消息队列还剩多少空间
uint32_t osMessageAvailableSpace(osMessageQId queue_id);
//7、删除消息队列
osStatus osMessageDelete (osMessageQId queue_id)
1、创建消息队列
/**
* @brief 创建并初始化一个消息队列
* @参数 queue_def 所定义的队列\ref osMessageQ.
* @参数 thread_id 线程ID(一般也用不上就用NULL)
* @返回值 返回消息队列的句柄,也就是消息队列的ID
*/
osMessageQId osMessageCreate (const osMessageQDef_t *queue_def, osThreadId thread_id)
{
(void) thread_id;
#if( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
//如果同时支持动态创建和静态创建
if ((queue_def->buffer != NULL) && (queue_def->controlblock != NULL)) {
return xQueueCreateStatic(queue_def->queue_sz, queue_def->item_sz, queue_def->buffer, queue_def->controlblock);
}
else {
return xQueueCreate(queue_def->queue_sz, queue_def->item_sz);
}
#elif ( configSUPPORT_STATIC_ALLOCATION == 1 )
//如果只支持静态创建
return xQueueCreateStatic(queue_def->queue_sz, queue_def->item_sz, queue_def->buffer, queue_def->controlblock);
#else
//如果只支持动态创建
return xQueueCreate(queue_def->queue_sz, queue_def->item_sz);
#endif
}
osMessageCreate函数只不过是CMSIS重新封装了一下而已,实质上还是调用FreeRTOS的原生函数:xQueueCreateStatic()和xQueueCreate()。
消息队列的要素:
uxQueueLength,队列的长度,就是有多少个数据的意思
uxItemSize,每个数据单元的大小
下面创建一个队列深度为16,每个消息单元为uint8_t的消息队列
//定义消息队列的ID
osMessageQId myQueue01Handle;
//定义消息队列的名称,大小,类型
osMessageQDef(myQueue01, 16, uint8_t);
//创建消息队列
myQueue01Handle = osMessageCreate(osMessageQ(myQueue01), NULL);
2、往消息队列中写消息
**
* @简介 往消息队列中写消息
* @参数 queue_id 消息队列的ID.
* @参数 info 消息内容,发送的消息需要强制转化为uint32_t类型
* @参数 millisec 超时时间.
* @返回值 状态信息,是否写入成功
*/
osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec)
{
portBASE_TYPE taskWoken = pdFALSE;
TickType_t ticks;
ticks = millisec / portTICK_PERIOD_MS; //超时时间转换为tick单位
if (ticks == 0) {
ticks = 1;
}
//Cortex-M3支持两种操作模式handler模式和thread模式,
if (inHandlerMode()) { //handler模式就用中断的方式发送消息
if (xQueueSendFromISR(queue_id, &info, &taskWoken) != pdTRUE) {
return osErrorOS;
}
portEND_SWITCHING_ISR(taskWoken);
}
else { //thread模式
if (xQueueSend(queue_id, &info, ticks) != pdTRUE) {
return osErrorOS;
}
}
return osOK;
}
往队列中写消息函数osMessagePut(),是CMSIS重新封装了一下,本质也是调用xQueueSendFromISR()或者xQueueSend()。函数内部自动判断是在中断状态还是在正常状态,方便用户使用。
往消息队列中写个0x08进去,超时时间1ms;
osMessagePut(myQueue01Handle,0x08,1);
3、从消息队列中获取消息
/**
* @brief 从消息队列中获取一个消息,没消息就等消息
* @param queue_id 消息队列的ID
* @param millisec 超时时间
* @retval event 返回event
*/
osEvent osMessageGet (osMessageQId queue_id, uint32_t millisec)
{
portBASE_TYPE taskWoken;
TickType_t ticks;
osEvent event;
event.def.message_id = queue_id;
event.value.v = 0;
if (queue_id == NULL) { //找不到消息队列,那就报错
event.status = osErrorParameter;
return event;
}
taskWoken = pdFALSE;
ticks = 0;
if (millisec == osWaitForever) { //超时时间设置为portMAX_DELAY
ticks = portMAX_DELAY;
}
else if (millisec != 0) {
ticks = millisec / portTICK_PERIOD_MS; //超时时间转化为TICK单位
if (ticks == 0) {
ticks = 1;
}
}
if (inHandlerMode()) { //中断里面获得消息
if (xQueueReceiveFromISR(queue_id, &event.value.v, &taskWoken) == pdTRUE) {
/* We have mail */
event.status = osEventMessage;
}
else {
event.status = osOK;
}
portEND_SWITCHING_ISR(taskWoken);
}
else {//线程模式获得消息,把消息存到&event.value.v中
if (xQueueReceive(queue_id, &event.value.v, ticks) == pdTRUE) {
/* We have mail */
event.status = osEventMessage;
}
else {
event.status = (ticks == 0) ? osOK : osEventTimeout;
}
}
return event;
}
event结构体
typedef struct {
osStatus status; ///< status code: event or error information
union {
uint32_t v; ///< message as 32-bit value
void *p; ///< message or mail as void pointer
int32_t signals; ///< signal flags
} value; ///< event value
union {
osMailQId mail_id; ///< mail id obtained by \ref osMailCreate
osMessageQId message_id; ///< message id obtained by \ref osMessageCreate
} def; ///< event definition
} osEvent;
event结构体主要包含3个内容:
1、状态osStatus:
如果什么都没发生那就是osOK
如果有信息发过来,那就是osEventMessage
如果是邮箱发过来,那就是osEventMail
。。。。。。
2、值value:
就拿osEventMessage来说,收到的值可以是一个32位的值,也可以是一个指针
3、定义:还没用到,用到再解释
osMessageGet()也是CMSIS封装的函数,本质还是调用xQueueReceiveFromISR()或者xQueueReceive(),封装之后使用更方便,函数内部可以判断是在中断中还是在任务中。
消息队列发送与接收实验(传递一个数据)
知道了消息队列的发送和接收函数就可以做一些简单的实验。建两个任务,如果的创建这里不解释,请参考任务创建章节。一个任务往队列中写消息,一个任务从队列中读消息。
//Task02用来往队列中写消息
//按键按下再松开就往队列中发送数据0x08
void StartTask02(void const * argument)
{
for(;;)
{
if(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(20);
while(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET);
osMessagePut(myQueue01Handle,0x08,1);
}
osDelay(1);
}
}
//Task03用来从队列中读消息
//消息内容如果是8的话就发送一些内容出去
//读队列任务也不添加阻塞
void StartTask03(void const * argument)
{
osEvent QueueEvent;
uint8_t Tx_Buffer[] = "FreeRTOS queue test\r\n";
for(;;)
{
QueueEvent = osMessageGet (myQueue01Handle, osWaitForever);
if((uint8_t)QueueEvent.value.v == 8)
{
HAL_UART_Transmit(&huart1,Tx_Buffer,sizeof(Tx_Buffer),10);
}
osDelay(1);
}
}
4、瞥一眼消息队列中的消息,但是不拿走
跟osMessageGet()函数的用法差不多,只是不把队列的消息拿走
osEvent osMessagePeek (osMessageQId queue_id, uint32_t millisec)
{
TickType_t ticks;
osEvent event;
event.def.message_id = queue_id;
if (queue_id == NULL) {
event.status = osErrorParameter;
return event;
}
ticks = 0;
if (millisec == osWaitForever) {
ticks = portMAX_DELAY;
}
else if (millisec != 0) {
ticks = millisec / portTICK_PERIOD_MS;
if (ticks == 0) {
ticks = 1;
}
}
if (xQueuePeek(queue_id, &event.value.v, ticks) == pdTRUE)
{
/* We have mail */
event.status = osEventMessage;
}
else
{
event.status = (ticks == 0) ? osOK : osEventTimeout;
}
return event;
}
5、统计消息队列中消息的个数
/**
* @brief Get the number of messaged stored in a queue.
* @param queue_id message queue ID obtained with \ref osMessageCreate.
* @retval number of messages stored in a queue.
*/
uint32_t osMessageWaiting(osMessageQId queue_id)
{
if (inHandlerMode()) {
return uxQueueMessagesWaitingFromISR(queue_id);
}
else
{
return uxQueueMessagesWaiting(queue_id);
}
}
用法
//定义一个变量
uint8_t n;
//读消息队列里面消息的数量
n = osMessageWaiting(myQueue01Handle);
6、统计消息队列的剩余空间
/**
* @brief Get the available space in a message queue.
* @param queue_id message queue ID obtained with \ref osMessageCreate.
* @retval available space in a message queue.
*/
uint32_t osMessageAvailableSpace(osMessageQId queue_id)
{
return uxQueueSpacesAvailable(queue_id);
}
消息队列发送与接收实验(传递一个指针)
很多时候消息不仅仅是一个数据,也可能是一个数组或者结构体,因此传输这类消息的时候就可以用到指针的收发。
//osEvent.value.*p
typedef struct {
osStatus status; ///< status code: event or error information
union {
uint32_t v; ///< message as 32-bit value
void *p; ///< message or mail as void pointer
int32_t signals; ///< signal flags
} value; ///< event value
union {
osMailQId mail_id; ///< mail id obtained by \ref osMailCreate
osMessageQId message_id; ///< message id obtained by \ref osMessageCreate
} def; ///< event definition
} osEvent;
消息队列常见的使用方式是多个任务产生消息,一个任务来接收消息。因此消息的来源需要标记一下,也就是定一个消息ID,可以用枚举的方式定义消息ID,如下所示:
typedef enum{
SendTask1,
SendTask2,
} ID_t;
最终还是要发消息出去的,也就是数据,因此可以把ID和数据封装在一个结构体里面,如下所示
typedef struct{
ID_t eDataID; //任务ID
uint8_t data1; //数据1
uint32_t data2; //数据2
uint32_t data3; //数据3
} DataGroup;
在task1中,按下按键发送一条消息给消息队列
void StartTask01(void const * argument)
{
//准备的数据发到消息队列
DataGroup myDataGroup1_t = {SendTask1, 1, 2, 3};
for(;;)
{
if(HAL_GPIO_ReadPin(KEY_2_GPIO_Port,KEY_2_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(20);
while(HAL_GPIO_ReadPin(KEY_2_GPIO_Port,KEY_2_Pin) == GPIO_PIN_RESET);
//按键按下再松开就发送消息到消息队列
if(osMessagePut(myQueue01Handle,(uint32_t)&myDataGroup1_t,1) != osOK)
{
printf("send message fail! \r\n");
}else{
osThreadSuspendAll();
printf("send message sucess! \r\n");
osThreadResumeAll();
}
}
}
}
在task2中,也按下按键发送一条消息给消息队列
void StartTask02(void const * argument)
{
DataGroup myDataGroup2_t = {SendTask2,10, 20, 30};
for(;;)
{
if(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(20);
while(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET);
if(osMessagePut(myQueue01Handle,(uint32_t)&myDataGroup2_t,1) !=osOK)
{
//send failed
} else{
osThreadSuspendAll();
printf("send message sucess! \r\n");
osThreadResumeAll();
}
}
osDelay(1);
}
}
在task3中,从消息队列中读消息
void StartTask03(void const * argument)
{
osEvent QueueEvent;
DataGroup *myDataGroup_r;
uint8_t n;
for(;;)
{
n = osMessageWaiting(myQueue01Handle);
QueueEvent = osMessageGet(myQueue01Handle,osWaitForever);
if(QueueEvent.status == osEventMessage)
{
myDataGroup_r = QueueEvent.value.p;
printf("message count = %d\r\n",n);
printf("ID = %d \r\n",myDataGroup_r->eDataID);
printf("1stdata = %d\r\n",myDataGroup_r->data1);
printf("2nddata = %d\r\n",myDataGroup_r->data2);
printf("3rddata = %d\r\n",myDataGroup_r->data3);
if(myDataGroup_r->eDataID == SendTask1)
{
printf("data form task01");
} else if(myDataGroup_r->eDataID == SendTask2){
printf("data form task02");
}
}
osDelay(1);
}
}
总结:
使用CMSIS封装的函数,跟原版的FreeRTOS函数还是有不小的差异,大家可以根据兴趣来作选择,不管怎样,还是要多阅读函数的实现源码。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)