Xe điều khiển từ xa với ESP32
Cơ bản3/6/2026

Xe điều khiển từ xa với ESP32

Chế tạo xe điều khiển từ xa qua Wi-Fi bằng ESP32, nâng cao kiến thức về động cơ và mạch điều khiển.

0 lượt thích1 người đã làm1 giờ7 bước6 linh kiện
← Quay lại dự án
Hướng dẫn chi tiết

Phần 5: Hoàn thiện và kiểm tra xe điều khiển từ xa

1. Lắp ráp các linh kiện vào khung xe, 2. Kiểm tra kết nối giữa ứng dụng và xe, 3. Thực hiện các bài test điều khiển xe, 4. Giải quyết các vấn đề kỹ thuật phát sinh, 5. Đánh giá hiệu suất của xe và cải thiện nếu cần.

Cập nhật 09/06/2026

Phần 5: Hoàn thiện và kiểm tra xe điều khiển từ xa

1. Lắp ráp các linh kiện vào khung xe

Trước khi lắp ráp, hãy đảm bảo rằng bạn có đầy đủ các linh kiện: ESP32, động cơ DC, bánh xe, khung xe, mạch điều khiển động cơ (ví dụ: L298N hoặc L293D), và pin. Bắt đầu lắp đặt động cơ vào khung xe, sau đó kết nối chúng với mạch điều khiển động cơ. Đảm bảo rằng tất cả các kết nối đều chắc chắn và không có dây nào bị lỏng.

2. Kiểm tra kết nối giữa ứng dụng và xe

Sử dụng ứng dụng điều khiển trên smartphone hoặc máy tính bảng để kết nối với ESP32 qua Wi-Fi. Đảm bảo rằng ESP32 đã được lập trình để tạo một điểm truy cập Wi-Fi hoặc kết nối với mạng Wi-Fi có sẵn. Kiểm tra địa chỉ IP của ESP32 và đảm bảo ứng dụng có thể gửi lệnh đến nó.

3. Thực hiện các bài test điều khiển xe

Chạy thử xe bằng cách sử dụng ứng dụng. Dưới đây là code hoàn chỉnh: Xe ESP32 điều khiển qua Wi-Fi bằng điện thoại.


#include <WiFi.h>
#include <WebServer.h>

// =======================
// Wi-Fi Access Point
// =======================
const char* apSsid = "IoTLabs-ESP32-Car";
const char* apPassword = "12345678"; // tối thiểu 8 ký tự

WebServer server(80);

// =======================
// Motor driver pins - L298N
// =======================
// Motor trái
#define ENA 25
#define IN1 26
#define IN2 27

// Motor phải
#define ENB 32
#define IN3 14
#define IN4 33

// Tốc độ mặc định: 0 - 255
int motorSpeed = 180;

// Thời gian an toàn: nếu quá lâu không nhận lệnh thì tự dừng
unsigned long lastCommandTime = 0;
const unsigned long commandTimeout = 1500;

// =======================
// HTML giao diện điều khiển
// =======================
const char MAIN_page[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
  <title>IoTLabs ESP32 Wi-Fi Car</title>
  <style>
    body {
      margin: 0;
      font-family: Arial, sans-serif;
      background: #0f172a;
      color: white;
      text-align: center;
    }

    .container {
      max-width: 420px;
      margin: auto;
      padding: 20px;
    }

    h1 {
      font-size: 24px;
      margin-bottom: 4px;
    }

    .brand {
      color: #2dd4bf;
      font-weight: bold;
      margin-bottom: 20px;
    }

    .status {
      background: #1e293b;
      border-radius: 12px;
      padding: 12px;
      margin-bottom: 20px;
      font-size: 14px;
    }

    .controls {
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      gap: 12px;
      margin-top: 20px;
    }

    button {
      height: 76px;
      border: none;
      border-radius: 16px;
      font-size: 20px;
      font-weight: bold;
      color: white;
      background: #2563eb;
      box-shadow: 0 6px 0 #1e40af;
      touch-action: manipulation;
    }

    button:active {
      transform: translateY(4px);
      box-shadow: 0 2px 0 #1e40af;
    }

    .stop {
      background: #dc2626;
      box-shadow: 0 6px 0 #991b1b;
    }

    .empty {
      visibility: hidden;
    }

    .speed-box {
      background: #1e293b;
      border-radius: 16px;
      padding: 16px;
      margin-top: 24px;
    }

    input[type=range] {
      width: 100%;
    }

    .small {
      font-size: 13px;
      color: #cbd5e1;
      margin-top: 16px;
      line-height: 1.5;
    }
  </style>
</head>

<body>
  <div class="container">
    <h1>ESP32 Wi-Fi Car</h1>
    <div class="brand">IoTLabs Maker</div>

    <div class="status">
      Điều khiển xe qua Wi-Fi bằng điện thoại<br>
      Giữ nút để chạy, thả nút để dừng
    </div>

    <div class="controls">
      <button class="empty">.</button>
      <button 
        ontouchstart="sendCmd('forward')" 
        ontouchend="sendCmd('stop')" 
        onmousedown="sendCmd('forward')" 
        onmouseup="sendCmd('stop')">▲</button>
      <button class="empty">.</button>

      <button 
        ontouchstart="sendCmd('left')" 
        ontouchend="sendCmd('stop')" 
        onmousedown="sendCmd('left')" 
        onmouseup="sendCmd('stop')">◀</button>

      <button class="stop" onclick="sendCmd('stop')">STOP</button>

      <button 
        ontouchstart="sendCmd('right')" 
        ontouchend="sendCmd('stop')" 
        onmousedown="sendCmd('right')" 
        onmouseup="sendCmd('stop')">▶</button>

      <button class="empty">.</button>
      <button 
        ontouchstart="sendCmd('backward')" 
        ontouchend="sendCmd('stop')" 
        onmousedown="sendCmd('backward')" 
        onmouseup="sendCmd('stop')">▼</button>
      <button class="empty">.</button>
    </div>

    <div class="speed-box">
      <label>Tốc độ: <span id="speedValue">180</span></label>
      <input type="range" min="80" max="255" value="180" oninput="setSpeed(this.value)">
    </div>

    <div class="small">
      Website: iotlabs.vn<br>
      Maker: maker.iotlabs.vn
    </div>
  </div>

  <script>
    function sendCmd(cmd) {
      fetch('/cmd?act=' + cmd).catch(err => console.log(err));
    }

    function setSpeed(value) {
      document.getElementById('speedValue').innerText = value;
      fetch('/speed?value=' + value).catch(err => console.log(err));
    }
  </script>
</body>
</html>
)rawliteral";

// =======================
// Motor functions
// =======================

void setMotorLeft(int speedValue, bool forward) {
  speedValue = constrain(speedValue, 0, 255);

  if (forward) {
    digitalWrite(IN1, HIGH);
    digitalWrite(IN2, LOW);
  } else {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, HIGH);
  }

  analogWrite(ENA, speedValue);
}

void setMotorRight(int speedValue, bool forward) {
  speedValue = constrain(speedValue, 0, 255);

  if (forward) {
    digitalWrite(IN3, HIGH);
    digitalWrite(IN4, LOW);
  } else {
    digitalWrite(IN3, LOW);
    digitalWrite(IN4, HIGH);
  }

  analogWrite(ENB, speedValue);
}

void moveForward() {
  setMotorLeft(motorSpeed, true);
  setMotorRight(motorSpeed, true);
}

void moveBackward() {
  setMotorLeft(motorSpeed, false);
  setMotorRight(motorSpeed, false);
}

void turnLeft() {
  // Bánh trái lùi, bánh phải tiến
  setMotorLeft(motorSpeed, false);
  setMotorRight(motorSpeed, true);
}

void turnRight() {
  // Bánh trái tiến, bánh phải lùi
  setMotorLeft(motorSpeed, true);
  setMotorRight(motorSpeed, false);
}

void stopCar() {
  analogWrite(ENA, 0);
  analogWrite(ENB, 0);

  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
}

// =======================
// Web handlers
// =======================

void handleRoot() {
  server.send(200, "text/html", MAIN_page);
}

void handleCommand() {
  if (!server.hasArg("act")) {
    server.send(400, "text/plain", "Missing command");
    return;
  }

  String action = server.arg("act");
  lastCommandTime = millis();

  if (action == "forward") {
    moveForward();
  } else if (action == "backward") {
    moveBackward();
  } else if (action == "left") {
    turnLeft();
  } else if (action == "right") {
    turnRight();
  } else if (action == "stop") {
    stopCar();
  } else {
    server.send(400, "text/plain", "Invalid command");
    return;
  }

  server.send(200, "text/plain", "OK: " + action);
}

void handleSpeed() {
  if (!server.hasArg("value")) {
    server.send(400, "text/plain", "Missing speed value");
    return;
  }

  int value = server.arg("value").toInt();
  motorSpeed = constrain(value, 80, 255);

  server.send(200, "text/plain", "Speed set to " + String(motorSpeed));
}

// =======================
// Setup
// =======================

void setup() {
  Serial.begin(115200);
  delay(500);

  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);

  pinMode(ENB, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);

  stopCar();

  WiFi.mode(WIFI_AP);
  WiFi.softAP(apSsid, apPassword);

  IPAddress ip = WiFi.softAPIP();

  Serial.println();
  Serial.println("ESP32 Wi-Fi Car started");
  Serial.print("Wi-Fi name: ");
  Serial.println(apSsid);
  Serial.print("Password: ");
  Serial.println(apPassword);
  Serial.print("Open this address on your phone: http://");
  Serial.println(ip);

  server.on("/", handleRoot);
  server.on("/cmd", handleCommand);
  server.on("/speed", handleSpeed);

  server.begin();

  lastCommandTime = millis();
}

// =======================
// Loop
// =======================

void loop() {
  server.handleClient();

  // Tự dừng nếu mất kết nối/lâu không nhận lệnh
  if (millis() - lastCommandTime > commandTimeout) {
    stopCar();
  }
}

Kiểm tra các chức năng như tiến, lùi, rẽ trái và phải. Ghi lại phản hồi từ xe để đánh giá độ nhạy của điều khiển.

Chú ý: Hãy sử dụng nguồn riêng cho động cơ DC và không lấy nguồn trực tiếp từ ESP32, vì dòng tiêu thụ của động cơ có thể làm sập nguồn ESP32. Nên sử dụng diode flyback để bảo vệ ESP32 khỏi nhiễu điện từ động cơ.

4. Giải quyết các vấn đề kỹ thuật phát sinh

Tip: Nếu xe không phản hồi đúng lệnh, hãy kiểm tra lại kết nối Wi-Fi và mã nguồn. Đảm bảo rằng tất cả các chân GPIO được kết nối chính xác.

Các vấn đề thường gặp bao gồm mất kết nối Wi-Fi, động cơ không quay hoặc quay ngược chiều. Kiểm tra lại các kết nối điện và mã lệnh để đảm bảo mọi thứ hoạt động như mong muốn.

5. Đánh giá hiệu suất của xe và cải thiện nếu cần

Sau khi thực hiện các bài test, đánh giá hiệu suất của xe như tốc độ, độ ổn định và khả năng điều khiển. Nếu xe hoạt động không như mong đợi, xem xét việc thay đổi các thông số trong mã nguồn hoặc cải tiến phần cứng như sử dụng động cơ mạnh hơn hoặc pin có dung lượng lớn hơn.