回顾:
1、队列概述
基于 FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权限的小程序。这些独立的任务之间很可能会通过相互通信以提供有用的系统功能。FreeRTOS 中所有的通信与同步机制都是基于队列(queue)实现的 。
2、特性
数据存储:
队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目被称为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。
数据写入为先进先出(FIFO),可以队尾写入,对首读出;当然也可以对首写入,队尾读出
可被多任务存取:
队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。所有任务都可以向同一队列写入和读出。
读队列时阻塞 :
当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
写队列时阻塞:
同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。
当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。 由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。
3、队列函数
xQueueCreate() API 函数
函数定义:
xQueueHandle xQueueCreate(unsigned portBASE_TYPE uxQueueLength,//队列长度,即单元个数 unsigned portBASE_TYPE uxItemSize);//每个单元大小看到一个非常熟悉的类型:portBASE_TYPE,在上篇博客中,该类型用于得到任务的优先级;
unsigned portBASE_TYPE uxPriority; uxPriority=uxTaskPriorityGet(NULL);//获取当前任务的优先级,所以参数NULL发送数据到队列:
xQueueSendToBack()//发送到队尾 xQueueSendToFront()//发送到队头 xQueueSend()//同上,发送到对首 xQueueSendToBackFromISR()//中断服务中,发送数据到队尾 xQueueSendToFrontFromISR()//中断服务中,发送数据到队首
调用示例:
portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue,//队列句柄 const void * pvItemToQueue,//发送数据的指针 portTickType xTicksToWait );//阻塞超时时间
注意:
如 果 把 xTicksToWait 设 置 为 portMAX_DELAY , 并 且 在FreeRTOSConig.h 中设定 INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制;
函数返回值: pdPASS //成功
errQUEUE_FULL //队列满,知道超时结束,队列依旧满
接受队列数据:
xQueueReceive();//接收队列数据,接一个数据,队首就删一个数据 xQueuePeek();//只接收数据,不对队列进行操作 xQueueReceiveFromISR()//中断中使用的队列接收
调用示例:
portBASE_TYPE xQueuePeek( xQueueHandle xQueue, const void * pvBuffer, portTickType xTicksToWait );
uxQueueMessagesWaiting() API 函数
查询队列中有效数据单元个数
unsigned xportBASE_TYPE uxQueueMessagesWaiting(xQueueHandle xQueue)
队列任务示例:
一个任务不断地往队列中写数据,一个任务读取队列中的数据(读任务设定优先级更高)
因此,一旦队列中有数据,读队列的任务立刻结束阻塞,读取该数据,理论上队列中数据长度不会超过1.
首先是main.c
#include "stm32f4xx.h" #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "list.h" #include "my_include.h"//自定义头文件库 extern xQueueHandle xQueue;//声明 已经定义了队列 static void BSP_init(void); int main(void) { xQueue=xQueueCreate(5,sizeof(long));//创建队列 BSP_init(); APPTaskCreate();//建立任务 vTaskStartScheduler(); while(1); } //设备初始化 static void BSP_init(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);// 中断分组:第四组,4位全用于设置抢占优先级,0位由于响应优先级,中断中也要设置为第四组 LED_GPIO_Config(); Key_GPIO_Config(); BEEP_GPIO_Config(); USART2_Config(); }
APPTaskCreate():
建立了三个任务,发送任务优先级为8,接收任务优先级为9
//任务汇总 void APPTaskCreate(void) {
xTaskCreate(vTaskSender,"Task queue sender1",1000,(void *)100,8,NULL);//queue任务1 ,传入参数为100
xTaskCreate(vTaskSender,"Task queue sender2",1000,(void *)200,8,NULL);//queue任务2 ,传入参数为200xTaskCreate(vTaskReceiver,"Task queue receive",1000,NULL,9,NULL);//queue 接收任务}
任务函数:
xQueueHandle xQueue;//全局队列 //队列发送任务 static void vTaskSender(void *length)//输入参数为队列要发送的值 { long lValueToSend; lValueToSend=(long) length; portBASE_TYPE xStatus; while(1) { xStatus=xQueueSendToBack(xQueue,&lValueToSend,0); if(xStatus!=pdPASS)//发送不成功 { Usart_SendString(USART2,(uint8_t *)"发送失败\n"); } //vTaskDelay(10);//阻塞10个心跳,若采用协作式调度,则使用taskYIELD(); taskYIELD(); } } //队列接收任务 static void vTaskReceiver(void *pvParameters) { long lReceiveValue; portBASE_TYPE xStatus; const portTickType wait_time=100/portTICK_PERIOD_MS;//设定时间为100ms while(1) { if(uxQueueMessagesWaiting(xQueue)!=0)//如果队列不为空 Usart_SendString(USART2,(uint8_t *)"队列中有数据未被读取"); xStatus=xQueueReceive(xQueue,&lReceiveValue,wait_time); if(xStatus==pdPASS)//1,读取成功 { Usart_SendString(USART2,(uint8_t *)"数据为"); Usart_SendString(USART2,(uint8_t *)lReceiveValue); } else Usart_SendString(USART2,(uint8_t *)"读取数据失败"); } }
因此实际通过USART发送的数据应该为:
100 200 100 200 100 200....交替
示例任务解释:
任务流程:
- Receiver任务优先级更高,因此优先执行
- Receiver执行到xStatus=xQueueReceive(xQueue,&lReceiveValue,wait_time)时,由于队列为空,因此会进入阻塞状态等待
- 接着执行次优先级Sender2任务(假设),任务中的发送函数执行完(但sender2尚未结束),队列中有数据了
- Receiver阻塞立刻结束,Sender2被抢占,Receiver执行完回到Sender2任务
- Sender2继续执行,执行到taskYIELD(),Sender2结束,让给同优先级的Sender1任务
- ......
上例,读队列任务具有最高优先级,所以队列不会拥有一个以上的数据单元。这是因为一旦数据被写队列任务写进队列,读队列任务立即抢占写队列任务,把刚写入的数据单元读走。
但如果写队列任务具有最高优先级,所以队列正常情况下一是处于满状态。这是因为一旦读队列任务从队列中读走一个数据单元,某个写队列任务就会立即抢占读队列任务,把刚刚读走的位置重新写入,之后便又转入阻塞态以等待队列空间有效