FreeRTOS(3)----消息队列

一.消息队列的基本概念

队列成为消息队列,可以进行任务与任务间,中断和任务间传递信息,实现任务接收来自其他任务或中断的不固定长度的消息,任务可以从队列中读取消息,当队列消息为空的时候,读取消息的任务将会被阻塞,但是可以设定等待阻塞任务的时候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中断中将接收到的信息发送到消息队列,清零任务标志位以及数组内容【为下一次接收做准备】

//就向队列发送接收到的数据
	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显示屏上

//定时器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);  //清除中断标志位
}
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜路难行々

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值