在实际应用中,我们会遇到一个任务或者中断服务需要和另一个任务进行消息传递,FreeRTOS提供了队列的机制来完成任务与任务、任务与中断之间的消息传递。
0x01 队列简介
队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中断之间传递消息,队列中可以存储有限的、大小固定的数据项目。队列中能保存的最大数据项目数量叫做队列的长度。
1. 数据存储
队列提供了FIFO、LIFO的存储缓冲机制,数据发送到队列中会导致数据拷贝,数据拷贝是值传递,在队列中存储的是数据的原始值,而不是原始值的引用(即值传递数据的引用),FreeRTOS中使用队列传递消息的话虽然使用的是数据拷贝,但是也可以使用引用来传递消息,直接往队列中发送指向这个消息的地址指针就可以了。
2. 多任务访问
队列不属于某个特别指定的任务,任何任务都可以向队列中发送消息,提取消息
0x02 队列结构体
队列的类型是Queue_t,定义在queue.c文件中
typedef struct QueueDefinition
{int8_t *pcHead; /*< Points to the beginning of the queue storage area. */int8_t *pcTail; /*< Points to the byte at the end of the queue storage area. Once more byte is allocated than necessary to store the queue items, this is used as a marker. */int8_t *pcWriteTo; /*< Points to the free next place in the storage area. */union /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */{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;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */} u;List_t xTasksWaitingToSend; /*< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */List_t xTasksWaitingToReceive; /*< List of tasks that are blocked waiting to read from this queue. Stored in priority order. */volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. */UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. */volatile int8_t cRxLock; /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */volatile int8_t cTxLock; /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */#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;
- pcHead:指向队列存储区首地址
- pcTail:指向队列存储区最后一个字节地址
- pcWriteTo:指向下一个可以存储的地址
- pcReadFrom:当用作队列的时候,指向最后一个出队的队列项首地址
- uxRecursiveCallCount:当用作递归互斥量的时候用来记录递归互斥量被调用的次数
- xTasksWaitingToSend:等待发送任务列表,那些因为队列满导致入队失败而进入阻塞态的任务就会挂到此列表上
- xTasksWaitingToReceive:等待接受任务列表,那些因为队列空导致出队失败而进入阻塞态的任务就会挂到此列表上
- uxMessagesWaiting:队列中当前消息数
- uxLength:队列中最大允许的消息数量
- uxItemSize:每个消息最大长度
- cRxLock:当列表上锁后,统计出队的消息数量
- cTxLock:当列表上锁后,统计入队的消息数量
0x03 队列创建
队列创建有两种方法:
- 方法1:使用xQueueCreate函数动态创建
- 方法2:使用xQueueCreateStatic动态创建
1. xQueueCreate
从源码可以看出,使用xQueueCreate,configSUPPORT_DYNAMIC_ALLOCATION 要等于1,支持动态分配内存
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize)
- uxQueueLength:要创建的队列的队列长度,即最多可以接受多少个消息
- uxItemSize :队列中每个消息的长度
创建成功返回队列句柄,失败返回NULL
2.xQueueCreateStatic
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,uint8_t *pucQueueStorageBuffer,StaticQueue_t *pxQueueBuffer);
- uxQueueLength:要创建的队列的队列长度,即最多可以接受多少个消息
- uxItemSize :队列中每个消息的长度
- pucQueueStorage:指向消息的存储区,是一个uint8_t类型的数组,数组的大小要大于等于(uxQueueLength*uxItemSize )
- pxQueueBuffer :保存队列结构体
创建成功返回队列句柄,失败返回NULL
0x04 向队列发送消息
1. 函数原型
创建好队列就可以向队列发送消息了,FreeRTOS提供了8个向对列发送消息的API函数。
1.1 xQueueSend、xQueueSendToBack、xQueueSendToToFront
BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);BaseType_t xQueueSendToBack(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);BaseType_t xQueueSendToToFront(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);
- QueueHandle_t xQueue:任务句柄,指明要向那个队列发送数据
- const void *pvItemToQueue:指向要发送的数据,发送的时候会将这个消息拷贝到队列中
- TickType_t xTicksToWait:阻塞时间,此参数指示队列满的时候任务进入阻塞态等待队列空闲的最大时间,如果是0,队列满会立即返回,当为portMAX_DELAY就会一直等待。
成功返回pdPASS,失败返回errQUEUE_FULL
1.2 xQueueOverwrite
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,const void * pvItemToQueue);
- QueueHandle_t xQueue:队列句柄,指明要向那个队列发送数据
- const void * pvItemToQueue:要发送的数据
1.3 xQueueSendFromISR、xQueueSendToBackFromISR、xQueueSendToFrontFromISR
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,const void * pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
- QueueHandle_t xQueue:队列句柄,指明要向那个队列发送数据
- const void *pvItemToQueue:要发送的数据
- BaseType_t *pxHigherPriorityTaskWoken:标记退出此函数以后是否进行任务切换,这个变量由三个函数来设置,用户不用进行设置,用户只需提供一个变量来保存这个值就行了。当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换。
成功返回pdTURE,失败返回errQUEUE_FULL
0x05 从队列读取消息
从队列中获取消息,FreeRTOS相关API函数如下
1. xQueueReceive、xQueuePeek
BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait)BaseType_t xQueuePeek(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);
- QueueHandle_t xQueue:指明读取那个队列的数据
- void *pvBuffer:读取队列的过程中将读取的数据拷贝到这个缓冲区
- TickType_t xTicksToWait:阻塞时间,如果为0,队列为空立即返回,当为portMAX_DELAY的话就一直等待,直到队列有数据。
2. xQueueReceiveFromISR
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxTaskWoken);BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,void *pvBuffer,);
- QueueHandle_t xQueue:指明读取那个队列的数据
- void *pvBuffer:读取队列的过程中将读取的数据拷贝到这个缓冲区
- BaseType_t *pxTaskWoken:标记退出此函数以后是否进行任务切换
返回值:
返回pdTRUE,从队列中读取数据成功,返回pdFALSE,从队列中读取数据失败。
验证
实验设计三个任务,start_task、task1_task、Keyprocess_task这三个任务。
start_task:用来创建其他两个任务
task1_task:读取按键的键值,然后将键值发送到KEY_Queue队列中,并且检查队列的剩余容量等信息
Keyprocess_task:按键处理任务,读取队列Key_Queue中的信息,根据不同的消息值做相应的处理。
实验需要是哪个按键KEY_UP、KEY2、KEY0,不同的按键对应不同的按键值,任务task1_task会将这些值发送到队列Key_Queue中。
实验中创建两个队列Key_Queue和Message_Queue,队列Key_Queue用于传递按键值,队列Message_Queue用于传递串口发送过来的消息。
实验还需要两个中断,一个是串口1接收中断,一个是定时器9中断,他们的作用如下:串口1接收中断,接收串口发送过来的数据,并将接收到的数据发送到队列Message_Queue中,定时器9中断,定时周期为500ms,在定时器中断中读取队列Message_Queue中的消息,并将其显示在LCD上。
start_task代码:
//开始任务任务函数
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); //进入临界区//创建消息队列Key_Queue=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8)); //创建消息Key_QueueMessage_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(); //退出临界区
}
在start_task中创建了两个队列,分别是Key_Queue和Message_Queue,Key_Queue用于和按键通信,Message_Queue用于和串口通信。
task1_task代码
//task1任务函数
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个时钟节拍 }
}
task1_task获取按键值,并将按键值发送到Key_Queue中
Keyprocess_task代码:
//Keyprocess_task函数
void Keyprocess_task(void *pvParameters)
{u8 num,key,beepsta=1;while(1){if(Key_Queue!=NULL){if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//请求消息Key_Queue{switch(key){case WKUP_PRES: //KEY_UP控制LED1LED1=!LED1;break;case KEY1_PRES: //KEY1控制蜂鸣器beepsta=!beepsta;BEEP=beepsta;break;case KEY0_PRES: //KEY0刷新LCD背景num++;LCD_Fill(126,111,233,313,lcd_discolor[num%14]);break;}}} vTaskDelay(10); //延时10ms,也就是10个时钟节拍 }
}
Keyprocess_task获取Key_Queue队列中按键值
整个main.c代码如下:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "sdram.h"
#include "key.h"
#include "timer.h"
#include "beep.h"
#include "string.h"
#include "malloc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/************************************************ALIENTEK 水星STM32F429开发板 FreeRTOS实验13-1FreeRTOS队列操作实验-HAL库版本技术支持:www.openedv.com淘宝店铺:http://eboard.taobao.com 关注微信公众平台微信号:"正点原子",免费获取STM32资料。广州市星翼电子科技有限公司 作者:正点原子 @ALIENTEK
************************************************///任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 256
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);//任务优先级
#define KEYPROCESS_TASK_PRIO 3
//任务堆栈大小
#define KEYPROCESS_STK_SIZE 256
//任务句柄
TaskHandle_t Keyprocess_Handler;
//任务函数
void Keyprocess_task(void *pvParameters);//按键消息队列的数量
#define KEYMSG_Q_NUM 1 //按键消息队列的数量
#define MESSAGE_Q_NUM 4 //发送数据的消息队列的数量
QueueHandle_t Key_Queue; //按键值消息队列句柄
QueueHandle_t Message_Queue; //信息队列句柄//LCD刷屏时使用的颜色
int lcd_discolor[14]={ WHITE, BLACK, BLUE, BRED, GRED, GBLUE, RED, MAGENTA, GREEN, CYAN, YELLOW,BROWN, BRRED, GRAY };//用于在LCD上显示接收到的队列的消息
//str: 要显示的字符串(接收到的消息)
void disp_str(u8* str)
{LCD_Fill(5,230,110,245,WHITE); //先清除显示区域LCD_ShowString(5,230,100,16,16,str);
}//加载主界面
void freertos_load_main_ui(void)
{POINT_COLOR = RED;LCD_ShowString(10,10,200,16,16,"Apollo STM32F4/F7"); LCD_ShowString(10,30,200,16,16,"FreeRTOS Examp 13-1");LCD_ShowString(10,50,200,16,16,"Message Queue");LCD_ShowString(10,70,220,16,16,"KEY_UP:LED1 KEY0:Refresh LCD");LCD_ShowString(10,90,200,16,16,"KEY1:SendMsg KEY2:BEEP");POINT_COLOR = BLACK;LCD_DrawLine(0,107,239,107); //画线LCD_DrawLine(119,107,119,319); //画线LCD_DrawRectangle(125,110,234,314); //画矩形POINT_COLOR = RED;LCD_ShowString(0,130,120,16,16,"DATA_Msg Size:");LCD_ShowString(0,170,120,16,16,"DATA_Msg rema:");LCD_ShowString(0,210,100,16,16,"DATA_Msg:");POINT_COLOR = BLUE;
}//查询Message_Queue队列中的总队列数量和剩余队列数量
void check_msg_queue(void)
{u8 *p;u8 msgq_remain_size; //消息队列剩余大小u8 msgq_total_size; //消息队列总大小taskENTER_CRITICAL(); //进入临界区msgq_remain_size=uxQueueSpacesAvailable(Message_Queue);//得到队列项剩余大小msgq_total_size=uxQueueMessagesWaiting(Message_Queue)+uxQueueSpacesAvailable(Message_Queue);//得到队列总大小,总大小=使用+剩余的。p=mymalloc(SRAMIN,20); //申请内存sprintf((char*)p,"Total Size:%d",msgq_total_size); //显示DATA_Msg消息队列总的大小LCD_ShowString(10,150,100,16,16,p);sprintf((char*)p,"Remain Size:%d",msgq_remain_size); //显示DATA_Msg剩余大小LCD_ShowString(10,190,100,16,16,p);myfree(SRAMIN,p); //释放内存taskEXIT_CRITICAL(); //退出临界区
}int main(void)
{HAL_Init(); //初始化HAL库 Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhzdelay_init(180); //初始化延时函数uart_init(115200); //初始化串口LED_Init(); //初始化LED KEY_Init(); //初始化按键BEEP_Init(); //初始化蜂鸣器SDRAM_Init(); //初始化SDRAMLCD_Init(); //初始化LCDTIM9_Init(5000,18000-1); //初始化定时器9,周期500msmy_mem_init(SRAMIN); //初始化内部内存池freertos_load_main_ui(); //加载主UI//创建开始任务xTaskCreate((TaskFunction_t )start_task, //任务函数(const char* )"start_task", //任务名称(uint16_t )START_STK_SIZE, //任务堆栈大小(void* )NULL, //传递给任务函数的参数(UBaseType_t )START_TASK_PRIO, //任务优先级(TaskHandle_t* )&StartTask_Handler); //任务句柄 vTaskStartScheduler(); //开启任务调度
}//开始任务任务函数
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); //进入临界区//创建消息队列Key_Queue=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8)); //创建消息Key_QueueMessage_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(); //退出临界区
}//task1任务函数
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个时钟节拍 }
}//Keyprocess_task函数
void Keyprocess_task(void *pvParameters)
{u8 num,key,beepsta=1;while(1){if(Key_Queue!=NULL){if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//请求消息Key_Queue{switch(key){case WKUP_PRES: //KEY_UP控制LED1LED1=!LED1;break;case KEY1_PRES: //KEY1控制蜂鸣器beepsta=!beepsta;BEEP=beepsta;break;case KEY0_PRES: //KEY0刷新LCD背景num++;LCD_Fill(126,111,233,313,lcd_discolor[num%14]);break;}}} vTaskDelay(10); //延时10ms,也就是10个时钟节拍 }
}