FreeRTOS study notes (3)-message queue

One, the header file

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

Two, create a queue

2.1 Related API description

2.1.1 xQueueCreate

Create a new queue using dynamic memory.

function QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize )
parameter uxQueueLength: The maximum number of units that the queue can store, that is, the depth of the queue.

uxItemSize: The length of the data unit in the queue, in bytes
return value If the creation is successful, a queue handle is returned for accessing the created queue. If the creation is unsuccessful, NULL is returned. The possible reason is that the RAM needed to create the queue cannot be allocated successfully.

To use this function must be FreeRTOSConfig.hin the configSUPPORT_DYNAMIC_ALLOCATIONdefined to be enabled.

2.1.2 vQueueDelete

The queue deletion function is directly deleted according to the message queue handle. After deletion, all the information in the message queue will be emptied by the system, and the message queue cannot be used again.

function void vQueueDelete( QueueHandle_t xQueue )
parameter xQueue: The message queue handle, which indicates which queue to delete
return value no

2.2 Example

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(); //退出临界区

Three, message sending and receiving

3.1 Related API description

3.1.1 xQueueSend

Used to send a queue message to the tail of the queue. Messages are enqueued in the form of copies, not in the form of references. This function must not be called in the interrupt service routine. In the interrupt, the xQueueSendFromISR() with interrupt protection function must be used instead.

function BaseType_t xQueueSend (QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait)
parameter xQueue: The handle of the target queue. This handle is the return value of calling xQueueCreate() to create the queue

pvItemToQueue: the pointer to send data. It points to the data unit to be copied to the target queue. Since the length of the data unit in the queue is set when the queue is created, the data of the corresponding length will be copied from the space pointed to by the pointer to the storage area of ​​the queue

xTicksToWait: When the queue is full, the maximum timeout time for waiting for the queue to be idle. If the queue is full and xTicksToWait is set to 0, the function returns immediately. The unit of the timeout time is the system tick period. The constant portTICK_PERIOD_MS is used to assist in calculating the real time, and the unit is ms. If INCLUDE_vTaskSuspend is set to 1, and the specified delay is portMAX_DELAY, the task will be suspended (no timeout)
return value If the message is sent successfully, it returns pdTRUE, otherwise it returns errQUEUE_FULL

3.1.2 xQueueSendFromISR

This macro is an interrupt protected version of xQueueSend(), used to send a queue message to the tail of the queue in the interrupt service routine, which is equivalent to xQueueSendToBackFromISR().

function BaseType_t xQueueSendFromISR (QueueHandle_t xQueue, const void * pvItemToQueue, BaseType_t * pxHigherPriorityTaskWoken)
parameter xQueue: The handle of the target queue. This handle is the return value of calling xQueueCreate() to create the queue

pvItemToQueue: the pointer to send data. It points to the data unit to be copied to the target queue. Since the length of the data unit in the queue is set when the queue is created, the data of the corresponding length will be copied from the space pointed to by the pointer to the storage area of ​​the queue

pxHigherPriorityTaskWoken: If enqueuing causes a task to be unlocked, and the priority of the unlocked task is higher than For the currently interrupted task, set *pxHigherPriorityTaskWoken to pdTRUE, and then need to perform a context switch before the interrupt exits to execute the higher priority task that is awakened. Starting from FreeRTOS V7.3.0, pxHigherPriorityTaskWoken is an optional parameter that can be set to NULL
return value If the message is sent successfully, it returns pdTRUE, otherwise it returns errQUEUE_FULL

3.1.3 xQueueSendToFront

Used to send a message to the head of the queue. Messages are enqueued in the form of copies, not in the form of references. This function must not be called in the interrupt service routine, but must use xQueueSendToFrontFromISR () with interrupt protection function instead.

function BaseType_t xQueueSendToFront (QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait)
parameter 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教程

Guess you like

Origin blog.csdn.net/qq_36347513/article/details/109488543