本项目实现一个可观测的数据链:周期采样、五点滑动平均、有界待发队列、批量发送和断网恢复。先用虚拟时间把数据与失败路径跑通,再把输入输出替换成真实 ADC 和通信驱动。
需求和边界
模型每 10 ms 产生一个合成模拟量,按理想 12 位、3.3 V ADC 量化。滤波窗口最多五条,待发队列最多八条。发送端每 20 ms 最多处理两条;t=300~600 ms 的区间模拟网络不可用。队列满时丢最旧,保留较新的状态,并记录丢弃计数。
这里关注遥测新鲜度,不保证完整保留历史。若业务是计费或实验原始数据记录,需要另一种持久化与背压策略。采集时刻来自单调虚拟时钟,实际设备应记录采样触发时刻,而不是到上传时才补一个时间戳。
每条记录包含序号、采样时刻、原始码值和滤波结果。序号在丢样后保留间隙,不能为了让序列连续而隐藏丢失。断网恢复后按队列顺序发送,缓冲区内存始终有界。
运行三组条件
下载 节点仿真脚本,用 Python 3.11 或更新版本运行:
python sensor_node.py
脚本分别运行正常网络、300 ms 断网和长期慢消费,打印 produced、delivered、dropped、队列峰值和最大排队年龄,并断言:
- 每组都满足
产生数 = 已发数 + 丢弃数 + 剩余数。 - 队列从未超过八条,发送序号严格递增,ADC 码值在范围内。
- 正常组不丢数据;断网组和慢消费组确实发生可观察丢弃。
实验不等待墙上时间,固定输入下结果可重复。本地已执行这些断言,但它没有运行真实 RTOS、射频链路、ADC 或 TLS;打印的时间都是模型毫秒。
用结果检查设计
正常组的发送能力为 100 条/秒,与采集率相同,能消化交替产生的小积压。长期慢消费组改为每 40 ms 发送两条,只能消费 50 条/秒,队列最终必满。把队列扩大只能延后丢弃,并增加旧数据的等待时间。
五点平均降低一部分高频变化,也增加群延迟;满窗口的理想等权 FIR 在低频附近约有两个采样周期的延迟。本模型启动时使用尚未填满的窗口,初始若干点的滤波特性不同,图表和下游应能识别这个阶段。
映射到 STM32 / FreeRTOS
硬件定时器触发 ADC,DMA 写双缓冲。完成中断只交接缓冲区索引与采样序号,处理任务计算滤波后将独立记录入队,通信任务按容量和超时发送。明确 DMA、处理任务与队列各自拥有哪份内存,避免把还在写的缓冲区交给消费者。
先用固定电压源验证码值与参考,再输入已知频率信号检查滤波、采样频率和延迟。核对实际采样时间、中断优先级和 DMA/cache 条件,不能直接把 Python 的每步运算时间当成任务执行时间。
映射到 ESP32 / ESP-IDF
保持采集与联网任务分离,按所选芯片使用 ADC 校准和驱动接口;网络事件通知连接状态机。断网期间继续采集,队列满策略与模型一致。用启动会话加序号区分重启前后记录,服务端去重,TLS 验证服务器身份。
厂商移植步骤没有在本页上板验证。实物验收至少记录实际采样间隔、输入量程误差、端到端延迟、丢样数、队列峰值、CPU/栈/堆和电流;同时说明固件版本、硬件连接和测量仪器。
扩展练习与面试自查
加入最大允许年龄,发送前丢弃已过期记录,并区分“队列溢出”和“数据过期”计数。再加入突发采样和 ACK 丢失,证明数据守恒式仍成立。使用固定随机种子注入失败后,测试必须可重复。
自查:为何选丢最旧?平均滤波引入什么延迟?网络恢复后为何队列不一定立刻清空?什么条件下需要持久队列或独立记录存储?把需求变化与策略变化联系起来回答。