实战课题
第 34 课:联网天气站
板子第一次去外面的世界取数据——它读的不再是你桌上那点东西了
我是吴老师。这个课题,跟前面 33 个都不一样。
我给你说清楚不一样在哪。
前面你做的所有东西——光控小夜灯、倒车雷达、打地鼠、桌面温度计——它们读的全是你桌上那点东西:你房间的光、你手边的距离、你屋里的温度。
它们活在一个小盒子里。盒子就是你那张桌子。
这节课,盒子被打开了。
你的板子会连上你家的路由器,穿过互联网,跑到几百公里外的一台服务器上,把此时此刻真实的天气取回来,画在那块 OLED 上。
而这一切,只用了一根 USB 线、一块几十块钱的板子,和不到五十行代码。
从这节课起,你的板子读的不再是"你桌上的世界",是"真实世界"。这是这块板子区别于一块普通 Arduino 的地方——第 20 课我说过这句话,今天是它真正兑现的时候。
做完之后你手上是一个纸皮相框,立在书桌上或者挂在墙上,屏幕上写着城市名、当前温度、湿度。它会自己更新,你什么都不用管。
这是这套课里第一个"你每天都会看一眼"的作品。
一、先说清楚:这个课题的三个环节
| 环节 | 这个课题里是谁 |
|---|---|
| 传感器(看) | WiFi + HTTP——去互联网上把天气取回来 |
| 主控(想) | 取到了吗?是 200 吗?温度是多少? |
| 执行器(做) | OLED 把城市、温度、湿度显示出来 |
注意这次的"传感器"这一栏——它不是一块插在板子上的模块。
它是一条看不见的链路:板子 → 路由器 → 互联网 → 天气接口 → 再原路跑回来。第 20 课讲的那条路,今天真的要跑一趟。
所以这个课题最特殊的地方是:传感器可能"取不到数"。 你桌上的光敏不会突然罢工,但几百公里外那台服务器会。这件事,第九节和第十节专门讲。
二、材料清单
套件里的:ESP32-C3 主控 + 拓展板、OLED 显示屏、杜邦线若干。
身边要准备的:纸皮、美工刀、剪刀、铅笔、直尺、热熔胶枪 + 胶棒,还有——你家 WiFi 的名字和密码(这个最重要,第九节你会知道为什么)。
知识点上的准备:这个课题要用到第 20 课(连 WiFi)和第 22 课(HTTP 请求)。那两节没走扎实的话,先回去补一下——这里会用到它们全部的排查顺序。
还得装一个库:
ArduinoJson。在库管理器里搜这个名字,装上就行。它就是第 22 课结尾我提过的那个库——当时我说"真正要从中取值的时候,我们会用它"。就是现在。
三、接线:四根线
接之前先断电。这次只有 OLED 一个模块。
| OLED | 接到 |
|---|---|
| GND | GND |
| VDD | 3.3V |
SCK |
GPIO9 |
| SDA | GPIO8 |
跟第 19 课一模一样,VDD 就是正极、SCK 就是时钟。
这个课题的接线极其简单——因为它真正的难点不在线上。
提前把话说在这儿:这一课最容易出问题的地方,一个是 WiFi 名字,一个是供电。 两个都在第九节等着你。
四、动手第一步:先连上 WiFi,并且把状态写到屏幕上
连 WiFi 的代码,第 20 课原样搬过来。但这次不做"连上就完事"——我们要把连接过程显示在屏幕上。
先定义一个画屏幕的小函数,后面反复用:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
// 顶上一行状态、中间一个大数、底下一行小字
void drawScreen(String status, String big, String small) {
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(1);
oled.setCursor(0, 0);
oled.print(status);
oled.setTextSize(3);
oled.setCursor(0, 22);
oled.print(big);
oled.setTextSize(1);
oled.setCursor(0, 54);
oled.print(small);
oled.display();
}然后 setup() 里:
const char* WIFI_NAME = "把这里换成你家 WiFi 的名字";
const char* WIFI_PASS = "把这里换成你家 WiFi 的密码";
void setup() {
Serial.begin(115200);
delay(1500);
Wire.begin(8, 9);
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
WiFi.mode(WIFI_STA);
drawScreen("正在连 WiFi", "...", WIFI_NAME); // ★ 先说自己在忙什么
WiFi.begin(WIFI_NAME, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.print("连上了,IP:");
Serial.println(WiFi.localIP());
drawScreen("WiFi 已连接", "OK", WiFi.localIP().toString());
delay(1500);
}烧进去。屏幕先出现"正在连 WiFi",然后变成"WiFi 已连接"和一个 IP。
这一步别小看它。 加了这块屏幕之后,你不用打开电脑就知道板子连没连上——这个习惯,后面做第 35 课远程开关灯的时候,会救你好几次。
为什么要先说"我正在忙"
因为连 WiFi 要好几秒,取数据又要好几秒。
这几秒里,如果屏幕一片空白,你会以为板子死了。 你会上来拔线、重新烧录、怀疑人生——而它其实只是在等。
让设备"说出自己在忙什么",是所有联网设备的基本礼貌。 你手机上转的那个圈、电梯里的"正在运行"、网页上的进度条,都是同一个道理。
五、动手第二步:把天气取回来
第 22 课那段 HTTP 请求,搬过来,加上 ArduinoJson 取值:
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
// 北京的经纬度。换成你自己城市的——去搜"XX市 经纬度"
const char* URL =
"http://api.open-meteo.com/v1/forecast"
"?latitude=39.9&longitude=116.4"
"¤t=temperature_2m,relative_humidity_2m";
const char* CITY = "北京";
void loop() {
HTTPClient http;
drawScreen("正在取数据", "...", CITY); // ★ 发请求之前先说一声
http.begin(URL);
int code = http.GET(); // 第 22 课那个状态码
Serial.print("状态码:");
Serial.println(code);
if (code == 200) {
String body = http.getString();
Serial.println(body); // 先打印出来看——第 7 课那句
JsonDocument doc; // 新版 ArduinoJson(v7)
deserializeJson(doc, body);
float t = doc["current"]["temperature_2m"];
float h = doc["current"]["relative_humidity_2m"];
Serial.print("温度 "); Serial.print(t);
Serial.print(" 湿度 "); Serial.println(h);
drawScreen(CITY, String(t, 1) + "C", "湿度 " + String(h, 0) + "%");
} else {
Serial.println("没取到——先看状态码,再怀疑代码");
drawScreen(CITY, "--.-", "取不到数据");
}
http.end();
delay(600000); // 10 分钟更新一次,别刷太快
}烧进去。屏幕上先出现"正在取数据",然后跳出你城市的温度。
那一刻,你屏幕上那个数,是此时此刻、几百公里外的真实天气。
JsonDocument doc;是新版 ArduinoJson(7.x)的写法。 如果你装的是旧版(6.x),这一行要改成DynamicJsonDocument doc(2048);。编译报错的话,把报错原文复制给 AI,它会告诉你该用哪种写法。(第 22 课那句:报错原文是最好的线索。)同理,如果
http.begin(URL)那行报参数不对,按第 22 课给的写法改成WiFiClient client; http.begin(client, URL);。
调试的时候把
delay(600000)先改成delay(30000)(30 秒),不然你改一次代码要等十分钟才能看到下一次结果。等功能都稳定了再改回去——免费接口有限流,问太频繁会被拒(第 22 课那张表里的429)。
六、动手第三步:三种状态,一个都不能少
现在回头看一眼刚才那两段代码——你会发现屏幕上其实在说三种不同的话:
| 什么时候 | 屏幕显示什么 |
|---|---|
| 正在连 WiFi | 正在连 WiFi / ... / WiFi 名字 |
| 正在取数据 | 正在取数据 / ... / 城市名 |
| 取到了 | 城市名 / 24.3C / 湿度 55% |
| 取不到 | 取不到数据 / --.- / 等一会儿再试 |
最后那一行是这一节的重点。
免费接口是公网上的东西,它不欠你什么——可能挂了、可能限流了、可能今天改了规矩(第 22 课第八节整节都在讲这件事)。
它一挂,你的程序就走 else 那一支。 如果你在 else 里什么都没写,屏幕上就停在上一次的内容,或者干脆一片空白。
空白是最糟的结果——它会让人以为"板子坏了",而其实板子好好的,只是这一趟没取到数。
所以:取不到的时候,也要给个明确的交代。 哪怕只是两个横杠。
再进一步:把上一次取到的数据留着。 定义一个全局变量存着,取不到的时候显示旧数,旁边标一个"上次的"。你自己试试看怎么写——这是很好的练习。
七、动手第四步:先用电脑浏览器试通
这一步是第 22 课教你的排查思路,我在这里再说一遍,因为它是这一课最省时间的一招。
在写板子代码之前,先把那个网址原封不动复制到电脑浏览器里,回车。
你应该看到一大坨 JSON 文本糊在屏幕上,里面有 temperature_2m 这样的字段。
电脑上能打开,你的板子才有戏。 电脑上都打不开,那问题根本不在板子——是在外面(接口挂了、你家网不通),你的代码一个字都不用改。
先在外面把接口试通,再回来写板子的代码。 这个顺序反过来,你会陷入最痛苦的调试——改一晚上代码,其实是对方服务器倒了。
八、组装:纸皮相框
现在给它一个像样的身子。
目标:一个立得住(或者挂得上)的相框,屏幕嵌在正中间,像一幅画。
要剪的纸皮:面板(约 12 cm × 10 cm,开 OLED 的窗)、背板(同样大小)、框边条若干(约 1 cm 宽,做相框的边)、撑脚(10 cm × 3 cm,折成 L 形)。
① 开窗。 老规矩——OLED 背面朝上,沿着玻璃屏的边缘描一圈,不是描整块板子。垫上废纸皮,美工刀慢慢割。
② 给相框做"边"。 沿着窗口四周贴一圈 1 cm 宽的纸皮条,稍微比窗口大一点——这一圈边,就是相框的"框"。
想更好看,可以换个材料:用麻绳、旧布条、或者把纸皮条斜着剪(两头剪成 45 度)拼成一个真正的"斜角相框"。这是唯一一个你可以放手玩外观的课题,做完是要摆在桌上的。
③ 屏幕下面写一行字。 用铅笔或者马克笔写上 北京天气站 或者你的城市名,下面小字写日期或者你的名字。相框要有落款。
④ 装背板和主控。 主控板用热熔胶固定在背板中央——胶打在拓展板边缘,别打在芯片和 USB 口上。 USB 口要留出插线的空间。
⑤ 面板和背板合起来,做成能翻开的。 用一条纸皮当"合页"粘在顶部或侧面。为什么不做死? 因为这是联网的东西——以后你可能要改 WiFi 名字、要改城市、要改 delay,翻开来就能插线重烧。 粘死了你就得撕。
⑥ 装撑脚,或者穿挂绳。 想立着放就粘 L 形撑脚(先干着试角度);想挂墙上就在面板顶边穿一根绳,绳子从纸皮里穿过去的地方加一小块纸皮垫着,不然纸皮很快会被勒破。
⑦ 最后,把 USB 线固定一下。 从背板下方留个缺口让线出来,用一小块胶带把线固定在背板上——线一拽,插头松了,板子就断电重启。这是"时好时坏"故障的经典来源。
九、排查:一直连不上
这是这个课题里我给你留的坑。而且这次有两个。
做完之后,你会遇到这么个现象:
屏幕一直停在"正在连 WiFi",串口里一直往外刷点,就是不变成"已连接"。
别急。第 20 课那套七步顺序,就是为这一刻准备的。
坑一:WiFi 名字里那个你打不出来的字符
第 1 步:先看串口。 别改代码,先看它说了什么——"先打印,再判断"(第 7 课那句)。
串口里应该打印着"准备连的是:MyHome"。看起来完全正确。
第 2 步:看状态码。 打印出来是 1。1 是**"找不到这个名字的网"**——注意,不是密码错(密码错一般是 4)。
"我手机明明能连上这个 WiFi,怎么会找不到?" 这就是最坑的地方。
第 3 步:让板子自己把周围的网报出来。 跑第 20 课第三节那段扫描代码,把附近所有 WiFi 的名字打出来。
然后,把你家那个名字从串口里选中、复制,粘到代码里的引号中间。
别手打。 为什么这么强调?
- 大小写:
MyHome和myhome是两个完全不同的网,差一个字母都不行 - 空格:名字末尾或者中间夹着一个空格,你手打的时候根本看不见
- 全角半角:
Home(全角)和Home(半角)长得几乎一模一样,其实是两串完全不同的字符。中文输入法没切回来的时候,打出来的就是全角 - 中文和表情:有的路由器名字是中文的,甚至带个 emoji——你手打几乎不可能打对
根因就在这儿:你在手机 WiFi 列表里看到的名字,和路由器实际广播出来的字符,可能不是同一串。
这个坑是我故意留的,但它也完全是真实情况——第 20 课第 2 步专门讲过这件事,今天你踩上了。
一次性的根治办法:登录路由器后台,把 WiFi 名字改成纯英文的短名字(比如
wangjia)。这不是偷懒,是省掉你以后无数次的麻烦——家里换了新设备、来了客人要连网,都会轻松很多。
坑二:名字密码全对,还是连不上——这次得请万用表出场
如果在第 3 步之后,名字密码都确认无误了,它还是连不上,那就往下看。
第 4 步:再仔细看一遍串口。 这次不看"点",看最开头。
如果你发现串口里在反复打印同一段启动信息——就是那种板子刚上电时会打的几行字,打完一遍,过几秒又打一遍,那就说明板子在反复重启。
方向完全变了:这不是网络问题。
第 5 步:请万用表上场。
- 拨到直流电压档
- 黑表笔搭板子的
GND,红表笔点板子的3.3V引脚 - 眼睛盯住表,等它开始联网的那一瞬间
你会看到:平时稳稳的 3.3V,联网那一瞬间掉到 2.6V、甚至 2.4V,然后板子重启。
第 6 步:换算成"电流"想一想。 板子待机的时候只吃几十毫安,WiFi 一开,瞬间要两三百毫安——一下子涨了五六倍。
如果你的 USB 线太细、太长,或者中间接了个便宜的分线器 / 延长线 / 老充电宝,它撑不住这一下。电压一塌,板子就重启;一重启,网当然永远连不上。
根因
供电撑不住联网那一瞬间的电流。
为什么这个坑只有万用表能定位? 因为从外面看,现象是**"连不上网"**——跟 WiFi 名字写错、密码不对、信号弱长得一模一样。你会一直在网络那边打转,永远想不到问题在电源上。
只有把表笔搭上去,盯着那一瞬间的电压,你才能看见真相。
解决办法很简单:换一根短的、粗一点的数据线,直接插电脑的 USB 口。 别用分线器、别用延长线。如果要用充电头,选能输出 1A 以上的。
顺便说一句:这个坑我留得有点坏,但它非常真实。 现实里"设备一联网就重启""一开电机就重启"这种故障,十次里有七八次是电源。你以后做第 35 课、第 37 课,模块一多,这个问题还会再来找你。
记住这个动作:电压档,黑笔搭 GND,红笔点电源脚,盯着看它干活的那一瞬间。 这是你这门课学会的、最值钱的一个动作。
自测
答案与解析
一片空白是最糟的结果——它会让人以为"板子坏了",而其实板子好好的,只是这一趟没取到数。免费接口挂了、限流了、改规矩了,都是常态(第 22 课第八节)。所以取不到的时候也要给个明确交代,最好再保留上次的数据并标注"上次的"。
MyHome",状态码是 1,但手机能连上这个 WiFi。最可能的原因是什么?答案与解析
状态码 1 是**"找不到这个名字的网"**,不是密码错(那是 4)。名字看着一样、实际不一样的原因特别多:大小写、看不见的空格、全角半角(Home vs Home)、中文和表情。正确做法:跑一遍扫描代码,把名字从串口里复制粘贴进代码,别手打;或者干脆去路由器后台把名字改成纯英文短名。
3.3V 引脚发现联网瞬间掉到 2.6V。这说明什么?答案与解析
板子待机只吃几十毫安,WiFi 一开瞬间要两三百毫安,涨了五六倍。USB 线太细太长、接了便宜的分线器或延长线,就撑不住这一下——电压一塌,板子重启;一重启,网永远连不上。 解决:换一根短的粗线,直接插电脑 USB 口。
答题要点
答题要点应该包含:这一步的作用是"一刀切开外面和里面"。电脑上也打不开 → 问题在外面(接口挂了 / 你家网不通),你的代码一个字都不用改;电脑上能打开 → 问题在板子这边,回去查 WiFi 和状态码。它把"外面的世界"和"你的代码"分开,不然你会陷入最痛苦的一种调试——改一晚上代码,其实是对方服务器倒了。这跟第 5 课"电 → 信号 → 代码"是同一个思路:先确定问题在哪一段,再动手。
拓展板子是怎么根据一串经纬度,找到对应城市的天气的?这个经纬度到底喂给了谁?
这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。
提示:搜"open-meteo API 文档 参数"、"经纬度 网格 气象模型"、"逆地理编码"。想一想:天气预报是怎么算出来的?为什么同一个坐标,不同的天气服务给出的结果会不一样?
拓展ESP32 是怎么把"文字"变成无线电波发出去的?为什么它只能连 2.4GHz 的 WiFi?
这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。
提示:搜"ESP32 WiFi 射频 调制 OFDM"、"2.4GHz 5GHz 波长 穿透 区别"。想一想:5GHz 更快,为什么穿墙更差?如果让你给一块电池供电的板子设计联网方案,你会选哪个频段?
本节你要动手做的事
- [ ] 先接好 OLED:
VDD→3.3V,GND→GND,SCK/SDA→GPIO9/GPIO8 - [ ] 装
ArduinoJson库——第 22 课结尾埋的那个,现在用上了 - [ ] 先把网址粘到电脑浏览器里试通,看到那坨 JSON 再回来写代码
- [ ] 跑通"连 WiFi + 屏幕显示状态",确认屏幕上出现 IP
- [ ] 跑通取数据,屏幕上出现你城市的温度
- [ ] 故意把 WiFi 名字改错一个字符,看状态码变成几,记住这个数
- [ ] 故意把网址改错,看状态码变成
404——第 22 课让你看过一次,再确认一次 - [ ] 做纸皮相框:开窗、贴框边、装撑脚或挂绳、留 USB 缺口并固定线
- [ ] 如果连不上,按第九节那六步走一遍。最后记得量一次联网瞬间的 3.3V
这一节课你一行硬件都没接(就四根线),但你做成了一件挺大的事。
你的板子走出了你那张桌子。
前面 33 节课,它读的全是你手边的东西——你房间的光、你桌上的温度。今天它读的是几百公里外、此时此刻真实的天气。而你只写了不到五十行代码。
说句实在的:这一课你多半会在"连不上"上花掉一半时间。这不是你的问题——网络这东西不受你控制:路由器可能限制新设备、免费接口可能正好在那一刻挂了、你家的网可能刚好抽风。
但你没有摔板子,你在按顺序排查。 从"看串口"到"量电压"到"用浏览器试接口",这套流程你已经走熟了——它比"能跑通的那份代码"值钱得多。
还有件事你要记住:这一课是唯一一个"你自己没做错,也可能失败"的课题。别人的服务器挂了,不是你的错——分辨"是不是我的问题",是联网调试的基本功。
下一课是第 35 课:远程开关灯——从 C 档开始,没人给你完整答案了。
今天你的板子是去"取"数据:它主动问,服务器才答。下一课方向反过来:你在外面发一条消息,家里的灯亮了。
"控制"这件事,比"取数据"要难,也有意思得多。