PID trong một hệ robot: không chỉ một vòng lặp, mà nhiều vòng lồng nhau
Atlasbot · 17/8/2026 · 11 phút đọc
Bài PID là gì? (chuyên mục Lập trình nhúng) đã giải thích chi tiết ba thành phần P/I/D, code C, và quy trình tuning tay cho một vòng lặp PID cụ thể — giữ tốc độ động cơ. Bài này không lặp lại nội dung đó, mà đặt PID đúng vị trí của nó trong bức tranh lớn hơn: một robot hoàn chỉnh hiếm khi chỉ có một vòng PID — mà là nhiều vòng lồng nhau, mỗi vòng phụ trách một tầng khác nhau.
Khái niệm chính
Bài Sense-Think-Act mô tả vòng lặp tổng quát Cảm biến→Xử lý→Điều khiển; bài Điều khiển vòng kín mô tả nguyên lý closed-loop tổng quát. PID chính là thuật toán cụ thể nhất, phổ biến nhất để hiện thực hoá bước "so sánh → sửa sai" đó — không phải khái niệm riêng biệt, mà là một lựa chọn thuật toán trong khuôn khổ closed-loop.
Nguyên lý hoạt động
Ví dụ thực tế trong một robot AMR: bộ điều khiển Nav2 (tầng cao) không trực tiếp set PWM — nó tính ra vận tốc mong muốn (v, ω), gửi xuống tầng thấp hơn qua /cmd_vel. Tầng thấp đó lại chạy một vòng PID riêng để giữ đúng tốc độ bánh xe theo lệnh vừa nhận. Đây là mô hình cascade control (điều khiển tầng bậc) — output của vòng lặp cấp cao là setpoint đầu vào cho vòng lặp cấp thấp hơn:
Nav2 controller_server (10-50Hz)
│ tính v, ω mong muốn để bám path, né vật cản
▼
/cmd_vel
│
▼
PID velocity control trên MCU (100Hz-1kHz)
│ giữ tốc độ từng bánh bám đúng v, ω vừa nhận
▼
PWM → Motor
Tóm lại: Vòng ngoài (Nav2) không cần biết gì về PWM hay đặc tính động cơ. Vòng trong (PID trên MCU) không cần biết gì về path planning hay costmap. Tách tầng giúp mỗi vòng PID đơn giản, dễ tune độc lập, dù toàn hệ thống rất phức tạp.
Về lý thuyết có thể tưởng tượng một bộ điều khiển "khổng lồ" nhận thẳng dữ liệu LiDAR, tính thẳng ra PWM từng bánh — nhưng cách này gần như không ai làm trong thực tế, vì tần số khác nhau quá xa giữa các tầng, và debug dễ hơn nhiều khi tách rời.
Kiến thức kỹ thuật
Công thức
Quy tắc kinh nghiệm phổ biến để cascade control ổn định: vòng trong phải chạy nhanh hơn đáng kể so với vòng ngoài, thường tối thiểu gấp 5-10 lần:
Tần số vòng trong ≥ 5 đến 10 × Tần số vòng ngoài
VD: vòng ngoài (Nav2) chạy 20Hz
Vòng trong (PID tốc độ) nên chạy tối thiểu 100-200Hz để đủ "theo kịp"
Nếu vòng trong quá chậm so với vòng ngoài, setpoint mới từ vòng ngoài
liên tục thay đổi trước khi vòng trong kịp ổn định theo setpoint cũ
→ dao động/mất ổn định toàn hệ thống, dù từng vòng riêng lẻ tune đúng
Thông số
Mở rộng ví dụ cascade 2 tầng (Nav2 → PID tốc độ) thành mô hình 3 tầng phổ biến trong servo drive công nghiệp:
| Tầng | Biến điều khiển | Tần số điển hình |
|---|---|---|
| Vòng vị trí (ngoài cùng) | Vị trí trục | 10 – 100Hz |
| Vòng vận tốc (giữa) | Tốc độ quay | 100Hz – 1kHz |
| Vòng dòng điện (trong cùng) | Dòng điện qua cuộn dây động cơ | 1 – 20kHz |
Ứng dụng thực tế
Mô hình cascade 3 tầng ở bảng trên là kiến trúc chuẩn của hầu hết servo drive công nghiệp điều khiển tay máy: vòng dòng điện (trong cùng, nhanh nhất) đảm bảo động cơ sinh đúng lực/momen yêu cầu tức thời; vòng vận tốc (giữa) dùng output đó để giữ đúng tốc độ quay mong muốn; vòng vị trí (ngoài cùng, chậm nhất) dùng vòng vận tốc để đưa trục tới đúng vị trí đích. Ba tầng này được tune độc lập theo thứ tự từ trong ra ngoài — vòng dòng điện phải ổn định trước, sau đó mới tune vòng vận tốc dựa trên vòng dòng điện đã ổn định, cuối cùng mới tune vòng vị trí — tune sai thứ tự (từ ngoài vào trong) gần như chắc chắn gây mất ổn định vì vòng ngoài "dựa" vào một vòng trong chưa đáng tin cậy.
Ví dụ thực tế
# Minh hoạ cascade 2 tầng đơn giản: vòng ngoài ra setpoint cho vòng trong
target_position = 10.0
current_position = 0.0
current_velocity = 0.0
Kp_outer = 0.5 # vòng vị trí (ngoài) - chạy chậm hơn
Kp_inner = 2.0 # vòng vận tốc (trong) - chạy nhanh hơn nhiều
for step in range(5):
# Vòng ngoài: sai số vị trí -> ra setpoint vận tốc mong muốn
pos_error = target_position - current_position
velocity_setpoint = Kp_outer * pos_error
# Vòng trong: sai số vận tốc -> ra lực điều khiển thực tế
vel_error = velocity_setpoint - current_velocity
control_output = Kp_inner * vel_error
current_velocity += control_output * 0.1
current_position += current_velocity * 0.1
print(f"Step {step}: pos={current_position:.2f}, vel={current_velocity:.2f}")
Output:
Step 0: pos=0.10, vel=1.00
Step 1: pos=0.42, vel=3.15
Step 2: pos=0.99, vel=5.68
Step 3: pos=1.86, vel=8.72
Step 4: pos=3.06, vel=12.14
Vòng ngoài (vị trí) liên tục cập nhật setpoint vận tốc dựa trên sai số còn lại; vòng trong (vận tốc) chạy theo setpoint đó — minh hoạ đúng cấu trúc cascade dù đây chỉ là mô phỏng rất đơn giản hoá (thực tế cần cả I/D và tần số 2 vòng khác biệt lớn hơn nhiều).
Lỗi thường gặp
- Tune vòng ngoài trước khi vòng trong ổn định — như đã nói ở phần Ứng dụng thực tế, luôn tune từ trong ra ngoài — tune vòng vị trí khi vòng vận tốc bên dưới còn dao động khiến việc tune trở nên vô nghĩa vì nền tảng nó dựa vào chưa ổn định.
- Vòng trong không đủ nhanh so với vòng ngoài — vi phạm quy tắc 5-10 lần đã nêu ở mục Công thức, gây dao động/mất ổn định dù từng vòng riêng lẻ tune đúng.
- Integral windup lan truyền qua nhiều tầng — nếu vòng trong bị bão hoà (saturated) kéo dài, phần tích luỹ (I) của vòng ngoài tiếp tục cộng dồn dựa trên sai số không thể thu hẹp được, gây vọt lố nghiêm trọng khi vòng trong cuối cùng thoát bão hoà — cần cơ chế chống windup ở cả 2 tầng, không chỉ tầng trong.
- Setpoint vòng ngoài đổi đột ngột, nhanh hơn khả năng đáp ứng của vòng trong — gây phản ứng giật cục dù mỗi vòng PID riêng lẻ đã tune mượt — cần giới hạn tốc độ thay đổi setpoint (rate limiting) ở vòng ngoài, chủ đề của bài Giới hạn gia tốc tiếp theo trong chuyên mục.
Kết luận
PID không phải một khối duy nhất mà là building block có thể lồng nhiều tầng — mỗi tầng tách biệt theo tần số và trách nhiệm, giúp hệ thống phức tạp vẫn dễ tune và debug độc lập từng phần. Luôn tune từ vòng trong ra vòng ngoài, và đảm bảo đủ khoảng cách tần số giữa các tầng để tránh mất ổn định. Bài tiếp theo trong chuyên mục nói về Velocity Control — đi sâu vào chính vòng PID tốc độ vừa nhắc tới trong ví dụ cascade ở trên.