4、点对点通信(一):发送端配置——初始化ESP-NOW、注册发送回调函数、发送数据
好,咱们正式开始动手写代码了。
这一章,我带你完成ESP-NOW发送端的配置。说白了,就是让一块ESP32学会“说话”——把数据发出去。接收端怎么收,下一章再讲。
我个人习惯,做通信项目时,先把发送端调通。为什么呢?因为发送端逻辑相对独立,你发出去的数据对不对,用串口打印出来就能验证。接收端那边,等发送端稳定了再搞,效率更高。
4.1 初始化ESP-NOW
ESP-NOW的初始化,其实就两步:先初始化WiFi,再初始化ESP-NOW本身。嗯,这里有个坑,我当年第一次搞的时候踩过。
为什么是STA模式?你可以这么理解:ESP-NOW本质上是在WiFi的2.4G频段上直接通信,它借用了WiFi的硬件,但跳过了TCP/IP协议栈。所以WiFi得先“开机”,但不用连网。
来看代码:
#include <esp_now.h>
#include <WiFi.h>
void setup() {
Serial.begin(115200);
// 第一步:设置WiFi为STA模式
WiFi.mode(WIFI_STA);
// 第二步:初始化ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW 初始化失败!");
return;
}
Serial.println("ESP-NOW 初始化成功");
}
这段代码里,esp_now_init()返回ESP_OK才算成功。我曾经在项目里遇到过WiFi没初始化就直接调这个函数的情况,结果返回错误码,查了半天才发现是顺序问题。
esp_err_to_name(err)把错误码转成字符串打印出来,方便定位问题。
4.2 注册发送回调函数
初始化完了,下一步是注册回调函数。这个回调函数,会在每次数据发送完成后被自动调用。
你想想看,数据发出去了,你怎么知道对方收到了没有?回调函数就是干这个的——它会告诉你发送结果:成功还是失败,失败的原因是什么。
回调函数的格式是固定的:
// 发送回调函数
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("发送状态:");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "成功" : "失败");
// 打印目标MAC地址
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.print("目标设备:");
Serial.println(macStr);
}
然后在setup()里注册它:
esp_now_register_send_cb(OnDataSent);
就这么简单。注册之后,每次发数据,系统都会自动调用OnDataSent。
delay(1000),结果整个系统卡死。
4.3 发送数据
终于到发送环节了。ESP-NOW发送数据,核心函数就一个:esp_now_send()。
它的原型是:
esp_err_t esp_now_send(const uint8_t *peer_addr, const uint8_t *data, size_t len);
三个参数:
- peer_addr:目标设备的MAC地址。如果是广播,传
NULL。 - data:要发送的数据缓冲区指针。
- len:数据长度,单位字节。
来看一个完整的发送示例:
// 要发送的数据结构
typedef struct {
int id;
float temperature;
float humidity;
} sensor_data_t;
sensor_data_t myData;
void sendData() {
// 填充数据
myData.id = 1;
myData.temperature = 25.6;
myData.humidity = 60.2;
// 发送数据
esp_err_t result = esp_now_send(NULL, (uint8_t *)&myData, sizeof(myData));
if (result == ESP_OK) {
Serial.println("数据发送请求已提交");
} else {
Serial.println("数据发送失败");
}
}
这里我传了NULL作为目标地址,表示广播发送。如果你想点对点发送给某个特定设备,就把它的MAC地址传进去。
4.4 完整的发送端代码
把上面几部分拼起来,就是一个完整的发送端程序:
#include <esp_now.h>
#include <WiFi.h>
// 数据格式
typedef struct {
int id;
float temperature;
float humidity;
} sensor_data_t;
sensor_data_t myData;
// 发送回调
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("发送状态:");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "成功" : "失败");
}
void setup() {
Serial.begin(115200);
// 初始化WiFi
WiFi.mode(WIFI_STA);
// 初始化ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("初始化失败");
return;
}
// 注册回调
esp_now_register_send_cb(OnDataSent);
}
void loop() {
// 填充数据
myData.id = 1;
myData.temperature = 25.0 + random(0, 100) / 10.0;
myData.humidity = 60.0 + random(0, 100) / 10.0;
// 发送
esp_err_t result = esp_now_send(NULL, (uint8_t *)&myData, sizeof(myData));
if (result == ESP_OK) {
Serial.println("数据已发送");
} else {
Serial.println("发送失败");
}
delay(5000); // 每5秒发一次
}
这段代码每5秒发送一次模拟的温湿度数据。你可以用串口监视器看到发送结果。
delay(5000)改成delay(1000),这样能更快看到回调函数的输出。等确认通信正常了,再改回实际需要的间隔。
4.5 常见问题与避坑
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 初始化返回错误 | WiFi模式未设置或顺序不对 | 先调WiFi.mode(WIFI_STA),再调esp_now_init() |
| 发送回调一直显示失败 | 接收端未初始化或MAC地址错误 | 先确认接收端已上电并初始化ESP-NOW |
| 数据发送成功但接收端收不到 | 数据长度超过250字节 | 检查sizeof(myData)是否超过250 |
| 程序卡死或重启 | 回调函数中执行了耗时操作 | 回调里只做标记,数据处理放到loop() |
嗯,这一章的内容就到这里。你先把发送端的代码烧进去,看看串口输出。下一章,我们搞接收端,让两块ESP32真正聊起来。
记住:先跑通发送,再搞接收。这是我在多个项目里总结出来的经验,能省不少调试时间。