ESP32 NOW là gì? Cách ghép nối và gửi dữ liệu giữa các mạch ESP32
Nhờ có ESP32 NOW, chúng ta có thể dễ dàng trao đổi dữ liệu giữa các mạch lập trình với nhau. Cụ thể, ESP32-NOW là một giao thức truyền thông không dây được Espressif (nhà phát triển mạch ESP32) xây dựng, giúp truyền các gói dữ liệu ngắn.
ESP32 NOW là gì?
ESP32-NOW là một giao thức truyền thông không dây, cho phép nhiều thiết bị có thể dễ dàng kết nối và giao tiếp với nhau mà không cần phải dùng đến mạng WiFi. Chúng có cách kết nối tương tự như 2.4GHz, các thiết bị cần phải ghép nối với nhau trước khi truyền dữ liệu.
Sau khi ghép nối, các thiết bị có thể gửi và nhận dữ liệu ngang hàng một cách an toàn. Điều này đồng nghĩa với việc chúng ta chỉ cần ghép nối 1 lần đầu tiên. Giả sử trong trường hợp bạn reset mạch hoặc tắt mở lại, thì chúng sẽ tự động kết nối lại.
Một số tính năng của ESP32 NOW gồm:
- Gửi dữ liệu mã hóa hoặc không mã hóa
- Có thể gửi dữ liệu lên đến 250 byte
- Các thiết bị có thể được mã hóa hoặc không mã hóa
Tuy nhiên, ESP32 NOW cũng có một số nhược điểm mà bạn cần biết:
- Việc mã hóa các thiết bị bị hạn chế, chỉ hỗ trợ tối đa 10 thiết bị
- Dữ liệu gửi đi bị giới hạn trong mức 250 byte
Nói ngắn gọn lại, bạn chỉ cần hiểu rằng ESP32 NOW là một giao thức truyền tải thông tin nhanh chóng, thường dùng trong việc gửi các tin nhắn gọn nhẹ, ngắn giữa các mạch ESP32 với nhau.
ESP32 NOW giao tiếp một chiều
Giao tiếp một chiều bao gồm các trường hợp như:
1. Một ESP32 này gửi dữ liệu đến một ESP32 khác. Bạn có thể sử dụng giao tiếp 1 chiều trong các trường hợp phổ biến như bật/tắt để điều khiển các chân GPIO:
2. Một ESP32 master gửi dữ liệu đến nhiều ESP32 Slave khác. Trường hợp này, chúng ta thường dùng trong các ứng dụng như điều khiển từ xa, trong đó một ESP32 gửi dữ liệu đến nhiều mạch ESP32 khác:
3. Một ESP32 Slave nhận thông tin từ nhiều ESP32 master. Chúng ta thường ứng dụng vào các ứng dụng như tạo một Web Server để hiển thị dữ liệu nhận được từ nhiều mạch ESP32 khác nhau:
Tuy nhiên, bạn cần hiểu rằng không có quy định cụ thể mạch nào là slave và mạch nào là master. Mỗi một mạch ESP32 đều có thể là slave và master.
ESP32 NOW giao tiếp hai chiều
Trong phương thức giao tiếp 2 chiều này, mỗi một mạch ESP32 đều có thể là người gửi và đồng thời là người nhận. Do đó, chúng ta gọi là giao tiếp 2 chiều:
Tuy nhiên, ở hình trên thì mình chỉ ví dụ về một giao tiếp 2 chiều đơn giản. Trên thực tế, bạn có thể tạo một mạng gồm nhiều ESP32 được kết nối, giao tiếp 2 chiều với nhau qua ESP32 NOW, chẳng hạn như hình dưới:
Chuẩn bị – Lấy địa chỉ MAC của ESP32
Để tạo kết nối ESP32 NOW giữa nhiều mạch ESP32 với nhau, bạn cần biết địa chỉ MAC của mỗi mạch ESP32 là gì. Điều này giúp bạn biết được mình sẽ gửi dữ liệu tới mạch nào.
Mỗi một ESP32 sẽ có một địa chỉ MAC duy nhất. Bạn có thể nạp đoạn code sau vào mạch ESP32 của mình để lấy địa chỉ đó:
#include "WiFi.h" void setup(){ Serial.begin(115200); WiFi.mode(WIFI_MODE_STA); Serial.println(WiFi.macAddress()); } void loop(){ }
Sau khi nạp code, bạn mở Serial Monitor ở tốc độ 115200 và nhấn nút RST trên ESP32. Lúc đó, trên màn hình Serial sẽ hiển thị địa chỉ MAC, ví dụ như hình:
Cách tạo ESP32 NOW giao tiếp một chiều – Point to point
Trước tiên, mình sẽ hướng dẫn bạn cách tạo một giao tiếp ESP32 NOW một chiều đơn giản, với dữ liệu được gửi từ 1 ESP32 này (master) đến 1 ESP32 khác (slave).
Mình sẽ lập trình gửi một biến có kiểu dữ liệu số nguyên (int) hoặc ký tự (char). Bạn có thể chọn bất kỳ kiểu dữ liệu nào khác mà bạn thích đều được nhé!
Mình sẽ gọi ESP32 gửi dữ liệu là #1, và code cho chúng tuân theo thuật toán sau:
- Khởi tạo ESP32 NOW
- Sử dụng hàm callback khi gửi dữ liệu, hàm OnDataSent sẽ được chạy khi một tin nhắn được gửi đi. Điều này giúp mình biết tin nhắn đã gửi thành công hay chưa
- Thêm một thiết bị ESP32 làm người nhận (chúng ta cần biết địa chỉ MAC của ESP32 nhận này)
- Gửi tin nhắn đến ESP32 nhận
Mình gọi ESP32 nhận dữ liệu là #2, và code của ESP32 nhận này là:
- Khởi tạo ESP32 NOW
- Sử dụng hàm callback OnDataRecv, hàm này được chạy khi đã nhận được tin nhắn
- Lưu thông báo vào một biến cụ thể, cho phép hệ thống thực hiện một tác vụ nhất định với thông tin này.
Cụ thể:
Code cho ESP32 gửi
#include <esp_now.h> #include <WiFi.h> // REPLACE WITH YOUR RECEIVER MAC Address uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // Structure example to send data // Must match the receiver structure typedef struct struct_message { char a[32]; int b; float c; bool d; } struct_message; // Create a struct_message called myData struct_message myData; esp_now_peer_info_t peerInfo; // callback when data is sent void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\nLast Packet Send Status:\t"); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void setup() { // Init Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } // Once ESPNow is successfully Init, we will register for Send CB to // get the status of Trasnmitted packet esp_now_register_send_cb(OnDataSent); // Register peer memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; // Add peer if (esp_now_add_peer(&peerInfo) != ESP_OK){ Serial.println("Failed to add peer"); return; } } void loop() { // Set values to send strcpy(myData.a, "THIS IS A CHAR"); myData.b = random(1,20); myData.c = 1.2; myData.d = false; // Send message via ESP-NOW esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); if (result == ESP_OK) { Serial.println("Sent with success"); } else { Serial.println("Error sending the data"); } delay(2000); }
Dưới đây mình sẽ giải thích chi tiết cho chương trình:
Khai báo thư viện:
#include <esp_now.h>
#include <WiFi.h>
Chèn địa chỉ MAC của ESP32 nhận:
uint8_t broadcastAddress[] = {0x30, 0xAE, 0xA4, 0x07, 0x0D, 0x64};
Lưu ý: Bạn cần thay đổi địa chỉ MAC này thành địa chỉ ESP32 của bạn nhé!
Tạo cấu trúc dữ liệu cần gửi, hiện tại mình cần cấu trúc có chứa 4 loại kiểu dữ liệu khác nhau:
typedef struct struct_message {
char a[32];
int b;
float c;
bool d;
} struct_message;
Tạo biến mới là myData để lưu trữ giá trị cho biến:
struct_message myData;
Tạo biến peerInfo để lưu thông tin:
esp_now_peer_info_t peerInfo;
Gọi hàm OnDataSent, hàm này sẽ chạy khi tin nhắn đã gửi đi thành công:
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
Trong setup(), khởi tạo Serial:
Serial.begin(115200);
Cấu hình thiết bị làm trạm WiFi:
WiFi.mode(WIFI_STA);
Khởi tạo ESP32 NOW:
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
Đăng ký chức năng callback OnDataSent() đã tạo:
esp_now_register_send_cb(OnDataSent);
Ghép nối với ESP32 khác thông qua ESP32 NOW để gửi dữ liệu:
//Register peer
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
//Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
Trong loop(), lập trình cho hệ thống sau mỗi 2 giây thì gửi tin nhắn 1 lần. Đầu tiên, bạn cần tạo các biến:
strcpy(myData.a, "THIS IS A CHAR");
myData.b = random(1,20);
myData.c = 1.2;
myData.d = false;
Bạn đừng quên myData là một cấu trúc nhé! Bên trong cấu trúc này có chứa các biến có các kiểu dữ liệu mà mình muốn gửi. Mình đang dùng các kiểu dữ liệu ký tự (char), số nguyên (int), float và boolean. Đây là những kiểu dữ liệu phổ biến nhất, bạn có thể thay đổi cấu trúc để gửi những dữ liệu khác tùy thích theo nhu cầu nhé.
Câu lệnh gửi tin nhắn:
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
Kiểm tra xem tin nhắn được gửi đi thành công chưa:
if (result == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
Chọn độ trễ là 2000 milli giây (2 giây):
delay(2000);
Code cho ESP32 nhận
#include <esp_now.h> #include <WiFi.h> // Structure example to receive data // Must match the sender structure typedef struct struct_message { char a[32]; int b; float c; bool d; } struct_message; // Create a struct_message called myData struct_message myData; // callback function that will be executed when data is received void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&myData, incomingData, sizeof(myData)); Serial.print("Bytes received: "); Serial.println(len); Serial.print("Char: "); Serial.println(myData.a); Serial.print("Int: "); Serial.println(myData.b); Serial.print("Float: "); Serial.println(myData.c); Serial.print("Bool: "); Serial.println(myData.d); Serial.println(); } void setup() { // Initialize Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } // Once ESPNow is successfully Init, we will register for recv CB to // get recv packer info esp_now_register_recv_cb(OnDataRecv); } void loop() { }
Dưới đây mình sẽ giải thích chi tiết về đoạn code này:
Khai báo thư viện:
#include <esp_now.h>
#include <WiFi.h>
Tạo cấu trúc nhận dữ liệu (lưu ý phải giống với cấu trúc đã khai báo trong ESP32 gửi):
typedef struct struct_message {
char a[32];
int b;
float c;
bool d;
} struct_message;
Tạo một biến là myData:
struct_message myData;
Gọi chức năng OnDataRecv():
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
Sao chép dữ liệu trong incomingData vào myData:
memcpy(&myData, incomingData, sizeof(myData));
Lúc này, trong cấu trúc myData đã có các biến bên trong, với các dữ liệu được gửi đến từ ESP32 #1 (ESP32 gửi). Để truy cập vào giá trị a đã được gửi, chúng ta chỉ cần gọi myData.a.
Ví dụ, chúng ta sẽ in giá trị nhận được:
Serial.print("Bytes received: ");
Serial.println(len);
Serial.print("Char: ");
Serial.println(myData.a);
Serial.print("Int: ");
Serial.println(myData.b);
Serial.print("Float: ");
Serial.println(myData.c);
Serial.print("Bool: ");
Serial.println(myData.d);
Serial.println();
Trong setup(), khởi tạo Serial Monitor:
Serial.begin(115200);
Cấu hình thiết bị thành trạm WiFi:
WiFi.mode(WIFI_STA);
Khởi tạo ESP32 NOW:
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
Gọi hàm OnDataRecv():
esp_now_register_recv_cb(OnDataRecv);
Nạp code và kiểm tra chương trình
Bạn hãy nạp code vào cho ESP32 gửi và ESP32 nhận nhé! Sau đó, mở 2 cửa sổ Serial Monitor cho 2 mạch (lưu ý mỗi mạch ESP32 phải có một cổng COM khác nhau).
Trên màn hình Serial của ESP32 gửi sẽ có các dòng thông báo đã gửi tin nhắn thành công (sau mỗi 2 giây sẽ có 1 tin nhắn như vậy).
Với ESP32 nhận, trên màn hình ESP32 sẽ in ra các giá trị nhận được, ví dụ:
Trên thực tế, ESP32 NOW có thể gửi dữ liệu ở khoảng cách lên đến khoảng 220 mét trong không gian ngoài trời, không bị rào chắn bởi tường hoặc chướng ngại vật. Bạn cần để ăng ten trên 2 mạch ESP32 gửi và nhận hướng vào nhau để có thể gửi dữ liệu ở khoảng cách này nhé!
Lời kết
Trên đây, IoTZone đã giới thiệu đến bạn chi tiết về ESP32 NOW là gì, cũng như hướng dẫn cách lập trình khởi tạo kết nối ESP32-NOW đơn giản nhất cho bạn dễ hình dung. Với gợi ý cơ bản này, bạn có thể ứng dụng vào các dự án nâng cao hơn như quét tìm các thiết bị Slave xung quanh, quản lý nhiều ESP32 khác nhau,… Chúc các bạn thành công!