本帖最后由 gaoyang9992006 于 2023-8-17 22:27 编辑
@21小跑堂
ESP32-C3系列是RISC-V核心单片机中的一颗璀璨明珠,是一款性价比超高的WIFI单片机。
厂家提供了一种基于底层通信的协议ESP-NOW。无需无线路由器即可实现高速远距离通信。
ESP-NOW 是乐鑫定义的一种无线通信协议,能够在无路由器的情况下直接、快速、低功耗地控制智能设备。它能够与 Wi-Fi 和 Bluetooth LE 共存,支持乐鑫 ESP8266、ESP32、ESP32-S 和 ESP32-C 等多系列 SoC。ESP-NOW 广泛应用于智能家电、远程控制和传感器等领域。在 ESP-NOW 中,应用程序数据被封装在各个供应商的动作帧中,然后在无连接的情况下,从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备。
ESP-NOW 是基于数据链路层的无线通信协议,它将五层 OSI 上层协议精简为一层,数据传输时无需依次经过网络层、传输层、会话层、表示层、应用层等复杂的层级,也无需层层增加包头和解包,大大缓解了网络拥挤时因为丢包而导致的卡顿和延迟,拥有更高的响应速度。
经过两天的学习,已经掌握了基本用法,现将学习笔记记录如下:
发送端,发送端作为STA模式运行,接收端是被发现设备,因此作为AP模式运行。
发送端代码:
#include <esp_now.h>
#include <WiFi.h>
#include <esp_wifi.h>
//定义开发板载LED
#define LED4 12
#define LED5 13
//定义通信通道宏,指定在通道1通信
#define CHANNEL 1
// Global copy of slave
esp_now_peer_info_t slave;
esp_err_t addStatus;
//发送回调函数,即发送完成后通过该函数返回相关的地址与状态
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status)
{
for(int i = 0; i<6 ; i++ )
{
Serial.print("0x");Serial.print(*(mac_addr+i) , HEX);Serial.print(" ");
}
Serial.println();
if( status == ESP_NOW_SEND_SUCCESS )
{
Serial.println("ESP_NOW_SEND_SUCCESS"); //0是成功,1是失败
digitalWrite(LED4,!digitalRead(LED4));
}
}
void setup()
{
pinMode( LED4 , OUTPUT );
pinMode( LED5 , OUTPUT );
digitalWrite(LED4,LOW);
digitalWrite(LED5,LOW);
Serial.begin(115200);
WiFi.mode(WIFI_STA);
esp_err_t erro = esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE);
Serial.println("ESP_NOW_DEMO");
Serial.print("esp_wifi_set_channel:");
switch (erro)
{
case ESP_OK:Serial.println("succeed");
break;
case ESP_ERR_WIFI_NOT_INIT:Serial.println("WiFi is not initialized by esp_wifi_init");
break;
case ESP_ERR_WIFI_IF:Serial.println("invalid interface");
break;
case ESP_ERR_INVALID_ARG:Serial.println("invalid argument");
break;
}
Serial.print("STA MAC: "); Serial.println(WiFi.macAddress());
Serial.print("STA CHANNEL "); Serial.println(WiFi.channel());
//初始化ESP-NOW
if(esp_now_init() == ESP_OK)
{
Serial.println("ESPNow Init Success");
}
else
{
Serial.println("ESPNow Init Failed");
ESP.restart();
}
// Once ESP-Now is successfully Init, we will register for Send CB to
// get the status of Trasnmitted packet
esp_now_register_send_cb(OnDataSent);
int16_t scanResults = WiFi.scanNetworks(false,false,false,300,CHANNEL);
Serial.print("Found "); Serial.print(scanResults); Serial.println(" devices ");
for (int i = 0; i < scanResults; ++i)
{
// Print SSID and RSSI for each device found
String SSID = WiFi.SSID(i);
int32_t RSSI = WiFi.RSSI(i);
String BSSIDstr = WiFi.BSSIDstr(i);
Serial.print(i + 1);Serial.print(": ");
Serial.print(SSID);//热点名字
Serial.print(" (");Serial.print(RSSI);Serial.print(") ");//热点信号强度
Serial.print(" [");Serial.print(BSSIDstr);Serial.println("] ");//热点物理地址
Serial.println(BSSIDstr.c_str());
// Check if the current device starts with `Slave`
if (SSID.indexOf("Slave") == 0)
{
// SSID of interest
Serial.println("Found a Slave.");
Serial.print(i + 1); Serial.print(": "); Serial.print(SSID);
Serial.print(" ["); Serial.print(BSSIDstr); Serial.print("]");
Serial.print(" ("); Serial.print(RSSI); Serial.print(")"); Serial.println("");
// Get BSSID => Mac Address of the Slave
int mac[6];
if( 6== sscanf(BSSIDstr.c_str(), "%2x:%2x:%2x:%2x:%2x:%2x",&mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5] ))
{
Serial.print("<");
for( int i = 0 ; i<6 ; i++ )
{
slave.peer_addr[i] = (uint8_t)mac[i];
Serial.print(slave.peer_addr[i]);Serial.print(" ");
}
Serial.println(">");
slave.channel = CHANNEL; // pick a channel,选择一个信道
slave.encrypt = 0; // no encryption,不加密
}
}
}
addStatus = esp_now_add_peer( &slave );
}
uint8_t data = 0;
void loop()
{
// put your main code here, to run repeatedly:
data++;
const uint8_t *peer_addr = slave.peer_addr;
if(addStatus == ESP_OK)
{
esp_err_t result = esp_now_send(peer_addr, &data, sizeof(data));
Serial.print("Send Status: ");
if (result == ESP_OK)
{
Serial.println("Success");
}
else
{
Serial.println("Error");
}
}
delay(3000);
}
该例子,通过主动发现存在的指定通道的热点,然后逐个判断是否是想要的那些,然后找到目标后,对该目标发送一个变量,根据需要可以将发送内容改成别的内容,可以是字符串,可以是结构体。。。
下面是接收端代码
#include <esp_now.h>
#include <WiFi.h>
#include <esp_wifi.h>
//定义开发板载LED
#define LED4 12
#define LED5 13
//定义通信通道宏,指定在通道1通信
#define CHANNEL 1
// callback when data is recv from Master,回调返回的变量为发送设备的MAC地址,传输来的数据,数据包的长度
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len)
{
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("Last Packet Recv from: "); Serial.println(macStr);
Serial.print("Last Packet Recv Data: "); Serial.println(*data);
Serial.print("Last Packet Recv len:"); Serial.println(data_len);
Serial.println("");
digitalWrite(LED4,!digitalRead(LED4));
if(*data%2 == 0)
{
digitalWrite(LED5,!digitalRead(LED5));
}
}
void setup()
{
// put your setup code here, to run once:
pinMode( LED4 , OUTPUT );
pinMode( LED5 , OUTPUT );
digitalWrite(LED4,LOW);
digitalWrite(LED5,LOW);
Serial.begin(115200);
WiFi.mode(WIFI_AP);
const char *SSID = "Slave_A";
bool result = WiFi.softAP(SSID,"Slave_A_Password",CHANNEL,0);
if(!result)
{
Serial.println("Ap Config failed.");
}
else
{
Serial.println("AP Config Success. Broadcasting with AP: " + String(SSID));
Serial.print("AP CHANNEL "); Serial.println(WiFi.channel());
}
// This is the mac address of the Slave in AP Mode
Serial.print("AP MAC: "); Serial.println(WiFi.softAPmacAddress());
WiFi.disconnect();
//初始化ESP-NOW
if(esp_now_init() == ESP_OK)
{
Serial.println("ESPNow Init Success");
}
else
{
Serial.println("ESPNow Init Failed");
ESP.restart();
}
// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info.
esp_now_register_recv_cb(OnDataRecv);
}
void loop()
{
// put your main code here, to run repeatedly:
}
通过接收回调函数可以获取到发送者的MAC地址,以及发送的数据和数据长度。
该例子中根据接收的内容控制两个LED的亮灭。用于远距离测试时候作为指示标志。
本人经过接收器插入一个移动电源,在楼内走动,发现穿墙能力非常的强大,值的学习。
|
@gaoyang9992006 :通过MAC地址和对应的信道让发送函数发送数据
技术总结:发送端查找接收端的热点,并把符合要求的热点的MAC找到,通过MAC地址和信号让发送函数发送数据;接收端监听对应信道发送给自己MAC地址的消息,并返回发送者的MAC与数据。