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.
| 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) | | |
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:
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 |

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.
Thông thường, bạn cần hai nguồn điện:
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.
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.
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.
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
#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
#define IN1_PIN 27
#define IN2_PIN 26
#define IN3_PIN 25
#define IN4_PIN 33
#define ENB_PIN 32
const char WIFI_SSID[] = "YOUR_WIFI_SSID";
const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";
DIYables_ESP32_WebServer server;
DIYables_ESP32_WebSocket* webSocket;
void handleHome(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) {
server.sendResponse(client, HTML_CONTENT);
}
void onWebSocketOpen(net::WebSocket& ws) {
Serial.println("New WebSocket connection");
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);
digitalWrite(ENB_PIN, HIGH);
Serial.println("ESP32 Web Server and WebSocket Server");
server.addRoute("/", handleHome);
server.begin(WIFI_SSID, WIFI_PASSWORD);
webSocket = server.enableWebSocket(81);
if (webSocket != nullptr) {
webSocket->onOpen(onWebSocketOpen);
webSocket->onMessage(onWebSocketMessage);
webSocket->onClose(onWebSocketClose);
} else {
Serial.println("Failed to start WebSocket server");
}
}
void loop() {
server.handleClient();
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:


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;
}
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:
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.ino và index.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.
Connecting to WiFi...
Connected to WiFi
ESP32 Web Server's IP address IP address: 192.168.0.2
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....)
Code ESP32 ở trên chứa giải thích từng dòng. Vui lòng đọc các comment trong code!