Robot dò line với ESP32: dự án STEM cuối khóa cho học sinh
Làm robot dò line với ESP32, thanh 5 cảm biến TCRT5000 và L298N: bản 1 ba quy tắc nếu thì, bản 2 lái theo tỉ lệ độ lệch (điều khiển P), cách làm sân thi đấu cuối khóa.
Robot dò line là dự án quen thuộc nhất của các khóa học STEM và các cuộc thi robot cho học sinh: một chiếc xe nhỏ tự chạy theo vạch đen trên nền trắng. Dự án gom lại gần như mọi thứ đã học: cảm biến, câu lệnh điều kiện, vòng lặp, điều khiển động cơ, và cả một chút toán. Bài viết hướng dẫn làm robot dò line bằng ESP32, thanh 5 cảm biến hồng ngoại và driver L298N, với hai bản chương trình: bản 1 dùng ba quy tắc "nếu… thì…", bản 2 lái theo tỉ lệ độ lệch cho xe chạy mượt hơn. Cuối bài là gợi ý làm sân thi đấu cho buổi tổng kết khóa học.
1. Chuẩn bị
- Một mạch ESP32 DevKit V1 và dây USB nạp code.
- Một thanh 5 cảm biến dò line TCRT5000.
- Dây cắm test board và khay pin 18650 loại 2S cùng 2 viên pin 18650.
- Khung xe hai bánh, 2 động cơ giảm tốc kèm bánh xe, một bánh tự do (bánh mắt trâu), driver L298N, một công tắc nguồn.
Phần khung xe, động cơ, L298N và pin giống hệt các bài xe robot ESP32 đã đăng, nên có thể dùng lại chiếc xe đã làm và chỉ thêm thanh dò line.
2. Nối dây

| Linh kiện | Chân | Nối tới ESP32 |
|---|---|---|
| Thanh dò line | S1, S2, S3, S4, S5 | D4, RX2 (16), TX2 (17), D18, D19 |
| Thanh dò line | VCC, GND | 3V3, GND |
| L298N (động cơ trái) | ENA, IN1, IN2 | D27, D26, D25 |
| L298N (động cơ phải) | ENB, IN3, IN4 | D13, D33, D32 |
| L298N | 5V, GND | VIN, GND |
Lắp thanh cảm biến ở đầu xe, cách mặt sân khoảng 1–2 cm, S1 bên trái và S5 bên phải (nhìn từ sau xe tới). Mọi dây GND (ESP32, L298N, pin, thanh cảm biến) phải nối chung với nhau.
3. Cảm biến dò line nhìn vạch thế nào?
Mỗi mắt TCRT5000 gồm một đèn phát hồng ngoại và một bộ thu đặt cạnh nhau. Nền trắng phản xạ nhiều ánh sáng hồng ngoại về bộ thu, vạch đen hấp thụ gần hết. Mạch trên thanh so sánh lượng ánh sáng nhận được với một ngưỡng và cho ra mức 0 hoặc 1 ở từng chân S1–S5.
Trước khi lắp lên xe, nên chạy chương trình 1 của bài thanh dò line TCRT5000 (link ở cuối bài) để xem kênh nằm trên vạch đen cho mức nào. Nếu là mức 1, đổi MUC_VACH thành HIGH trong cả hai chương trình dưới đây.
4. Bản 1: ba quy tắc "nếu… thì…"

// PYWORLD.VN - Robot dò line cho học sinh, bản 1: ba quy tắc "nếu… thì…"
// Thanh dò line 5 kênh: S1..S5 -> D4, RX2 (16), TX2 (17), D18, D19; L298N như loạt xe robot
// Động cơ trái: ENA 27, IN1 26, IN2 25; động cơ phải: ENB 13, IN3 33, IN4 32
#define ENA 27
#define IN1 26
#define IN2 25
#define ENB 13
#define IN3 33
#define IN4 32
const int CHAN_LINE[5] = {4, 16, 17, 18, 19}; // S1 (trái) ... S5 (phải)
const int MUC_VACH = LOW; // mức của một kênh khi nằm trên vạch đen; đổi thành HIGH nếu module ngược lại
const int TOC_DO = 55; // % khi đi thẳng
const int TOC_DO_RE = 60; // % bánh ngoài khi rẽ
void banh(int chanEN, int chanA, int chanB, int v) {
v = constrain(v, -100, 100);
digitalWrite(chanA, v > 0 ? HIGH : LOW);
digitalWrite(chanB, v < 0 ? HIGH : LOW);
ledcWrite(chanEN, (uint32_t)abs(v) * 1023 / 100);
}
void chay(int trai, int phai) {
banh(ENA, IN1, IN2, trai);
banh(ENB, IN3, IN4, phai);
}
bool thayVach(int kenh) {
return digitalRead(CHAN_LINE[kenh]) == MUC_VACH;
}
void setup() {
Serial.begin(115200);
for (int i = 0; i < 5; i++) pinMode(CHAN_LINE[i], INPUT);
int chan[] = {IN1, IN2, IN3, IN4};
for (int p : chan) pinMode(p, OUTPUT);
ledcAttach(ENA, 1000, 10);
ledcAttach(ENB, 1000, 10);
chay(0, 0);
delay(2000); // đặt xe lên vạch
}
void loop() {
bool trai = thayVach(0) || thayVach(1);
bool giua = thayVach(2);
bool phai = thayVach(3) || thayVach(4);
if (giua && !trai && !phai) {
chay(TOC_DO, TOC_DO); // vạch ở giữa: đi thẳng
} else if (trai) {
chay(0, TOC_DO_RE); // vạch lệch sang trái: rẽ trái
} else if (phai) {
chay(TOC_DO_RE, 0); // vạch lệch sang phải: rẽ phải
} else {
chay(0, 0); // không thấy vạch: dừng lại
}
delay(5);
}
- Chương trình gộp năm kênh thành ba nhóm: trái (S1, S2), giữa (S3), phải (S4, S5).
- Vạch ở giữa: hai bánh cùng chạy, xe đi thẳng. Vạch lệch trái: bánh trái dừng, bánh phải chạy, xe quẹo trái về phía vạch. Lệch phải thì ngược lại.
- Không kênh nào thấy vạch: xe dừng lại cho an toàn.
Bản này rất dễ hiểu với học sinh, nhưng xe thường lắc qua lắc lại vì chỉ có ba cách lái: thẳng, quẹo hẳn trái, quẹo hẳn phải.
5. Bản 2: lái theo tỉ lệ độ lệch

// PYWORLD.VN - Robot dò line cho học sinh, bản 2: lái theo tỉ lệ độ lệch (điều khiển P)
// Vị trí vạch tính từ 5 kênh (-2 bên trái ... +2 bên phải); lệch càng nhiều, lái càng mạnh
// Mất vạch thì quay về phía vạch vừa thấy lần cuối; chân giống bản 1
#define ENA 27
#define IN1 26
#define IN2 25
#define ENB 13
#define IN3 33
#define IN4 32
const int CHAN_LINE[5] = {4, 16, 17, 18, 19}; // S1 (trái) ... S5 (phải)
const int TRONG_SO[5] = {-2, -1, 0, 1, 2};
const int MUC_VACH = LOW; // đổi thành HIGH nếu module cho mức 1 khi gặp vạch
const int TOC_DO = 50; // % tốc độ nền
const float KP = 22.0; // hệ số lái: vạch lệch 1 thì chênh tốc độ 2 bánh là 2 x 22 %
const int TOC_DO_TIM = 45; // % khi quay tại chỗ để tìm lại vạch
float viTriCuoi = 0;
void banh(int chanEN, int chanA, int chanB, int v) {
v = constrain(v, -100, 100);
digitalWrite(chanA, v > 0 ? HIGH : LOW);
digitalWrite(chanB, v < 0 ? HIGH : LOW);
ledcWrite(chanEN, (uint32_t)abs(v) * 1023 / 100);
}
void chay(int trai, int phai) {
banh(ENA, IN1, IN2, trai);
banh(ENB, IN3, IN4, phai);
}
void setup() {
Serial.begin(115200);
for (int i = 0; i < 5; i++) pinMode(CHAN_LINE[i], INPUT);
int chan[] = {IN1, IN2, IN3, IN4};
for (int p : chan) pinMode(p, OUTPUT);
ledcAttach(ENA, 1000, 10);
ledcAttach(ENB, 1000, 10);
chay(0, 0);
delay(2000);
}
void loop() {
int tong = 0, dem = 0;
for (int i = 0; i < 5; i++) {
if (digitalRead(CHAN_LINE[i]) == MUC_VACH) {
tong += TRONG_SO[i];
dem++;
}
}
if (dem == 0) { // mất vạch: quay tại chỗ về phía vạch vừa thấy
if (viTriCuoi < 0) chay(-TOC_DO_TIM, TOC_DO_TIM);
else chay(TOC_DO_TIM, -TOC_DO_TIM);
} else {
float viTri = (float)tong / dem; // -2.0 ... +2.0
viTriCuoi = viTri;
int lai = (int)(KP * viTri); // vạch lệch phải (dương): bánh trái nhanh hơn
chay(TOC_DO + lai, TOC_DO - lai);
}
delay(5);
}
- Vị trí vạch: mỗi kênh có một trọng số từ −2 (S1) tới +2 (S5). Vị trí là trung bình trọng số của các kênh đang thấy vạch, ví dụ S2 và S3 cùng thấy vạch thì vị trí là (−1 + 0) : 2 = −0,5. Đây là phép trung bình cộng học sinh đã biết từ lớp 4.
- Lái tỉ lệ: vạch lệch càng xa, chênh lệch tốc độ hai bánh càng lớn: bánh trái = 50 + KP × vị trí, bánh phải = 50 − KP × vị trí. Lệch ít thì quẹo nhẹ, lệch nhiều thì quẹo gắt, nên xe đi mượt hơn hẳn bản 1. Cách lái này gọi là điều khiển P (tỉ lệ), phần đầu tiên của bộ điều khiển PID.
- Mất vạch: xe nhớ vị trí vạch lần cuối (
viTriCuoi) và quay tại chỗ về phía đó để tìm lại, thay vì dừng hẳn. - Chỉnh KP: xe không kịp vào cua thì tăng
KP; xe lắc mạnh trên đường thẳng thì giảmKPhoặc giảmTOC_DO.
6. Sân thi đấu cho buổi tổng kết

- Sân: nền giấy trắng khổ lớn hoặc tấm xốp trắng ghép lại, vạch bằng băng dính điện màu đen rộng khoảng 2 cm.
- Độ khó tăng dần: vòng đầu chỉ có khúc cua rộng (bán kính từ 20 cm); vòng sau thêm khúc S, góc vuông, ngã tư.
- Luật gợi ý: mỗi đội chạy 2 lượt, tính thời gian một vòng tốt nhất; trượt khỏi vạch quá 3 giây thì lượt đó không tính. Có thêm giải "xe chạy mượt nhất" và "thuyết trình hay nhất" để mọi đội đều có động lực.
7. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Xe chạy lùi thay vì tiến | Dây động cơ nối ngược | Đổi hai dây của động cơ đó ở cọc OUT của L298N |
| Vạch lệch trái mà xe quẹo phải, càng chạy càng xa vạch | Thanh cảm biến lắp ngược (S1 bên phải) hoặc hai động cơ bị đổi chỗ | Lắp S1 bên trái; hoặc đổi chỗ dây hai động cơ ở cọc OUT1/OUT2 và OUT3/OUT4 |
| Xe không bao giờ thấy vạch, hoặc luôn thấy vạch | Sai mức MUC_VACH; cảm biến quá cao hoặc quá thấp | Kiểm tra bằng chương trình đọc 5 kênh; chỉnh độ cao cảm biến 1–2 cm |
| ESP32 tự khởi động lại khi động cơ chạy | Pin yếu, sụt áp khi động cơ kéo dòng | Sạc đầy pin; giảm TOC_DO; kiểm tra dây GND chung |
| Xe lắc mạnh trên đường thẳng | KP quá lớn hoặc chạy quá nhanh | Giảm KP từng bước 2–3, giảm TOC_DO |
8. Gợi ý cho phụ huynh và giáo viên
- Làm theo đội: mỗi đội 3–4 học sinh với các vai: lắp ráp, nối dây, lập trình, thử nghiệm và ghi chép. Đổi vai giữa các buổi.
- Ghi chép như kỹ sư: mỗi lần đổi KP hay tốc độ, ghi lại giá trị và thời gian chạy một vòng. Cuối khóa, đội trình bày biểu đồ "KP và thời gian".
- Kiến thức gom lại: cảm biến hồng ngoại (Khoa học), trung bình cộng và tỉ lệ (Toán), điều kiện và vòng lặp (Tin học), động cơ và bánh xe (Công nghệ).
- Thời lượng: 3–4 buổi 60 phút: lắp xe và nối dây; bản 1; bản 2 và chỉnh KP; thi đấu và thuyết trình.
- An toàn: pin 18650 nằm trong khay, có công tắc nguồn; tắt công tắc khi nạp chương trình và khi không chạy xe.
9. Xem thêm
- Thanh cảm biến dò line 5 kênh TCRT5000 với ESP32 (đọc 5 kênh, tính vị trí vạch)
- ESP32 điều khiển động cơ DC với driver L298N
- Xe robot ESP32 điều khiển qua Wi-Fi (khung xe, pin, nối L298N)
- Xe tránh vật cản với HC-SR04
- Trong cùng loạt còn có bài robot thoát mê cung trong Scratch, dùng cảm biến màu để luyện câu lệnh điều kiện trước khi làm robot thật.
Xem thêm