(Khóa học: Lập trình & Điều khiển Động Cơ từ Cơ Bản tới Nâng Cao)
🎯 Mục tiêu bài học
Trong phần 3, bạn sẽ học cách lập trình cơ bản cho tay cầm điều khiển và xe robot, sử dụng ESP32-C3 và ESP32 DevKit kết hợp với module NRF24L01 để gửi – nhận lệnh điều khiển qua sóng RF 2.4GHz.
Kết thúc bài học, bạn sẽ tạo được một bộ điều khiển robot từ xa hoàn chỉnh:
- Tay cầm gửi lệnh di chuyển (Tiến, Lùi, Trái, Phải, Dừng).
- Xe robot nhận lệnh và điều khiển hai động cơ DC thông qua driver L298N.
⚙️ 1. Tổng quan chương trình
Hệ thống được chia thành hai chương trình độc lập:
| Thiết bị | Vi điều khiển | Chức năng |
|---|---|---|
| Tay cầm điều khiển (TX) | ESP32-C3 | Gửi lệnh điều khiển qua NRF24L01 |
| Xe robot (RX) | ESP32 DevKit 38Pin | Nhận lệnh và điều khiển động cơ |
Cả hai chương trình đều sử dụng thư viện RF24 (Arduino) để giao tiếp NRF24L01 qua SPI.
🕹️ 2. Chương trình cho tay cầm điều khiển (Transmitter – TX)
🔧 Thành phần:
- ESP32-C3
- NRF24L01
- 4 nút nhấn: Tiến, Lùi, Trái, Phải
- (Tùy chọn) Joystick để điều chỉnh tốc độ
💻 Code mẫu:
#include <SPI.h>
#include <RF24.h>
// Khai báo chân kết nối NRF24L01
#define CE_PIN 7
#define CSN_PIN 10
RF24 radio(CE_PIN, CSN_PIN);
const byte address[6] = "ROBOT";
// Cấu trúc dữ liệu gói tin gửi đi
struct Command {
char action; // F, B, L, R, S
uint8_t speed; // 0-255
} cmd;
// Khai báo các nút điều khiển
#define BTN_F 2
#define BTN_B 3
#define BTN_L 8
#define BTN_R 9
#define JOY_Y 1 // trục Y (nếu có joystick)
void setup() {
Serial.begin(115200);
pinMode(BTN_F, INPUT_PULLUP);
pinMode(BTN_B, INPUT_PULLUP);
pinMode(BTN_L, INPUT_PULLUP);
pinMode(BTN_R, INPUT_PULLUP);
analogReadResolution(12);
SPI.begin();
radio.begin();
radio.setChannel(100);
radio.setDataRate(RF24_1MBPS);
radio.setPALevel(RF24_PA_LOW);
radio.openWritingPipe(address);
radio.stopListening();
Serial.println("Remote TX Ready");
}
char readButton() {
if (!digitalRead(BTN_F)) return 'F';
if (!digitalRead(BTN_B)) return 'B';
if (!digitalRead(BTN_L)) return 'L';
if (!digitalRead(BTN_R)) return 'R';
return 'S';
}
void loop() {
cmd.action = readButton();
int joy = analogRead(JOY_Y);
cmd.speed = constrain(map(joy, 0, 4095, 120, 255), 0, 255);
if (cmd.action == 'S') cmd.speed = 0;
radio.write(&cmd, sizeof(cmd));
Serial.printf("Send: %c (%d)\n", cmd.action, cmd.speed);
delay(20);
}
📊 Giải thích:
- radio.openWritingPipe(address) → mở kênh gửi dữ liệu tới “ROBOT”.
- radio.write(&cmd, sizeof(cmd)) → gửi gói lệnh điều khiển.
- Dữ liệu gửi đi chỉ gồm 2 byte (action và speed) → cực kỳ nhanh.
- Mỗi 20ms (50Hz), tay cầm gửi 1 gói lệnh → điều khiển mượt mà.
🚗 3. Chương trình cho xe robot (Receiver – RX)
🔧 Thành phần:
- ESP32 DevKit 38Pin
- NRF24L01
- Driver L298N
- 2 motor DC
💻 Code mẫu:
#include <SPI.h>
#include <RF24.h>
#define CE_PIN 17
#define CSN_PIN 16
RF24 radio(CE_PIN, CSN_PIN);
const byte address[6] = "ROBOT";
struct Command {
char action;
uint8_t speed;
} cmd;
#define IN1 26
#define IN2 27
#define IN3 25
#define IN4 33
#define ENA 14
#define ENB 32
void setupMotors() {
pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
ledcSetup(0, 5000, 8); ledcAttachPin(ENA, 0);
ledcSetup(1, 5000, 8); ledcAttachPin(ENB, 1);
}
void drive(char action, uint8_t speed) {
switch (action) {
case 'F': // Forward
digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
break;
case 'B': // Backward
digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH);
digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);
break;
case 'L': // Turn Left
digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH);
digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
break;
case 'R': // Turn Right
digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);
break;
default: // Stop
digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
}
ledcWrite(0, speed);
ledcWrite(1, speed);
}
unsigned long lastPacket = 0;
void setup() {
Serial.begin(115200);
setupMotors();
SPI.begin();
radio.begin();
radio.setChannel(100);
radio.setDataRate(RF24_1MBPS);
radio.setPALevel(RF24_PA_LOW);
radio.openReadingPipe(1, address);
radio.startListening();
Serial.println("Robot RX Ready");
}
void loop() {
if (radio.available()) {
radio.read(&cmd, sizeof(cmd));
drive(cmd.action, cmd.speed);
Serial.printf("Recv: %c (%d)\n", cmd.action, cmd.speed);
lastPacket = millis();
}
// Dừng xe nếu mất tín hiệu > 300ms
if (millis() - lastPacket > 300) {
drive('S', 0);
}
}
🧠 4. Giải thích logic chương trình
| Thành phần | Vai trò | Mô tả hoạt động |
|---|---|---|
| radio.begin() | Khởi tạo module NRF24L01 | Chuẩn bị SPI và kênh RF |
| radio.write() | Gửi dữ liệu từ TX | Tay cầm gửi gói lệnh |
| radio.read() | Nhận dữ liệu từ RX | Xe nhận lệnh từ tay cầm |
| drive() | Hàm điều khiển động cơ | Thực thi lệnh tương ứng |
| lastPacket | Biến giám sát tín hiệu | Dừng xe khi mất kết nối |
🧩 5. Mẹo lập trình và kiểm thử
- Test từng phần: kiểm tra NRF trước, sau đó mới thêm điều khiển motor.
- Nếu “radio not responding” → kiểm tra CE/CSN, tụ 10µF gần NRF.
- Thêm Serial.println() để theo dõi dữ liệu truyền đi – nhận về.
- Sử dụng delay(20) để duy trì tốc độ gửi ~50Hz.
- Đảm bảo mass chung giữa ESP32 và L298N.
🚀 6. Kết quả mong đợi
Sau khi nạp chương trình:
- Khi bấm nút tiến/lùi/rẽ, robot phản hồi ngay lập tức.
- Khi thả tay khỏi nút, robot tự động dừng lại.
- Nếu tắt tay cầm → robot tự dừng trong 0.3 giây (failsafe).
🧠 7. Chuẩn bị cho bài tiếp theo
Bài tiếp theo – (phần 4): Tối ưu điều khiển, bạn sẽ học cách:
- Làm mượt tốc độ PWM.
- Giảm rung giật khi khởi động.
- Cải thiện phản hồi điều khiển.
- Thêm chế độ “Kid Mode” và “Sport Mode” cho tay cầm.


Để lại một bình luận