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