ESP32 điều khiển servo SG90 bằng PWM, không cần thư viện: quét góc, biến trở, hiệu chỉnh
- 1. Servo hiểu tín hiệu thế nào
- 2. Tạo xung servo bằng LEDC của ESP32
- 3. Linh kiện cần chuẩn bị
- 4. Sơ đồ nối dây
- 5. Chương trình 1: quét từ 0 đến 180 độ
- 6. Chương trình 2: vặn biến trở để chỉnh góc
- 7. Chương trình 3: hiệu chỉnh hai đầu hành trình
- 8. Gợi ý dùng trong giờ học STEM
- 9. Xử lý lỗi thường gặp
- 10. Xem thêm
Hướng dẫn điều khiển servo SG90 bằng ESP32 với bộ PWM LEDC 50 Hz có sẵn: xung 0,5–2,4 ms, sơ đồ nối, quét góc, vặn biến trở, hiệu chỉnh hai đầu hành trình và thí nghiệm STEM.
Servo SG90 là động cơ nhỏ quay tới một góc cho trước trong khoảng 0–180° rồi giữ nguyên ở góc đó. Nhờ vậy servo rất hợp với các mô hình STEM như cánh tay robot, cửa tự động, cần gạt, đầu quay cảm biến. Bên trong servo có động cơ DC, hộp số, biến trở đo góc và mạch điều khiển; ESP32 chỉ cần gửi đúng dạng xung.
Bài viết giải thích xung điều khiển servo, cách tạo xung bằng bộ PWM có sẵn của ESP32 (không cần thư viện servo), cách nối dây, ba chương trình mẫu (quét góc, vặn biến trở, hiệu chỉnh) và gợi ý thí nghiệm cho giờ học STEM.
1. Servo hiểu tín hiệu thế nào

- Servo nhận một chuỗi xung lặp lại mỗi 20 ms, tức tần số 50 Hz.
- Thời gian xung ở mức cao quyết định góc. Datasheet SG90 ghi 1–2 ms cho dải −90° đến +90°. Trên thực tế, nhiều servo cần khoảng 0,5–2,4 ms mới quay đủ 180°, và mỗi con lệch nhau một ít.
- Bộ PWM của ESP32 tự phát xung liên tục, chương trình không phải lo việc này. Khi mất xung, nhiều servo thôi giữ lực và có thể bị đẩy lệch khỏi góc.
| Thông số | Giá trị |
|---|---|
| Điện áp làm việc | 4,8–6 V (dùng 5 V) |
| Góc quay | khoảng 180° |
| Tín hiệu | xung 50 Hz, rộng 0,5–2,4 ms tùy servo |
| Dây | nâu: GND, đỏ: +5 V, cam: tín hiệu |
| Mô-men | nhỏ, hợp với cơ cấu nhẹ bằng nhựa, bìa cứng |
2. Tạo xung servo bằng LEDC của ESP32
Bộ PWM (LEDC) của ESP32 đặt được tần số 50 Hz với độ phân giải 16 bit. Một chu kỳ 20 ms được chia thành 65 536 bước, mỗi bước khoảng 0,3 µs. Muốn có xung rộng x micro giây thì ghi vào ledcWrite() giá trị:
giá trị = x × 65536 / 20000
ví dụ: 1450 µs → 1450 × 65536 / 20000 = 4751
Vì vậy không cần cài thư viện servo. Chỉ cần hai hàm có sẵn trong gói esp32 3.x: ledcAttach() và ledcWrite().
3. Linh kiện cần chuẩn bị
- ESP32 DevKit V1 30 chân.
- Servo SG90.
- Biến trở 10 kΩ (dùng cho chương trình 2).
- Breadboard 830 lỗ và dây cắm test board.
- Khi dùng từ hai servo trở lên: nguồn 5 V riêng, ví dụ hai pin 18650 qua mạch hạ áp LM2596 chỉnh về 5 V.
4. Sơ đồ nối dây

- Một servo SG90 chạy nhẹ có thể lấy 5 V từ chân VIN khi ESP32 đang cắm USB.
- Khi servo chịu tải hoặc dùng nhiều servo, dòng điện tăng vọt mỗi lúc servo khởi động có thể làm ESP32 khởi động lại. Lúc đó dùng nguồn 5 V riêng nối vào thanh 5V và tháo dây từ VIN. GND của nguồn riêng vẫn phải nối chung với GND của ESP32.
- Một tụ hóa 470–1000 µF cắm giữa thanh 5V và thanh GND, gần servo, giúp nguồn ổn định hơn.
- Tín hiệu 3,3 V của ESP32 đủ để điều khiển SG90. Không nối biến trở vào 5 V vì chân ADC của ESP32 chỉ chịu được tới 3,3 V.
5. Chương trình 1: quét từ 0 đến 180 độ
/*
PYWORLD.VN - ESP32 điều khiển servo SG90 bằng PWM 50 Hz, không cần thư viện servo
Servo: dây cam (tín hiệu) -> GPIO26, dây đỏ -> 5 V, dây nâu -> GND (chung với ESP32)
Quét 0 -> 180 -> 0 độ, in góc và độ rộng xung ra Serial Monitor
*/
#define SERVO_PIN 26
#define SERVO_TAN_SO 50 // 50 Hz: chu kỳ 20 ms
#define SERVO_BIT 16 // độ phân giải 16 bit, mỗi bước khoảng 0,3 micro giây
#define CHU_KY_US 20000
// Độ rộng xung ứng với 0 độ và 180 độ. Mỗi servo lệch một chút:
// dùng chương trình SERVO_HIEU_CHINH để tìm giá trị đúng cho servo của bạn.
#define XUNG_MIN_US 500
#define XUNG_MAX_US 2400
uint32_t usToDuty(uint32_t us) {
return (uint32_t)((uint64_t)us * (1UL << SERVO_BIT) / CHU_KY_US);
}
uint32_t gocToUs(int goc) {
goc = constrain(goc, 0, 180);
return XUNG_MIN_US + (uint32_t)(XUNG_MAX_US - XUNG_MIN_US) * goc / 180;
}
void datGoc(int goc) {
ledcWrite(SERVO_PIN, usToDuty(gocToUs(goc)));
}
void setup() {
Serial.begin(115200);
if (!ledcAttach(SERVO_PIN, SERVO_TAN_SO, SERVO_BIT)) {
Serial.println("Khong cau hinh duoc PWM cho servo!");
while (true) delay(1000);
}
datGoc(90);
delay(1000);
}
void quet(int tu, int den) {
int buoc = (den > tu) ? 1 : -1;
for (int goc = tu; goc != den + buoc; goc += buoc) {
datGoc(goc);
if (goc % 30 == 0) {
Serial.printf("Goc %3d do -> xung %4u us\n", goc, (unsigned)gocToUs(goc));
}
delay(15); // servo cần thời gian để quay tới góc mới
}
}
void loop() {
quet(0, 180);
delay(500);
quet(180, 0);
delay(500);
}
Servo quay đi quay lại từ 0 đến 180°, mỗi bước 1° cách nhau 15 ms. Cứ mỗi 30°, Serial Monitor in ra góc và độ rộng xung. Nếu ở hai đầu hành trình servo kêu rè rè, xem mục 7.
6. Chương trình 2: vặn biến trở để chỉnh góc
/*
PYWORLD.VN - Vặn biến trở để điều khiển góc servo SG90 (ESP32, PWM 50 Hz)
Servo: tín hiệu -> GPIO26, 5 V, GND. Biến trở 10 kΩ: hai chân ngoài -> 3V3 và GND, chân giữa -> GPIO34
Đọc trung bình 8 lần cho đỡ dao động, chỉ đổi góc khi lệch từ 2 độ để servo không rung
*/
#define SERVO_PIN 26
#define BIEN_TRO_PIN 34
#define SERVO_BIT 16
#define CHU_KY_US 20000
#define XUNG_MIN_US 500
#define XUNG_MAX_US 2400
int gocHienTai = -1;
uint32_t gocToUs(int goc) {
goc = constrain(goc, 0, 180);
return XUNG_MIN_US + (uint32_t)(XUNG_MAX_US - XUNG_MIN_US) * goc / 180;
}
void datGoc(int goc) {
uint32_t us = gocToUs(goc);
ledcWrite(SERVO_PIN, (uint32_t)((uint64_t)us * (1UL << SERVO_BIT) / CHU_KY_US));
}
int docBienTro() {
long tong = 0;
for (int i = 0; i < 8; i++) tong += analogRead(BIEN_TRO_PIN);
return tong / 8;
}
void setup() {
Serial.begin(115200);
ledcAttach(SERVO_PIN, 50, SERVO_BIT);
}
void loop() {
int giaTri = docBienTro(); // 0..4095
int goc = map(giaTri, 0, 4095, 0, 180);
if (gocHienTai < 0 || abs(goc - gocHienTai) >= 2) {
datGoc(goc);
gocHienTai = goc;
Serial.printf("Bien tro: %4d -> goc %3d do -> xung %4u us\n", giaTri, goc, (unsigned)gocToUs(goc));
}
delay(20);
}

map(giaTri, 0, 4095, 0, 180)đổi giá trị ADC 12 bit ra góc.- Đọc 8 lần rồi lấy trung bình, và chỉ đổi góc khi lệch từ 2° trở lên. Nhờ vậy servo không rung khi tay đã thôi vặn biến trở.
7. Chương trình 3: hiệu chỉnh hai đầu hành trình
Nếu xung ngắn hơn hoặc dài hơn mức servo quay được, servo sẽ cố quay tiếp vào chặn cơ khí, kêu rè rè, nóng lên và nhanh hỏng hộp số. Chương trình này cho phép tìm đúng hai đầu hành trình của từng servo.
/*
PYWORLD.VN - Hiệu chỉnh servo: gõ độ rộng xung trên Serial Monitor để tìm vị trí 0 và 180 độ
Lệnh: x 1500 = đặt xung 1500 micro giây (400..2600), g 90 = đặt góc theo XUNG_MIN/XUNG_MAX hiện tại
min 520 / max 2380 = lưu giá trị vừa tìm được. Serial Monitor: 115200 baud, "New Line"
*/
#define SERVO_PIN 26
#define SERVO_BIT 16
#define CHU_KY_US 20000
uint32_t xungMin = 500, xungMax = 2400;
void datXung(uint32_t us) {
ledcWrite(SERVO_PIN, (uint32_t)((uint64_t)us * (1UL << SERVO_BIT) / CHU_KY_US));
Serial.printf("Xung %4u us\n", (unsigned)us);
}
void setup() {
Serial.begin(115200);
ledcAttach(SERVO_PIN, 50, SERVO_BIT);
datXung(1500);
Serial.println("Lenh: x <us>, g <goc>, min <us>, max <us>");
}
void loop() {
if (!Serial.available()) return;
String lenh = Serial.readStringUntil('\n');
lenh.trim();
lenh.toLowerCase();
int cach = lenh.indexOf(' ');
String ten = (cach < 0) ? lenh : lenh.substring(0, cach);
int so = (cach < 0) ? 0 : lenh.substring(cach + 1).toInt();
if (ten == "x" && so >= 400 && so <= 2600) {
datXung(so);
} else if (ten == "g" && so >= 0 && so <= 180) {
datXung(xungMin + (xungMax - xungMin) * (uint32_t)so / 180);
} else if (ten == "min" && so >= 400 && so <= 2600) {
xungMin = so;
Serial.printf("Da luu XUNG_MIN = %u us\n", (unsigned)xungMin);
} else if (ten == "max" && so >= 400 && so <= 2600) {
xungMax = so;
Serial.printf("Da luu XUNG_MAX = %u us\n", (unsigned)xungMax);
} else {
Serial.println("Lenh khong hop le. Vi du: x 1500, g 90, min 520, max 2380");
}
}
- Gõ
x 1500: servo về giữa hành trình. - Giảm dần, ví dụ
x 700,x 600,x 550, tới lúc servo không quay thêm nữa hoặc bắt đầu kêu. Lấy giá trị trước đó, cộng thêm khoảng 20 µs cho an toàn, rồi gõminkèm giá trị đó. - Làm tương tự theo chiều tăng để có
max. - Gõ
g 0,g 90,g 180và đo bằng thước đo độ để kiểm tra. - Chép hai giá trị vào
XUNG_MIN_US,XUNG_MAX_UScủa chương trình 1 và 2.
8. Gợi ý dùng trong giờ học STEM
Mục tiêu: học sinh hiểu cách mã hóa một con số (góc) bằng độ rộng xung, làm quen với hệ điều khiển vòng kín bên trong servo, và đo đạc rồi lập bảng.
- Lập bảng độ rộng xung theo góc. Dùng chương trình 3 và thước đo độ in trên giấy dán dưới càng servo, ghi độ rộng xung ứng với 0°, 45°, 90°, 135°, 180°. Vẽ đồ thị, nhận xét quan hệ có tuyến tính không, rồi tìm công thức.
- Cảm nhận vòng kín. Đặt servo ở 90°, dùng tay vặn nhẹ càng servo: servo chống lại và tự quay về. Thảo luận: servo biết mình lệch nhờ bộ phận nào? So sánh với động cơ DC không có cảm biến.
- Làm cửa tự động hoặc rào chắn. Gắn càng servo vào miếng bìa làm thanh chắn, nhấn nút hoặc vặn biến trở để nâng, hạ. Có thể nối tiếp với cảm biến khoảng cách ở bài xe tránh vật cản trong cùng loạt.
- Đo thời gian quay. Bỏ
delay(15), ra lệnh từ 0° nhảy thẳng lên 180°, quay video chậm bằng điện thoại để ước lượng tốc độ quay của servo, tính bằng độ trên giây.
Các hoạt động này dùng kiến thức về góc và đồ thị trong Toán, chuyển động quay trong Vật lý, và lập trình trong Tin học.
9. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Servo không quay | Chưa nối GND chung; dây tín hiệu cắm nhầm chân | Nối GND servo với GND ESP32; kiểm tra dây cam ở D26 |
| ESP32 khởi động lại khi servo quay | Nguồn yếu, dòng tăng vọt | Dùng nguồn 5 V riêng; thêm tụ 470–1000 µF giữa 5V và GND |
| Servo kêu rè rè ở hai đầu | Xung vượt quá hành trình cơ khí | Hiệu chỉnh XUNG_MIN_US, XUNG_MAX_US bằng chương trình 3 |
| Servo rung nhẹ khi đứng yên | Giá trị biến trở dao động; nguồn nhiễu | Lấy trung bình nhiều lần đọc, tăng ngưỡng đổi góc; dây nguồn ngắn và chắc |
| Góc thực tế lệch so với lệnh | Mỗi servo một khác, có khe hở hộp số | Hiệu chỉnh hai đầu; với cơ cấu cần chính xác, chọn servo bánh răng kim loại |
10. Xem thêm
- PWM trên ESP32: điều chỉnh độ sáng LED bằng analogWrite và LEDC
- Mạch hạ áp LM2596 và MP1584: chỉnh điện áp và cấp nguồn
- Đọc ADC trên ESP32
Các bài tiếp theo trong loạt dùng servo: xe tránh vật cản có cảm biến quét và cánh tay robot bốn servo ghi nhớ động tác.
Xem thêm