0968140191Miền Nam
0975897066Miền Bắc

Điều khiển động cơ servo bằng Arduino: đấu nối, cấp nguồn, code mẫu và cách xử lý lỗi

Viết bởi: Vũ Hồng Phúc
Vũ Hồng Phúc
Vũ Hồng Phúc, sinh năm 1985, sau khi tốt nghiệp cử nhân tại Đại Học Bách Khoa Hà Nội, Vũ Hồng Phúc đã tự mình khám phá thế giới qua việc du học tại EF Language School London và nhiều trường đại học khác. Tích lũy kinh nghiệm trong nhiều năm, anh quyết định trở về đất nước. Vũ Hồng Phúc đã hướng đến mục tiêu phát triển Dongco3pha.com là thương hiệu "Động cơ điện" hàng đầu Việt Nam. Trong nhiều năm liền danh liên tục mang lại nhiều thành tựu cho doanh nghiệp và đóng góp nhiều giải pháp tối ưu về chế tạo máy, chế tạo động cơ giảm tốc, động cơ điện.
10 thg 10 2026 17:36

Điều khiển động cơ servo bằng Arduino là bài thực hành gần như ai học vi điều khiển cũng đi qua: chỉ cần ba sợi dây, vài dòng code với thư viện Servo.h là trục servo đã quay đến góc mong muốn. Thế nhưng phần lớn sự cố thực tế lại không nằm ở code mà ở nguồn điện, cách nối mass, xung đột timer giữa các thư viện và cách hiệu chỉnh hành trình cho từng con servo. Một chiếc MG996R khởi động có thể kéo dòng hơn 2 A, đủ làm bo Arduino sụt áp và tự khởi động lại giữa chừng.

Bài này đi từ đấu nối, cấp nguồn, thư viện Servo.h đến code mẫu điều khiển bằng biến trở, joystick, nút nhấn, Serial, Bluetooth, chạy mượt không dùng delay, module PCA9685, ESP32 và servo 360 độ. Các ví dụ áp dụng cho SG90, MG90S, MG996R, DS3218 với Arduino Uno, Nano, Mega và ESP32.

1. Servo RC và Arduino làm việc với nhau như thế nào

Servo RC gồm động cơ DC nhỏ, hộp số, biến trở đo góc và mạch điều khiển trong cùng một vỏ. Arduino không cấp công suất cho động cơ mà chỉ gửi chuỗi xung trên dây tín hiệu, khoảng 50 lần mỗi giây; độ rộng xung (khoảng 500–2.500 micro giây) cho mạch bên trong biết trục cần đứng ở góc nào. Phần bản chất dạng sóng, tần số và độ phân giải của tín hiệu được phân tích riêng trong bài điều khiển động cơ RC servo bằng PWM. Thư viện Servo.h lo việc tạo xung, người lập trình chỉ ra lệnh góc hoặc độ rộng xung.

Dây tín hiệu gần như không tốn dòng, còn dây nguồn có thể kéo dòng rất lớn khi khởi động, mang tải hoặc bị kẹt:

ServoĐiện áp làm việcMô-men giữDòng khi kẹt (stall)Nguồn nên dùng cho 1 servo
SG90 (bánh răng nhựa)4,8–6 Vkhoảng 1,8 kg.cm @4,8 Vdưới 1 A5 V – 1 A
MG90S (bánh răng kim loại)4,8–6 V1,8–2,2 kg.cmdưới 1 A5 V – 1 A
MG996R4,8–7,2 V9,4 kg.cm @4,8 V; 11 kg.cm @6 Vkhoảng 2,5 A @6 V5–6 V – 3 A
DS3218 (servo số)4,8–6,8 Vkhoảng 20 kg.cmkhoảng 1,8–2,2 A, có lô cao hơn6–6,8 V – 3 A

Cách đọc thông số kg.cm, tốc độ s/60° và chọn servo analog hay servo số được trình bày trong bài điều khiển động cơ RC servo.

2. Đấu nối và cấp nguồn khi điều khiển servo bằng Arduino

Servo RC có ba dây. Màu dây phổ biến trên SG90, MG996R: nâu là GND, đỏ là nguồn dương, cam là tín hiệu. Một số loại khác dùng đen – đỏ – trắng hoặc đen – đỏ – vàng; dây đỏ luôn nằm giữa nên cắm ngược đầu thường không làm cháy servo.

Dây servoNối tớiGhi chú
Tín hiệu (cam/vàng/trắng)Một chân digital bất kỳ của Arduino, ví dụ D9Không bắt buộc phải là chân có dấu ~
Nguồn (đỏ)Cực dương nguồn riêng 5–6 VChỉ dùng chân 5V của Arduino cho 1 servo nhỏ, chạy không tải để thử
GND (nâu/đen)Cực âm nguồn riêng, đồng thời nối về GND ArduinoThiếu dây nối chung GND, servo sẽ giật hoặc không quay

Sơ đồ dưới là cách nối quen thuộc trên breadboard: tín hiệu servo vào chân 9, biến trở vào A0. Cách lấy nguồn từ bo chỉ dùng khi thử một servo nhỏ; servo lớn hơn phải chuyển dây đỏ sang nguồn riêng.

Sơ đồ đấu nối điều khiển động cơ servo bằng Arduino Uno với biến trở trên breadboard

Vì sao không nên lấy nguồn servo từ chân 5V của Arduino

Khi cắm USB, bo Uno được cấp qua cầu chì tự phục hồi khoảng 500 mA; khi cấp qua jack DC hoặc Vin 9–12 V, IC ổn áp tuyến tính trên bo nóng rất nhanh và chỉ cho được vài trăm mA. Một chiếc MG996R lúc khởi động đã vượt cả hai giới hạn đó. Điện áp 5V tụt trong vài mili giây, ATmega328P bị reset, chương trình chạy lại, servo lại khởi động và vòng lặp cứ thế tiếp diễn.

Cách cấp nguồn đúng

  • Nguồn riêng 5–6 V cho servo: adapter 5 V, module hạ áp (buck) từ 12 V, hoặc 4 pin AA sạc NiMH (khoảng 4,8 V). Không dùng pin 9 V vuông.
  • Nối GND nguồn servo với GND Arduino; tín hiệu là điện áp so với mass, thiếu dây chung servo không hiểu được xung.
  • Đi dây hình sao: dây nguồn servo chạy thẳng về cực nguồn, không đi vòng qua bo Arduino, để dòng lớn không chạy trên đường mass của vi điều khiển.
  • Tụ hóa 470–1.000 µF (từ 10 V trở lên) sát hàng nguồn servo, thêm tụ gốm 100 nF; tính sơ bộ khoảng 100 µF mỗi servo. Tụ chỉ đỡ xung dòng ngắn lúc khởi động, không thay được bộ nguồn thiếu công suất.
  • Dây đủ tiết diện, hạn chế dây cắm breadboard cho đường nguồn servo lớn.

Ví dụ tính nguồn cho cánh tay robot 4 servo MG996R

Dòng khi kẹt của mỗi MG996R khoảng 2,5 A ở 6 V. Trường hợp xấu nhất cả 4 khớp cùng khởi động hoặc cùng bị chặn: 4 × 2,5 = 10 A. Thực tế hiếm khi tất cả cùng kẹt, nên nguồn 6 V – 6 đến 8 A kèm tụ 2.200 µF là hợp lý; cho các khớp chạy lần lượt thay vì đồng loạt sẽ giảm đáng kể dòng đỉnh. Với 4 servo SG90, nguồn 5 V – 3 A là đủ thoải mái.

3. Thư viện Servo.h: các hàm cần biết và giới hạn

Thư viện Servo.h có sẵn trong Arduino IDE, dùng chung cú pháp cho Uno, Nano, Mega, Leonardo, Uno R4 và nhiều bo khác:

HàmChức năngLưu ý thực tế
attach(pin)Gắn servo vào chân, dải xung mặc định 544–2.400 µsGọi xong là chân bắt đầu phát xung
attach(pin, min, max)Gắn servo với dải xung tùy chọn (µs)Dùng để hiệu chỉnh 0° và 180° cho từng servo
write(goc)Ra lệnh góc 0–180Giá trị nhỏ hơn 544 hiểu là góc, từ 544 trở lên hiểu là micro giây
writeMicroseconds(us)Ra lệnh trực tiếp bằng độ rộng xungBị giới hạn trong khoảng min–max đã khai báo ở attach
read()Trả về góc lệnh gần nhấtKhông đọc được vị trí thật của trục
detach()Ngừng phát xung trên chânServo thả lỏng, không còn giữ vị trí

Hiệu chỉnh hành trình bằng attach(pin, min, max)

Dải mặc định 544–2.400 µs không khớp với mọi servo: có con chỉ quay khoảng 160°, có con tì vào cữ chặn ở 0° hoặc 180°, kêu rè và nóng lên. Dùng chương trình Serial ở mục 4e, tăng giảm xung từng 10 µs để tìm hai giá trị trục vừa đến hai đầu hành trình mà chưa tì cữ, rồi đưa vào attach(pin, min, max). Khi đó write(0) và write(180) khớp đúng hành trình thật. Servo.h lưu min/max dưới dạng độ lệch so với 544 và 2.400 µs theo bước 4 µs, nên giá trị phổ biến như 500–2.500 µs hoàn toàn dùng được.

Ra lệnh bằng độ rộng xung để mịn hơn

Với write(), mỗi độ ứng với khoảng 10 µs; khi cần chuyển động chậm và mịn, ra lệnh bằng writeMicroseconds() (độ phân giải trên AVR khoảng 0,5 µs).

Số servo tối đa và xung đột Timer1

Trên Uno, Nano (ATmega328P), Servo.h dùng Timer1 để tạo xung cho tối đa 12 servo. Ngay khi servo đầu tiên được gắn, analogWrite() trên chân 9 và 10 ngừng hoạt động, kể cả khi servo nằm ở chân khác; dự án có thêm điều tốc motor DC bằng PWM nên dùng chân 3, 5, 6 hoặc 11. Arduino Mega dùng được tới 48 servo; khi dùng từ 12 đến 23 servo, PWM chân 11 và 12 của Mega bị mất. Vì xung tạo bằng ngắt timer, mọi chân digital, kể cả A0–A5, đều nối servo được.

Các thư viện hay làm servo giật

Thư viện nào tắt ngắt lâu sẽ làm xung lệch vài chục micro giây và servo giật theo:

  • SoftwareSerial: tắt ngắt khi gửi nhận từng byte, servo rung mỗi khi có dữ liệu Bluetooth hay GPS. Dùng Serial phần cứng (chân 0, 1) hoặc bo nhiều UART như Mega, ESP32.
  • AltSoftSerial trên Uno cũng dùng Timer1 nên xung đột trực tiếp với Servo.h.
  • Thư viện LED WS2812 (NeoPixel, FastLED) tắt ngắt khi đẩy dữ liệu ra dải LED; dải càng dài servo càng giật. Giải pháp là tạo xung servo bằng PCA9685.

Trước khi lắp mạch thật, có thể mô phỏng trong Proteus để kiểm tra code; hình dưới là servo nối chân 9 của Arduino Uno, đồng hồ bên dưới hiển thị góc theo lệnh:

Mô phỏng điều khiển động cơ servo bằng Arduino Uno chân 9 trong Proteus

Mô phỏng chỉ xác nhận logic chương trình, không phản ánh sụt áp nguồn hay độ lệch hành trình của servo thật.

4. Code mẫu điều khiển động cơ servo bằng Arduino

Các chương trình dưới biên dịch được trên Arduino IDE với bo Uno hoặc Nano. Servo nối chân 9 (trừ khi ghi khác), nguồn servo lấy từ nguồn riêng có GND nối chung.

a. Quét góc 0–180 độ có hiệu chỉnh hành trình

Đây là bản cải tiến của ví dụ Sweep: khai báo dải xung riêng cho servo và ra lệnh vị trí trước khi gắn chân để servo không giật mạnh khi vừa cấp điện.

#include <Servo.h>

Servo servo;
const int US_0 = 544;     // xung ứng với 0 độ, chỉnh theo servo thực tế
const int US_180 = 2400;  // xung ứng với 180 độ

void setup() {
  servo.write(90);                // đặt vị trí trước khi attach
  servo.attach(9, US_0, US_180);
}

void loop() {
  for (int a = 0; a <= 180; a++) { servo.write(a); delay(15); }
  delay(500);
  for (int a = 180; a >= 0; a--) { servo.write(a); delay(15); }
  delay(500);
}

Mỗi bước 1° cách nhau 15 ms, cả hành trình khoảng 2,7 giây.

b. Điều khiển servo bằng biến trở

Biến trở 10 kΩ nối hai đầu vào 5V và GND, chân giữa vào A0. Nếu chỉ map() giá trị analog sang góc, servo rung nhẹ liên tục vì số đọc ADC luôn dao động; code dưới thêm bộ lọc và vùng chết.

#include <Servo.h>

const byte SERVO_PIN = 9;
const byte POT_PIN = A0;
Servo servo;
float filtered = 0;
int lastAngle = -1;

void setup() {
  filtered = analogRead(POT_PIN);
  servo.write(map((int)filtered, 0, 1023, 0, 180));
  servo.attach(SERVO_PIN);
}

void loop() {
  filtered += (analogRead(POT_PIN) - filtered) * 0.2;  // lọc thông thấp
  int angle = map((int)filtered, 0, 1023, 0, 180);
  if (abs(angle - lastAngle) >= 2) {                    // vùng chết 2 độ
    servo.write(angle);
    lastAngle = angle;
  }
  delay(15);
}

Nếu servo vẫn giật khi chuyển động, thường do dòng servo chạy chung đường GND với biến trở làm số đọc ADC nhảy theo. Hãy nối biến trở bằng dây ngắn trực tiếp về chân 5V và GND của Arduino, thêm tụ 100 nF từ A0 xuống GND.

c. Điều khiển 2 servo pan–tilt bằng joystick

Joystick 2 trục gồm hai biến trở (VRx vào A0, VRy vào A1) và nút nhấn SW (vào D2). Cách điều khiển dễ dùng là coi cần gạt như tay ga: gạt càng xa tâm servo quay càng nhanh, buông tay thì giữ nguyên góc, nhấn cần để về giữa.

#include <Servo.h>

Servo pan, tilt;
float panAngle = 90, tiltAngle = 90;
const int DEADZONE = 40;          // vùng chết quanh tâm joystick
const float MAX_SPEED = 120.0;    // độ/giây khi gạt hết cần
unsigned long lastUpdate = 0;

float readAxis(byte pin) {
  int v = analogRead(pin) - 512;
  if (abs(v) < DEADZONE) return 0;
  return v / 512.0;               // -1 .. 1
}

void setup() {
  pinMode(2, INPUT_PULLUP);       // SW của joystick
  pan.write(90);
  tilt.write(90);
  pan.attach(9);
  tilt.attach(10);
}

void loop() {
  unsigned long now = millis();
  if (now - lastUpdate < 20) return;
  float dt = (now - lastUpdate) / 1000.0;
  lastUpdate = now;

  panAngle  = constrain(panAngle  + readAxis(A0) * MAX_SPEED * dt, 0, 180);
  tiltAngle = constrain(tiltAngle + readAxis(A1) * MAX_SPEED * dt, 20, 160);
  if (digitalRead(2) == LOW) { panAngle = 90; tiltAngle = 90; }

  pan.write((int)panAngle);
  tilt.write((int)tiltAngle);
}

Trục tilt giới hạn 20–160° để giá camera không va vào đế; nên giới hạn hành trình bằng phần mềm như vậy cho mọi cơ cấu.

d. Điều khiển servo bằng nút nhấn

Hai nút nhấn nối giữa D2, D3 và GND, dùng điện trở kéo lên bên trong nên không cần điện trở ngoài. Mỗi lần nhấn servo tăng hoặc giảm 10°.

#include <Servo.h>

Servo servo;
const byte BTN_UP = 2, BTN_DOWN = 3;
const int STEP = 10;
int angle = 90;
byte lastUp = HIGH, lastDown = HIGH;
unsigned long lastPress = 0;

void setup() {
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_DOWN, INPUT_PULLUP);
  servo.write(angle);
  servo.attach(9);
}

void loop() {
  byte up = digitalRead(BTN_UP);
  byte down = digitalRead(BTN_DOWN);
  if (millis() - lastPress > 50) {               // chống dội phím
    if (lastUp == HIGH && up == LOW) {
      angle = min(angle + STEP, 180);
      lastPress = millis();
    }
    if (lastDown == HIGH && down == LOW) {
      angle = max(angle - STEP, 0);
      lastPress = millis();
    }
    servo.write(angle);
  }
  lastUp = up;
  lastDown = down;
}

Muốn giữ nút để servo quay đều, thay điều kiện bắt cạnh bằng up == LOW và giảm bước xuống 1–2°.

e. Điều khiển qua Serial Monitor và Bluetooth HC-05

Chương trình này vừa để điều khiển, vừa là công cụ hiệu chỉnh hành trình ở mục 3. Gõ 0–180 để ra lệnh góc, gõ 500–2.500 để ra lệnh độ rộng xung (µs); Serial Monitor đặt 9600 baud, gửi kèm ký tự xuống dòng.

#include <Servo.h>

Servo servo;
char buf[8];
byte len = 0;

void setup() {
  Serial.begin(9600);
  servo.write(90);
  servo.attach(9, 500, 2500);   // mở rộng dải để dò giới hạn thật
  Serial.println(F("Nhap goc 0-180 hoac xung 500-2500 us"));
}

void loop() {
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '\n' || c == '\r') {
      if (len == 0) continue;
      buf[len] = '\0';
      len = 0;
      int v = atoi(buf);
      if (v >= 0 && v <= 180) servo.write(v);
      else if (v >= 500 && v <= 2500) servo.writeMicroseconds(v);
      else { Serial.println(F("Gia tri khong hop le")); continue; }
      Serial.print(F("Xung hien tai (us): "));
      Serial.println(servo.readMicroseconds());
    } else if (len < sizeof(buf) - 1) {
      buf[len++] = c;
    }
  }
}

Dùng chính code trên với Bluetooth HC-05

HC-05 (hoặc HC-06) là cầu nối Serial không dây: điện thoại gửi chuỗi số qua ứng dụng terminal Bluetooth, Arduino nhận như từ Serial Monitor. Nối TX của HC-05 vào D0 (RX) của Uno; RX của HC-05 qua cầu chia áp 1 kΩ – 2 kΩ vào D1 (TX) vì chân này làm việc ở 3,3 V. HC-05 thường mặc định 9600 baud. Rút dây D0 mỗi lần nạp code vì cổng USB dùng chung hai chân này.

5. Cho servo chạy mượt: millis() thay delay() và easing

Lệnh write(150) khi servo đang ở 30° làm servo lao hết tốc độ tới đích, cánh tay robot giật mạnh, dòng đỉnh tăng vọt. Vòng for kèm delay() thì chậm được nhưng chương trình bị chặn, không đọc được nút hay cảm biến. Giải pháp là tính vị trí trung gian theo millis() và dùng đường cong easing để servo tăng tốc từ từ, giảm tốc êm ở cuối hành trình.

#include <Servo.h>

Servo servo;
const unsigned long MOVE_TIME = 1500;   // thời gian mỗi hành trình (ms)
int startUs = 1500, targetUs = 1500;
unsigned long moveStart = 0;
bool moving = false;
unsigned long lastBlink = 0;

void moveTo(int us) {
  startUs = servo.readMicroseconds();
  targetUs = us;
  moveStart = millis();
  moving = true;
}

void updateServo() {
  if (!moving) return;
  unsigned long t = millis() - moveStart;
  if (t >= MOVE_TIME) {
    servo.writeMicroseconds(targetUs);
    moving = false;
    return;
  }
  float x = (float)t / MOVE_TIME;        // 0 .. 1
  float s = x * x * (3 - 2 * x);         // easing tăng tốc - giảm tốc
  servo.writeMicroseconds(startUs + (int)((targetUs - startUs) * s));
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  servo.writeMicroseconds(1500);
  servo.attach(9);
}

void loop() {
  updateServo();
  if (!moving) moveTo(servo.readMicroseconds() < 1500 ? 2300 : 700);

  if (millis() - lastBlink >= 200) {     // việc khác vẫn chạy song song
    lastBlink = millis();
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
}

LED trên bo vẫn nhấp nháy đều khi servo chạy, chứng tỏ loop không bị chặn. Với nhiều servo, mỗi servo giữ bộ biến startUs, targetUs, moveStart riêng (gom thành struct hoặc mảng).

Không muốn tự viết thì dùng thư viện ServoEasing: setSpeed() (độ/giây), easeTo() chạy chặn, startEaseTo() chạy nền, nhiều kiểu đường cong, hỗ trợ cả PCA9685 và ESP32, khai báo được góc ban đầu ngay trong attach(pin, goc).

6. Điều khiển nhiều servo bằng module PCA9685

Khi cần 6–16 servo (hexapod, cánh tay 6 bậc) hoặc dự án dùng thư viện tắt ngắt, module PCA9685 là lựa chọn hợp lý: IC tự tạo 16 kênh xung bằng phần cứng, độ phân giải 12 bit, Arduino chỉ gửi lệnh qua I2C nên không tốn timer.

Đấu nối PCA9685 với Arduino Uno

  • VCC của module nối 5V Arduino: đây chỉ là nguồn logic cho IC.
  • GND nối GND; SDA nối A4, SCL nối A5 (Mega: chân 20, 21; ESP32: GPIO 21, 22).
  • Nguồn servo cấp vào cầu đấu V+ trên module, 5–6 V, đủ dòng cho toàn bộ servo. Tuyệt đối không nối nhầm V+ vào VCC.
  • Hàn thêm tụ hóa vào vị trí dành sẵn trên module, khoảng 100 µF cho mỗi servo (ví dụ 6 servo dùng tụ 680–1.000 µF).
  • Địa chỉ I2C mặc định 0x40, hàn các cầu A0–A5 để đổi; ghép tối đa 62 module (992 kênh) trên một bus, tránh địa chỉ 0x70 là địa chỉ gọi chung.
  • Chân OE (Output Enable) kéo lên mức cao sẽ tắt toàn bộ ngõ ra, có thể dùng làm nút ngắt khẩn cấp.

Code dưới dùng thư viện Adafruit PWM Servo Driver (cài trong Library Manager), điều khiển 6 servo đánh sóng ngược chiều nhau:

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pca = Adafruit_PWMServoDriver(0x40);
const int SERVO_COUNT = 6;
const int US_MIN = 600;    // xung 0 độ
const int US_MAX = 2400;   // xung 180 độ

void setAngle(uint8_t ch, int angle) {
  angle = constrain(angle, 0, 180);
  pca.writeMicroseconds(ch, map(angle, 0, 180, US_MIN, US_MAX));
}

void setup() {
  pca.begin();
  pca.setOscillatorFrequency(27000000);
  pca.setPWMFreq(50);
  delay(10);
  for (int ch = 0; ch < SERVO_COUNT; ch++) setAngle(ch, 90);
}

void loop() {
  for (int a = 30; a <= 150; a += 2) {
    for (int ch = 0; ch < SERVO_COUNT; ch++) setAngle(ch, (ch % 2) ? a : 180 - a);
    delay(20);
  }
  for (int a = 150; a >= 30; a -= 2) {
    for (int ch = 0; ch < SERVO_COUNT; ch++) setAngle(ch, (ch % 2) ? a : 180 - a);
    delay(20);
  }
}

Lưu ý về độ chính xác xung của PCA9685

Bộ dao động nội của PCA9685 lệch vài phần trăm giữa các module, nên xung 1.500 µs thực tế có thể ra 1.450 hoặc 1.560 µs. Nếu có máy đo tần số, đo ngõ ra rồi chỉnh setOscillatorFrequency() (thường 23–27 MHz) đến khi đúng 50 Hz; không có thiết bị đo thì hiệu chỉnh US_MIN, US_MAX cho từng servo.

7. Điều khiển servo bằng ESP32 với thư viện ESP32Servo

Thư viện Servo.h cài từ Library Manager không khai báo hỗ trợ ESP32, nên cách thông dụng là cài thư viện ESP32Servo; bản mới tương thích Arduino ESP32 core 3.x, dùng bộ LEDC phần cứng nên xung rất ổn định, không bị ảnh hưởng bởi WiFi hay Bluetooth. Số servo tối đa phụ thuộc chip: ESP32 thường 16 kênh, ESP32-S3 tới 20, ESP32-S2 8, ESP32-C3 6.

#include <ESP32Servo.h>

Servo servo;
const int SERVO_PIN = 18;
const int POT_PIN = 34;      // chân ADC1, chỉ vào

void setup() {
  ESP32PWM::allocateTimer(0);
  servo.setPeriodHertz(50);              // chu kỳ servo chuẩn 50 Hz
  servo.attach(SERVO_PIN, 500, 2400);    // dải xung, chỉnh theo servo
}

void loop() {
  int raw = analogRead(POT_PIN);         // 0 .. 4095
  servo.write(map(raw, 0, 4095, 0, 180));
  delay(20);
}

Những điểm khác biệt khi dùng ESP32

  • Nên dùng các chân 2, 4, 12–19, 21–23, 25–27, 32–33 cho servo; tránh chân 6–11 (nối bộ nhớ flash) và các chân chỉ vào 34–39.
  • ADC của ESP32 là 12 bit, nên map() từ 0–4095. Biến trở nối vào 3,3 V, không nối 5 V.
  • Mức tín hiệu 3,3 V điều khiển được phần lớn servo SG90, MG996R; servo vẫn cấp nguồn 5–6 V riêng, chung GND với ESP32.
  • ESP32 nhạy với sụt áp: servo khởi động kéo nguồn xuống là Serial báo "Brownout detector was triggered" và chip khởi động lại. Không lấy nguồn servo từ chân 3V3 hay 5V của bo ESP32.
  • ESP32 có sẵn WiFi và Bluetooth nên điều khiển servo không dây không cần module HC-05.

8. Điều khiển servo quay liên tục 360 độ bằng Arduino

Servo 360 độ (bản quay liên tục của SG90, MG996R, FS90R) không có cữ chặn và không còn phản hồi vị trí trục. Lệnh từ Arduino lúc này là tốc độ và chiều quay: khoảng 1.500 µs là đứng yên, nhỏ hơn quay một chiều, lớn hơn quay chiều ngược lại. Với Servo.h, write(90) tương ứng dừng, write(0) và write(180) là tốc độ tối đa hai chiều.

Lỗi hay gặp nhất là gửi write(90) mà servo vẫn bò chậm, vì điểm dừng mỗi con lệch vài chục micro giây. Xoay biến trở chỉnh điểm 0 (lỗ nhỏ trên vỏ, nếu có) trong lúc Arduino phát 1.500 µs, hoặc dò bằng chương trình Serial ở mục 4e rồi khai báo vào code dưới:

#include <Servo.h>

Servo wheel;
int stopUs = 1500;                // chỉnh theo thực tế để servo đứng yên

void setSpeed(int percent) {      // -100 .. 100
  percent = constrain(percent, -100, 100);
  wheel.writeMicroseconds(stopUs + percent * 5);
}

void setup() {
  wheel.attach(9, 1000, 2000);
  setSpeed(0);
}

void loop() {
  setSpeed(60);  delay(2000);     // quay thuận 60 %
  setSpeed(0);   delay(1000);
  setSpeed(-60); delay(2000);     // quay ngược 60 %
  setSpeed(0);   delay(1000);
}

Servo 360 độ không biết mình đã quay bao nhiêu vòng; cần chạy theo quãng đường thì phải gắn thêm encoder hoặc cảm biến đếm vòng. Với tải nặng chạy liên tục như băng tải nhỏ, cửa cuốn mô hình, động cơ giảm tốc mini kèm mạch điều tốc bền hơn nhiều so với servo RC độ thành quay liên tục.

9. Arduino bị reset, servo giật, kêu: nguyên nhân và cách xử lý

Các hiện tượng thường gặp khi điều khiển động cơ servo bằng Arduino, sắp theo tần suất:

Hiện tượngNguyên nhân thường gặpCách xử lý
Arduino tự reset, Serial in lại dòng khởi động mỗi khi servo chạySụt áp do servo lấy nguồn từ bo hoặc nguồn yếuNguồn riêng đủ dòng, nối chung GND, tụ 470–1.000 µF
Servo giật, chạy lung tung, rung tại chỗThiếu dây GND chung, dây cắm breadboard tiếp xúc kémKiểm tra dây GND, hàn hoặc dùng cầu đấu
Servo giật về 90° khi mới cấp điệnattach() phát ngay xung 1.500 µs mặc địnhGọi write(goc) trước attach()
Servo kêu rè ở đầu hành trình, nóngXung vượt hành trình cơ khí, tì vào cữ chặnThu hẹp min/max trong attach()
Servo rung khi có dữ liệu Bluetooth, GPS, LED WS2812Thư viện tắt ngắt làm lệch xungXem mục 3
Servo không quay, chỉ nóngSai dây tín hiệu, nguồn quá thấp, mẻ bánh răngKiểm tra dây, đo điện áp lúc tải

Cách kiểm tra nhanh lỗi nguồn

Thêm Serial.println("Khoi dong"); vào đầu setup(): dòng này lặp lại mỗi lần servo chạy nghĩa là bo đang reset do sụt áp. Đo điện áp giữa dây đỏ và nâu ngay đầu cắm servo khi đang mang tải; dưới 4,5 V là nguồn hoặc dây dẫn không đạt.

Có nên detach() để servo bớt kêu

detach() làm servo thôi giữ vị trí nên hết kêu, nhưng trục có thể bị tải kéo lệch. Chỉ dùng khi cơ cấu tự giữ được vị trí (chốt cửa, cơ cấu tự hãm). Servo kêu khi đứng giữa hành trình thường do kẹt cơ khí hoặc nguồn yếu; detach chỉ che giấu triệu chứng.

Khi servo RC không còn đáp ứng được

Servo RC phù hợp mô hình, robot nhỏ, mô-men vài chục kg.cm. Máy chạy liên tục nhiều giờ, tải nặng hoặc cần định vị chính xác lặp lại nên chuyển sang động cơ servo AC công nghiệp với driver riêng. Arduino vẫn có thể ra lệnh cho driver bằng tín hiệu xung và hướng; ví dụ một bộ motor servo 400W dùng encoder 2.500 xung/vòng, đủ cho trục máy CNC hay băng tải định vị cỡ nhỏ.

10. Câu hỏi thường gặp về điều khiển động cơ servo bằng Arduino

Có thể cấp nguồn cho servo trực tiếp từ chân 5V của Arduino không?

Chỉ với một servo nhỏ như SG90 chạy không tải để thử. Từ hai servo trở lên hoặc với MG996R, DS3218, dòng khởi động vượt khả năng của cổng USB và IC ổn áp trên bo, gây reset; hãy dùng nguồn 5–6 V riêng, nối chung GND.

Arduino Uno điều khiển được tối đa bao nhiêu servo?

12 servo với Servo.h; Arduino Mega được 48. Thực tế giới hạn thường nằm ở nguồn chứ không phải số kênh; cần nhiều hơn thì dùng PCA9685, mỗi module 16 kênh, ghép được nhiều module.

Servo có bắt buộc phải nối vào chân PWM có dấu ~ không?

Không. Servo.h tạo xung bằng ngắt timer nên chân digital nào cũng dùng được, kể cả A0–A5. Ngược lại, khi đã dùng Servo.h trên Uno thì chân 9 và 10 mất chức năng analogWrite(), nên chuyển LED hay chân điều tốc motor sang chân 3, 5, 6, 11.

Hàm write() và writeMicroseconds() khác nhau thế nào?

write() nhận góc 0–180 và tự quy đổi sang xung theo dải min/max khai báo; writeMicroseconds() nhận thẳng độ rộng xung. Dùng micro giây khi cần chuyển động mịn, khi hiệu chỉnh hành trình và khi điều khiển servo 360 độ. Lưu ý giá trị đưa vào write() từ 544 trở lên sẽ được hiểu là micro giây.

Vì sao servo không quay đủ 180 độ dù đã write(0) và write(180)?

Vì dải xung mặc định 544–2.400 µs không khớp hành trình thật của servo đó. Dò hai giá trị giới hạn bằng chương trình Serial rồi khai báo vào attach(pin, min, max); một số servo giá rẻ về cơ khí chỉ quay được 160–170°.

Vì sao servo giật mạnh mỗi khi bật nguồn hoặc nhấn reset?

Vì attach() phát ngay xung trung tâm 1.500 µs, servo lao về 90° rồi mới chạy tới góc lệnh. Gọi write() với góc mong muốn trước attach(), hoặc dùng thư viện ServoEasing khai báo góc ban đầu trong attach().

Nên lắp tụ bao nhiêu µF cho servo?

Thường dùng 470–1.000 µF, điện áp tụ từ 10 V trở lên, đặt sát hàng nguồn servo; tính sơ bộ khoảng 100 µF cho mỗi servo.

Làm sao chỉnh tốc độ quay của servo bằng Arduino?

Phải chia chuyển động thành nhiều bước nhỏ, vì servo RC luôn chạy hết tốc độ tới đích. Tính vị trí trung gian theo millis() như mục 5 hoặc dùng setSpeed() của thư viện ServoEasing.

ESP32 có dùng thư viện Servo.h của Arduino được không?

Thông thường là không: bản Servo.h cài từ Library Manager không khai báo hỗ trợ ESP32. Hãy cài thư viện ESP32Servo, cú pháp gần như giống hệt, thêm setPeriodHertz(50) và nên khai báo dải xung trong attach(). Arduino Uno R4 và các bo AVR dùng Servo.h bình thường.

Vì sao servo rung khi điều khiển bằng Bluetooth HC-05?

Thường do SoftwareSerial tắt ngắt khi nhận dữ liệu làm xung servo lệch. Chuyển HC-05 sang Serial phần cứng chân 0, 1, dùng bo nhiều UART như Mega, hoặc tạo xung servo bằng PCA9685.

Làm sao để servo 360 độ dừng hẳn khi điều khiển bằng Arduino?

Tìm đúng độ rộng xung mà servo đứng yên (gần 1.500 µs) bằng cách dò từng 5–10 µs, rồi dùng giá trị đó làm điểm dừng; hoặc chỉnh biến trở điểm 0 trên servo. Khi cần dừng tuyệt đối, gọi detach() để ngừng phát xung.

Arduino Nano điều khiển servo có khác Arduino Uno không?

Không khác. Nano dùng cùng chip ATmega328P nên code, giới hạn 12 servo và việc mất PWM chân 9, 10 giống hệt Uno; IC ổn áp trên Nano nhỏ hơn nên càng không nên lấy nguồn servo từ bo.

11. Kết luận

Điều khiển động cơ servo bằng Arduino chỉ ổn định khi phần nguồn làm đúng: nguồn riêng đủ dòng, GND nối chung, tụ bù gần servo. Về phần mềm, hãy hiệu chỉnh dải xung bằng attach(pin, min, max), ra lệnh vị trí trước khi attach, dùng millis() hoặc easing để chạy mượt và nhớ các giới hạn của Servo.h. Khi số servo tăng, PCA9685 gánh việc tạo xung; khi cần không dây, ESP32 với ESP32Servo là lựa chọn gọn. Các mẫu code trong bài có thể ghép lại thành cánh tay robot, giá camera pan–tilt, khóa cửa hay mô hình chuyển động theo ý muốn.

4.847 reviews

Tin tức liên quan

Hộp giảm tốc harmonic: thông số, model CSF, SHF và cách chọn

Hộp giảm tốc harmonic: thông số, model CSF, SHF và cách chọn

14 thg 10 2026 15:12

Hộp giảm tốc harmonic (strain wave) size 14 – 32, tỉ số 1/50 – 1/120, mô men 5,4 – 137 Nm. Cách đọc tem, phân loại CSF, CSG, SHF-2UH, SHF-2UJ, SHF-2SO, SHD-2SH và cách chọn theo servo.
Đọc tiếp
Khám phá Bí Quyết Điều Khiển Động Cơ RC Servo Chính Xác Bằng STM32F4

Khám phá Bí Quyết Điều Khiển Động Cơ RC Servo Chính Xác Bằng STM32F4

15 thg 4 2025 20:44

Điều khiển động cơ RC Servo bằng STM32F4 mang đến độ chính xác cao và khả năng tùy chỉnh tín hiệu PWM tối ưu. Bài viết này hướng dẫn từ cấu hình bộ định thời, lập trình tín hiệu PWM, đến tối ưu hóa hiệu suất động cơ. Đây là giải pháp lý tưởng cho kỹ sư điện, kỹ sư cơ khí và các doanh nghiệp công nghiệp.
Đọc tiếp
Điều khiển động cơ RC servo bằng PWM: chu kỳ, độ rộng xung và cách tạo xung bằng timer

Điều khiển động cơ RC servo bằng PWM: chu kỳ, độ rộng xung và cách tạo xung bằng timer

10 thg 10 2026 17:36

Điều khiển động cơ RC servo bằng PWM: chu kỳ 20 ms, độ rộng xung 500–2500 µs, tính prescaler cho AVR, STM32, ESP32, PCA9685, mạch 555 và cách đo kiểm.
Đọc tiếp
Cách Điều Khiển Động Cơ DC Servo Chính Xác Bằng Giải Thuật PID

Cách Điều Khiển Động Cơ DC Servo Chính Xác Bằng Giải Thuật PID

12 thg 10 2026 08:32

Điều khiển động cơ DC Servo bằng giải thuật PID mang lại sự ổn định và hiệu suất cao cho hệ thống công nghiệp. Tìm hiểu cách áp dụng Proportional-Integral-Derivative, vi điều khiển và encoder để tối ưu hóa tốc độ và vị trí của động cơ. Phương pháp này được sử dụng rộng rãi trong robot, dây chuyền sản xuất và các hệ thống tự động hóa.
Đọc tiếp
Điều khiển động cơ DC servo: vòng dòng – tốc độ – vị trí, PID, encoder và cầu H

Điều khiển động cơ DC servo: vòng dòng – tốc độ – vị trí, PID, encoder và cầu H

12 thg 10 2026 08:32

Hướng dẫn điều khiển động cơ DC servo: 3 vòng dòng – tốc độ – vị trí, đọc encoder x4, cầu H BTS7960/VNH5019, PWM 20 kHz, chỉnh PID và code Arduino mẫu.
Đọc tiếp