个人名片:
🎓作者简介:嵌入式领域优质创作者
🌐个人主页:妄北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基础知识的实践,做项目是最锻炼能力的一个学习方法,这里我们会学习到一些简单基础的项目开发与应用,而且都是毕业设计级别的哦。🤸🌱🚀
非常期待与您一同在这个广阔的互联网天地里,携手探索知识的海洋,互相学习,共同进步。🌐💫🌱 熠熠星光,照亮我们的成长之路
✨✨ 欢迎订阅本专栏,对专栏内容任何问题都可以随时联系博主,共同书写属于我们的精彩篇章!✨✨
文章介绍:
📚本篇文章将深入剖析FreeRTOS学习的精髓与奥秘,与您一同分享相关知识!🎉🎉🎉
若您觉得文章尚可入目,期待您能慷慨地送上点赞、收藏与分享的三连支持!您的每一份鼓励,都是我创作路上源源不断的动力。让我们携手并进,共同奔跑,期待在顶峰相见的那一天,共庆辉煌!🚀🚀🚀
🙏衷心感谢大家的点赞👍、收藏⭐和评论✍️,您的支持是我前进的动力!
目录:
目录
目录:
一、FreeRTOS任务特性:
1.1 简单:
1.2 没有使用限制:
1.3 支持抢占:
1.4 支持优先级:
1.5 每个任务都拥有堆栈导致了RAM使用量增大:
1.6 如果使用抢占的话的必须仔细的考虑重入的问题:
二、FreeRTOS任务状态:
2.1 运行态(Running):
2.2 就绪态(Ready):
2.3 阻塞态(Blocked):
2.4 挂起态(Suspended):
三、任务优先级
3.1 优先级范围:
3.2 优先级和硬件支持:
3.3 优先级的含义:
3.4 任务调度:
3.5 时间片轮转调度:
3.6 小结:
四、任务实现:
4.1 任务函数的概念
4.2 任务函数的结构
4.3 任务函数详细执行过程
五、任务控制块:
六、任务堆栈
6.1 FreeRTOS任务堆栈管理
6.2 创建任务
1. 动态方法:xTaskCreate():
2. 静态方法:xTaskCreateStatic()
6.3 堆栈大小
一、FreeRTOS任务特性:
在使用实时操作系统(RTOS)时,可以将实时应用作为一个独立的任务来运行。每个任务都有自己的运行环境,包括堆栈空间、寄存器状态等,这些环境是独立的,不依赖于系统中的其他任务或RTOS调度器。
RTOS的核心功能之一是任务调度,它负责决定在任何给定的时间点上运行哪个任务。由于在任何时刻只能有一个任务在运行,RTOS调度器需要不断地在任务之间进行切换,即开启一个任务并关闭其他任务。这种切换过程称为任务切换(task switching)或上下文切换(context switching)。
在任务切换过程中,RTOS调度器必须确保当前运行的任务的上下文环境(包括寄存器值、程序计数器、堆栈指针等)被正确地保存,以便在任务再次被调度运行时能够恢复到之前的状态。这是通过将当前任务的上下文信息保存到该任务的堆栈中来实现的。当任务被重新调度时,RTOS调度器会从堆栈中恢复这些上下文信息,从而使任务能够从上次中断的地方继续执行。
任务本身不需要了解RTOS调度器的具体行为,它们只需要按照RTOS提供的API进行编程,例如创建任务、挂起任务、恢复任务等。RTOS调度器负责管理任务的执行顺序和时间,确保系统的实时性和任务的正确执行。
1.1 简单:
FreeRTOS设计简洁,容易理解和使用。它提供了一个基本但功能强大的API,用于创建和管理任务、队列、信号量和其他实时操作系统(RTOS)功能。
1.2 没有使用限制:
FreeRTOS是开源的,不存在使用上的限制。开发人员可以自由地在各种项目中使用和修改FreeRTOS代码,适用于商业和非商业用途。
1.3 支持抢占:
FreeRTOS支持基于优先级的抢占式调度。高优先级任务可以打断低优先级任务的执行,确保关键任务能及时运行。
1.4 支持优先级:
FreeRTOS允许为每个任务分配优先级。任务的调度基于其优先级,高优先级任务获得更多的CPU时间。
1.5 每个任务都拥有堆栈导致了RAM使用量增大:
每个任务在创建时分配一个独立的堆栈空间。这确保了任务之间的内存隔离,但也会增加总的RAM消耗,尤其是在任务数量较多的情况下。
1.6 如果使用抢占的话的必须仔细的考虑重入的问题:
在抢占式调度的环境中,任务可能会在任意时刻被打断,这可能会导致重入问题。开发人员需要确保代码是线程安全的,特别是在访问共享资源时,常用的技术包括使用信号量、互斥锁等机制。
这些特性使FreeRTOS成为一个灵活且广泛应用的实时操作系统,适用于从简单的嵌入式系统到复杂的工业和消费类设备。
二、FreeRTOS任务状态:
2.1 运行态(Running):
任务处于运行态时,表示它当前正在被处理器执行。
在单核处理器系统中,任意时刻只有一个任务可以处于运行态。
2.2 就绪态(Ready):
任务已经准备好执行,但由于处理器正被其他更高优先级或同优先级的任务占用,因此还没有被执行。
一旦处理器空闲或者当前运行的任务被阻塞或挂起,就绪态的任务有机会进入运行态。
2.3 阻塞态(Blocked):
任务在等待某些特定事件(如I/O操作完成、信号量、互斥量等)时进入阻塞态。
阻塞态任务在等待的事件发生或者超时时间到达时会转换为就绪态。
2.4 挂起态(Suspended):
任务在挂起态时不会被调度器调度,类似于阻塞态,但没有超时时间限制。
任务通过调用vTaskSuspend()
进入挂起态,通过调用xTaskResume()
退出挂起态。
- 就绪态 → 运行态:如果调度器选择就绪态的任务运行。
- 运行态 → 阻塞态:任务等待某个事件或资源。
- 运行态 → 就绪态:任务在时间片用完后被调度器切换出。
- 阻塞态 → 就绪态:等待的事件发生或超时。
- 运行态 → 挂起态:任务调用了
vTaskSuspend()
。 - 挂起态 → 就绪态:任务调用了
xTaskResume()
。
三、任务优先级
3.1 优先级范围:
每个任务在FreeRTOS中可以分配一个优先级,范围是从 0
到 configMAX_PRIORITIES - 1
。
configMAX_PRIORITIES
是在 FreeRTOSConfig.h
文件中定义的。
3.2 优先级和硬件支持:
在一些硬件平台上,如果支持“计算前导零”指令(如Cortex-M处理器),并且宏 configUSE_PORT_OPTIMISED_TASK_SELECTION
设置为 1
,那么 configMAX_PRIORITIES
不能超过 32
。这是因为这些硬件优化指令只能处理32个优先级以内的任务。
在其他情况下,configMAX_PRIORITIES
可以为任意值。但为了优化RAM的使用,建议设置为应用所需的最小值。
3.3 优先级的含义:
优先级数字越低表示任务的优先级越低。优先级为 0
是最低优先级,configMAX_PRIORITIES - 1
是最高优先级。
系统的空闲任务优先级为 0
,这是最低优先级。
3.4 任务调度:
FreeRTOS调度器确保高优先级的任务获取处理器使用权。只有处于就绪态的最高优先级任务会运行。
3.5 时间片轮转调度:
如果宏 configUSE_TIME_SLICING
定义为 1
(默认情况下它在 FreeRTOS.h
文件中定义为 1
),多个任务可以共享同一个优先级。
在这种情况下,具有相同优先级的就绪态任务会使用时间片轮转调度器进行调度,从而分享处理器时间。
3.6 小结:
1. 优先级设置:优先级从 0
到 configMAX_PRIORITIES - 1
,0
最低,configMAX_PRIORITIES - 1
最高。
2. 硬件和优化:在支持硬件优化指令的情况下,优先级不能超过32。
3. 调度机制:FreeRTOS使用优先级调度,高优先级任务优先运行。如果 configUSE_TIME_SLICING
为 1
,那么相同优先级的任务会进行时间片轮转调度。
四、任务实现:
在FreeRTOS中,任务是操作系统的基本执行单元,每个任务实际上就是一个独立的执行流。为了创建一个任务,你需要定义一个任务函数,这个函数包含了该任务需要完成的所有工作。
4.1 任务函数的概念
1. 工作单元:任务函数是一个独立的工作单元,它定义了该任务需要完成的具体操作。比如在流水灯的例子中,任务函数将包含点亮和熄灭LED灯的逻辑。
2. 循环执行:任务函数通常包含一个无限循环,这样任务一旦启动就会在这个循环中不断地运行,直到被显式删除或者系统重启。这个循环体内会有任务所需的操作,比如读取传感器数据、处理通信、控制设备等。
3. 调度管理:FreeRTOS的调度器会管理这些任务函数的执行,确保每个任务在适当的时间获得CPU资源执行。任务函数本身不需要担心调度的细节,只需要专注于其核心工作。
4.2 任务函数的结构
1. 初始化部分:在循环开始前,任务函数通常会进行一些初始化操作,比如设置初始状态、配置硬件资源等。
2. 无限循环部分:这是任务函数的主体,包含任务的主要逻辑。每个任务函数通常都会有一个无限循环,在这个循环中执行任务的核心操作。
3. 任务延时:为了避免占用过多的CPU资源,任务函数通常会在每次循环中调用延时函数,使任务进入阻塞状态一段时间,等待下一次执行。这通常是通过FreeRTOS提供的延时函数实现的。
// 任务函数
void vATaskFunction(void *pvParameters)
{for(;;) // 无限循环{// 任务应用程序// 在这里添加你的任务代码vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1秒(1000毫秒)/* 不能从任务函数中返回或者退出,从任务函数中返回或退出的话就会调用configASSERT,前提是你定义了configASSERT。如果一定要从任务函数中退出的话,那一定要调用函数vTaskDelete(NULL)来删除此任务。*/}// 下面的代码通常不会被执行,因为任务函数是一个无限循环。// 如果确实需要退出任务,可以通过调用vTaskDelete(NULL)来删除此任务。vTaskDelete(NULL);
}
4.3 任务函数详细执行过程
1. 任务函数的定义:任务函数是FreeRTOS任务的核心部分,具有以下特点:
- 返回类型: 一定是
void
类型,即无返回值。 - 参数: 只能是
void*
类型的指针,通常用来传递参数给任务。 - 任务名: 可以根据实际情况自定义任务函数名。
2. 任务的执行过程:
- 任务的执行过程通常是一个无限循环。常见的方式是使用
for(;;)
或while(1)
来实现。
3. 任务代码:
- 循环内的代码就是具体任务需要执行的操作。
- 这部分代码可以包含各种逻辑操作、数据处理、外设控制等。
4. 任务调度和延时:
- FreeRTOS提供了多种方式来实现任务调度,其中最常用的是延时函数,如
vTaskDelay
。 - 其他能引发任务切换的API函数也可以使用,比如请求信号量、队列操作等。
5. 任务的删除:
- 任务函数一般不允许跳出循环。如果必须跳出循环,则需要调用
vTaskDelete(NULL)
来删除此任务。
- 删除任务是为了释放资源,防止任务继续执行。
FreeRTOS的任务函数结构是非常严谨而规范的,它为实时操作系统提供了灵活而强大的任务管理功能。
五、任务控制块:
FreeRTOS的每个任务都有一些属性需要存储,这些属性被集合在一个结构体中,这个结构体被称为任务控制块(Task Control Block, TCB)。在创建任务时,通过调用 xTaskCreate()
函数,FreeRTOS 会自动为每个任务分配一个 TCB。
在较老版本的 FreeRTOS 中,这个任务控制块结构体被命名为 tskTCB
,而在新版本中重命名为 TCB_t
。虽然名称有所更改,但它们在本质上是相同的。这个结构体在文件 tasks.c
中定义。
换句话说,任务控制块(TCB_t)是 FreeRTOS 中用于管理任务的核心数据结构,它包含了任务执行所需的各种信息。在创建任务时,FreeRTOS 会自动初始化并分配这个结构体,以管理任务的生命周期和状态。
typedef struct tskTaskControlBlock
{volatile StackType_t *pxTopOfStack; /*< 指向任务堆栈上最后一个放置的项的位置。这必须是TCB结构体的第一个成员。 */#if ( portUSING_MPU_WRAPPERS == 1 )xMPU_SETTINGS xMPUSettings; /*< MPU设置作为端口层的一部分定义。这必须是TCB结构体的第二个成员。 */#endifListItem_t xStateListItem; /*< 任务的状态列表项引用的列表表示该任务的状态(就绪、阻塞、挂起)。 */ListItem_t xEventListItem; /*< 用于从事件列表引用任务。 */UBaseType_t uxPriority; /*< 任务的优先级。0 是最低优先级。 */StackType_t *pxStack; /*< 指向堆栈的起始位置。 */char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< 创建任务时给出的描述性名称。仅便于调试。 */ /*lint !e971 未限定的char类型仅允许用于字符串和单个字符。 */#if ( portSTACK_GROWTH > 0 )StackType_t *pxEndOfStack; /*< 在堆栈从低内存向上增长架构中,指向堆栈的末端。 */#endif#if ( portCRITICAL_NESTING_IN_TCB == 1 )UBaseType_t uxCriticalNesting; /*< 对于不在端口层维护自身计数的端口,保存临界区嵌套深度。 */#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxTCBNumber; /*< 存储一个每次创建TCB时递增的数字。它允许调试器确定任务何时被删除然后重新创建。 */UBaseType_t uxTaskNumber; /*< 存储一个专门供第三方跟踪代码使用的数字。 */#endif#if ( configUSE_MUTEXES == 1 )UBaseType_t uxBasePriority; /*< 最近分配给任务的优先级 - 用于优先级继承机制。 */UBaseType_t uxMutexesHeld;#endif#if ( configUSE_APPLICATION_TASK_TAG == 1 )TaskHookFunction_t pxTaskTag;#endif#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];#endif#if( configGENERATE_RUN_TIME_STATS == 1 )uint32_t ulRunTimeCounter; /*< 存储任务处于运行状态的时间量。 */#endif#if ( configUSE_NEWLIB_REENTRANT == 1 )/* 为该任务分配一个特定的新lib重入结构。注意:新lib支持是应大众需求而包含的,但FreeRTOS维护者自己并不使用。FreeRTOS不负责新lib的操作。用户必须熟悉新lib,并提供系统范围内必要的存根实现。请注意(在撰写时)当前新lib设计实现了一个需要锁定的系统范围malloc()。 */struct _reent xNewLib_reent;#endif#if( configUSE_TASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue;volatile uint8_t ucNotifyState;#endif/* 参见tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE定义上方的注释。 */#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )uint8_t ucStaticallyAllocated; /*< 如果任务是静态分配的,则设置为pdTRUE,以确保不会尝试释放内存。 */#endif#if( INCLUDE_xTaskAbortDelay == 1 )uint8_t ucDelayAborted;#endif} tskTCB;/* 旧的tskTCB名称在上方保持不变,然后typedef为新的TCB_t名称,以启用使用较旧的内核感知调试器。 */
typedef tskTCB TCB_t;
可以看出来FreeRTOS的任务控制块中的成员变量相比UCOSⅢ要少很多,而且大多数与
裁剪有关,当不使用某些功能的时候与其相关的变量就不参与编译,任务控制块大小就会进一
步的减小。
六、任务堆栈
6.1 FreeRTOS任务堆栈管理
在FreeRTOS中,每个任务都有自己独立的堆栈空间,用来保存任务的上下文(包括CPU寄存器值等)。当任务被切换(即任务调度)时,当前任务的上下文会被保存在它自己的堆栈中。等到该任务再次运行时,它的上下文会从堆栈中恢复,从而任务可以继续执行之前中断的地方。这种机制是通过任务堆栈来实现的。
6.2 创建任务
FreeRTOS提供了两种创建任务的方法:动态方法和静态方法。
1. 动态方法:xTaskCreate():
xTaskCreate
函数使用动态内存分配来创建任务,包括任务堆栈和任务控制块(TCB)。
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,const char * const pcName,configSTACK_DEPTH_TYPE usStackDepth,void *pvParameters,UBaseType_t uxPriority,TaskHandle_t *pxCreatedTask
);
pxTaskCode
:任务函数。pcName
:任务名称。usStackDepth
:任务堆栈深度(即堆栈大小)。pvParameters
:传递给任务函数的参数。uxPriority
:任务优先级。pxCreatedTask
:任务句柄。
这个函数会自动分配和管理任务堆栈,用户不用自己定义堆栈。
2. 静态方法:xTaskCreateStatic()
xTaskCreateStatic
函数使用静态内存来创建任务,这意味着用户需要自己提供任务堆栈和任务控制块(TCB)。
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,const char * const pcName,configSTACK_DEPTH_TYPE usStackDepth,void *pvParameters,UBaseType_t uxPriority,TaskHandle_t *pxCreatedTask
);
每个参数的含义与xTaskCreate
基本相同,不同的是:
puxStackBuffer
:指向用户定义的任务堆栈缓冲区。pxTaskBuffer
:指向用户定义的静态任务控制块(TCB)。
6.3 堆栈大小
在FreeRTOS中,任务堆栈的大小是以字节为单位指定的,而堆栈的数据类型 StackType_t
通常被定义为 uint32_t
,这意味着每个堆栈单元占用4个字节。因此,当你在创建任务时指定堆栈大小(以 StackType_t
为单位),实际的堆栈大小(以字节为单位)确实是所指定大小的4倍。
例如,如果你使用 xTaskCreate()
或 xTaskCreateStatic()
函数并指定堆栈大小为 100
,那么实际的堆栈大小将是 100 * 4 = 400
字节。
任务堆栈用来保存任务现场(CPU寄存器值),创建任务的时候需要指定任务堆栈,任务堆栈的变量类型为StackType_t,此变量类型如下:
#define portSTACK_TYPE uint32_t
#define portBASE_TYPE longtypedef portSTACK_TYPE StackType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
#define portSTACK_TYPE uint32_t
定义了一个宏portSTACK_TYPE
,表示uint32_t
类型。#define portBASE_TYPE long
定义了一个宏portBASE_TYPE
,表示long
类型。typedef portSTACK_TYPE StackType_t;
使用typedef
将portSTACK_TYPE
定义为StackType_t
类型。typedef long BaseType_t;
使用typedef
将long
定义为BaseType_t
类型。typedef unsigned long UBaseType_t;
使用typedef
将unsigned long
定义为UBaseType_t
类型。
📝大佬觉得本文有所裨益,不妨轻点一下👍给予鼓励吧!
❤️❤️❤️本人虽努力,但能力尚浅,若有不足之处,恳请各位大佬不吝赐教,您的批评指正将是我进步的动力!😊😊😊
💖💖💖若您认为此篇文章对您有所帮助,烦请点赞👍并收藏🌟,您的支持是我前行的最大动力!
🚀🚀🚀任务在默默中完成,价值在悄然间提升。让我们携手共进,一起加油,迎接更美好的未来!🌈🌈🌈