使用 FreeRTOS 和 OLED 构建模拟 ESP32 房间监控系统
本项目是一个基于 PlatformIO 的模拟 ESP32 房间监控系统,采用 Espressif 物联网开发框架(ESP-IDF)和 FreeRTOS 实现。系统可实时监测环境参数,包括温度、湿度、环境光照和运动状态,并通过 SSD1306 OLED 显示屏、旋转编码器和蜂鸣器报警提供实时反馈。整个系统完全基于 FreeRTOS 原生 API 运行,展示了无需依赖 Arduino 抽象层即可实现的正确多任务嵌入式架构。
动机
大多数初学者的微控制器项目都使用单个 loop() 函数来运行所有操作。这种方法适用于简单任务,但无法扩展。我想学习真正的嵌入式系统如何处理并发——它们如何调度多个任务、安全地共享数据,并实时响应事件。FreeRTOS 提供了这样的功能,而 ESP32 是学习的最佳平台。通过从零开始构建这个系统,我获得了关于任务调度、进程间通信(IPC)、同步和状态机的实际操作经验。
功能
•环境传感:实时监测温度和湿度(DHT22)、环境光照(通过ADC的LDR)以及运动(PIR)。
•用户界面:采用SSD1306 OLED显示屏,结合旋转编码器实现导航,可循环切换四个传感器页面。
•报警系统:当温度超出18°C至30°C的安全范围时,蜂鸣器将发出警报。
•状态机:系统根据PIR运动动态在“活动”和“非活动”状态之间切换,并设有15秒的无操作超时。
•FreeRTOS架构:包含六个并发任务,具备明确的优先级、队列、事件组及互斥锁。
组件
•ESP32开发套件 C V4
•DHT22 温湿度传感器
•光敏电阻(LDR)
•PIR 运动传感器
•KY-040 旋转编码器
•SSD1306 OLED 显示屏(128x64,I2C)
•被动蜂鸣器
电路
系统架构
FreeRTOS 架构
工作原理
1. 传感器任务:每2秒,SensorTask会读取DHT22(温度和湿度)以及LDR(通过ADC获取的光照强度),然后将SensorData结构体同时发布到displayQueue和alarmQueue中。
2. 显示任务:DisplayTask 拥有 OLED 显示屏。它从 displayQueue 接收 SensorData,从 modeQueue 接收 DisplayMode,并渲染当前页面(温度、湿度、光照或运动)。当系统处于非活动状态时,显示屏将关闭。
3. 输入任务:InputTask 每 2 毫秒轮询一次旋转编码器。当编码器顺时针旋转时,显示模式向前推进;逆时针旋转则将其返回。选定的模式通过 modeQueue 发送。
4. 运动任务:MotionTask 每 50 毫秒轮询一次 PIR 传感器。当检测到运动时,它会在事件组中设置 EVENT_MOTION 位;当运动停止时,清除该位。
5. 状态任务:状态任务每100毫秒运行一次。如果连续15秒未检测到任何运动,系统将从“活动”状态转为“非活动”状态,并清除EVENT_ACTIVE标志。任何运动都会将其重新设为“活动”状态。
6. 报警任务:AlarmTask 从 alarmQueue 接收温度读数。如果温度超出 18°C 至 30°C 范围,它会设置 EVENT_ALARM 并通过 PWM(LEDC)开启蜂鸣器。
测试与验证
单元测试(13 个测试):所有纯逻辑功能均在 PlatformIO 的原生环境中使用原生平台进行了测试。
•温度报警逻辑(5 个测试)
•显示导航(4 个测试)
•系统状态转换(4 个测试)
所有 13 个测试均通过 pio test -e native 命令执行。
Wokwi 功能测试(FT-01 至 FT-10):在 Wokwi 模拟器中,全部 10 个功能测试均通过,涵盖温度更新、湿度更新、光照变化、编码器导航(顺时针和逆时针)、报警触发、报警复位、PIR 触发、无操作超时以及重新激活等场景。
故障实验:为了解调度行为,进行了三项有目的的 FreeRTOS 故障实验:
- 移除阻塞延迟导致 CPU 饱食;
- 为慢速任务分配高优先级,使系统无响应;
- 移除互斥锁后,显示出日志交错的可能性。
演示
以下截图展示了系统在Wokwi中的运行情况:
•OLED显示“房间监测”和传感器读数
•旋转编码器在页面间切换
•当温度超过限值时,蜂鸣器报警
•PIR触发状态转换
遇到的挑战
最困难的部分是DHT22驱动程序。该协议使用微秒级的脉冲宽度来编码位——“1”位约为70微秒,“0”位约为27微秒。我最初设定的位分类阈值过低,导致几乎所有位都被误判为“1”,从而在每次读取时都未能通过校验和验证。
调试过程中,我需要从校验和失败问题追溯到原始的位时序逻辑。当我将阈值调整为与DHT22数据手册一致后,驱动程序便能可靠地读取数据。主要教训是:位操作协议必须直接对照数据手册检查其时序值,而非进行近似。
第二个挑战是FreeRTOS的调度行为。在一次故障实验中,我将DisplayTask设置为最高优先级,导致编码器和OLED变得无响应,因为缓慢的I2C更新占用了CPU时间。这让我明白,优先级应反映调度的紧迫性,而非任务的重要性。
本文编译自hackster.io





