ESP-NOW - giao tiếp ESP32 với ESP32, làm tay cầm và robot bầy đàn

ESP-NOW gửi gói tin trực tiếp giữa các ESP32 không cần router, trễ vài ms, xa 100m+. Code cặp tay cầm joystick - robot hoàn chỉnh và mẫu robot bầy đàn. Trong bài viết này, chúng ta sẽ cùng tìm hiểu chi tiết và ứng dụng thực tế trong robot.
ESP-NOW là gì và vì sao dân robot mê nó?
ESP-NOW là giao thức không dây riêng của Espressif cho phép các ESP32 (và ESP8266) bắn gói tin trực tiếp cho nhau trên sóng 2.4 GHz: không cần router, không quá trình kết nối WiFi, không TCP/IP. Gói tin đi thẳng từ chip đến chip theo địa chỉ MAC — như tin nhắn bắn giữa hai bộ đàm đã lưu số của nhau.
Ba con số làm nên danh tiếng của nó với robot:
- Độ trễ ~2-10 ms — nhanh hơn HTTP hàng chục lần, ngang tay cầm RC thương mại
- Tầm 50-200 m ngoài trời thoáng (xa hơn hẳn kết nối WiFi thông thường vì không cần duy trì phiên)
- 0 đồng — mọi ESP32 đều có sẵn; cặp tay cầm + robot tự chế trọn bộ ~300.000 đ
Đánh đổi: chỉ nói chuyện được giữa các chip Espressif (điện thoại không tham gia trực tiếp), gói tin tối đa 250 byte — quá đủ cho lệnh điều khiển.
Khái niệm tối thiểu cần nắm
- Địa chỉ MAC: số danh định 6 byte của mỗi ESP32 (dạng 24:6F:28:xx:xx:xx). Lấy bằng
WiFi.macAddress()— bước đầu tiên của mọi dự án ESP-NOW là nạp code in MAC lên hai bo và ghi lại - Peer: thiết bị đối tác đã đăng ký — muốn gửi cho ai phải add MAC người đó làm peer trước
- Callback: hàm được gọi tự động khi nhận gói (
onReceive) hoặc khi gửi xong (onSent) — mô hình sự kiện thay vì hỏi vòng - Struct chung: hai bên thống nhất một cấu trúc dữ liệu (kiểu và thứ tự field giống hệt nhau) — gói tin chính là bản chụp struct đó
Dự án chuẩn: tay cầm joystick điều khiển robot
Phần cứng tay cầm
ESP32 + module joystick 2 trục (10.000-15.000 đ) + hộp pin — joystick trả 2 giá trị analog (X, Y) vào GPIO 34, 35.
Code bên TAY CẦM (gửi)
#include <esp_now.h>
#include <WiFi.h>
// MAC của ESP32 TRÊN ROBOT - thay bằng số của bạn
uint8_t robotMac[] = {0x24, 0x6F, 0x28, 0xAA, 0xBB, 0xCC};
typedef struct {
int16_t x; // -255..255
int16_t y;
uint8_t buttons;
} ControlPacket;
ControlPacket packet;
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA); // ESP-NOW cần mode STA
esp_now_init();
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, robotMac, 6);
peer.channel = 0;
esp_now_add_peer(&peer);
}
void loop() {
// đọc joystick, quy về -255..255 với vùng chết ở giữa
int rawX = analogRead(34), rawY = analogRead(35);
packet.x = map(rawX, 0, 4095, -255, 255);
packet.y = map(rawY, 0, 4095, -255, 255);
if (abs(packet.x) < 20) packet.x = 0; // deadzone chống trôi
if (abs(packet.y) < 20) packet.y = 0;
esp_now_send(robotMac, (uint8_t *)&packet, sizeof(packet));
delay(20); // 50 gói/giây - quá đủ mượt
}Code bên ROBOT (nhận)
#include <esp_now.h>
#include <WiFi.h>
typedef struct {
int16_t x;
int16_t y;
uint8_t buttons;
} ControlPacket;
volatile int16_t joyX = 0, joyY = 0;
volatile unsigned long lastPacket = 0;
void onReceive(const esp_now_recv_info_t *info,
const uint8_t *data, int len) {
if (len == sizeof(ControlPacket)) {
ControlPacket p;
memcpy(&p, data, len);
joyX = p.x;
joyY = p.y;
lastPacket = millis();
}
}
void setup() {
Serial.begin(115200);
setupMotors();
WiFi.mode(WIFI_STA);
Serial.println(WiFi.macAddress()); // MAC để điền vào code tay cầm
esp_now_init();
esp_now_register_recv_cb(onReceive);
}
void loop() {
if (millis() - lastPacket > 300) { // FAILSAFE mất sóng
drive(0, 0);
return;
}
// trộn lái kiểu arcade: Y = ga, X = lái
int left = constrain(joyY + joyX, -255, 255);
int right = constrain(joyY - joyX, -255, 255);
drive(left, right);
}Hai khối đáng học nhất: arcade mixing (một joystick lái cả hai bánh — công thức cộng trừ ngắn mà thanh lịch) và failsafe 300 ms (mất sóng là robot dừng, không phóng tiếp vô định — quy tắc an toàn bắt buộc của mọi thiết bị điều khiển từ xa).
Từ một-một đến bầy đàn
ESP-NOW không giới hạn ở cặp đôi:
- Broadcast: gửi tới MAC FF:FF:FF:FF:FF:FF — mọi ESP32 trong tầm cùng nhận. Một lệnh "xuất phát!" cho cả chục robot đua — nền tảng của màn đồng diễn robot
- Một tay cầm nhiều robot: thêm trường
robotIdvào struct, robot chỉ phản ứng gói đúng số mình — chuyển kênh điều khiển bằng nút bấm trên tay cầm - Chuỗi tiếp sức (mesh thô): robot A ngoài tầm tay cầm nhưng trong tầm robot B — B nhận và phát tiếp. ESP-NOW không có mesh chính thức nhưng tự dựng tiếp sức 2-3 chặng khá dễ, thành bài học định tuyến trực quan
Robot bầy đàn (swarm) cấp độ học đường — vài robot chia sẻ vị trí, giữ đội hình, tránh nhau — hoàn toàn khả thi trên nền broadcast + struct chung, và là đề tài nghiên cứu KHKT ăn điểm sáng tạo.
Bẫy kỹ thuật cần biết trước
- API callback đổi theo phiên bản core: core 3.x dùng
esp_now_recv_info_tnhư code trên; core 2.x cũ là(const uint8_t *mac, ...)— code trên mạng lẫn lộn hai đời, lỗi biên dịch callback gần như chắc chắn gặp; sửa theo đúng core đang cài - Hai bên phải cùng kênh WiFi: mặc định cùng channel 0 là ổn; nhưng nếu robot đồng thời kết nối WiFi router (dual mode), kênh bị router quyết định — tay cầm phải đặt theo đúng kênh đó
- Struct hai bên phải khớp từng byte: thêm field một bên mà quên bên kia = dữ liệu lệch pha; giữ file định nghĩa struct chung copy sang cả hai sketch
- Không mã hóa mặc định: ai biết MAC đều gửi lệnh được; ESP-NOW hỗ trợ mã hóa peer (LMK/PMK) — bật khi dự án ra khỏi phòng lab
- ADC2 vẫn bị cấm khi radio bật: joystick ở tay cầm cắm vào GPIO 32-39 (ADC1) như code trên
Câu hỏi thường gặp
ESP-NOW và Bluetooth, cái nào điều khiển robot tốt hơn?
Nếu cả hai đầu đều là ESP32: ESP-NOW thắng về trễ, tầm và độ đơn giản giao thức. Bluetooth thắng khi đầu điều khiển là điện thoại — điện thoại không nói được ESP-NOW. Tay cầm tự chế → ESP-NOW; app điện thoại → Bluetooth/BLE (xem bài Điều khiển robot từ xa ở mục Kiến thức).
Gửi được dữ liệu cảm biến ngược từ robot về tay cầm không?
Được — ESP-NOW hai chiều tự nhiên: robot cũng add tay cầm làm peer và send struct telemetry (pin, khoảng cách). Gắn màn OLED lên tay cầm hiện số liệu là có "tay cầm FPV mini" — nâng cấp được yêu thích của học sinh.
250 byte mỗi gói có chật không?
Lệnh điều khiển dùng ~5-10 byte — dư 25 lần. 250 byte chỉ chật khi truyền ảnh/âm thanh, việc vốn không phải đất của ESP-NOW; luồng dữ liệu lớn hãy dùng WiFi thường.
Pin tay cầm dùng được bao lâu?
ESP32 phát ESP-NOW mỗi 20 ms ăn trung bình ~100-150 mA — pin 18650 2500 mAh chạy ~15-20 giờ. Muốn hơn: tăng chu kỳ gửi lên 50 ms khi joystick đứng yên, hoặc light sleep giữa các lần gửi — tay cầm chạy cả tháng cho người chịu tối ưu.
Tài nguyên tải về
Tải bản PDF của bài viết để in giáo án hoặc gửi trước cho học viên.



