-
-
-
Tổng tiền thanh toán:
-
Xe ô tô điều khiển bằng điện thoại qua wifi với ESP32
24/02/2026
Xe ô tô điều khiển bằng điện thoại qua wifi với ESP32
✔ Servo trái phải nằm bên phải
✔ Tiến lùi bên trái
✔ Xi nhan dưới servo
✔ Còi, đèn pha, cốt bên cạnh tiến lùi
✔ Tốc độ 1-6 ở giữa (3 cột)
✔ Hiển thị:
-
GPC-CAR
-
SPEED
-
SERVO ANGLE
✔ Servo:
-
Bấm giữ → quay liên tục
-
Thả → giữ nguyên góc
-
KHÔNG tự về 0
-
Bật nguồn → mặc định 0°
-
Đồng bộ góc servo giữa các thiết bị cùng điều khiển
-
Điều khiển đa điểm : vừa tiến, vừa quay góc
✔ Giới hạn:
-
trái: −80°
-
phải: +80°
-
bước: 0.5
I. SƠ ĐỒ CHÂN
| Chức năng | ESP32 |
|---|---|
| Servo | GPIO13 |
| IN1 | GPIO27 |
| IN2 | GPIO26 |
| IN3 | GPIO25 |
| IN4 | GPIO33 |
| ENA | GPIO14 |
| ENB | GPIO12 |
| Còi | GPIO32 |
| Pha | GPIO4 |
| Cốt | GPIO2 |
| Xi nhan trái | GPIO15 |
| Xi nhan phải | GPIO16 |
-
Truy cập
wifi "GPC-CAR"; password = "12345678";
-
Vào trình duyệt web gõ: 192.168.4.1 để mở giao diện điều khiển
II. CODE ESP32 FULL
Bạn dùng thư viện này:
1. ESP32Servo by Kevin Harrington, John K. Bennett
2. ESPAsyncWebServer by me-no-dev: https://github.com/me-no-dev/ESPAsyncWebServer -vào Sketch → Include Library → Manage Libraries
3. AsyncTCP by me-no-dev: https://github.com/me-no-dev/AsyncTCP
4. Mapping phím:
| Phím | Chức năng |
|---|---|
| ↑ ↓ ← → | Di chuyển |
| Space | Dừng |
| W | Còi |
| A | Xi nhan trái |
| S | Xi nhan phải |
| R | Đèn pha |
| F | Đèn cốt |
| 1 | Tốc độ S1 |
| 2 | Tốc độ S2 |
| 3 | Tốc độ S3 |
| 4 | Tốc độ S4 |
| 5 | Tốc độ S5 |
| 6 | Tốc độ S6 |
Code phần động cơ:
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ESP32Servo.h>
// WIFI
const char* ssid="GPC-CAR";
const char* password="12345678";
AsyncWebServer server(80);
// MOTOR
#define ENA 14
#define IN1 27
#define IN2 26
#define ENB 12
#define IN3 25
#define IN4 33
// OUTPUT KHÁC
#define HORN 4
#define LIGHT_LOW 16
#define LIGHT_HIGH 17
#define SIG_LEFT 5
#define SIG_RIGHT 19
// SERVO
#define SERVO_PIN 18
Servo steering;
float servoAngle=0;
bool servoLeftFlag=false;
bool servoRightFlag=false;
float stepSize=0.5;
// SPEED
int speedLevel=0;
int pwmValue=0;
// PWM
#define PWM_FREQ 1000
#define PWM_RES 8
#define PWM_CH_A 0
#define PWM_CH_B 1
// HTML
#include "index.h" // file html bạn đã có
// MOTOR CONTROL
void stopMotor(){
ledcWrite(PWM_CH_A,0);
ledcWrite(PWM_CH_B,0);
}
void forward(){
digitalWrite(IN1,HIGH);
digitalWrite(IN2,LOW);
digitalWrite(IN3,HIGH);
digitalWrite(IN4,LOW);
ledcWrite(PWM_CH_A,pwmValue);
ledcWrite(PWM_CH_B,pwmValue);
}
void back(){
digitalWrite(IN1,LOW);
digitalWrite(IN2,HIGH);
digitalWrite(IN3,LOW);
digitalWrite(IN4,HIGH);
ledcWrite(PWM_CH_A,pwmValue);
ledcWrite(PWM_CH_B,pwmValue);
}
// SERVO UPDATE
void updateServo(){
if(servoLeftFlag){
servoAngle -= stepSize;
if(servoAngle<-80) servoAngle=-80;
}
if(servoRightFlag){
servoAngle += stepSize;
if(servoAngle>80) servoAngle=80;
}
steering.write(servoAngle+90);
}
// SETUP
void setup(){
Serial.begin(115200);
// PIN MODE
pinMode(IN1,OUTPUT);
pinMode(IN2,OUTPUT);
pinMode(IN3,OUTPUT);
pinMode(IN4,OUTPUT);
pinMode(HORN,OUTPUT);
pinMode(LIGHT_LOW,OUTPUT);
pinMode(LIGHT_HIGH,OUTPUT);
pinMode(SIG_LEFT,OUTPUT);
pinMode(SIG_RIGHT,OUTPUT);
// PWM
ledcSetup(PWM_CH_A,PWM_FREQ,PWM_RES);
ledcAttachPin(ENA,PWM_CH_A);
ledcSetup(PWM_CH_B,PWM_FREQ,PWM_RES);
ledcAttachPin(ENB,PWM_CH_B);
// SERVO
steering.attach(SERVO_PIN);
steering.write(90);
// WIFI
WiFi.softAP(ssid,password);
Serial.println(WiFi.softAPIP());
// SERVER
server.on("/",HTTP_GET,[](AsyncWebServerRequest *request){
request->send(200,"text/html",index_html);
});
// MOTOR
server.on("/forward",HTTP_GET,[](AsyncWebServerRequest *request){
forward();
request->send(200,"text/plain","OK");
});
server.on("/back",HTTP_GET,[](AsyncWebServerRequest *request){
back();
request->send(200,"text/plain","OK");
});
server.on("/stop",HTTP_GET,[](AsyncWebServerRequest *request){
stopMotor();
request->send(200,"text/plain","OK");
});
// SPEED
server.on("/speed",HTTP_GET,[](AsyncWebServerRequest *request){
if(request->hasParam("val")){
speedLevel=request->getParam("val")->value().toInt();
pwmValue=map(speedLevel,0,6,0,255);
}
request->send(200,"text/plain","OK");
});
// SERVO
server.on("/servoLeft",HTTP_GET,[](AsyncWebServerRequest *request){
servoLeftFlag=true;
servoRightFlag=false;
request->send(200,"text/plain","OK");
});
server.on("/servoRight",HTTP_GET,[](AsyncWebServerRequest *request){
servoRightFlag=true;
servoLeftFlag=false;
request->send(200,"text/plain","OK");
});
server.on("/servoStop",HTTP_GET,[](AsyncWebServerRequest *request){
servoLeftFlag=false;
servoRightFlag=false;
request->send(200,"text/plain","OK");
});
// READ ANGLE
server.on("/angle",HTTP_GET,[](AsyncWebServerRequest *request){
request->send(200,"text/plain",String(servoAngle));
});
// HORN
server.on("/hornOn",HTTP_GET,[](AsyncWebServerRequest *request){
digitalWrite(HORN,HIGH);
request->send(200,"text/plain","OK");
});
server.on("/hornOff",HTTP_GET,[](AsyncWebServerRequest *request){
digitalWrite(HORN,LOW);
request->send(200,"text/plain","OK");
});
// LIGHT
server.on("/lightLow",HTTP_GET,[](AsyncWebServerRequest *request){
digitalWrite(LIGHT_LOW,!digitalRead(LIGHT_LOW));
request->send(200,"text/plain","OK");
});
server.on("/lightHigh",HTTP_GET,[](AsyncWebServerRequest *request){
digitalWrite(LIGHT_HIGH,!digitalRead(LIGHT_HIGH));
request->send(200,"text/plain","OK");
});
// SIGNAL
server.on("/leftSignal",HTTP_GET,[](AsyncWebServerRequest *request){
digitalWrite(SIG_LEFT,!digitalRead(SIG_LEFT));
request->send(200,"text/plain","OK");
});
server.on("/rightSignal",HTTP_GET,[](AsyncWebServerRequest *request){
digitalWrite(SIG_RIGHT,!digitalRead(SIG_RIGHT));
request->send(200,"text/plain","OK");
});
server.begin();
}
// LOOP
void loop(){
updateServo();
delay(20);
}
*********************************************
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<style>
body{
background:#111;
color:white;
font-family:Arial;
margin:0;
overflow:hidden;
touch-action:none;
}
.main{
display:flex;
justify-content:space-between;
align-items:center;
height:100vh;
padding:10px;
}
.leftBlock{
display:flex;
flex-direction:column;
gap:40px;
}
.rightBlock{
display:flex;
flex-direction:column;
gap:20px;
align-items:center;
}
.servoRow{
display:flex;
gap:40px;
}
.centerBlock{
text-align:center;
}
.title{
font-size:40px;
}
.info{
font-size:22px;
margin:5px;
}
.speedGrid{
display:grid;
grid-template-columns:repeat(3,60px);
gap:10px;
justify-content:center;
margin-top:10px;
}
.speedBtn{
width:60px;
height:60px;
font-size:22px;
border:none;
border-radius:10px;
background:#333;
color:white;
}
.speedBtn:active{
background:#00ffcc;
box-shadow:0 0 20px #00ffcc;
}
.arrow{
width:90px;
height:90px;
background:#333;
border:none;
border-radius:20px;
}
.arrow:active{
background:#00ffcc;
box-shadow:0 0 20px #00ffcc;
}
.up{
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}
.down{
clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.left{
clip-path: polygon(0 50%, 100% 0, 100% 100%);
}
.right{
clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.ctrlBtn{
width:80px;
height:50px;
border:none;
border-radius:10px;
background:#444;
color:white;
font-size:18px;
}
.ctrlBtn.active{
background:#00ffcc;
}
</style>
</head>
<body>
<div class="main">
<!-- TIẾN LÙI -->
<div class="leftBlock">
<button class="arrow up"
onpointerdown="motorForward()"
onpointerup="motorStop()"
onpointerleave="motorStop()"
></button>
<button class="arrow down"
onpointerdown="motorBack()"
onpointerup="motorStop()"
onpointerleave="motorStop()"
></button>
<button class="ctrlBtn"
onpointerdown="hornOn()"
onpointerup="hornOff()"
onpointerleave="hornOff()"
>HORN</button>
</div>
<!-- GIỮA -->
<div class="centerBlock">
<div class="title">
GPC-CAR
</div>
<div class="info">
Speed:
<span id="speed">
0
</span>
</div>
<div class="info">
Servo:
<span id="angle">
0
</span>
</div>
<div class="speedGrid">
<button class="speedBtn" onclick="setSpeed(1)">1</button>
<button class="speedBtn" onclick="setSpeed(2)">2</button>
<button class="speedBtn" onclick="setSpeed(3)">3</button>
<button class="speedBtn" onclick="setSpeed(4)">4</button>
<button class="speedBtn" onclick="setSpeed(5)">5</button>
<button class="speedBtn" onclick="setSpeed(6)">6</button>
</div>
<br>
<button class="ctrlBtn"
onclick="toggleLight(this)"
>
LIGHT
</button>
<button class="ctrlBtn"
onclick="toggleLeft(this)"
>
LEFT
</button>
<button class="ctrlBtn"
onclick="toggleRight(this)"
>
RIGHT
</button>
</div>
<!-- LÁI -->
<div class="rightBlock">
<div class="servoRow">
<button class="arrow left"
onpointerdown="servoLeft()"
onpointerup="servoStop()"
onpointerleave="servoStop()"
></button>
<button class="arrow right"
onpointerdown="servoRight()"
onpointerup="servoStop()"
onpointerleave="servoStop()"
></button>
</div>
</div>
</div>
<script>
// MOTOR
function motorForward(){
fetch("/forward");
}
function motorBack(){
fetch("/back");
}
function motorStop(){
fetch("/stop");
}
// SERVO
function servoLeft(){
fetch("/servoLeft");
}
function servoRight(){
fetch("/servoRight");
}
function servoStop(){
fetch("/servoStop");
}
// SPEED
function setSpeed(val){
fetch("/speed?val="+val);
document.getElementById("speed").innerHTML=val;
}
// HORN
function hornOn(){
fetch("/hornOn");
}
function hornOff(){
fetch("/hornOff");
}
// LIGHT
var light=false;
function toggleLight(btn){
light=!light;
fetch("/light?state="+light);
btn.classList.toggle("active");
}
// LEFT SIGNAL
var left=false;
function toggleLeft(btn){
left=!left;
fetch("/left?state="+left);
btn.classList.toggle("active");
}
// RIGHT SIGNAL
var right=false;
function toggleRight(btn){
right=!right;
fetch("/right?state="+right);
btn.classList.toggle("active");
}
// READ ANGLE
setInterval(function(){
fetch("/angle")
.then(res=>res.text())
.then(val=>{
document.getElementById("angle").innerHTML=val;
});
},200);
// ==========================
// KEYBOARD CONTROL
// ==========================
let keyState = {};
document.addEventListener("keydown", function(e){
if(keyState[e.code]) return;
keyState[e.code] = true;
// ===== MOTOR =====
if(e.code=="ArrowUp") motorForward();
if(e.code=="ArrowDown") motorBack();
if(e.code=="ArrowLeft") servoLeft();
if(e.code=="ArrowRight") servoRight();
if(e.code=="Space") motorStop();
// ===== HORN =====
if(e.code=="KeyW") hornOn();
// ===== SIGNAL =====
if(e.code=="KeyA"){
left=!left;
fetch("/left?state="+left);
}
if(e.code=="KeyS"){
right=!right;
fetch("/right?state="+right);
}
// ===== LIGHT =====
if(e.code=="KeyR"){
light=!light;
fetch("/light?state="+light);
}
if(e.code=="KeyF"){
fetch("/low"); // nếu bạn có đèn cốt riêng
}
// ===== SPEED =====
if(e.code=="Digit1") setSpeed(1);
if(e.code=="Digit2") setSpeed(2);
if(e.code=="Digit3") setSpeed(3);
if(e.code=="Digit4") setSpeed(4);
if(e.code=="Digit5") setSpeed(5);
if(e.code=="Digit6") setSpeed(6);
});
// KEY UP
document.addEventListener("keyup", function(e){
keyState[e.code] = false;
// MOTOR STOP
if(e.code=="ArrowUp" || e.code=="ArrowDown")
motorStop();
// SERVO STOP
if(e.code=="ArrowLeft" || e.code=="ArrowRight")
servoStop();
// HORN STOP
if(e.code=="KeyW")
hornOff();
});
</script>
</body>
</html>
)rawliteral";
)rawliteral";
III. TÍNH NĂNG PRO
Servo:
✔ giữ quay liên tục
✔ mượt
✔ chính xác 0.5°
Motor:
✔ giữ chạy
✔ thả dừng
Web:
✔ hiển thị servo realtime
✔ tốc độ realtime
IV. KẾT QUẢ
Xe hoạt động như xe thật:
-
vô lăng mượt
-
giữ góc lái
-
chỉnh tốc độ
-
còi
-
xi nhan
-
đèn
V. Nâng cấp PRO MAX tiếp theo (nếu bạn muốn)
Tôi có thể thêm:
✔ vô lăng tròn thật
✔ joystick
✔ animation xe
✔ giao diện giống Tesla