一.消息队列的基本概念
队列成为消息队列,可以进行任务与任务间,中断和任务间传递信息,实现任务接收来自其他任务或中断的不固定长度的消息,任务可以从队列中读取消息,当队列消息为空的时候,读取消息的任务将会被阻塞,但是可以设定等待阻塞任务的时候xTicksToWait(),当队列中有了新的信息,被阻塞的任务就会被唤醒去处理新的信息。当等待的时间超过指定的阻塞时间,且队列无有效信息,任务就会从阻塞态转为就绪态。
一个任务可以将多个消息放入任何一个消息队列,同时一个或者多个任务可以从消息队列中获得信息,当有多个信息发送到消息队列时,通常会把先放入消息队列给任务【先进先出原则】,但也支持后进先出。
二.消息队列的运作机制
创建消息队列freertos会给消息队列分配一块内存空间,这块内存大小等于消息队列控制块大小加上(单个消息的内存空间与消息队列长度的乘积),接着再初始化消息队列,此时消息队列为空。
系统会为队列控制块分配对应的内存空间,保存消息队列的一些信息,例如:头指针pcHead,尾指针pcTail,消息大小uxItemSize,队列长度uxLength等。
在创建成功的时候,内存已经被占用了,只有删除了消息队列,这段内存才会被释放掉。 消息空间可以存放不大于消息大小uxItemSize的任意类型数据,消息空间总数即是消息队列的长度。
主要分析:
当发送信息的时候,如果队列未满,则会将消息拷贝到队尾,否则,会根据用户指定的阻塞超过时间进行阻塞,如果队列一直不允许入队,任务则一直保持阻塞状态,等待队列允许入队,在这段时间,当别的任务从其他等待队列读入了数据(队列未满),任务将自动从阻塞态变为就绪态,当等待的时间超过阻塞时间,即使队列还不允许入队,任务也会从阻塞态变为就绪态,且发送信息的任务或者中断程序会收到一个错误码。
发送紧急消息:唯一的不同在于,消息会直接放在队头,这样接收者就可以优先接收到紧急消息,从而进行处理。
当任务读取一个队列的时候,可以指定一个阻塞时间,在这段时间,任务将保持阻塞状态来等待队列数据的有效,当其他任务或者中断程序往等待的队列写入了数据,任务将从阻塞态转为就绪态。或者当阻塞时间到了且队列中还是没有数据,任务也会从阻塞态转为就绪态。
三.消息队列的阻塞机制
一个任务A要读取队列中的信息:
三种选择:
·队列无消息,直接却干别的事情
·进入阻塞态,等待队列中来信息,当阻塞时间到了,任务转为就绪态,返回一个错误代码
·一直在阻塞态,直到队列中有信息
在中断中不允许带有阻塞机制的,所以需要调用在中断发送信息的API函数接口。
注意:当多个任务阻塞在一个消息队列中,这些阻塞的任务会按照任务的优先级进排序,优先级高的任务将优先获得访问队列的权利。
四.消息队列创建函数
1.创建队列
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );
uxQueueLength:队列能够存储的最大消息单元数目,即队列长度uxItemSize :队列中消息单元的大小,以字节为单位。eg. xQueueCreate(3,sizeof(int )*3) --->创建三个消息,大小为12字节
2.创建静态队列QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,uint8_t *pucQueueStorageBuffer,StaticQueue_t *pxQueueBuffer );uxQueueLength :队列能够存储的最大单元数目,即队列深度。uxItemSize :队列中数据单元的长度,以字节为单位。pucQueueStorageBuffer: 指针,指向一个 uint8_t 类型的数组,数组的大小至少uxQueueLength* uxItemSize 个字节。当 uxItemSize 为 0 时,pucQueueStorageBuffer 可以为 NULL 。pxQueueBuffer :指针,指向 StaticQueue_t 类型的变量,该变量用于存储队列的数据结构。
3.发送消息到队列BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);xQueue :队列句柄。pvItemToQueue: 指针,指向要发送到队列尾部的队列消息。xTicksToWait:队列满时,等待队列空闲的最大超时时间。如果队列满并 且xTicksToWait 被设置成 0 ,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms 。如果 INCLUDE_vTaskSuspend 设置成 1 ,并且指定延时为 portMAX_DELAY 将导致任务挂起(没有超时)。
4.中断发送消息到队列
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);xQueue :队列句柄。pvItemToQueue :指针,指向要发送到队列尾部的消息。pxHigherPriorityTaskWoken 如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将 *pxHigherPriorityTaskWoken设置成 pdTRUE ,然后在中断退出前需要进行一次上下文切换, 去 执 行 被 唤醒 的 优 先 级 更高 的 任 务 。从FreeRTOS V7.3.0 起, pxHigherPriorityTaskWoken 作为一个可选参数,可以设置为 NULL 。
5.紧急任务发送消息到队头【不可以用在中断中】BaseType_t xQueueSendToFront( QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait );xQueue :队列句柄。pvItemToQueue : 指针,指向要发送到队首的消息。xTicksToWait:队列满时,等待队列空闲的最大超时时间。如果队列满并 且xTicksToWait 被设置成 0 ,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms 。如果 INCLUDE_vTaskSuspend 设置成 1 ,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(没有超时)。xQueueSendToFrontFromISR() 【可以在中断中使用】
6.接收队列消息BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);xQueue :队列句柄。pvBuffer:指针,指向接收到要保存的数据。xTicksToWait :队列空时,阻塞超时的最大时间。如果该参数设置为 0 ,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms 。如果 INCLUDE_vTaskSuspend 设置成 1 ,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(没有超时)。
7. 中断接收消息
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxHigherPriorityTaskWoken);xQueue :队列句柄。pvBuffer:指针,指向接收到要保存的数据。pxHigherPriorityTaskWoken :任务在往队列投递信息时,如果队列满,则任务将阻塞在该队列上。如果 xQueueReceiveFromISR() 到账了一个任 务 解 锁 了 则将 *pxHigherPriorityTaskWoken 设 置为pdTRUE , 否 则 *pxHigherPriorityTaskWoken 的 值将不变。从 FreeRTOS V7.3.0 起, pxHigherPriorityTaskWoken作为一个可选参数,可以设置为 NULL 。
代码理解:
创建开始代码,设置任务1和任务2
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建消息队列
Key_Queue=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8)); //创建消息Key_Queue
Message_Queue=xQueueCreate(MESSAGE_Q_NUM,USART_REC_LEN); //创建消息Message_Queue,队列项长度是串口接收缓冲区长度
//创建TASK1任务
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建TASK2任务
xTaskCreate((TaskFunction_t )Keyprocess_task,
(const char* )"keyprocess_task",
(uint16_t )KEYPROCESS_STK_SIZE,
(void* )NULL,
(UBaseType_t )KEYPROCESS_TASK_PRIO,
(TaskHandle_t* )&Keyprocess_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
任务1发送按键状态到消息队列
void task1_task(void *pvParameters)
{
u8 key,i=0;
BaseType_t err;
while(1)
{
key=KEY_Scan(0); //扫描按键
if((Key_Queue!=NULL)&&(key)) //消息队列Key_Queue创建成功,并且按键被按下
{
err=xQueueSend(Key_Queue,&key,10);
if(err==errQUEUE_FULL) //发送按键值
{
printf("队列Key_Queue已满,数据发送失败!\r\n");
}
}
i++;
if(i%10==0) check_msg_queue();//检Message_Queue队列的容量
if(i==50)
{
i=0;
LED0=!LED0;
}
vTaskDelay(10); //延时10ms,也就是10个时钟节拍
}
}
任务2接收队列中的按键消息
void Keyprocess_task(void *pvParameters)
{
u8 num,key;
while(1)
{
if(Key_Queue!=NULL)
{
if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//请求消息Key_Queue
{
switch(key)
{
case WKUP_PRES: //KEY_UP控制LED1
LED1=!LED1;
break;
case KEY0_PRES: //KEY0刷新LCD背景
num++;
LCD_Fill(126,111,233,313,lcd_discolor[num%14]);
break;
}
}
}
vTaskDelay(10); //延时10ms,也就是10个时钟节拍
}
}
在串口1中断中将接收到的信息发送到消息队列,清零任务标志位以及数组内容【为下一次接收做准备】文章来源:https://www.toymoban.com/news/detail-841308.html
//就向队列发送接收到的数据
if((USART_RX_STA&0x8000)&&(Message_Queue!=NULL))
{
xQueueSendFromISR(Message_Queue,USART_RX_BUF,&xHigherPriorityTaskWoken);//向队列中发送数据
USART_RX_STA=0;
memset(USART_RX_BUF,0,USART_REC_LEN);//清除数据接收缓冲区USART_RX_BUF,用于下一次数据接收
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
}
设置定时器在定时器中断中接收串口发送到队列的消息,将接收到的信息打印在LCD显示屏上文章来源地址https://www.toymoban.com/news/detail-841308.html
//定时器2中断服务函数
void TIM2_IRQHandler(void)
{
u8 *buffer;
BaseType_t xTaskWokenByReceive=pdFALSE;
BaseType_t err;
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //溢出中断
{
buffer=mymalloc(USART_REC_LEN);
if(Message_Queue!=NULL)
{
memset(buffer,0,USART_REC_LEN); //清除缓冲区
err=xQueueReceiveFromISR(Message_Queue,buffer,&xTaskWokenByReceive);//请求消息Message_Queue
if(err==pdTRUE) //接收到消息
{
disp_str(buffer); //在LCD上显示接收到的消息
}
}
myfree(buffer); //释放内存
portYIELD_FROM_ISR(xTaskWokenByReceive);//如果需要的话进行一次任务切换
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断标志位
}
到了这里,关于FreeRTOS(3)----消息队列的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!