Điều khiển tốc độ động cơ: từ open-loop tới closed-loop với PID
Atlasbot · 16/8/2026 · 11 phút đọc
Ba bài trước đã tách riêng từng mảnh: DC motor cần driver để chạy, encoder đo được tốc độ thực, PID biết cách tính toán điều chỉnh dựa trên sai số. Bài này ghép cả ba lại thành một vòng điều khiển tốc độ hoàn chỉnh — thứ nằm ở lõi của mọi bánh xe AMR di chuyển ổn định.
Khái niệm chính
Có hai cách điều khiển tốc độ động cơ, khác nhau ở một điểm duy nhất: có dùng phản hồi (feedback) hay không.
Open-loop — chỉ đặt PWM, không kiểm tra kết quả
Cách đơn giản nhất: quyết định một giá trị PWM cố định tương ứng với "tốc độ mong muốn", gửi thẳng cho driver, và tin rằng động cơ sẽ quay đúng tốc độ đó.
set_motor_pwm(600); // "Hy vọng" động cơ quay khoảng 60% tốc độ tối đa
Vấn đề: tốc độ quay thực tế phụ thuộc cả vào điện áp pin (giảm dần khi pin yếu) và tải (robot leo dốc hay chạy trên sàn phẳng) — cùng một giá trị PWM, tốc độ thực tế sẽ khác nhau tuỳ điều kiện. Open-loop chấp nhận sự thiếu chính xác này để đổi lấy sự đơn giản.
Closed-loop — đo tốc độ thật, tự điều chỉnh liên tục
Cách chính xác hơn: dùng encoder đo tốc độ thực tế, đưa vào PID để tự động tính lại giá trị PWM mỗi chu kỳ, sao cho tốc độ đo được luôn bám sát tốc độ mong muốn — bất kể tải hay điện áp pin thay đổi ra sao.
Tóm lại: Open-loop rẻ và đơn giản, chấp nhận sai số theo tải · Closed-loop (encoder + PID) phức tạp hơn một chút nhưng giữ tốc độ ổn định thực sự — gần như bắt buộc cho bánh xe AMR, nơi tải trọng và độ dốc sàn nhà thay đổi liên tục trong lúc vận hành.
Nguyên lý hoạt động
Vòng lặp điều khiển tốc độ hoàn chỉnh, chạy định kỳ trong ngắt Timer (ví dụ mỗi 10ms = 100Hz):
void speed_control_loop(void) { // Gọi định kỳ trong ngắt Timer
int32_t pulses = __HAL_TIM_GET_COUNTER(&htim2);
__HAL_TIM_SET_COUNTER(&htim2, 0); // Đọc & reset bộ đếm encoder
float measured_speed = pulses_to_rpm(pulses); // Quy đổi số xung → vòng/phút
float error = target_speed - measured_speed;
integral += error * DT;
float derivative = (error - prev_error) / DT;
float pwm = Kp * error + Ki * integral + Kd * derivative;
pwm = constrain(pwm, -1000, 1000); // Giới hạn trong dải PWM hợp lệ
set_motor_pwm(pwm);
prev_error = error;
}
Sàn phẳng, tải nhẹ: PWM ổn định ở mức thấp để giữ đúng tốc độ mục tiêu
Leo dốc, tải nặng: PID tự động TĂNG PWM để bù lại, tốc độ vẫn giữ ổn định
Xuống dốc: PID tự động GIẢM PWM, tránh vọt tốc độ
Kiến thức kỹ thuật
Công thức
So sánh định lượng sai số tốc độ giữa 2 cách tiếp cận khi tải thay đổi đột ngột (VD: robot bắt đầu leo dốc):
Sai số open-loop (%) = |tốc độ thực tế - tốc độ mong muốn| / tốc độ mong muốn × 100%
VD: PWM cố định cho tốc độ mong muốn 100 RPM trên sàn phẳng
Khi leo dốc, tốc độ thực tế tụt còn 70 RPM (PWM không đổi, tải tăng)
Sai số open-loop = |70 - 100| / 100 × 100% = 30%
Closed-loop (PID chỉnh tốt): sau thời gian ổn định (settling time, thường vài trăm ms
đến vài giây tuỳ hệ thống), sai số thường giữ được dưới 1-5%
Thông số
Bảng so sánh nhanh giúp quyết định khi nào thực sự cần đầu tư thêm encoder + PID:
| Tiêu chí | Open-loop | Closed-loop (encoder + PID) |
|---|---|---|
| Độ chính xác khi tải đổi | Thấp (sai số có thể 20-30%+) | Cao (thường dưới vài % sau khi ổn định) |
| Chi phí phần cứng | Thấp (không cần encoder) | Cao hơn (cần encoder + xử lý thêm) |
| Độ phức tạp code | Rất đơn giản | Cần tuning Kp/Ki/Kd |
| Phù hợp cho | Quạt tản nhiệt, tải cố định | Bánh xe AMR, tải thay đổi liên tục |
Ứng dụng thực tế
Ngoài việc giữ tốc độ ổn định, có sẵn vòng closed-loop còn mở ra khả năng phát hiện trượt bánh (wheel slip) — nếu PID phải liên tục tăng PWM lên gần mức tối đa mà tốc độ đo được từ encoder vẫn không tăng tương ứng, đây là dấu hiệu bánh xe đang trượt trên sàn trơn hoặc treo khỏi mặt đất (VD: một bánh đang ở mép bậc thềm) — thông tin này có thể dùng làm tín hiệu cảnh báo an toàn, điều mà hệ thống open-loop hoàn toàn không có khả năng phát hiện vì nó không bao giờ biết tốc độ thực tế là bao nhiêu.
Ví dụ thực tế
#include <stdio.h>
int main() {
float target = 100.0; // RPM mong muốn
float pwm_openloop = 600; // giá trị PWM cố định tương ứng 100 RPM trên sàn phẳng
// Mô phỏng đơn giản: mỗi đơn vị PWM tạo ra tốc độ khác nhau tuỳ tải
float speed_flat = pwm_openloop * 100.0 / 600.0; // sàn phẳng: đúng như thiết kế
float speed_incline_openloop = speed_flat * 0.7; // leo dốc: tụt 30% (open-loop bất lực)
// Closed-loop: PID tự tăng PWM bù lại, giả định đạt lại ~98% target sau khi ổn định
float speed_incline_closedloop = target * 0.98;
printf("Sàn phẳng (open-loop): %.1f RPM\n", speed_flat);
printf("Leo dốc (open-loop): %.1f RPM (sai so %.0f%%)\n",
speed_incline_openloop, (target - speed_incline_openloop)/target*100);
printf("Leo dốc (closed-loop PID): %.1f RPM (sai so %.0f%%)\n",
speed_incline_closedloop, (target - speed_incline_closedloop)/target*100);
return 0;
}
Output:
San phang (open-loop): 100.0 RPM
Leo doc (open-loop): 70.0 RPM (sai so 30%)
Leo doc (closed-loop PID): 98.0 RPM (sai so 2%)
Kết quả minh hoạ đúng bảng so sánh ở mục Kiến thức kỹ thuật — cùng gặp tải thay đổi (leo dốc), open-loop chấp nhận sai số 30%, trong khi closed-loop PID tự điều chỉnh để giữ sai số chỉ còn khoảng 2%.
Lỗi thường gặp
- Dùng open-loop cho ứng dụng tải thay đổi liên tục — chọn sai công cụ cho bài toán, như minh hoạ ở trên; open-loop chỉ phù hợp khi tải gần như cố định (VD: quạt tản nhiệt), không phù hợp cho bánh xe robot di chuyển trên địa hình thay đổi.
- Không xử lý tràn số bộ đếm encoder — thanh ghi đếm encoder trên nhiều MCU chỉ 16-bit, nếu không đọc và reset đều đặn (như dòng
__HAL_TIM_SET_COUNTER(&htim2, 0)ở ví dụ trên), số đếm có thể tràn giữa 2 lần đọc, gây tính sai tốc độ ở đúng thời điểm đó. - Tần số vòng lặp điều khiển không ổn định — nếu
speed_control_loop()không chạy đúng chu kỳ cố định (VD: bị chèn bởi ngắt khác ưu tiên cao hơn, hoặc đặt trongloop()thay vì ngắt Timer), giá trịDTgiả định trong code sẽ không khớp thời gian thực tế trôi qua, làm sai lệch cả phần I lẫn D của PID. - Bỏ qua bước tuning khi đổi động cơ/tải trọng robot — bộ Kp/Ki/Kd đã chỉnh tốt cho 1 động cơ/tải cụ thể không tự động phù hợp khi đổi sang động cơ khác hoặc tải trọng robot thay đổi đáng kể (VD: thêm hàng hoá lên robot chở hàng) — cần kiểm tra và chỉnh lại nếu thay đổi phần cứng đáng kể.
Kết luận
Kết hợp DC motor, encoder, và PID thành vòng điều khiển closed-loop hoàn chỉnh là nền tảng cho bất kỳ bánh xe AMR nào cần di chuyển ổn định qua địa hình và tải trọng thay đổi — đánh đổi bằng chi phí phần cứng và công sức tuning cao hơn open-loop, nhưng gần như bắt buộc cho ứng dụng thực tế. Đây cũng là bài cuối cùng của chuyên mục Điều khiển động cơ, khép lại toàn bộ category Lập trình nhúng (Embedded) — từ Kiến thức nền tảng, Linh kiện điện tử, Vi điều khiển, Lập trình C/C++, Giao tiếp phần cứng, tới Điều khiển động cơ vừa học, đã đủ nền tảng để bắt tay vào xây dựng tầng điều khiển thời gian thực hoàn chỉnh cho một robot AMR thực tế.