零知ESP32--AM2302温湿度预测器
项目概述
这是一个基于 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 拉取室外天气,实现室内外对比。
项目亮点
- 端侧神经网络推理:24×16 + 16×2 全连接网络在 ESP32 上实时推理,权重以
const float形式存放在 Flash(model.h),运行时拷贝到 RAM 用于在线微调。 - 离线训练 + 在线微调闭环:
train_model.py用合成数据(90 天日周期 + 天气漂移 + 噪声)预训练,烧录后设备用真实数据继续微调,模型越用越"懂"你的房间。 - 双速采样模式:视频演示模式(3 秒一采,约 1 分钟就能看到训练步数上涨)与长期运行模式(5 分钟一采,可存 7 天数据)一键切换(
DEMO_MODE)。 - 双通道数据展示:240×240 屏幕卡片式局部刷新 UI + 响应式 Web 仪表盘(canvas 趋势图、露点计算、舒适度、运势)。
- 免费天气对比:
wttr.in无需 API Key,30 分钟自动刷新,室内外温度同图对比。 - 工程化防御设计:NaN/Inf 权重自愈、梯度/权重裁剪、传感器读取失败沿用旧值、WiFi 连不上自动降级为 AP 热点——长期无人值守也不"死机"。
项目难点及解决方案
| # | 难点 | 解决方案 | 关键代码 |
|---|---|---|---|
| 1 | MCU 内存有限:网络权重 + 7 天数据 + 训练缓冲都放 RAM 会爆 | 权重放 Flash(static const),运行时 memcpy 到 RAM 副本;数据用定长环形缓冲,零动态分配 |
model.h:18-87, temp_predictor.ino:176-179 |
| 2 | AM2302 要求读取间隔 ≥ 2s,否则读失败 | 只在采样时刻读一次,间隔恒为 3s/5s/5min;失败时沿用上次有效值,屏幕不黑 | temp_predictor.ino:209-243, readAM2302 |
| 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) |
目录
2.1 系统初始化(setup, temp_predictor.ino:172-201)
2.2 主循环逻辑(loop, temp_predictor.ino:205-298)
一、硬件系统部分
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 系统初始化(setup, temp_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 主循环逻辑(loop, temp_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 24、NN_HIDDEN_SIZE 16、NN_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、零第三方框架,一条命令出模型:
-
合成数据(
generate_data, 22-45):90 天逐分钟数据 = 日周期正弦(凌晨 20°C/下午 30°C)+ 随机天气漂移(cumsum)+ 高斯噪声;湿度与温度反相关; -
构造样本(
create_sequences, 48-63):X = 最近 12 个时刻的(temp,hum)(24 维),y = 12 步之后(1 小时)的(temp,hum); -
训练(
train, 104-174):Xavier 初始化 + 全批量梯度下降,批量 256、300 轮、学习率 0.01,按验证集最优保存权重; -
归一化融合(main, 371-374):把标准化参数折叠进权重——部署端就不用做归一化了:
W1' = W1 / std_x b1' = b1 - W1·(mean_x/std_x) W2' = W2 × std_y b2' = b2 × std_y + mean_y -
导出(
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 行):
- 前向:计算并保留
z1(用于 ReLU 梯度); - 输出层梯度:
d_out = 2×(pred - target),裁剪 ±20 防爆炸; - 反向:
d_hid = d_out·W2ᵀ × (z1>0)(ReLU 门控),同样裁剪; - 更新:
W -= lr × 输入 × 梯度,最后nnClipWeights()把所有权重裁剪到 ±50; - 自愈:
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 仪表盘 — 暗色玻璃拟态风格,从上到下:
- 四张数据卡:室内温度 / 室内湿度 / 露点温度(前端 Magnus 公式计算+舒适度)/ 室外天气(城市、温度、体感、天气描述);
- 温湿度趋势图:1小时/6小时/24小时/7天 四个标签页,温度红色实线、湿度青色虚线、室外温度蓝色参考线、右侧黄色"预"预测点;
- 预测卡片:1小时后温度/湿度,带与当前值的差值(↑/↓);
- 在线学习卡片:累计训练步数 + 缓冲训练对数量;
- 统计卡片:最低/平均/最高温、平均湿度;
- 环境建议(运势中文版)+ 运行时长 + 采样次数 + 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/std、b2×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 时文件为空属正常 |
更多推荐


所有评论(0)