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指向位置的数据拷贝出来

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

读队列流程图:

相关推荐
qq_459730032 小时前
4-3 MCU中ARM存储器的作用
arm开发·单片机·嵌入式硬件
嵌入式科普5 小时前
嵌入式科普(24)从SPI和CAN通信重新理解“全双工”
c语言·stm32·can·spi·全双工·ra6m5
重生之我是数学王子5 小时前
点亮核心板小灯 STM32U575
stm32·单片机·嵌入式硬件
end_SJ5 小时前
初学stm32 --- 定时器中断
stm32·单片机·嵌入式硬件
南城花随雪。5 小时前
单片机:实现数码管动态显示(0~99999999)74hc138驱动(附带源码)
单片机·嵌入式硬件
南城花随雪。8 小时前
单片机:实现信号发生器(附带源码)
单片机·嵌入式硬件
灵槐梦9 小时前
【速成51单片机】2.点亮LED
c语言·开发语言·经验分享·笔记·单片机·51单片机
三月七(爱看动漫的程序员)10 小时前
HiQA: A Hierarchical Contextual Augmentation RAG for Multi-Documents QA---附录
人工智能·单片机·嵌入式硬件·物联网·机器学习·语言模型·自然语言处理
大风起兮1211 小时前
STM32HAL库中RTC闹钟设置时分秒,年月日
stm32·嵌入式硬件
超能力MAX12 小时前
IIC驱动EEPROM
单片机·嵌入式硬件·fpga开发