当前位置:首页 > 工业控制 > 电路设计项目集锦
[导读]该STM32蓝芯板可读取温度和湿度,测量环境光照,检测PIR运动传感器,并将所有数据显示在小型OLED屏幕上。当房间温度过高时会发出蜂鸣声,若十五秒内无人经过,显示屏便会自动进入睡眠状态。其硬件组件简单,有趣的是其架构设计——包含八个FreeRTOS任务、三个队列、一个事件组和一个互斥锁,每个组件都有明确的任务、优先级和用途说明,并辅以单元测试、静态分析、无头模拟器的串行日志记录,以及三项精心设计的“让系统崩溃”实验。

该STM32蓝芯板可读取温度和湿度,测量环境光照,检测PIR运动传感器,并将所有数据显示在小型OLED屏幕上。当房间温度过高时会发出蜂鸣声,若十五秒内无人经过,显示屏便会自动进入睡眠状态。其硬件组件简单,有趣的是其架构设计——包含八个FreeRTOS任务、三个队列、一个事件组和一个互斥锁,每个组件都有明确的任务、优先级和用途说明,并辅以单元测试、静态分析、无头模拟器的串行日志记录,以及三项精心设计的“让系统崩溃”实验。

项目概述

该系统是一个房间监测器,通过Wokwi进行端到端模拟:

- DHT22提供温度和湿度;

- LDR检测环境光;

- PIR感应运动;

- SSD1306 OLED显示屏显示读数,通过KY-040旋转编码器实现分页显示;

- 当温度超出18.0–30.0°C时,蜂鸣器发出声音;

- USART1以115200波特率输出所有数据,本页面的大部分信息均来自此接口。

固件使用STM32Cube HAL和原生FreeRTOS API,针对STM32F103C8T6(“蓝板”)开发,不包含Arduino层,由PlatformIO构建。应用程序按子系统拆分为多个模块(`sensors`、`display`、`input`、`alarm`、`motion`、`system_state`,以及共享的RTOS对象),而`src/main.cpp`仅执行实验室规范要求的四个阶段启动流程:初始化硬件、创建对象、创建任务并启动调度器。

接线方式符合蓝板标准:LDR的模拟输出连接至PA0(ADC1_IN0,12位单通道转换);DHT22的数据线连接至PA1,采用开漏输出,并外接一个独立的10 kΩ上拉电阻至3V3,以确保单线握手具有明确的上升时间;蜂鸣器通过PB0高电平驱动;SSD1306连接至I2C1的PB6/PB7引脚,带外部上拉电阻。PIR 输出每 10 毫秒通过 PB8 进行一次轮询;编码器使用 PB12/PB13/PB14(SW 引脚已连接但故意未映射——仅用于旋转);USART1 通过 PA9 发送数据,PA10 已配置但未使用。在初始化期间,板子上的 PC13 LED 被关闭,之后不再使用。

动机

四个传感器和一个显示屏可以用一个包含大量 `delay()` 调用的大型 `loop()` 来驱动。这种实现方式也难以进行逻辑分析,因为这四个输入之间互不相关:编码器必须准确记录每次按键动作,无论旋钮移动多么短暂;PIR 的输出脉冲是唯一能证明有人进入的证据;传感器采样可能已经延迟了两秒;而显示屏的响应速度则可以随意设定。

实时操作系统(RTOS)会将这些组件分别转换为独立的任务,每个任务拥有自己的周期、阻塞行为和优先级,因此“这件事有多紧急?”的问题只需在一次表格中一次性回答,而无需每次添加延迟时都重新推导。该项目是为 BCA182 实验室活动 1 设计的,该活动要求对上述逻辑进行文档化,而非仅依赖于断言验证。

功能

•周期性采样 — `SensorTask` 每 2000 毫秒通过 `vTaskDelayUntil()` 周期读取一次 DHT22 和 LDR;如果 DHT22 校验和错误,将最多重试三次,之后采样数据将被丢弃。

•温度报警 — `AlarmTask` 通过纯函数将每个采样值与 18.0–30.0 °C 的范围进行比较,并驱动蜂鸣器引脚。

•分页 OLED 显示 — `DisplayTask` 是唯一允许访问 I2C 总线的任务,支持上下双向循环的四个页面(温度、湿度、光照、运动)。

•编码器导航 — `InputTask` 每 2 毫秒轮询并去抖动 KY-040,然后发布所选页面。

•活动/非活动状态机 — 若 15 秒内无运动,OLED 将显示一次并忽略编码器信号;一旦有运动,所有功能将恢复,而采样和报警则继续运行。

•原子级串行输出 — 每条数据线均在互斥锁下写入,确保两个任务不会交叉执行,也不会因总线冲突丢失任何数据。

•诊断功能——两个小型任务每秒交替打印一次行,演示了系统中无任何忙循环的阻塞等待。

项目简介

这个项目是一个基于STM32 Blue Pill和FreeRTOS开发的实时房间监控系统。它从DHT22传感器读取温度和湿度,从光敏电阻读取环境光照,从PIR传感器检测运动。OLED显示屏将数据分别显示在不同页面上,旋转编码器用于切换所选页面,蜂鸣器则在温度超出18–30°C工作范围时发出警报。当连续15秒未检测到运动时,显示屏自动关闭;一旦检测到运动,系统将恢复至活动状态。在显示屏不活跃期间,传感器采样和温度报警功能仍持续运行。

该项目的核心亮点不仅在于传感器本身,更在于其工作流程的组织方式。八个独立的FreeRTOS任务分别负责采样、显示更新、输入处理、运动检测、状态变化、报警以及诊断功能。队列用于传输传感器和显示数据,事件组用于表示系统状态,互斥锁则保护共享的串行输出。

我为何制作此项目

该项目是为BCA182嵌入式系统编程实验课的第1项活动开发的,旨在实现一个多传感器监控系统,演示实时操作系统的核心概念。目标是将这些概念应用到实际用途中:每个任务都有明确的责任和优先级,周期性工作采用阻塞调度方式,共享资源则被刻意保护。

通过仿真,无需物理组件即可构建并验证完整的固件。这还促进了仔细的测试:决策逻辑被分离为纯函数,并由主机单元测试覆盖,而Wokwi则运行集成后的固件进行练习。此外,项目还记录了静态分析结果以及故障实验,用以展示移除阻塞、更改优先级或省略串行保护所带来的影响。

它是如何工作的?

在启动时,固件会打印其标识信息,创建FreeRTOS对象并启动调度器。系统进入“活动”状态:串口上显示“State: ACTIVE”,此时任何消费者都无法读取过期的事件组,OLED屏幕则停留在“温度”页面。每两秒就会有一个新样本到来,屏幕和串口上的数值会同时更新,因为两者都来自同一个 `SensorData` 结构体。

编码器的步进顺序为温度 → 湿度 → 光照 → 运动,并且在双向循环中进行。当温度超过 30.0°C 时,会触发 `Alarm: HIGH_TEMPERATURE`,PB0 输出高电平,并在 OLED 的数值行末尾添加 `ALARM`;回到窗口时,该信息会被清除。

如果十五秒内没有运动,`StateTask` 将转为 INACTIVE 状态:OLED 会短暂变黑,编码器的停机信号被消耗并忽略,`EVENT_ACTIVE` 被清零——此时采样、PIR 报警和报警功能仍在运行,因为房间仍处于监控状态,只是显示屏静默了。当 PB8 上的 PIR 边沿触发时,会设置 `EVENT_MOTION`,`StateTask` 醒来,恢复显示并重新打开编码器。

该判断逻辑本身位于 `include/system_state.h` 中的纯函数 `evaluateSystemState()` 中:运动始终优先,处于 ACTIVE 状态下的超时将进入休眠,其他所有组合则保持当前状态不变。保持其纯粹性——不使用 HAL、无队列、无时钟——使得它可以在桌面环境中进行单元测试。

演示

无需触碰任何物体即可演示:Wokwi 实时模拟器(`wokwi-cli` 或 VS Code 的 Wokwi 扩展)启动后显示横幅;串行输出——每两秒采集一次样本、页面切换、报警状态变化以及 `State:` 行;包含 `ALARM` 标记的四个 OLED 页面;由模拟 DHT22 温度升高驱动的温度报警;以及在无刺激运行一分钟内自动发生的 ACTIVE → INACTIVE → ACTIVE 循环。

需要在 Wokwi 网页界面中手动操作,因为模拟器未提供这些部分的自动化控制功能:

•旋转编码器 — 点击旋钮并朝不同方向转动,以观察页面的步进和循环;

•触发 PIR — 在激活状态时点击“模拟运动”一次,在非激活状态时再点击一次。

依赖上述交互的四个功能测试行已在相同网络上通过等效电刺激进行了验证,并在 `docs/functional-verification.md` 中予以标注。本页面仅说明了代码仓库之外暂无视频、图集或托管演示,目前尚未存在。

测试与验证

1. 单元测试——29/29通过。`test_alarm`(10)在温度评估边界上锁定了18.0/30.0°C(含),`test_navigation`(11)覆盖了双向循环,`test_state`(8)涵盖了运动胜利、仅在ACTIVE状态下超时以及保持状态。它们编译的是生产源码而非副本,因此固件和测试不会出现漂移。使用 `pio test` 运行这些测试。

2. 静态分析 — `pio check`:0 高 / 0 中 / 35 低。所有发现均记录在 `docs/static-analysis.md` 中:其中三项已在代码中修复,其余则被标注为供应商类型、宏强制参数或针对测试桩进行的常量折叠。目的是解读输出结果,而非截图。

3. 功能验证 — FT-01 至 FT-10,全部通过,每项均附有观察到的串行线编号、VCD 时间戳或 OLED 截图,详见 `docs/functional-verification.md`。需要说明的是:Wokwi 并未提供对旋转编码器或 PIR 的自动化控制功能,因此四个测试行(FT-04、FT-05、FT-08、FT-10)是通过同一电路中的瞬时按钮进行电控驱动的。固件路径已验证;物理元件也应在 Wokwi 网页界面中手动点击确认。

4. 有意的故障实验(`docs/fault-experiments.md`)——依次引入三个回归问题,进行观察、分析并恢复:移除阻塞延迟、更改任务优先级以及删除串行互斥锁。其中第三个最为有说服力:在没有互斥锁的情况下,冲突写入操作会因 `HAL_BUSY` 冲突而丢失整条指令。

遇到的挑战

Wokwi 接口在第一个任务运行前就卡住了。要让 FreeRTOS Cortex-M3 接口在模拟器中正确传递数据,需要编写调试程序,并记录在实验报告中。

DHT22 时钟在 72 MHz 芯片上的表现。`HAL_GetTick()` 为 1 毫秒,`HAL_Delay()` 会阻塞,两者都无法在单线通信帧内完成。驱动程序使用 DWT 计数器统计 CPU 循环,并仅在 ~5 毫秒的帧内暂停调度器,之后才开始输出;读取失败则会重试,而非直接信任。

串行线路丢失。在互斥锁之前,冲突写入操作会因 `HAL_BUSY` 而丢失整个数据流。两个诊断信号之间存在相位偏移,但不足以解决问题;传输过程中需进行一次“拿-给”的操作。

首次页面切换时发生栈溢出。`DisplayTask` 从 128 字节开始执行,格式化和 HAL 发送操作导致其溢出。堆栈现在根据实际的调用链来确定大小,错误会被记录下来,而不是被静默修复。

优先级设置得既不过高也不过低。`TaskA` 的优先级最初设为 2,直到发现它抢占了一个正在进行的 OLED 渲染任务以打印演示行——这种操作毫无意义。随后将其提升至 1,相关原因已记录在优先级表中。

Wokwi 不会自动调节旋钮或 PIR。这些部件的所有控制名称都会被静默忽略,因此总线必须通过电气方式驱动网络。我们坦诚地记录了这一点,而非掩盖事实。

经验总结

将决策封装为纯函数。`evaluateTemperature()` 和 `evaluateSystemState()` 接受输入值并返回输出值——正是这种习惯,使得在桌面环境中运行的 29 个测试能够针对固件所使用的精确对象执行,而生产代码中完全无需任何测试保护。

所有权胜于互斥。一个任务、一条总线、I2C1 周围没有锁;这一规则比锁更简单,也绝不会在调用点被遗忘。

长度为1的覆盖队列能消除一类错误。如果只关心最新的样本,那么“慢消费者”不再构成死锁风险。

优先级应通过讨论来确定,而非直接指定。记录当某个级别过高或过低时会发生什么,会暴露出那些“这个任务很重要”这类想法根本无法察觉的错误。

验证应包含在构建过程中。基准运行可通过两条命令复现,而故障实验之所以有效,仅仅是因为先存在一个干净的基准环境。

本文编译自hackster.io

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