【FreeRTOS 教程 四】队列创建与发布项目到队列

发布于:2025-02-10 ⋅ 阅读:(135) ⋅ 点赞:(0)

目录

一、FreeRTOS队列:

(1)队列介绍:

(2)用户模型说明:

(3)阻塞队列:

二、队列管理 API:

(1)uxQueueMessagesWaiting:

(2) uxQueueMessagesWaitingFromISR:

(3)uxQueueSpacesAvailable:

(4)vQueueDelete:

(5)xQueueReset:

(6)xQueueIsQueueEmptyFromISR:

(7)xQueueIsQueueFullFromISR:

三、队列的创建及使用:

(1)动态创建队列:

(2)静态创建队列:

(3)向队列中发布项目:

(4)项目发布到队列尾部:

(5)队列尾部入队数据项:

(6)项目发布到队列尾部:

(7)队列头部入队数据项:

(8)项目发布到队列头部:

四、队列示例程序:

(1)动态创建队列:

(2)静态创建队列:

​五、队列创建时的常见问题:

(1)函数未定义:

​(2)动态堆空间太大导致静态分配空间不足:

(3)队列创建后程序下载没反应:

六、FreeRTOS教程示例代码下载:


一、FreeRTOS队列:

(1)队列介绍:

队列是任务间通信的主要形式。它们可以用于在任务之间以及中断和任务之间发送消息。在大多数情况下,队列用作线程安全的 FIFO(先进先出)缓冲区, 新数据被发送到队列的后面,但也可以发送到前面。

向队列中写入和从队列中读取。此示例中创建队列来保存 5 个项目,并且队列永远不会满。

(2)用户模型说明:

FreeRTOS 队列使用模型既简单又灵活, 这两者通常是不可兼得的。消息通过队列以副本的方式发送, 这意味着数据(可以是更大的缓冲区的指针)本身被复制到队列中, 而不是队列始终只存储对数据的引用。这是最好的方法,因为:

  • 已经包含在 C 语言变量(整数、 小结构体等)中的小消息可以直接送入队列。没有 必要为消息分配一个缓冲区, 然后将变量复制到分配的缓冲区中。同样,可以直接从队列中将消息读取到 C 变量中 。
  • 此外,以这种方式向队列发送消息, 允许发送任务立即覆盖发送到队列的变量或缓冲区, 即使发送的消息仍在队列中。
  • 由于变量中包含的数据已复制到队列中, 变量本身可以重复使用。不要求发送消息的任务 和接收消息的任务约定哪个任务拥有该消息, 以及哪个任务负责在不需要该消息时 将其清空。
  • 使用通过复制传递数据的队列不会导致无法将队列 用于通过引用传递数据。当消息的大小达到一定程度, 将整条消息逐字节复制到队列中是不现实的, 此时可将消息定义为保存若干指针并复制消息的 一个指针至队列。
  • 内核独自负责分配用于队列存储区的内存 。
  • 可变大小的消息可以通过定义队列来保存结构体, 其中包含一个指向队列消息的成员, 以及另一个保存队列消息大小的成员。
  • 单个队列可用于接收不同的消息类型, 以及来自多个地点的消息, 方法是将队列定义为保存一个结构体,该结构的一个成员持有消息类型, 另一个成员保存消息数据(或消息数据的一个指针)。如何解释数据 取决于消息类型。
  • 正是使用这种方式,管理 FreeRTOS-Plus-TCP IP 堆栈的任务才能使用一个队列来接收 ARP 定时器事件、 从以太网硬件接收的数据包、 从应用程序接收的数据包、网络故障事件等的通知。
  • 该实现适用于在内存保护环境中使用 。一个被限制在受保护的内存区域的任务可以将数据传递给一个被限制在不同的受保护内存区域的任务, 因为通过调用队列发送函数 来调用 RTOS 将提高微控制器的权限等级 。队列存储区 仅可由 RTOS 访问(具有完整权限)。
  • 提供一个单独的 API 用于中断内部。将 RTOS 任务中使用的 API 与中断服务程序中使用的 API 分开, 意味着 RTOS API 函数的实现 不承担每次执行时检查其调用上下文的开销。 使用单独的中断 API 也意味着,在大多数情况下,创建 RTOS 感知的中断服务程序对终端用户而言更简单—— 与其他 RTOS 产品相比。
  • 从任何意义上来说,API 都更加简单。

(3)阻塞队列:

  • 队列 API 函数允许指定阻塞时间。
  • 当一个任务试图从一个空队列中读取时,该队列将进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行) 直到队列中的数据变得可用,或者阻塞时间过期。
  • 当一个任务试图写入到一个满队列时,该队列将进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行) 直到队列中出现可用空间,或者阻塞时间过期。
  • 如果同一个队列上有多个处于阻塞状态的任务, 那么具有最高优先级的任务将最先解除阻塞。
  • 请注意,中断只能使用以 "FromISR" 结尾的 API 函数。

二、队列管理 API:

(1)uxQueueMessagesWaiting

  • 作用:返回队列中存储的消息数。

函数原型:

UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
参数/返回 描述
xQueue 正在查询的队列的句柄。
返回值 队列中可用的消息数。

(2) uxQueueMessagesWaitingFromISR

  • 作用:uxQueueMessagesWaiting()的一个版本,可以从 ISR 中调用。返回队列中存储的消息数。

函数原型:

UBaseType_t uxQueueMessagesWaitingFromISR( QueueHandle_t xQueue );
参数/返回 描述
xQueue 正在查询的队列的句柄。
返回值 队列中可用的消息数。

(3)uxQueueSpacesAvailable

  • 作用:返回队列中的可用空间数。

函数原型:

UBaseType_t uxQueueSpacesAvailable( QueueHandle_t xQueue );
参数/返回值 描述
xQueue 正在查询的队列的句柄。
返回值 队列中可用的可用空间数。

(4)vQueueDelete

  • 作用:删除队列 — 释放分配用于存储放置在队列中的项目的所有内存。

函数原型:

void vQueueDelete( QueueHandle_t xQueue );
参数 描述
xQueue 要删除的队列的句柄。

(5)xQueueReset

  • 作用:将队列重置为其原始的空状态。

函数原型:

BaseType_t xQueueReset( QueueHandle_t xQueue );
参数/返回值 描述
xQueue 正在重置的队列的句柄。
返回值 因为 FreeRTOS V7.2.0 中 xQueueReset() 总是返回 pdPASS

(6)xQueueIsQueueEmptyFromISR

  • 作用:查询队列以确定队列是否为空。此函数只能用于 ISR。 

函数原型:

BaseType_t xQueueIsQueueEmptyFromISR( const QueueHandle_t pxQueue );
参数/返回值 描述
xQueue 正在查询的队列的句柄。
返回值 如果队列不为空,则返回 pdFALSE;如果队列为空,则返回 pdTRUE

(7)xQueueIsQueueFullFromISR

  • 作用:查询队列以确定队列是否已满。此函数只能用于 ISR。

函数原型:

BaseType_t xQueueIsQueueFullFromISR( const QueueHandle_t pxQueue );
参数/返回 描述
xQueue 正在查询的队列的句柄。
返回值 如果队列未满,则返回 pdFALSE;如果队列已满,则返回 pdTRUE

三、队列的创建及使用:

(1)动态创建队列:

函数原型:

 QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
                             UBaseType_t uxItemSize );
  • 该函数可以创建新队列并返回一个可以引用该队列的句柄。configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,或保留为未定义状态(默认为 1), 才可使用此RTOS API 函数。
  • 每个队列都需要 RAM 来保存队列状态 以及队列中包含的项目(队列存储区)。 如果使用 
  • xQueueCreate()创建队列,则所需的 RAM 会自动 从 FreeRTOS 堆中分配。 如果使用xQueueCreateStatic() 创建队列, 则 RAM 由应用程序编写者提供,这会产生更多的参数, 但这样能够在编译时静态分配 RAM 。
参数/返回值 描述
uxQueueLength 队列一次可存储的最大项目数。
uxItemSize 存储队列中每个项目所需的大小(以字节为单位)。队列中的每个项目必须具有相同的大小。
返回值 如果队列创建成功,则返回所创建队列的句柄。如果创建队列所需的内存无法分配,则返回 NULL。

用法示例:

struct AMessage
{
    char ucMessageID;        // 消息ID,使用1个字节的字符类型存储
    char ucData[20];         // 数据字段,使用20个字节的字符数组存储
};

void vATask( void *pvParameters )
{
    QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量

    /* 创建一个能够存储10个无符号长整型(unsigned long)的队列 */
    xQueue1 = xQueueCreate( 10, sizeof( unsigned long ) );

    if( xQueue1 == NULL ) // 检查队列是否创建成功
    {
        /* 如果队列创建失败(内存分配失败),则不能使用这个队列 */
    }

    /* 创建一个能够存储10个指向AMessage结构体的指针的队列
       这些结构体通过指针入队,因为它们相对较大 */
    xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );

    if( xQueue2 == NULL ) // 检查队列是否创建成功
    {
        /* 如果队列创建失败(内存分配失败),则不能使用这个队列 */
    }

    /* ... 任务代码的其余部分 */
}

(2)静态创建队列:

函数原型:

 QueueHandle_t xQueueCreateStatic(
                             UBaseType_t uxQueueLength,
                             UBaseType_t uxItemSize,
                             uint8_t *pucQueueStorageBuffer,
                             StaticQueue_t *pxQueueBuffer );
  • 创建新队列并返回一个可以引用该队列的句柄。configSUPPORT_STATIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,才可使用此 RTOS API 函数。
  • 每个队列都需要 RAM 来保存队列状态以及队列中包含的项目(队列存储区)。如果使用 xQueueCreate() 创建队列,则这部分 RAM 会自动从 FreeRTOS 堆中分配。如果使用 xQueueCreateStatic() 创建队列,则 RAM 由应用程序编写者提供,这会产生更多的参数,但这样能够在编译时静态分配 RAM。
参数/描述 说明
uxQueueLength 队列一次可存储的最大项目数。
uxItemSize 存储队列中每个项目所需的大小(以字节为单位)。项目通过复制而非引用的方式入队,因此该参数值是每个入队项目将复制的字节数。队列中的每个项目必须具有相同的大小。
pucQueueStorageBuffer 如果 uxItemSize 不为零,则 pucQueueStorageBuffer 必须指向一个 uint8_t 数组,该数组的大小至少要能容纳队列中最多可能存在的项目的总字节数,即 (uxQueueLength * uxItemSize) 字节。如果 uxItemSize 为零,则 pucQueueStorageBuffer 可以为 NULL。
pxQueueBuffer 必须指向 StaticQueue_t 类型的变量,该变量将用于保存队列的数据结构体。
返回 如果队列创建成功,则返回所创建队列的句柄。如果 pxQueueBuffer 为 NULL,则返回 NULL。

用法示例:

/* 定义队列的最大长度为10,即队列可以存储10个uint64_t类型的变量。 */
#define QUEUE_LENGTH    10

/* 定义每个队列项的大小为uint64_t类型的大小。 */
#define ITEM_SIZE       sizeof( uint64_t )

/* 用于保存队列数据结构的静态变量。 */
static StaticQueue_t xStaticQueue;

/* 定义队列的存储区域数组。该数组的大小必须至少为 uxQueueLength * uxItemSize 字节。 */
uint8_t ucQueueStorageArea[ QUEUE_LENGTH * ITEM_SIZE ];

void vATask( void *pvParameters )
{
    QueueHandle_t xQueue; // 定义队列句柄变量

    /* 创建一个能够存储10个uint64_t值的静态队列。
       xQueueCreateStatic函数的参数分别为:
       - QUEUE_LENGTH:队列的最大长度。
       - ITEM_SIZE:每个队列项的大小。
       - ucQueueStorageArea:指向队列存储区域的指针。
       - &xStaticQueue:指向用于保存队列数据结构的静态变量的指针。 */
    xQueue = xQueueCreateStatic( QUEUE_LENGTH,
                                 ITEM_SIZE,
                                 ucQueueStorageArea,
                                 &xStaticQueue );

    /* 由于 pxQueueBuffer(即 &xStaticQueue)不为 NULL,因此 xQueue 不应为 NULL。
       使用 configASSERT 宏来断言 xQueue 不为 NULL,如果 xQueue 为 NULL,则触发断言失败。 */
    configASSERT( xQueue );
}

(3)向队列中发布项目:

函数原型:

 BaseType_t xQueueSend(
                        QueueHandle_t xQueue,
                        const void * pvItemToQueue,
                        TickType_t xTicksToWait
                      );
  • 此宏用于调用 xQueueGenericSend() 函数。之所以包含此宏,是为了向后兼容那些未提供 xQueueSendToFront()xQueueSendToBack() 宏的 FreeRTOS 版本。其功能等同于 xQueueSendToBack()
  • 在队列中发布项目。项目通过复制而非引用的方式入队。不得从中断服务程序中调用此函数。请参阅 xQueueSendFromISR(),这是一个可在 ISR 中使用的替代函数。
参数/返回值 描述
xQueue 要向其中发布项目的队列的句柄。
pvItemToQueue 指向要放入队列中的项目的指针。队列能够存储的项目的大小在创建队列时即已定义,因此 pvItemToQueue 中的这些字节将复制到队列存储区中。
xTicksToWait 队列已满的情况下,任务处于阻塞状态且愿意等待队列中出现可用空间的最长时间。如果队列已满且 xTicksToWait 设置为 0,则调用将立即返回。时间以滴答周期为单位定义,如果需要转换为实际时间,可以使用 portTICK_PERIOD_MS 常量。如果 INCLUDE_vTaskSuspend 设置为 1,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时限制)。
返回 如果成功发布项目,返回 pdTRUE,否则返回 errQUEUE_FULL

 用法示例:

struct AMessage
{
    char ucMessageID;        // 消息ID,使用1个字节的字符类型存储
    char ucData[20];         // 数据字段,使用20个字节的字符数组存储
} xMessage;                // 定义一个AMessage类型的变量xMessage

unsigned long ulVar = 10UL; // 定义一个无符号长整型变量ulVar并初始化为10

void vATask(void *pvParameters)
{
    QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量
    struct AMessage *pxMessage;    // 定义一个指向AMessage结构体的指针变量

    /* 创建一个能够存储10个无符号长整型(unsigned long)的队列 */
    xQueue1 = xQueueCreate(10, sizeof(unsigned long));

    /* 创建一个能够存储10个指向AMessage结构体的指针的队列
       这些结构体通过指针入队,因为它们相对较大 */
    xQueue2 = xQueueCreate(10, sizeof(struct AMessage*));

    /* ... 其他任务代码 ... */

    if (xQueue1 != NULL) // 检查xQueue1是否创建成功
    {
        /* 向xQueue1发送一个无符号长整型变量。如果队列满,最多等待10个滴答周期 */
        if (xQueueSend(xQueue1, (void*)&ulVar, (TickType_t)10) != pdPASS)
        {
            /* 发送失败,即使等待了10个滴答周期 */
        }
    }

    if (xQueue2 != NULL) // 检查xQueue2是否创建成功
    {
        /* 向xQueue2发送一个指向AMessage结构体的指针。如果队列满,不等待 */
        pxMessage = &xMessage; // 将pxMessage指向xMessage
        xQueueSend(xQueue2, (void*)&pxMessage, (TickType_t)0);
    }

    /* ... 任务代码的其余部分 */
}

(4)项目发布到队列尾部:

函数原型:

 BaseType_t xQueueSendFromISR
           (
               QueueHandle_t xQueue,
               const void *pvItemToQueue,
               BaseType_t *pxHigherPriorityTaskWoken
           );
  • 此宏用于调用 xQueueGenericSendFromISR() 函数。包含此宏是为了向后兼容某些版本的 FreeRTOS,这些版本未提供 xQueueSendToBackFromISR()xQueueSendToFrontFromISR() 宏。
  • 将项目发布到队列尾部。可以在中断服务程序中安全使用此函数。
  • 项目通过复制而非引用的方式入队,因此最好只将较小的项目放入队列,特别是从 ISR 调用时。在大多数情况下,最好存储一个指向正在排队的项目的指针。
参数/返回值 描述
xQueue 要向其中发布项目的队列的句柄。
pvItemToQueue 指向要放入队列中的项目的指针。队列能够存储的项目的大小在创建队列时即已定义,因此 pvItemToQueue 中的这些字节将复制到队列存储区中。
pxHigherPriorityTaskWoken 如果发送到队列会导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务,则 xQueueSendFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE。如果 xQueueSendFromISR() 将此值设置为 pdTRUE,则应在中断退出前请求上下文切换。从 FreeRTOS V7.3.0 开始,pxHigherPriorityTaskWoken 为可选参数,可设置为 NULL。
返回 如果数据成功发送至队列,则返回 pdTRUE,否则返回 errQUEUE_FULL

 用法示例:

缓冲 IO 的用法示例(每次调用时 ISR 可获得多个值):

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)); // 检查缓冲区计数器,如果非零则继续循环

    /* 现在缓冲区为空,如果有必要,我们可以切换上下文。 */
    if(xHigherPriorityTaskWoken)
    {
        /* 实际使用的宏是特定于端口的。 */
        taskYIELD_FROM_ISR(); // 如果有更高优先级的任务被唤醒,则请求上下文切换
    }
}

(5)队列尾部入队数据项:

函数原型:

 BaseType_t xQueueSendToBack(
 QueueHandle_t xQueue,
 const void * pvItemToQueue,
 TickType_t xTicksToWait
 );
  • 这是一个调用 xQueueGenericSend() 的宏。它等同于 xQueueSend()。
  • 从队列尾部入队一个数据项。数据项通过复制而非引用入队。不得从中断服务程序 。可以参考 xQueueSendToBackFromISR (),获取可在 ISR 中使用的替代方案。
参数/返回值 描述
xQueue 要向其中添加数据项的队列的句柄。
pvItemToQueue 指向待入队数据项的指针。创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区。
xTicksToWait 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。如果 INCLUDE_vTaskSuspend 设置为 "1",则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。
返回 如果成功发布项目,返回 pdTRUE,否则返回 errQUEUE_FULL

用法示例:

struct AMessage
{
    char ucMessageID;        // 消息ID,使用1个字节的字符类型存储
    char ucData[20];         // 数据字段,使用20个字节的字符数组存储
} xMessage;                // 定义一个AMessage类型的变量xMessage

unsigned long ulVar = 10UL; // 定义一个无符号长整型变量ulVar并初始化为10

void vATask(void *pvParameters)
{
    QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量
    struct AMessage *pxMessage;    // 定义一个指向AMessage结构体的指针变量

    /* 创建一个能够存储10个无符号长整型(unsigned long)的队列 */
    xQueue1 = xQueueCreate(10, sizeof(unsigned long));

    /* 创建一个能够存储10个指向AMessage结构体的指针的队列
       这些结构体通过指针入队,因为它们相对较大 */
    xQueue2 = xQueueCreate(10, sizeof(struct AMessage*));

    /* ... 其他任务代码 ... */

    if (xQueue1 != NULL) // 检查xQueue1是否创建成功
    {
        /* 向xQueue1发送一个无符号长整型变量。如果队列满,最多等待10个滴答周期 */
        if (xQueueSendToBack(xQueue1, (void*)&ulVar, (TickType_t)10) != pdPASS)
        {
            /* 发送失败,即使等待了10个滴答周期 */
        }
    }

    if (xQueue2 != NULL) // 检查xQueue2是否创建成功
    {
        /* 向xQueue2发送一个指向AMessage结构体的指针。如果队列满,不等待 */
        pxMessage = &xMessage; // 将pxMessage指向xMessage
        xQueueSendToBack(xQueue2, (void*)&pxMessage, (TickType_t)0);
    }

    /* ... 任务代码的其余部分 */
}

(6)项目发布到队列尾部:

函数原型:

 BaseType_t xQueueSendToBackFromISR
 (
 QueueHandle_t xQueue,
 const void *pvItemToQueue,
 BaseType_t *pxHigherPriorityTaskWoken
 );
  • 此宏用于调用 xQueueGenericSendFromISR() 函数。
  • 将项目发布到队列尾部。可以在中断服务程序中安全使用此函数。
  • 项目通过复制而非引用的方式入队,因此最好只将较小的项目放入队列,特别是从 ISR 调用时。
参数/返回值 描述
xQueue 要向其中发布项目的队列的句柄。
pvItemToQueue 指向要放入队列中的项目的指针。队列能够存储的项目的大小在创建队列时即已定义,因此 pvItemToQueue 中的这些字节将复制到队列存储区中。
pxHigherPriorityTaskWoken 如果发送到队列会导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务,则 xQueueSendToBackFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE。如果 xQueueSendToBackFromISR() 将此值设置为 pdTRUE,则应在中断退出前请求上下文切换。从 FreeRTOS V7.3.0 开始,pxHigherPriorityTaskWoken 为可选参数,可设置为 NULL。
返回 如果成功发送至队列,则返回 pdPASS,否则返回 errQUEUE_FULL

用法示例:

缓冲 IO 的用法示例(每次调用时 ISR 可获得多个值):

void vBufferISR(void)
{
    char cIn; // 定义一个字符变量cIn,用于临时存储从接收寄存器读取的字节
    BaseType_t xHigherPriorityTaskWoken; // 定义一个BaseType_t类型的变量,用于指示是否有更高优先级的任务被唤醒

    /* 在ISR开始时,我们还没有唤醒任何任务。 */
    xHigherPriorityTaskWoken = pdFALSE; // 初始化xHigherPriorityTaskWoken为pdFALSE

    /* 循环直到缓冲区为空。 */
    do
    {
        /* 从指定的接收寄存器地址RX_REGISTER_ADDRESS读取一个字节。 */
        cIn = portINPUT_BYTE(RX_REGISTER_ADDRESS); // 从接收寄存器读取一个字节到cIn

        /* 将读取的字节发送到队列xRxQueue的尾部。 */
        xQueueSendToBackFromISR(xRxQueue, &cIn, &xHigherPriorityTaskWoken); // 将cIn发送到队列,如果操作导致更高优先级任务被唤醒,xHigherPriorityTaskWoken会被设置为pdTRUE

    } while(portINPUT_BYTE(BUFFER_COUNT)); // 检查缓冲区计数器,如果非零则继续循环

    /* 现在缓冲区为空,如果有必要,我们可以切换上下文。 */
    if(xHigherPriorityTaskWoken) // 如果有更高优先级的任务被唤醒
    {
        /* 使用特定于端口的宏请求上下文切换。 */
        taskYIELD_FROM_ISR(); // 请求上下文切换,以运行更高优先级的任务
    }
}

(7)队列头部入队数据项:

函数原型:

 BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
                 const void * pvItemToQueue,
                 TickType_t xTicksToWait );
  • 此宏用于调用 xQueueGenericSend()。
  • 从队列头部入队一个数据项。数据项通过复制而非引用入队。不得从中断服务程序调用此函数。请参阅 xQueueSendToFrontFromISR() 了解 可在 ISR 中使用的替代方法。
参数/返回值 描述
xQueue 要向其中添加数据项的队列的句柄。
pvItemToQueue 指向待入队数据项的指针。创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区。
xTicksToWait 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。如果 INCLUDE_vTaskSuspend 设置为 "1",则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。
返回 如果成功发布项目,返回 pdTRUE,否则返回 errQUEUE_FULL

用法示例:

struct AMessage
{
    char ucMessageID;        // 消息ID,使用1个字节的字符类型存储
    char ucData[20];         // 数据字段,使用20个字节的字符数组存储
} xMessage;                // 定义一个AMessage类型的变量xMessage

unsigned long ulVar = 10UL; // 定义一个无符号长整型变量ulVar并初始化为10

void vATask(void *pvParameters)
{
    QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量
    struct AMessage *pxMessage;    // 定义一个指向AMessage结构体的指针变量

    /* 创建一个能够存储10个无符号长整型(unsigned long)的队列 */
    xQueue1 = xQueueCreate(10, sizeof(unsigned long));

    /* 创建一个能够存储10个指向AMessage结构体的指针的队列
       这些结构体通过指针入队,因为它们相对较大 */
    xQueue2 = xQueueCreate(10, sizeof(struct AMessage*));

    /* ... 其他任务代码 ... */

    if (xQueue1 != NULL) // 检查xQueue1是否创建成功
    {
        /* 向xQueue1发送一个无符号长整型变量。如果队列满,最多等待10个滴答周期 */
        if (xQueueSendToFront(xQueue1, (void*)&ulVar, (TickType_t)10) != pdPASS)
        {
            /* 发送失败,即使等待了10个滴答周期 */
        }
    }

    if (xQueue2 != NULL) // 检查xQueue2是否创建成功
    {
        /* 向xQueue2发送一个指向AMessage结构体的指针。如果队列满,不等待 */
        pxMessage = &xMessage; // 将pxMessage指向xMessage
        xQueueSendToFront(xQueue2, (void*)&pxMessage, (TickType_t)0);
    }

    /* ... 任务代码的其余部分 */
}

(8)项目发布到队列头部:

函数原型:

 BaseType_t xQueueSendToFrontFromISR
 (
         QueueHandle_t xQueue,
         const void *pvItemToQueue,
         BaseType_t *pxHigherPriorityTaskWoken
 );
  • 此宏用于调用 xQueueGenericSendFromISR() 函数。
  • 将项目发布到队列头部。可以在中断服务程序中安全使用此函数。
  • 项目通过复制而非引用的方式入队,因此最好只发送较小的项目, 或者发送指向该项目的指针。
参数/返回值 描述
xQueue 要向其中发布项目的队列的句柄。
pvItemToQueue 指向要放入队列中的项目的指针。队列能够存储的项目的大小在创建队列时即已定义,因此 pvItemToQueue 中的这些字节将复制到队列存储区中。
pxHigherPriorityTaskWoken 如果发送到队列会导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务,则 xQueueSendToFrontFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE。如果 xQueueSendToFrontFromISR() 将此值设置为 pdTRUE,则应在中断退出前请求上下文切换。从 FreeRTOS V7.3.0 开始,pxHigherPriorityTaskWoken 为可选参数,可设置为 NULL。
返回 如果数据成功发送至队列,则返回 pdPASS,否则返回 errQUEUE_FULL

用法示例:

void vBufferISR(void)
{
    char cIn; // 定义一个字符变量cIn,用于临时存储从接收寄存器读取的字节
    BaseType_t xHigherPriorityTaskWoken; // 定义一个BaseType_t类型的变量,用于指示是否有更高优先级的任务被唤醒

    /* 在ISR开始时,我们还没有唤醒任何任务。 */
    xHigherPriorityTaskWoken = pdFALSE; // 初始化xHigherPriorityTaskWoken为pdFALSE

    /* 从指定的接收寄存器地址RX_REGISTER_ADDRESS读取一个字节。 */
    cIn = portINPUT_BYTE(RX_REGISTER_ADDRESS); // 从接收寄存器读取一个字节到cIn

    if (cIn == EMERGENCY_MESSAGE) // 检查读取的字节是否为紧急消息
    {
        /* 如果是紧急消息,将字节发送到队列的前面。 */
        xQueueSendToFrontFromISR(xRxQueue, &cIn, &xHigherPriorityTaskWoken); // 将cIn发送到队列的前面,如果操作导致更高优先级任务被唤醒,xHigherPriorityTaskWoken会被设置为pdTRUE
    }
    else // 如果不是紧急消息
    {
        /* 将字节发送到队列的后面。 */
        xQueueSendToBackFromISR(xRxQueue, &cIn, &xHigherPriorityTaskWoken); // 将cIn发送到队列的后面,如果操作导致更高优先级任务被唤醒,xHigherPriorityTaskWoken会被设置为pdTRUE
    }

    /* 检查是否发送到队列导致更高优先级的任务被唤醒。 */
    if (xHigherPriorityTaskWoken) // 如果有更高优先级的任务被唤醒
    {
        /* 使用特定于端口的宏请求上下文切换。 */
        taskYIELD_FROM_ISR(); // 请求上下文切换,以运行更高优先级的任务
    }
}

四、队列示例程序:

(1)动态创建队列:

动态创建两个任务,一个用于发送消息到队列,另一个用于从队列接收消息并通过串口打印。

#include "stm32f10x.h"                  // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h"                      // 包含任务相关函数的头文件,用于任务创建和管理。
#include "queue.h"
#include "stdio.h"
#include "uart.h"

/***********************************
* @method  			创建两个任务,一个用于发送消息到队列,
*			 			另一个用于从队列接收消息并通过串口打印。
* @Platform  		CSDN
* @author  			The_xzs
* @date    			2025.1.25
************************************/
// 定义一个消息结构体,用于存储消息ID和消息数据
struct AMessage
{
    char ucMessageID;        // 消息ID,使用1个字节的字符类型存储
    char ucData[20];         // 数据字段,使用20个字节的字符数组存储
};

QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量

 //发送任务函数
void vSenderTask(void *pvParameters)
	
{
    unsigned long ulValueToSend = 0; 				// 用于发送的无符号长整型变量
	
    struct AMessage *pxMessage;     				// 指向AMessage结构体的指针

    for( ;; )
    {
        ulValueToSend++; 							// 每次循环递增发送值
		 
        pxMessage = (struct AMessage *) pvPortMalloc(sizeof(struct AMessage)); // 动态分配内存
		
        if(pxMessage != NULL) 								// 检查内存分配是否成功
        {
            pxMessage->ucMessageID = (char)ulValueToSend; 	// 设置消息ID
            snprintf(pxMessage->ucData, 20, "Message %lu", ulValueToSend); // 格式化消息数据
            
            // 将无符号长整型值发送到队列xQueue1
            if( xQueueSend( xQueue1, &ulValueToSend, portMAX_DELAY ) != pdPASS )
            {
                // 如果发送失败,添加错误处理代码
				printf("xQueue1 failed to send\r\n");
            }

            // 将AMessage结构体的指针发送到队列xQueue2
            if( xQueueSend( xQueue2, &pxMessage, portMAX_DELAY ) != pdPASS )
            {
                printf("xQueue2 failed to send\r\n");
            }
        }
        vTaskDelay(1000 / portTICK_RATE_MS); // 延时1秒
    }
}

// 接收任务函数
void vReceiverTask( void *pvParameters )
{
    unsigned long ulReceivedValue; // 用于接收的无符号长整型变量
    struct AMessage *pxReceivedMessage; // 指向AMessage结构体的指针

    for( ;; ) // 无限循环,任务持续运行
    {
        // 从队列xQueue1接收数据
        if( xQueueReceive( xQueue1, &ulReceivedValue, portMAX_DELAY ) == pdPASS )
        {
            // 如果接收成功,通过串口打印接收到的值
            printf("Received from xQueue1: %lu\r\n", ulReceivedValue);
        }

        // 从队列xQueue2接收数据
        if( xQueueReceive( xQueue2, &pxReceivedMessage, portMAX_DELAY ) == pdPASS )
        {
            // 如果接收成功,通过串口打印接收到的消息ID和数据
            printf("Received from xQueue2: ID = %d, Data = %s\r\n", pxReceivedMessage->ucMessageID, pxReceivedMessage->ucData);
            vPortFree(pxReceivedMessage); // 释放动态分配的内存
        }
    }
}

// 主函数
int main(void)
{
    Uart_Init(115200); 
    DMA1_Init();      

    /* 创建一个能够存储10个无符号长整型(unsigned long)的队列 */
    xQueue1 = xQueueCreate( 10, sizeof( unsigned long ) );

    if( xQueue1 == NULL ) // 检查队列是否创建成功
    {
        /* 如果队列创建失败(内存分配失败),则不能使用这个队列 */
        while(1); // 无限循环,停止程序运行
    }

    /* 创建一个能够存储10个指向AMessage结构体的指针的队列
       这些结构体通过指针入队,因为它们相对较大 */
    xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );

    if( xQueue2 == NULL ) // 检查队列是否创建成功
    {
        /* 如果队列创建失败(内存分配失败),则不能使用这个队列 */
        while(1); // 无限循环,停止程序运行
    }

    /* 创建发送和接收任务 */
    xTaskCreate( vSenderTask, "Sender", 128, NULL, 2, NULL ); 		// 创建发送任务
    xTaskCreate( vReceiverTask, "Receiver", 128, NULL, 2, NULL ); 	// 创建接收任务

	
    // 启动任务调度器,FreeRTOS开始执行任务的地方
    vTaskStartScheduler();  

    return 0;  // 理论上不会到达这里
}

效果:

(2)静态创建队列:

静态创建两个任务,一个用于发送消息到队列,另一个用于从队列接收消息并通过串口打印。

#include "stm32f10x.h"                  // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h"                      // 包含任务相关函数的头文件,用于任务创建和管理。
#include "queue.h"
#include "stdio.h"
#include "uart.h"

/***********************************
* @method  			静态创建两个任务,一个用于发送消息到队列,
*			 			另一个用于从队列接收消息并通过串口打印。
* @Platform  		CSDN
* @author  			The_xzs
* @date    			2025.1.25
************************************/
// 定义一个消息结构体,用于存储消息ID和消息数据
struct AMessage
{
    char ucMessageID;        // 消息ID,使用1个字节的字符类型存储
    char ucData[20];         // 数据字段,使用20个字节的字符数组存储
};

QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量

// 发送任务函数
void vSenderTask(void *pvParameters)
{
    unsigned long ulValueToSend = 0; 				// 用于发送的无符号长整型变量
    struct AMessage *pxMessage;     				// 指向AMessage结构体的指针

    for( ;; )
    {
        ulValueToSend++; 							// 每次循环递增发送值
        pxMessage = (struct AMessage *) pvPortMalloc(sizeof(struct AMessage)); // 动态分配内存
        if(pxMessage != NULL) 								// 检查内存分配是否成功
        {
            pxMessage->ucMessageID = (char)ulValueToSend; 	// 设置消息ID
            snprintf(pxMessage->ucData, 20, "Message %lu", ulValueToSend); // 格式化消息数据

            // 将无符号长整型值发送到队列xQueue1
            if( xQueueSend( xQueue1, &ulValueToSend, portMAX_DELAY ) != pdPASS )
            {
                // 如果发送失败,添加错误处理代码
                printf("xQueue1 failed to send\r\n");
            }

            // 将AMessage结构体的指针发送到队列xQueue2
            if( xQueueSend( xQueue2, &pxMessage, portMAX_DELAY ) != pdPASS )
            {
                printf("xQueue2 failed to send\r\n");
            }
        }
        vTaskDelay(1000 / portTICK_RATE_MS); // 延时1秒
    }
}

// 接收任务函数
void vReceiverTask( void *pvParameters )
{
    unsigned long ulReceivedValue; // 用于接收的无符号长整型变量
    struct AMessage *pxReceivedMessage; // 指向AMessage结构体的指针

    for( ;; ) // 无限循环,任务持续运行
    {
        // 从队列xQueue1接收数据
        if( xQueueReceive( xQueue1, &ulReceivedValue, portMAX_DELAY ) == pdPASS )
        {
            // 如果接收成功,通过串口打印接收到的值
            printf("Received from xQueue1: %lu\r\n", ulReceivedValue);
        }

        // 从队列xQueue2接收数据
        if( xQueueReceive( xQueue2, &pxReceivedMessage, portMAX_DELAY ) == pdPASS )
        {
            // 如果接收成功,通过串口打印接收到的消息ID和数据
            printf("Received from xQueue2: ID = %d, Data = %s\r\n", pxReceivedMessage->ucMessageID, pxReceivedMessage->ucData);
            vPortFree(pxReceivedMessage); // 释放动态分配的内存
        }
    }
}


/* 用于保存队列数据结构的静态变量。 */
static StaticQueue_t xStaticQueue;

// 主函数
int main(void)
{
    Uart_Init(115200); 
    DMA1_Init();      

    // 静态创建队列所需的存储空间
    static uint8_t ucQueueStorage1[10 * sizeof(unsigned long)];
    static uint8_t ucQueueStorage2[10 * sizeof(struct AMessage *)];

    /* 静态创建一个能够存储10个无符号长整型(unsigned long)的队列 */
    xQueue1 = xQueueCreateStatic(10, sizeof(unsigned long), ucQueueStorage1, &xStaticQueue);

    if( xQueue1 == NULL ) // 检查队列是否创建成功
    {
        /* 如果队列创建失败(内存分配失败),则不能使用这个队列 */
        while(1); // 无限循环,停止程序运行
    }

    /* 静态创建一个能够存储10个指向AMessage结构体的指针的队列
       这些结构体通过指针入队,因为它们相对较大 */
    xQueue2 = xQueueCreateStatic(10, sizeof(struct AMessage *), ucQueueStorage2, &xStaticQueue);

    if( xQueue2 == NULL ) // 检查队列是否创建成功
    {
        /* 如果队列创建失败(内存分配失败),则不能使用这个队列 */
        while(1); // 无限循环,停止程序运行
    }
	
    /* 创建发送和接收任务 */
    xTaskCreate(vSenderTask, "Sender", 64, NULL, 2, NULL); 		// 创建发送任务
    xTaskCreate(vReceiverTask, "Receiver", 64, NULL, 2, NULL); 	// 创建接收任务

    // 启动任务调度器,FreeRTOS开始执行任务的地方
    vTaskStartScheduler();  

    return 0;  // 理论上不会到达这里
}

效果:

五、队列创建时的常见问题:

(1)函数未定义:

  • 如果 configSUPPORT_STATIC_ALLOCATION设置为 1,则 RTOS 对象可以 通过应用程序编写者提供的 RAM 创建。
  • 如果 configSUPPORT_STATIC_ALLOCATION设置为 0,则 RTOS 对象 只能通过从 FreeRTOS 堆中分配的 RAM 创建。
  • 如果 configSUPPORT_STATIC_ALLOCATION未定义,则默认为 0。
  • 如果 configSUPPORT_STATIC_ALLOCATION设置为 1,则应用程序编写者还必须提供两个回调 函数:vApplicationGetIdleTaskMemory(),为 RTOS 空闲任务提供内存;(如果 configUSE_TIMERS设置为 1)vApplicationGetTimerTaskMemory(),为 RTOS 守护进程/定时器服务任务提供内存。

在task.h头文件可以看到该函数的声明:

 打开task.c文件打开找到该函数所在位置:

 向上滑动添加上该函数实现即可,如下:

(2)动态堆空间太大导致静态分配空间不足:

如下所示:

 原因:

在 FreeRTOS 中,configTOTAL_HEAP_SIZE 定义的堆空间是用于动态内存分配的区域。除了这个堆空间之外,系统中的其他内存空间可以用于静态内存分配。

默认定义的动态内存分配堆空间为17KB(c8t6):

只剩余3KB的内存空间用于静态内存分配导致空间不足。

解决办法减小,动态内存分配堆空间大小即可。

(3)队列创建后程序下载没反应:

静态创建队列中函数最后一个参数不能为NULL,且必须指向 StaticQueue_t 类型的变量因为该参数用于保存队列数据结构的静态变量。 

六、FreeRTOS教程示例代码下载:

FreeRTOS教程示例代码将会持续更新...
通过网盘分享的文件:FreeRTOS教程示例代码


网站公告


今日签到

点亮在社区的每一天
去签到