实战课题

第 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 课原样搬过来。但这次不做"连上就完事"——我们要把连接过程显示在屏幕上。

先定义一个画屏幕的小函数,后面反复用:

C++
#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() 里:

C++
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 取值:

C++
#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"
  "&current=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 / ... / 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 步:看状态码。 打印出来是 11 是**"找不到这个名字的网"**——注意,不是密码错(密码错一般是 4)。

"我手机明明能连上这个 WiFi,怎么会找不到?" 这就是最坑的地方。

第 3 步:让板子自己把周围的网报出来。 跑第 20 课第三节那段扫描代码,把附近所有 WiFi 的名字打出来。

然后,把你家那个名字从串口里选中、复制,粘到代码里的引号中间。

别手打。 为什么这么强调?

  • 大小写MyHomemyhome 是两个完全不同的网,差一个字母都不行
  • 空格:名字末尾或者中间夹着一个空格,你手打的时候根本看不见
  • 全角半角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,红笔点电源脚,盯着看它干活的那一瞬间。 这是你这门课学会的、最值钱的一个动作。

自测

单选题板子取不到数据的时候,屏幕上应该显示什么?
单选题串口打印"准备连的是:MyHome",状态码是 1,但手机能连上这个 WiFi。最可能的原因是什么?
单选题板子连 WiFi 时反复重启,用万用表量 3.3V 引脚发现联网瞬间掉到 2.6V。这说明什么?
问答题联网失败的时候,为什么"先把网址粘到电脑浏览器里试一遍"能省掉你一半的排查时间?
拓展板子是怎么根据一串经纬度,找到对应城市的天气的?这个经纬度到底喂给了谁?

这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。

提示:搜"open-meteo API 文档 参数"、"经纬度 网格 气象模型"、"逆地理编码"。想一想:天气预报是怎么算出来的?为什么同一个坐标,不同的天气服务给出的结果会不一样?

拓展ESP32 是怎么把"文字"变成无线电波发出去的?为什么它只能连 2.4GHz 的 WiFi?

这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。

提示:搜"ESP32 WiFi 射频 调制 OFDM"、"2.4GHz 5GHz 波长 穿透 区别"。想一想:5GHz 更快,为什么穿墙更差?如果让你给一块电池供电的板子设计联网方案,你会选哪个频段?

本节你要动手做的事

  • [ ] 先接好 OLEDVDD3.3VGNDGNDSCK/SDAGPIO9/GPIO8
  • [ ] ArduinoJson——第 22 课结尾埋的那个,现在用上了
  • [ ] 先把网址粘到电脑浏览器里试通,看到那坨 JSON 再回来写代码
  • [ ] 跑通"连 WiFi + 屏幕显示状态",确认屏幕上出现 IP
  • [ ] 跑通取数据,屏幕上出现你城市的温度
  • [ ] 故意把 WiFi 名字改错一个字符,看状态码变成几,记住这个数
  • [ ] 故意把网址改错,看状态码变成 404——第 22 课让你看过一次,再确认一次
  • [ ] 做纸皮相框:开窗、贴框边、装撑脚或挂绳、留 USB 缺口并固定线
  • [ ] 如果连不上,按第九节那六步走一遍。最后记得量一次联网瞬间的 3.3V

这一节课你一行硬件都没接(就四根线),但你做成了一件挺大的事。

你的板子走出了你那张桌子。

前面 33 节课,它读的全是你手边的东西——你房间的光、你桌上的温度。今天它读的是几百公里外、此时此刻真实的天气。而你只写了不到五十行代码。

说句实在的:这一课你多半会在"连不上"上花掉一半时间。这不是你的问题——网络这东西不受你控制:路由器可能限制新设备、免费接口可能正好在那一刻挂了、你家的网可能刚好抽风。

但你没有摔板子,你在按顺序排查。 从"看串口"到"量电压"到"用浏览器试接口",这套流程你已经走熟了——它比"能跑通的那份代码"值钱得多。

还有件事你要记住:这一课是唯一一个"你自己没做错,也可能失败"的课题。别人的服务器挂了,不是你的错——分辨"是不是我的问题",是联网调试的基本功。

下一课是第 35 课:远程开关灯——从 C 档开始,没人给你完整答案了。

今天你的板子是去"取"数据:它主动问,服务器才答。下一课方向反过来:你在外面发一条消息,家里的灯亮了。

"控制"这件事,比"取数据"要难,也有意思得多。