FreeRTOS队列和队列集(STM32F103C8T6)

       在实际的项目开发中,经常会遇到在任务于任务之间或任务于中断之间需要进行“沟通交 流”,这里的“沟通交流”就是消息传递的过程。在不使用操作系统的情况下,函数与函数,或 函数与中断之间的“沟通交流”一般使用一个或多多个全局变量来完成,但是在操作系统中, 因为会涉及“资源管理”的问题,比方说读写冲突,因此使用全局变量在任务于任务或任务于 中断之间进行消息传递,并不是很好的解决方案。FreeRTOS 为此提供了“队列”的机制。

FreeRTOS 队列简介
       队列的结构体为 Queue_t,在 queue.c 文件中有定义。
        队列是一种任务到任务、任务到中断、中断到任务数据交流的一种机制。在队列中可以存 储数量有限、大小固定的多个数据,队列中的每一个数据叫做队列项目,队列能够存储队列项 目的最大数量称为队列的长度,在创建队列的时候,就需要指定所创建队列的长度及队列项目 的大小。因为队列是用来在任务与任务或任务于中断之间传递消息的一种机制,因此 队列也叫做消息队列。 基于队列,FreeRTOS 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二 值信号量、递归互斥信号量,因此很有必要深入了解 FreeRTOS 的队列。
        基于队列,FreeRTOS 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二 值信号量、递归互斥信号量,因此很有必要深入了解 FreeRTOS 的队列。
1. 数据存储
       队列通常采用 FIFO(先进先出)的存储缓冲机制,当有新的数据被写入队列中时,永远都 是写入到队列的尾部,而从队列中读取数据时,永远都是读取队列的头部数据。但同时 FreeRTOS 的队列也支持将数据写入到队列的头部,并且还可以指定是否覆盖先前已经在队列头部的数据。
2. 多任务访问
       队列不属于某个特定的任务,可以在任何的任务或中断中往队列中写入消息,或者从队列中读取消息。
3. 队列读取阻塞
       在任务从队列读取消息时,可以指定一个阻塞超时时间。如果任务在读取队列时,队列为空,这时任务将被根据指定的阻塞超时时间添加到阻塞态任务列表中进行阻塞,以等待队列中 有可用的消息。当有其他任务或中断将消息写入队列中,因等待队列而阻塞任务将会被添加到 就绪态任务列表中,并读取队列中可用的消息。如果任务因等待队列而阻塞的时间超过指定的 阻塞超时时间,那么任务也将自动被转移到就绪态任务列表中,但不再读取队列中的数据。 
       因为同一个队列可以被多个任务读取,因此可能会有多个任务因等待同一个队列,而被阻 塞,在这种情况下,如果队列中有可用的消息,那么也只有一个任务会被解除阻塞并读取到消 息,并且会按照阻塞的先后和任务的优先级,决定应该解除哪一个队列读取阻塞任务。
4. 队列写入阻塞
       与队列读取一样,在任务往队列写入消息时,也可以指定一个阻塞超时时间。如果任务在写入队列时,队列已经满了,这时任务将被根据指定的阻塞超时时间添加到阻塞态任务列表中 进行阻塞,以等待队列有空闲的位置可以写入消息。指定的阻塞超时时间为任务阻塞的最大时 间,如果在阻塞超时时间到达之前,队列有空闲的位置,那么队列写入阻塞任务将会解除阻塞, 并往队列中写入消息,如果达到指定的阻塞超时时间,队列依旧没有空闲的位置写入消息,那么队列写入阻塞任务将会自动转移到就绪态任务列表中,但不会往队列中写入消息。 
       因为同一个队列可以被多个任务写入,因此可有会有多个任务因等待统一个任务,而被阻塞,在这种情况下,如果队列中有空闲的位置,那么也之后一个任务会被解除阻塞并往队列中写入消息,并且会按照阻塞的先后和任务的优先级,决定应该解除哪一个队列写入阻塞任务。
5. 队列操作
       下面简单介绍一下队列操作的过程,包括创建队列、往队列中写入消息、从队列中读取消息等操作。
(1) 创建队列

       如图 13.1.1,创建了一个用于任务 A 与任务 B 之间“沟通交流”的队列,这个队列最大可容纳5个队列项目,即队列的长度为 5。刚创建的队列是不包含内容的,因此这个队列为空。

(2) 往队列写入第一个消息

       如图 13.1.2,任务 A 将一个私有变量写入队列的尾部。由于在写入队列之前,队列是空的, 因此新写入的消息,既是是队列的头部,也是队列的尾部。

(3) 往队列中写入第二个消息 

       如图 13.1.3,任务 A 改变了私有变量的值,并将新值写入队列。现在队列中包含了队列 A 写入的两个值,其中第一个写入的值在队列的头部,而新写入的值在队列的尾部。这时队列还 有 3 个空闲的位置。

(4) 从队列读取第一个消息

       如图 13.1.4,任务B从队列中读取消息,任务B读取的消息是处于队列头部的消息,这是 任务 A 第一次往队列中写入的消息。在任务B从队列中读取消息后,队列中任务 A 第二次写 入的消息,变成了队列的头部,因此下次任务 B 再次读取消息时,将读取到这个消息。此时队列中剩余4个空闲的位置。

FreeRTOS 队列相关 API 函数

队列创建

函数 xQueueCreate()

      此函数用于使用动态方式创建队列,队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理 的堆中分配。函数 xQueueCreate()实际上是一个宏定义,在 queue.h 文件中有定义,具体的代码如下所示:

      函数 xQueueCreate()的形参描述,如下表所示:

       可以看到,函数 xQueueCreate()实际上是调用了函数 xQueueGenericCreate(),函数 xQueueGenericCreate()用于使用动态方式创建指定类型的队列,前面说 FreeRTOS 基于队列实现 了多种功能,每一种功能对应一种队列类型,队列类型的 queue.h 文件中有定义,具体的代码如下所示:

队列写入消息
FreeRTOS 中用于往队列中写入消息的 API 函数如下表所示:

        在任务中往队列写入消息的函数有函数 xQueueSend() 、 xQueueSendToBack() 、 xQueueSendToFront()、xQueueOverwrite(),这 4 个函数实际上都是宏定义,在 queue.h 文件中有 定义,具体的代码如下所示:

        从上面的代码中可以看到,函数 xQueueSend()、函数 xQueueSendToBack()、函数 xQueueSendToFront()和函数 xQueueOverwrite()实际上都是调用了函数 xQueueGenericSend(),只 是指定了不同的写入位置,队列一共有 3 种写入位置,在 queue.h 文件中有定义,具体的代码 如下所示:

        要注意的是,覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用。

        函 数 xQueueGenericSend() 用 于 在 任 务 中 往 队 列 的 指 定 位 置 写 入 消 息 。 函 数 xQueueGenericSend()的函数原型如下所示:

       函数 xQueueGenericSend()的形参描述,如下表所示:

队列读取消息

        FreeRTOS 中用于从队列中读取消息的 API 函数如下表所示:

函数 xQueueReceive()

       此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。 消息的读取是通过拷贝的形式传递的,具体拷贝数据的大小,为队列项目的大小。该函数的函 数原型如下所示:

      函数 xQueueReceive()的形参描述,如下表所示:

FreeRTOS 队列操作实验
       程序设计:依然采用了之前的LED_GREEN,LED_RED,WS2812B,MPU6050例程。
设计预期达成效果:将红色led灯和绿色led灯按照一定频率闪烁,比如红色led灯按照1S频率闪烁,绿色led灯按照2S频率闪烁。在led灯闪烁任务中,发送消息队列,当红灯亮绿灯灭时:RGB亮红;当红灯亮绿灯亮时:RGB亮黄;当红灯灭绿灯灭时:RGB灭;当红灯灭绿灯亮时:RGB亮绿。
       代码解析:
      首先时消息队列的定义:消息队列采用长度为1,队列项大小为char类型。
#define QUEUE_LENGTH        1
#define QUEUE_ITEM_SIZE     sizeof(unsigned char)
QueueHandle_t xQueue;

       然后再start任务中创建队列:

xQueue = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE);

       分别在led灯任务中,实现消息队列发送逻辑:

#define WS2812B_RED_STATE_ON              0XA1
#define WS2812B_RED_STATE_OFF             0XA2
#define WS2812B_GREEN_STATE_ON            0XA3
#define WS2812B_GREEN_STATE_OFF           0XA4

void led_green_task(void *pvParameters)
{
    unsigned char led_green_on_flag = 0;
    unsigned char ws2812b_color_num = 0;
    
    while(1)
    {
        if(led_green_on_flag && mpu6050_init_success_flag == 1)
        {
             LED_GREEN_ON;
            
             ws2812b_color_num = WS2812B_GREEN_STATE_ON;
             xQueueSend(xQueue, &ws2812b_color_num, portMAX_DELAY);
            
        }else
        {
             ws2812b_color_num = WS2812B_GREEN_STATE_OFF;
             LED_GREEN_OFF;
             xQueueSend(xQueue, &ws2812b_color_num, portMAX_DELAY);
        }
        
        led_green_on_flag = ~led_green_on_flag;
        vTaskDelay(1000);
    }
}

void led_red_task(void *pvParameters)
{
    unsigned char led_red_on_flag = 0;
    unsigned char ws2812b_color_num = 0;
    
    while(1)
    {
        if(led_red_on_flag && mpu6050_init_success_flag == 1)
        {
             LED_RED_ON;
            
             ws2812b_color_num = WS2812B_RED_STATE_ON;
             xQueueSend(xQueue, &ws2812b_color_num, portMAX_DELAY);
        }else
        {
             ws2812b_color_num = WS2812B_RED_STATE_OFF;
             LED_RED_OFF;
             xQueueSend(xQueue, &ws2812b_color_num, portMAX_DELAY);
        }    
        
        led_red_on_flag = ~led_red_on_flag;
        vTaskDelay(2000);
    }
}  

       最后在ws2812b任务中,实现消息队列获取并执行亮灯逻辑:

void ws2812b_task(void *pvParameters)
{
    unsigned char queue_recv;
    unsigned char ws2812b_red_state = 0;
    unsigned char ws2812b_green_state = 0;
    
    while(1)
    {
        xQueueReceive(xQueue, &queue_recv, portMAX_DELAY);
        
        if(queue_recv == WS2812B_RED_STATE_ON)    ws2812b_red_state = 1;
        if(queue_recv == WS2812B_RED_STATE_OFF)   ws2812b_red_state = 0;
        if(queue_recv == WS2812B_GREEN_STATE_ON)  ws2812b_green_state = 1;
        if(queue_recv == WS2812B_GREEN_STATE_OFF) ws2812b_green_state = 0;
        
        
        if(ws2812b_red_state == 1 && ws2812b_green_state == 0)
        {
             ws2812b_write_rgb(255, 0, 0);
            
        }else if(ws2812b_red_state == 0 && ws2812b_green_state == 1)
        {
             ws2812b_write_rgb(0, 255, 0);
            
        }else if(ws2812b_red_state == 1 && ws2812b_green_state == 1)
        {
             ws2812b_write_rgb(255, 255, 0);
            
        }else if(ws2812b_red_state == 0 && ws2812b_green_state == 0)
        {
             ws2812b_write_rgb(0, 0, 0);
        }
        
        vTaskDelay(20);
    }
}

FreeRTOS 队列集简介

       在使用队列进行任务之间的“沟通交流”时,一个队列只允许任务间传递的消息为同一种 数据类型,如果需要在任务间传递不同数据类型的消息时,那么就可以使用队列集。FreeRTOS 提供的队列集功能可以对多个队列进行“监听”,只要被监听的队列中有一个队列有有效的消息, 那么队列集的读取任务都可以读取到消息,如果读取任务因读取队列集而被阻塞,那么队列集 将解除读取任务的阻塞。使用队列集的好处在于,队列集可以是的任务可以读取多个队列中的 消息,而无需遍历所有待读取的队列,以确定具体读取哪一个队列。 
        使用队列集功能,需要在 FreeRTOSConfig.h 文件中将配置项 configUSE_QUEUE_SETS 配 置为 1,来启用队列集功能。
        队列集的结构体为 QueueSetHandle_t
FreeRTOS 队列集相关 API 函数
FreeRTOS 中队列集相关的 API 函数入下表所示:

函数 xQueueCreateSet()

此函数用于创建队列集,该函数在 queue.c 文件中有定义,函数的原型如下所示:

函数 xQueueAddToSet()

       此函数用于往队列集中添加队列,要注意的时,队列在被添加到队列集之前,队列中不能 有有效的消息,该函数在 queue.c 文件中有定义,函数的原型如下所示:

函数 xQueueRemoveFromSet()

       此函数用于从队列集中移除队列,要注意的时,队列在从队列集移除之前,必须没有有效 的消息,该函数在 queue.c 文件中有定义,函数的原型如下所示:

函数 xQueueSelectFromSet()

       此函数用于在任务中获取队列集中有有效消息的队列,该函数在 queue.c 文件中有定义,函 数的原型如下所示:

FreeRTOS 队列集操作实验
       程序设计:依然采用了之前的LED_GREEN,LED_RED,WS2812B,MPU6050例程。
设计预期达成效果:将红色led灯和绿色led灯按照一定频率闪烁,比如红色led灯按照1S频率闪烁,绿色led灯按照2S频率闪烁。在led灯闪烁任务中,发送消息队列,当红灯亮绿灯灭时:RGB亮红;当红灯亮绿灯亮时:RGB亮黄;当红灯灭绿灯灭时:RGB灭;当红灯灭绿灯亮时:RGB亮绿。
       程序验证思路:在上一个例程的基础上,将队列集的方法应用以达到相同的效果。上一个例程中,所有的消息都存储在了一个队列中,由于筛选任务获取的频率较快,所以可以达到实时刷新的效果。这次,将led_green和led_red分别放在了两个队列中,来实现不同消息的不同队列传输。
代码解析:
首先是队列和队列集的定义:
#define QUEUE_GREEN_LENGTH        1
#define QUEUE_GREEN_ITEM_SIZE     sizeof(unsigned char)
QueueHandle_t xQueue_green;

#define QUEUE_RED_LENGTH          1
#define QUEUE_RED_ITEM_SIZE       sizeof(unsigned char)
QueueHandle_t xQueue_red;

#define QUEUESET_LENGTH           2
QueueHandle_t xQueueSet;

然后是在start任务中进行队列集的创建,队列的创建,以及队列加入到队列集操作。

xQueueSet    = xQueueCreateSet(QUEUESET_LENGTH);
xQueue_green = xQueueCreate(QUEUE_GREEN_LENGTH, QUEUE_GREEN_ITEM_SIZE);
xQueue_red   = xQueueCreate(QUEUE_RED_LENGTH, QUEUE_RED_ITEM_SIZE);
    
xQueueAddToSet(xQueue_green, xQueueSet);
xQueueAddToSet(xQueue_red, xQueueSet);

然后是在led_green和led_red任务中分别进行不同队列的消息传递。

#define WS2812B_RED_STATE_ON              0XA1
#define WS2812B_RED_STATE_OFF             0XA2
#define WS2812B_GREEN_STATE_ON            0XA3
#define WS2812B_GREEN_STATE_OFF           0XA4
  
void led_green_task(void *pvParameters)
{
    unsigned char led_green_on_flag = 0;
    unsigned char ws2812b_color_num = 0;
    
    while(1)
    {
        if(led_green_on_flag && mpu6050_init_success_flag == 1)
        {
             LED_GREEN_ON;
            
             ws2812b_color_num = WS2812B_GREEN_STATE_ON;
             xQueueSend(xQueue_green, &ws2812b_color_num, portMAX_DELAY);
            
        }else
        {
             ws2812b_color_num = WS2812B_GREEN_STATE_OFF;
             LED_GREEN_OFF;
             xQueueSend(xQueue_green, &ws2812b_color_num, portMAX_DELAY);
        }
        
        led_green_on_flag = ~led_green_on_flag;
        vTaskDelay(1000);
    }
}


void led_red_task(void *pvParameters)
{
    unsigned char led_red_on_flag = 0;
    unsigned char ws2812b_color_num = 0;
    
    while(1)
    {
        if(led_red_on_flag && mpu6050_init_success_flag == 1)
        {
             LED_RED_ON;
            
             ws2812b_color_num = WS2812B_RED_STATE_ON;
             xQueueSend(xQueue_red, &ws2812b_color_num, portMAX_DELAY);
        }else
        {
             ws2812b_color_num = WS2812B_RED_STATE_OFF;
             LED_RED_OFF;
             xQueueSend(xQueue_red, &ws2812b_color_num, portMAX_DELAY);
        }    
        
        led_red_on_flag = ~led_red_on_flag;
        vTaskDelay(2000);
    }
}

最后是在ws2812b任务中对队列集进行消息获取以及不同队列的消息识别执行亮灯逻辑。

void ws2812b_task(void *pvParameters)
{
    QueueSetMemberHandle_t activate_member = NULL;
    unsigned char queue_recv;
    unsigned char ws2812b_red_state = 0;
    unsigned char ws2812b_green_state = 0;
    
    while(1)
    {
        
        activate_member = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
        
        if(activate_member == xQueue_green)
        {
             xQueueReceive(activate_member, &queue_recv, portMAX_DELAY);
             
             if(queue_recv == WS2812B_GREEN_STATE_ON)  ws2812b_green_state = 1;
             if(queue_recv == WS2812B_GREEN_STATE_OFF) ws2812b_green_state = 0;
        }
        
        if(activate_member == xQueue_red)
        {
             xQueueReceive(activate_member, &queue_recv, portMAX_DELAY);
             
             if(queue_recv == WS2812B_RED_STATE_ON)  ws2812b_red_state = 1;
             if(queue_recv == WS2812B_RED_STATE_OFF) ws2812b_red_state = 0;
        }
        
        if(ws2812b_red_state == 1 && ws2812b_green_state == 0)
        {
             ws2812b_write_rgb(20, 0, 0);
            
        }else if(ws2812b_red_state == 0 && ws2812b_green_state == 1)
        {
             ws2812b_write_rgb(0, 20, 0);
            
        }else if(ws2812b_red_state == 1 && ws2812b_green_state == 1)
        {
             ws2812b_write_rgb(20, 20, 0);
            
        }else if(ws2812b_red_state == 0 && ws2812b_green_state == 0)
        {
             ws2812b_write_rgb(0, 0, 0);
        }
        
        vTaskDelay(20);
    }
}

下载验证:与上一个demo能够实现相同的效果,且逻辑更加清楚。NICE!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

moon2shine

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值