当前位置:首页 > 工业控制 > 电路设计项目集锦
[导读]该项目是一个基于ESP-IDF提供的原生FreeRTOS API构建的模拟ESP32房间监控系统。它展示了并发嵌入式系统的实现方式:一组相互协作的任务,每个任务都有明确的职责,通过队列和事件组进行通信,在必要时使用互斥锁(mutex)实现同步,并由状态机驱动。

项目概述

该项目是一个基于ESP-IDF提供的原生FreeRTOS API构建的模拟ESP32房间监控系统。它展示了并发嵌入式系统的实现方式:一组相互协作的任务,每个任务都有明确的职责,通过队列和事件组进行通信,在必要时使用互斥锁(mutex)实现同步,并由状态机驱动。

该系统已在Wokwi模拟器中验证,并包含13个单元测试,涵盖纯决策逻辑、人为注入的故障实验以及静态分析。

动机

大多数初学者教程都将所有应用程序代码放入一个 `loop()` 函数或一个 FreeRTOS 任务中。这种方法适用于小型程序,但却掩盖了并发系统所存在的各类问题:竞态条件、优先级反转、任务饥饿以及未被检测的阻塞。本项目则恰恰相反——将每个行为独立放置在各自的任务中,明确指定优先级并加以说明,并通过单元测试和故障注入来验证每项交互。

功能

•每2秒通过DHT22采样一次温度和湿度

•通过LDR(0–100%)监测环境光照

•具备15秒无操作超时的运动检测功能

•OLED显示屏,每次显示一个测量数据

•通过旋转编码器在四个测量页面间导航

•带可配置阈值的温度报警(18°C / 30°C)

主动/非活跃状态机

使用互斥锁实现线程安全的串行日志记录

•13个主机端单元测试

•10个Wokwi功能测试

•3个已记录的故障实验

组件

查看“器件”选项卡。唯一必须的外部部件是 DHT22 数据线上一个 10 kΩ 的上拉电阻。

电路

电路完全在 Wokwi 中进行模拟。精确的接线图请参见 GitHub 仓库中的 `diagram.json` 文件。引脚映射如下:

系统架构

固件按关注点被分解为多个单责任模块:

•`main.cpp` — 仅包含硬件初始化、RTOS对象创建和任务创建

•`rtos_objects.cpp` — 所有队列、互斥锁和事件组都集中在一个地方

•`sensors.cpp`、`display.cpp`、`input.cpp`、`alarm.cpp`、`motion.cpp`、`system_state.cpp` — 每个模块拥有一个任务和一个外设

•`logic.h` — 纯粹的决策函数,无需硬件即可测试

FreeRTOS 架构

六个任务通过三个队列、一个互斥锁和一个事件组进行协调:

| 任务 | 优先级 | 周期 | 是否阻塞 |

(注:此处“Blocks On”表示是否阻塞,但原文未提供具体值,故保留原意)

|---|---|---|---|

| 运动任务 | 3 | 50 毫秒 | `vTaskDelayUntil` |

| 输入任务 | 3 | 5 毫秒 | `vTaskDelay(1)` |

| 传感器任务 | 2 | 2 秒 | `vTaskDelayUntil`,队列发送 |

| 报警任务 | 2 | 事件 | `xQueueReceive`(超时 100 毫秒) |

| 状态任务 | 2 | 500 毫秒 | `vTaskDelay` |

| 显示任务 | 1 | 事件 | `xQueueReceive`(100 毫秒超时) |

优先级反映调度的紧急程度,而非主观重要性。具有人类可感知延迟的任务(如编码器、运动)优先级为 3;周期性或预算宽松的任务优先级为 2;显示任务由于可以容忍数十毫秒的不可见延迟,优先级为 1。

工作原理

每两秒,SensorTask 读取传感器数据,并将 `SensorData` 结构体发布到两个队列中:

- `sensorQueue` → DisplayTask(渲染当前选定的页面)

- `alarmQueue` → AlarmTask(判断温度阈值)

InputTask 解码 KY-040 编码器,并将所选页面推入一个长度为1的 `modeQueue`。DisplayTask 将最新的传感器读数与最新模式组合,以渲染 OLED。

MotionTask 每50毫秒轮询一次,并在系统事件组上设置或清除 `EVENT_MOTION` 位。StateTask 监听 `lastMotionTick`;如果15秒内未检测到任何运动,则系统状态转为 INACTIVE 并清除 `EVENT_ACTIVE`。后续任何运动都会恢复 ACTIVE 状态。

所有串行输出均通过 `safe_print()` 处理,在调用 `vprintf` 之前先获取互斥锁。

测试与验证

**13 个主机端单元测试**(`pio test -e native`),用于纯逻辑功能模块——全部通过

**10 个 Wokwi 功能性测试**,涵盖传感器显示、编码器导航、报警触发以及完整的 ACTIVE → INACTIVE → ACTIVE 循环

**3 项有意识的故障实验:**

- SensorTask 中移除了 `vTaskDelayUntil` → 导致其他任务 CPU 饱和 + 触发看门狗

将显示任务的优先级提升至5 → 传感器任务和运动任务中的调度抖动

移除了 `safe_print` 中的互斥锁 → 已说明 Wokwi 能掩盖竞态条件,但源码层面的漏洞是真实存在的

演示

模拟和功能测试的截图位于 GitHub 仓库中的 `docs/screenshots/` 目录下。

遇到的挑战

1. Wokwi 的 DHT22 模拟器在 ESP-IDF 驱动下不可靠。无论是自定义的位操作实现,还是 `esp-idf-lib/dht` 组件,都因“B 阶段”初始化错误而失败。问题已通过在 `dht_read()` 边界处模拟传感器值得到解决——函数签名保持不变,因此无需修改其他文件即可直接插入真实的 DHT22 驱动程序。

2. **InputTask 最初会阻塞看门狗**,因为 `pdMS_TO_TICKS(5)` 在 100 Hz 的定时器频率下会被截断为零 —— `vTaskDelay(0)` 并不会阻塞。已通过使用 `vTaskDelay(1)`(10 毫秒)修复。

3. 状态机发生振荡,是因为在从 INACTIVE 过渡到 ACTIVE 时 `lastMotionTick` 没有被重置,导致超时立即重新触发。已通过在重新激活时将 `lastMotionTick = now` 来修复。

经验总结

`vTaskDelayUntil()` 并非 `vTaskDelay()` 的风格化替代方案——对于周期性任务,它才是正确的基础函数,因为它能防止时间漂移累积。

一个永不阻塞的任务会饿死其同伴并触发看门狗。这并非理论上的问题——故障实验在几秒钟内就证明了这一点。

仿真是一种有用的但不完整的验证环境。Wokwi 能掩盖串行输出的竞态条件,却无法模拟 DHT22 的微秒级时序。识别模拟器无法验证的内容,与识别它可以验证的内容同样重要。

互斥锁并非装饰功能。它保护的是特定的非线程安全函数(`vprintf`),防止特定的失败模式(字节交错)——而不是抽象意义上的“竞态条件”。

限制

DHT22 和 PIR 读数在驱动器边界处进行模拟(如上文所述)。

SSD1306 驱动器使用的是过时的 I2C API,该 API 在 ESP-IDF v6.0 中已弃用,并在 v7.0 中被移除。

无持久化存储——复位后读数将丢失。

未来改进

•将固件移植到物理硬件

•迁移至 `driver/i2c_master.h`

•通过 MQTT 实现 Wi-Fi 软件遥测

•通过 NVS 添加可运行时配置的温度阈值

•添加 SD 卡数据日志记录功能

本文编译自hackster.io

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭