FreeRTOS基础入门——FreeRTOS队列及其API函数介绍(十二)

2024-09-02 23:28

本文主要是介绍FreeRTOS基础入门——FreeRTOS队列及其API函数介绍(十二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 个人名片:

🎓作者简介:嵌入式领域优质创作者
🌐个人主页:妄北y

📞个人QQ:2061314755

💌个人邮箱:[mailto:2061314755@qq.com]
📱个人微信:Vir2025WBY

🖥️个人公众号:科技妄北
🖋️本文为妄北y原创佳作,独家首发于CSDN🎊🎊🎊
💡座右铭:改造世界固然伟大,但改造自我更为可贵。

专栏导航:

妄北y系列专栏导航:

物联网嵌入式开发项目:大学期间的毕业设计,课程设计,大创项目,各种竞赛项目,全面覆盖了需求分析、方案设计、实施与调试、成果展示以及总结反思等关键环节。📚💼💡

QT基础入门学习:对QT的基础图形化页面设计进行了一个简单的学习与认识,利用QT的基础知识进行了翻金币小游戏的制作。🛠️🔧💭

Linux基础编程:初步认识什么是Linux,为什么学Linux,安装环境,进行基础命令的学习,入门级的shell编程。🍻🎉🖥️

深耕Linux应用开发:分享Linux的基本概念、命令行操作、文件系统、用户和权限管理等,网络编程相关知识,TCP/IP 协议、套接字(Socket)编程等,可以实现网络通信功能。常见开源库的二次开发,如libcurl、OpenSSL、json-c、freetype等💐📝💡

Linux驱动开发:Linux驱动开发是Linux系统不可或缺的组成部分,它专注于编写特殊的程序——驱动程序。这些程序承载着硬件设备的详细信息,并扮演着操作系统与硬件间沟通的桥梁角色。驱动开发的核心使命在于确保硬件设备在Linux系统上顺畅运作,同时实现与操作系统的无缝集成,为用户带来流畅稳定的体验。🚀🔧💻

Linux项目开发:Linux基础知识的实践,做项目是最锻炼能力的一个学习方法,这里我们会学习到一些简单基础的项目开发与应用,而且都是毕业设计级别的哦。🤸🌱🚀

非常期待与您一同在这个广阔的互联网天地里,携手探索知识的海洋,互相学习,共同进步。🌐💫🌱 熠熠星光,照亮我们的成长之路

✨✨ 欢迎订阅本专栏,对专栏内容任何问题都可以随时联系博主,共同书写属于我们的精彩篇章!✨✨

文章介绍:

📚本篇文章将深入剖析RTOS学习的精髓与奥秘,与您一同分享相关知识!🎉🎉🎉

若您觉得文章尚可入目,期待您能慷慨地送上点赞、收藏与分享的三连支持!您的每一份鼓励,都是我创作路上源源不断的动力。让我们携手并进,共同奔跑,期待在顶峰相见的那一天,共庆辉煌!🚀🚀🚀

🙏衷心感谢大家的点赞👍、收藏⭐和评论✍️,您的支持是我前进的动力!

目录:

目录:

一、什么是队列?

1.1 数据存储:

值传递 vs 引用传递:

1.2 多任务访问

1.3 出队阻塞:

1.4 入队阻塞:

1.5 队列操作过程图示:

二、队列结构体

三、队列创建:

3.1 函数原型:

3.1.1  函数 xQueueCreate():

3.1.2 函数xQueueCreateStatic()

3.1.3 函数xQueueGenericCreate()

3.1.4 函数xQueueGenericCreateStatic()

3.2 队列初始化函数:

3.3 队列复位函数:

四、向队列发送消息:

4.1 函数xQueueSend()、xQueueSendToBack()和xQueueSendToFront()

4.2 函数xQueueOverwrite()

4.3 函数xQueueGenericSend()

4.4 函数xQueueSendFromISR()、xQueueSendToBackFromISR()、xQueueSendToFrontFromISR()

4.5 函数xQueueOverwriteFromISR()

4.6 函数xQueueGenericSendFromISR()

五、从队列读取消息

5.1 函数xQueueReceive()

5.2 函数xQueuePeek()

5.3 函数xQueueGenericReceive()

5.4 函数xQueueReceiveFromISR()

5.5 函数xQueuePeekFromISR()

六、队列操作设计:

6.1 设计思路:

6.1.1 设计目的:

6.1.2 任务设计:

6.1.3 队列设计:

6.1.4 中断设计:

6.2 程序与分析:

6.2.1 任务设置:

6.2.2 其他应用函数:

6.2.3 main() 函数:

6.2.4 任务函数:

6.3 中断初始化及处理过程:

七、运行结果:


一、什么是队列?

队列是一种用于实现任务与任务、任务与中断之间通信的机制。它允许消息在不同的任务或任务与中断之间进行传递。队列中可以存储一定数量的固定大小的数据项,这些数据项被称为队列项目。当任务之间或任务与中断之间需要交流信息时,这些信息会被存放在队列中。

在创建队列时,可以指定队列项目的大小和队列的长度,后者指的是队列中可以存储的最大数据项目数量。由于队列的主要用途是消息传递,因此它们也常被称为消息队列。在 FreeRTOS 中,信号量也是基于队列的实现方式。

  • 功能:队列用于在任务与任务、任务与中断之间传递消息。
  • 数据项:队列中存储的数据项称为队列项目,具有固定大小。
  • 队列长度:队列的长度是指能够容纳的最大数据项目数量,在创建时设定。
  • 替代称谓:由于其消息传递的性质,队列也被称为消息队列。
  • 实现基础:在 FreeRTOS 中,信号量的实现也依赖于队列机制。

1.1 数据存储:

队列是一种数据结构,通常采用先进先出(FIFO)的方式进行数据存储和管理,这意味着数据总是从队列的尾部添加(入队),并从头部移除(出队)。然而,在某些情况下,队列也可以实现为后进先出(LIFO),即最近添加的数据最先被移出,FreeRTOS中的队列就支持这种机制。

值传递 vs 引用传递:

1. 值传递

  • 在FreeRTOS的队列中,数据通常是通过值传递的方式进行存储和传递。这意味着数据在放入队列时,会进行一次拷贝,队列中存储的是数据的副本而非原始数据的引用。
  • 优点:一旦数据被复制到队列中,原始数据就可以安全地删除或重用,因为队列中已有独立的副本。
  • 缺点:数据拷贝会占用额外的时间和内存,尤其是在数据量较大时。

2. 引用传递

  • 与之相对,引用传递只传递数据的地址(指针),不进行实际的数据拷贝。这种方式在UCOS中被采用。
  • 优点:省去了数据拷贝的时间和内存,效率较高。
  • 缺点:传递的消息必须在其生存期内保持有效性,局部变量不能直接用作消息传递,因为它们可能会在函数返回后失效。

1.2 多任务访问

队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。

1.3 出队阻塞:

在实时操作系统(RTOS)中,出队阻塞机制为任务提供了灵活的方式来处理从队列中读取数据的情况。具体来说,当一个任务尝试从队列中读取消息时,可以通过设置阻塞时间来决定任务的行为。如果队列中暂时没有数据可供读取,任务有以下三种选择:

1. 立即返回

如果任务在尝试读取队列时发现队列为空,并且阻塞时间设置为0,那么任务不会等待,而是立即返回。这意味着任务会继续执行接下来的代码部分,而不阻塞。这种方式适用于任务不愿意浪费时间等待数据的情况。

2. 等待一段时间

如果阻塞时间设置为一个大于0的值且小于 portMAX_DELAY,任务将在读取队列失败后进入阻塞状态,等待指定的时间。在这段时间内,如果队列中有数据到达,任务将立即退出阻塞状态,读取数据并继续执行后续代码。如果在指定时间内没有数据到达,任务将超时退出阻塞状态,继续执行。这种方式适用于希望短暂等待数据,但又不希望永久阻塞的情况。

3. 无限期等待

如果阻塞时间设置为 portMAX_DELAY,任务将在读取失败后进入阻塞状态,直到有数据可用为止。任务会一直等待,不会因为时间限制而超时退出。这种方式适用于任务必须获取数据才能继续执行的场景,愿意等待任意长的时间。

1.4 入队阻塞:

在实时操作系统中,入队阻塞机制允许任务在尝试向队列发送消息时设定阻塞时间,以应对队列已满的情况。与出队阻塞类似,入队阻塞为任务提供了三种选择来处理队列满的情况:

1. 立即返回

通过入队阻塞,开发人员可以根据任务的重要性和数据发送的紧迫性选择合适的队列发送策略,从而更好地管理任务调度和系统资源。这样可以确保任务在需要时能够有效地发送数据,而不会因为队列满而导致任务执行中断或失败。

2. 等待一段时间

如果阻塞时间设置为一个大于0的值且小于 portMAX_DELAY,任务将在发送失败后进入阻塞状态,等待指定的时间。在这段时间内,如果队列中腾出了空间(例如,其他任务或中断从队列中取走了一条消息),任务将退出阻塞状态,成功发送消息,并继续执行后续代码。如果在指定时间内队列仍然满,任务将超时退出阻塞状态,继续执行。这种方式适用于希望短暂等待队列有空间,但又不希望永久阻塞的情况。

3. 无限期等待

如果阻塞时间设置为 portMAX_DELAY,任务将在发送失败后进入阻塞状态,直到队列中有空间可用为止。任务会一直等待,不会因为时间限制而超时退出。这种方式适用于任务必须成功发送消息才能继续执行的场景,愿意等待任意长的时间。

1.5 队列操作过程图示:

1. 创建队列:

 图1-1 创建队列

在图1-1中,任务A需要向任务B发送一个消息,该消息包含变量x的值。首先,我们需要创建一个队列,并为其指定长度和每条消息的大小。在这个例子中,我们创建了一个长度为4的队列。这意味着队列可以容纳最多4条消息。

由于要传递的消息是变量x的值,而x是一个int类型的变量,在STM32平台上,int类型的大小为4字节,因此每条消息的长度就是4字节。这样,队列能够有效存储最多4个int类型的消息。

2. 向队列发送一个消息:

图1-2 向队列发送一个消息 

在图1-2 中,任务A的变量x的值为10,任务A将这个值发送到消息队列中。在成功发送之后,队列中将存储这个值,而此时队列的剩余容量就减少到3个位置。正如前面提到的,向队列发送消息是通过拷贝的方式进行的。

这意味着一旦消息发送完成,变量x的值已经被复制到队列中,任务A可以立即继续使用变量x,并且可以将其赋值为其他内容。因此,任务A不必担心队列中的消息会被后续对变量x的修改所影响。这样,任务A能够高效地管理和利用变量x,实现灵活的数据处理。

3. 向队列发送第二个消息:

图1·3 向队列发送第二个消息

图1-3 中任务A又向队列发送了一个消息,即新的x的值,这里是20。此时队列剩余长度为2。

4. 从队列中读取信息:

图1-4 从队列中读取消息

在图1-4 中,任务B从消息队列中读取了任务A发送的消息,将读取到的值赋给变量y,因此y的值现在为10。任务B在读取消息后可以选择是否清除这个消息。

如果任务B选择删除消息:该消息将从队列中移除,导致其他任务或中断无法再访问该消息。同时,队列的可用空间将增加1,变成3个空位。

如果任务B选择不删除消息:消息将继续保留在队列中,其他任务或中断仍然可以访问它,而队列的剩余空间保持不变,仍为2个空位。

二、队列结构体

有一个结构体用于描述队列,叫做Queue_t,这个结构体在文件queue.c中定义如下:

typedef struct QueueDefinition
{int8_t *pcHead;                  // 指向队列存储区开始地址int8_t *pcTail;                  // 指向队列存储区最后一个字节int8_t *pcWriteTo;               // 指向存储区中下一个空闲区域union{int8_t *pcReadFrom;          // 当用作队列时,指向最后一个出队的队列项首地址UBaseType_t uxRecursiveCallCount; // 当用作递归互斥量时,用来记录递归互斥量被调用的次数};List_t xTasksWaitingToSend;      // 等待发送任务列表List_t xTasksWaitingToReceive;   // 等待接收任务列表volatile UBaseType_t uxMessagesWaiting; // 队列中当前队列项数量,即消息数UBaseType_t uxLength;            // 创建队列时指定的队列长度,即最大允许的队列项(消息)数量UBaseType_t uxItemSize;          // 创建队列时指定的每个队列项(消息)最大长度,单位字节volatile int8_t cRxLock;         // 队列上锁时统计接收到的队列项数量(出队的数量)volatile int8_t cTxLock;         // 队列上锁时统计发送到队列中的队列项数量(入队的数量)#if ((configSUPPORT_STATIC_ALLOCATION == 1) && (configSUPPORT_DYNAMIC_ALLOCATION == 1))uint8_t ucStaticallyAllocated;   // 使用静态存储时此字段设置为pdTRUE#endif#if (configUSE_QUEUE_SETS == 1)struct QueueDefinition *pxQueueSetContainer; // 队列集相关#endif#if (configUSE_TRACE_FACILITY == 1)UBaseType_t uxQueueNumber;       // 跟踪调试相关uint8_t ucQueueType;#endif} xQUEUE;typedef xQUEUE Queue_t;

三、队列创建:

3.1 函数原型:

在使用队列之前必须先创建队列,有两种创建队列的方法:

静态创建:使用函数 xQueueCreateStatic()

动态创建:使用函数 xQueueCreate()

这两个函数本质上都是宏,真正完成队列创建的函数是 exQueueGenericCreat()xQueueGenericCreateStatic(),这两个函数在文件 queue.c 中定义。

3.1.1  函数 xQueueCreate():

xQueueCreate() 是一个宏,用于动态创建队列,最终调用的函数是 xQueueGenericCreate()。其函数原型如下:

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, // 要创建的队列的队列长度(项目数)UBaseType_t uxItemSize     // 队列中每个项目(消息)的长度,单位为字节
);

参数

  • uxQueueLength:要创建的队列的队列长度,即队列的项目数。
  • uxItemSize:队列中每个项目(消息)的长度,单位为字节。

返回值

  • 成功时返回队列句柄。
  • 失败时返回 NULL,表示队列创建失败。

3.1.2 函数xQueueCreateStatic()

xQueueCreateStatic() 函数用于静态创建队列,所需的内存由用户自行分配。此函数本质上也是一个宏,最终调用的函数是 xQueueGenericCreateStatic()。其函数原型如下:

QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,        // 要创建的队列的队列长度(项目数)UBaseType_t uxItemSize,           // 队列中每个项目(消息)的长度,单位为字节uint8_t *pucQueueStorageBuffer,    // 指向队列项目的存储区,用户自分配StaticQueue_t *pxQueueBuffer       // 指向一个 StaticQueue_t 类型的变量,用于保存队列结构体
);

参数

  • uxQueueLength:要创建的队列的队列长度,即队列的项目数。
  • uxItemSize:队列中每个项目(消息)的长度,单位为字节。
  • pucQueueStorageBuffer:指向队列项目的存储区,需用户自行分配,并且该存储区大小应大于等于 uxQueueLength * uxItemSize 字节。
  • pxQueueBuffer:指向一个 StaticQueue_t 类型的变量,用于保存队列结构体。

返回值

  • 成功时返回队列句柄。
  • 失败时返回 NULL,表示队列创建失败。

3.1.3 函数xQueueGenericCreate()

xQueueGenericCreate() 函数用于动态创建队列,创建过程中所需的内存通过 FreeRTOS 中的动态内存管理函数 pvPortMalloc() 分配。其函数原型如下:

QueueHandle_t xQueueGenericCreate(const UBaseType_t uxQueueLength,  // 要创建的队列的队列长度(项目数)const UBaseType_t uxItemSize,     // 队列中每个项目(消息)的长度,单位为字节const uint8_t ucQueueType         // 队列类型
);

参数

  • uxQueueLength:要创建的队列的队列长度,即队列的项目数。

  • uxItemSize:队列中每个项目(消息)的长度,单位为字节。

  • ucQueueType:队列类型。由于 FreeRTOS 中的信号量等也是通过队列来实现的,因此在创建时需要指定队列的用途。可选的队列类型包括:

    • queueQUEUE_TYPE_BASE:普通的消息队列。
    • queueQUEUE_TYPE_SET:队列集。
    • queueQUEUE_TYPE_MUTEX:互斥信号量。
    • queueQUEUE_TYPE_COUNTING_SEMAPHORE:计数型信号量。
    • queueQUEUE_TYPE_BINARY_SEMAPHORE:二值信号量。
    • queueQUEUE_TYPE_RECURSIVE_MUTEX:递归互斥信号量。

    在使用 xQueueCreate() 创建队列时,此参数默认选择 queueQUEUE_TYPE_BASE

返回值

  • 成功时返回队列句柄。
  • 失败时返回 NULL,表示队列创建失败。`

3.1.4 函数xQueueGenericCreateStatic()

xQueueGenericCreateStatic() 函数用于静态创建队列,创建过程中所需的内存由用户自行分配。其函数原型如下:

QueueHandle_t xQueueGenericCreateStatic(const UBaseType_t uxQueueLength,        // 要创建的队列的队列长度(项目数)const UBaseType_t uxItemSize,           // 队列中每个项目(消息)的长度,单位为字节uint8_t *pucQueueStorage,                // 指向队列项目的存储区,用户自分配StaticQueue_t *pxStaticQueue,           // 指向一个 StaticQueue_t 类型的变量,用于保存队列结构体const uint8_t ucQueueType                // 队列类型
);

参数

  • uxQueueLength:要创建的队列的队列长度,即队列的项目数。
  • uxItemSize:队列中每个项目(消息)的长度,单位为字节。
  • pucQueueStorage:指向队列项目的存储区,需用户自行分配,并且该存储区大小应大于等于 uxQueueLength * uxItemSize 字节。
  • pxStaticQueue:指向一个 StaticQueue_t 类型的变量,用于保存队列结构体。
  • ucQueueType:队列类型。

返回值

  • 成功时返回队列句柄。
  • 失败时返回 NULL,表示队列创建失败。

3.2 队列初始化函数:

队列初始化函数prvInitialiseNewQueue(用于队列的初始化,此函数在文件queue.c中有定
义,函数代码如下:

static void prvInitialiseNewQueue(const UBaseType_t uxQueueLength,    // 队列长度const UBaseType_t uxItemSize,       // 队列项目长度uint8_t *pucQueueStorage,           // 队列项目存储区const uint8_t ucQueueType,          // 队列类型Queue_t *pxNewQueue                 // 队列结构体
) {// 防止编译器报错,避免未使用的参数警告(void)ucQueueType;// 判断队列项(消息)长度是否为0if (uxItemSize == (UBaseType_t)0) {// 队列项长度为0,说明没有队列存储区,将 pcHead 指向队列开始地址pxNewQueue->pcHead = (int8_t *)pxNewQueue;} else {// 设置 pcHead 指向队列项存储区的首地址pxNewQueue->pcHead = (int8_t *)pucQueueStorage;}// 初始化队列结构体的相关成员变量pxNewQueue->uxLength = uxQueueLength;        // 队列长度pxNewQueue->uxItemSize = uxItemSize;         // 队列项目长度// 重置队列(将队列初始化为就绪状态)(void)xQueueGenericReset(pxNewQueue, pdTRUE);#if configUSE_TRACE_FACILITY == 1// 如果启用了跟踪调试功能,初始化相关字段pxNewQueue->ucQueueType = ucQueueType;
#endif /* configUSE_TRACE_FACILITY */#if (configUSE_QUEUE_SETS == 1)// 如果启用了队列集功能,初始化相关字段pxNewQueue->pxQueueSetContainer = NULL;
#endif /* configUSE_QUEUE_SETS */// 跟踪队列创建事件traceQUEUE_CREATE(pxNewQueue);
}

1.队列项长度判断:如果 uxItemSize 为 0,则认为这是一个无实际存储区的队列(可能是用于信号量等)。在这种情况下,将 pcHead 指针指向队列结构体本身。

2.初始化队列结构体:设置队列的长度和项目大小,并调用 xQueueGenericReset() 函数将队列重置为初始状态。

3.3 队列复位函数:

队列初始化函数prvInitialiseNewQueue0中调用了函数xQueueGenericResetO)来复位队列,
函数xQueueGenericReset()代码如下:
 

BaseType_t xQueueGenericReset(QueueHandle_t xQueue, BaseType_t xNewQueue) {Queue_t * const pxQueue = (Queue_t *)xQueue;  // 将队列句柄转换为队列结构体指针configASSERT(pxQueue);                          // 确保队列句柄有效taskENTER_CRITICAL();                           // 进入临界区,避免中断// 初始化队列相关成员变量pxQueue->pcTail = pxQueue->pcHead + (pxQueue->uxLength * pxQueue->uxItemSize);  // 队列尾指针pxQueue->uxMessagesWaiting = (UBaseType_t)0U; // 当前队列中等待的消息数pxQueue->pcWriteTo = pxQueue->pcHead;         // 写入指针初始化为队列头pxQueue->u.pcReadFrom = pxQueue->pcHead + ((pxQueue->uxLength - (UBaseType_t)1U) * pxQueue->uxItemSize); // 读取指针初始化为队列尾pxQueue->cRxLock = queueUNLOCKED;              // 接收锁初始化为解锁状态pxQueue->cTxLock = queueUNLOCKED;              // 发送锁初始化为解锁状态if (xNewQueue == pdFALSE) {// 如果不是新队列,保持阻塞状态// 队列依旧是空的,阻塞的任务保持不变if (listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToSend)) == pdFALSE) {// 如果有任务在等待发送if (xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToSend)) != pdFALSE) {queueYIELD_IF_USING_PREEMPTION(); // 如果使用抢占,放弃当前任务} else {mtCOVERAGE_TEST_MARKER(); // 覆盖测试标记}} else {mtCOVERAGE_TEST_MARKER(); // 覆盖测试标记}}// 初始化队列中的等待任务列表vListInitialise(&(pxQueue->xTasksWaitingToSend)); // 初始化等待发送的任务列表vListInitialise(&(pxQueue->xTasksWaitingToReceive)); // 初始化等待接收的任务列表taskEXIT_CRITICAL();                           // 退出临界区return pdPASS;                                 // 返回成功
}
  1. 队列句柄转换和验证:将传入的队列句柄类型转换为 Queue_t 结构体指针,并使用 configASSERT 确保其有效性。
  2. 进入临界区:使用 taskENTER_CRITICAL() 进入临界区,以防止在队列操作期间发生中断。
  3. 初始化队列成员
    • pcTail:队列尾指针初始化为队列存储区的结束地址。
    • uxMessagesWaiting:当前队列中等待的消息数初始化为 0。
    • pcWriteTo:写入指针初始化为队列头。
    • pcReadFrom:读取指针初始化为队列尾。
    • cRxLock 和 cTxLock:接收和发送锁初始化为解锁状态。
  4. 处理阻塞的任务:如果 xNewQueue 为 pdFALSE,表示不是新创建的队列,检查是否有任务在等待发送。如果有,则移除这些任务,并根据情况决定是否要放弃当前任务。
  5. 初始化等待任务列表:调用 vListInitialise 初始化等待发送和接收的任务列表。
  6. 退出临界区:使用 taskEXIT_CRITICAL() 退出临界区,允许中断。
  7. 返回值:返回 pdPASS 以指示成功。

图3-1 创建完成后的初始化队列 

至此,队列创建成功,比如我们创建一个有4个队列项,每个队列项长度为32个字节的队
列TestQueue,创建成功的队列如图3-1 所示

四、向队列发送消息:

创建好队列以后就可以向队列发送消息了,FreeRTOS提供了8个向队列发送消息的API函数如表所示:

4.1 函数xQueueSend()、xQueueSendToBack()和xQueueSendToFront()

  • xQueueSend(): 将消息发送到队列的末尾(后向入队)。
  • xQueueSendToBack(): 与 xQueueSend() 功能完全相同,都是将消息发送到队列的末尾。
  • xQueueSendToFront(): 将消息发送到队列的前面(前向入队)。

所有这些函数最终调用的是通用的 xQueueGenericSend() 函数,并且只能在任务函数中使用,而不能在中断服务函数中使用。对于中断服务函数,FreeRTOS 提供了以 FromISR 结尾的专用函数。

BaseType_t xQueueSend(QueueHandle_t xQueue,         // 队列句柄,指定要向哪个队列发送数据const void *pvItemToQueue,    // 指向要发送的消息TickType_t xTicksToWait       // 阻塞时间
);BaseType_t xQueueSendToBack(QueueHandle_t xQueue,         // 队列句柄,指定要向哪个队列发送数据const void *pvItemToQueue,    // 指向要发送的消息TickType_t xTicksToWait       // 阻塞时间
);BaseType_t xQueueSendToFront(QueueHandle_t xQueue,         // 队列句柄,指定要向哪个队列发送数据const void *pvItemToQueue,    // 指向要发送的消息TickType_t xTicksToWait       // 阻塞时间
);

参数:

  • xQueue: 队列句柄,标识要向哪个队列发送数据。在创建队列时,系统会返回一个句柄,后续通过该句柄对队列进行操作。

  • pvItemToQueue: 指向要发送的消息。消息将从此地址拷贝到队列中。

  • xTicksToWait: 阻塞时间。指定当队列已满时,任务进入阻塞状态并等待队列空闲的最大时间:

    • 为 0 时,如果队列已满,函数立即返回。
    • 为 portMAX_DELAY 时,任务将无限期地等待,直到队列空闲(需要宏 INCLUDE_vTaskSuspend 为 1)。

返回值:

  • pdPASS: 消息成功发送到队列。
  • errQUEUE_FULL: 队列已满,消息发送失败(仅适用于 xQueueSend()xQueueSendToBack() 和 xQueueSendToFront())。

4.2 函数xQueueOverwrite()

xQueueOverwrite 函数用于向队列发送数据。在队列已满的情况下,此函数会覆盖旧的数据,无论这些旧数据是否已被其他任务或中断取走。该函数常用于长度为1的队列,因为它允许新的数据覆盖现有的数据。

BaseType_t xQueueOverwrite(QueueHandle_t xQueue,         // 队列句柄,指明要向哪个队列发送数据const void *pvItemToQueue     // 指向要发送的消息
);

参数:

  • xQueue:

    • 类型: QueueHandle_t
    • 描述: 队列句柄,指明要向哪个队列发送数据。该句柄在成功创建队列后返回。
  • pvItemToQueue:

    • 类型: const void *
    • 描述: 指向要发送的消息。当调用此函数时,会将消息从此地址拷贝到队列中。

4.3 函数xQueueGenericSend()

xQueueGenericSend 函数是 FreeRTOS 中用于向队列发送消息的核心函数。所有的任务级入队函数(如 xQueueSendxQueueSendToBackxQueueSendToFrontxQueueOverwrite)最终都会调用此函数来执行实际的消息发送操作。

BaseType_t xQueueGenericSend(QueueHandle_t xQueue,            // 队列句柄,指明要向哪个队列发送数据const void * const pvItemToQueue, // 指向要发送的消息TickType_t xTicksToWait,         // 阻塞时间const BaseType_t xCopyPosition    // 入队方式
);

参数:

  • xQueue:

    • 类型: QueueHandle_t
    • 描述: 队列句柄,指明要向哪个队列发送数据。队列在成功创建后会返回此句柄。
  • pvItemToQueue:

    • 类型: const void * const
    • 描述: 指向要发送的消息。在调用此函数时,消息会从此地址拷贝到队列中。
  • xTicksToWait:

    • 类型: TickType_t
    • 描述: 阻塞时间。指定当队列已满时,任务进入阻塞状态并等待队列空闲的最大时间。如果为 0,则在队列满的情况下立即返回;如果为 portMAX_DELAY,则任务将无限期等待,直到队列有空闲。
  • xCopyPosition:

    • 类型: BaseType_t
    • 描述: 入队方式,决定消息入队的方式。可取值如下:
      • queueSEND_TO_BACK: 后向入队,即将新消息插入到队列的末尾。
      • queueSEND_TO_FRONT: 前向入队,即将新消息插入到队列的前面。
      • queueOVERWRITE: 覆写入队,即在队列满时覆盖旧的数据。

返回值:​​​​​​​

  • pdTRUE:

    • 描述: 向队列发送消息成功,消息成功入队。
  • errQUEUE_FULL:

    • 描述: 队列已满,消息发送失败。在这种情况下,如果 xCopyPosition 为 queueOVERWRITE,则可以覆盖旧数据而不返回该错误。

4.4 函数xQueueSendFromISR()、xQueueSendToBackFromISR()、xQueueSendToFrontFromISR()

在 FreeRTOS 中,处理队列的函数 xQueueSendFromISRxQueueSendToBackFromISRxQueueSendToFrontFromISR 是用于在中断服务例程(ISR)中发送消息到队列的专用函数。这些函数的实现本质上是宏,它们都调用了一个底层函数 xQueueGenericSendFromISR 来执行实际的消息发送操作。

BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void * pvItemToQueue,BaseType_t * pxHigherPriorityTaskWoken
);BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,const void * pvItemToQueue,BaseType_t * pxHigherPriorityTaskWoken
);BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,const void * pvItemToQueue,BaseType_t * pxHigherPriorityTaskWoken
);

参数:

  • xQueue:

    • 类型: QueueHandle_t
    • 描述: 队列句柄,指明要向哪个队列发送数据。该句柄在成功创建队列后返回。
  • pvItemToQueue:

    • 类型: const void *
    • 描述: 指向要发送的消息的指针。在调用时,该消息会被拷贝到队列中。
  • pxHigherPriorityTaskWoken:

    • 类型: BaseType_t *
    • 描述: 标记是否需要进行任务切换。这个变量的值由这些函数设置,用户只需提供一个变量以保存该值。当其值为 pdTRUE 时,意味着在退出中断服务函数之前需要进行任务切换。

返回值:

  • pdTRUE:

    • 描述: 向队列发送消息成功。
  • errQUEUE_FULL:

    • 描述: 队列已满,消息发送失败。

4.5 函数xQueueOverwriteFromISR()

xQueueOverwriteFromISR 是 FreeRTOS 中的一个函数,用于在中断服务例程(ISR)中覆盖队列中的旧数据。当队列满时,该函数会自动覆盖队列中的旧数据,而不会像其他队列发送函数那样返回队列已满的错误。这使得它特别适合用于那些需要保证数据存储,但不需要保留队列中所有历史数据的场景。

BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,const void * pvItemToQueue,BaseType_t * pxHigherPriorityTaskWoken
);

参数:

  • xQueue:

    • 类型: QueueHandle_t
    • 描述: 队列句柄,指明要向哪个队列发送数据。此句柄在成功创建队列后返回。
  • pvItemToQueue:

    • 类型: const void *
    • 描述: 指向要发送的消息的指针。在调用时,该消息会被拷贝到队列中。如果队列已满,该函数会覆盖队列中的旧数据。
  • pxHigherPriorityTaskWoken:

    • 类型: BaseType_t *
    • 描述: 标记是否需要进行任务切换。这个变量的值由函数设置,用户只需提供一个变量以保存该值。当其值为 pdTRUE 时,意味着在退出中断服务函数之前需要进行一次任务切换。

返回值:

  • pdTRUE:

    • 描述: 成功向队列发送(或覆盖)消息。
  • errQUEUE_FULL:

    • 描述: 队列已满,此返回值通常不会由 xQueueOverwriteFromISR 返回,因为它会直接覆盖旧数据。

4.6 函数xQueueGenericSendFromISR()

在 FreeRTOS 中,xQueueGenericSendFromISR在中断服务例程(ISR)中用于发送消息到队列的核心函数。上面提到的四个中断级入队函数 (xQueueSendFromISRxQueueSendToBackFromISRxQueueSendToFrontFromISRxQueueOverwriteFromISR) 都是对该函数的封装,最终都调用了这个函数来执行实际的消息发送操作。

BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue,const void * pvItemToQueue,BaseType_t * pxHigherPriorityTaskWoken,BaseType_t xCopyPosition
);

参数:

  • xQueue:

    • 类型: QueueHandle_t
    • 描述: 队列句柄,用于指示向哪个队列发送数据。这个句柄是在成功创建队列后返回的。
  • pvItemToQueue:

    • 类型: const void *
    • 描述: 指向要发送的消息的指针。在调用函数时,这个消息会被拷贝到队列中。
  • pxHigherPriorityTaskWoken:

    • 类型: BaseType_t *
    • 描述: 标记是否需要在退出此函数后进行任务切换。这个值由 xQueueGenericSendFromISR 设置,用户只需提供一个变量来保存该值。当该值为 pdTRUE 时,意味着在退出中断服务函数之前需要进行一次任务切换,以确保更高优先级的任务能够及时执行。
  • xCopyPosition:

    • 类型: BaseType_t
    • 描述: 指定消息的入队方式,有以下三种方式:
      • queueSEND_TO_BACK: 后向入队,即将消息插入到队列的末尾。
      • queueSEND_TO_FRONT: 前向入队,即将消息插入到队列的开头。
      • queueOVERWRITE: 覆写入队,即当队列已满时,覆盖队列中的旧数据。

返回值:

  • pdTRUE:

    • 描述: 成功向队列发送消息。
  • errQUEUE_FULL:

    • 描述: 队列已满,消息发送失败(但如果使用 queueOVERWRITE 模式,则不会返回此错误,因为它会覆盖旧数据)。

五、从队列读取消息

有入队就有出队,出队就是从队列中获取队列项(消息),FreeRTOS中出队函数如表所示:

5.1 函数xQueueReceive()

xQueueReceive 是 FreeRTOS 中用于从队列中读取消息的函数。当调用该函数读取一条消息后,队列中的该消息会被移除。虽然 xQueueReceive 本质上是一个宏,但它实际上调用了 xQueueGenericReceive 来执行实际的消息读取操作。

BaseType_t xQueueReceive(QueueHandle_t xQueue,void * pvBuffer,TickType_t xTicksToWait
);

参数:

  • xQueue:

    • 类型: QueueHandle_t
    • 描述: 队列句柄,用于指示从哪个队列读取数据。这个句柄是在成功创建队列后返回的,标识特定的队列。
  • pvBuffer:

    • 类型: void *
    • 描述: 保存读取到的数据的缓冲区。在从队列中读取数据的过程中,读取的数据会被拷贝到这个缓冲区中。缓冲区的大小应与创建队列时设置的每个队列项的大小一致。
  • xTicksToWait:

    • 类型: TickType_t
    • 描述: 阻塞时间,指定当队列为空时任务应等待数据到达的最大时间。这个参数有以下几种取值:
      • 0: 如果队列为空,则函数立即返回,不会等待。
      • 非零值: 函数将等待指定的时钟周期数 (xTicksToWait) 以等待队列中有数据。如果在指定的时间内队列中仍然没有数据,函数将返回 pdFALSE
      • portMAX_DELAY: 任务将无限期等待,直到队列中有数据为止(这意味着“死等”)。在使用 portMAX_DELAY 时,必须确保宏 INCLUDE_vTaskSuspend 已启用。

返回值:

  • pdTRUE:

    • 描述: 成功从队列中读取数据并将其拷贝到 pvBuffer 中。
  • pdFALSE:

    • 描述: 读取队列失败,通常是由于在指定的等待时间内队列中没有数据可供读取。

5.2 函数xQueuePeek()

xQueuePeek 是 FreeRTOS 中用于从队列中读取一条消息但不删除该消息的函数。该函数只能在任务上下文中使用,即不能在中断服务例程中调用。虽然 xQueuePeek 是一个宏,但它最终调用的是 xQueueGenericReceive 来完成实际的操作。

BaseType_t xQueuePeek(QueueHandle_t xQueue,void * pvBuffer,TickType_t xTicksToWait
);

参数: 

  • xQueue:

    • 类型: QueueHandle_t
    • 描述: 队列句柄,指示要从哪个队列中读取数据。这个句柄是在成功创建队列时返回的,用于标识特定的队列。
  • pvBuffer:

    • 类型: void *
    • 描述: 保存读取到的数据的缓冲区。该缓冲区用于存储从队列中读取的数据,但不会从队列中删除该数据。缓冲区的大小应与创建队列时指定的每个队列项的大小一致。
  • xTicksToWait:

    • 类型: TickType_t
    • 描述: 阻塞时间,指定当队列为空时任务应等待数据到达的最大时间。这个参数有以下几种取值:
      • 0: 如果队列为空,函数会立即返回,不会等待。
      • 非零值: 函数将等待指定的时钟周期数 (xTicksToWait),以等待队列中有数据。如果在指定时间内队列中仍然没有数据,函数将返回 pdFALSE
      • portMAX_DELAY: 任务将无限期等待,直到队列中有数据为止(即“死等”)。在使用 portMAX_DELAY 时,必须确保宏 INCLUDE_vTaskSuspend 已启用。

返回值:

  • pdTRUE:

    • 描述: 成功从队列中读取数据并将其拷贝到 pvBuffer 中。
  • pdFALSE:

    • 描述: 读取队列失败,通常是因为在指定的等待时间内队列中没有数据可供读取。

5.3 函数xQueueGenericReceive()

BaseType_t xQueueGenericReceive(QueueHandle_t xQueue,void * pvBuffer,TickType_t xTicksToWait,BaseType_t xJustPeek
);

参数:

  • xQueue:

    • 类型: QueueHandle_t
    • 描述: 队列句柄,用于指示要从哪个队列读取数据。该句柄是在成功创建队列后返回的。
  • pvBuffer:

    • 类型: void *
    • 描述: 用于保存读取到的数据的缓冲区。在读取过程中,消息会被拷贝到这个缓冲区中。缓冲区的大小应与队列项的大小一致。
  • xTicksToWait:

    • 类型: TickType_t
    • 描述: 阻塞时间,指定当队列为空时任务应等待数据到达的最大时间。参数说明如下:
      • 0: 如果队列为空,则函数立即返回,不会等待。
      • 非零值: 函数将在指定的时钟周期内等待队列中有数据。如果在指定的时间内队列中仍然没有数据,函数将返回 pdFALSE
      • portMAX_DELAY: 任务将无限期等待,直到队列中有数据可供读取(即“死等”)。在使用 portMAX_DELAY 时,必须确保宏 INCLUDE_vTaskSuspend 已启用。
  • xJustPeek:

    • 类型: BaseType_t
    • 描述: 指示在读取消息后是否删除该消息的标志。如果该值为 pdTRUE,则读取操作不会删除队列中的消息,后续调用 xQueueReceive 仍然可以获取到相同的消息。如果该值为 pdFALSE,则读取操作会将消息从队列中移除。

返回值:

  • pdTRUE:

    • 描述: 成功从队列中读取数据,并将数据拷贝到 pvBuffer 中。
  • pdFALSE:

    • 描述: 从队列中读取数据失败,通常是因为在指定的等待时间内队列中没有数据可供读取。

5.4 函数xQueueReceiveFromISR()

xQueueReceiveFromISR 是 FreeRTOS 提供的专门用于在中断服务例程(ISR)中从队列中读取消息的函数。它的设计目的是在中断上下文中安全地从队列中获取数据,并在读取成功后自动将该数据从队列中删除。

BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void * pvBuffer,BaseType_t * pxTaskWoken
);

参数:

  • xQueue:

    • 类型: QueueHandle_t
    • 描述: 队列句柄,用于指示从哪个队列读取数据。这个句柄是在成功创建队列后返回的,用来标识特定的队列。
  • pvBuffer:

    • 类型: void *
    • 描述: 保存读取到的数据的缓冲区。该缓冲区用于存储从队列中读取的数据。在读取过程中,数据会被拷贝到这个缓冲区中。缓冲区的大小应与创建队列时指定的每个队列项的大小一致。
  • pxTaskWoken:

    • 类型: BaseType_t *
    • 描述: 用于指示在退出中断服务例程时是否需要进行上下文切换。此参数是一个指向 BaseType_t 类型变量的指针。该变量在函数内部设置:
      • 如果函数将其设置为 pdTRUE,表示读取操作导致一个优先级更高的任务准备就绪,此时在退出中断服务例程时应该进行一次上下文切换。
      • 如果为 pdFALSE,则不需要进行任务切换。

    注意: 用户在调用该函数时,只需提供一个变量来保存 pxTaskWoken 的值,而不需要自己设置这个值。

返回值:

  • pdTRUE:

    • 描述: 成功从队列中读取数据,并将数据拷贝到 pvBuffer 中
  • pdFALSE:

    • 描述: 读取队列数据失败,通常是因为队列为空,没有数据可供读取。

5.5 函数xQueuePeekFromISR()

xQueuePeekFromISR 是 FreeRTOS 提供的一个用于在中断服务例程(ISR)中从队列读取消息但不删除该消息的函数。xQueueReceiveFromISR 不同的是,xQueuePeekFromISR 读取消息后不会将其从队列中删除,这意味着后续的读取操作仍然可以获取到相同的消息。

BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,void * pvBuffer
);

参数:

  • xQueue:

    • 类型: QueueHandle_t
    • 描述: 队列句柄用于指示从哪个队列读取数据该句柄是在成功创建队列后返回的。
  • pvBuffer:

    • 类型: void *
    • 描述: 保存读取到的数据的缓冲区。在读取过程中,消息会被拷贝到这个缓冲区中。缓冲区的大小应与创建队列时指定的每个队列项的大小一致。

返回值:

  • pdTRUE:

    • 描述: 成功从队列中读取数据,并将数据拷贝到 pvBuffer 中。
  • pdFALSE:

    • 描述: 从队列中读取数据失败,通常是因为队列为空,没有数据可供读取。

六、队列操作设计:

6.1 设计思路:

6.1.1 设计目的:

本设计的目的是学习和使用 FreeRTOS 的队列相关 API 函数,掌握如何在任务或中断中向队列发送消息或者从队列中接收消息。

设计了三个任务和两个中断,任务和中断的功能及其相互作用如下:

6.1.2 任务设计:

1. start_task:

  • 功能:用于创建其他两个任务 taskl_task 和 Keyprocess_task

2. taskl_task:

  • 功能:负责读取按键的键值,并将该键值发送到队列 Key_Queue 中。同时,检查队列的剩余容量等信息。
  • 说明:本任务处理三个按键(KEY_UPKEY2 和 KEY0,每个按键对应不同的键值。当用户按下任意按键时,该任务会将相应的键值发送到 Key_Queue 中,供后续处理。

3. Keyprocess_task:

  • 功能:按键处理任务,从队列 Key_Queue 中读取消息,并根据不同的键值执行相应的处理操作。
  • 说明:此任务不断检查队列 Key_Queue,当有新的按键信息进入队列时,它会读取并处理该信息,执行特定的逻辑操作。

6.1.3 队列设计:

1. Key_Queue:

  • 用途:用于在 taskl_task 和 Keyprocess_task 之间传递按键值。taskl_task 将读取到的按键值发送到该队列,Keyprocess_task 从该队列读取消息并处理。

2. Message_Queue:

  • 用途:用于传递串口接收的数据消息。串口1的接收中断将接收到的数据发送到该队列,定时器2中断定时从该队列读取消息并将其显示在 LCD 上。

6.1.4 中断设计:

1. 串口1接收中断:

  • 功能:接收串口发送过来的数据,并将接收到的数据发送到 Message_Queue 队列中。
  • 说明:当串口1接收到数据时,中断触发,数据被存储到 Message_Queue 中供后续处理。

2. 定时器2中断:

  • 功能:定时周期为500ms。在定时中断中,从 Message_Queue 队列中读取消息,并将读取到的消息显示在 LCD 上。
  • 说明:每500ms定时器触发一次中断,期间从 Message_Queue 中获取消息,并显示在 LCD 上,以提供及时的用户反馈。

6.2 程序与分析:

6.2.1 任务设置:

//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		256  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);//任务优先级
#define TASK1_TASK_PRIO		2
//任务堆栈大小	
#define TASK1_STK_SIZE 		256  
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);//任务优先级
#define KEYPROCESS_TASK_PRIO 3
//任务堆栈大小	 
#define KEYPROCESS_STK_SIZE  256 
//任务句柄
TaskHandle_t Keyprocess_Handler;
//任务函数
void Keyprocess_task(void *pvParameters);//按键消息队列的数量
#define KEYMSG_Q_NUM    1  		//按键消息队列的数量  
#define MESSAGE_Q_NUM   4   	//发送数据的消息队列的数量 
QueueHandle_t Key_Queue;   		//按键值消息队列句柄
QueueHandle_t Message_Queue;	//信息队列句柄

(1)队列Key_Queue用来传递按键值的,也就是一个u8变量,所以队列长度为1就行了。并且消息长度为1个字节。

(2)队列Message_Queue用来传递串口接收到的数据,队列长度设置为4,每个消息的长度为USART REC LEN(在usart.h中有定义)。

6.2.2 其他应用函数:

在main.c中实现的一些其他函数:

//用于在LCD上显示接收到的队列的消息
//str: 要显示的字符串(接收到的消息)
void disp_str(u8* str)
{LCD_Fill(5,230,110,245,WHITE);					//先清除显示区域LCD_ShowString(5,230,100,16,16,str);
}//加载主界面
void freertos_load_main_ui(void)
{POINT_COLOR = RED;LCD_ShowString(10,10,200,16,16,"ATK STM32F103/407");	LCD_ShowString(10,30,200,16,16,"FreeRTOS Examp 13-1");LCD_ShowString(10,50,200,16,16,"Message Queue");LCD_ShowString(10,70,220,16,16,"KEY_UP:LED1 KEY1:BEEP");LCD_ShowString(10,90,200,16,16,"KEY0:Refresh LCD");POINT_COLOR = BLACK;LCD_DrawLine(0,107,239,107);		//画线LCD_DrawLine(119,107,119,319);		//画线LCD_DrawRectangle(125,110,234,314);	//画矩形POINT_COLOR = RED;LCD_ShowString(0,130,120,16,16,"DATA_Msg Size:");LCD_ShowString(0,170,120,16,16,"DATA_Msg rema:");LCD_ShowString(0,210,100,16,16,"DATA_Msg:");POINT_COLOR = BLUE;
}//查询Message_Queue队列中的总队列数量和剩余队列数量
void check_msg_queue(void)
{u8 *p;u8 msgq_remain_size;	//消息队列剩余大小u8 msgq_total_size;     //消息队列总大小taskENTER_CRITICAL();   //进入临界区msgq_remain_size=uxQueueSpacesAvailable(Message_Queue);//得到队列剩余大小msgq_total_size=uxQueueMessagesWaiting(Message_Queue)+uxQueueSpacesAvailable(Message_Queue);//得到队列总大小,总大小=使用+剩余的。p=mymalloc(SRAMIN,20);	//申请内存sprintf((char*)p,"Total Size:%d",msgq_total_size);	//显示DATA_Msg消息队列总的大小LCD_ShowString(10,150,100,16,16,p);sprintf((char*)p,"Remain Size:%d",msgq_remain_size);	//显示DATA_Msg剩余大小LCD_ShowString(10,190,100,16,16,p);myfree(SRAMIN,p);		//释放内存taskEXIT_CRITICAL();    //退出临界区
}

在本实验中,定时器9的中断服务函数会调用 disp_str() 函数,将从 Message_Queue 队列中接收到的消息显示在 LCD 上。为了初始化实验的用户界面(UI),会调用 freertos_load_main_ui() 函数,这个函数负责在屏幕上绘制实验的初始 UI 界面。

此外,实验中还包含了对 Message_Queue 队列的状态进行监控的功能,这个功能通过 check_msg_queue() 函数实现。该函数可以查询 Message_Queue 的相关信息,包括队列的总大小和当前剩余的大小。

1. uxQueueSpacesAvailable()

  • 用途:获取 Message_Queue 队列的剩余容量,即队列中还可以容纳的消息数量。这帮助监控队列的使用情况,防止队列满溢。

2. uxQueueMessagesWaiting()

  • 用途:获取 Message_Queue 队列中当前已存储的消息数量,也就是队列的使用量。通过此函数可以知道队列中有多少消息等待处理。

3. 队列总大小

  • 可以通过将 uxQueueMessagesWaiting() 获取到的队列使用量与 uxQueueSpacesAvailable() 获取到的队列剩余容量相加来计算队列的总大小。这有助于了解队列的容量配置和当前的使用情况。

6.2.3 main() 函数:

int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4	 delay_init();	    				//延时函数初始化	 uart_init(115200);					//初始化串口LED_Init();		  					//初始化LEDKEY_Init();							//初始化按键BEEP_Init();						//初始化蜂鸣器LCD_Init();							//初始化LCDTIM2_Int_Init(5000,7200-1);			//初始化定时器2,周期500msmy_mem_init(SRAMIN);            	//初始化内部内存池freertos_load_main_ui();        	//加载主UI//创建开始任务xTaskCreate((TaskFunction_t )start_task,            //任务函数(const char*    )"start_task",          //任务名称(uint16_t       )START_STK_SIZE,        //任务堆栈大小(void*          )NULL,                  //传递给任务函数的参数(UBaseType_t    )START_TASK_PRIO,       //任务优先级(TaskHandle_t*  )&StartTask_Handler);   //任务句柄              vTaskStartScheduler();          //开启任务调度
}

6.2.4 任务函数:

//开始任务任务函数
void start_task(void *pvParameters)
{taskENTER_CRITICAL();           //进入临界区//创建消息队列Key_Queue=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8));        //创建消息Key_QueueMessage_Queue=xQueueCreate(MESSAGE_Q_NUM,USART_REC_LEN); //创建消息Message_Queue,队列项长度是串口接收缓冲区长度//创建TASK1任务xTaskCreate((TaskFunction_t )task1_task,             (const char*    )"task1_task",           (uint16_t       )TASK1_STK_SIZE,        (void*          )NULL,                  (UBaseType_t    )TASK1_TASK_PRIO,        (TaskHandle_t*  )&Task1Task_Handler);   //创建TASK2任务xTaskCreate((TaskFunction_t )Keyprocess_task,     (const char*    )"keyprocess_task",   (uint16_t       )KEYPROCESS_STK_SIZE,(void*          )NULL,(UBaseType_t    )KEYPROCESS_TASK_PRIO,(TaskHandle_t*  )&Keyprocess_Handler); vTaskDelete(StartTask_Handler); //删除开始任务taskEXIT_CRITICAL();            //退出临界区
}//task1任务函数
void task1_task(void *pvParameters)
{u8 key,i=0;BaseType_t err;while(1){key=KEY_Scan(0);            	//扫描按键if((Key_Queue!=NULL)&&(key))   	//消息队列Key_Queue创建成功,并且按键被按下{err=xQueueSend(Key_Queue,&key,10);if(err==errQUEUE_FULL)   	//发送按键值{printf("队列Key_Queue已满,数据发送失败!\r\n");}}i++;if(i%10==0) check_msg_queue();//检Message_Queue队列的容量if(i==50){i=0;LED0=!LED0;}vTaskDelay(10);                           //延时10ms,也就是10个时钟节拍	}
}//Keyprocess_task函数
void Keyprocess_task(void *pvParameters)
{u8 num,key;while(1){if(Key_Queue!=NULL){if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//请求消息Key_Queue{switch(key){case WKUP_PRES:		//KEY_UP控制LED1LED1=!LED1;break;case KEY1_PRES:		//KEY1控制蜂鸣器BEEP=!BEEP;break;case KEY0_PRES:		//KEY0刷新LCD背景num++;LCD_Fill(126,111,233,313,lcd_discolor[num%14]);break;}}} vTaskDelay(10);      //延时10ms,也就是10个时钟节拍	}
}

1. 队列创建:

  • 首先,使用 xQueueCreate() 函数创建队列 Key_Queue,该队列的长度为1,每个队列项(消息)的长度为1个字节,用于存储按键的键值。
  • 同样地,使用 xQueueCreate() 函数创建 Message_Queue,该队列的长度为4,每个队列项的长度为 USART_REC_LEN,其中 USART_REC_LEN 定义为50字节,用于存储通过串口接收的数据。

2. 按键值发送

  • 当按键被触发并获取到相应的键值后,将调用 xQueueSend() 函数将该键值发送到 Key_Queue 中。
  • 由于 Key_Queue 只有一个队列项,因此在需要时,可以选择使用 xQueueOverwrite() 函数,这个函数会覆写已有的队列项,确保总是存储最新的键值。

3. 队列信息检查

  • 实验中,通过调用 check_msg_queue() 函数来检查 Message_Queue 的状态。该函数会获取并显示队列的相关信息,包括队列的总大小、剩余大小等,这些信息将显示在 LCD 上,方便用户实时监控队列的使用情况。

4. 消息接收与处理

  • 调用 xQueueReceive() 函数从 Key_Queue 中接收消息(按键值),将接收到的消息保存到变量 key 中。
  • 随后,根据 key 变量中的不同按键值,进行相应的操作处理,实现不同的功能响应。

6.3 中断初始化及处理过程:

本设计用到了两个中断,串口1的接收中断和定时器9的定时中断,串口1的具体配置看基础例程中的串口实验就可以了,这里要将串口中断接收缓冲区大小改为50,如下:

#define USART_REC_LEN        50        //定义最大接收字节数50
#define EN_USARTI_RX          1        //使能(1)/禁止(0)串口1接收

还要注意!由于要在中断服务函数中使用FreeRTOS中的API函数,所以一定要注意中断优先级的设置,这里设置如下:

void uart_init(u32 bound){//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟//USART1_TX   GPIOA.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9//USART1_RX	  GPIOA.10初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  //Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=7 ;//抢占优先级7NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		//子优先级0NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器//USART 初始化设置USART_InitStructure.USART_BaudRate = bound;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式USART_Init(USART1, &USART_InitStructure); //初始化串口1USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断USART_Cmd(USART1, ENABLE);                    //使能串口1 
}

这里设置抢占优先级为7,子优先级为0。这个优先级中可以调用FreeRTOS中的API函数。串口1的中断服务函数如下:

void USART1_IRQHandler(void)                	//串口1中断服务程序
{u8 Res;BaseType_t xHigherPriorityTaskWoken;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾){Res =USART_ReceiveData(USART1);	//读取接收到的数据if((USART_RX_STA&0x8000)==0)//接收未完成{if(USART_RX_STA&0x4000)//接收到了0x0d{if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始else USART_RX_STA|=0x8000;	//接收完成了 }else //还没收到0X0D{	if(Res==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  }		 }}   		 } //就向队列发送接收到的数据if((USART_RX_STA&0x8000)&&(Message_Queue!=NULL)){xQueueSendFromISR(Message_Queue,USART_RX_BUF,&xHigherPriorityTaskWoken);//向队列中发送数据USART_RX_STA=0;	memset(USART_RX_BUF,0,USART_REC_LEN);//清除数据接收缓冲区USART_RX_BUF,用于下一次数据接收portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换}
} 

1. 判断是否接收到数据

  • 在串口中断服务函数中,首先判断是否有数据接收到(例如检查接收标志位或寄存器)。
  • 如果接收到数据,则进入下一步处理。

2. 将数据发送到队列

  • 使用 xQueueSendFromISR() 函数,将串口接收缓冲区 USART_RX_BUF[] 中的数据发送到 Message_Queue 队列中。这个函数专门用于在中断服务例程(ISR)中安全地将数据发送到队列中。

3. 清零接收缓冲区

  • 数据发送到队列后,清空串口接收缓冲区 USART_RX_BUF[],以准备接收下一次数据。这通常通过将缓冲区的内容清零实现。

4. 任务调度

  • 如果需要进行任务调度,在退出串口中断服务函数之前调用 portYIELD_FROM_ISR() 函数。这个函数用于在中断上下文中安全地切换任务,确保实时系统的任务调度保持及时和高效。

在定时2的中断服务函数中请求队列Message Queue中的数据并将请求到的消息显示在LCD上,定时器2的定时周期设置为500s,定时器初始化很简单,唯一要注意的就是中断优先级的设置,如下:

	//中断优先级NVIC设置NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级4级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //从优先级0级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器

定时器2 的中断服务函数是重点:

extern QueueHandle_t Message_Queue;	//信息队列句柄
extern void disp_str(u8* str);//定时器2中断服务函数
void TIM2_IRQHandler(void)
{u8 *buffer;BaseType_t xTaskWokenByReceive=pdFALSE;BaseType_t err;if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //溢出中断{buffer=mymalloc(SRAMIN,USART_REC_LEN);if(Message_Queue!=NULL){memset(buffer,0,USART_REC_LEN);	//清除缓冲区err=xQueueReceiveFromISR(Message_Queue,buffer,&xTaskWokenByReceive);//请求消息Message_Queueif(err==pdTRUE)			//接收到消息{disp_str(buffer);	//在LCD上显示接收到的消息}}myfree(SRAMIN,buffer);		//释放内存portYIELD_FROM_ISR(xTaskWokenByReceive);//如果需要的话进行一次任务切换}TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //清除中断标志位
}

1. 准备数据缓冲区

  • 在从队列中获取消息之前,需要先准备一个数据缓冲区来保存消息。可以通过动态内存管理的方式(例如ALIENTEK编写的内存管理方法或FreeRTOS提供的动态内存管理函数)来分配这个缓冲区。也可以直接定义一个数组来作为缓冲区。无论哪种方式,数据缓冲区的大小必须与队列项的大小一致(在本例程中是 USART_REC_LEN)。

2. 清除缓冲区

  • 在将数据从队列复制到缓冲区之前,需要先清空缓冲区,以确保不包含旧数据,避免干扰新接收的数据处理。

3. 从队列获取消息

  • 使用 xQueueReceiveFromISR() 函数从 Message_Queue 队列中获取消息。这个函数在中断服务例程中使用,可以安全地从队列中获取数据。

4. 显示消息到LCD

  • 如果成功获取消息,则调用 disp_str() 函数将消息显示在LCD上。这一步是将从队列中获取的消息进行实际显示的操作。

5. 释放缓冲区内存

  • 在使用完动态分配的缓冲区之后,应该立即释放该内存,以防止内存泄漏。对于使用动态内存管理分配的缓冲区,可以使用 free() 函数释放内存。

6. 任务调度

  • 在退出定时器中断服务函数之前,调用 portYIELD_FROM_ISR() 函数进行一次任务调度(如果需要)。这可以确保系统根据优先级及时切换任务,保持实时性能。

七、运行结果:

图7-1 LCD默认显示 

通过串口调试助手给开发板发送一串字符串,比如“ALIENTEK”,由于定时器9会周期性的读取队列Message Queue中的数据,当读取成功以后就会将相应的数据显示在LCD上,所以LCD上会显示字符串“ALENTEK”,如图7-2 所示:

图7-1 显示队列中消息

通过串口向开发板发送数据的时候注意观察队列Message Queue剩余大小的变化,最后按不同的按键看看有什么反应,是否和我们的代码中设置的相同。

📝大佬觉得本文有所裨益,不妨轻点一下👍给予鼓励吧!

❤️❤️❤️本人虽努力,但能力尚浅,若有不足之处,恳请各位大佬不吝赐教,您的批评指正将是我进步的动力!😊😊😊

💖💖💖若您认为此篇文章对您有所帮助,烦请点赞👍并收藏🌟,您的支持是我前行的最大动力!

🚀🚀🚀任务在默默中完成,价值在悄然间提升。让我们携手共进,一起加油,迎接更美好的未来!🌈🌈🌈

这篇关于FreeRTOS基础入门——FreeRTOS队列及其API函数介绍(十二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1131373

相关文章

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.

Redis延迟队列的实现示例

《Redis延迟队列的实现示例》Redis延迟队列是一种使用Redis实现的消息队列,本文主要介绍了Redis延迟队列的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习... 目录一、什么是 Redis 延迟队列二、实现原理三、Java 代码示例四、注意事项五、使用 Redi

Python实现NLP的完整流程介绍

《Python实现NLP的完整流程介绍》这篇文章主要为大家详细介绍了Python实现NLP的完整流程,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 编程安装和导入必要的库2. 文本数据准备3. 文本预处理3.1 小写化3.2 分词(Tokenizatio

使用SpringBoot创建一个RESTful API的详细步骤

《使用SpringBoot创建一个RESTfulAPI的详细步骤》使用Java的SpringBoot创建RESTfulAPI可以满足多种开发场景,它提供了快速开发、易于配置、可扩展、可维护的优点,尤... 目录一、创建 Spring Boot 项目二、创建控制器类(Controller Class)三、运行

MySQL中my.ini文件的基础配置和优化配置方式

《MySQL中my.ini文件的基础配置和优化配置方式》文章讨论了数据库异步同步的优化思路,包括三个主要方面:幂等性、时序和延迟,作者还分享了MySQL配置文件的优化经验,并鼓励读者提供支持... 目录mysql my.ini文件的配置和优化配置优化思路MySQL配置文件优化总结MySQL my.ini文件

Oracle的to_date()函数详解

《Oracle的to_date()函数详解》Oracle的to_date()函数用于日期格式转换,需要注意Oracle中不区分大小写的MM和mm格式代码,应使用mi代替分钟,此外,Oracle还支持毫... 目录oracle的to_date()函数一.在使用Oracle的to_date函数来做日期转换二.日

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,