Đ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
Đ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.
Nội dung
- 1. Servo RC và Arduino làm việc với nhau như thế nào
- 2. Đấu nối và cấp nguồn khi điều khiển servo bằng Arduino
- 3. Thư viện Servo.h: các hàm cần biết và giới hạn
- 4. Code mẫu điều khiển động cơ servo bằng Arduino
- 5. Cho servo chạy mượt: millis() thay delay() và easing
- 6. Điều khiển nhiều servo bằng module PCA9685
- 7. Điều khiển servo bằng ESP32 với thư viện ESP32Servo
- 8. Điều khiển servo quay liên tục 360 độ bằng Arduino
- 9. Arduino bị reset, servo giật, kêu: nguyên nhân và cách xử lý
- 10. Câu hỏi thường gặp về điều khiển động cơ servo bằng Arduino
- 11. Kết luận
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ệc | Mô-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 V | khoảng 1,8 kg.cm @4,8 V | dưới 1 A | 5 V – 1 A |
| MG90S (bánh răng kim loại) | 4,8–6 V | 1,8–2,2 kg.cm | dưới 1 A | 5 V – 1 A |
| MG996R | 4,8–7,2 V | 9,4 kg.cm @4,8 V; 11 kg.cm @6 V | khoảng 2,5 A @6 V | 5–6 V – 3 A |
| DS3218 (servo số) | 4,8–6,8 V | khoảng 20 kg.cm | khoảng 1,8–2,2 A, có lô cao hơn | 6–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 servo | Nối tới | Ghi chú |
| Tín hiệu (cam/vàng/trắng) | Một chân digital bất kỳ của Arduino, ví dụ D9 | Khô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 V | Chỉ 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 Arduino | Thiế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.

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àm | Chức năng | Lưu ý thực tế |
attach(pin) | Gắn servo vào chân, dải xung mặc định 544–2.400 µs | Gọ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–180 | Giá 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 xung | Bị giới hạn trong khoảng min–max đã khai báo ở attach |
read() | Trả về góc lệnh gần nhất | Không đọc được vị trí thật của trục |
detach() | Ngừng phát xung trên chân | Servo 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 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ượng | Nguyên nhân thường gặp | Cách xử lý |
| Arduino tự reset, Serial in lại dòng khởi động mỗi khi servo chạy | Sụt áp do servo lấy nguồn từ bo hoặc nguồn yếu | Nguồ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ém | Kiể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ện | attach() phát ngay xung 1.500 µs mặc định | Gọi write(goc) trước attach() |
| Servo kêu rè ở đầu hành trình, nóng | Xung vượt hành trình cơ khí, tì vào cữ chặn | Thu hẹp min/max trong attach() |
| Servo rung khi có dữ liệu Bluetooth, GPS, LED WS2812 | Thư viện tắt ngắt làm lệch xung | Xem mục 3 |
| Servo không quay, chỉ nóng | Sai dây tín hiệu, nguồn quá thấp, mẻ bánh răng | Kiể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.






