每当空闲任务执行一次,钩子函数都会被执行一次
- USE_TICK_HOOK
使能后,系统生成一个空回调函数,由用户编写函数主体
void vApplicationTickHook(void)
每个TICK周期,钩子函数都会执行一次
- USE_MALLOC_FAILED_HOOK
使能后,系统生成一个空回调函数,由用户编写函数主体
void vApplicationMallocFailedHook(void)
当申请动态内存失败时,钩子函数会执行一次
- USE_DAEMON_TASK_STARTUP_HOOK
使能后,系统生成一个空回调函数,由用户编写函数主体
void vApplicationDaemonTaskStartupHook(void).
任务刚启动时,钩子函数会执行一次
- CHECK_FOR_STACK_OVERFLOW
使能后,系统生成一个空回调函数,由用户编写函数主体
void vApplicationStackOverflowHook( xTaskHandle xTask, signed char \*pcTaskName );
任务栈溢出时,钩子函数会执行一次,传入任务 TCB 和任务名称
当我们在 CubeMX 里面开启对应钩子函数,生成代码之后,在FreeRTOS就可以看到自动生成的钩子函数,我们在里面编写相应的功能就行
2.5 任务运行追踪配置
功能配置项如下:
- GENERATE_RUN_TIME_STATS
开启时间统计功能,在调用 vTaskGetRunTimeStats() 函数时,将任务运行时间信息保存到可读列表中
- USE_TRACE_FACILITY
使能后会包含额外的结构成员和函数以帮助执行可视化和跟踪,默认开启,方便 MDK 软件工具调试使用
- USE_STATS_FORMATTING_FUNCTIONS
使能后会生成 vTaskList() 和 vTaskGetRunTimeStats() 函数用于获取任务运行状态
2.6 协程配置
Co-routine related definitions 是协程的配置项,两个选项用来配置协程是否开启,以及协程的优先级,开启后,需要用户手动创建协程,在协程几乎很少用到了,是 FreeRTOS目前还没有把协程移除的计划,但 FreeRTOS是不会再更新和维护协程了,因此大家解一下就行
协程特点:
- 堆栈使用
所有的协程使用同一个堆栈(如果是任务的话每个任务都有自己的堆栈),这样就比使用任务消耗更少的 RAM - 调度器和优先级
协程使用合作式的调度器,但是可以在使用抢占式的调度器中使用协程 - 宏实现
协程是通过宏定义来实现的 - 使用限制
为了降低对 RAM 的消耗做了很多的限制
具体 API 接口和调度原理可以参考这篇文章 : FreeRTOS协程
2.7 软件定时器配置
软件定时器配置的一些相关项如下:
这四个配置项主要与软件定时器处理任务有关,软件定时器任务属于系统任务(守护线程),开启软件定时器后用于维护软件定时器
- USE_TIMERS
默认开启软件定时器任务
- TIMER_TASK_PRIORITY
软件定时器任务优先级
- TIMER_QUEUE_LENGTH
定时器任务队列长度,FreeRTOS 是通过队列来发送控制命令给定时器任务,叫做定时器命令队列,此处设置队列长度
- TIMER_TASK_STACK_DEPTH
软件定时器任务堆栈大小
2.8 中断优先级配置
- LIBRARY_LOWEST_INTERRUPT_PRIORITY
此宏是用来设置最低优先级,FreeRTOS 使用的4位优先级,对应16位优先级,对应的最低优先级为15
- LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
设置FreeRTOS 系统可管理的最大优先级,也就是设置阈值优先级,这个大家可以自由设置,这里设置为5,也就是高于5 的优先级(优先级数小于5)不归 FreeRTOS 管理
三、内核裁剪
Include Parameters 下的选项应用于内核裁剪,裁剪不必要的功能,精简系统功能,减少资源占用,主要有以下几个选项:
配置项可裁剪的函数功能如下:
| 选项 | 功能 |
|---|---|
| vTaskPrioritySet | 改变某个任务的任务优先级。 |
| uxTaskPriorityGet | 查询某个任务的优先级。 |
| vTaskDelete | 删除任务 |
| vTaskCleanUpResources | 回收任务删除后的资源如RAM等等 |
| vTaskSuspend | 挂起任务 |
| vTaskDelayUntil | 阻塞延时一段绝对时间(绝对延时去去除程序执行时间,执行更精准) |
| vTaskDelay | 阻塞延时一段相对时间 |
| xTaskGetSchedulerState | 获取任务调度器的状态,开启或未开启 |
| xTaskResumeFromISR | 在中断服务函数中恢复一个任务的运行 |
| xQueueGetMutexHolder | 获取信号量的队列拥有者,返回拥有此信号量的队列 |
| xSemaphoreGetMutexHolder | 查询拥有互斥锁的任务,返回任务控制块 |
| pcTaskGetTaskName | 获取任务名称 |
| uxTaskGetStackHighWaterMark | 获取任务的堆栈的历史剩余最小值,FreeRTOS 中叫做“高水位线” |
| xTaskGetCurrentTaskHandle | 此函数用于获取当前任务的任务句柄,就是获取当前任务控制块 |
| eTaskGetState | 此函数用于查询某个任务的运行壮态,比如:运行态、阻塞态、挂起态、就绪态等 |
| xEventGroupSetBitFromISR | 在中断服务函数中将指定的事件位清零 |
| xTimerPendFunctionCall | 定时器守护任务的回调函数(定时器守护任务使用到一个命令队列,只要向队列发送信号就可以执行相应代码,可以实现“中断推迟处理”功能) |
| xTaskAbortDelay | 中止延时函数,该函数能立即解除任务的阻塞状态,将任务插入就绪列表中 |
| xTaskGetHandle | 此函数根据任务名字获取的任务句柄(控制块) |
四、创建任务与队列
4.1 CubeMX 下任务创建与配置
任务(线程)是操作系统运行的基本单元,也是资源分配的基本单元, CubeMX 任务的创建基本以图形化进行,配置方式如下
进入Tashs and Queues 配置,点击 Add 添加新任务
任务配置参数介绍
| 参数 | 功能 |
|---|---|
| Task Name | 任务名称,保存在 TCB 结构体中,设置时自己起名字 |
| Priority | 任务优先级,任务的调度等级,根据自己创建任务的紧急程度设定比如通信任务不能被打断,可以设计较高优先级 |
| Stack Size(Words) | 设定给任务分配的内存大小,单位是字,对于32位单片机来说占4个字节 |
| Entry Function | 任务实体,即任务的运行函数名 |
| Code Generation | 代码生成模式As weak: 产生一个用 __weak 修饰的弱定义任务函数,用户可自己在进行定义;As external: 产生一个外部引用的任务函数,用户需要自己定义该函数;Default: 产生一个默认格式的任务函数,用户需要在该函数内实现自己的功能 |
| Parameter: | 传入的参数,保持默认就行 |
| Allocation: | 内存分配方式Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间 |
设置完成后点击OK,配置就完成了,之后生成代码,使用 MDK 进一步配置任务的具体信息
在生成的代码中,我们打开 freertos.c 文件可以在代码中看到任务的配置信息
在 freertos.c 文件的末尾部分,我们可以看到生成的任务实体
任务实体本身就是一个死循环函数,循环执行程序代码,但循环体代码里面必须要有延时函数,释放当前任务对 MCU 的控制权,使其他低优先级可以执行,此外,关于任务,CubeMX 提供了一系列的用户调用接口函数,具体如下
| 函数 | 功能 |
|---|---|
| osThreadNew | 创建新任务 |
| *osThreadGetName | 获取任务名称 |
| osThreadGetId | 获取当前任务的控制块(TCB) |
| osThreadGetState | 获取当前任务的运行状态 |
| osThreadGetStackSize | 获取任务的堆栈大小 |
| osThreadGetStackSpace | 获取任务剩余的堆栈大小 |
| osThreadSetPriority | 设定任务优先级 |
| osThreadGetPriority | 获取任务优先级 |
| osThreadYield | 切换控制权给下一个任务 |
| osThreadSuspend | 挂起任务 |
| osThreadResume | 恢复任务(挂起多少次恢复多少次) |
| osThreadDetach | 分离任务,方便任务结束进行回收 |
| osThreadJoin | 等待指定的任务停止 |
| osThreadExit | 停止当前任务 |
| osThreadTerminate | 停止指定任务 |
| osThreadGetCount | 获取激活的任务数量 |
| osThreadEnumerate | 列举激活的任务 |
4.2 CubeMX 下队列的创建与配置
队列,又称为消息队列,用于任务间的数据通信,传输数据,在操作系统里面,直接使用全局变量传输数据十分危险,看似正常运行,但不知道啥时候就会因为寄存器或者内存等等原因引起崩溃,所以引入消息,队列的概念,任务发送数据到队列,需要接受消息的任务挂起在队列的挂起列表,等待消息的到来,CubeMX 创建队列的步骤如下:
先点击 Add 添加队列
队列配置参数介绍
| 参数 | 功能 |
|---|---|
| Queue Name | 队列名称(自己设定) |
| Queue Size | 消息队列大小 |
| Item Size | 队列传输类型,保持默认16 位就行 |
| Allocation | 队列内存的分配方式Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间 |
配置需要的参数后,点击OK,然后生成代码
生成代码后,我们可以在 freertos.c 中系统初始话函数中看到队列的初始化
初始化函数会在一开始被调用,对 FreeRTOS 系统和内核对象进行初始化,初始化后系统就可以进行调度和使用内核对象,CubeMX 生成的代码自动将创建的内核对象放到初始化函数内,所以我们在任务和中断中直接使用就可以,队列的 FreeRTOS API 接口在CubeMX 内再次进行了封装,使用更加简单,使用方式如下:
我们使用的 CMSIS 2.0 版本,所以在任务文件中包含调用声明头文件
#include "cmsis\_os2.h"
在队列头文件内我们可以在 600 多行的位置找到有关队列的 API 函数声明:
下面介绍一下队列有关接口的函数接口:
| 函数 | 功能 |
|---|---|
| osMessageQueueNew | 创建并初始化一个新的队列 |
| osMessageQueueGetName | 获取队列的名字 |
| osMessageQueuePut | 发送一条消息到队列 |
| osMessageQueueGet | 从队列等待一条消息 |
| osMessageQueueGetCapacity | 获取队列传输消息的峰值 |
| osMessageQueueGetMsgSize | 获取队列使用内存池的最大峰值 |
| osMessageQueueGetCount | 获取队列的消息数量 |
| osMessageQueueGetSpace | 获取队列剩余的可用空槽 |
| osMessageQueueReset | 清空队列 |
| osMessageQueueDelete | 删除队列 |
以上的API接口有其对应的传入参数,具体使用方式需要在翻源码的注释,这里我选常用的来介绍一下:
消息队列常用的是插入与获取消息,初始化系统已经帮助我们完成,在初始化的时候会获取一个队列的句柄,之后对队列的操作都是围绕这个句柄展开,比如上面的代码中,句柄就是 myQueue01Handle ,我们发送一个消息到这个队列,就是调用发送函数,对句柄进行操作,先看一下发送消息的函数原型
osStatus\_t osMessageQueuePut (osMessageQueueId\_t mq_id, const void \*msg_ptr, uint8\_t msg_prio, uint32\_t timeout);
参数的功能
| 参数 | 功能 |
|---|---|
| mq_id | 传入队列的句柄 |
| *msg_ptr | 指向需要发送的消息内容的指针 |
| msg_prio | 本次发送消息的优先级(目前API未加入功能) |
| timeout | 发送消息的超时时间(设置为0代表一直等待发送成功) |
| osStatus_t(返回值) | 返回执行结果 |
返回值的可能
| 错误 | 含义 |
|---|---|
| osOK | 执行正常 |
| osError | 系统错误 |
| osErrorTimeout | 执行超时 |
| osErrorResource | 资源不可用 |
| osErrorParameter | 参数无效 |
| osErrorNoMemory | 内存不足 |
| osErrorISR | 不允许在中断调用 |
| osStatusReserved | 防止编译器优化项,不需要管他 |
所以我们发送一个消息到队列,函数用法如下:
void StartTask02(void \*argument)
{
/\* USER CODE BEGIN StartTask02 \*/
osStatus\_t result;
uint8\_t dat[]="666\r\n";
/\* Infinite loop \*/
for(;;)
{
result= osMessageQueuePut(myQueue01Handle,dat,1,0);
if(result == osOK)
{
//发送成功
}else
{
//发送失败
}
osDelay(1);
}
/\* USER CODE END StartTask02 \*/
}
发送消息的优先级暂时无用,CubeMX 对 FreeRTOS 的支持还不完善,发送消息里面的优先级未使用到,并且入队方式使用的是发送到队列尾部,没有从头部插入的方式,有需求可以 通过包含 queue.h 文件,调用 FreeRTOS 的官方代码,或者自己修改 生成代码的 API 接口结合优先级使用队列的向前插入和向后插入,丰富系统功能!
除了发送消息到队列,接受队列的消息 API 接口也经常用到,函数原型如下
osStatus\_t osMessageQueueGet (osMessageQueueId\_t mq_id, void \*msg_ptr, uint8\_t \*msg_prio, uint32\_t timeout);
参数的功能
| 参数 | 功能 |
|---|---|
| mq_id | 接受队列的句柄 |
| *msg_ptr | 用于接受消息内容的指针 |
| msg_prio | 存放接受消息的优先级(目前API未加入功能) |
| timeout | 接受消息的超时时间(设置为10代表,当前任务挂起在挂起列表,直到接收成功时恢复,或者10个TICK等待周期到达然后任务强行恢复,不再等待,为0则是不等待,等待期间任务挂起在内核对象的挂起队列) |
| osStatus_t(返回值) | 返回执行结果 |
函数用法
void StartTask02(void \*argument)
{
/\* USER CODE BEGIN StartTask02 \*/
osStatus\_t result;
uint8\_t dat[10]={};
uint8\_t \*pro;
/\* Infinite loop \*/
for(;;)
{
result= osMessageQueueGet(myQueue01Handle,dat,pro,10);
if(result == osOK)
{
//接受成功
}else
{
//接受失败
}
osDelay(1);
}
/\* USER CODE END StartTask02 \*/
}
注意:FreeRTOS 中获取和发送消息的 API 接口函数分为任务中调用和中断中调用,CubeMX 代码接口将两者整合了,调用时自动判断调用环境是在 ISR 还是正常运行环境中
五、创建定时器和信号量
5.1 CubeMX下定时器的创建和配置
软件定时器本质上就是设置一段时间,当设置的时间到达之后就执行指定的功能函数,调用的这个函数叫做回调函数。回调函数的两次执行间隔叫做定时器的定时周期,简而言之,当定时器的定时周期到了以后就会执行回调函数,下面介绍一下 CubeMX 中开启定时器的方法:
在 CubeMX 里面按下面步骤添加定时器
然后配置具体参数,参数的功能如下:
| 参数 | 功能 |
|---|---|
| Timer Name | 设置定时器的名称 |
| Callback | 设定定时器的回调函数体 |
| Type | 设定定时器的执行类型osTimerPeriodic 定时器周期执行回调函数osTimerOnce 定时器只执行一次回调函数 |
| Code Generation Option | 代码生成模式As weak: 产生一个用 __weak 修饰的弱定义任务函数,用户可自己在进行定义;As external: 产生一个外部引用的任务函数,用户需要自己定义该函数;Default: 产生一个默认格式的任务函数,用户需要在该函数内实现自己的功能 |
| Parameter | 传入参数,保持默认NULL就行 |
| Allocation | 软件定时器内存的分配方式,一般使用动态Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间 |
参数配置完成后,生成代码,我们可以在 freertos.c 文件里面看到定时器创建后获得的句柄,以及生成的回调函数:
有了句柄,我们就可以调用 cmsis_os2.c 里面的定时器接口函数对定时器进行操作,先看一下 CubeMX 提供的定时器接口函数及其功能
| 函数 | 功能 |
|---|---|
| osTimerNew | 新建定时器,返回定时器控制句柄 |
| osTimerGetName | 获取定时器名称 |
| osTimerStart | 设置定时器周期,启动定时器 |
| osTimerStop | 停止定时器 |
| osTimerIsRunning | 检测定时器是否在运行 |
| osTimerDelete | 删除定时器 |
其中常用的接口是定时器的启动和停止
定时器启动: osTimerStart,函数原型
osStatus\_t osTimerStart (osTimerId\_t timer_id, uint32\_t ticks);
参数介绍:
| 参数 | 功能 |
|---|---|
| timer_id | 需要启动的定时器句柄 |
| ticks | 设置定时器的运行周期 |
此处的 ticks 设定的数字是定时器两次调用回调函数的周期数目,每个 tick 是一个心跳时钟的长度
使用例程:
void StartTask02(void \*argument)
{
/\* USER CODE BEGIN StartTask02 \*/
osStatus\_t result;
uint8\_t dat[10]={0};
uint8\_t \*pro;
result= osTimerStart(myTimer01Handle,10);
if(result == osOK)
{
//启动成功
}else
{
//启动失败
}
/\* Infinite loop \*/
for(;;)
{
osDelay(10);
}
/\* USER CODE END StartTask02 \*/
}
按照例程启动定时器,定时器会以 10个tick 的周期,调用回调函数
回调函数不要放阻塞函数,程序尽可能短
定时器启动: osTimerStop,函数原型
osStatus\_t osTimerStop (osTimerId\_t timer_id);
参数只有一个,就是定时器的控制句柄,传入即可停止定时器,例程如下
void StartTask02(void \*argument)
{
/\* USER CODE BEGIN StartTask02 \*/
osStatus\_t result;
uint8\_t dat[10]={0};
uint8\_t \*pro;
result= osTimerStop(myTimer01Handle);
if(result == osOK)
{
//停止成功
}else
{
//停止失败
}
/\* Infinite loop \*/
for(;;)
{
osDelay(10);
}
/\* USER CODE END StartTask02 \*/
}
软件定时器是由软件定时器维护任务进行维护,检测各个定时器的状态,进行处理,回调回调函数,软件定时器维护任务的参数配置在前面的 Config 就已经提到过
5.2 CubeMX下信号量的创建和配置
信号量是 RTOS 的一个内核对象,该对象有一个队列表示该信号量拥有的信号数目,任何任务都可以对这个信号数目进行获取和释放,获取时信号-1,释放时信号+1,为0时不能继续获取,此时有任务想要继续获取信号量的话,任务会挂起在该内核对象的挂起列表,等到信号可以获取时进行恢复,根据这个特性,信号量常用于控制对共享资源的访问和任务同步,下面介绍一下 CubeMX 下信号量的配置:
点开配置页面,可以看到有两个信号量添加页面,其中 Binary Semaphores 是二值信号量,Counting Semaphores 是计数信号量,二进制信号量,仅有一个队列或者说 token,用于同步一个操作;计数信号量则拥有多个 tokens,可用于同步多个操作,或者管理有限资源
二值信号量创建:
点击 Add,配置参数
参数介绍
| 参数 | 功能 |
|---|---|
| Semaphore Name | 信号量名称 |
| Allocation | 内存分配方式,一般使用动态Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间 |
计数信号量:
点击 Add,配置参数
参数介绍
| 参数 | 功能 |
|---|---|
| Semaphore Name | 信号量名称 |
| Count | 计数信号量的最大数目 |
| Allocation | 内存分配方式,一般使用动态Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间 |
配置完成后我们生成代码,在 freertos.c 的初始化代码中可以看到信号量被创建,并且返回了信号量的控制句柄
下面介绍一下 CubeMX 提供的信号量操作函数接口:
| 函数 | 功能 |
|---|---|
| osSemaphoreNew | 创建新的信号量 |
| *osSemaphoreGetName | 获取信号量的名称 |
| osSemaphoreAcquire | 获取信号量 |
| osSemaphoreRelease | 释放信号量 |
| osSemaphoreGetCount | 获取当前可用信号量的数目 |
| osSemaphoreDelete | 删除信号量 |
其中常用的函数有获取和释放信号量,下面介绍一下这两个函数的参数和使用方式
获取信号量 osSemaphoreAcquire
函数原型
osStatus\_t osSemaphoreAcquire (osSemaphoreId\_t semaphore_id, uint32\_t timeout);
参数介绍
| 参数 | 功能 |
|---|---|
| semaphore_id | 传入要获取信号量的控制句柄 |
| timeout | 获取等待时间(等待期间任务挂起在内核对象的挂起队列) |
使用例程
void StartDefaultTask(void \*argument)
{
/\* USER CODE BEGIN StartDefaultTask \*/
osStatus\_t result;
/\* Infinite loop \*/
for(;;)
{
result = osSemaphoreAcquire(myBinarySem01Handle,10);
if(result == osOK)
{
//获取成功
}else
{
//获取失败
}
osDelay(1);
}
/\* USER CODE END StartDefaultTask \*/
}
释放信号量 osSemaphoreRelease
函数原型
osStatus\_t osSemaphoreRelease (osSemaphoreId\_t semaphore_id);
| 参数 | 功能 |
|---|---|
| semaphore_id | 传入要释放的信号量控制句柄 |
使用例程
void StartDefaultTask(void \*argument)
{
/\* USER CODE BEGIN StartDefaultTask \*/
osStatus\_t result;
/\* Infinite loop \*/
for(;;)
{
result = osSemaphoreRelease(myBinarySem01Handle);
if(result == osOK)
{
//释放成功
}else
{
//释放失败
}
osDelay(1);
}
/\* USER CODE END StartDefaultTask \*/
}
二值信号量和计数信号量的操作基本一致,没用区别,只是用有的信号队列最大数目不同而已
同时注意信号量在使用过程中会出现优先级反转的Bug,使用时需要注意
六、创建互斥量
6.1 CubeMX下互斥量的创建和配置
互斥量其实就是一个拥有优先级继承的二值信号量,互斥信号量适合用于那些需要互斥访问的应用中,在互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源,与信号量不同的是,互斥量的释放必须由获取他的任务进行释放,如果不释放,可能会造成死锁
死锁就是两个任务获取对方拥有的锁,各自进入挂起列表,无法释放互斥锁
下面介绍一下 CubeMX 下互斥量的配置,在配置界面我们可用看到两个互斥量配置界面,上面的是普通互斥量,其获取只能获取一次,重复获取是无效的,而第二个则是递归互斥量,递归互斥信号量可以获取多次,但对应的也要释放多次才能让出使用权,比如我获取3次,任务要释放3次才能释放该互斥量的使用权
使用互斥量,需要点击 Add 然后配置参数
参数介绍:
| 参数 | 功能 |
|---|---|
| Mutex Name | 互斥量名称 |
| Allocation | 内存分配方式,一般使用动态Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间 |
递归互斥信号量的配置方式与其相同,包括配置参数也相同,两者只是在用法上有些许区别,添加方式如下:
添加配置完成后,点击生成代码,在 freertos.c 文件中我们可以看到互斥量初始化完成,并且生成了对应的控制句柄
CubeMX 提供的 API 接口函数如下
| 函数 | 功能 |
|---|---|
| osMutexNew | 创建互斥量 |
| *osMutexGetName | 获取互斥量名称 |
| osMutexAcquire | 任务获取互斥量 |
| osMutexRelease | 任务释放互斥量 |
| osMutexGetOwner | 获取互斥量的拥有任务的任务 TCB |
| osMutexDelete | 删除互斥量 |
主要使用到的还是互斥量的获取与释放,下面分析一下这两个函数:
获取互斥量 osMutexAcquire
函数原型
osStatus\_t osMutexAcquire (osMutexId\_t mutex_id, uint32\_t timeout);
参数介绍:
| 参数 | 功能 |
|---|---|
| mutex_id | 互斥量控制句柄 |
| timeout | 获取互斥量时的等待时间(等待期间任务挂起在内核对象的挂起队列) |
使用方式
void StartTask02(void \*argument)
{
/\* USER CODE BEGIN StartTask02 \*/
osStatus\_t result;
result= osMutexAcquire(myMutex01Handle,10);
if(result == osOK)
{
//获取成功
}else
{
//获取失败
}
/\* Infinite loop \*/
for(;;)
{
osDelay(10);
}
/\* USER CODE END StartTask02 \*/
}
释放互斥量 osMutexRelease
函数原型
osStatus\_t osMutexRelease (osMutexId\_t mutex_id);
参数介绍:
| 参数 | 功能 |
|---|---|
| mutex_id | 互斥量控制句柄 |
使用方式
void StartTask02(void \*argument)
{
/\* USER CODE BEGIN StartTask02 \*/
osStatus\_t result;
result= osMutexRelease(myMutex01Handle);
if(result == osOK)
{
//释放成功
}else
{
//释放失败
}
/\* Infinite loop \*/
for(;;)
{
osDelay(10);
}
/\* USER CODE END StartTask02 \*/
}
使用方式和信号量基本相同,因为互斥量本质上就是信号量的一种
七、创建事件标志组
7.1 CubeMX下事件的创建和配置
任务间的同步除了信号量还有时间标志组,信号的同步通常是一对一的同步,有的时候系统需要多对一的同步,比如同时满足5个按键按下时,任务启动,如果使用信号会很占据资源,所以 RTOS 引入了事件标志组来满足这一需求,下面我们看一下 CubeMX 内事件标志组的配置方法:
点击 Add 创建事件标志组
配置介绍
| 参数 | 功能 |
|---|---|
| Event flags Name | 事件标志组名称 |
| Allocation | 内存分配方式,一般使用动态Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间 |
配置完成后,生成代码,在系统初始化内,看有没有生成事件标志组控制句柄,可以看到句柄创建完成
CubeMX 提供的配置事件标志组的接口 API 如下:
| 函数 | 功能 |
|---|---|
| osEventFlagsNew | 创建事件标志组 |
| *osEventFlagsGetName | 获取事件标志组名称 |
| osEventFlagsSet | 设置事件标志组 |
| osEventFlagsClear | 清除事件标志组 |
| osEventFlagsGet | 获取当前事件组标志信息 |
| osEventFlagsWait | 等待事件标志组触发 |
| osEventFlagsDelete | 删除事件标志组 |
常用的 API 接口是设置事件标志组以及等待事件标志组的触发,下面我们分析一下这两个 API
在了解 API 前我们需要简单了解一下事件的触发原理:首先事件标志组的数据类型为 EventGroupHandle_t,事件标志组中的所有事件位都存储在一个无符号的 EventBits_t 类型的变量中,当 configUSE_16_BIT_TICKS 为 1 的时候事件标志组可以存储 8 个事件位,当 configUSE_16_BIT_TICKS 为 0 的时候事件标志组存储 24个事件位,每个事件位其实就是一个0或者1数字,就像下面的24位组成一个事件标志组
我们在使用事件API接口函数前需要先定义我们需要的触发事件位,比如添加如下的代码
#define event1 1<<1 //事件1
#define event2 1<<2 //事件2
编写好触发事件后,我们在看如何使用 API 接口
设置事件标志 osEventFlagsSet
函数原型
uint32\_t osEventFlagsSet (osEventFlagsId\_t ef_id, uint32\_t flags);
参数介绍:
| 参数 | 功能 |
|---|---|
| ef_id | 事件标志组控制句柄 |
| flags | 事件位 |
使用方式:设置事件1和事件2
void StartDefaultTask(void \*argument)
{
/\* USER CODE BEGIN StartDefaultTask \*/
osStatus\_t result;
/\* Infinite loop \*/
for(;;)
{
result = osEventFlagsSet(myEvent01Handle,event1);
if(result == osOK)
{
//事件1设置成功
}else
{
//事件1设置失败
}
result = osEventFlagsSet(myEvent01Handle,event2);
if(result == osOK)
{
//事件2设置成功
}else
{
//事件2设置失败
}
osDelay(1);
}
/\* USER CODE END StartDefaultTask \*/
}
等待事件标志 osEventFlagsWait
函数原型
uint32\_t osEventFlagsWait (osEventFlagsId\_t ef_id, uint32\_t flags, uint32\_t options, uint32\_t timeout);
参数介绍:
| 参数 | 功能 |
|---|---|
| ef_id | 事件标志组控制句柄 |
| flags | 等待的事件位 |
| options | 等待事件位的操作osFlagsWaitAny :等待的事件位有任意一个等到就恢复任务osFlagsWaitAll:等待的事件位全部等到才恢复任务 osFlagsNoClear:等待成功后不清楚所等待的标志位(默认清除) |
| timeout | 等待事件组的等待时间(等待期间任务挂起在内核对象的挂起队列) |
使用例子:同时等待事件1和事件2,且等待到不清除
void StartDefaultTask(void \*argument)
{
/\* USER CODE BEGIN StartDefaultTask \*/
osStatus\_t result;
/\* Infinite loop \*/


**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://gitee.com/vip204888)**