【FreeRTOS】队列管理2

时间:2022-07-24
本文章向大家介绍【FreeRTOS】队列管理2,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

使用队列传递复合数据类型

一个任务从单个队列中接收来自多个发送源的数据是经常的事。通常接收方收到数据后,需要知道数据的来源,并根据数据的来源决定下一步如何处理。一个简单的方式就是利用队列传递结构体,结构体成员中就包含了数据信息和来源信息。

创建一个队列用于保存类型为xData 的结构体数据单元。结构体成员包括了一个数据值和表示数据含义的编码,两者合为一个消息可以一次性发送到队列。 ? 中央控制任务用于完成主要的系统功能。其必须对队列中传来的输入和其它系统状态的改变作出响应。 ? CAN 总线任务用于封装CAN 总线的接口功能。当CAN 总线任务收到并解码一个消息后,其将把解码后的消息放到xData 结构体中发往控制任务。结构体的iMeaning成员用于让中央控制任务知道这个数据是用来干什么的 — 从图中的描述可以看出,这个数据表示电机速度。结构体的iValue 成员可以让中央控制任务知道电机的实际速度值。 ? 人机接口(HMI)任务用于对所有的人机接口功能进行封装。设备操作员可能通过各种方式进行命令输入和参数查询,人机接口任务需要对这些操作进行检测并解析。当接收到一个新的命令后,人机接口任务通过xData 结构将命令发送到中央控制任务。结构体的iMeaning 成员用于让中央控制任务知道这个数据是用来干什么的 — 从图中的描述可以看出,这个数据表示一个新的参数设置。结构体的iValue 成员可以让中央控制任务知道具体的设置值。

例子11:

例11 与例10 类似,只是写队列任务与读队列任务的优先级交换了,即读队列任务的优先级低于写队列任务的优先级。并且本例中的队列用于在任务间传递结构体数据,而非简单的长整型数据。 结构体定义 /* 定义队列传递的结构类型。 */ typedef struct { unsigned char ucValue; unsigned char ucSource; } xData; /* 声明两个xData类型的变量,通过队列进行传递。 */ static const xData xStructsToSend[ 2 ] = { { 100, mainSENDER_1 }, /* Used by Sender1. */ { 200, mainSENDER_2 } /* Used by Sender2. */ }; 在例10 中读队列任务具有最高优先级,所以队列不会拥有一个以上的数据单元。这是因为一旦数据被写队列任务写进队列,读队列任务立即抢占写队列任务,把刚写入的数据单元读走。在例11 中,写队列任务具有最高优先级,所以队列正常情况下一直是处于满状态。这是因为一旦读队列任务从队列中读走一个数据单元,某个写队列任务就会立即抢占读队列任务,把刚刚读走的位置重新写入,之后便又转入阻塞态以等待队列空间有效。 写队列任务的实现: 写队列任务指定了100 毫秒的阻塞超时时间,以便在队列满时转入阻塞态以等待队列空间有效。进入阻塞态后,一旦队列空间有效,或是等待超过了100 毫秒队列空间尚无效,其将解除阻塞。在本例中,将永远不会出现100 毫秒超时的情况,因为读队列任务在不停地从队列中读出数据从而腾出队列数据空间。 读队列任务的实现: 读队列任务的优先级最低,所以只有在所有写队列任务都进入阻塞态后才有机会得到执行。而写队列任务只会在队列满时才会进入阻塞态,所以读队列任务得到执行时队列已满。因此读队列任务只管不停地读取数据,不必设定超时时间。 主函数main()与上一例比起来只作了微小的改动。创建的队列数可以保存三个xData 类型的数据单元,并且交换了写队列任务与读队列任务的优先级 代码实现: 点击(此处)折叠或打开

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
 
// FreeRTOS head file, add here.
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "list.h"
#include "portable.h"
#include "FreeRTOSConfig.h"
 
#define SENDER_1 (unsigned char )1
#define SENDER_2 (unsigned char )2
 
typedef struct data_type{
 
    unsigned char ucValue;
    unsigned char ucSource;
 
}data_type;
 
 
static const data_type structDateToSend[2] = {
    {100, SENDER_1},
    {200, SENDER_2},
};
 
 
/* declare a queueHandle variable, using to save queue handler. */
xQueueHandle xQueue;
 
 
 
 
void vSendTask(void *pvParameters)
{
    data_type * valueToSend;
    portBASE_TYPE status;
 
    valueToSend = (data_type *) pvParameters;
 
    while(1)
    {
        status = xQueueSendToBack(xQueue, valueToSend, 100 / portTICK_RATE_MS);
        if(status != pdPASS)
        {
            printf("could not send to the queue. rn");
        }
 
        taskYIELD();
    }
}
 
 
void vReceiveTask(void *pvParameters)
{
    data_type lReceivedValue;
    portBASE_TYPE status;
 
    while(1)
    {
        if( uxQueueMessagesWaiting( xQueue ) != 3 )
        {
            printf( "Queue should have been full! rn");
        }
 
        status = xQueueReceive( xQueue, &lReceivedValue, 0 );
        if( status == pdPASS )
        {
            if(lReceivedValue.ucSource == SENDER_1)
                printf("from sender1 = %d rn", lReceivedValue.ucValue);
            else if(lReceivedValue.ucSource == SENDER_2)
                printf("from sender2 = %d rn", lReceivedValue.ucValue);
        }
        else
        {
            printf( "Could not receive from the queue.rn" );
        }
    }
}
 
int main(void)
{
    // board initialize.
    LED_Init();             
    uart_init(115200);
 
    // create queue, can store 3 value which data type is data_type
    xQueue = xQueueCreate(3, sizeof(data_type));
 
    if(xQueue != NULL) // adjust the return value, to confirm whether create queue successful.
    {
        // Create two write queue task, priority = 2;
        xTaskCreate(vSendTask, "SendTask1", configMINIMAL_STACK_SIZE, (void *)&structDateToSend[0], 2, NULL);
        xTaskCreate(vSendTask, "SendTask2", configMINIMAL_STACK_SIZE, (void *)&structDateToSend[1], 2, NULL);
 
        // create one read queue task, priority = 1;
        xTaskCreate(vReceiveTask, "RecTask", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
 
        // start scheduler now
        vTaskStartScheduler();
    }
    else
    {
        // queue create unsuccessful here. add your code.
    }
 
    return 0;
}

打印结果:

执行流程:

执行流程分析: t1 写队列任务1 得到执行,并往队列中发送数据. t2 写队列任务1 切换到写队列任务2。写队列任务2 往队列中发送数据。 t3 写队列任务2 又切回写队列任务1。写队列任务1 再次将数据写入队列,导致队列满。 t4 写队列任务1 切换到写队列任务2。 t5 写队列任务2 试图往队列中写入数据。但由于队列已满,所以写队列任务2 转入阻塞态以等待队列空间有效。这使得写队列任务1 再次得到执行。 t6 写队列任务1 试图往队列中写入数据。但由于队列已满,所以写队列任务1 也转入阻塞态以等待队列空间有效。此时写队列任务均处于阻塞态,这才使得被赋予最低优先级的读队列任务得以执行。 t7 读队列任务从队列读取数据,并把读出的数据单元从队列中移出。一旦队列空间有效,写队列任务2 立即解除阻塞,并且因为其具有更高优先级,所以抢占读队列任务。写队列任务2 又往队列中写入数据,填充到刚刚被读队列任务腾出的存储空间,使得队列再一次变满。写队列发送完数据后便调用taskYIELD(),但写队列任务1 尚还处理阻塞态,所以写队列任务2 并未被切换出去,继续执行。 t8 写队列任务2 试图往队列中写入数据。但队列已满,所以写队列任务2 转入阻塞态。两个写队列任务再一次同时处于阻塞态,所以读队列任务得以执行。t9 读队列任务从队列读取数据,并把读出的数据单元从队列中移出。一旦队列空间有效,写队列任务1 立即解除阻塞,并且因为其具有更高优先级,所以抢占读队列任务。写队列任务1 又往队列中写入数据,填充到刚刚被读队列任务腾出的存储空间,使得队列再一次变满。写队列发送完数据后便调用taskYIELD(),但写队列任务2 尚还处理阻塞态,所以写队列任务1 并未被切换出去,继续执行。写队列任务1 试图往队列中写入数据。但队列已满,所以写队列任务1 转入阻塞态。

工作于大型数据单元

如果队列存储的数据单元尺寸较大,那最好是利用队列来传递数据的指针而不是对数据本身在队列上一字节一字节地拷贝进或拷贝出。传递指针无论是在处理速度上还是内存空间利用上都更有效。但是,当你利用队列传递指针时,一定要十分小心地做到以下两点: 1. 指针指向的内存空间的所有权必须明确 当任务间通过指针共享内存时,应该从根本上保证所不会有任意两个任务同时 修改共享内存中的数据,或是以其它行为方式使得共享内存数据无效或产生一致性 问题。原则上,共享内存在其指针发送到队列之前,其内容只允许被发送任务访问; 共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。 2. 指针指向的内存空间必须有效 如果指针指向的内存空间是动态分配的,只应该有一个任务负责对其进行内存释放。当这段内存空间被释放之后,就不应该有任何一个任务再访问这段空间。 切忌用指针访问任务栈上分配的空间。因为当栈帧发生改变后,栈上的数据将不再有效。