目录
三、使用STM32CubeMX配置FreeRTOS实时操作系统
1)xSemaphoreCreateBinary(创建二值信号量)
1、FreeRTOS实时操作系统是什么
FreeRTOS实时操作系统,是一种轻量级的实时操作系统,为了实现快速响应、快速处理
FreeRTOS实时操作系统,是RTOS实时操作系统下的一个版本/子集
FreeRTOS实时操作系统 = 裸机开发 + 多线程(多任务)并发处理
RTOS实时操作系统具备很多版本/子集:FreeRTOS、RT-Thread、Thread-X ······
基于操作系统开发的是系统开发,不基于操作系统开发的是裸机开发
2、裸机开发和系统开发的区别
裸机开发:不使用操作系统开发,所有的驱动代码都按照main函数中的执行顺序执行
系统开发:使用操作系统开发,所有的驱动代码支持多进程/多线程的并发处理机制,实现快速响应、快速处理
多进程和多线程的区别:
1. 资源量(进程是资源分配的最小单位、线程是任务调度的最小单位)
2. 安全性(进程间用户空间相互独立[IPC通讯机制]、同一进程下的线程间共享同一用户资源[同步互斥])
3. 高效性(进程间切换涉及到进程的上下文切换、线程间可以直接切换,固然线程的并发效率更高)
3、FreeRTOS实时操作系统的特点
1)多任务并发处理(实时性)
具有严格的任务调度机制,确保任务按照优先级和时间约束、以预定的先后顺序执行
(1)抢占式调度机制(任务管理)
抢占式任务调度机制:
给每个任务/线程分配对应的优先级等级,
优先级等级高的任务/线程先执行、优先级等级低的任务/线程后执行
FreeRTOS实时操作系统默认使用抢占式调度机制:
优先级不同时:系统内核会优先执行线程等待列表中优先等级最高的线程,
优先级相同时:系统内核会按照队列思想逐个执行
注意:
1.只有当线程变为阻塞态时,才会被抢夺内核资源
2.任务的优先级等级就是一个数字,数字越大,优先级等级越高
中断的优先级等级就是一个数字,数字越小,优先级等级越高
linux操作系统的任务调度机制:默认使用时间片轮询机制,也可以使用抢占式任务调度机制
FreeRTOS实时操作系统的任务调度机制:默认使用抢占式任务调度机制,也可以使用时间片轮询机制
(2)通讯机制(同步互斥)
多种通讯机制,实现同步互斥,使任务在相同优先级下,也可以按照规定有序执行
1. 互斥锁
2. 消息队列
3. 信号量
4. 事件组
2)内存管理
可以动态分配内存空间,静态分配内存空间
3)时间管理
有软件定时器,严格管理任务时间
4)记录功能
有内核,文件信息,设备信息,内核链表/内核数组
4、FreeRTOS实时操作系统的任务调度机制
linux操作系统的任务调度机制:默认使用时间片轮询机制,也可以使用抢占式任务调度机制
FreeRTOS实时操作系统的任务调度机制:默认使用抢占式任务调度机制,也可以使用时间片轮询机制
抢占式任务调度机制:给每个任务/线程分配对应的优先级等级,优先级等级高的任务/线程先执行、优先级等级低的任务/线程后执行
注意:
任务的优先级等级就是一个数字,数字越大,优先级等级越高
中断的优先级等级就是一个数字,数字越小,优先级等级越高
5、线程状态
就绪态:任务的资源分配成功,等待被运行
运行态:被分配资源的任务使用系统的时间片,成功开始运行
阻塞态:任务中存在耗时、延时操作(Delay函数、超时检测)时,任务处于阻塞状态
当某个任务处于阻塞态时,别的任务可以抢占系统的资源,进而运行
挂起态:任务1中使用挂起函数,挂起任务2,任务2处于挂起态,不会被系统调用
需要在别的任务/任务1中使用解除挂起函数,解除任务2的挂起,此时任务2处于就绪态
6、FreeRTOS操作系统和Linux操作系统的区别
二、STM32CubeMX在线下载FreeRTOS内核
三、使用STM32CubeMX配置FreeRTOS实时操作系统
1、安装实时操作系统
2、CubeMX配置实时操作系统
四、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:紧急消息