尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

5分钟学习笔记(FreeRTOS)(七)

5分钟学习笔记(FreeRTOS)(七)
📅 发布时间:2026/7/10 6:00:04

一、EventGroup 事件组

EventGroup 是 FreeRTOS 中用于任务同步的一种机制。事件组本质上是一组事件标志位,每一个 bit 可以表示一个事件是否发生。

例如:

#define EVENT_UART_RX (1UL << 0) #define EVENT_CAN_RX (1UL << 1) #define EVENT_ADC_DONE (1UL << 2) #define EVENT_FLASH_OK (1UL << 3)

这几个 bit 可以放在同一个 EventGroup 中,用来表示不同事件:

bit0:串口接收事件 bit1:CAN 接收事件 bit2:ADC 采样完成事件 bit3:Flash 操作完成事件

任务可以通过xEventGroupWaitBits()等待一个或多个事件位。

当配置为等待任意 bit 时,只要任务关心的事件中有任意一个发生,任务就会被唤醒,这相当于逻辑或。

当配置为等待所有 bit 时,只有任务关心的所有事件都发生后,任务才会被唤醒,这相当于逻辑与。

EventGroup 的事件位具有保持作用,不要求多个事件在同一时刻发生。例如 bit0 先被置位,bit1 后被置位,只要两个 bit 最终都满足,等待所有 bit 的任务就可以被唤醒。

需要注意的是,EventGroup 只表示事件状态,不传递具体数据。如果需要传递数据内容,应使用队列;如果只是中断一对一通知任务,可以使用任务通知;如果是保护共享资源,应使用 Mutex。

另外,EventGroup 并不是所有 bit 都可以给用户使用。FreeRTOS 内部会占用高 8 位。通常情况下:

EventBits_t 为 32 位时,用户一般可用 24 个事件 bit EventBits_t 为 16 位时,用户一般可用 8 个事件 bit

所以实际工程中建议从 bit0 开始依次定义事件位,不要随意使用高位 bit。


二、EventGroup 创建

使用事件组前,需要先创建 EventGroup。

#include "FreeRTOS.h" #include "event_groups.h" #define EVENT_UART_RX (1UL << 0) #define EVENT_CAN_RX (1UL << 1) #define EVENT_ADC_DONE (1UL << 2) static EventGroupHandle_t g_event_group = NULL; void App_EventGroupInit(void) { g_event_group = xEventGroupCreate(); if(g_event_group == NULL) { Error_Handler(); } }

xEventGroupCreate()创建成功后,会返回一个事件组句柄。后续设置事件位、等待事件位,都需要使用这个句柄。


三、等待任意一个事件

下面这个例子表示:任务等待 UART 或 CAN 任意一个事件发生。

#define EVENT_UART_RX (1UL << 0) #define EVENT_CAN_RX (1UL << 1) extern EventGroupHandle_t g_event_group; void CommTask(void *argument) { EventBits_t bits; while(1) { bits = xEventGroupWaitBits(g_event_group, EVENT_UART_RX | EVENT_CAN_RX, pdTRUE, // 退出时清除已满足的 bit pdFALSE, // 等任意一个 bit portMAX_DELAY); if(bits & EVENT_UART_RX) { // 处理串口接收事件 } if(bits & EVENT_CAN_RX) { // 处理 CAN 接收事件 } } }

这里:

xWaitForAllBits = pdFALSE;

表示等待任意一个 bit。

只要EVENT_UART_RX或EVENT_CAN_RX任意一个事件发生,CommTask就会被唤醒。


四、等待所有事件都发生

下面这个例子表示:任务需要等待文件名正确、Flash 擦除完成、CRC 校验正确,三个条件全部满足后才继续执行。

#define EVENT_FILE_NAME_OK (1UL << 0) #define EVENT_FLASH_ERASE_OK (1UL << 1) #define EVENT_CRC_OK (1UL << 2) #define EVENT_UPGRADE_ALL_OK (EVENT_FILE_NAME_OK | \ EVENT_FLASH_ERASE_OK | \ EVENT_CRC_OK) extern EventGroupHandle_t g_event_group; void UpgradeTask(void *argument) { EventBits_t bits; while(1) { bits = xEventGroupWaitBits(g_event_group, EVENT_UPGRADE_ALL_OK, pdTRUE, // 退出时清除 pdTRUE, // 等所有 bit portMAX_DELAY); if((bits & EVENT_UPGRADE_ALL_OK) == EVENT_UPGRADE_ALL_OK) { // 文件名正确、擦除完成、CRC 正确 // 可以进入下一步升级流程 } } }

这里:

xWaitForAllBits = pdTRUE;

表示必须所有指定 bit 都被置位,等待条件才成立。

这类用法适合:

多个条件最终都要满足 但这些条件不一定在同一时刻发生

五、xEventGroupWaitBits() 函数说明

函数原型:

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

这个函数用于等待事件组中的一个或多个事件位。

1. xEventGroup

事件组句柄,由xEventGroupCreate()创建。

2. uxBitsToWaitFor

指定任务要等待哪些 bit,也就是任务关心哪些事件。

例如:

EVENT_UART_RX | EVENT_CAN_RX

表示任务关心 UART 接收事件和 CAN 接收事件。

3. xClearOnExit

xClearOnExit表示当等待条件满足并且函数返回时,是否自动清除对应事件位。

如果设置为:

xClearOnExit = pdTRUE;

表示函数因为事件满足而返回前,会清除uxBitsToWaitFor中已经满足的事件位。

如果设置为:

xClearOnExit = pdFALSE;

表示函数返回时不改变事件组中的 bit。

例如:

xEventGroupWaitBits(g_event_group, EVENT_UART_RX, pdTRUE, pdFALSE, pdMS_TO_TICKS(100));

如果 100ms 内EVENT_UART_RX发生了,函数返回前会清除EVENT_UART_RX。

如果 100ms 到了还没发生,函数超时返回,不会因为xClearOnExit去清除EVENT_UART_RX。

使用xClearOnExit = pdTRUE时要注意一个问题:

xEventGroupWaitBits(g_event_group, EVENT_UART_RX | EVENT_CAN_RX, pdTRUE, pdFALSE, portMAX_DELAY);

这表示等待 UART 或 CAN 任意一个事件。

如果 UART 和 CAN 两个 bit 都已经置位,那么函数返回前可能会把两个 bit 都清除。所以必须根据返回值bits及时判断并处理对应事件。

如果希望更可控,也可以设置:

xClearOnExit = pdFALSE;

然后处理完事件后手动清除:

xEventGroupClearBits(g_event_group, EVENT_UART_RX);

4. xWaitForAllBits

xWaitForAllBits用来决定等待条件是逻辑或还是逻辑与。

如果设置为:

xWaitForAllBits = pdFALSE;

表示uxBitsToWaitFor中任意一个 bit 被置位,等待条件就满足。

如果设置为:

xWaitForAllBits = pdTRUE;

表示必须所有指定 bit 都被置位,等待条件才满足。

5. xTicksToWait

xTicksToWait表示当等待条件不满足时,任务最多阻塞等待多少 tick。

例如:

0

表示不等待,立即检查事件位,条件不满足就立即返回。

pdMS_TO_TICKS(100)

表示最多等待 100ms。

portMAX_DELAY

通常表示一直等待,直到条件满足。

6. 返回值

xEventGroupWaitBits()的返回值类型是:

EventBits_t

返回值表示函数返回时事件组中的 bit 状态。使用时应该根据返回值判断具体是哪一个事件发生。

例如:

EventBits_t bits; bits = xEventGroupWaitBits(g_event_group, EVENT_UART_RX | EVENT_CAN_RX, pdTRUE, pdFALSE, portMAX_DELAY); if(bits & EVENT_UART_RX) { // UART 事件发生 } if(bits & EVENT_CAN_RX) { // CAN 事件发生 }

即使xClearOnExit设置为pdTRUE,返回值bits仍然可以用来判断本次是哪些事件满足了条件。


六、软件定时器

软件定时器是 FreeRTOS 提供的一种定时机制,它基于系统 Tick 和 Timer Service Task 实现,不需要每个定时功能都占用一个硬件定时器。

软件定时器创建后,启动定时器。当经过设定的 tick 数后,FreeRTOS 会在 Timer Service Task 中调用用户注册的回调函数。

软件定时器的定时精度与系统 Tick 周期有关,同时也会受到任务调度、Timer Service Task 优先级和系统负载影响。

软件定时器适合低精度定时任务,例如:

LED 周期闪烁 通信超时检测 按键消抖 状态机超时 心跳包发送

不适合高精度实时控制,例如:

PWM 输出 ADC 精确定时采样 电机电流环 微秒级延时

七、软件定时器相关配置

使用 FreeRTOS 软件定时器,需要在FreeRTOSConfig.h中打开相关配置。

常见配置如下:

#define configUSE_TIMERS 1 #define configTIMER_TASK_PRIORITY 2 #define configTIMER_QUEUE_LENGTH 10 #define configTIMER_TASK_STACK_DEPTH 256 #define configTICK_RATE_HZ 1000

含义:

configUSE_TIMERS: 是否启用软件定时器。 configTIMER_TASK_PRIORITY: Timer Service Task 的优先级。 configTIMER_QUEUE_LENGTH: 软件定时器命令队列长度。 configTIMER_TASK_STACK_DEPTH: Timer Service Task 的栈大小。 configTICK_RATE_HZ: 系统 Tick 频率。

例如:

#define configTICK_RATE_HZ 1000

表示 1 秒产生 1000 个 Tick,也就是:

1 tick = 1ms

实际工程中,通常使用pdMS_TO_TICKS()把毫秒转换为 tick。


八、单次定时器

单次定时器启动后,定时时间到达时只执行一次回调函数。回调执行完后,定时器停止,但不会自动删除。

如果需要再次使用,可以重新调用:

xTimerStart();

或者:

xTimerReset();

如果不再使用,需要调用:

xTimerDelete();

示例代码:

#include "FreeRTOS.h" #include "task.h" #include "timers.h" static TimerHandle_t g_one_shot_timer = NULL; static void OneShotTimerCallback(TimerHandle_t xTimer) { (void)xTimer; /* 单次定时器到期后执行一次这里。 回调执行完后,定时器停止,但不会自动删除。 注意:不要在软件定时器回调中写耗时操作。 */ LED_Toggle(); } void App_OneShotTimerCreate(void) { g_one_shot_timer = xTimerCreate("OneShot", pdMS_TO_TICKS(1000), pdFALSE, NULL, OneShotTimerCallback); if(g_one_shot_timer == NULL) { Error_Handler(); } if(xTimerStart(g_one_shot_timer, 0) != pdPASS) { Error_Handler(); } }

这个例子表示:

启动后 1000ms 执行一次 OneShotTimerCallback() 执行完后定时器停止 定时器对象不会自动删除

如果想再次启动:

xTimerStart(g_one_shot_timer, 0);

如果不再使用:

xTimerDelete(g_one_shot_timer, 0);

九、周期定时器

周期定时器启动后,会按照设置的周期反复执行回调函数,直到用户调用xTimerStop()停止,或者调用xTimerDelete()删除。

示例代码:

#include "FreeRTOS.h" #include "task.h" #include "timers.h" static TimerHandle_t g_period_timer = NULL; static void PeriodTimerCallback(TimerHandle_t xTimer) { (void)xTimer; /* 周期执行。 注意:不要在这里写耗时操作。 */ LED_Toggle(); } void App_PeriodTimerCreate(void) { g_period_timer = xTimerCreate("PeriodTimer", pdMS_TO_TICKS(500), pdTRUE, NULL, PeriodTimerCallback); if(g_period_timer == NULL) { Error_Handler(); } if(xTimerStart(g_period_timer, 0) != pdPASS) { Error_Handler(); } }

这个例子表示:

每 500ms 执行一次 PeriodTimerCallback() 直到用户调用 xTimerStop() 或 xTimerDelete()

停止周期定时器:

xTimerStop(g_period_timer, 0);

删除周期定时器:

xTimerDelete(g_period_timer, 0);

十、软件定时器回调注意事项

软件定时器的回调函数不是在硬件中断中执行,而是在 FreeRTOS 的 Timer Service Task 中执行。

因此,软件定时器回调函数中不要执行耗时操作,也不要长时间阻塞。

不建议在回调函数中做这些事情:

Flash 擦除 大量数据处理 长时间 while 循环 等待队列、信号量、事件组 调用长时间阻塞的 API

因为所有软件定时器回调都由同一个 Timer Service Task 调用。如果某个回调函数执行时间过长,会影响其他软件定时器的回调执行。

比较推荐的做法是:

软件定时器回调中只做简单标志置位或通知任务 真正耗时的处理放到任务中完成

例如:

static void TimeoutCallback(TimerHandle_t xTimer) { (void)xTimer; xEventGroupSetBits(g_event_group, EVENT_TIMEOUT); }

然后在任务中处理超时事件。


十一、EventGroup 和软件定时器的典型组合

在工程中,EventGroup 和软件定时器经常配合使用。

例如 IAP 升级超时检测:

收到升级开始命令 ↓ 启动单次软件定时器 ↓ 等待升级数据帧 ↓ 每收到一帧合法数据,重置软件定时器 ↓ 如果长时间没有收到下一帧,软件定时器超时 ↓ 定时器回调设置 EVENT_TIMEOUT 事件位 ↓ UpgradeTask 被唤醒,进入超时错误处理

这种设计的好处是:

软件定时器负责判断是否超时 EventGroup 负责通知任务发生了超时事件 任务负责真正的错误处理

定时器回调中只设置事件位,不做复杂处理。


十二、总结

EventGroup 适合处理多个事件条件的同步。每一个 bit 可以表示一个事件,任务可以等待任意一个 bit,也可以等待所有指定 bit。EventGroup 只表示事件状态,不传递具体数据。

xEventGroupWaitBits()是等待事件位的核心 API。其中uxBitsToWaitFor指定等待哪些 bit,xClearOnExit决定返回时是否清除事件位,xWaitForAllBits决定等待任意 bit 还是所有 bit,xTicksToWait决定最大阻塞时间。函数返回值EventBits_t可以用来判断具体哪些事件发生。

软件定时器是 FreeRTOS 基于系统 Tick 和 Timer Service Task 实现的定时机制,适合低精度定时任务。单次定时器到期后执行一次回调并停止,但不会自动删除;周期定时器到期后会自动重新装载并继续运行。

软件定时器回调函数中不应执行耗时或长时间阻塞的操作。更好的工程设计是:回调函数中只设置事件位或通知任务,真正的数据处理、Flash 操作、协议处理等耗时工作放到任务中完成。

一句话总结:

EventGroup 用来等多个事件条件,软件定时器用来做低精度超时和周期触发,两者配合可以很好地实现任务同步和超时控制。

相关新闻

  • 我有个问题,anaconda管理的环境有些不同环境却要用同样的库,下两份不会占用空间吗
  • Pearcleaner:让Mac告别“数字幽灵“的终极清理方案
  • 2026年天津滨海全屋定制哪家好?5家环保与收纳实力派专业推荐 - 本地品牌推荐

最新新闻

  • 劳力士官方更换原装表带价格查询|全新维修门店地址及电话权威信息公告(2026年7月最新) - 劳力士官方服务中心
  • 自主获取最新相关文献:codex+ollama自动化办公
  • 2026年7月最新百达翡丽广州王府井百货维修保养服务电话 - 百达翡丽服务中心
  • Python 接入美股实时行情 API:AAPL.US ticker、K线和 timestamp 字段校验
  • Rust编写的DeepSeek命令行工具deepseek-tui实战指南
  • 5分钟掌握AMD Ryzen处理器性能调校:RyzenAdj终极优化指南

日新闻

  • OpenClaw本地化部署:xParse文档解析引擎实战指南
  • 蓝牙 5.4 协议栈深度解析:从 HCI 到 L2CAP 的 7 层数据流
  • PyTorch nn.CrossEntropyLoss 实战:3种权重设置与标签平滑对比(附代码)

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号