联网与通信
第 24 课:ESP-NOW——两块板子对话
不用路由器、不用网络,两块板子隔空直接说话
这节课需要两块板子。 你只有一块的话可以跳过——它不影响后面的任何课程。
我是吴老师。先说清楚,这节课你要多花几十块钱。 我不绕弯子。
但我要告诉你:这是整个套件里最有"科技感"的一节课。 前面所有课,你的板子都在跟"别人"打交道——跟路由器、跟服务器、跟手机。只有这节课,是两块板子面对面说话。
而且方式很硬核:不连路由器、不连 WiFi、不连互联网,什么都不用。
一、先把两块板子的事说完
为什么非要两块? 因为"说话"这件事,一个人干不了。你对着空气喊,那不叫通信。办法就两个:
- 再买一块。就买跟现在一样的 ESP32-C3 开发板,几十块钱。跟同学合买也行——你俩一人一块,还能互相烧程序、互相检查接线,比一个人闷头搞强多了。
- 跳过这节课。真的可以,它不挡你后面的任何一节课。第 25 课的蓝牙你一块板子就能做,第 26 课往后全是实战课题,也用不上两块板子。
我建议买。 理由不是"这课很重要",而是——两块板子能玩的东西,比一块板子多出一大截。 一个是"探测器",一个是"报警器",中间什么都不用连。很多东西只有这样才做得出来。
二、不用路由器?那怎么说话
先看这张对比图。这张图你得看明白,它是这节课的全部。
上面那条路,是你前四节课一直在走的:
板 A → 路由器 → 服务器 → 板 B。
看它绕了多大一圈。 板 A 说一句话,得先给路由器,路由器给它转上互联网,跑到某台服务器上,服务器再原路送回你家,交给板 B。
这条路很好用——你家在北京,板 B 在广州都行。但它有个前提:路由器得有、网得通、服务器不能挂、信号得覆盖到、板 B 也得连上同一个网——五样,少一样就全完蛋。
下面那条路,就是 ESP-NOW:板 A 直接跟板 B 说。
没有路由器,没有服务器,没有互联网。两个巴掌大的小板子,隔着一间屋子,就那么把话说了。
它是怎么做到的? 简单说:ESP-NOW 用的还是 WiFi 那套无线电,但不用 WiFi 那套"入网规矩"。
你可以这么想:WiFi 上网像去学校食堂吃饭——你得先办卡、排队、刷卡、被系统认出来,才能拿到饭。ESP-NOW 像两个同学在操场上喊话——不用办卡,直接喊。
(正式叫法是"点对点"或者"对等网络"。你以后查资料看到 peer-to-peer、P2P,就是这个意思。)
那它靠什么认出对方?
靠 MAC 地址。
第 20 课你连路由器的时候见过这个名字。每块板子的 WiFi 芯片,出厂就带一个 MAC 地址,全世界唯一。 跟你手机、你家的路由器一样,也是一串 XX:XX:XX:XX:XX:XX 这样的东西。
ESP-NOW 认的就是这串号。 板 A 想跟板 B 说话,就得把板 B 的 MAC 地址写进自己的程序里。
这就带出一个必然的后果:你得先把两块板子的 MAC 读出来。
记住这句话,它是这节课所有麻烦的源头。 我先把话撂这儿:你后面要是收不到消息,十有八九是这串地址写错了。
三、动手(一):先把两块板子的 MAC 读出来
这一步千万别跳。 而且两块板子都要做,各自读出各自的。
接线很简单——两块板子都不用接任何模块,插上数据线就行。
#include <WiFi.h>
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA); // 先切到"上网模式",再读 MAC
Serial.print("我的 MAC 地址是:");
Serial.println(WiFi.macAddress());
}
void loop() {
}这块板子烧一遍,打开串口,抄下来。 再换另一块板子,同样的程序再烧一遍,抄下来。
拿张纸,或者记事本,写清楚:
| MAC 地址 | |
|---|---|
| 板 A(探测器) | __:__:__:__:__:__ |
| 板 B(报警器) | __:__:__:__:__:__ |
这里有个小坑: 如果你读出来是
00:00:00:00:00:00,别慌,不是你板子坏了——先调用WiFi.mode(WIFI_STA)再读一遍。这行代码看着没用,其实是在告诉芯片"把 WiFi 打开,我要用"。什么时候需要核对 MAC? 任何时候你觉得"地址可能写错了",就把这个程序再烧一遍。它是个工具,不是一次性的。
四、动手(二):一块发,一块收
现在最关键的一句话:两块板子要烧不同的程序。
这是新手最容易犯的错,而且错得毫无痕迹。
你很自然就会想:"两块板子嘛,程序当然一样。"——然后两块都在发,没人收。现象是:两块板子都在串口里刷"我发了",对面一点动静都没有。
一块当"探测器"(发),一块当"报警器"(收)。程序不一样。
板 A(探测器)的程序
#include <WiFi.h>
#include <esp_now.h>
// ⚠ 换成你抄下来的"板 B"的 MAC 地址
uint8_t receiverMAC[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW 初始化失败");
return;
}
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, receiverMAC, 6);
peer.channel = 0;
peer.encrypt = false;
esp_now_add_peer(&peer); // 把对方"加为联系人"
}
void loop() {
uint8_t data = 1; // 1 = 有情况
esp_now_send(receiverMAC, &data, sizeof(data));
Serial.println("已发出:1");
delay(1000);
}看 esp_now_add_peer 那一步。 这一步就是在说:"板 B 的地址我记住了,以后发给它。" 跟手机里存个联系人是一个意思。
板 B(报警器)的程序
先接上灯和蜂鸣器:
| 模块 | 针脚 | 接到 |
|---|---|---|
| LED 模块 | VCC / GND / Signal |
3.3V / GND / GPIO3 |
| 蜂鸣器 | VCC / GND / I/O |
3.3V / GND / GPIO4 |
#include <WiFi.h>
#include <esp_now.h>
unsigned long lastAlarm = 0;
// 收到消息就会自动跑这个函数
void onReceive(const uint8_t *mac, const uint8_t *data, int len) {
Serial.print("收到来自:");
for (int i = 0; i < 6; i++) {
Serial.printf("%02X", mac[i]);
if (i < 5) Serial.print(":");
}
Serial.println();
if (len > 0 && data[0] == 1) {
digitalWrite(3, HIGH); // 灯亮
digitalWrite(4, HIGH); // 蜂鸣器响
lastAlarm = millis();
Serial.println("→ 报警!");
}
}
void setup() {
Serial.begin(115200);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
WiFi.mode(WIFI_STA);
Serial.print("我的 MAC:");
Serial.println(WiFi.macAddress()); // 顺便再确认一次
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW 初始化失败");
return;
}
esp_now_register_recv_cb(onReceive); // 挂号:收到了就叫我
}
void loop() {
if (millis() - lastAlarm > 500) { // 响半秒就停,免得吵死人
digitalWrite(3, LOW);
digitalWrite(4, LOW);
}
}两块板子都插上电。 板 B 的串口应该开始刷:
收到来自:AA:BB:CC:DD:EE:FF
→ 报警!灯亮半秒、蜂鸣器响半秒,一秒一次。
成了。这就是两块板子隔空说话。
你可以走两步试试。 拿到隔壁房间,看看还响不响。空旷处厂家标称能到几十甚至上百米,但你家有墙——能穿一两道墙就不错了。到底多远,你自己实测一遍,这个数字是你自己测出来的,比任何参数表都靠谱。
收不到?按这个顺序查
ESP-NOW 出问题,现象比联网课还干净:什么都没发生。 所以更得按顺序来。
① 先看两边串口。 板 A 那边在刷"已发出"吗?板 B 那边有没有动静?
- 板 A 都没在发 → 问题在板 A,先别管板 B
- 板 A 在发,板 B 没收到 → 往下走
② 一个字符一个字符核对 MAC 地址。
在数组里写的顺序是 {0xAA, 0xBB, 0xCC, ...},照抄串口打印的顺序,一位都不能差。少一位、错一位、0x 漏了——都不报错,就是不通。 这是最常见的失败原因,没有之一。
③ 确认两块板子烧的不是同一个程序。
板 B 的串口里有没有打印 我的 MAC:?有的话,它是接收端,对的。板 A 那边不应该打印 MAC——如果你两边都打印了,说明你烧混了。
④ 确认两块板子都没连路由器。 这一步很反直觉,但很重要:ESP-NOW 和 WiFi 用同一个无线电,得在同一个信道上。 你要是让一块板子连了你家 WiFi、另一块没连,它俩可能根本不在一个频道上。
最省事的做法:两块都别连路由器。 刚才的代码里我们只
WiFi.mode(WIFI_STA)、没调WiFi.begin(),就是故意的——两块板子都在默认信道上,一定能碰上。
⑤ 拔了重插,串口重开。 听起来很蠢,但**"板子没复位"这种问题真的存在**。两块板子同时断电、重新上电,很多时候就通了。
⑥ 最后再怀疑代码。 改一个地方,测一次。别一口气改五处——这条跟第 23 课是同一个规矩。
五、这东西能干嘛
我给你几个真实场景,你感受一下:
野外。 山里、地里、渔船上——那儿根本没有 WiFi。 你想让几十米外的传感器把数据传回来,ESP-NOW 就是最省事的办法。
地下室、车库、老房子。 信号差、穿墙难、路由器够不着。但两块板子之间隔个十来米,直连没问题。
一屋子的设备。 一个遥控器板子,同时给十几个灯板子发指令。用广播地址(FF:FF:FF:FF:FF:FF)一句话全收到,比挨个点名快得多。
你要记住一个事实:很多实际场景里,"没有网络"才是常态。
我们在教室里用 WiFi 用得太顺手,容易产生一种错觉——觉得哪儿都有网。 但你真去干点活就会发现:地下车库没信号、大棚里没信号、船上没信号。这时候,"不依赖网络的通信"就不是炫技,是刚需。
顺便说个反直觉的点:ESP-NOW 其实比 WiFi 省电得多。 因为它不用跟路由器保持连接、不用"入网"那一套握手,发完就睡。 你要是用电池供电,这个差别很大。
自测
答案与解析
第 20~23 课全都要"绕一大圈"——板子 → 路由器 → 服务器 → 对方。ESP-NOW 甩掉了中间所有环节,两块板子直接对话。代价是距离短、只能点对点(要传得更远还是得上网)。跟传得远不远、型号一不一样没关系——两块 ESP32-C3 就行,一块 ESP32 一块 C3 一般也能通。
答案与解析
关键判断是:板 B 连"收到来自:..."都没打印,说明消息压根没到它手上——那就跟 LED、蜂鸣器一点关系都没有(那两样是"收到了之后"才用到的)。ESP-NOW 收不到,最常见的原因就是 MAC 地址写错,而且它不报错、不提示,就是不通。一个字符一个字符对。
答案与解析
ESP-NOW 跑在 WiFi 的无线电上,两个设备必须同一个信道才能通。板子一连路由器,信道就跟着路由器走了(2.4G 里 1~13 随便哪个),两块板子很容易跑到不同信道上——现象还是"没反应",你根本想不到是信道问题。所以最省事的做法就是:两块都别连。
答题要点
答题要点应该包含:① ESP-NOW 是一问一答的关系,得有一方发、一方收,角色不同;② 发送端要把对方的 MAC 地址写进代码(esp_now_add_peer),接收端要注册接收回调(esp_now_register_recv_cb)——这两件事在代码里是两段不同的东西;③ 如果两块都烧成发送端,现象就是两块都在"我发了",没人收,而且从串口上看还挺正常,特别容易卡住;④ 能提到"用串口打印的 MAC 来判断这块板子烧的是哪份程序"这个技巧就更好了。
拓展一块板子能不能同时给好几块板子发消息?广播是怎么做到的?
这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。
提示:搜"ESP-NOW 广播地址 FF:FF:FF:FF:FF:FF"、"esp_now_add_peer 群发"。想一想:如果广播是"喊一嗓子谁都能听见",那安全吗?——你的指令会不会被别人的板子收走?
拓展为什么 ESP-NOW 比一直连着 WiFi 省电那么多?
这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。
提示:搜"ESP-NOW 低功耗 唤醒"、"WiFi 省电模式 DTIM"。想一想:"保持连接"这件事本身就是耗电的——你得一直听着路由器说话,生怕漏了一句。ESP-NOW 发完就睡,这个差别在电池供电的设备上有多大?
本节你要动手做的事
- [ ] 先确认你有两块板子。没有的话,这节课可以跳过(真的不影响后面的课)
- [ ] 两块板子各烧一遍"读 MAC"的程序,把自己的 MAC 抄在纸上——这一步不能跳
- [ ] 板 B 接好 LED(
GPIO3)和蜂鸣器(GPIO4) - [ ] 把板 B 的 MAC 填进板 A 的代码里,一个字符一个字符核对
- [ ] 烧两份不同的程序:板 A 当探测器,板 B 当报警器
- [ ] 看着灯亮、蜂鸣器响——两块板子隔空说话,通了
- [ ] 拿着板子走两步,走到隔壁房间,看多远还响——这个距离是你自己测出来的
- [ ] 故意把 MAC 的最后一位改错,跑一遍,看看是什么现象——下次真写错时你就能认出来
这节课你多花了几十块钱。 我不说"很值"这种空话,我说两个事实:
第一,你已经做出了一个不依赖任何外部设施的通信系统。 没有路由器、没有服务器、没有互联网——断电重来一遍,它照样能用。 世界上很多真正在干活的设备,就是这么工作的。
第二,你现在有"两头"了。 一块板子只能自言自语,两块板子就能互相触发。"探测器"和"报警器"分开放在两个地方——这个结构,你后面做项目会一直用。
下一节我们换一种无线:不用第二块板子,用你的手机。
而且我要先给你破个误解——关于蓝牙,你脑子里想的那个,多半是错的。