项目概述

这是一个基于 ESP32 + AM2302 温湿度传感器 + 240×240 TFT 屏幕 的智能环境预测装置:

  • 传感器每隔一段时间采集一次室内温湿度,存入 7 天环形缓冲区(2016 条);
  • 一个 24→16→2 的三层神经网络(12 次温度 + 12 次湿度 → 预测温度和湿度)在 ESP32 本机运行推理,预测 1 小时后 的室内温湿度;
  • 设备会 在线上微调(SGD)这个神经网络:用"过去的窗口 → 现在实测值"作为训练对,让模型逐渐适应当前房间的真实环境;
  • 240×240 屏幕实时显示当前值、预测值、迷你趋势图和"环境运势";
  • 板载 Web 服务器提供可视化仪表盘(实时数据、1h/6h/24h/7d 趋势图、室外天气对比、统计、CSV 导出);
  • 通过免费的 wttr.in 天气 API 拉取室外天气,实现室内外对比

项目亮点

  1. 端侧神经网络推理:24×16 + 16×2 全连接网络在 ESP32 上实时推理,权重以 const float 形式存放在 Flash(model.h),运行时拷贝到 RAM 用于在线微调。
  2. 离线训练 + 在线微调闭环:train_model.py 用合成数据(90 天日周期 + 天气漂移 + 噪声)预训练,烧录后设备用真实数据继续微调,模型越用越"懂"你的房间。
  3. 双速采样模式:视频演示模式(3 秒一采,约 1 分钟就能看到训练步数上涨)与长期运行模式(5 分钟一采,可存 7 天数据)一键切换(DEMO_MODE)。
  4. 双通道数据展示:240×240 屏幕卡片式局部刷新 UI + 响应式 Web 仪表盘(canvas 趋势图、露点计算、舒适度、运势)。
  5. 免费天气对比:wttr.in 无需 API Key,30 分钟自动刷新,室内外温度同图对比。
  6. 工程化防御设计:NaN/Inf 权重自愈、梯度/权重裁剪、传感器读取失败沿用旧值、WiFi 连不上自动降级为 AP 热点——长期无人值守也不"死机"。

项目难点及解决方案

# 难点 解决方案 关键代码
1 MCU 内存有限:网络权重 + 7 天数据 + 训练缓冲都放 RAM 会爆 权重放 Flash(static const),运行时 memcpy 到 RAM 副本;数据用定长环形缓冲,零动态分配 model.h:18-87temp_predictor.ino:176-179
2 AM2302 要求读取间隔 ≥ 2s,否则读失败 只在采样时刻读一次,间隔恒为 3s/5s/5min;失败时沿用上次有效值,屏幕不黑 temp_predictor.ino:209-243readAM2302
3 在线训练数值爆炸(学习率不当 → 权重发散成 NaN/Inf) 三层防线:梯度裁剪 ±20 → 权重裁剪 ±50 → 检测到 NaN 直接从 Flash 恢复初始权重 nnTrainStep (351), nnClipWeights (344), nnRestoreWeights (337)
4 训练数据积累太慢:正常 5min 间隔要攒 1 小时才有 12 个点 开机先用 3~5s 快采攒足点数(FAST 模式),攒够后自动切慢速;训练对用更短历史(lookback)提前开始 FAST_COLLECT_N / fastMode (238), TRAIN_LOOKBACK
5 无 JSON 库也能解析天气接口(减小依赖) 手写轻量解析:定位 current_condition 段后,用 indexOf/substring 提取字段,避免误匹配预报数据 fetchWeather (505-555)
6 TFT 刷新残影/闪烁(整屏重绘太慢) 局部刷新:静态布局只画一次,数值变化区域才重绘;静态变量追踪上一次值做"脏检查",每 80 次刷新(~2.7min)整屏重绘一次防残影累积 drawUI (911)
7 网络不可用时设备不可用 WiFi 以 AP_STA 双模启动:先连家里路由器,失败则自建热点 TempPredictor,手机连热点即可看仪表盘 initWebServer (732-750)
8 预测结果出现 NaN 会污染 UI 推理输出做 isfinite 检查,NaN 回退到最近实测值;输出裁剪到物理范围(温度 -10~60,湿度 0~100) runPrediction (432-436)

目录

一、硬件系统部分

1.1 硬件清单

1.2 接线方案

1.3 硬件连接图

1.4 实物连接图

二、软件架构设计

2.1 系统初始化(setup, temp_predictor.ino:172-201)

2.2 主循环逻辑(loop, temp_predictor.ino:205-298)

三、代码拆分讲解

3.1 配置区

3.2 model.h — 神经网络骨架(自动生成)

3.3 train_model.py — 离线训练与导出

3.4 数据采集与环形缓冲

3.5 推理与在线学习

四、操作过程及数据展示

4.1 操作步骤

4.2 数据展示

4.3 视频演示

五、技术原理

5.1 工作原理

5.2 工作模式配置

六、常见问题指引


一、硬件系统部分

1.1 硬件清单

硬件 型号/规格 数量 说明
主控 零知派 ESP32 开发板 1 240MHz, 4MB Flash, sketch.yaml 中 FQBN 为 lingzhi:ESP32:lzesp32
扩展板 零知派 ESP32 扩展板 1 带显示屏接口,方便接线
温湿度传感器 AM2302(DHT22 同系) 1 数字单总线,精度 ±0.5°C / ±2%RH,最小读取间隔 2s
显示屏 240×240 SPI TFT(ST7789 驱动) 1 1.3 英寸圆角屏,SPI 四线 + 复位/DC
杜邦线 公对母 若干 3 根左右
供电 USB 数据线 + 充电头/电脑 USB 1 5V 供电,板载稳压到 3.3V

1.2 接线方案

AM2302(共 3 根线):

AM2302 引脚 接 ESP32 说明
VCC 3V3 推荐 3.3V 供电,与 IO 电平一致
DATA GPIO5(D5) 代码 #define DHT_PIN 5(temp_predictor.ino:30),可选 10kΩ 上拉到 3V3
GND GND 共地

1.3 硬件连接图

1.4 实物连接图


二、软件架构设计

整体是一个事件驱动的单线程协作式调度结构:loop() 里按"定时器到期"依次执行采样、训练、天气、显示四个任务,任何任务都不阻塞太久。

┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
│ AM2302   │──▶│ 环形缓冲  │──▶│ 神经网络  │──▶│ 屏幕 UI  │   │ Web 仪表盘 │
│ 采样任务  │   │ 2016条   │   │ 推理+微调 │   │ 局部刷新  │◀──│ JSON API │
└──────────┘   └──────────┘   └──────────┘   └──────────┘   └──────────┘
                     ▲              │                              ▲
                     └── 训练对 ─────┘        wttr.in 天气 ◀────────┘

2.1 系统初始化(setuptemp_predictor.ino:172-201)

setup() 按固定顺序完成 6 件事,任何一步失败都不阻塞后续:

步骤 代码 说明
① 串口初始化 Serial.begin(115200) 日志输出,波特率 115200
② 拷贝权重 Flash→RAM memcpy(w1, nn_w1, ...) (176-179) 初始权重是 const,运行时的 RAM 副本才能被在线学习修改
③ 屏幕初始化 tft.init(); setRotation(3); 显示启动画面 240×240 横屏;启动画面带进度条
④ 传感器初始化 dht.setup(DHT_PIN, DHTesp::AM2302) (189) 不设 INPUT_PULLUP,DHTesp 自行控制 GPIO5 时序
⑤ WiFi + Web initWebServer() (195) 先连路由器(10s 超时),失败转 AP 热点
⑥ 天气 + 预热 fetchWeather() + runPrediction() (197-199) 首屏即有天气;有数据时先跑一次推理预热

2.2 主循环逻辑(looptemp_predictor.ino:205-298)

loop() 用 millis() 差值做非阻塞定时,每轮 5 个任务依次检查:

loop():
  ├─ 1. server.handleClient()           # Web 请求响应(每轮)
  ├─ 2. 采样定时(3s/5s 或 5min 到期?)
  │     ├─ 读 AM2302
  │     ├─ 成功 → addSample() 入环形缓冲
  │     ├─ 攒够 2×lookback 个点 → 构造训练对入缓冲(输入=过去窗口, 目标=当前实测)
  │     └─ 快采攒够 FAST_COLLECT_N 个点 → fastMode=false 切慢速
  ├─ 3. 训练定时(10s/60s 到期且训练对 ≥ 阈值?)
  │     └─ 随机抽 batch 对样本,做 TRAIN_ROUNDS 轮 SGD 微调
  ├─ 4. 天气定时(30min 到期?) → fetchWeather()
  └─ 5. 显示定时(2s 到期?)
        ├─ runPrediction() 推理 → 预测值
        └─ drawUI() 局部刷新屏幕

关键设计点:

  • 非阻塞:所有定时都用 millis() - lastXxx >= interval 判断,只有 Web/天气请求会短暂阻塞(天气 5s 超时),delay(50) 兜底防饿死看门狗;
  • 采样节奏自切换:开机进入 fastMode(快采快速攒数据),攒够 FAST_COLLECT_N 个点后自动切到正式间隔(temp_predictor.ino:238),视频演示和长期运行两不误;
  • 训练对构造与推理同构:训练用的"过去窗口→现在"与推理用的"最近窗口→未来"是同一类映射,保证在线学习与离线训练目标一致。

三、代码拆分讲解

3.1 配置区

所有"想改的东西"集中在一处:

#define DHT_PIN 5              // 传感器引脚
#define DEMO_MODE 1            // 1=视频演示 0=正常长期运行
#define SAMPLE_INTERVAL 300000UL  // 正常采样间隔 5 分钟
#define INPUT_LEN 12           // 网络输入窗口(12 次采样 = 1 小时)
#define MAX_SAMPLES 2016       // 7 天环形缓冲(12×24×7)

改 WiFi、城市、采样间隔都只动这里,不需要动业务代码。

3.2 model.h — 神经网络骨架(自动生成)

由 train_model.py 生成,包含三部分:

  • 网络维度宏:NN_INPUT_SIZE 24NN_HIDDEN_SIZE 16NN_OUTPUT_SIZE 2;
  • 权重常量:nn_w1[384](24×16)、nn_b1[16]nn_w2[32](16×2)、nn_b2[2],全部 static const float,存放在 Flash 不占 RAM;
  • 前向传播函数 nn_forward():两层全连接,隐藏层 ReLU,输出层线性(回归任务不加激活),按 w1[i * NN_HIDDEN_SIZE + j] 行主序索引。

3.3 train_model.py — 离线训练与导出

纯 NumPy、零第三方框架,一条命令出模型:

  1. 合成数据(generate_data, 22-45):90 天逐分钟数据 = 日周期正弦(凌晨 20°C/下午 30°C)+ 随机天气漂移(cumsum)+ 高斯噪声;湿度与温度反相关;

  2. 构造样本(create_sequences, 48-63):X = 最近 12 个时刻的(temp,hum)(24 维),y = 12 步之后(1 小时)的(temp,hum);

  3. 训练(train, 104-174):Xavier 初始化 + 全批量梯度下降,批量 256、300 轮、学习率 0.01,按验证集最优保存权重;

  4. 归一化融合(main, 371-374):把标准化参数折叠进权重——部署端就不用做归一化了:

    W1' = W1 / std_x        b1' = b1 - W1·(mean_x/std_x)
    W2' = W2 × std_y        b2' = b2 × std_y + mean_y
    
  5. 导出(export_model_h, 186-293):生成带注释、可直接编译的 model.h

3.4 数据采集与环形缓冲

samples[headIdx] = ...;  sampleTimes[headIdx] = millis();
headIdx = (headIdx + 1) % MAX_SAMPLES;   // 环形指针
if (sampleCount < MAX_SAMPLES) sampleCount++;
  • 固定数组 + 取模指针,零动态内存,7 天数据 ≈ 24KB RAM;
  • sampleTimes 记录毫秒偏移,供 CSV 导出时间轴用;
  • 读取失败(readAM2302 返回 false)时沿用 lastValidT/lastValidH,显示不中断。

3.5 推理与在线学习

  • 最近 n 个样本(不足 12 个用最早样本重复填充)组成 24 维输入;
  • 调用 RAM 版前向 nnForwardRam(与 Flash 版逻辑一致,但权重可被微调修改);
  • 输出做三道保险:isfinite 回退实测值 → 裁剪到物理范围(-10~60°C / 0~100%)→ 权重损坏先恢复。

手写单步 SGD + 全梯度反向传播(代码量 ~40 行):

  1. 前向:计算并保留 z1(用于 ReLU 梯度);
  2. 输出层梯度:d_out = 2×(pred - target),裁剪 ±20 防爆炸;
  3. 反向:d_hid = d_out·W2ᵀ × (z1>0)(ReLU 门控),同样裁剪;
  4. 更新:W -= lr × 输入 × 梯度,最后 nnClipWeights() 把所有权重裁剪到 ±50;
  5. 自愈:nnWeightsHealthy() 检查全部 434 个浮点是否有限,发现 NaN 直接 nnRestoreWeights() 恢复出厂权重,设备永不"死机"。

注意 lr 极小的设计意图:在线微调只做"温和的持续适应",不让新数据剧烈改写预训练模型。


四、操作过程及数据展示

4.1 操作步骤

第 1 步:接线

按 1.2 接线方案连接 AM2302 与屏幕,上电前检查有无接反。

第 2 步:修改配置(temp_predictor.ino 配置区)

  • WIFI_SSID / WIFI_PASS → 改成你自己的路由器账号密码(代码里是示例值);
  • WEATHER_CITY → 改成你的城市拼音,如 "beijing""shanghai";

第 3 步:编译烧录

烧录完成后打开串口监视器(115200),应能看到初始化日志(见下方示例)。

第 4 步:观察串口日志

=== Temp Predictor (Online Learning + Weather) ===

[NN] Weights copied to RAM for online learning
[DHT] Setup status: OK
[WiFi] Connecting to zaixinjian
.....
[WiFi] Connected!  IP: 192.168.1.100
[Web] Server started
[Weather] shenzhen: 31.0C, 75%, 多云
[Sample #1] 28.3C, 72% (fast)
[Sample #2] 28.4C, 72% (fast)
...
[Train] steps=24 pairs=20 batch=4

看到 [Sample] 说明传感器正常,看到 [Train] steps= 上涨说明在线学习已开始。

第 5 步:日常使用与数据导出

  • 页面每 3 秒自动刷新,切换 1小时/6小时/24小时/7天 查看不同粒度的趋势;
  • 点击 📥 导出 CSV 下载全部历史数据(可用于二次分析/重新训练)。

4.2 数据展示

屏幕(240×240) — 上电显示启动画面后进入主界面:

┌──────────────────────────┐
│ AI Predictor       00:05 │  ← 标题 + 运行时间
│ ┌────────┐ ┌────────┐    │
│ │ TEMP   │ │ HUM    │    │
│ │ 28.3   │ │ 72     │    │  ← 当前温度/湿度(大字)
│ └────────┘ └────────┘    │
│ ┌──────────────────────┐ │
│ │ TREND  FAST  OUT 31C │ │  ← 模式徽章 + 室外温度
│ │  ╭──╮  ╭─╮     ●    │ │  ← 迷你趋势图 + 黄色预测点
│ │  ╯  ╰──╯ ╰──         │ │
│ └──────────────────────┘ │
│ ┌──────────────────────┐ │
│ │ 1H FORECAST  29.1C 74%│ │  ← 预测 1 小时后温湿度
│ │                +0.8C │ │  ← 预测差(升/降)
│ └──────────────────────┘ │
│ Warm and pleasant   n=24│ │  ← 运势 + 累计采样数
└──────────────────────────┘

Web 仪表盘 — 暗色玻璃拟态风格,从上到下:

  1. 四张数据卡:室内温度 / 室内湿度 / 露点温度(前端 Magnus 公式计算+舒适度)/ 室外天气(城市、温度、体感、天气描述);
  2. 温湿度趋势图:1小时/6小时/24小时/7天 四个标签页,温度红色实线、湿度青色虚线、室外温度蓝色参考线、右侧黄色"预"预测点;
  3. 预测卡片:1小时后温度/湿度,带与当前值的差值(↑/↓);
  4. 在线学习卡片:累计训练步数 + 缓冲训练对数量;
  5. 统计卡片:最低/平均/最高温、平均湿度;
  6. 环境建议(运势中文版)+ 运行时长 + 采样次数 + CSV 导出按钮。

4.3 视频演示

零知ESP32--AM2302温湿度预测器

​​​​​​​


五、技术原理

5.1 工作原理

整体数据流:

AM2302 每 3s/5min 采样
      │
      ▼
环形缓冲(最近 2016 条 = 7 天)
      │
      ├─ 推理路径:最近 12 次 (temp,hum) ──▶ 24 维输入 ──▶ 神经网络 ──▶ 预测 1 小时后 (temp,hum)
      │
      └─ 学习路径:12 步前的窗口 ──▶ 网络输入;当前实测值 ──▶ 目标 → 攒成训练对
                    每隔训练周期,随机抽 batch 做 SGD 微调

网络结构(全连接,~460 个参数,推理耗时 <1ms):

输入 24 维       隐藏层 16 神经元         输出 2 维
[temp×12] ──▶  Σ(w1·x + b1) ──▶ ReLU ──▶  Σ(w2·h + b2) ──▶ [predTemp, predHum]
[hum×12]                                 (线性输出,回归任务)
  • 为什么能预测:温湿度变化有日周期和惯性,过去 1 小时的走势对 1 小时后的状态有强相关性;离线阶段网络已经在 90 天合成数据上学会了"日周期正弦 + 天气漂移"这类规律;
  • 为什么部署端不用归一化:train_model.py 导出时把标准化参数融进了权重(W1/stdb2×std+mean),见 3.3 节公式,因此 model.h 里的网络直接吃原始温湿度值;
  • 在线学习为什么有效:真实房间的冷暖周期与合成数据不完全一样,设备用"过去窗口→当前实测"对持续微调(lr=0.0005,正常模式),权重向真实环境缓慢漂移,越用越准;
  • 天气对比:wttr.in 免费接口返回当前室外温湿度,屏幕显示 OUT xxC,Web 端画在同一张趋势图上,直观看到开窗通风时机。

5.2 工作模式配置

DEMO_MODE(temp_predictor.ino:31)控制整套节奏参数,两套参数在编译期选择:

参数 DEMO_MODE=1(视频演示) DEMO_MODE=0(正常长期运行) 作用
FAST_SAMPLE_MS 3000ms 5000ms 开机快采间隔
SAMPLE_INTERVAL_MS 5000ms 300000ms(5 分钟) 快采结束后的正式间隔
FAST_COLLECT_N 40 30 快采多少点后切正式间隔
TRAIN_INTERVAL 10000ms 60000ms 每隔多久训练一轮
TRAIN_MIN_PAIRS 1 3 最少几对样本才开始训练
TRAIN_BATCH_SIZE 4 16 每轮抽几对样本
TRAIN_ROUNDS 6 8 每轮重复几次随机抽样
TRAIN_LOOKBACK 8 12 训练窗口长度(越小越早开始攒对)
ONLINE_LR 0.00005 0.0005 在线学习率

演示模式时间线(默认配置):开机 3s/次快采 → 约 48 秒后开始攒训练对、每 10s 训练 → 约 2 分钟后采满 40 点切 5s 间隔,训练步数持续上涨,非常适合拍演示视频。

正常模式时间线:开机 5s/次快采 30 点(约 2.5 分钟)→ 切 5 分钟/次正式采样 → 约 2 小时后训练对攒足,开始每 60s 微调 → 环形缓冲可存满 7 天(2016 = 12×24×7 个点)。

其他可调参数:SAMPLE_INTERVAL(正式采样间隔)、DISPLAY_INTERVAL(屏幕刷新)、WEATHER_INTERVAL(天气刷新)、WEIGHT_CLIP(权重裁剪上限)、TRAIN_BUFFER_SIZE(训练对缓冲,200 对 ≈ 21KB RAM)。

⚠️ 所有宏都是编译期常量,修改后必须重新编译烧录才生效。


六、常见问题指引

# 现象 原因与解决
1 串口反复打印 [DHT] Read failed ① 检查 DATA 是否接对 GPIO5;② 检查 VCC/GND;③ AM2302 相邻两次读取必须 ≥2s,代码已保证,注意别在 Web 请求里加读传感器;④ 裸传感器需加 10kΩ 上拉
2 界面显示 nan 或预测值离谱 设备有自愈机制:NaN 自动回退实测值、权重自动恢复出厂。若反复出现,把 DEMO_MODE 改为 0(正常模式学习率更稳),或重新生成 model.h 重新烧录
3 在线学习步数不涨(trainSteps 为 0) ① 演示模式需攒够 16 个样本(约 48s),正常模式需 24 个样本且每 60s 才训练一次;② 采样失败(问题 1)导致没有训练对
4 天气一直"获取中" ① 设备未联网(见问题 3);② wttr.in 被墙/超时,接口有 5s 超时不会卡死,会自动重试;③ 城市名要用拼音英文
5 重启后历史数据清零 数据存在 RAM 环形缓冲,断电即失。需要长期留痕请用 Web 的 CSV 导出定期备份(或后续版本加 SPIFFS 落盘)
6 CSV 下载失败 / 内容为空 直接在浏览器访问 http://<设备IP>/api/export/csv;刚开机采样数为 0 时文件为空属正常
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐