FreeRTOS 快速入门(七)之事件组


一、事件组的概念

事件组可以简单地认为就是一个整数:

  • 每一位表示一个事件
  • 每一位事件的含义由程序员决定,比如:Bit0 表示用来串口是否就绪,Bit1 表示按键是否被按下。这些位,值为 1 表示事件发生了,值为 0 表示事件没发生
  • 一个或多个任务、ISR 都可以去写这些位;一个或多个任务、ISR 都可以去读这些位
  • 可以等待某一位、某些位中的任意一个,也可以等待多位

事件组用一个整数来表示,其中的高 8 位留给内核使用,只能用其他的位来表示事件。这个整数的位数由宏 configUSE_16_BIT_TICKS 决定:

  • 如果 configUSE_16_BIT_TICKS 是 1,那么这个整数就是 16 位的,低 8 位用来表示事件
  • 如果 configUSE_16_BIT_TICKS 是 0,那么这个整数就是 32 位的,低 24 位用来表示事件

configUSE_16_BIT_TICKS 是用来表示 Tick Count 的,怎么会影响事件组?这只是基于效率来考虑

  • 如果 configUSE_16_BIT_TICKS 是 1,就表示该处理器使用 16 位更高效,所以事件组也使用 16 位
  • 如果 configUSE_16_BIT_TICKS 是 0,就表示该处理器使用 32 位更高效,所以事件组也使用 32 位

如果 EvenBits_t 变量中的某个位为 1,则表示该位表示的事件以发生。如果 EvenBits_t 变量中的某个位为 0,则表示该位表示的事件未发生。

例如:事件组的值为 0x92,即事件位1、4、7为1,因此仅发生由位1、4、7表示的事件,如下图:

1、事件组和队列、信号量的对比

事件组和队列、信号量等不太一样,主要集中在两个地方:

  • 唤醒谁?
    • 队列、信号量:事件发生时,只会唤醒一个任务
    • 事件组:事件发生时,会唤醒所有符号条件的任务,简单地说它有"广播"的作用
  • 是否清除事件?
    • 队列、信号量:是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了
    • 事件组:被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件

二、事件组函数

1、创建

使用事件组之前,要先创建,得到一个句柄;使用事件组时,要使用句柄来表明使用哪个事件组。有两种创建方法:动态分配内存、静态分配内存。函数原型如下:

/* 创建一个事件组,返回它的句柄。
 * 此函数内部会分配事件组结构体
 * 返回值: 返回句柄,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreate(void);

/* 创建一个事件组,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreateStatic(
						StaticEventGroup_t *pxEventGroupBuffer);

2、删除

对于动态创建的事件组,不再需要它们时,可以删除它们以回收内存。

/*
 * xEventGroup: 事件组句柄,你要删除哪个事件组
 */
void vEventGroupDelete(EventGroupHandle_t xEventGroup)

3、设置事件

可以设置事件组的某个位、某些位,使用的函数有 2 个:

  • 在任务中使用 xEventGroupSetBits()
  • 在 ISR 中使用 xEventGroupSetBitsFromISR()

有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤醒。函数原型如下:

/** 
 * 设置事件组中的位
 * xEventGroup: 哪个事件组
 * uxBitsToSet: 设置哪些位?
 * 如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
 * 可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
 * 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了)
 */
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
							   const EventBits_t uxBitsToSet );

/**
 * 设置事件组中的位
 * xEventGroup: 哪个事件组
 * uxBitsToSet: 设置哪些位?
 * 如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
 * 可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
 * pxHigherPriorityTaskWoken: 有没有导致更高优先级的任务进入就绪态? pdTRUE-有,
pdFALSE-没有
 * 返回值: pdPASS-成功, pdFALSE-失败
 */
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
								const EventBits_t uxBitsToSet,
								BaseType_t * pxHigherPriorityTaskWoken );

值得注意的是,ISR 中的函数,比如队列函数 xQueueSendToBackFromISR 、信号量函数 xSemaphoreGiveFromISR,它们会唤醒某个任务,最多只会唤醒一个任务。

但是设置事件组时,有可能导致多个任务被唤醒,这会带来很大的不确定性。所以 xEventGroupSetBitsFromISR 函数不是直接去设置事件组,而是给一个 FreeRTOS 后台任务发送队列数据,由这个任务来设置事件组。

如果后台任务的优先级比当前被中断的任务优先级高,xEventGroupSetBitsFromISR 会设置 *pxHigherPriorityTaskWoken 为 pdTRUE。

如果后台任务成功地把队列数据发送给了后台任务,那么 xEventGroupSetBitsFromISR 的返回值就是 pdPASS

4、等待事件

使用 xEventGroupWaitBits 来等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位;等到期望的事件后,还可以清除某些位。函数原型如下:

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
								 const EventBits_t uxBitsToWaitFor,
								 const BaseType_t xClearOnExit,
								 const BaseType_t xWaitForAllBits,
								 TickType_t xTicksToWait );

unblock condition
一个任务在等待事件发生时,它处于阻塞状态;当期望的时间发生时,这个状态就叫"unblock condition",非阻塞条件,或称为"非阻塞条件成立";当"非阻塞条件成立"后,该任务就可以变为就绪态。

函数参数说明列表如下:

参数 说明
xEventGroup 等待哪个事件组
uxBitsToWaitFor 等待哪些位,哪些位要被测试
xWaitForAllBits 怎么测试?是"AND"还是"OR"?
pdTRUE: 等待的位,全部为 1;
pdFALSE: 等待的位,某一个为 1 即可
xClearOnExit 函数提出前是否要清除事件?
pdTRUE: 清除 uxBitsToWaitFor 指定的位
pdFALSE: 不清除
xTicksToWait 如果期待的事件未发生,阻塞多久。
可以设置为 0:判断后即刻返回;
可设置为 portMAX_DELAY:一定等到成功才返回;
可以设置为期望的 Tick Count,一般用 pdMS_TO_TICKS() 把 ms 转换为 Tick
Count
返回值 返回的是事件值,
如果期待的事件发生了,返回的是"非阻塞条件成立"时的事件值;
如果是超时退出,返回的是超时时刻的事件值。

可以使用 xEventGroupWaitBits() 等待期望的事件,它发生之后再使用 xEventGroupClearBits() 来清除。但是这两个函数之间,有可能被其他任务或中断抢占,它们可能会修改事件组。

可以使用设置 xClearOnExit 为 pdTRUE,使得对事件组的测试、清零都在 xEventGroupWaitBits() 函数内部完成,这是一个原子操作。

5、同步点

使用 xEventGroupSync() 函数可以同步多个任务:

  • 可以设置某位、某些位,表示自己做了什么事
  • 可以等待某位、某些位,表示要等等其他任务
### FreeRTOS 快速入门指南教程 FreeRTOS 是一款开源的实时操作系统RTOS),专为嵌入式系统设计。它提供了一组 API 和软件库,帮助开发者实现任务管理、内存管理、中断处理、时间管理等功能,从而开发出高可靠性的实时嵌入式应用程序。为了快速入门并开始使用 FreeRTOS,以下是一些关键步骤和指导。 #### 安装和配置 FreeRTOS 1. **获取 FreeRTOS 源代码** FreeRTOS 的源代码可以从其官方网站下载。下载后,将源代码解压到您的项目目录中。通常,FreeRTOS 的源代码包含多个目录,其中 `FreeRTOS/Source` 目录下包含了核心的 RTOS 源文件,而 `FreeRTOS/Source/include` 目录下包含了头文件 [^3]。 2. **选择开发环境和目标平台** FreeRTOS 支持多种开发环境和目标平台,包括但不限于 ARM Cortex-M 系列、ESP32、AVR、PIC 等。根据您的目标平台选择合适的开发工具链,例如 Keil、IAR、GCC 等。 3. **配置 FreeRTOS** 在 `FreeRTOS/Source/include` 目录下有一个名为 `FreeRTOSConfig.h` 的配置文件。该文件定义了 FreeRTOS 的一些基本配置,例如系统时钟频率、任务调度策略、内存分配方式等。根据您的项目需求调整这些配置 [^2]。 #### 创建任务和调度 1. **创建任务** FreeRTOS 的任务是通过 `xTaskCreate()` 函数创建的。该函数需要传入任务函数、任务名称、堆栈大小、任务参数、任务优先级以及任务句柄等参数。例如: ```c void vTaskFunction(void *pvParameters) { for (;;) { // 任务执行的代码 vTaskDelay(pdMS_TO_TICKS(1000)); // 廷迟 1 秒 } } xTaskCreate(vTaskFunction, "Task1", 200, NULL, 1, NULL); ``` 2. **启动调度器** 在所有任务创建完成后,调用 `vTaskStartScheduler()` 启动调度器。此时,FreeRTOS 将开始调度任务的执行 。 #### 同步与通信机制 FreeRTOS 提供了多种同步与通信机制,例如信号量(Semaphore)、互斥锁(Mutex)、队列(Queue)等。这些机制可以用于任务之间的同步和数据交换。 1. **信号量** 信号量可以通过 `xSemaphoreCreateBinary()` 创建,通过 `xSemaphoreTake()` 和 `xSemaphoreGive()` 进行获取和释放操作。 2. **队列** 队列可以通过 `xQueueCreate()` 创建,并通过 `xQueueSend()` 和 `xQueueReceive()` 进行发送和接收数据。 #### 内存管理 FreeRTOS 提供了多种内存分配策略,开发者可以根据项目需求选择合适的内存管理方式。默认情况下,FreeRTOS 使用静态内存分配,但也可以通过修改 `FreeRTOSConfig.h` 文件中的 `configUSE_MALLOC_LOCK_AND_RELEASE` 配置项来启用动态内存分配 [^2]。 #### 调试与优化 1. **调试工具** FreeRTOS 支持多种调试工具,例如 Tracealyzer、Percepio DevTools 等。这些工具可以帮助开发者分析任务调度、内存使用、中断响应等关键信息。 2. **性能优化** 通过调整任务优先级、优化任务调度策略、减少任务切换次数等方式,可以提高系统的实时性和性能。 #### 进一步学习 为了更深入地了解 FreeRTOS 的高级功能,建议阅读官方文档和相关教程。官方文档提供了详细的 API 说明和示例代码,帮助开发者更好地理解和使用 FreeRTOS [^1]。 --- ###
评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值