STM32F429-DISCO 学习之FreeRTOS队列管理

/ 0评 / 0

FreeRTOS有队列管理的概念,不过换句话来看就像FIFO/LIFO结合体.他们是两个(或者更多更多)任务用来共享数据的一个通道.比如Task A是队列的生产者,Task B是队列的消费者.队列的变量暂且定义为x[3],共3个元素.队列状态应该如此转移:

  1. 初始化队列后,队列都是空的,可以存放3个数据.
  2. Task A向队列末端x[2],加一个数据.
  3. Task A再向队列加数据,因为末端已经被占用,而且,Task B还没取走,那么Task A只能把数据写在倒数第二个,就是x[1].
  4. Task B取走一个数据,取走的总是末端的数据,x[2]会被取走.
  5. 那么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时候的阻塞,这时候整个任务就无效了,所以发送者可以写,当写入后,接受者立即有效... 偷一个官方的图.
QQ截圖20151120124059
一个任务从单个队列中接收来自多个发送源的数据是经常的事,就刚才的例子来说,如果两个都要传递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();
    }
}

当然,也可以从传入时候就指定要发送的包,但是一般只传递指针,并不传递真正的内容.另外需要注意好内存的管理.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注