Day09_STM32 单片机开发 - FreeRTOS 实时操作系统
FreeRTOS 实时操作系统是一种轻量型实时操作系统,实现快速响应FreeRTOS 实时操作系统是 RTOS 实时操作系统下的一个版本(子集)RTOS 实时操作系统具有很多版本:FreeRTOS、RT-Thread、Thread-X……
一、在线下载 FreeRTOS 内核
图 1
图 2
二、使用STM32CubeMX配置FreeRTOS实时操作系统
1、安装实时操作系统
图3
图4
图5
2、CubeMX配置实时操作系统
图6
图7
图8
图9
三、FreeRTOS 实时操作系统介绍
FreeRTOS 实时操作系统是一种轻量型实时操作系统,实现快速响应
FreeRTOS 实时操作系统是 RTOS 实时操作系统下的一个版本(子集)
RTOS 实时操作系统具有很多版本:FreeRTOS、RT-Thread、Thread-X……
2.1 裸机开发和系统开发的区别
裸机开发:不使用操作系统,所有驱动代码按照 main.c 中的编写顺序按顺序执行代码。
系统开发:使用操作系统,所有驱动代码支持多进程 / 多线程的并发处理机制,可以实现快速响应。
2.2 多进程和多线程的区别
- 资源量:进程是资源分配的最小单位,线程是任务调度的最小单位,1 个线程 = 1 个任务;
- 安全性:进程的用户空间相互独立(IPC 机制),线程同享同一进程下的资源(同步互斥);
- 效率:多线程的并发处理的效率更高(进程的上下文切换)。
四、FreeRTOS 实时操作系统的特点
- 小巧与灵活性:FreeRTOS 非常小巧,适合在资源有限的微控制器中运行,但其应用并不仅限于微控制器。开发者可以根据实际应用需求进行定制和配置,选择所需的功能模块。
- 可移植性:FreeRTOS 支持多种处理器架构,并能在不同的硬件平台上运行,这为开发者提供了更大的硬件选择灵活性。
- 实时性:FreeRTOS 提供严格的任务调度机制,确保任务按照预定的优先级和时间约束执行,使其非常适合对时间要求较高的应用场景,如工业控制和汽车电子等领域。
- 丰富的功能:包括任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等。
- 开源与免费:FreeRTOS 是一个开源项目,其源代码可以免费获取和使用,这有助于促进其在开发者社区中的普及和发展。
- FreeRTOS 的应用领域非常广泛,包括但不限于工业控制、汽车电子、智能家居、医疗设备、航空航天等需要实时响应和稳定运行的领域。
五、FreeRTOS 实时操作系统的任务调度机制
linux 操作系统的任务调度机制:默认使用时间片轮询机制,也可以使用抢占式任务调度机制;
FreeRTOS 操作系统的任务调度机制:默认使用抢占式任务调度机制,也可以使用时间片轮询机制。
抢占式任务调度机制:给每个任务/线程分配对应的优先级等级,优先级高的先执行,优先级低的后执行。
注意:
优先级等级的数字越小,优先级越高
优先级相同时,使用时间片轮询机制
六、任务的状态
- 就绪态:任务的资源分配成功,等待被运行。
- 运行态:被分配资源的任务使用系统的时间片,成功开始运行。
- 阻塞态:任务中存在耗时、延时(Delay 函数、超时检测等)时,任务处于阻塞状态;处于阻塞状态的任务,可以被别的任务抢占系统的资源,从而运行。
- 挂起态:在任务 1 中使用挂起函数挂起任务 2 ,使任务 2 处于挂起状态,系统不会调用任务 2;需要在别的任务/任务 1 中使用解除挂起函数,解除任务 2 的挂起,使任务 2 处于就绪态。
图 3 任务的状态
七、CubeMX 中配置 FreeRTOS

图 4 设置 FreeRTOS

图 5 heap_1 - heap_5 的选择

图 6 设置 USART1

图 7 设置 Debug 下载接口

图 8 配置 X-CUBE-FREERTOS
将如图所示的 CMSIS RTOS2 选项勾上,在下面进行任务的配置。
图 9 创建任务的相关参数
图 10 新任务创建参数
一个任务的参数有:任务名、当前优先级、任务大小、任务的入口函数等。

图 11 FreeRTOS 代码生成出现 RTOS 相关警告
如果出现如图 11 所示的 RTOS 相关警告,解决方法如下图:
图 12 警告解决方法
八、CMSIS_RTOS2和FreeRTOS提供的库函数
os开头的函数:是由ARM定义的所有操作系统都支持的CMSIS_RTOS2通用接口(相当于标准IO)
x开头的函数:FreeRTOS内核自身提供的函数(相当于文件IO)
1、CMSIS_RTOS2和FreeRTOS的区别
CMSIS_RTOS2是由ARM公司定义的,专门作用于Cortex-M内核的操作系统的通用API标准接口,优点是可移植性强
FreeRTOS是RTOS操作系统中的一个子集,一个具体的操作系统内核
2、CMSIS_RTOS2提供的函数
在cmsis_os2.c这个文件中的所有函数都是由CMSIS_RTOS2所提供的API函数接口
这些API接口在所有Cortex-M内核的实时操作系统中都可以使用
由CMSIS_RTOS2所提供的API接口本质上是由FreeRTOS所提供的API接口包装而成
图
图
3、FreeRTOS提供的函数
图
九、创建线程
1、静态创建线程分配任务资源
1)CubeMX创建线程
图
图
2)CubeMX初始化的代码
(1)主函数

图
(2)定义结构体并填充

图
(3)定义任务入口函数(线程执行函数)

图
(4)向内核申请线程资源

图
2、动态创建线程(手动创建线程)
1)osThreadNew(新建线程函数)
osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr)
功能:
CMSIS_RTOS2通用接口下提供的用于创建任务的函数
参数:
func:当前创建的任务对应的入口函数名(入口函数地址)
argument:给当前创建的任务对应的入口函数的传参
attr:当前创建的任务的信息结构体
返回值:
返回线程标识符,线程对象,创建失败返回NULL
// 线程/任务的信息结构体,他需要被传输给内核进行识别
typedef struct {
const char *name; // 当前被创建的任务的名字,内核通过这个名字识别他
uint32_t attr_bits; // 当前被创建的任务的特殊属性位
void *cb_mem; // 当前被创建的任务需要创建在堆区的哪片地址上
uint32_t cb_size; // 当前被创建的任务需要创建在堆区的空间大小
void *stack_mem; // 当前被创建的任务需要创建在栈区的哪片地址上
uint32_t stack_size; // 当前被创建的任务需要创建在栈区的空间大小
osPriority_t priority; // 当前被创建的任务的优先级等级
TZ_ModuleId_t tz_module; // 使用MON模式时,受信模块的属性
uint32_t reserved; // 保留信息
} osThreadAttr_t;
2)定义线程对象和参数结构体

图
3)定义任务的入口函数

图
4)初始化新建线程向内核申请资源

图
十、任务/线程间的同步互斥
由于FreeRTOS操作系统中提供的任务的优先等级个数有限
如果任务很多,数量超过优先级个数,就会被操作系统按照任务调取机制随机分配
此时可以引入同步互斥,使相同优先级的任务按照规定的顺序执行
1、二值信号量
信号量本质就是一个数字,一个计数单位,用于记录空间资源的个数
当信号量 > 0 时,代表存在空闲资源,可以申请空闲资源使用
当信号量 = 0 时,代表没有空闲资源,无法申请空闲资源使用
二值信号量(二进制信号量),就是信号量的值只有 0 和 1
信号量的核心操作(PV操作)
P操作:申请信号量(信号量 -1)
V操作:释放信号量(信号量 +1)
1)xSemaphoreCreateBinary(创建二值信号量)
SemaphoreHandle_t xSemaphoreCreateBinary(void);
功能:
FreeRTOS内核提供的用于创建二值信号量的函数(初始信号量的值为0,也就是无法申请到信号量)
参数:
无
返回值:
函数执行成功,二值信号量创建成功,返回创建成功的二值信号量对象
函数执行失败,返回NULL
osSemaphoreNew函数也能实现创建信号量的效果
2)xSemaphoreTake(申请信号量)
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xBlockTime)
功能:
FreeRTOS内核提供的用于申请信号量的函数(不管什么类型的信号量都可以申请,信号量-1)
参数:
xSemaphore:需要申请的信号量对象
xBlockTime:超时检测时间,当前函数的最大阻塞时间
返回值:
函数执行成功,返回pdTRUE(1)
函数执行失败,返回pdFALSE(0)
osSemaphoreAcquire函数也能实现申请信号量的效果
3)xSemaporeGive(释放信号量)
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)
功能:
FreeRTOS内核提供的用于释放信号量的函数(不管什么类型的信号量都可以释放,信号量+1)
参数:
xSemaphore:需要释放的信号量对象
返回值:
函数执行成功,释放信号量成功,返回pdTRUE(1)
函数执行失败,释放信号量失败,返回pdFALSE(0)
osSemaphoreRelease函数也能实现释放信号量的效果
4)示例代码
使用二进制信号量,实现默认任务、任务1、任务2、按顺序执行
(1)定义信号量对象

图
(2)创建二值信号量

图
(3)使用二值信号量实现同步

图
(4)程序现象

图
2、计数型信号量
1)xSemaporeCounting(创建计数型信号量)
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
功能:
FreeRTOS内核提供的用于创建计数型信号量的函数
计数型信号量:就是信号量的个数有很多
参数:
uxMaxCount:创建出来的计数型信号量个数的最大值
uxInitialCount:创建出来的计数型信号量初始个数
返回值:
函数执行成功,计数型信号量创建成功,函数返回创建成功的计数型信号量对象
函数执行失败,返回NULL
2)xSemaporeTake(申请信号量)
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xBlockTime)
功能:
FreeRTOS内核提供的用于申请信号量的函数(不管什么类型的信号量都可以申请,信号量-1)
参数:
xSemaphore:需要申请的信号量对象
xBlockTime:超时检测时间,当前函数的最大阻塞时间
返回值:
函数执行成功,返回pdTRUE(1)
函数执行失败,返回pdFALSE(0)
osSemaphoreAcquire函数也能实现申请信号量的效果
3)xSemaporeGive(释放信号量)
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)
功能:
FreeRTOS内核提供的用于释放信号量的函数(不管什么类型的信号量都可以释放,信号量+1)
参数:
xSemaphore:需要释放的信号量对象
返回值:
函数执行成功,释放信号量成功,返回pdTRUE(1)
函数执行失败,释放信号量失败,返回pdFALSE(0)
osSemaphoreRelease函数也能实现释放信号量的效果
4)示例代码
使用二进制信号量和计数信号量,实现任务1、任务2,交替执行5次,再执行默认任务
(1)定义信号量对象

图
(2)创建信号量

图
(3)使用信号量实现操作

图
(4)程序现象

图
3、事件组(事件标志位)
事件组又称为事件标志位
事件组就是多个事件标志位的集合
事件组可以理解为一个寄存器,这个寄存器的每一位都代表一个事件
每一位的值都代表一个事件是否执行成功
如果 某一位的值 = 0 ,则代表这一位对应的事件没有执行
如果 某一位的值 = 1 ,则代表这一位对应的之间执行成功
1)事件组的使用情景
通过多个任务管理一个任务
1. 当所有任务均执行成功后,执行主任务
2. 当诸多任务中有一个执行成功后,执行主任务
2)(创建事件组)
osEventFlagsId_t osEventFlagsNew (const osEventFlagsAttr_t *attr);
功能:
适用于CMSIS_RTOS V2通用接口下用于创建事件组的函数
参数:
attr:需要创建出来的事件组的信息结构体(可以填充NULL默认属性信息,由FreeRTOS自动分配)
如果自己填充填充name即可,有特殊需求可以全部自行填充
返回值:
函数执行成功,代表事件组创建成功,返回创建成功的事件组对象
函数执行失败,返回NULL
// 参数attr的结构体变量
// Attributes structure for event flags.
typedef struct {
const char *name; ///< name of the event flags
uint32_t attr_bits; ///< attribute bits
void *cb_mem; ///< memory for control block
uint32_t cb_size; ///< size of provided memory for control block
} osEventFlagsAttr_t;
3)(将标志位置一)
uint32_t osEventFlagsSet (osEventFlagsId_t ef_id, uint32_t flags);
功能:
适用于CMSIS_RTOS V2通用接口下用于对事件组中某一位/某些位置1的函数
置1:代表当前事件执行成功,也就是当前任务执行成功
参数:
ef_id:需要操作的事件组对象
flags:需要被置1的事件标志位(需要被置1的对应任务的事件标志位)
返回值:
函数执行成功,返回osOK(0)
函数执行失败,返回错误码
// os函数使用的返回值
typedef enum {
osOK = 0, ///< Operation completed successfully.
osError = -1, ///< Unspecified RTOS error: run-time error but no other error message fits.
osErrorTimeout = -2, ///< Operation not completed within the timeout period.
osErrorResource = -3, ///< Resource not available.
osErrorParameter = -4, ///< Parameter error.
osErrorNoMemory = -5, ///< System is out of memory: it was impossible to allocate or reserve memory for the operation.
osErrorISR = -6, ///< Not allowed in ISR context: the function cannot be called from interrupt service routines.
osStatusReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization.
} osStatus_t;
4)(阻塞等待标志位置一)
uint32_t osEventFlagsWait (osEventFlagsId_t ef_id,
uint32_t flags,
uint32_t options,
uint32_t timeout);
功能:
适用于CMSIS_RTOS V2通用接口下用于阻塞等待对应事件标志位被置1的函数
参数:
ef_id:需要操作的事件组对象
flags:需要等待被置1的事件标志位
options:
osFlagsWaitAny:等待第二个参数flags中任意一个标志位被置1
osFlagsWaitAll:等待第二个参数flags中所有标志位被置1
timeout:超时检测时间,当前函数的最大阻塞时间
// Timeout value.
#define osWaitForever 0xFFFFFFFFU ///< Wait forever timeout value.
返回值:
函数执行成功,返回osOK(0)
函数执行失败,返回错误码
//选择阻塞的状态
// Flags options (\ref osThreadFlagsWait and \ref osEventFlagsWait).
#define osFlagsWaitAny 0x00000000U //等待其中1位被置1
#define osFlagsWaitAll 0x00000001U //等待所有位被置1
#define osFlagsNoClear 0x00000002U //不清除那些已经被设置为等待的标志位
//当前函数的错误码
// Flags errors (returned by osThreadFlagsXxxx and osEventFlagsXxxx).
#define osFlagsError 0x80000000U ///< Error indicator.
#define osFlagsErrorUnknown 0xFFFFFFFFU ///< osError (-1).
#define osFlagsErrorTimeout 0xFFFFFFFEU ///< osErrorTimeout (-2).
#define osFlagsErrorResource 0xFFFFFFFDU ///< osErrorResource (-3).
#define osFlagsErrorParameter 0xFFFFFFFCU ///< osErrorParameter (-4).
#define osFlagsErrorISR 0xFFFFFFFAU ///< osErrorISR (-6).
任务1:对应事件组中的第0位 0x1 << 0
任务2:对应事件组中的第1位 0x1 << 1
任务3:等待任务1和任务2都被执行完,任务3才能执行
也就是任务1的事件标志位被置1并且任务2的事件标志位也要被置1,任务3才能执行
flags = (0x1 << 0)| (0x1 << 1)
5)(将标志位清零)
uint32_t osEventFlagsClear (osEventFlagsId_t ef_id, uint32_t flags)
功能:
适用于CMSIS_RTOS V2通用接口下用于清除对应标志位的函数
参数:
ef_id:需要操作的事件组对象
flags:需要清除的事件标志位
返回值:
函数执行成功,返回osOK
函数执行失败,返回错误码
6)示例代码
实现所有任务都完成后,执行默认任务
(1)定义事件组对象

图
(2)定义宏体代表事件组某一位

图
(3)创建事件组

图
(4)操作事件标志位完成功能

图
(5)程序现象

图
7)示例代码
任务1、任务2、任务3中随便一个任务执行成功,都能让默认任务执行

图

图
4、消息队列
消息队列:
1.默认使用FIFO(先进先出,队列)
2.可以使用优先级划分消息处理等级(优先级高的消息先被处理,优先级低的消息后被处理)
3.相同优先级下,遵循FIFO规则,先进先出
4.消息队列中的消息是一次性的,读取后就不存在于消息队列中了
1)(创建消息队列)
osMessageQueueId_t osMessageQueueNew (uint32_t msg_count, uint32_t msg_size, const osMessageQueueAttr_t *attr)
功能:
适用于CMSIS_RTOS V2通用接口用于创建一个消息队列的函数
参数:
msg_count:当前创建的消息队列支持的消息最大个数
msg_size:当前创建的消息队列中的每个消息的大小,单位为字节
attr:当前创建的消息队列的信息结构体,需要传递给内核(NULL默认信息)
返回值:
函数执行成功,代表消息队列创建成功,返回创建成功的消息队列对象
函数执行失败,返回NULL
2)(向消息队列写入数据)
osStatus_t osMessageQueuePut (osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout)
功能:
适用于CMSIS_RTOS V2通用接口用于向消息队列中写入数据的函数
参数:
mq_id:需要操作的消息队列对象
msg_ptr:需要写入到消息队列中的消息
msg_prio:当前写入到消息队列中的消息的优先级等级
timeout:超时检测时间,也就是当前函数的最大阻塞时间
返回值:
函数执行成功,返回osOK(0)
函数执行失败,返回错误码
3)(从消息队列读取数据)
osStatus_t osMessageQueueGet (osMessageQueueId_t mq_id, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout)
功能:
适用于CMSIS_RTOS V2通用接口用于从消息队列中读取数据的函数
参数:
mq_id:需要操作的消息队列对象
msg_ptr:从消息队列中读取的消息存储的位置
msg_prio:需要读取的消息队列中消息的优先级等级
timeout:超时检测时间,也就是当前函数的最大阻塞时间
返回值:
函数执行成功,返回osOK(0)
函数执行失败,返回错误码
消息队列的消息优先级等级(0-9级,数字越大,优先级等级越高)
0:普通日志消息
9:紧急消息
4)示例代码
(1)定义消息队列对象

图
(2)创建消息队列

图
(3)操作消息队列完成功能

图
(4)程序现象

图
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



















所有评论(0)