ESP8285 自制 WiFi 天气时钟 — 从零到固件的全过程
项目简介
一块 ESP-01 模块 + 一块 0.96 寸 OLED,用 PlatformIO 手写固件,实现了一个全功能 WiFi 天气时钟:
- NTP 网络时间(自动校准,180° 旋转显示)
- 实况天气(温度、体感温度、湿度、风力、AQI 空气质量、PM2.5)
- 三日预报(日期、星期、天气图标、温度区间)
- 逐 8 小时预报(横排时间轴 + 图标 + 温度 + 当前降水概率)
- 自定义文字(网页配置,自动换行)
- 动态天气图标(太阳光线旋转、雨滴下落、雪花飘落、闪电闪烁)
- Web 配置面板(WiFi、坐标搜索/定位/手填、页面开关、停留时长、静态 IP)
- OTA 无线更新(两跳桥接模式,突破 1MB flash 限制)
硬件总成本约 ¥15(ESP-01 + CH340 + OLED + 面包板)。

---
硬件清单
| 部件 | 型号 | 价格 | 说明 |
| 主控 | ESP8285N08(ESP-01 模块) | ~¥5 | 内置 1MB Flash,ESP8266 变体 |
| USB 转串口 | CH340G 模块 | ~¥3 | 用于烧录和调试 |
| 显示屏 | 0.96" OLED SSD1306 I2C | ~¥5 | 4 引脚:VCC/GND/SCL/SDA |
| 电源 | 外部 3.3V 稳压 | ~¥2 | CH340 的 3.3V 不够 WiFi 峰值电流 |
| 面包板 + 杜邦线 | — | ~¥3 |
接线

ESP-01 OLED CH340
─────────────────────────────────────
GPIO2 (pin2) → SCL
GPIO0 (pin3) → SDA
VCC → VCC 3.3V
GND → GND GND
TXD → RXD (交叉)
RXD → TXD (交叉)
关键注意事项:
- CH340 的 3.3V 输出电流不足 WiFi 峰值需求,必须外接 3.3V 稳压
- GPIO0 在烧录时需接地(低电平进入 Download 模式),烧录后断开
- EN 引脚必须拉高(接 VCC)
---
软件架构
技术栈
- 框架: Arduino for ESP8266
- 构建: PlatformIO (espressif8266, esp01_1m)
- 显示: U8g2(I2C 软件驱动)
- 字体: wqy12_t_gb2312a(文泉驿点阵,覆盖 GB2312 一级汉字 3755 个)
- 配置存储: LittleFS 纯文本 key=value(无 JSON 库)
- 天气 API: 实况用 suyanw.cn,预报+逐小时+体感用 Open-Meteo,AQI 用 Open-Meteo Air Quality
源码结构
src/
├── main.cpp (45行) 入口:setup/loop
├── config.h/cpp (30+85行) 配置结构体 + LittleFS 读写
├── net.h/cpp (10+80行) WiFi AP/STA + NTP + OTA
├── weather.h/cpp (46+520行) 天气数据获取 + JSON 解析
├── display.h/cpp (8+530行) OLED 渲染 + 手绘矢量图标
├── web.h/cpp (3+280行) Web 配置面板 + REST API
└── main_bridge.cpp.bak 最小桥接固件模板(OTA 用)
总代码量约 60KB(10 个文件),编译后固件 530KB,占 1MB flash 的 55%。
数据流
[Open-Meteo API] ──→ fetchForecast() ──→ g_fc (3日预报)
│ g_hour[8] (逐8小时)
│ g_cur.feelTemp (体感)
│
[suyanw API] ─────→ fetchCurrent() ───→ g_cur (实况天气)
│
[Open-Meteo AQI] ─→ fetchAQI() ──────→ g_aqi (空气质量)
│
↓
dispTick() 1Hz ──→ render() ──→ u8g2 OLED
│
webTick() ──→ HTTP Server (配置面板)
│
cfgSave() ──→ /config.txt (LittleFS)

---
核心技术挑战与解决方案
1. 1MB Flash 限制下的 OTA
ESP-01 只有 1MB flash。编译后的完整固件约 530KB,加上 WiFi + HTTP 栈开销,OTA 上传区(958KB - 当前固件)不足以容纳新固件。
解决方案:两跳桥接模式
完整固件 (530KB) ──→ 桥接固件 (390KB) ──→ 新完整固件 (530KB)
先腾出空间 再写入目标
1. 第一跳:上传最小桥接固件(仅 WiFi + OTA,无显示/天气功能)
2. 设备重启进入桥接模式 → 空闲空间 958KB - 390KB = 568KB ≥ 530KB
3. 第二跳:上传完整固件
每次部署双击 deploy.bat,全程自动化,约 60 秒完成。

2. JSON 解析(无 ArduinoJson)
为节省 flash 空间(ArduinoJson 约 30KB),手写了轻量级 JSON 解析器:
// 提取 "key":"string_value"
static bool arrStr(const String& s, const char* key, int idx, char* out, size_t n);
// 提取 "key":[array] 中第 idx 个数值
static float arrNum(const String& s, const char* key, int idx);
// 提取 "section":{...} 顶层对象
static String sectionOf(const String& s, const char* sect);
踩过的坑:
- arrStr 最初复制了 JSON 引号字符,导致 sscanf 解析日期失败 → 所有预报显示相同星期
- Open-Meteo 响应中 hourly_units 和 daily_units 排在数据前面,用 indexOf 需精确匹配 "daily":{ 而非 "daily"
- suyanw API 使用 chunked 编码 + 冒号后空格不一致,需要容错解析
3. OLED 180° 旋转 + 中文显示
用户要求屏幕倒装(180° 旋转),U8g2 一行搞定:
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R2, 2, 0, U8X8_PIN_NONE);
// ↑ ↑
// SCL=GPIO2 SDA=GPIO0
中文字体用文泉驿点阵 u8g2_font_wqy12_t_gb2312a,覆盖 GB2312 一级汉字,完美显示天气/星期/配置界面。
4. 手绘矢量天气图标
不用位图(太占空间),用 U8g2 的画线/画圆 API 手绘 9 种天气图标:
☀️ 晴 ⛅ 多云 ☁️ 阴 🌧️ 雨 ⛈️ 雷阵雨
❄️ 雪 🌨️ 冻雨 🧊 冰雹 🌫️ 雾
每个图标在 24×24 网格上设计,运行时缩放到任意尺寸。最终加入帧动画:太阳光线旋转、雨滴下落、雪花飘落、闪电闪烁。


5. 配置系统
去掉 ArduinoJson 后,配置用纯文本存储:
ssid=Zyzjt
pass=***
city=惠州
text=今天记得带伞
flags=1,1,1,1,1
dwell=6
lat=23.11147
lon=114.41523
读取时用 sscanf + 局部 int 变量(非指针),避免 LittleFS 字符串对齐问题导致的崩溃。
---
API 数据源
| 数据 | API | 刷新频率 | 说明 |
| 实况天气 | suyanw.cn | 5 分钟 | chunked JSON,免费无 Key |
| 三日预报 | Open-Meteo | 60 分钟 | WMO 天气代码 + 温度极值 |
| 逐 8 小时 | Open-Meteo | 60 分钟 | 含降水概率 |
| 体感温度 | Open-Meteo | 60 分钟 | 与预报同请求,零额外开销 |
| AQI 空气质量 | Open-Meteo Air Quality | 30 分钟 | US AQI + PM2.5 + PM10 |
| 地理编码 | Open-Meteo Geocoding | 按需 | 城市名 → 坐标 |
---
踩坑记录(血泪教训)
坑1:CH340 供电不足,WiFi 一开就重启
ESP-01 的 WiFi 启动瞬间峰值电流约 300mA,而 CH340 模块的 3.3V 引脚最大输出只有 ~50mA。
症状: 设备上电后串口有输出,WiFi 一连就掉电重启,反复循环。串口日志看到 [MAIN] boot 反复打印。
原因: CH340 的 3.3V 是 LDO 稳压,电流不够 WiFi 射频用。ESP8266 系列对电源要求严格,峰值电流不足会导致 brownout 复位。
解决: 外接 3.3V 稳压模块(AMS1117-3.3 或类似),直接给 ESP-01 的 VCC 和 GND 供电。CH340 只负责 TXD/RXD 信号线。

CH340 3.3V ──✗── 不接 ESP-01 VCC
外部稳压 3.3V ──→ ESP-01 VCC + OLED VCC
CH340 GND ──→ 共地(ESP-01 GND + OLED GND + 外部稳压 GND)
坑2:TX/RX 接反,烧录无反应
ESP-01 和 CH340 之间是交叉连接:ESP 的 TXD 接 CH340 的 RXD,ESP 的 RXD 接 CH340 的 TXD。
症状: esptool 报 Timed out waiting for packet header 或 A fatal error occurred: Wrong boot mode detected,烧录完全不成功。
原因: 串口通信是交叉的——一端的发送(TX)必须接另一端的接收(RX)。如果 TX 接 TX、RX 接 RX,信号完全对不上。
检查方法: 看 ESP-01 模块上的丝印:
ESP-01 引脚排列(从顶部看,天线在左):
┌─────────────┐
│ GND TXD │ ← TXD 是发送,接 CH340 的 RXD
│ GPIO2 RST │
│ GPIO0 ADC0 │
│ VCC CH_PD │ ← CH_PD(EN) 必须接 VCC
│ TXD GND │ ← 别搞混,模块上有两排引脚
│ RXD GPIO0 │ ← RXD 是接收,接 CH340 的 TXD
│ RST CH_PD │
│ GND VCC │
└─────────────┘
关键: ESP-01 的 CH_PD(也叫 EN)引脚必须接 VCC(拉高),否则芯片不工作。
坑3:烧录时不要按 RST 按钮
ESP-01 模块上没有 RST 按钮(只有引脚),但外接面包板上可能有人加了。烧录过程中如果意外按下 RST:
症状: 烧录到一半设备重启,flash 内容损坏 → 变砖。设备不响应任何烧录命令。
原因: esptool 烧录时通过软复位(DTR/RTS 信号线)控制 ESP8285 进入 Download 模式。手动按 RST 会打断这个流程,导致 flash 写入不完整。
烧录时的操作规范:
1. GPIO0 接地 → 上电 → 进入 Download 模式
2. pio run -t upload 开始烧录
3. 全程不要碰任何按钮和线
4. 烧录完成 → 断电 → 拔掉 GPIO0 的接地线 → 重新上电
5. 如果烧录失败,先断电再重新来,不要在线路上反复插拔
坑4:GPIO0 烧录后忘记拔,设备永远进 Download 模式
GPIO0 接地 = Download 模式。烧录完成后如果 GPIO0 还接地,设备重启会再次进入 Download 模式而不是正常运行。
症状: 串口显示 upload complete,但设备重启后没有正常工作,串口输出 waiting for download。
解决: 烧录成功后,必须断电 → 拔掉 GPIO0 接地线 → 重新上电。OTA 烧录不存在这个问题(OTA 通过 WiFi 传输,不需要 GPIO0)。

坑5:LittleFS 字符串对齐导致崩溃
ESP8266 的 LittleFS 对内存地址有对齐要求。直接从文件缓冲区用 sscanf 解析整数可能触发异常。
症状: cfgLoad() 调用时设备 hard fault 重启,串口看到 exception 信息。
解决: 先将文件读入 char line[192] 栈缓冲区,再用 sscanf 或 atoi 解析:
// 错误:
// sscanf(f.readStringUntil('\n').c_str(), "%d", &x); // 可能崩溃
// 正确:
char line[192];
int len = f.readBytesUntil('\n', line, sizeof(line) - 1);
line[len] = 0;
sscanf(line, "%d", &x); // 从栈缓冲区解析,安全
坑6:Python + PowerShell 编码冲突
Windows PowerShell 默认 GBK 编码,Python 脚本中的中文和转义字符会被错误编码。
症状: Python 报 SyntaxError: invalid syntax,但语法明明正确。
解决: 用 PowerShell here-string 管道传递(@'...'@ | python -),或写入临时文件后执行。不要在 PowerShell 的 -Command 参数中嵌入复杂 Python 代码。
---
最终成果
- 530KB 固件,运行在 1MB flash 的 ESP-01 上
- 5 页轮播:时间 → 实况 → 预报 → 逐小时 → 自定义文字
- 动态图标:8 帧动画,200ms 切换
- 3 个 API 数据源,智能刷新频率(5/30/60 分钟)
- Web 配置:WiFi、坐标搜索、页面开关、静态 IP
- OTA 更新:两跳桥接,60 秒完成
- 串口调试:完整日志输出,远程故障排查
总开发时间约 2 天,从"研究一台成品天气时钟"到全自研固件,代码量 60KB,注释占比 <5%。
---
文件清单
E:\esp8233\
├── platformio.ini 项目配置
├── deploy.bat 一键部署脚本
├── serial_listen.py 串口监听脚本
├── src/
│ ├── main.cpp 入口
│ ├── main_full.cpp.bak 完整固件备份
│ ├── main_bridge.cpp.bak 桥接固件模板
│ ├── config.h / config.cpp 配置管理
│ ├── net.h / net.cpp 网络 + OTA
│ ├── weather.h / weather.cpp 天气数据
│ ├── display.h / display.cpp OLED 渲染
│ └── web.h / web.cpp Web 服务
└── .pio/build/esp01_1m/ 编译输出