STM32---FreeRTOS项目
FreeRTOS是一个迷你的实时操作系统内核,以其源码公开、可移植、可裁减、调度策略灵活等特点,广泛应用于嵌入式系统中。以下是关于FreeRTOS的基础知识概述:
前置准备
FreeRTOS通过STM32CubeMX移植
1.选择一个定时器作为系统(HAL库)的时基, 选择滴答定时器作为RTOS的时基 /*目的是 ;为了可以识别到调试器*/ 2.使能FREERTOS
一、功能和特点
- 任务管理:FreeRTOS支持多任务管理,包括任务的创建、删除、优先级设置等。 - 时间管理:提供时间测量和滴答中断功能,用于任务调度和延时操作。 - 内存管理:提供多种堆内存分配示例,支持动态内存分配。 - 通信和同步:包括队列、信号量、事件组、互斥量等机制,用于任务间的通信和同步。 - 软件定时器:支持软件定时器的创建和管理,可用于实现定时任务。 - 可配置性:用户可以根据需要配置内核功能,如任务数量、优先级数量等。 - 多平台支持:FreeRTOS可以在多种编译器和处理器架构上运行,具有良好的可移植性。
二、任务状态
FreeRTOS中的任务永远处于以下四个状态中的某一个:
1. 运行态:任务正在执行,占用CPU。 2. 就绪态:任务已经准备好执行,但由于优先级或其他任务正在运行而暂时无法执行。 3. 阻塞态:任务因为等待某个事件(如信号量、消息队列等)而暂停执行。 4. 挂起态:任务被显式挂起,不会被调度器调用执行。
三、FreeRTOS的优势
以下是以表格方式呈现的FreeRTOS相对于其他实时操作系统的优势:
特性/优势 | FreeRTOS 描述 |
---|---|
开源免费 | 完全开源且免费使用,适用于各种规模的项目,无需支付额外许可费用。 |
轻量级 | 内核小,资源占用少,适合在资源受限的嵌入式系统上运行。 |
可裁剪性 | 允许根据项目需求裁剪功能,优化资源使用。 |
多平台支持 | 支持广泛的硬件平台,易于移植到不同处理器架构。 |
调度策略灵活 | 支持抢占式、合作式和时间片调度等多种调度策略。 |
中间件和钩子函数 | 提供消息队列、信号量、互斥量、事件组等中间件,以及多种钩子函数,增强系统扩展性和调试能力。 |
低功耗设计 | 包含Tickless模式,适用于低功耗应用场景。 |
任务管理 | 支持无限制的任务数量和优先级数量,允许任务共享优先级。 |
易于学习和使用 | 丰富的文档和社区支持,帮助新用户快速上手。 |
商业支持 | 提供免费论坛技术支持和可选的商业支持,满足不同用户需求。 |
内存分配策略 | 提供静态和动态内存分配策略,适应不同的应用场景。 |
高性能 | 在任务切换速度等性能测试中表现出竞争力。 |
社区和论坛 | 活跃的社区和论坛,提供技术支持和交流平台。 |
教育和培训资源 | 大量的教学资源和教程,便于学习和教育目的。 |
跨编译器支持 | 与多种编译器兼容,包括GCC、Keil、IAR等。 |
可扩展性 | 能够通过添加第三方组件来扩展功能,如文件系统、TCP/IP协议栈等。 |
这个表格总结了FreeRTOS的一些关键优势,这些优势使其在实时操作系统领域中非常受欢迎。
四、任务调度机制
调度器是实时操作系统(RTOS)的一个关键组件,它负责在多个可运行任务中决定哪一个将获得CPU时间以执行。在FreeRTOS这样的RTOS中,调度器的作用尤为重要,因为它需要确保任务能够按照预定的优先级和调度策略高效运行。以下是对FreeRTOS中调度器的详细解释:
1.调度器的基本功能
调度器的主要功能包括:
1. 任务管理:负责创建、删除和调度任务。 2. 优先级管理:根据任务的优先级来决定任务的执行顺序。 3. 上下文切换:在任务之间切换时保存和恢复任务的上下文信息,以便任务能够继续执行。 4. 时间管理:处理与时间相关的任务调度,如时间片轮转调度。
2.FreeRTOS中的调度器类型
FreeRTOS提供了几种不同的调度策略,主要包括:
1. 抢占式调度: -抢占式调度器是一种基于优先级的调度器,它始终执行当前就绪态中优先级最高的任务。 -当一个更高优先级的任务准备就绪时,它会抢占当前正在执行的低优先级任务,从而获得CPU的控制权。 -这种调度策略可以确保高优先级任务能够及时得到响应,从而满足实时系统的需求。 2. 时间片调度 : -时间片轮转调度器是一种特殊的抢占式调度器,它将CPU时间划分成固定长度的时间片。 -每个任务在一个时间片内运行,当时间片结束时,如果任务还未完成,则会被挂起,并将CPU的控制权转交给下一个任务。 -这种调度策略可以确保每个任务都有机会执行,从而避免某个任务长时间占用CPU资源。 3. 协作式调度(注意:FreeRTOS官方已明确表示不更新此类型调度器): -协作式调度器依赖于任务自行释放CPU控制权。 -任务之间没有明确的优先级概念,任务必须自愿放弃CPU以便其他任务能够运行。 -由于这种调度策略缺乏抢占机制,因此可能不适用于需要高实时性的系统。
3.调度器的启动和停止
1. 启动调度器: - 在FreeRTOS中,通常通过调用`vTaskStartScheduler()`函数来启动任务调度器。 - 在启动调度器之前,需要创建至少一个任务(通常是空闲任务),并配置好任务优先级和堆栈大小等参数。 - 一旦调度器启动,FreeRTOS就会开始按照调度策略执行任务调度。 2. 停止调度器: - 在FreeRTOS中,没有直接停止调度器的API函数。 - 如果需要停止任务调度,通常需要通过删除所有任务或重置系统来实现。
4.、总结
FreeRTOS中的调度器是确保任务能够高效、有序执行的关键组件。通过提供抢占式调度和时间片轮转调度等策略,FreeRTOS能够满足不同实时系统的需求。同时,调度器的实现也涉及多个方面,需要仔细设计和实现以确保系统的稳定性和可靠性。
FreeRTOS采用基于优先级的调度策略,支持可剥夺型内核。优先级高的任务一旦就绪就能剥夺优先级较低任务的CPU使用权,从而提高了系统的实时响应能力。对于相同优先级的任务,FreeRTOS支持轮换调度算法,确保任务能够轮流执
五、中断管理
FreeRTOS提供了中断配置宏,用于设置中断优先级和内核中断优先级等。这些配置宏确保了FreeRTOS系统能够正确地管理中断,并避免中断服务程序中的FreeRTOS API调用导致系统紊乱。
六、源码结构和文件
FreeRTOS的源码结构清晰,主要包括核心源码文件和特定于端口的源码文件。核心源码文件(如tasks.c、list.c等)包含了FreeRTOS的核心功能实现,而特定于端口的源码文件则包含了与具体硬件平台相关的代码。
下面是一个简化的表格,用于说明FreeRTOS中一些主要文件及其大致功能:
文件名 | 描述 |
---|---|
tasks.c | 包含任务管理的核心代码,如任务的创建、删除、调度等。 |
list.c | 实现双向链表数据结构,用于任务、队列等内部管理的需要。 |
queue.c | 实现队列功能,用于任务间的通信,是一种先进先出(FIFO)的数据结构。 |
croutine.c | 提供协程(co-routine)的支持,一种轻量级的并发执行体。 |
event_groups.c | 实现事件标志组功能,允许任务与多个事件或任务进行同步。 |
time.c | 实现软件定时器功能,允许用户设置定时任务。 |
heap_x.c (x=1,2,3,4,5) | 不同的内存管理方案实现,x代表不同的内存分配策略。 |
FreeRTOSConfig.h | FreeRTOS的配置文件,用于对FreeRTOS进行功能配置和裁剪。 |
FreeRTOS.h | 包含FreeRTOS的许多默认宏定义、数据类型定义和接口函数定义等。 |
projdefs.h | 包含FreeRTOS中的通用定义,如错误编号宏定义、逻辑值宏定义等。 |
stack_macros.h | 定义了进行栈溢出检查的函数,通常根据需要进行配置。 |
portmacro.h | 重新定义了一些数据类型的符号,以适应不同编译器和硬件平台的需求。 |
portable/目录下文件 | 包含了针对特定处理器体系结构的移植层代码,使FreeRTOS能够运行在不同硬件平台上。 |
请注意,heap_x.c
中的x
是一个占位符,实际上FreeRTOS可能提供了多种内存管理策略,如heap_1.c
、heap_2.c
等,每种策略都有其特点和适用场景。同样,portable/目录下文件
也是一个泛指,具体文件名和目录结构会根据目标硬件平台的不同而有所变化。
此外,表格中的描述是基于FreeRTOS的一般功能进行的概括,具体细节可能会根据FreeRTOS的版本和具体实现有所不同,以下是对重要代码的说明
七、开发和应用
在开发FreeRTOS应用时,通常需要完成以下步骤:
1. 移植FreeRTOS:将FreeRTOS源码移植到目标硬件平台上。 2. 配置FreeRTOS:通过修改FreeRTOS配置头文件(如FreeRTOSConfig.h)来配置内核功能。 /*这里我们是通过STM32CubeMX完成的移植*/ 3. 创建任务:使用FreeRTOS提供的API函数创建任务,并设置任务的优先级和堆栈大小等参数。 4. 实现任务函数:编写任务函数,实现具体的功能逻辑。 5. 任务调度和管理:通过FreeRTOS的调度机制管理任务的执行和通信
动态任务的创建与删除
Ⅰ、动态任务的创建
1. 函数原型 -->>FreeRTOS中动态创建任务的函数是xTaskCreate
,其函数原型如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 任务函数指针 const char * const pcName, // 任务名称 const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小(以字为单位) void * const pvParameters, // 传递给任务函数的参数 UBaseType_t uxPriority, // 任务优先级 TaskHandle_t * const pxCreatedTask // 指向任务句柄的指针,用于获取任务句柄 );
2. 创建步骤
- 配置任务参数:包括任务名称、栈大小、优先级等。 - 调用xTaskCreate函数:将任务函数指针、任务名称、栈大小等参数传递给`xTaskCreate`函数。 - 获取任务句柄:如果任务创建成功,`xTaskCreate`函数将返回`pdPASS`,并通过`pxCreatedTask`参数返回任务句柄。
3. 注意事项
- 在调用`xTaskCreate`之前,需要确保FreeRTOS的配置文件(如`FreeRTOSConfig.h`)中的`configSUPPORT_DYNAMIC_ALLOCATION`宏被设置为1,以支持动态内存分配。 - 任务的栈大小需要根据任务的实际需求来设置,过大或过小的栈大小都可能导致问题。 - 任务名称主要用于调试目的,帮助开发者在调试时识别任务。
Ⅱ、动态任务的删除
1. 函数原型-->>FreeRTOS中删除任务的函数是vTaskDelete
,其函数原型如下:
void vTaskDelete( TaskHandle_t xTaskToDelete );
2. 删除步骤
- 获取任务句柄:确保在调用`vTaskDelete`之前,已经通过`xTaskCreate`或其他方式获取了要删除任务的任务句柄。 - 调用vTaskDelete函数:将要删除任务的任务句柄作为参数传递给`vTaskDelete`函数。
3. 注意事项
- 只能删除已经被创建的任务。 - 如果传入`vTaskDelete`的参数为`NULL`,则代表删除当前正在运行的任务(即“自杀”)。 - 在删除任务之前,需要确保该任务已经完成了所有必要的清理工作,以避免资源泄露或其他问题。
总结
FreeRTOS动态任务的创建与删除是通过调用xTaskCreate
和vTaskDelete
两个API函数来实现的。在创建任务时,需要配置任务的各种参数,并确保FreeRTOS支持动态内存分配。在删除任务时,需要确保已经获取了要删除任务的任务句柄,并注意避免资源泄露或其他问题。通过合理地创建和删除任务,可以实现多任务协同工作,提高系统的实时性和并发性。
静态任务的创建与删除
FreeRTOS静态任务的创建与删除涉及特定的API函数和配置步骤,以下是详细的解释:
Ⅰ、静态任务的创建
1. 系统配置
在`FreeRTOSConfig.h`文件中,需要将`configSUPPORT_STATIC_ALLOCATION`宏定义为1,以支持静态内存分配。如果不定义此宏,它可能会默认为1,但明确设置可以避免潜在的混淆。
2. 定义任务堆栈和任务控制块
静态任务需要用户自己定义任务的堆栈和任务控制块。堆栈用于存储任务运行时的上下文信息,任务控制块(TCB)则用于存储任务的状态和控制信息。
示例代码:
// 定义任务堆栈 StackType_t TaskStack[configMINIMAL_STACK_SIZE]; // 定义任务控制块 StaticTask_t TaskTCB;
3. 创建任务
使用
xTaskCreateStatic
函数创建静态任务。该函数需要传递任务的堆栈、任务控制块等参数。函数原型:
BaseType_t xTaskCreateStatic( TaskFunction_t pxTaskCode, // 任务函数指针 const char * const pcName, // 任务名称 const uint32_t ulStackDepth, // 栈深度(以字为单位) void * const pvParameters, // 传递给任务函数的参数 UBaseType_t uxPriority, // 任务优先级 StackType_t * const puxStackBuffer, // 指向任务堆栈的指针 StaticTask_t * const pxTaskBuffer // 指向任务控制块的指针 );
4. 注意事项
确保堆栈大小足够大,以满足任务的需求。
任务控制块和堆栈需要在全局或静态存储区中定义,以确保它们在任务的生命周期内保持有效。
Ⅱ、静态任务的删除
1. 函数原型
使用
vTaskDelete
函数删除任务,无论是静态创建的任务还是动态创建的任务。函数原型: void vTaskDelete( TaskHandle_t xTaskToDelete );
2. 删除步骤
- 获取要删除任务的任务句柄。 - 调用`vTaskDelete`函数,并传入要删除任务的任务句柄。
3. 注意事项
- 空闲任务负责从已删除的任务中释放RTOS内核分配的内存。因此,如果您的应用程序调用`vTaskDelete()`,那么空闲任务需要处理器的时间是很重要的。 - 由任务代码分配的内存不会自动释放,应该在删除任务之前由任务代码释放。 - 任务的删除操作通常需要在另一个任务中进行,因为正在执行的任务不能被自己删除(除非传入`vTaskDelete`的参数为`NULL`,即删除当前任务)。
总结
FreeRTOS静态任务的创建与删除涉及系统配置、堆栈和任务控制块的定义、xTaskCreateStatic
和vTaskDelete
函数的调用等步骤。通过静态创建任务,用户可以在编译时分配任务的堆栈和控制块,从而避免运行时的内存分配开销。然而,这也要求用户更加仔细地管理内存,确保堆栈大小足够且任务控制块在任务的生命周期内保持有效。
FreeRTOS消息队列
FreeRTOS消息队列是一种用于任务间通信的数据结构,它允许任务之间异步地传递和共享数据。以下是对FreeRTOS消息队列的详细解析:
Ⅰ、基本概念
消息队列: - 是一种常用于任务间通信的数据结构,队列中的数据项按照先进先出(FIFO)或后进先出(LIFO)的原则进行排序。 - 在FreeRTOS中,消息队列用于在不同任务之间传递消息,实现了任务间的解耦,提高了系统的灵活性和可维护性。
Ⅱ、主要特点
数据入队与出队
- 入队:发送任务将消息放入队列中,可以指定一个阻塞时间。如果队列已满且阻塞时间不为0,则发送任务会等待直到队列中有空间或超时。 - 出队:接收任务从队列中获取消息,同样可以指定一个阻塞时间。如果队列为空且阻塞时间不为0,则接收任务会等待直到队列中有消息或超时。
多任务访问
- 队列不属于某个特定的任务,任何任务和中断都可以向队列发送或读取消息。 - 当多个任务等待同一个队列时,FreeRTOS会根据任务的优先级和阻塞的先后顺序来决定哪个任务将被解除阻塞并读取消息。
阻塞与非阻塞操作
- 发送任务和接收任务可以选择在操作队列时是阻塞还是非阻塞的。 - 阻塞操作会使任务在队列操作无法立即执行时进入阻塞状态,而非阻塞操作则会立即返回,无论队列操作是否成功。
数据传递方式
支持直接拷贝数据到队列中进行传递,也支持传递数据的指针(特别是当数据较大时)。
队列大小与消息类型
- 消息队列具有固定的容量,即可以容纳的消息数量。在创建队列时,需要指定队列的大小。 - 消息队列可以传递不同类型的消息,包括整数、结构体、指针等。
Ⅲ、API函数
FreeRTOS提供了一系列API函数来管理消息队列,包括创建队列、向队列中发送消息、从队列中接收消息等。以下是一些常用的API函数:
1. 创建队列: QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize ) - `uxQueueLength`:队列可同时容纳的最大项目数。 - `uxItemSize`:存储队列中的每个数据项所需的大小(以字节为单位)。 2. 向队列中发送消息: BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait ) - `xQueue`:队列的句柄。 - `pvItemToQueue`:指向要发送的数据的指针。 - `xTicksToWait`:阻塞超时时间。 3. 从队列中接收消息: BaseType_t xQueueReceive( QueueHandle_t xQueue, void * pvBuffer, TickType_t xTicksToWait ) - `xQueue`:队列的句柄。 - `pvBuffer`:用于存储接收到的数据的缓冲区指针。 - `xTicksToWait`:阻塞超时时间。
Ⅳ、总结
FreeRTOS消息队列是一种强大的任务间通信机制,它提供了灵活的数据传递方式和多任务访问能力。通过合理使用消息队列,可以提高系统的并发性和可维护性。在实际应用中,开发者应根据具体需求选择合适的队列大小和消息类型,并合理设置阻塞超时时间以确保系统的稳定运行。
信号量
FreeRTOS中的信号量(Semaphore)是一种用于任务间同步或保护共享资源的机制。信号量可以被视为一种特殊的队列,但它只关注队列的头部,并不涉及队列后部的环形存储区,主要用于任务间的消息传递而非数据传递。在FreeRTOS中,信号量主要包括以下几种类型:
Ⅰ、信号量的类型
二值信号量(Binary Semaphore)
1. 特点:二值信号量在同一时间只允许一个任务访问资源,它们通常用于实现任务间的同步。二值信号量只有两个状态:0(表示资源被占用)和1(表示资源空闲)。/*计数值为1*/ 2. API函数: - 创建:`SemaphoreHandle_t xSemaphoreCreateBinary(void)` - 获取:`BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)` - 释放:`BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)`
计数型信号量(Counting Semaphore)
1. 特点:计数信号量允许多个任务访问相同的资源。它们有一个初始计数值,每次获取信号量时计数减一,每次释放信号量时计数加一。当计数值为零时,表示没有可用资源,任务必须等待。 /*计数值0~MAX(所设置的最大值)*/ 2. API函数: - 创建:`SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount)` - 获取与释放与二值信号量相同。
互斥信号量(Mutex)
- 特点:互斥信号量主要用于任务间的互斥访问,确保同一时间只有一个任务可以访问某个资源。互斥信号量具有优先级继承的特性,可以减少优先级反转的影响。 - 注意:虽然互斥信号量在FreeRTOS中也是一个重要的概念,但这里主要介绍了二值信号量和计数信号量。
Ⅱ、信号量的实现原理
信号量的实现基于队列的阻塞机制。在FreeRTOS中,信号量的各种操作都是在队列的基础上建立起来的。信号量的数量(资源的数量)通过队列结构体中的`uxMessagesWaiting`变量来表示。当`uxMessagesWaiting`为0时,表示信号量资源被占用;当`uxMessagesWaiting`为1(对于二值信号量)或大于1(对于计数信号量)时,表示信号量资源空闲。
Ⅲ、拓展(二值信号量与优先级反转)
1、二值信号量概述
二值信号量(Binary Semaphore)是信号量的一种,其值只有两种状态:0和1。它通常用于实现两个或多个任务之间的同步,以及管理对共享资源的访问。当一个任务需要访问某个共享资源时,它会尝试获取与该资源相关联的二值信号量。如果信号量的值为1(表示资源可用),则该任务将信号量的值减1(变为0,表示资源正在被使用),并继续执行以访问资源。如果信号量的值为0(表示资源已被其他任务占用),则该任务将被阻塞,直到信号量的值变为1。
2、优先级反转现象
优先级反转(Priority Inversion)是实时操作系统(RTOS)中可能出现的一种现象,特别是在使用信号量管理共享资源时。当高优先级的任务因为等待低优先级任务释放它所持有的信号量而被阻塞时,就发生了优先级反转。在此期间,如果有中优先级的任务变得可运行,它可能会抢占低优先级任务的CPU时间,导致高优先级任务长时间得不到运行,尽管它的优先级更高。
3、二值信号量与优先级反转的关系
在使用二值信号量管理共享资源时,如果多个任务具有不同的优先级,并且它们都需要访问同一个资源,就可能发生优先级反转。具体过程如下: 1. 初始状态:高优先级任务H、中优先级任务M和低优先级任务L都处于就绪状态,但尚未运行。 2. 低优先级任务执行:任务L开始执行,并获取了某个共享资源的二值信号量。 3. 高优先级任务被阻塞:此时,如果高优先级任务H需要访问同一个资源,它将尝试获取信号量但会失败(因为信号量已被L占用),于是H被阻塞。 4. 中优先级任务抢占:如果任务M变得可运行,它可能会抢占L的CPU时间并开始执行。 5. 优先级反转发生:由于M的优先级高于L但低于H,M的执行可能导致H长时间等待信号量,尽管H的优先级更高。
4、解决优先级反转的方法
为了解决优先级反转问题,可以采取以下几种方法:
1. 优先级继承(Priority Inheritance):当高优先级任务因为等待低优先级任务释放信号量而被阻塞时,将低优先级任务的优先级临时提升到与高优先级任务相同的级别。这样,低优先级任务就可以尽快释放信号量,让高优先级任务继续执行。当信号量被释放后,低优先级任务的优先级再恢复到原来的级别。 2. 优先级天花板(Priority Ceiling):为每个共享资源设置一个优先级天花板,该优先级是所有可能访问该资源的任务中的最高优先级。任何访问该资源的任务都将被提升到该优先级天花板级别,以确保它们能够尽快完成并释放资源。 3. 使用互斥信号量(Mutex):互斥信号量是一种特殊的二值信号量,它除了具有二值信号量的基本功能外,还提供了优先级继承等机制来防止优先级反转。在FreeRTOS等RTOS中,可以使用互斥信号量来代替普通的二值信号量来管理共享资源。
综上所述,二值信号量在RTOS中用于任务同步和共享资源管理时,可能会引发优先级反转问题。通过采用优先级继承、优先级天花板或使用互斥信号量等方法,可以有效地解决这一问题。
Ⅳ、拓展(互斥信号量与优先级继承)
1.互斥信号量
互斥信号量(Mutex)是一种特殊的信号量,用于控制对共享资源的访问,确保在任何时候只有一个任务可以访问该资源。这有助于防止数据损坏或不一致性。互斥信号量具有以下特点:
1. 互斥性:保证在任何时刻,只有一个任务可以持有信号量,即访问受保护的资源。 2. 等待队列:当任务尝试获取一个已被占用的互斥信号量时,该任务会被阻塞,并放入等待队列中。一旦信号量被释放,等待队列中的最高优先级任务将获得信号量。 3. 所有权:持有信号量的任务被称为信号量的所有者,只有该任务才能释放信号量。
2.优先级继承
优先级继承是实时操作系统中一种解决优先级反转问题的技术。优先级反转是指高优先级的任务因为等待低优先级任务释放资源而被阻塞,导致系统的整体性能下降。优先级继承通过临时提升持有关键资源的低优先级任务的优先级来避免这种情况,具体做法如下:
1. 检测阻塞:当高优先级任务尝试获取一个已被低优先级任务持有的互斥信号量时,系统会检测到这种阻塞情况。 2. 提升优先级:为了防止优先级反转,系统会临时提升持有信号量的低优先级任务的优先级,使其与高优先级任务的优先级相同。 3.释放资源:一旦低优先级任务完成资源的使用并释放信号量,其优先级将恢复到原始水平。高优先级任务现在可以获取信号量并继续执行。
3.优先级继承的作用
优先级继承机制在实时操作系统中起着至关重要的作用,它有助于:
1. 减少任务阻塞时间:通过提升低优先级任务的优先级,可以减少高优先级任务因等待资源而阻塞的时间。 2. 提高系统响应性:确保高优先级任务能够更快地获得所需资源并继续执行,从而提高系统的整体响应性。 3. 防止优先级反转:通过优先级继承,可以有效防止优先级反转问题的发生,确保系统的稳定性和可预测性。
4.总结
互斥信号量与优先级继承是实时操作系统中管理资源访问和防止任务间冲突的重要机制。互斥信号量用于确保对共享资源的互斥访问,而优先级继承则通过临时提升持有关键资源的低优先级任务的优先级来防止优先级反转问题的发生。这两个机制共同协作,确保实时操作系统的高效、稳定和可预测性。
队列集
FreeRTOS队列集是一种用于在FreeRTOS实时操作系统中高效处理多个队列或信号量等待的机制。在FreeRTOS中,任务之间常常需要通过队列或信号量进行通信和同步,但传统的单个队列或信号量等待方式在处理多个事件源时可能不够灵活。队列集则提供了一种更为灵活和高效的解决方案。
Ⅰ、队列集的作用
队列集允许任务同时等待多个队列或信号量中的任何一个变为可用。当集合中的任意一个队列或信号量有数据可用时,任务将被唤醒,并可以确定是哪个队列或信号量变为可用。这种机制在嵌入式系统中非常有用,特别是在需要同时处理来自多个传感器、通信通道或事件源的数据时。
Ⅱ、队列集的使用流程
1. 启用队列集功能: 在FreeRTOS的配置文件中(如FreeRTOSConfig.h),需要将宏`configUSE_QUEUE_SETS`配置为1,以启用队列集功能。 2. 创建队列集: 使用`xQueueCreateSet`函数创建队列集,并指定其大小,即可以包含的队列和信号量的总数。 3. 创建队列或信号量: 创建需要被加入到队列集中的队列或信号量。 4. 添加队列或信号量到队列集: 使用`xQueueAddToSet`函数将队列或信号量添加到队列集中。需要注意的是,队列或信号量在被添加到队列集之前不能含有有效消息。 5. 等待队列集事件: 任务可以使用`xQueueSelectFromSet`函数阻塞等待队列集中的任何一个队列或信号量变为可用。该函数会返回变为可用的队列或信号量的句柄。 6. 处理事件: 任务被唤醒后,需要检查`xQueueSelectFromSet`返回的成员是哪个队列或信号量,并进行相应处理。
事件标志组及任务通知
ⅠFreeRTOS事件标志组
1.、概念
FreeRTOS的事件标志组是一种实现任务/中断间通信的机制,主要用于实现多任务间的同步。事件标志位用一个位来表示事件是否发生,而事件标志组则是一组事件标志位的集合,可以简单地理解为一个整数(根据配置,可以是16位或32位)。虽然使用了32位无符号的数据类型变量来存储事件标志,但其中的高8位用作存储事件标志组的控制信息,低24位用作存储事件标志,因此一个事件组最多可以存储24个事件标志。
2、相关API
1. 创建事件标志组 - 函数原型:`EventGroupHandle_t xEventGroupCreate(void);` - 功能:动态创建一个事件标志组。如果配置支持动态分配(`configSUPPORT_DYNAMIC_ALLOCATION`为1),则该函数会申请内存并初始化事件标志组;否则,需要静态分配事件标志组。 2. 设置事件标志位 - 函数原型:`EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet);` - 功能:在事件标志组中设置一个或多个事件标志位。设置的事件标志位可以是任意组合,通过位或操作指定。 3. 清除事件标志位 - 函数原型:`EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear);` - 功能:在事件标志组中清除一个或多个事件标志位。要清零的事件标志位,可以是一位,也可以是多位。 4. 等待事件标志位 - 函数原型:`EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait);` - 功能:等待事件标志组的某些位被设置,选择性地进入阻塞状态。该函数允许任务等待一个或多个事件标志位被设置,支持超时设置。
3、应用场景
事件标志组在需要多个任务同步或通信时非常有用。例如,一个任务可能需要等待多个外设的状态变化(如多个传感器或开关的状态),或者需要等待来自多个中断的事件。通过事件标志组,这些任务可以高效地等待并处理这些事件,而无需创建多个信号量或队列。
Ⅱ、FreeRTOS任务通知
1、概念
FreeRTOS的任务通知(Task Notifications)提供了一种任务间轻量级的同步和通信机制,允许任务相互通知和等待事件。每个任务都有一个结构体TCB(Task Control Block),其中包含了任务通知的状态和值。任务通知可以直接将事件通知或数据发送到某个任务,而无需创建额外的通信媒介对象(如队列或信号量)。
2、特点
1. 高效性:任务通知在发送和接收通知时,不需要经过额外的数据结构(如队列或信号量),因此效率更高。 2. 节省内存:由于任务通知直接使用任务结构体中的成员进行通信,因此不需要额外的内存来创建通信媒介对象。 3. 灵活性:任务通知支持多种通知方式,包括发送通知值、设置通知位的某些位等,可以满足不同的通信需求
3、相关API
1. 发送任务通知 - `xTaskNotifyGive(TaskHandle_t xTaskToNotify);`:向指定任务发送通知,使该任务的通知值加一。 - `xTaskNotifyGiveFromISR(TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken);`:在中断服务例程中向指定任务发送通知。 2. 接收任务通知 - `UBaseType_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);`:等待任务通知,如果通知值大于0,则立即返回并减少通知值;如果通知值为0,则阻塞等待直到接收到通知或超时。
定时器
FreeRTOS作为一款流行的嵌入式实时操作系统,提供了丰富的定时器功能,这些定时器主要用于执行特定任务、延时操作或周期性执行代码。以下是对FreeRTOS定时器的详细解析:
Ⅰ、定时器的类型
在FreeRTOS中,定时器主要分为软件定时器和硬件定时器。但需要注意的是,FreeRTOS本身主要提供的是软件定时器功能,而硬件定时器则依赖于具体的硬件平台。
1. 软件定时器 - 由软件实现,通过FreeRTOS的守护任务(或称为服务任务)来管理。 - 可以在FreeRTOS的内存中创建多个软件定时器,适用于对定时器精度要求不高的周期性任务。 - 定时精度基于系统时钟SysTick,一般达不到微秒级别。 2. 硬件定时器 - 由芯片自带的定时器模块实现,具有高精度(通常可达到微秒级)和多种高级功能(如PWM输出、输入捕获等)。 - 硬件定时器资源有限,且一旦用于特定的高级功能,可能无法再用于定时。
Ⅱ、软件定时器的特点
1. 可裁剪性:软件定时器是可裁剪可配置的功能,通过配置FreeRTOS的宏定义`configUSE_TIMERS`来使能或禁用。 2. 单次和周期模式:支持设置为单次定时器或周期定时器。单次定时器在超时后只执行一次回调函数,而周期定时器则会在每次超时后自动重启,从而周期性地执行回调函数。 3. 回调函数:定时器的回调函数在守护任务中执行,用户可以在回调函数中实现定时到期后需要执行的操作。但需要注意的是,在回调函数中不能调用任何会阻塞守护任务的API函数。 4. 守护任务(Daemon Task):FreeRTOS通过一个名为prvTimerTask的守护任务来管理软件定时器。该任务在启动调度器时自动创建,负责检查并处理到期的定时器,调用其回调函数。
Ⅲ、软件定时器的运行机制
软件定时器的运行基于以下几个关键组件:
1. 两条定时器链表:一条用于存储正常等待超时的定时器,另一条用于处理时间溢出的情况。 2. 一个消息队列:称为软件定时器命令队列,用于在任务或中断中向守护任务发送启动、停止、更改周期等命令。 3. 一个守护任务:负责监视定时器链表中的定时器是否超时,并调用超时的定时器的回调函数。守护任务的优先级可以通过FreeRTOS的配置项进行设置。
Ⅳ、软件定时器的相关API函数
FreeRTOS提供了丰富的软件定时器API函数,这些函数大多用于往软件定时器命令队列中写入消息(发送命令),从而实现对软件定时器的管理。以下是一些常用的API函数:
- xTimerCreate():创建软件定时器。 - xTimerStart():启动软件定时器。 - xTimerStop():停止软件定时器。 - xTimerReset():复位软件定时器,使其重新从当前时刻开始计时。 - xTimerChangePeriod():更改软件定时器的定时周期。 - xTimerDelete():删除软件定时器。
Ⅴ、软件定时器的状态
低功耗
FreeRTOS低功耗是一个重要的功能,尤其在可穿戴设备、物联网设备等对功耗要求严格的场合中。FreeRTOS通过提供Tickless低功耗模式,帮助开发者有效地降低设备的功耗。以下是对FreeRTOS低功耗的详细解析:
Ⅰ、Tickless低功耗模式简介
Tickless低功耗模式是FreeRTOS为了解决传统RTOS(实时操作系统)中系统时钟(通常由滴答定时器中断提供)频繁唤醒MCU(微控制器)进入和退出休眠模式,导致功耗增加的问题而设计的一种低功耗机制。该模式的核心思想是在MCU空闲时尽可能长时间地关闭系统节拍中断(滴答定时器中断),只有当其他中断发生或任务需要处理时才唤醒MCU。
Ⅱ、STM32低功耗模式
低功耗模式通常是指在嵌入式系统或计算机中,为了减少能耗而在不需要全速运行时降低系统活动级别的状态。这些模式通常由硬件和软件共同实现。以下是三种常见的低功耗模式:
1. 睡眠(Sleep)或低功耗模式(Low Power Mode): - 在此模式下,CPU停止执行指令,但大部分外设仍然可以运行,允许接收唤醒信号。 - 系统时钟频率降低,部分或全部的CPU缓存可能被关闭。 - 适用于短暂的休息周期,可以快速唤醒。 2. 停机(Stop)或深度睡眠(Deep Sleep)模式: - CPU和大部分外设停止工作,只有关键的时钟和电源管理逻辑保持活动。 - RAM通常保持供电,以保持系统状态,但能耗显著降低。 - 适用于较长的非活动时间,唤醒时间相对较长。 3. 待机(Standby)或休眠(Hibernate)模式: - 这是一种更深层次的低功耗状态,其中系统将RAM内容保存到非易失性存储器中(如果有的话),然后关闭大部分电源。 - 系统几乎不消耗能量,直到外部事件或定时器唤醒它。 - 适用于非常长的非活动时间,但唤醒过程可能需要较长时间,并且需要从非易失性存储器恢复RAM内容。
Ⅲ、Tickless低功耗模式的实现
1. 启用Tickless模式: - 需要在FreeRTOS的配置文件(如FreeRTOSConfig.h)中将宏`configUSE_TICKLESS_IDLE`设置为1,以启用Tickless低功耗模式。 - 根据需要,可以定义宏`configEXPECTED_IDLE_TIME_BEFORE_SLEEP`来设置系统进入低功耗模式的最短时长(默认为2个时钟节拍)。 2. 低功耗模式相关宏和函数: - `portSUPPRESS_TICKS_AND_SLEEP(xExpectedIdleTime)`:当系统处于低功耗模式的时间大于`configEXPECTED_IDLE_TIME_BEFORE_SLEEP`且空闲任务是唯一可运行的任务时,FreeRTOS内核会调用此宏来处理低功耗相关的工作。该宏的参数`xExpectedIdleTime`表示处理器将要在低功耗模式运行的时长(单位为时钟节拍数)。 - `configPRE_SLEEP_PROCESSING(x)`和`configPOST_SLEEP_PROCESSING(x)`:这两个宏分别用于定义在系统进入低功耗模式前和退出低功耗模式后需要执行的事务,如关闭和重新打开外设时钟等。 3. 低功耗模式的具体实现: - 在FreeRTOS中,Tickless低功耗模式的实现依赖于具体的硬件平台。对于STM32等Cortex-M内核的MCU,通常使用WFI(Wait For Interrupt)或WFE(Wait For Event)指令来进入低功耗模式。 - 当需要退出低功耗模式时,可以通过外部中断或定时器中断来唤醒MCU。此时,FreeRTOS会重新计算系统时间并进行软件补偿,以确保系统时间的准确性。
Ⅳ、Tickless低功耗模式的优势
1. 降低功耗:通过减少MCU进入和退出休眠模式的次数,Tickless低功耗模式可以显著降低设备的功耗。 2. 提高系统响应速度:在需要处理任务时,MCU能够迅速从低功耗模式中唤醒并投入工作。 3. 灵活性:开发者可以根据具体的应用场景和需求,通过配置FreeRTOS的相关宏和函数来优化低功耗模式的表现。
Ⅴ、注意事项
- 在使用Tickless低功耗模式时,需要确保系统的实时性和稳定性不会受到影响。 - 需要根据具体的硬件平台和MCU型号来编写或调整相关的低功耗处理代码。 - 在某些情况下,可能需要结合硬件的低功耗模式(如STM32的睡眠模式、停止模式或待机模式)来实现更低的功耗。
综上所述,FreeRTOS的Tickless低功耗模式是一种有效的低功耗解决方案,通过合理的配置和使用,可以帮助开发者在保持系统实时性和稳定性的同时,显著降低设备的功耗。
FreeRTOS内存管理
FreeRTOS内存管理是FreeRTOS实时操作系统中非常重要的一项功能,它提供了多种内存分配和管理方法,以满足不同应用场景的需求。以下是对FreeRTOS内存管理的详细解析:
Ⅰ、内存管理概述
FreeRTOS支持两种主要的内存分配方式:动态内存分配和静态内存分配。
- 动态内存分配:允许程序在运行时动态地申请和释放内存空间。这是最常用的内存分配方式,因为它提供了灵活性,可以根据需要动态地调整内存的使用情况。 - 静态内存分配:在编译时就已经确定了内存空间的大小和位置,无法在运行时进行修改。这种方式适用于那些内存需求固定且不会改变的应用场景。
Ⅱ、动态内存分配方法
FreeRTOS提供了五种动态内存管理方法,分别通过文件heap_1、heap_2、heap_3、heap_4和heap_5实现。每种方法都有其特点和适用场景:(------STM32F051K8U6学习使用的是heap_4-----)
堆管理策略 | 优点 | 缺点 |
---|---|---|
heap_1 | 1. 实现简单,适合小型系统 2. 不会产生内存碎片 3. 确定性高,执行时间一致 | 1. 只能分配不能回收内存,容易导致内存耗尽 2. 灵活性较低,不适合需要频繁动态内存分配的场景 3. 数组总大小固定,无法动态调整 |
heap_2 | 1. 允许释放内存 2. 适用于频繁创建和删除相同大小任务的应用 | 1. 不合并相邻空闲块,可能导致内存碎片 2. 适用场景较为局限 3. 被认为是遗留的,建议使用heap_4代替 |
heap_3 | 1. 线程安全,简单包装标准malloc()和free() 2. 堆大小由链接器配置定义 3. 适用于需要标准库malloc和free行为的场景 | 1. 依赖于标准库malloc和free的实现 2. 可能导致RTOS内核大小增加 3. 不是专门针对实时系统优化 |
heap_4 | 1. 合并相邻空闲块,减少内存碎片 2. 官方称比大多数标准C库malloc实现要快 3. 允许将堆放置在内存中的特定地址 4. 适用于需要频繁动态分配和释放内存的复杂场景 | 1. 相对于静态分配,动态分配有一定开销 2. 实现相对复杂 |
heap_5 | 1. 跨越多个非相邻内存区域,灵活分配 2. 提供了更大的内存分配灵活性 | 1. 初始化较为复杂,需要调用vPortDefineHeapRegions() 2. 使用和维护难度较高 |
Ⅲ、内存管理相关函数
FreeRTOS提供了一系列的内存操作相关接口,用于对内存进行管理和操作。以下是一些常用的函数:
- xTaskCreate():创建一个任务,并为其分配一定的栈空间和堆空间。 - vTaskDelete():删除一个任务,并释放其占用的栈空间和堆空间。 - vPortFree():释放一段已经分配好的内存空间。 - prvAllocateRAM():在系统中申请一段连续的RAM空间,并将其分配给指定的数据结构使用。 - uxTaskGetStackHighWaterMark():获取任务栈的高水位标记,即任务栈中剩余的最大空间。
Ⅳ、内存管理优化
在嵌入式系统中,有效地管理和优化内存使用至关重要。以下是一些内存管理优化的建议:
1. 合理设置任务栈大小:根据任务的实际需求,合理设置任务栈的大小,避免过度分配。 2. 选择合适的内存分配策略:根据系统的实际需求选择合适的内存分配方式,可以是静态内存分配或动态内存分配。 3. 监控内存使用情况:在系统运行过程中,不断监控内存的使用情况,及时发现和解决内存相关的问题。 4. 使用编译器优化选项:合理使用编译器的优化选项可以进一步优化内存使用情况,提高系统的性能。
通过上述分析,可以看出FreeRTOS提供了灵活多样的内存管理方法,以满足不同应用场景的需求。在实际使用中,开发者应根据具体的应用场景和需求选择合适的内存管理方法,并采取相应的优化措施,以确保系统的稳定性和可靠性。
FreeRTOS手动移植
/*见笔记<STM32>*/