FreeRTOS有队列管理的概念,不过换句话来看就像FIFO/LIFO结合体.他们是两个(或者更多更多)任务用来共享数据的一个通道.比如Task A是队列的生产者,Task B是队列的消费者.队列的变量暂且定义为x[3],共3个元素.队列状态应该如此转移:
- 初始化队列后,队列都是空的,可以存放3个数据.
- Task A向队列末端x[2],加一个数据.
- Task A再向队列加数据,因为末端已经被占用,而且,Task B还没取走,那么Task A只能把数据写在倒数第二个,就是x[1].
- Task B取走一个数据,取走的总是末端的数据,x[2]会被取走.
- 那么x[1]就会被移动到x[2]的位置,以便Task A继续填充,Task B继续取走数据.
队列的创建,是事先通过xQueueCreate来完成的,然后使用xQueueSend来发送数据, xQueueSend完全等同于xQueueSendToBack,xQueueSendToBack用于将数据发送到队列 尾,而xQueueSendToFront用于将数据发送到队列首.发送数据都可以发生阻塞等待,因为如果队列是满的,阻塞等待就非常重要了.因为你数据塞不下去,需要等一下.
而接收数据,用的是xQueueReceive,接收后,从队列删除这个数据,当然,也可以用xQueuePeek接收,这个接收就不会删除队列中已经读走的数据.函数uxQueueMessagesWaiting用于查询有没有数据可以读的.
我们还是利用那两个LED做实验,在两个不同任务,或者同一个任务的两个参数内,不停发送数值,100和200.搞一个程序接收,判断接收的内容来点亮LED.
首先发送函数,我们周期性地执行一个任务.
static void vSenderTask( void *pvParameters ) { long lValueToSend; portTickType xLastExecutionTime; portBASE_TYPE xStatus; lValueToSend = ( long ) pvParameters; xLastExecutionTime = xTaskGetTickCount(); for( ;; ) { vTaskDelayUntil( &xLastExecutionTime, lValueToSend * 3 / portTICK_RATE_MS ); xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 ); if( xStatus != pdPASS ) { } } }
这样就是周期性发送100和200到队列,而100发的比较多,每300ms发一次,而200要每600ms才能发一次.然后执行这个读取函数.读取时候,每400ms阻塞为极限来取数据,并且uxQueueMessagesWaiting会发现队列一直是空的,因为本任务每次都把东西取走了.要想测试,需要到断点调试去看看.实在没有足够的LED灯.
static void vReceiverTask( void *pvParameters ) { long lReceivedValue; portBASE_TYPE xStatus; const portTickType xTicksToWait = 400 / portTICK_RATE_MS; for( ;; ) { if( uxQueueMessagesWaiting( xQueue ) != 0 ) { } xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait ); if( xStatus == pdPASS ) { if(lReceivedValue == 100) { GPIO_ToggleBits(GPIOG, GPIO_Pin_13); } if(lReceivedValue == 200) { GPIO_ToggleBits(GPIOG, GPIO_Pin_14); } } else { } taskYIELD(); } }
这里发现一个新函数taskYIELD,这个函数意思是,主动放弃就绪状态,就是事情做完了,可以主动放弃这个状态了,可以用这句话.
这里要解释一下,为什么接受者的优先级如此高,还能让发送者发送,这主要是接受者有Read时候的阻塞,这时候整个任务就无效了,所以发送者可以写,当写入后,接受者立即有效... 偷一个官方的图.
一个任务从单个队列中接收来自多个发送源的数据是经常的事,就刚才的例子来说,如果两个都要传递100这个数值,其中一个是传递电机A,另一个传递电机B,那收到的人可就分不清了.所以就用到了复合结构.也就是一次传递一个结构体.比如定义一个如下的发信包.
typedef struct { uint8_t ucValue; uint8_t ucSource; } xData;
那初始化队列就变成了:
xQueue = xQueueCreate( 3, sizeof( xQueue ) );
接受者和发送者的区别:
static void vSenderTask( void *pvParameters ) { long lValueToSend; portTickType xLastExecutionTime; xData xPost; portBASE_TYPE xStatus; lValueToSend = ( long ) pvParameters; if(lValueToSend == 100){ xPost.ucValue = 100; xPost.ucSource = 1; }else{ xPost.ucValue = 200; xPost.ucSource = 2; } xLastExecutionTime = xTaskGetTickCount(); for( ;; ) { vTaskDelayUntil( &xLastExecutionTime, lValueToSend * 3 / portTICK_RATE_MS ); xStatus = xQueueSendToBack( xQueue, &xPost, 0 ); if( xStatus != pdPASS ) { } } } static void vReceiverTask( void *pvParameters ) { xData lReceivedValue; portBASE_TYPE xStatus; const portTickType xTicksToWait = 400 / portTICK_RATE_MS; for( ;; ) { if( uxQueueMessagesWaiting( xQueue ) != 0 ) { } xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait ); if( xStatus == pdPASS ) { if(lReceivedValue.ucValue == 100 && lReceivedValue.ucSource == 1) { GPIO_ToggleBits(GPIOG, GPIO_Pin_13); } if(lReceivedValue.ucValue == 200 && lReceivedValue.ucSource == 2) { GPIO_ToggleBits(GPIOG, GPIO_Pin_14); } } else { } taskYIELD(); } }
当然,也可以从传入时候就指定要发送的包,但是一般只传递指针,并不传递真正的内容.另外需要注意好内存的管理.