美文网首页物联网loT从业者
FreeRTOS学习笔记(3)——消息队列

FreeRTOS学习笔记(3)——消息队列

作者: Leung_ManWah | 来源:发表于2020-11-04 13:30 被阅读0次

    一、头文件

    #include "FreeRTOS.h"
    #include "queue.h"
    

    二、创建队列

    2.1 相关API说明

    2.1.1 xQueueCreate

    使用动态内存的方式创建一个新的队列。

    函数 QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize )
    参数 uxQueueLength: 队列能够存储的最大单元数目,即队列深度

    uxItemSize: 队列中数据单元的长度,以字节为单位
    返回值 如果创建成功则返回一个队列句柄,用于访问创建的队列。如果创建不成功则返回 NULL,可能原因是创建队列需要的 RAM 无法分配成功

    要想使用该函数必须在 FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1 来使能。

    2.1.2 vQueueDelete

    队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了。

    函数 void vQueueDelete( QueueHandle_t xQueue )
    参数 xQueue: 消息队列句柄,表示的是要删除哪个想队列
    返回值

    2.2 示例

    QueueHandle_t Test_Queue = NULL;
    
    #define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
    #define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) */
    
    taskENTER_CRITICAL(); //进入临界区
    
    /* 创建 Test_Queue */ 
    Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */ 
                              (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */ 
    if (NULL != Test_Queue) 
    {
        printf("创建 Test_Queue 消息队列成功!\r\n"); 
    }
    
    taskEXIT_CRITICAL(); //退出临界区
    

    三、消息发送与接收

    3.1 相关API说明

    3.1.1 xQueueSend

    用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,而不是以引用的形式。该函数绝对不能在中断服务程序里面被调用,中断中必须使用带有中断保护功能的 xQueueSendFromISR()来代替。

    函数 BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait )
    参数 xQueue: 目标队列的句柄。这个句柄即是调用 xQueueCreate() 创建该队列时的返回值

    pvItemToQueue: 发送数据的指针。其指向将要复制到目标队列中的数据单元。由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域

    xTicksToWait: 队列满时,等待队列空闲的最大超时时间。如果队列满并且 xTicksToWait 被设置成 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务挂起(没有超时)
    返回值 消息发送成功成功返回 pdTRUE,否则返回 errQUEUE_FULL

    3.1.2 xQueueSendFromISR

    该宏是 xQueueSend()的中断保护版本,用于在中断服务程序中向队列尾部发送一个队列消息,等价于 xQueueSendToBackFromISR()。

    函数 BaseType_t xQueueSendFromISR( QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken )
    参数 xQueue: 目标队列的句柄。这个句柄即是调用 xQueueCreate() 创建该队列时的返回值

    pvItemToQueue: 发送数据的指针。其指向将要复制到目标队列中的数据单元。由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域

    pxHigherPriorityTaskWoken: 如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将*pxHigherPriorityTaskWoken 设置成 pdTRUE,然后在中断退出前需要进行一次上下文切换 ,去执行被唤醒的优先级更高的任务。从 FreeRTOS V7.3.0 起,pxHigherPriorityTaskWoken 作为一个可选参数,可以设置为 NULL
    返回值 消息发送成功成功返回 pdTRUE,否则返回 errQUEUE_FULL

    3.1.3 xQueueSendToFront

    用于向队列队首发送一个消息。消息以拷贝的形式入队,而不是以引 用的形式。该函数绝不能在中断服务程序里面被调用,而是必须使用带有中断保护功能的 xQueueSendToFrontFromISR () 来代替。

    函数 BaseType_t xQueueSendToFront( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait )
    参数 xQueue: 目标队列的句柄。这个句柄即是调用 xQueueCreate() 创建该队列时的返回值

    pvItemToQueue: 发送数据的指针。其指向将要复制到目标队列中的数据单元。由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域

    xTicksToWait: 队列满时,等待队列空闲的最大超时时间。如果队列满并且 xTicksToWait 被设置成 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务挂起(没有超时)
    返回值 消息发送成功成功返回 pdTRUE,否则返回 errQUEUE_FULL

    3.1.4 xQueueReceive

    用于从一个队列中接收消息并把消息从队列中删除。接收的消息是以拷贝的形式进行的,所以我们必须提供一个足够大空间的缓冲区。具体能够拷贝多少数据到缓冲区,这个在队列创建的时候已经设定。该函数绝不能在中断服务程序里面被调用,而是必须使用带有中断保护功能的 xQueueReceiveFromISR () 来代替。

    函数 BaseType_t xQueueReceive( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait )
    参数 xQueue: 被读队列的句柄。这个句柄即是调用 xQueueCreate() 创建该队列时的返回值

    pvBuffer: 接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据。数据单元的长度在创建队列时就已经被设定,所以该指针指向的内存区域大小应当足够保存一个数据单元。

    xTicksToWait: 队列空时,阻塞超时的最大时间。如果该参数设置为 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(没有超时)
    返回值 队列项接收成功返回 pdTRUE,否则返回 pdFALSE

    3.1.5 xQueueReceiveFromISR

    xQueueReceiveFromISR() 是 xQueueReceive () 的中断版本,用于在中断服务程序中接收一个队列消息并把消息从队列中删除。

    函数 BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void *pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken )
    参数 xQueue: 被读队列的句柄。这个句柄即是调用 xQueueCreate() 创建该队列时的返回值

    pvBuffer: 接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据。数据单元的长度在创建队列时就已经被设定,所以该指针指向的内存区域大小应当足够保存一个数据单元。

    xTicksToWait: 任务在往队列投递信息时,如果队列满,则任务将阻塞在该队列上。如果 xQueueReceiveFromISR() 到账了一个任务解锁了则将 *pxHigherPriorityTaskWoken 设置为 pdTRUE,否则 *pxHigherPriorityTaskWoken 的值将不变。从 FreeRTOS V7.3.0 起,pxHigherPriorityTaskWoken 作为一个可选参数,可以设置为 NULL
    返回值 队列项接收成功返回 pdTRUE,否则返回 pdFALSE

    3.1.6 xQueuePeek

    xQueuePeek() 也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。xQueuePeek() 从队列首接收到数据后,不会修改队列中的数据,也不会改变数据在队列中的存储序顺。该函数绝不能在中断服务程序里面被调用,而是必须使用带有中断保护功能的 xQueuePeekFromISR () 来代替。

    函数 BaseType_t xQueuePeek( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait )
    参数 xQueue: 被读队列的句柄。这个句柄即是调用 xQueueCreate() 创建该队列时的返回值

    pvBuffer: 接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据。数据单元的长度在创建队列时就已经被设定,所以该指针指向的内存区域大小应当足够保存一个数据单元。

    xTicksToWait: 队列空时,阻塞超时的最大时间。如果该参数设置为 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(没有超时)
    返回值 队列项接收成功返回 pdTRUE,否则返回 pdFALSE

    3.2 示例

    3.2.1 阻塞式发送与接收

    /* FreeRTOS 头文件 */
    #include "FreeRTOS.h"
    #include "task.h"
    #include "queue.h"
    /* 开发板硬件 bsp 头文件 */
    #include "bsp_led.h"
    #include "bsp_usart.h"
    #include "bsp_key.h"
    /**************************** 任务句柄 ********************************/
    /*
    * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
    * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
    * 这个句柄可以为 NULL。
    */
    static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
    static TaskHandle_t Receive_Task_Handle = NULL;/* LED 任务句柄 */
    static TaskHandle_t Send_Task_Handle = NULL;/* KEY 任务句柄 */
     
    /***************************** 内核对象句柄 *****************************/
    /*
    * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
    * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
    * 们就可以通过这个句柄操作这些内核对象。
    *
    * 
    内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
    * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
    * 来完成的
    *
    */
    QueueHandle_t Test_Queue = NULL;
    
    /*************************** 宏定义 ************************************/
    /*
    * 当我们在写应用程序的时候,可能需要用到一些宏定义。
    */
    #define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */ 
    #define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) */
    
     /*
    *************************************************************************
    * 函数声明
    *************************************************************************
    */
    static void AppTaskCreate(void);/* 用于创建任务 */
    
    static void Receive_Task(void* pvParameters);/* Receive_Task 任务实现 */
    static void Send_Task(void* pvParameters);/* Send_Task 任务实现 */
    
    static void BSP_Init(void);/* 用于初始化板载相关资源 */
    
    /*****************************************************************
    * @brief 主函数
    * @param 无
    * @retval 无
    * @note 第一步:开发板硬件初始化
            第二步:创建 APP 应用任务
            第三步:启动 FreeRTOS,开始多任务调度
    ****************************************************************/
    int main(void)
    {
        BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
    
        /* 开发板硬件初始化 */
        BSP_Init();
    
        /* 创建 AppTaskCreate 任务 */
        xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
                                (const char* )"AppTaskCreate",/* 任务名字 */
                                (uint16_t )512, /* 任务栈大小 */
                                (void* )NULL,/* 任务入口函数参数 */
                                (UBaseType_t )1, /* 任务的优先级 */
                                (TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指*/
        /* 启动任务调度 */
        if (pdPASS == xReturn)
        {
            vTaskStartScheduler(); /* 启动任务,开启调度 */
        }
        else
        {
            return -1;
        }
    
        while (1); /* 正常不会执行到这里 */
    }
    
    /***********************************************************************
    * @ 函数名 : AppTaskCreate
    * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
    * @ 参数 : 无
    * @ 返回值 : 无
    ********************************************************************/
    static void AppTaskCreate(void)
    {
        BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
     
        taskENTER_CRITICAL(); //进入临界区
    
        /* 创建 Test_Queue */ 
        Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */ 
                                (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */ 
        if (NULL != Test_Queue) 
        {
            printf("创建 Test_Queue 消息队列成功!\r\n"); 
        }
        /* 创建 Receive_Task 任务 */
        xReturn = xTaskCreate((TaskFunction_t )Receive_Task,/* 任务入口函数 */
                                (const char* )"Receive_Task",/* 任务名字 */
                                (uint16_t )512, /* 任务栈大小 */
                                (void* )NULL, /* 任务入口函数参数 */
                                (UBaseType_t )2, /* 任务的优先级 */
                                (TaskHandle_t* )&Receive_Task_Handle);/*任务控制块指针*/
        if (pdPASS == xReturn)
        {
            printf("创建 Receive_Task 任务成功!\r\n");
        }
        /* 创建 Send_Task 任务 */
        xReturn = xTaskCreate((TaskFunction_t )Send_Task, /* 任务入口函数 */
                                (const char* )"Send_Task",/* 任务名字 */
                                (uint16_t )512, /* 任务栈大小 */
                                (void* )NULL,/* 任务入口函数参数 */
                                (UBaseType_t )3, /* 任务的优先级 */
                                (TaskHandle_t* )&Send_Task_Handle);/*任务控制块指针 */
        if (pdPASS == xReturn)
        {
            printf("创建 Send_Task 任务成功!\n\n");
        }
        vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
    
        taskEXIT_CRITICAL(); //退出临界区
    }
    
    /**********************************************************************
    * @ 函数名 : Receive_Task
    * @ 功能说明: Receive_Task 任务主体
    * @ 参数 :
    * @ 返回值 : 无
    ********************************************************************/
    static void Receive_Task(void* parameter) 
    { 
        BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdTRUE */ 
        uint32_t r_queue; /* 定义一个接收消息的变量 */ 
        while (1) 
        { 
            xReturn = xQueueReceive( Test_Queue, /* 消息队列的句柄 */ 
                                      &r_queue, /* 发送的消息内容 */ 
                                      portMAX_DELAY); /* 等待时间 一直等 */ 
            if (pdTRUE == xReturn) 
            {
                printf("本次接收到的数据是%d\n\n",r_queue); 
            }
            else 
            {
                printf("数据接收出错,错误代码: 0x%lx\n",xReturn); 
            }
        } 
    } 
    
    /**********************************************************************
    * @ 函数名 : Send_Task
    * @ 功能说明: Send_Task 任务主体
    * @ 参数 :
    * @ 返回值 : 无
    ********************************************************************/
    static void Send_Task(void* parameter) 
    { 
        BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */ 
        uint32_t send_data1 = 1; 
        uint32_t send_data2 = 2; 
        while (1) 
        { 
            if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) 
            { 
                /* KEY1 被按下 */ 
                printf("发送消息 send_data1!\n"); 
                xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */ 
                                      &send_data1,/* 发送的消息内容 */ 
                                      0 ); /* 等待时间 0 */ 
                if (pdPASS == xReturn) 
                {
                    printf("消息 send_data1 发送成功!\n\n"); 
                }
            } 
            if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) 
            { 
                /* KEY2 被按下 */ 
                printf("发送消息 send_data2!\n"); 
                xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */ 
                                      &send_data2,/* 发送的消息内容 */ 
                                      0 ); /* 等待时间 0 */ 
                if (pdPASS == xReturn) 
                {
                    printf("消息 send_data2 发送成功!\n\n"); 
                } 
                vTaskDelay(20);/* 延时 20 个 tick */ 
            } 
        } 
    }
    
    /***********************************************************************
    * @ 函数名 : BSP_Init
    * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
    * @ 参数 :
    * @ 返回值 : 无
    *********************************************************************/
    static void BSP_Init(void)
    {
        /*
        * STM32 中断优先级分组为 4,即 4bit 都用来表示抢占优先级,范围为:0~15
        * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
        * 都统一用这个优先级分组,千万不要再分组,切忌。
        */
        NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
     
        /* LED 初始化 */
        LED_GPIO_Config();
    
        /* 串口初始化 */
        USART_Config();
    
        /* 按键初始化 */
        Key_GPIO_Config();
    }
    

    3.2.2 非阻塞式发送与接收

    void vBufferISR( void )
    {
        char cIn;
        BaseType_t xHigherPriorityTaskWoken; 
     
        /* 在 ISR 开始的时候,我们并没有唤醒任务 */
        xHigherPriorityTaskWoken = pdFALSE; 
     
        /* 直到缓冲区为空 */
        do 
        {
            /* 从缓冲区获取一个字节的数据 */
            cIn = portINPUT_BYTE( RX_REGISTER_ADDRESS );
    
            /* 发送这个数据 */ 
            xQueueSendFromISR( xRxQueue, &cIn, &xHigherPriorityTaskWoken ); 
    
        } while ( portINPUT_BYTE( BUFFER_COUNT ) );
    
        /* 这时候 buffer 已经为空,如果需要则进行上下文切换 */ 
        if ( xHigherPriorityTaskWoken ) 
        { 
            /* 上下文切换,这是一个宏,不同的处理器,具体的方法不一样 */ 
            portYIELD_FROM_ISR (); 
        } 
    }
    
    QueueHandle_t xQueue;
    
    /* 创建一个队列,并往队列里面发送一些数据 */
    void vAFunction( void *pvParameters )
    {
        char cValueToPost;
        const TickType_t xTicksToWait = ( TickType_t )0xff;
    
        /* 创建一个可以容纳 10 个字符的队列 */
        xQueue = xQueueCreate( 10, sizeof( char ) );
        if ( xQueue == 0 ) 
        {
            /* 队列创建失败 */
        }
     
        /* ... 任务其他代码 */
     
        /* 往队列里面发送两个字符
        如果队列满了则等待 xTicksToWait 个系统节拍周期*/
        cValueToPost = 'a';
        xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
        cValueToPost = 'b';
        xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
    
        /* 继续往队列里面发送字符
        当队列满的时候该任务将被阻塞*/
        cValueToPost = 'c';
        xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );
    }
    
    /* 中断服务程序:输出所有从队列中接收到的字符 */
    void vISR_Routine( void )
    {
        BaseType_t xTaskWokenByReceive = pdFALSE;
        char cRxedChar;
    
        while ( xQueueReceiveFromISR( xQueue, ( void * ) &cRxedChar, &xTaskWokenByReceive) ) 
        { 
    
            /* 接收到一个字符,然后输出这个字符 */
            vOutputCharacter( cRxedChar );
    
            /* 如果从队列移除一个字符串后唤醒了向此队列投递字符的任务,
            那么参数 xTaskWokenByReceive 将会设置成 pdTRUE,这个循环无论重复多少次,
            仅会有一个任务被唤醒 */
        }
    
        if ( xTaskWokenByReceive != pdFALSE ) 
        { 
            /* 我们应该进行一次上下文切换,当 ISR 返回的时候则执行另外一个任务 */
            /* 这是一个上下文切换的宏,不同的处理器,具体处理的方式不一样 */ 
            taskYIELD (); 
        }
    }
    

    四、查询消息个数

    4.1 相关API说明

    4.1.1 uxQueueMessagesWaiting

    用于查询队列中当前有效数据单元个数。切记不要在中断服务例程中调用 uxQueueMessagesWaiting()。应当在中断服务中
    使用其中断安全版本 uxQueueMessagesWaitingFromISR()。

    函数 UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue )
    参数 xQueue: 目标队列的句柄。这个句柄即是调用 xQueueCreate() 创建该队列时的返回值
    返回值 当前队列中保存的数据单元个数。返回 0 表明队列为空

    • 由 Leung 写于 2020 年 11 月 4 日

    • 参考:野火FreeRTOS视频与PDF教程

    相关文章

      网友评论

        本文标题:FreeRTOS学习笔记(3)——消息队列

        本文链接:https://www.haomeiwen.com/subject/gjzrvktx.html