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

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

Hình minh họa xung điều khiển servo SG90 tần số 50 Hz: xung rộng 0,5 ms cho 0 độ, 1,45 ms cho 90 độ, 2,4 ms cho 180 độ, kèm giá trị 16 bit ghi vào ledcWrite
Hình minh họa: độ rộng xung quyết định góc quay của servo.
  • 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ố chính của servo SG90
Thông sốGiá trị
Điện áp làm việc4,8–6 V (dùng 5 V)
Góc quaykhoảng 180°
Tín hiệuxung 50 Hz, rộng 0,5–2,4 ms tùy servo
Dâynâu: GND, đỏ: +5 V, cam: tín hiệu
Mô-mennhỏ, 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ị

4. Sơ đồ nối dây

Sơ đồ nối ESP32 DevKit V1 với servo SG90: dây cam vào D26, dây đỏ vào thanh 5V lấy từ chân VIN, dây nâu vào GND; biến trở 10 kΩ chân giữa vào D34, hai chân ngoài vào 3V3 và GND
Hình minh họa: sơ đồ nối servo và biến trở qua các hàng nguồn của breadboard.
  • 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);
}
Hình minh họa Serial Monitor in giá trị biến trở 0 đến 4095, góc servo 0 đến 180 độ và độ rộng xung 500 đến 2400 micro giây
Hình minh họa: giá trị biến trở, góc và độ rộng xung tương ứng.
  • 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");
  }
}
  1. Gõ x 1500: servo về giữa hành trình.
  2. 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õ min kèm giá trị đó.
  3. Làm tương tự theo chiều tăng để có max.
  4. Gõ g 0, g 90, g 180 và đo bằng thước đo độ để kiểm tra.
  5. Chép hai giá trị vào XUNG_MIN_US, XUNG_MAX_US củ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.

  1. 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.
  2. 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.
  3. 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.
  4. Đ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

Lỗi thường gặp
Hiện tượngNguyên nhân thường gặpCách xử lý
Servo không quayChưa nối GND chung; dây tín hiệu cắm nhầm chânNối GND servo với GND ESP32; kiểm tra dây cam ở D26
ESP32 khởi động lại khi servo quayNguồn yếu, dòng tăng vọtDùng nguồn 5 V riêng; thêm tụ 470–1000 µF giữa 5V và GND
Servo kêu rè rè ở hai đầuXung 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ênGiá trị biến trở dao động; nguồn nhiễuLấ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ệnhMỗ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

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.


Tin tức liên quan

Hướng dẫn đọc ADC trên ESP32 và đo điện áp pin 18650 bằng cầu phân áp
Hướng dẫn đọc ADC trên ESP32 và đo điện áp pin 18650 bằng cầu phân áp

Hướng dẫn đọc điện áp analog bằng ADC của ESP32 trên Arduino IDE, dùng analogReadMilliVolts, làm cầu phân áp đo pin Li-ion 18650 và ước lượng phần trăm pin còn lại.

Phased array siêu âm 40 kHz với ESP32 (phần 2): mạch đệm 12 V, đầu thu và quét búp sóng
Phased array siêu âm 40 kHz với ESP32 (phần 2): mạch đệm 12 V, đầu thu và quét búp sóng

Phần 2 của loạt phased array siêu âm: thêm mạch đệm 12 V bằng hai module L298N, lắp mạch thu dùng LM358, đo biên độ 40 kHz bằng ADC của ESP32 và chạy chương trình tự quét góc lái để xem búp sóng.

Hướng dẫn dùng màn hình OLED SSD1306 với ESP8266
Hướng dẫn dùng màn hình OLED SSD1306 với ESP8266

Hướng dẫn nối OLED SSD1306 với ESP8266, quét địa chỉ I2C, cài thư viện và hiển thị thời gian bằng mã Arduino mẫu.

Hướng dẫn dùng module thẻ nhớ Micro SD với ESP32
Hướng dẫn dùng module thẻ nhớ Micro SD với ESP32

Hướng dẫn nối module thẻ nhớ Micro SD với ESP32 DevKit V1 qua SPI, định dạng thẻ FAT32, chạy code Arduino đọc ghi file, ghi dữ liệu định kỳ ra CSV và xử lý lỗi Card Mount Failed.

Hướng dẫn dùng ngắt GPIO trên ESP32: đọc nút nhấn, chống dội và đếm xung
Hướng dẫn dùng ngắt GPIO trên ESP32: đọc nút nhấn, chống dội và đếm xung

Hướng dẫn dùng ngắt GPIO trên ESP32 với Arduino IDE: attachInterrupt, các kiểu kích ngắt, chống dội nút nhấn, đếm xung đo tần số và quy tắc viết hàm ngắt an toàn.

Hướng dẫn dùng MQTT với ESP32: gửi dữ liệu và điều khiển relay bằng PubSubClient
Hướng dẫn dùng MQTT với ESP32: gửi dữ liệu và điều khiển relay bằng PubSubClient

Hướng dẫn dùng MQTT với ESP32 trên Arduino IDE: broker, topic, publish, subscribe, cài Mosquitto và PubSubClient, gửi dữ liệu và điều khiển module relay qua mạng nội bộ.

Hướng dẫn ESP-NOW trên ESP32: truyền dữ liệu giữa hai mạch không cần router Wi-Fi
Hướng dẫn ESP-NOW trên ESP32: truyền dữ liệu giữa hai mạch không cần router Wi-Fi

Hướng dẫn dùng ESP-NOW để hai mạch ESP32 truyền dữ liệu trực tiếp không cần router: xem địa chỉ MAC, chương trình gửi, chương trình nhận, đọc RSSI và xử lý lỗi thường gặp.

Phased array siêu âm 40 kHz với ESP32 (phần 1): nguyên lý lái búp sóng và tạo 8 kênh lệch pha
Phased array siêu âm 40 kHz với ESP32 (phần 1): nguyên lý lái búp sóng và tạo 8 kênh lệch pha

Nguyên lý phased array và cách dùng ESP32 tạo 8 kênh xung 40 kHz lệch pha để lái búp sóng siêu âm: công thức độ trễ, giới hạn do khoảng cách đầu phát, cấu hình LEDC với hpoint, sơ đồ nối và code.


Bình luận
Đã thêm vào giỏ hàng