FreeRTOS——消息队列

引入:

在裸机中,若写入以下程序,若函数A一直没有将flag置1,则函数B需要一直判断,非常占用CPU资源

复制代码
void Fun_A()
{
    if(条件成立)
        flag = 1;
}

void Fun_B()
{
    if(flag = 1)
        进行操作;
}


int flag = 0;

int main()
{
    while(1)
    {
        Fun_A();
        Fun_B();
    }
}

若在实时操作系统中,使用消息队列传递信息,当消息队列非空,任务B读取数据;否则,任务阻塞,直到队列中有数据,任务B才可以运行,极大提高CPU的利用率

复制代码
void task_A()
{
    while(1)
    {
        if(条件成立)
            写队列;
    }

}

void task_B()
{
    while(1)
    {
        读队列;
        进行操作;
    }

}



int main()
{
    创建消息队列;
    创建task_A;
    创建task_B;
    开启任务调度器;
}

一、队列简介

队列:任务与任务、任务与中断之间进行数据交流的一种机制(消息传递)

FreeRTOS基于队列, 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、

二值信号量、 递归互斥信号量

队列的核心:关中断(实现互斥)、链表(实现休眠和唤醒)、环形缓冲区(保存数据)

Q:队列作用与全局变量作用的区别?

答:

功能类似于全局变量,但又比全局变量更安全

假设全局变量a= 0,现有两个任务都在操作全局变量a,正常情况下,a = 2,然而,在操作系统中:

对于简单的变量自增,其中也包含许多步骤。当a的值读到CPU中的 r0 寄存器中,正在修改数据时,此时任务2处于就绪态(任务2的优先级 > 任务1的优先级),就会打断任务1,任务对变量a操作完成后,此时a=1。当任务2执行完成后,任务1继续执行,a = 0,恢复出来后,进行修改,则a = 1,但实际值a应该等于2!

全局变量的弊端**:数据无保护,导致数据不安全,当多个任务同时**对同一变量操作时,数据易受损

对于队列而言,在写队列读队列 中都会进行关中断、开中断的操作 ,可以防止多任务同时访问冲突

在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做"队列项目 ",队列能够存储"队列项目"的最大数量称为队列的长度, 在创建队列时,队列长度以及队列项目的大小由编程者指定

FreeRTOS队列的特点:

  1. 数据入队出队方式:队列通常采用"先进先出"(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为"后进先出"LIFO方式;

  2. 数据传递方式:FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递 , FreeRTOS 采用拷贝数据传递,也可以传递指针**,** 所以传递较大的数据的时候采用指针传递(1)值拷贝的方式入队

    复制代码
    uint8_t value = 0;
    xQueueSend(Queue1,&value,portMAX_DELAY);

    (2)传递指针的方式入队

    复制代码
    uint8_t value = 0;
    uint8_t* p = &value;
    xQueueSend(Queue1,&p,portMAX_DELAY);
  3. 多任务访问:队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息

  4. 出队、入队阻塞:当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队或者队列已空无法出队

① 若阻塞时间为 0 : 直接返回 不会等待;
② 若阻塞时间为 0~port_MAX_DELAY : 等待设定的阻塞时间 ,若在该时间内还无法入队,超时后直接返回不再等待;
③ 若阻塞时间为 port_MAX_DELAY :死等, 一直等到可以入队或出队为止

出/入队阻塞:

当多个任务写入消息给一个"满队列"时或者在一个空队列中接收数据,这些任务都会进入阻塞状态 ,当队列有空间或者有队列项目时:

1、优先级最高的任务

2、如果大家的优先级相同,那等待时间最久的任务会进入就绪态

二、队列结构体

复制代码
typedef struct QueueDefinition 
{
    int8_t * pcHead;					    /* 存储区域的起始地址 */
    int8_t * pcWriteTo;        				/* 下一个写入的位置 */
    union
    {
        QueuePointers_t     xQueue; 
	    SemaphoreData_t  xSemaphore; 
    } u ;
    List_t xTasksWaitingToSend; 			    /* 等待发送列表 */
                                     /*队列满,而入队失败进入阻塞状态的任务,则挂在此列表上 */

    List_t xTasksWaitingToReceive;			    /* 等待接收列表 */
                                     /*队列空,而出队失败进入阻塞状态的任务,则挂在此列表上 */

    volatile UBaseType_t uxMessagesWaiting; 	/* 非空闲队列项目的数量 */
    UBaseType_t uxLength;			            /* 队列长度 */
    UBaseType_t uxItemSize;                 	/* 队列项目的大小 */
    volatile int8_t cRxLock; 		 /* 读取上锁计数器 ,queueUNLOCKED:未上锁*/
                                     /*上锁后,统计出队的队列项数量 */

    volatile int8_t cTxLock;	     /* 写入上锁计数器 ,queueUNLOCKED:未上锁*/
                                     /*上锁后,统计入队的队列项数量 */
   /* 其他的一些条件编译 */
} xQUEUE;

队列锁:当上锁后,无法操作队列 等待发送列表等待接收列表,但可以正常进行对队列的读写操作,默认初始化,不上锁

共用体:

队列的基本操作示意图:

三、队列相关API函数

使用队列的主要流程:创建队列 ------> 写队列------> 读队列

3.1 动态创建队列:xQueueCreate()

头文件:queue.h

使用条件:configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中被设置为 1,或保留未定义状态(此时,它默认为 1)

函数原型:

复制代码
 QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
                             UBaseType_t uxItemSize );

参数说明:

|---------------|--------------------------|
| 参数 | 描述 |
| uxQueueLength | 队列可同时容纳的最大项目数 |
| uxItemSize | 存储队列中的每个数据项所需的大小(以字节为单位) |
| 返回值 | 成功:返回所创建的队列句柄 失败:NULL |

用法示例:

复制代码
struct AMessage
{
    char ucMessageID;
    char ucData[ 20 ];
};

void vATask( void *pvParameters )
{
    QueueHandle_t xQueue1, xQueue2;

    /* Create a queue capable of containing 10 unsigned long values. */
    xQueue1 = xQueueCreate( 10, sizeof( unsigned long ) );

    if( xQueue1 == NULL )
    {
        /* Queue was not created and must not be used. */
    }

    /* Create a queue capable of containing 10 pointers to AMessage
    structures.  These are to be queued by pointers as they are
    relatively large structures. */
    xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );

    if( xQueue2 == NULL )
    {
        /* Queue was not created and must not be used. */
    }

    /* ... Rest of task code. */
 }

注:

复制代码
#define xQueueCreate (  uxQueueLength,   uxItemSize  )   						 					
	   xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE )) 

动态创建队列函数实际是通过一个宏实现的,被定义的函数有三个参数,最后一个参数指定了参数的不同类型

动态创建队列函数详解:

复制代码
    QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
                                       const UBaseType_t uxItemSize,
                                       const uint8_t ucQueueType )
    {
        Queue_t * pxNewQueue = NULL;
        size_t xQueueSizeInBytes;
        uint8_t * pucQueueStorage;

        /* 判断队列长度是否大于0,以及是否溢出
        if( ( uxQueueLength > ( UBaseType_t ) 0 ) &&
            ( ( SIZE_MAX / uxQueueLength ) >= uxItemSize ) &&
            /* Check for addition overflow. */
            ( ( SIZE_MAX - sizeof( Queue_t ) ) >= ( uxQueueLength * uxItemSize ) ) )
        {
            
            /* 计算队列项的总大小 */
            xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); 

            /* 申请队列结构体以及环形缓冲区的内存空间 */         
            pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );

            /* 申请成功 */
            if( pxNewQueue != NULL )
            {
                
                pucQueueStorage = ( uint8_t * ) pxNewQueue;  //队列结构体的首地址
                pucQueueStorage += sizeof( Queue_t );        //队列环形缓冲区的首地址

                #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
                {
                   
                    pxNewQueue->ucStaticallyAllocated = pdFALSE;
                }
                #endif /* configSUPPORT_STATIC_ALLOCATION */

                /* 初始化队列 */
                prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
            }
            else
            {
                traceQUEUE_CREATE_FAILED( ucQueueType );
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            configASSERT( pxNewQueue );
            mtCOVERAGE_TEST_MARKER();
        }

        return pxNewQueue;
    }

动态创建队列流程图:

各个成员的初值如图所示:

3.2 写入消息(入队)

|----------------------------|----------------------------|
| 函数 | 描述 |
| xQueueSend() | 往队列的尾部写入消息 |
| xQueueSendToBack() | 同 xQueueSend() |
| xQueueSendToFront() | 往队列的头部写入消息 |
| xQueueOverwrite() | 覆写队列消息(只用于队列长度为 1 的情况) |
| xQueueSendFromISR() | 在中断中往队列的尾部写入消息 |
| xQueueSendToBackFromISR() | 同 xQueueSendFromISR() |
| xQueueSendToFrontFromISR() | 在中断中往队列的头部写入消息 |
| xQueueOverwriteFromISR() | 在中断中覆写队列消息(只用于队列长度为 1 的情况) |

可以看出:写入函数调用的是同一个函数xQueueGenericSend( ),只是指定了不同的写入位置以及阻塞时间

队列写入位置一共有三种

注意:覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用,不论队列中是否有数据,都可以写进去,并且不会阻塞

总结:

写入队列尾部:从 pcWriteTo 位置开始写入,之后地址++,即向上增长,如果超过既定区域,返回头尾部(pcWriteTo指向初始位置,即pcHead指向的位置)

写入队列头部:从 pcReadFrom 位置开始写入,之后地址--,即向下增长,如果超过既定区域,返回头部 (pcReadFrom指向初始位置,即pcTail - 一个队列项的大小)

写入队列流程图:

3.3 读取消息(出队)

|------------------------|--------------------------|
| 函数 | 描述 |
| xQueueReceive() | 从队列头部读取 消息,并删除消息 |
| xQueuePeek() | 从队列头部读取 消息 |
| xQueueReceiveFromISR() | 在中断中从队列头部读取消息,并删除消息 |
| xQueuePeekFromISR() | 在中断中从队列头部读取消息 |

(1)xQueueReceive()

复制代码
BaseType_t    xQueueReceive( QueueHandle_t   xQueue,  void *   const pvBuffer,  TickType_t   xTicksToWait )

此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。

(2)xQueuePeek()

复制代码
BaseType_t   xQueuePeek( QueueHandle_t   xQueue,   void * const   pvBuffer,   TickType_t   xTicksToWait )

此函数用于在任务中,从队列中读取消息, 但与函数 xQueueReceive()不同,此函数在成功读取消息后,并不会移除已读取的消息

读消息

将pcReadFrom指向位置的数据拷贝出来

如果读取的位置等于尾部,则将头部位置赋给读取的位置,进行读取消息

读队列流程图:

相关推荐
智商偏低5 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen6 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森8 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白8 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D9 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术12 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt12 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘13 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang13 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n15 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件