ESP32 điều khiển động cơ không chổi than qua ESC: xung 1000–2000 µs, hiệu chuẩn dải ga, thử an toàn
Dùng ESP32 phát xung ga 1000–2000 µs bằng LEDC để điều khiển ESC và động cơ không chổi than: nối dây với pin LiPo, hiệu chuẩn dải ga, tăng ga từ từ, khóa động cơ và an toàn khi thử.
Động cơ không chổi than của flycam không nối thẳng vào vi điều khiển được: nó cần dòng vài chục ampe, đổi chiều theo ba pha đúng thời điểm. Việc đó do ESC (bộ điều tốc điện tử) đảm nhận. Mạch điều khiển chỉ gửi cho ESC một tín hiệu "ga" cho biết cần quay nhanh hay chậm.
Bài viết hướng dẫn dùng ESP32 phát tín hiệu ga chuẩn 1000–2000 µs bằng bộ LEDC, nối dây an toàn với ESC và pin LiPo, hiệu chuẩn dải ga, tăng ga từ từ và khóa động cơ bằng lệnh trên Serial Monitor. Chương trình này dùng được để thử động cơ trên bàn, đo lực đẩy, và là phần xuất lệnh động cơ khi tự làm mạch điều khiển bay.
1. ESC nhận lệnh thế nào

Một ESC thường có ba nhóm dây:
- Hai dây nguồn (đỏ, đen) nối với pin LiPo.
- Ba dây pha nối với ba dây của động cơ. Thứ tự không quan trọng; đảo hai dây bất kỳ thì động cơ đổi chiều quay.
- Cáp tín hiệu: dây trắng hoặc vàng là tín hiệu, dây đen hoặc nâu là GND. Dây đỏ ở giữa (nếu có) là ngõ ra 5 V của mạch ổn áp BEC trong ESC, dùng để cấp điện cho bộ thu trên máy bay mô hình.
Tín hiệu ga kiểu cũ nhất, ESC nào cũng nhận, là xung PWM như servo: mỗi chu kỳ có một xung mức cao, rộng 1000 µs là ga thấp nhất, 2000 µs là ga cao nhất. Mạch điều khiển bay hiện nay chủ yếu dùng giao thức số DShot (đã giới thiệu ở bài động cơ, ESC và cánh quạt cùng loạt); bài này dùng xung PWM vì đơn giản và hợp với mọi ESC.
2. Linh kiện cần chuẩn bị
- ESP32 DevKit V1 30 chân.
- Một ESC 20–30 A nhận xung PWM và động cơ không chổi than cỡ phù hợp (ví dụ loại 2212 cho khung 450 mm).
- Pin LiPo đúng số cell ESC và động cơ cho phép (ví dụ 3S), đầu nối pin cùng loại với ESC.
- Giá kẹp động cơ chắc chắn, hoặc kẹp động cơ vào khung flycam đã tháo cánh.
- Dây cắm test board để nối cáp tín hiệu vào ESP32.
3. Sơ đồ nối dây

- Dây tín hiệu của ESC nối GPIO25, dây GND nối GND của ESP32. Thiếu dây GND chung thì ESC không đọc được xung.
- Không nối dây đỏ 5 V của ESC khi ESP32 đang cắm USB: hai nguồn 5 V nối với nhau có thể làm hỏng cổng USB hoặc mạch ổn áp. Bọc đầu dây đỏ lại, hoặc rút chân đỏ khỏi đầu cắm.
- Khi ESP32 chạy độc lập không cắm USB, có thể lấy 5 V của BEC cấp vào chân VIN. ESC loại không có BEC (thường ghi OPTO) thì cáp tín hiệu chỉ có hai sợi.
- Mức cao 3,3 V của ESP32 đủ để phần lớn ESC nhận. Nếu ESC không phản ứng, thêm mạch chuyển mức 3,3 V sang 5 V cho dây tín hiệu.
4. Xung PWM và bộ LEDC

Bộ LEDC của ESP32 tạo xung PWM bằng phần cứng, không tốn thời gian CPU. Chương trình đặt tần số 50 Hz (chu kỳ 20 000 µs) và độ phân giải 16 bit, nên giá trị duty đi từ 0 tới 65 535:
duty = độ rộng xung (µs) × 65535 / 20000
- Mỗi bước duty ứng với khoảng 0,3 µs, tức dải ga 1000 µs được chia thành khoảng 3 270 mức, mịn hơn nhiều so với khả năng phân biệt của ESC.
- Nhiều ESC đời mới nhận xung lặp nhanh hơn (tới khoảng 490 Hz), giúp động cơ phản ứng sớm hơn. Chỉ cần sửa
TAN_SO_PWM, chương trình tự tính lại chu kỳ và duty. Chu kỳ phải dài hơn 2000 µs, nên không đặt quá 490 Hz.
5. Hiệu chuẩn dải ga

c và x.Mỗi mạch phát xung có sai lệch nhỏ, mỗi ESC xuất xưởng với dải ga hơi khác nhau. Hiệu chuẩn để ESC ghi nhớ đúng mức 1000 µs và 2000 µs của ESP32; khi dùng nhiều ESC (bốn động cơ của flycam), hiệu chuẩn cả bốn để chúng bắt đầu quay ở cùng một mức ga.
- Tháo cánh quạt. Rút pin ESC, cắm USB cho ESP32.
- Gõ
c: ESP32 xuất 2000 µs. - Cắm pin cho ESC. ESC phát chuỗi bíp báo đã nhận mức cao.
- Gõ
x: ESP32 xuất 1000 µs. ESC bíp xác nhận đã lưu dải ga. - Rút pin, cắm lại để dùng bình thường.
Kiểu tiếng bíp và thời gian chờ khác nhau giữa các loại ESC, nên đọc tài liệu của ESC đang dùng. Chương trình chỉ cho vào chế độ hiệu chuẩn khi động cơ đang khóa.
6. Chương trình
Mở Serial Monitor ở 115200 baud, kiểu xuống dòng "New Line", rồi gõ lệnh:
| Lệnh | Tác dụng |
|---|---|
a | Mở khóa (chỉ được khi đang khóa và ga ở mức thấp nhất) |
t1200 | Đặt ga 1200 µs; ga thật tăng hoặc giảm dần tới giá trị này |
d | Khóa: về 1000 µs ngay lập tức |
c, x | Bắt đầu và kết thúc hiệu chuẩn (mục 5) |
/*
PYWORLD.VN - ESP32 điều khiển động cơ không chổi than qua ESC bằng xung PWM chuẩn 1000-2000 us
Dây tín hiệu ESC (trắng hoặc vàng) = GPIO25, dây âm ESC (đen hoặc nâu) = GND; không nối dây đỏ BEC khi cắm USB
Lệnh Serial: a mở khóa, d khóa, t<us> đặt ga (ví dụ t1200), c hiệu chuẩn ESC, x kết thúc hiệu chuẩn
*/
#define CHAN_ESC 25
#define TAN_SO_PWM 50 // Hz; ESC đời mới nhận được tới khoảng 490 Hz
#define DO_PHAN_GIAI 16 // bit
#define GA_THAP_NHAT 1000 // us
#define GA_CAO_NHAT 2000 // us
#define GIOI_HAN_GA 1400 // us: ga tối đa cho phép khi thử trên bàn, tăng dần khi đã chắc chắn
#define TOC_DO_TANG 400 // us mỗi giây: ga thay đổi từ từ, không giật
const uint32_t CHU_KY_US = 1000000UL / TAN_SO_PWM;
const uint32_t DUTY_TOI_DA = (1UL << DO_PHAN_GIAI) - 1;
enum TrangThai { KHOA, MO_KHOA, HIEU_CHUAN };
TrangThai trangThai = KHOA;
float gaHienTai = GA_THAP_NHAT; // us đang xuất ra
int gaMucTieu = GA_THAP_NHAT; // us người dùng đặt
unsigned long lanCapNhatTruoc = 0;
unsigned long lanInTruoc = 0;
// Đổi độ rộng xung (us) sang giá trị duty của LEDC
uint32_t usSangDuty(uint32_t us) {
return us * DUTY_TOI_DA / CHU_KY_US;
}
void xuatXung(uint32_t us) {
ledcWrite(CHAN_ESC, usSangDuty(us));
}
void inTrangThai() {
const char *ten = (trangThai == KHOA) ? "khoa" : (trangThai == MO_KHOA) ? "da mo khoa" : "hieu chuan";
Serial.printf("Ga: %4d us | duty %5u/%u | %s\n", (int)gaHienTai, (unsigned)usSangDuty((uint32_t)gaHienTai),
(unsigned)DUTY_TOI_DA, ten);
}
void xuLyLenh() {
if (!Serial.available()) {
return;
}
String lenh = Serial.readStringUntil('\n');
lenh.trim();
if (lenh.length() == 0) {
return;
}
char ma = lenh.charAt(0);
if (ma == 'd') { // Khóa: về ga thấp nhất ngay
trangThai = KHOA;
gaMucTieu = GA_THAP_NHAT;
gaHienTai = GA_THAP_NHAT;
xuatXung(GA_THAP_NHAT);
Serial.println("Da khoa dong co");
} else if (ma == 'a') {
if (trangThai == KHOA && gaMucTieu == GA_THAP_NHAT) {
trangThai = MO_KHOA;
Serial.println("Da mo khoa: go t<us> de dat ga, d de khoa");
} else {
Serial.println("Chi mo khoa khi dang khoa va ga o muc thap nhat");
}
} else if (ma == 't') {
int us = lenh.substring(1).toInt();
if (trangThai != MO_KHOA) {
Serial.println("Dang khoa: go a de mo khoa truoc");
} else if (us < GA_THAP_NHAT || us > GIOI_HAN_GA) {
Serial.printf("Ga phai trong khoang %d-%d us\n", GA_THAP_NHAT, GIOI_HAN_GA);
} else {
gaMucTieu = us;
Serial.printf("Ga muc tieu: %d us\n", gaMucTieu);
}
} else if (ma == 'c') {
if (trangThai != KHOA) {
Serial.println("Khoa dong co (d) truoc khi hieu chuan");
return;
}
trangThai = HIEU_CHUAN;
gaHienTai = GA_CAO_NHAT;
xuatXung(GA_CAO_NHAT);
Serial.println("HIEU CHUAN: da thao canh? Dang xuat 2000 us.");
Serial.println("Bay gio cam pin cho ESC, doi tieng bip bao muc cao roi go x");
} else if (ma == 'x' && trangThai == HIEU_CHUAN) {
gaHienTai = GA_THAP_NHAT;
xuatXung(GA_THAP_NHAT);
trangThai = KHOA;
Serial.println("Da xuat 1000 us: doi tieng bip xac nhan, hieu chuan xong");
} else {
Serial.println("Lenh: a mo khoa | d khoa | t<us> dat ga | c hieu chuan | x ket thuc hieu chuan");
}
}
void setup() {
Serial.begin(115200);
// Xuất ga thấp nhất ngay khi khởi động để ESC không quay và nhận ra tín hiệu
ledcAttach(CHAN_ESC, TAN_SO_PWM, DO_PHAN_GIAI);
xuatXung(GA_THAP_NHAT);
delay(100);
Serial.println();
Serial.printf("ESC: %d Hz, chu ky %u us, ga %d-%d us, gioi han thu %d us\n", TAN_SO_PWM, (unsigned)CHU_KY_US,
GA_THAP_NHAT, GA_CAO_NHAT, GIOI_HAN_GA);
Serial.println("Dang khoa. Go a de mo khoa (THAO CANH khi thu tren ban)");
lanCapNhatTruoc = millis();
}
void loop() {
xuLyLenh();
unsigned long bayGio = millis();
float dt = (bayGio - lanCapNhatTruoc) / 1000.0;
lanCapNhatTruoc = bayGio;
if (trangThai == MO_KHOA) {
// Đưa ga hiện tại tiến dần về ga mục tiêu với tốc độ giới hạn
float buoc = TOC_DO_TANG * dt;
if (gaHienTai < gaMucTieu) {
gaHienTai = (gaHienTai + buoc > gaMucTieu) ? gaMucTieu : gaHienTai + buoc;
} else if (gaHienTai > gaMucTieu) {
gaHienTai = (gaHienTai - buoc < gaMucTieu) ? gaMucTieu : gaHienTai - buoc;
}
xuatXung((uint32_t)gaHienTai);
} else if (trangThai == KHOA) {
gaHienTai = GA_THAP_NHAT;
xuatXung(GA_THAP_NHAT);
}
if (bayGio - lanInTruoc >= 500) {
lanInTruoc = bayGio;
if (trangThai == MO_KHOA) {
inTrangThai();
}
}
delay(5);
}
7. Giải thích các phần chính
ledcAttach(CHAN_ESC, TAN_SO_PWM, DO_PHAN_GIAI)gắn chân GPIO25 vào bộ LEDC (cú pháp của gói ESP32 phiên bản 3), sau đóledcWrite(CHAN_ESC, duty)đổi độ rộng xung.- Ngay khi khởi động, chương trình xuất 1000 µs. ESC chỉ sẵn sàng (bíp báo sẵn sàng) khi thấy mức ga thấp nhất; nhờ đó động cơ không thể tự quay lúc vừa cắm pin.
- Ba trạng thái
KHOA,MO_KHOA,HIEU_CHUANquyết định lệnh nào được chấp nhận. Lệnhtbị từ chối khi đang khóa hoặc khi vượtGIOI_HAN_GA. GIOI_HAN_GAđặt 1400 µs cho lần thử đầu trên bàn. Khi đã chắc chắn về giá kẹp và nguồn, tăng dần giới hạn này.

Trong loop(), ga hiện tại tiến về ga đặt với tốc độ tối đa 400 µs mỗi giây. Ga không nhảy đột ngột nên động cơ không giật, dòng điện không vọt lên, và nếu gõ nhầm giá trị thì vẫn kịp gõ d. Lệnh khóa thì ngược lại: đưa ga về 1000 µs ngay, không chờ giảm dần.
8. Kết quả

Khi cắm pin, ESC thường phát vài tiếng bíp theo số cell, rồi một tiếng báo sẵn sàng. Sau lệnh a và t1100 đến t1200, động cơ bắt đầu quay chậm; mức ga bắt đầu quay của mỗi ESC khác nhau một chút (thường khoảng 1050–1150 µs).
9. An toàn khi thử động cơ
- Luôn tháo cánh khi nối dây, hiệu chuẩn và thử chương trình lần đầu. Động cơ flycam quay hàng chục nghìn vòng mỗi phút; cánh quạt có thể gây thương tích nặng.
- Khi gắn cánh để đo lực đẩy: giá phải bắt vít chắc vào bàn, người đứng ngoài mặt phẳng quay của cánh, đeo kính bảo hộ, tăng ga từng bước nhỏ.
- Cắm pin cho ESC sau cùng, rút pin trước khi sửa bất kỳ dây nào. Cách dừng nhanh nhất là gõ
dhoặc rút pin. - Pin LiPo không để chập hai cực, không dùng pin phồng, sạc bằng bộ sạc cân bằng đúng số cell.
10. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| ESC bíp đều mãi, không báo sẵn sàng | Không có xung (sai chân, thiếu GND chung) hoặc ga lúc cắm pin cao hơn mức thấp nhất đã lưu | Kiểm tra dây tín hiệu và GND; hiệu chuẩn lại |
| Động cơ rung, giật, không quay tròn | Một dây pha tiếp xúc kém hoặc đứt | Kiểm tra mối hàn, đầu nối của ba dây pha |
| Động cơ quay ngược chiều | Thứ tự dây pha | Rút pin, đảo hai dây pha bất kỳ |
| Ga đặt 1100 µs mà động cơ chưa quay | Mức bắt đầu quay của ESC cao hơn | Tăng dần ga; hiệu chuẩn để các ESC đồng đều |
| ESP32 khởi động lại khi động cơ chạy | Dây 5 V của ESC nối chung với USB; nhiễu từ dây nguồn động cơ | Bỏ dây đỏ 5 V; đi dây tín hiệu xa dây pha, dây GND ngắn |
| ESC rất nóng | Động cơ, cánh quá tải so với dòng định mức của ESC | Dùng cánh nhỏ hơn hoặc ESC dòng lớn hơn |
11. Xem thêm
- PWM trên ESP32: analogWrite và LEDC
- ESP32 điều khiển servo SG90 bằng PWM (cùng kiểu xung 1000–2000 µs)
- Cân điện tử với ESP32, HX711 và load cell: gắn động cơ lên load cell và module HX711 để đo lực đẩy theo mức ga
- Đo điện áp pin bằng ADC và cầu phân áp (theo dõi pin trong lúc thử)
Cùng loạt: động cơ flycam, ESC, cánh quạt và lực đẩy; pin LiPo cho flycam; ESP32 làm mạch điều khiển bay cho flycam mini.
Xem thêm