CubeMX使用FreeRTOS编程指南_cubemx freertos,2024年最新小白看完都会了

244 阅读27分钟

每当空闲任务执行一次,钩子函数都会被执行一次

  • 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就可以看到自动生成的钩子函数,我们在里面编写相应的功能就行

20211007080910

2.5 任务运行追踪配置

功能配置项如下:

20211007081447

  • GENERATE_RUN_TIME_STATS

开启时间统计功能,在调用 vTaskGetRunTimeStats() 函数时,将任务运行时间信息保存到可读列表中

  • USE_TRACE_FACILITY

使能后会包含额外的结构成员和函数以帮助执行可视化和跟踪,默认开启,方便 MDK 软件工具调试使用

  • USE_STATS_FORMATTING_FUNCTIONS

使能后会生成 vTaskList() 和 vTaskGetRunTimeStats() 函数用于获取任务运行状态

2.6 协程配置

Co-routine related definitions 是协程的配置项,两个选项用来配置协程是否开启,以及协程的优先级,开启后,需要用户手动创建协程,在协程几乎很少用到了,是 FreeRTOS目前还没有把协程移除的计划,但 FreeRTOS是不会再更新和维护协程了,因此大家解一下就行

协程特点:

  1. 堆栈使用
    所有的协程使用同一个堆栈(如果是任务的话每个任务都有自己的堆栈),这样就比使用任务消耗更少的 RAM
  2. 调度器和优先级
    协程使用合作式的调度器,但是可以在使用抢占式的调度器中使用协程
  3. 宏实现
    协程是通过宏定义来实现的
  4. 使用限制
    为了降低对 RAM 的消耗做了很多的限制

具体 API 接口和调度原理可以参考这篇文章 : FreeRTOS协程

2.7 软件定时器配置

软件定时器配置的一些相关项如下:

20211007084642

这四个配置项主要与软件定时器处理任务有关,软件定时器任务属于系统任务(守护线程),开启软件定时器后用于维护软件定时器

  • 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 下的选项应用于内核裁剪,裁剪不必要的功能,精简系统功能,减少资源占用,主要有以下几个选项:

20211007095602

配置项可裁剪的函数功能如下:

选项功能
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 添加新任务

20211008215753

任务配置参数介绍

参数功能
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 文件可以在代码中看到任务的配置信息

20211009123942

在 freertos.c 文件的末尾部分,我们可以看到生成的任务实体

20211009124134

任务实体本身就是一个死循环函数,循环执行程序代码,但循环体代码里面必须要有延时函数,释放当前任务对 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 添加队列

20211008221618

队列配置参数介绍

参数功能
Queue Name队列名称(自己设定)
Queue Size消息队列大小
Item Size队列传输类型,保持默认16 位就行
Allocation队列内存的分配方式Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间

配置需要的参数后,点击OK,然后生成代码

生成代码后,我们可以在 freertos.c 中系统初始话函数中看到队列的初始化

20211009130435

初始化函数会在一开始被调用,对 FreeRTOS 系统和内核对象进行初始化,初始化后系统就可以进行调度和使用内核对象,CubeMX 生成的代码自动将创建的内核对象放到初始化函数内,所以我们在任务和中断中直接使用就可以,队列的 FreeRTOS API 接口在CubeMX 内再次进行了封装,使用更加简单,使用方式如下:

我们使用的 CMSIS 2.0 版本,所以在任务文件中包含调用声明头文件

#include "cmsis\_os2.h"

在队列头文件内我们可以在 600 多行的位置找到有关队列的 API 函数声明:

20211009131537

下面介绍一下队列有关接口的函数接口:

函数功能
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 里面按下面步骤添加定时器

20211009221917

然后配置具体参数,参数的功能如下:

参数功能
Timer Name设置定时器的名称
Callback设定定时器的回调函数体
Type设定定时器的执行类型osTimerPeriodic 定时器周期执行回调函数osTimerOnce 定时器只执行一次回调函数
Code Generation Option代码生成模式As weak: 产生一个用 __weak 修饰的弱定义任务函数,用户可自己在进行定义;As external: 产生一个外部引用的任务函数,用户需要自己定义该函数;Default: 产生一个默认格式的任务函数,用户需要在该函数内实现自己的功能
Parameter传入参数,保持默认NULL就行
Allocation软件定时器内存的分配方式,一般使用动态Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间

参数配置完成后,生成代码,我们可以在 freertos.c 文件里面看到定时器创建后获得的句柄,以及生成的回调函数:

20211010142155

20211010142213

有了句柄,我们就可以调用 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,可用于同步多个操作,或者管理有限资源

20211010153231

二值信号量创建:

点击 Add,配置参数

20211010163823

参数介绍

参数功能
Semaphore Name信号量名称
Allocation内存分配方式,一般使用动态Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间

计数信号量:

点击 Add,配置参数

20211010163959

参数介绍

参数功能
Semaphore Name信号量名称
Count计数信号量的最大数目
Allocation内存分配方式,一般使用动态Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间

配置完成后我们生成代码,在 freertos.c 的初始化代码中可以看到信号量被创建,并且返回了信号量的控制句柄

20211010164347

下面介绍一下 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 然后配置参数

20211010173127

参数介绍:

参数功能
Mutex Name互斥量名称
Allocation内存分配方式,一般使用动态Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间

递归互斥信号量的配置方式与其相同,包括配置参数也相同,两者只是在用法上有些许区别,添加方式如下:

20211010173704

添加配置完成后,点击生成代码,在 freertos.c 文件中我们可以看到互斥量初始化完成,并且生成了对应的控制句柄

20211010174350

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 创建事件标志组

20211010201501

配置介绍

参数功能
Event flags Name事件标志组名称
Allocation内存分配方式,一般使用动态Static: 静态方式是直接在RAM占据一个静态空间Dynamic:动态方则是在初始配置的内存池大小数组中动态申请、释放空间

配置完成后,生成代码,在系统初始化内,看有没有生成事件标志组控制句柄,可以看到句柄创建完成

20211010201922

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位组成一个事件标志组

20211010203414

我们在使用事件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 \*/


![img](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/a38974c3d6434e03880d692c0a700a52~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1775484943&x-signature=sQWHL8HOlohpSqv7aIohLf4U01A%3D)
![img](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/e6c9f1dd16234a6497ab32ba05cae59d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1775484943&x-signature=ZZvM%2Bt64dXyt6R2r925jJAMBTJ4%3D)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://gitee.com/vip204888)**