深入了解FreeRTOS中的消息队列(第三部分)

一.消息队列的基本概念

队列成为消息队列,可以进行任务与任务间,中断和任务间传递信息,实现任务接收来自其他任务或中断的不固定长度的消息,任务可以从队列中读取消息,当队列消息为空的时候,读取消息的任务将会被阻塞,但是可以设定等待阻塞任务的时候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);  //清除中断标志位
}

作者:夜路难行々

物联沃分享整理
物联沃-IOTWORD物联网 » 深入了解FreeRTOS中的消息队列(第三部分)

发表评论