尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FreeRTOS事件组实战:STM32多任务同步与通信机制详解

FreeRTOS事件组实战:STM32多任务同步与通信机制详解 1. 项目概述从裸机到RTOS的思维跃迁如果你是从51单片机或者STM32标准库裸机开发一路走过来的那么第一次接触FreeRTOS时那种感觉可能既兴奋又困惑。兴奋在于你终于可以摆脱那个庞大臃肿、充斥着if-else和全局标志位的main.c超级循环了困惑则在于任务、队列、信号量、事件这些概念听起来很美好但真要用起来总觉得有点“杀鸡用牛刀”甚至担心RTOS本身的开销会不会把小小的STM32给压垮。我最初也是这么想的直到在一个实际项目中我需要同时处理按键扫描、OLED屏幕刷新、传感器数据采集和通过串口与上位机通信。用裸机状态机硬撸出来的代码调试起来简直是一场噩梦任何一个功能的改动都可能引发连锁反应。这时FreeRTOS的价值就凸显出来了——它提供了一套标准化的、基于任务和内核对象的并发编程模型。而STM32CubeIDE作为ST官方力推的集成开发环境它最大的便利就是将STM32CubeMX图形化配置工具和基于Eclipse的IDE无缝整合让FreeRTOS的移植和基础配置变得像勾选几个复选框一样简单。今天我们要聊的“事件实验”就是FreeRTOS中用于任务间同步与通信的一个核心机制。它不同于队列传递数据也不同于信号量仅做计数事件组Event Groups更像是一组可以独立设置和等待的二进制标志位。一个任务可以等待多个事件中的任意一个或全部发生而另一个任务可以设置这些事件。这种“多对多”的等待关系在处理复杂的、由多种条件触发的任务逻辑时尤其高效。比如一个数据打包任务可能需要等待“数据采集完成”、“网络连接就绪”、“用户确认发送”这三个事件全部就位后才能执行。用裸机实现你需要小心翼翼地维护一堆全局变量和复杂的判断逻辑用FreeRTOS事件组几行代码就能清晰表达。本实验将基于STM32CubeIDE带你从零开始构建一个使用FreeRTOS事件组的典型应用场景。我们不仅会完成代码编写更会深入理解事件组背后的位操作原理、任务阻塞与唤醒的机制以及在实际使用中如何避免常见的坑比如事件位溢出、优先级反转的潜在风险等。你会发现掌握了事件组你的多任务程序设计会变得更加优雅和健壮。2. 实验环境搭建与FreeRTOS基础配置2.1 STM32CubeIDE工程创建与芯片选型首先打开STM32CubeIDE选择“Start new STM32 project”。在弹出的芯片选择器中根据你手头的开发板进行选择。为了具有普遍性我们以STM32F407VET6这款中高端芯片为例它拥有足够的RAM和Flash来流畅运行FreeRTOS。选中芯片后给工程起个名字比如FreeRTOS_Event_Demo路径选择合适的位置确保“Targeted Language”为C然后点击“Finish”。工程创建完成后会自动进入STM32CubeMX的图形化配置界面。在这里我们先进行最基础的硬件配置。根据实验需求我们至少需要一个串口用于打印调试信息一个GPIO引脚连接LED用于指示状态一个GPIO引脚连接按键用于触发事件。配置系统时钟在“Pinout Configuration”标签页的“System Core”里找到“RCC”。将“High Speed Clock (HSE)”设置为“Crystal/Ceramic Resonator”。这样我们使用外部高速晶振。配置调试接口在“System Core” - “SYS”中将“Debug”设置为“Serial Wire”。这对于使用ST-Link进行调试和下载是必须的。配置USART1用于打印在左侧“Connectivity”中找到“USART1”。将其模式Mode设置为“Asynchronous”。然后在下方“Parameter Settings”中配置波特率为115200字长8位无校验1位停止位。接着你需要到“Pinout”视图上找到USART1的TX和RX引脚通常是PA9和PA10确保它们已被正确分配。配置用户LED和按键假设LED连接在PC13像很多开发板上的用户LED一样按键连接在PA0外部下拉按下为高电平。在“Pinout”视图找到PC13右键选择“GPIO_Output”并将其用户标签User Label设置为“USER_LED”。同样找到PA0右键选择“GPIO_Input”并将其用户标签设置为“USER_BTN”。对于按键通常还需要在“System Core” - “GPIO”中点击PA0将其“GPIO Pull-up/Pull-down”设置为“Pull-down”这样按键未按下时引脚电平稳定在低电平。2.2 FreeRTOS中间件使能与关键参数详解接下来是核心步骤启用FreeRTOS。在左侧“Middleware”分类下找到“FREERTOS”。将“Interface”从“Disabled”改为“CMSIS_V2”。CMSIS-RTOS V2是一个由ARM定义的通用RTOS抽象层它让你的应用程序代码与FreeRTOS内核解耦未来如果你想换用其他兼容CMSIS-RTOS V2的RTOS比如Azure RTOS ThreadX理论上只需更换底层实现应用层代码改动很小。启用后会多出很多配置项我们聚焦几个关键的configTOTAL_HEAP_SIZE这是FreeRTOS动态内存堆的总大小。所有内核对象任务、队列、信号量、事件组等创建时默认都从这个堆中分配内存。对于STM32F407默认的81928KB是个不错的起点。但要注意如果你创建了很多任务或大型队列这个值可能需要增加。你可以在工程生成的FreeRTOSConfig.h文件中后期修改它。一个快速估算方法是每个任务栈 任务控制块(TCB) 你创建的各种内核对象。在开发阶段可以适当设大一点如20KB并通过xPortGetFreeHeapSize()函数在运行时监控堆的使用情况。configUSE_16_BIT_TICKS设置为0。这意味着系统节拍计数器Tick Count使用32位。对于16位当系统时钟为1kHz时大约每65.5秒就会溢出一次这可能引发一些时间计算上的问题虽然FreeRTOS有处理机制。对于32位在1kHz下溢出时间约为49天安全得多。configUSE_PREEMPTION和configUSE_TIME_SLICING通常保持默认1和1。configUSE_PREEMPTION1启用抢占式调度高优先级任务可以抢占低优先级任务。configUSE_TIME_SLICING1启用时间片轮转使得同等优先级的任务可以分时共享CPU。configMAX_PRIORITIES最大优先级数量。默认值56足够用了。注意FreeRTOS中数值越大优先级越高。通常不建议设置太多优先级层次5-7个就足以构建清晰的系统。configUSE_TICKLESS_IDLE低功耗模式相关。如果你的应用对功耗不敏感可以先保持为0。如果启用需要仔细配置因为它会影响系统节拍的准确性。配置完成后点击右上角的“GENERATE CODE”生成代码。STM32CubeIDE会自动生成初始化代码、FreeRTOS的移植层代码以及一个基本的main.c框架。3. 事件组Event Groups的核心原理与API剖析在生成代码后我们暂时不急着写应用逻辑先彻底搞懂事件组到底是什么。事件组本质上是一个EventBits_t类型的变量这个类型在FreeRTOSConfig.h中定义通常是uint32_t或uint64_t取决于configUSE_16_BIT_TICKS等配置。这意味着一个事件组最多可以管理32个或64个独立的事件位Event Bits。你可以把每个位想象成一个布尔标志。事件组的强大之处在于它的“等待”操作。一个任务可以等待事件组中的某一位或某几位被设置并且可以指定等待条件是等待所有指定位都被设置逻辑与还是等待任意一个指定位被设置逻辑或。在等待期间任务会进入阻塞状态Blocked State不消耗CPU时间直到等待的条件满足或者指定的超时时间到期。FreeRTOS提供了几个核心API我们通过一个表格来快速理解API 函数主要功能描述关键参数解析xEventGroupCreate()动态创建一个事件组返回其句柄。无参数。从FreeRTOS堆中分配内存。xEventGroupSetBits()设置事件组中的一个或多个位。xEventGroup: 事件组句柄。uxBitsToSet: 要设置的位掩码。例如要设置位0和位2则传入(1UL 0) | (1UL 2)。xEventGroupClearBits()清除事件组中的一个或多个位。xEventGroup: 事件组句柄。uxBitsToClear: 要清除的位掩码。xEventGroupWaitBits()等待事件组中的一个或多个位被设置。这是使任务阻塞的关键函数。xEventGroup: 事件组句柄。uxBitsToWaitFor: 要等待的位掩码。xClearOnExit: 退出时是否清除等待的位。若为pdTRUE则条件满足后自动清除这些位常用于一次性事件。xWaitForAllBits: 等待逻辑。pdTRUE表示等待所有位被设置pdFALSE表示等待任意一位被设置。xTicksToWait: 最大阻塞时间以系统节拍为单位。可使用portMAX_DELAY无限等待。xEventGroupSync()同步多个任务。一个“屏障”Barrier操作等待多个任务都设置好各自的位然后同时唤醒它们。xEventGroup: 事件组句柄。uxBitsToSet: 本任务要设置的位。uxBitsToWaitFor: 要等待的所有位掩码。xTicksToWait: 超时时间。注意xEventGroupSetBits()和xEventGroupClearBits()有两个变体xEventGroupSetBitsFromISR()和xEventGroupClearBitsFromISR()专用于中断服务程序ISR中调用。因为ISR中不能进行可能导致任务切换的阻塞操作这些FromISR函数会将设置事件位的操作通过一个守护任务Daemon Task或直接处理取决于配置来延迟到任务上下文中执行从而保证内核数据结构的完整性。在中断中操作事件组时务必使用FromISR版本。理解位掩码Bit Mask是操作事件组的关键。我们通常用(1UL n)来定义第n位的事件。例如#define EVENT_BIT_0 (1UL 0) // 第0位表示按键按下事件 #define EVENT_BIT_1 (1UL 1) // 第1位表示数据接收完成事件 #define EVENT_BIT_2 (1UL 2) // 第2位表示定时器超时事件这样代码的可读性会大大提高。4. 实验场景设计与任务创建现在我们来设计一个具体的实验场景模拟一个简单的智能设备控制逻辑任务A按键检测任务优先级为2。循环检测按键USER_BTN是否按下。如果按下去抖动后设置“按键事件”例如EVENT_BIT_0并让LED快速闪烁一次作为反馈。任务B数据处理任务优先级为3最高。它等待两个事件EVENT_BIT_0按键事件和EVENT_BIT_1一个模拟的“数据就绪”事件我们用一个定时器周期性设置。它需要等待这两个事件都发生xWaitForAllBits pdTRUE后才执行一次“数据处理”操作比如通过串口打印一条特定消息然后清除等待的事件位继续等待下一轮。任务C模拟数据源任务优先级为1最低。它每隔5秒自动设置一次EVENT_BIT_1模拟数据就绪。这个设计体现了事件组的典型应用任务B需要多个前置条件同时满足才能执行。同时我们也创建了不同优先级的任务可以观察调度器的行为。首先在main.c的/* USER CODE BEGIN PV */区域定义事件组句柄和事件位/* Private variables ---------------------------------------------------------*/ EventGroupHandle_t xEventGroup; // 事件组句柄 #define EVENT_BIT_KEY (1UL 0) // 按键事件位 #define EVENT_BIT_DATA (1UL 1) // 数据就绪事件位接着在main()函数中MX_FREERTOS_Init()调用之前创建事件组/* USER CODE BEGIN 2 */ // 创建事件组 xEventGroup xEventGroupCreate(); if(xEventGroup NULL) { // 事件组创建失败通常是因为堆内存不足 Error_Handler(); } /* USER CODE END 2 */然后我们需要修改自动生成的Freertos.c文件或直接在main.c中创建任务。更清晰的做法是在main.c的/* USER CODE BEGIN 4 */区域编写任务函数然后在/* USER CODE BEGIN RTOS_THREADS */区域调用xTaskCreate来创建它们。STM32CubeIDE生成的代码通常建议在Freertos.c的StartDefaultTask函数里创建其他任务但为了集中管理我们直接在main.c里做。在/* USER CODE BEGIN 4 */区域编写三个任务函数/* 任务A按键检测任务 */ void vTaskKeyScan(void *argument) { uint32_t last_tick 0; const TickType_t debounce_delay pdMS_TO_TICKS(50); // 50ms消抖延时 for(;;) { if(HAL_GPIO_ReadPin(USER_BTN_GPIO_Port, USER_BTN_Pin) GPIO_PIN_SET) { // 检测到按键按下进行消抖 vTaskDelay(debounce_delay); if(HAL_GPIO_ReadPin(USER_BTN_GPIO_Port, USER_BTN_Pin) GPIO_PIN_SET) { // 确认按下设置按键事件位 xEventGroupSetBits(xEventGroup, EVENT_BIT_KEY); // LED快速闪烁一次作为反馈 HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, GPIO_PIN_SET); vTaskDelay(pdMS_TO_TICKS(100)); HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, GPIO_PIN_RESET); // 等待按键释放避免连续触发 while(HAL_GPIO_ReadPin(USER_BTN_GPIO_Port, USER_BTN_Pin) GPIO_PIN_SET) { vTaskDelay(10); } } } vTaskDelay(10); // 每10ms扫描一次按键避免占用过多CPU } } /* 任务B数据处理任务高优先级 */ void vTaskDataProcess(void *argument) { EventBits_t uxBits; const TickType_t xMaxBlockTime pdMS_TO_TICKS(10000); // 最大等待10秒 for(;;) { // 等待按键事件和数据就绪事件同时发生 uxBits xEventGroupWaitBits( xEventGroup, // 事件组句柄 EVENT_BIT_KEY | EVENT_BIT_DATA, // 等待的位位0和位1 pdTRUE, // 退出前清除等待的位 pdTRUE, // 等待 ALL 指定的位被设置 xMaxBlockTime // 阻塞时间 ); if((uxBits (EVENT_BIT_KEY | EVENT_BIT_DATA)) (EVENT_BIT_KEY | EVENT_BIT_DATA)) { // 成功等到两个事件 printf([DataProcess] Both KEY and DATA events received! Processing...\r\n); // 这里可以执行实际的数据处理操作 // 例如将模拟数据打包发送等 } else { // 超时未等到所需事件 printf([DataProcess] Wait timeout! Bits value: 0x%lx\r\n, uxBits); } // 任务B执行完后循环回到开头继续等待下一组事件 } } /* 任务C模拟数据源任务低优先级 */ void vTaskDataSimulator(void *argument) { const TickType_t xPeriod pdMS_TO_TICKS(5000); // 5秒周期 for(;;) { vTaskDelay(xPeriod); // 每隔5秒 printf([DataSimulator] Setting DATA ready event bit.\r\n); xEventGroupSetBits(xEventGroup, EVENT_BIT_DATA); // 设置数据就绪事件位 } }注意我们使用了printf进行串口打印你需要确保工程中重定向了printf到串口。通常的做法是在main.c中添加以下代码#ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif PUTCHAR_PROTOTYPE { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, HAL_MAX_DELAY); return ch; }并勾选“Use MicroLIB”在Project Properties - C/C Build - Settings - Tool Settings - MCU GCC Linker - Miscellaneous中或者使用newlib-nano并自行实现_write等系统调用。最后在/* USER CODE BEGIN RTOS_THREADS */区域创建这三个任务。注意我们需要先删除或注释掉CubeIDE默认生成的StartDefaultTask任务创建代码然后添加我们自己的/* USER CODE BEGIN RTOS_THREADS */ /* 创建按键检测任务栈大小128字优先级2 */ xTaskCreate(vTaskKeyScan, TaskKeyScan, 128, NULL, 2, NULL); /* 创建数据处理任务栈大小256字优先级3最高 */ xTaskCreate(vTaskDataProcess, TaskDataProcess, 256, NULL, 3, NULL); /* 创建数据模拟任务栈大小128字优先级1最低 */ xTaskCreate(vTaskDataSimulator, TaskDataSimulator, 128, NULL, 1, NULL); /* USER CODE END RTOS_THREADS */这里栈大小的单位是“字”Word在32位ARM Cortex-M内核中1字等于4字节。所以128字就是512字节。对于简单的任务128-256字通常足够。对于有局部数组、调用深层次函数的复杂任务需要增大栈大小。栈溢出是FreeRTOS调试中最常见的问题之一我们可以通过configCHECK_FOR_STACK_OVERFLOW配置项来启用栈溢出检测。5. 编译、调试与现象分析点击STM32CubeIDE的编译按钮小锤子。首次编译可能会花费一些时间。如果一切配置正确编译应该成功。将开发板通过ST-Link连接到电脑点击调试按钮小虫子。程序会下载并运行。打开串口调试助手如Putty、SecureCRT等配置波特率为115200。你将会看到如下现象每隔5秒串口会打印一次[DataSimulator] Setting DATA ready event bit.。这是任务C在正常工作。此时如果你按下按键USER_BTN用户LED会快速闪烁一次但串口不会有其他输出。因为此时只有EVENT_BIT_KEY被设置任务B在等待EVENT_BIT_KEY和EVENT_BIT_DATA同时被设置所以它继续阻塞。在任务C下一次设置EVENT_BIT_DATA即打印模拟数据就绪消息之后由于EVENT_BIT_KEY位早已被设置且xClearOnExit为pdTRUE但任务B尚未退出等待所以位还未被清除这里有个关键点需要澄清任务B的等待条件得到满足。实际上xEventGroupWaitBits的xClearOnExit参数仅在函数成功返回前清除指定的位。所以当按键先按下EVENT_BIT_KEY被设置并保持5秒后EVENT_BIT_DATA被设置任务B立即被唤醒并在返回前清除EVENT_BIT_KEY和EVENT_BIT_DATA这两个位。此时串口会打印出[DataProcess] Both KEY and DATA events received! Processing...。如果10秒内都没有等到两个事件同时发生任务B会超时退出并打印超时信息和当前事件组的值。这个实验清晰地展示了事件组如何用于同步多个异步事件。你可以尝试修改xEventGroupWaitBits的xWaitForAllBits参数为pdFALSE观察任务B是否在任意一个事件按键或定时数据就绪发生时就被唤醒。6. 深入排查事件组的常见陷阱与高级用法在实际项目中使用事件组可能会遇到一些不那么直观的问题。下面分享几个我踩过的坑和对应的解决方案。6.1 事件位的“粘性”与自动清除策略在上面的实验中我们设置了xClearOnExit pdTRUE。这意味着任务B成功等到事件后它等待的那些位EVENT_BIT_KEY | EVENT_BIT_DATA会被自动清除。这非常适合“一次性触发”的场景。但有时你需要的是“广播”或“状态”型事件。例如一个“系统初始化完成”事件多个任务都可能等待它且它一旦发生就永远有效。这时你应该使用xClearOnExit pdFALSE并且在设置事件位时使用xEventGroupSetBits()。等待的任务在条件满足后事件位依然保留其他等待该事件位的任务也会被唤醒如果等待条件是pdFALSE即任意位。或者你也可以让某个高优先级的管理任务在确认所有相关任务都收到通知后手动调用xEventGroupClearBits()来清除事件位。关键点仔细设计事件位的生命周期。问自己这个事件是瞬态信号还是持久状态是否需要手动清除由谁负责清除6.2 在中断服务程序ISR中使用事件组这是新手极易出错的地方。假设我们有一个外部中断引脚按键按下触发中断我们希望在ISR中设置事件位。绝对不能在ISR中直接调用xEventGroupSetBits()必须使用xEventGroupSetBitsFromISR()。它的原型是BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken );这个函数不会直接操作事件组而是将一个“设置事件位”的请求发送到FreeRTOS的定时器服务任务Timer Service Task的队列中。该任务拥有一个默认的、较高的优先级configTIMER_TASK_PRIORITY它会在退出中断后在任务上下文中执行实际的置位操作并可能唤醒等待该事件位的任务。pxHigherPriorityTaskWoken参数是一个输出参数。如果发送操作唤醒了某个优先级高于当前运行任务即被中断的任务的任务这个参数会被设置为pdTRUE。此时在ISR退出前我们应该进行一次上下文切换检查。新的FreeRTOS移植层通常使用portYIELD_FROM_ISR()宏。一个正确的中断处理示例如下// 假设在GPIO外部中断回调函数中 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { BaseType_t xHigherPriorityTaskWoken pdFALSE; if(GPIO_Pin USER_BTN_Pin) { // 在ISR中设置事件位 xEventGroupSetBitsFromISR(xEventGroup, EVENT_BIT_KEY, xHigherPriorityTaskWoken); // 如果需要进行任务切换 portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } }注意要使用FromISR函数必须确保FreeRTOS配置中configUSE_TIMERS被设置为1因为底层依赖定时器服务任务。6.3 优先级反转的潜在风险与xEventGroupSync事件组本身不直接导致优先级反转但结合任务优先级设计可能引发类似问题。考虑这个场景低优先级任务L设置了事件位A高优先级任务H正在等待事件位A和BxWaitForAllBits pdTRUE。中优先级任务M一直就绪。当L设置了A后H因为还在等B仍然阻塞。此时M被调度运行它会一直运行直到时间片用完或被更高优先级任务抢占。这导致了虽然H的等待条件完成了一半但它仍然无法运行因为M在“捣乱”。这不是经典的优先级反转涉及资源互斥但效果类似——高优先级任务被不必要的延迟。对于需要多个任务同步到达某个点的场景例如多个数据采集任务完成后启动一个数据处理任务xEventGroupSync()是比分别设置和等待更安全、更原子化的选择。xEventGroupSync()会先设置调用任务自己的位然后等待所有指定位被设置。这个“设置-等待”操作是原子的避免了在设置和等待之间被其他任务打断可能带来的竞态条件。它实现了“屏障”同步模式。6.4 调试技巧监控事件组的状态当程序行为不符合预期时查看事件组当前的值非常有用。虽然FreeRTOS没有直接提供打印事件组所有位的函数但我们可以通过xEventGroupGetBits()或xEventGroupGetBitsFromISR()来获取其值然后以二进制或十六进制形式打印出来。EventBits_t uxCurrentBits; uxCurrentBits xEventGroupGetBits(xEventGroup); printf(Current Event Group Bits: 0x%08lX\r\n, (unsigned long)uxCurrentBits);通过观察位的变化可以清晰地知道是哪个任务没有正确设置事件或是等待的条件逻辑是否有误。另一个强大的调试工具是FreeRTOS的trace功能但需要额外的软件如Percepio Tracealyzer支持它可以图形化地展示任务状态切换、内核对象操作等对于复杂系统的调试事半功倍。7. 从实验到实战事件组在复杂系统中的应用模式掌握了基础操作和避坑指南后我们可以看看事件组在更复杂的系统中如何大显身手。模式一复合条件触发器这是最经典的模式正如我们的实验所示。一个任务如控制任务的执行需要多个前置条件如“传感器数据有效”、“用户指令到达”、“系统状态正常”。每个条件由不同的异步任务或中断设置对应的事件位。控制任务使用xEventGroupWaitBits(..., pdTRUE, pdTRUE, ...)等待所有位。这种模式极大地降低了任务间的耦合度每个条件产生者无需知道消费者是谁。模式二选择性等待OR逻辑监控任务可能需要响应多种不同的告警。例如一个系统健康监测任务需要等待“温度过高”、“电压过低”、“通信超时”等事件中的任意一个发生。这时使用xEventGroupWaitBits(..., pdTRUE, pdFALSE, ...)xWaitForAllBits pdFALSE。一旦任一告警事件发生监测任务立即被唤醒然后通过检查返回值uxBits来确定具体是哪个事件并执行相应的处理程序。模式三轻量化的状态机事件组可以作为一个轻量级的、多任务共享的状态机。系统的不同状态用不同的事件位来表示。一个任务负责根据逻辑设置状态位如STATE_IDLE,STATE_RUNNING,STATE_ERROR其他任务可以等待特定的状态组合。注意这种用法下通常需要手动管理状态的切换和清除避免状态混乱。模式四替代多个二值信号量如果你有多个任务需要等待同一个事件但又希望事件发生后能唤醒所有等待者使用信号量就需要创建多个管理起来麻烦。使用事件组设置一次事件位所有等待该事件位且满足等待逻辑的任务都会被唤醒。这实现了“一对多”的通知机制。最后关于性能事件组的操作设置、清除、等待都是非常高效的主要涉及位运算和任务链表操作。在资源紧张的STM32上它是实现任务间同步通信的优选方案之一。当然对于纯粹的数据传输队列仍然是更合适的选择。理解每种通信机制队列、信号量、互斥量、事件组、任务通知的适用场景是构建健壮高效FreeRTOS应用的关键。事件组正是你工具箱中处理多条件同步问题时那把最趁手的扳手。
返回列表