ESP32 Điều Khiển Xe Hơi qua Web

Hướng dẫn này sẽ chỉ cho bạn cách sử dụng ESP32 để điều khiển không dây một chiếc xe robot từ trình duyệt Web trên smartphone hoặc PC của bạn bằng WiFi. Việc điều khiển được thực hiện thông qua giao diện web đồ họa người dùng sử dụng một công nghệ gọi là WebSocket, cho phép điều khiển xe một cách mượt mà và động.

ESP32 điều khiển xe robot qua web

Phần Cứng Cần Thiết

1×mô-đun phát triển ESP-WROOM-32
1×Alternatively, ESP32 Uno-form board
1×Alternatively, ESP32 S3 Uno-form board
1×Cáp USB Type-C
1×Xe RC 2WD
1×Module Driver Động Cơ L298N
1×Bộ Kit Điều Khiển IR Remote
1×Pin CR2025 (cho bộ điều khiển IR Remote)
1×Pin AA 1.5V (cho ESP32 và Xe)
1×Dây Jumper
1×breadboard
1×(Khuyến nghị) Screw Terminal Expansion Board for ESP32
1×(Khuyến nghị) Breakout Expansion Board for ESP32
1×(Khuyến nghị) Power Splitter for ESP32

Or you can buy the following kits:

1×DIYables ESP32 Starter Kit (ESP32 included)
1×DIYables Sensor Kit (30 sensors/displays)
1×DIYables Sensor Kit (18 sensors/displays)

Về Xe RC 2WD và WebSocket

Bây giờ, tại sao lại chọn WebSocket? Đây là lý do:

  • Không có WebSocket, việc thay đổi hướng của xe sẽ yêu cầu tải lại trang web mỗi lần. Không lý tưởng lắm!
  • Tuy nhiên, với WebSocket, chúng ta thiết lập một kết nối đặc biệt giữa trang web và ESP32. Điều này cho phép gửi lệnh đến ESP32 ở chế độ nền, mà không cần tải lại trang. Kết quả? Xe robot di chuyển mượt mà và theo thời gian thực. Khá tuyệt phải không?

Tóm lại, kết nối WebSocket cho phép điều khiển xe robot một cách mượt mà và thời gian thực.

Chúng tôi có các hướng dẫn riêng về Xe RC 2WD và WebSocket. Mỗi hướng dẫn chứa thông tin chi tiết và hướng dẫn từng bước về pinout phần cứng, nguyên lý hoạt động, kết nối dây với ESP32, code ESP32... Tìm hiểu thêm về chúng tại các liên kết sau:

Cách Thức Hoạt Động

Code ESP32 tạo cả web server và WebSocket Server. Đây là cách nó hoạt động:

  • Khi bạn nhập địa chỉ IP của ESP32 vào trình duyệt web, nó yêu cầu trang web (Giao diện Người dùng) từ ESP32.
  • Web server của ESP32 phản hồi bằng cách gửi nội dung trang web (HTML, CSS, JavaScript).
  • Trình duyệt web của bạn sau đó hiển thị trang web.
  • Code JavaScript trong trang web thiết lập kết nối WebSocket đến WebSocket server trên ESP32.
  • Một khi kết nối WebSocket này được thiết lập, nếu bạn nhấn/nhả các nút trên trang web, code JavaScript sẽ âm thầm gửi lệnh đến ESP32 thông qua kết nối WebSocket này ở chế độ nền.
  • WebSocket server trên ESP32, khi nhận được lệnh, điều khiển xe robot tương ứng.

Bảng dưới đây hiển thị danh sách lệnh mà trang web gửi đến ESP32 dựa trên hành động của người dùng:

Hành Động Người Dùng Nút Lệnh Hành Động Xe
NHẤN LÊN 1 DI CHUYỂN TIẾN
NHẤN XUỐNG 2 DI CHUYỂN LÙI
NHẤN TRÁI 4 RẼ TRÁI
NHẤN PHẢI 8 RẼ PHẢI
NHẤN DỪNG 0 DỪNG
NHẢ LÊN 0 DỪNG
NHẢ XUỐNG 0 DỪNG
NHẢ TRÁI 0 DỪNG
NHẢ PHẢI 0 DỪNG
NHẢ DỪNG 0 DỪNG

Sơ Đồ Kết Nối giữa Xe RC 2WD và ESP32

sơ đồ kết nối ESP32 xe rc 2wd

This image is created using Fritzing. Click to enlarge image

Nếu bạn chưa rõ cách cấp nguồn cho ESP32 và các linh kiện khác, xem: Cách Cung Cấp Nguồn Điện Cho ESP32.

ESP32 xe rc 2wd qua web

Thông thường, bạn cần hai nguồn điện:

  • Một cho động cơ thông qua module L298N.
  • Một khác cho bo mạch ESP32, module L298N (Motor driver).

Nhưng, bạn có thể đơn giản hóa việc này bằng cách chỉ sử dụng một nguồn điện cho mọi thứ – bốn pin 1.5V (tổng cộng 6V). Đây là cách:

  • Kết nối pin với module L298N như hình.
  • Tháo hai jumper từ chân ENA và ENB đến 5 volt trên module L298N.
  • Thêm một jumper được gán nhãn 5VEN (vòng tròn màu vàng trên sơ đồ).
  • Kết nối chân 12V trên module L298N với chân Vin trên ESP32 để cấp điện trực tiếp từ pin.

Vì xe RC 2WD có công tắc bật/tắt, bạn có thể tùy chọn kết nối pin qua công tắc để có thể bật/tắt nguồn điện cho xe. Nếu bạn muốn đơn giản, chỉ cần bỏ qua công tắc.

Code ESP32

Nội dung trang web (HTML, CSS, JavaScript) được lưu trữ riêng biệt trong file index.h. Vậy, chúng ta sẽ có hai file code trên Arduino IDE:

  • Một file .ino là code ESP32, tạo web server và WebSocket Server, và điều khiển xe
  • Một file .h, chứa nội dung trang web.

Các Bước Nhanh

  • Nếu đây là lần đầu bạn sử dụng ESP32, xem ESP32 - Cài Đặt Phần Mềm.
  • Thực hiện kết nối dây như hình trên.
  • Kết nối bo mạch ESP32 với PC của bạn qua cáp micro USB
  • Mở Arduino IDE trên PC của bạn.
  • Chọn đúng bo mạch ESP32 (ví dụ: ESP32 Dev Module) và cổng COM.
  • Mở Library Manager bằng cách nhấp vào biểu tượng Library Manager trên thanh điều hướng bên trái của Arduino IDE.
  • Tìm kiếm "DIYables ESP32 WebServer", sau đó tìm thư viện Web Server được tạo bởi DIYables.
  • Nhấp nút Install để cài đặt thư viện Web Server.
thư viện ESP32 web server
  • Trên Arduino IDE, tạo sketch mới, đặt tên cho nó, ví dụ: newbiely.com.ino
  • Sao chép code dưới đây và mở với Arduino IDE
/* * Mã ESP32 này được phát triển bởi newbiely.vn * Mã ESP32 này được cung cấp để sử dụng công khai, không có ràng buộc. * Để xem hướng dẫn chi tiết và sơ đồ kết nối, vui lòng truy cập: * https://newbiely.vn/tutorials/esp32/esp32-controls-car-via-web */ #include <DIYables_ESP32_WebServer.h> #include "index.h" #define CMD_STOP 0 #define CMD_FORWARD 1 #define CMD_BACKWARD 2 #define CMD_LEFT 4 #define CMD_RIGHT 8 #define ENA_PIN 14 // The ESP32 pin GPIO14 connected to the ENA pin L298N #define IN1_PIN 27 // The ESP32 pin GPIO27 connected to the IN1 pin L298N #define IN2_PIN 26 // The ESP32 pin GPIO26 connected to the IN2 pin L298N #define IN3_PIN 25 // The ESP32 pin GPIO25 connected to the IN3 pin L298N #define IN4_PIN 33 // The ESP32 pin GPIO33 connected to the IN4 pin L298N #define ENB_PIN 32 // The ESP32 pin GPIO32 connected to the ENB pin L298N // WiFi credentials const char WIFI_SSID[] = "YOUR_WIFI_SSID"; const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; // Create web server instance DIYables_ESP32_WebServer server; DIYables_ESP32_WebSocket* webSocket; // Web Page handlers void handleHome(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { server.sendResponse(client, HTML_CONTENT); // HTML_CONTENT is from index.h } // WebSocket event handlers void onWebSocketOpen(net::WebSocket& ws) { Serial.println("New WebSocket connection"); // Send welcome message const char welcome[] = "Connected to ESP32 WebSocket Server!"; } void onWebSocketMessage(net::WebSocket& ws, const net::WebSocket::DataType dataType, const char* message, uint16_t length) { String angle = String(message); int command = angle.toInt(); Serial.print("command: "); Serial.println(command); switch (command) { case CMD_STOP: Serial.println("Stop"); CAR_stop(); break; case CMD_FORWARD: Serial.println("Move Forward"); CAR_moveForward(); break; case CMD_BACKWARD: Serial.println("Move Backward"); CAR_moveBackward(); break; case CMD_LEFT: Serial.println("Turn Left"); CAR_turnLeft(); break; case CMD_RIGHT: Serial.println("Turn Right"); CAR_turnRight(); break; default: Serial.println("Unknown command"); } } void onWebSocketClose(net::WebSocket& ws, const net::WebSocket::CloseCode code, const char* reason, uint16_t length) { Serial.println("WebSocket client disconnected"); } void setup() { Serial.begin(9600); delay(1000); pinMode(ENA_PIN, OUTPUT); pinMode(IN1_PIN, OUTPUT); pinMode(IN2_PIN, OUTPUT); pinMode(IN3_PIN, OUTPUT); pinMode(IN4_PIN, OUTPUT); pinMode(ENB_PIN, OUTPUT); digitalWrite(ENA_PIN, HIGH); // set full speed digitalWrite(ENB_PIN, HIGH); // set full speed Serial.println("ESP32 Web Server and WebSocket Server"); // Configure web server routes server.addRoute("/", handleHome); // Start web server with WiFi connection server.begin(WIFI_SSID, WIFI_PASSWORD); // Enable WebSocket functionality webSocket = server.enableWebSocket(81); if (webSocket != nullptr) { // Set up WebSocket event handlers webSocket->onOpen(onWebSocketOpen); webSocket->onMessage(onWebSocketMessage); webSocket->onClose(onWebSocketClose); } else { Serial.println("Failed to start WebSocket server"); } } void loop() { // Then handle HTTP requests server.handleClient(); // Handle WebSocket server.handleWebSocket(); } void CAR_moveForward() { digitalWrite(IN1_PIN, HIGH); digitalWrite(IN2_PIN, LOW); digitalWrite(IN3_PIN, HIGH); digitalWrite(IN4_PIN, LOW); } void CAR_moveBackward() { digitalWrite(IN1_PIN, LOW); digitalWrite(IN2_PIN, HIGH); digitalWrite(IN3_PIN, LOW); digitalWrite(IN4_PIN, HIGH); } void CAR_turnLeft() { digitalWrite(IN1_PIN, HIGH); digitalWrite(IN2_PIN, LOW); digitalWrite(IN3_PIN, LOW); digitalWrite(IN4_PIN, LOW); } void CAR_turnRight() { digitalWrite(IN1_PIN, LOW); digitalWrite(IN2_PIN, LOW); digitalWrite(IN3_PIN, HIGH); digitalWrite(IN4_PIN, LOW); } void CAR_stop() { digitalWrite(IN1_PIN, LOW); digitalWrite(IN2_PIN, LOW); digitalWrite(IN3_PIN, LOW); digitalWrite(IN4_PIN, LOW); }
  • Sửa đổi thông tin WiFi (SSID và mật khẩu) trong code để khớp với thông tin đăng nhập mạng của bạn.
  • Tạo file index.h trên Arduino IDE bằng cách:
    • Nhấp vào nút ngay dưới biểu tượng serial monitor và chọn New Tab, hoặc sử dụng phím Ctrl+Shift+N.
    Arduino ide 2 thêm file
    • Đặt tên file là index.h và nhấp nút OK
    Arduino ide 2 thêm file index.h
    • Sao chép code dưới đây và dán vào index.h.
    /* * Mã ESP32 này được phát triển bởi newbiely.vn * Mã ESP32 này được cung cấp để sử dụng công khai, không có ràng buộc. * Để xem hướng dẫn chi tiết và sơ đồ kết nối, vui lòng truy cập: * https://newbiely.vn/tutorials/esp32/esp32-controls-car-via-web */ const char *HTML_CONTENT = R"=====( <!DOCTYPE html> <html> <head> <title>ESP32 Control Car via Web</title> <meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=1, user-scalable=no"> <style type="text/css"> body { text-align: center; font-size: 24px;} button { text-align: center; font-size: 24px;} #container { margin-right: auto; margin-left: auto; width: 400px; height: 400px; position: relative; margin-bottom: 10px; } div[class^='button'] { position: absolute; } .button_up, .button_down { width:214px; height:104px;} .button_left, .button_right { width:104px; height:214px;} .button_stop { width:178px; height:178px;} .button_up { background: url('https://esp32io.com/images/tutorial/up_inactive.png') no-repeat; background-size: contain; left: 200px; top: 0px; transform: translateX(-50%); } .button_down { background: url('https://esp32io.com/images/tutorial/down_inactive.png') no-repeat; background-size: contain; left:200px; bottom: 0px; transform: translateX(-50%); } .button_right { background: url('https://esp32io.com/images/tutorial/right_inactive.png') no-repeat; background-size: contain; right: 0px; top: 200px; transform: translateY(-50%); } .button_left { background: url('https://esp32io.com/images/tutorial/left_inactive.png') no-repeat; background-size: contain; left:0px; top: 200px; transform: translateY(-50%); } .button_stop { background: url('https://esp32io.com/images/tutorial/stop_inactive.png') no-repeat; background-size: contain; left:200px; top: 200px; transform: translate(-50%, -50%); } </style> <script> var CMD_STOP = 0; var CMD_FORWARD = 1; var CMD_BACKWARD = 2; var CMD_LEFT = 4; var CMD_RIGHT = 8; var img_name_lookup = { [CMD_STOP]: "stop", [CMD_FORWARD]: "up", [CMD_BACKWARD]: "down", [CMD_LEFT]: "left", [CMD_RIGHT]: "right" } var ws = null; function init() { var container = document.querySelector("#container"); container.addEventListener("touchstart", mouse_down); container.addEventListener("touchend", mouse_up); container.addEventListener("touchcancel", mouse_up); container.addEventListener("mousedown", mouse_down); container.addEventListener("mouseup", mouse_up); container.addEventListener("mouseout", mouse_up); } function ws_onmessage(e_msg) { e_msg = e_msg || window.event; // MessageEvent //alert("msg : " + e_msg.data); } function ws_onopen() { document.getElementById("ws_state").innerHTML = "OPEN"; document.getElementById("wc_conn").innerHTML = "Disconnect"; } function ws_onclose() { document.getElementById("ws_state").innerHTML = "CLOSED"; document.getElementById("wc_conn").innerHTML = "Connect"; console.log("socket was closed"); ws.onopen = null; ws.onclose = null; ws.onmessage = null; ws = null; } function wc_onclick() { if(ws == null) { ws = new WebSocket("ws://" + window.location.host + ":81"); document.getElementById("ws_state").innerHTML = "CONNECTING"; ws.onopen = ws_onopen; ws.onclose = ws_onclose; ws.onmessage = ws_onmessage; } else ws.close(); } function mouse_down(event) { if (event.target !== event.currentTarget) { var id = event.target.id; send_command(id); event.target.style.backgroundImage = "url('https://esp32io.com/images/tutorial/" + img_name_lookup[id] + "_active.png')"; } event.stopPropagation(); event.preventDefault(); } function mouse_up(event) { if (event.target !== event.currentTarget) { var id = event.target.id; send_command(CMD_STOP); event.target.style.backgroundImage = "url('https://esp32io.com/images/tutorial/" + img_name_lookup[id] + "_inactive.png')"; } event.stopPropagation(); event.preventDefault(); } function send_command(cmd) { if(ws != null) if(ws.readyState == 1) ws.send(cmd + "\r\n"); } window.onload = init; </script> </head> <body> <h2>ESP32 - RC Car via Web</h2> <div id="container"> <div id="0" class="button_stop"></div> <div id="1" class="button_up"></div> <div id="2" class="button_down"></div> <div id="8" class="button_right"></div> <div id="4" class="button_left"></div> </div> <p> WebSocket : <span id="ws_state" style="color:blue">closed</span><br> </p> <button id="wc_conn" type="button" onclick="wc_onclick();">Connect</button> <br> <br> <div class="sponsor">Sponsored by <a href="https://amazon.com/diyables">DIYables</a></div> </body> </html> )=====";
    • Bây giờ bạn có code trong hai file: newbiely.com.inoindex.h
    • Nhấp nút Upload trên Arduino IDE để tải code lên ESP32
    • Mở Serial Monitor
    • Kiểm tra kết quả trên Serial Monitor.
    COM6
    Send
    Connecting to WiFi... Connected to WiFi ESP32 Web Server's IP address IP address: 192.168.0.2
    Autoscroll Show timestamp
    Clear output
    9600 baud  
    Newline  
    • Ghi lại địa chỉ IP được hiển thị và nhập địa chỉ này vào thanh địa chỉ của trình duyệt web trên smartphone hoặc PC của bạn.
    • Bạn sẽ thấy trang web như dưới đây:
    ESP32 điều khiển xe qua trình duyệt web
    • Code JavaScript của trang web tự động tạo kết nối WebSocket đến ESP32.
    • Bây giờ bạn có thể điều khiển xe rẽ trái/phải, di chuyển tiến/lùi qua giao diện web.

    Để tiết kiệm bộ nhớ của ESP32, hình ảnh của các nút điều khiển KHÔNG được lưu trữ trên ESP32. Thay vào đó, chúng được lưu trữ trên internet, vì vậy điện thoại hoặc PC của bạn cần có kết nối internet để tải hình ảnh cho trang điều khiển web.

    ※ Lưu ý:

    • Nếu bạn sửa đổi nội dung HTML trong file index.h và không chạm vào gì trong file newbiely.com.ino, khi bạn compile và tải code lên ESP32, Arduino IDE sẽ không cập nhật nội dung HTML.
    • Để làm cho Arduino IDE cập nhật nội dung HTML trong trường hợp này, hãy thực hiện một thay đổi trong file newbiely.com.ino (ví dụ: thêm dòng trống, thêm comment....)

    Giải Thích Code Từng Dòng

    Code ESP32 ở trên chứa giải thích từng dòng. Vui lòng đọc các comment trong code!