Lập trình & Điều khiển Động Cơ – Nâng cao – Bài 16: Thiết kế bộ điều khiển từ xa cho Robot (phần 3): Lập trình cơ bản


(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ểnChức năng
Tay cầm điều khiển (TX)ESP32-C3Gửi lệnh điều khiển qua NRF24L01
Xe robot (RX)ESP32 DevKit 38PinNhậ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ầnVai tròMô tả hoạt động
radio.begin()Khởi tạo module NRF24L01Chuẩn bị SPI và kênh RF
radio.write()Gửi dữ liệu từ TXTay cầm gửi gói lệnh
radio.read()Nhận dữ liệu từ RXXe 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
lastPacketBiến giám sát tín hiệuDừ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.

Comments

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

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *