Bạn muốn tự viết một controller cho robot arm công nghiệp, nhưng mở tài liệu ra thì gặp ngay một rừng từ khóa: FK, IK, Jacobian, MoveJ, MoveL, MoveC, trajectory, interpolation, servo, driver, ROS 2 control, MoveIt 2, Ruckig, Pinocchio, KDL. Nếu bắt đầu từ một thư viện lớn như MoveIt, bạn dễ chạy được demo nhưng không hiểu tại sao robot đi như vậy. Nếu bắt đầu từ công thức toán, bạn dễ hiểu từng mảnh nhưng không biết làm sao biến nó thành lệnh chạy mượt trên robot thật.
Series này đi theo hướng giữa: classical robot arm controller, tức bộ điều khiển dựa trên mô hình hình học, động học, ràng buộc joint, profile vận tốc, interpolation và feedback servo. Đây là nền tảng của hầu hết robot arm công nghiệp, từ UR, ABB, FANUC, KUKA đến Franka Panda. Nó khác với hướng learning hoặc VLA ở chỗ ta không học trực tiếp mapping từ ảnh/ngôn ngữ sang action. Ta mô tả robot bằng model, lập kế hoạch chuyển động bằng thuật toán có thể kiểm chứng, rồi gửi setpoint xuống servo loop theo chu kỳ cố định.
Điều đó không có nghĩa learning/VLA không quan trọng. Những bài như VLA models trong robotics rất mạnh khi cần hiểu task từ camera, ngôn ngữ, demonstration hoặc dữ liệu lớn. Nhưng khi end-effector đã có mục tiêu "đi tới pose này", "đi theo đường thẳng này", "jog theo trục tool", robot vẫn cần một controller cổ điển để biến mục tiêu đó thành chuỗi joint command an toàn, mượt, đúng giới hạn. Nói ngắn gọn: VLA có thể là não chiến lược, classical controller là hệ thần kinh vận động.

Nguồn ảnh: MoveIt documentation. Sơ đồ này cho thấy user interface, planning scene, controller và robot sensor đi qua một node tích hợp như move_group.
Roadmap series
Đây là bài 1/15 của series Robot Arm Controller cổ điển (C++ & Python): FK/IK đến MoveJ, MoveL, Jogging và Motion Planning. Các slug dưới đây là lộ trình đầy đủ để bạn đọc theo thứ tự:
- robot-arm-classical-controller-1-roadmap - Bản đồ controller stack, triết lý học, công cụ và hệ sinh thái thư viện.
- robot-arm-classical-controller-2-cpp-cmake-setup - Setup C++17/20, CMake, Eigen, unit test và cấu trúc project controller.
- robot-arm-classical-controller-3-robot-model - Robot model, URDF, joint limit, frame tree, link transform và data structure.
- robot-arm-classical-controller-4-forward-kinematics - FK, IK, Jacobian, singularity và kiểm chứng bằng Python.
- robot-arm-classical-controller-5-inverse-kinematics - IK số: damped least squares, task priority, limit handling, Pinocchio/pink/mink.
- robot-arm-classical-controller-6-jacobian - Chủ đề nâng cao: redundancy, null-space, force-aware planning, nhiều constraint cùng lúc.
- robot-arm-classical-controller-7-frames - Base frame, tool frame, world frame, jogging trong joint/cartesian mode.
- robot-arm-classical-controller-8-movej - MoveJ và MoveL: khác nhau ở không gian joint hay không gian Cartesian.
- robot-arm-classical-controller-9-movel - Các loại trajectory: PTP, linear, spline, time-parameterized path.
- robot-arm-classical-controller-10-movec-movep - MoveC, cung tròn, frame chọn điểm giữa, ứng dụng hàn và dispensing.
- robot-arm-classical-controller-11-profiles - Profile vận tốc: trapezoid, S-curve, jerk-limited, Ruckig.
- robot-arm-classical-controller-12-blending - Blending qua waypoint, corner radius, tradeoff giữa chính xác và cycle time.
- robot-arm-classical-controller-13-jogging-servo - Servo loop cho jogging online, singularity scaling, collision stop.
- robot-arm-classical-controller-14-motion-planning - Motion planning có vật cản: sampling, collision checking, MoveIt 2, cuRobo.
- robot-arm-classical-controller-15-ros2-ur-rtde - Tích hợp thành controller thực tế: simulation-first, UR5/Panda,
ur_rtde, logging và safety.
Classical control khác learning/VLA ở đâu?
Một controller cổ điển bắt đầu từ mô hình rõ ràng. Ta biết robot có bao nhiêu joint, mỗi joint có giới hạn vị trí/vận tốc/gia tốc/jerk, mỗi link dài bao nhiêu, tool frame nằm đâu, base frame nằm đâu. Khi user gọi MoveL(pose), controller không "đoán" action bằng neural network. Nó giải một chuỗi bài toán:
- Pose mục tiêu có nằm trong workspace không?
- Có nghiệm IK nào hợp lệ không?
- Đường đi Cartesian có va chạm không?
- Khi biến đường đi thành trajectory theo thời gian, joint nào chạm giới hạn trước?
- Servo loop nên gửi setpoint nào ở chu kỳ 1 ms, 2 ms hoặc 8 ms?
- Nếu robot thật báo lệch vị trí, lỗi tracking, protective stop hoặc emergency stop thì làm gì?
Learning/VLA bắt đầu từ dữ liệu. Ví dụ OpenVLA mô tả VLA như một policy học từ dữ liệu vision-language và demonstration robot, có thể fine-tune cho task mới. Những hệ như vậy rất hợp với nhận thức, task-level policy và hành vi khó viết tay. Nhưng chúng không tự thay thế các lớp hard real-time bên dưới. Một policy có thể chọn "đưa tool tới cạnh trái của vật", còn controller vẫn phải đảm bảo đường đi không vượt joint limit, không rung, không đâm vào fixture và không gửi lệnh quá nhanh cho drive.
Vì vậy người làm robot arm nên biết cả hai. Nếu bạn làm AI robotics, classical control giúp bạn debug action và safety. Nếu bạn làm automation công nghiệp, learning giúp bạn thêm perception và adaptation. Series này tập trung vào phần classical, vì đó là phần bạn cần hiểu trước khi robot thật chạy nhanh bên cạnh con người.
Controller stack: từ lệnh người dùng tới motor
Một robot arm controller không phải một hàm move_to(target). Nó là stack nhiều tầng, mỗi tầng biến một dạng ý định thành một dạng lệnh cụ thể hơn:
User command
MoveJ(q_goal), MoveL(T_goal), MoveC(T_mid, T_goal), Jog(v)
|
v
Motion planner
feasibility, IK search, collision check, path constraints
|
v
Trajectory generator
path -> time law, velocity/acceleration/jerk limits
|
v
Interpolator
sample trajectory at control period: q, dq, ddq
|
v
Servo loop
feedback control, tracking error, safety checks
|
v
Joint driver
position/velocity/torque command to hardware
|
v
Robot arm
encoders, current, force/torque, status feedback
Một sơ đồ Mermaid tương đương:
flowchart TD
A[User API: MoveJ / MoveL / MoveC / Jog] --> B[Command validator]
B --> C[Motion planner]
C --> D[Trajectory generator]
D --> E[Interpolator at control cycle]
E --> F[Servo loop]
F --> G[Joint driver]
G --> H[Robot arm hardware]
H --> I[State feedback: joint state, IO, FT, safety]
I --> F
I --> C
1. User command: MoveJ, MoveL, MoveC, Jog
Đây là API mà người dùng hoặc chương trình cell gọi. MoveJ thường nghĩa là đi từ joint hiện tại tới joint mục tiêu theo đường mượt trong joint space. Tool có thể đi cong trong không gian Cartesian, nhưng joint di chuyển đơn giản và ổn định. MoveL nghĩa là tool đi theo đường thẳng trong Cartesian space từ pose hiện tại tới pose mục tiêu. Controller phải chạy IK dọc đường, vì mỗi điểm trên đường thẳng cần một cấu hình joint hợp lệ. MoveC đi theo cung tròn qua điểm trung gian, hay dùng trong hàn, phun keo, đánh bóng. Jog là điều khiển liên tục theo vận tốc nhỏ: giữ nút +X thì tool trôi theo trục X của base hoặc tool frame.
Điểm beginner hay nhầm: MoveJ và MoveL không chỉ khác cách gọi. Chúng khác không gian ràng buộc. MoveJ ưu tiên joint mượt. MoveL ưu tiên hình học của tool. Vì vậy cùng một target pose, hai lệnh có thể tạo đường đi hoàn toàn khác.
2. Motion planner: tìm đường hợp lệ
Motion planner trả lời câu hỏi: "Có đường nào từ trạng thái A tới B mà không va chạm và không vi phạm constraint không?" Với bài đơn giản không có vật cản, planner có thể chỉ cần kiểm tra IK và nội suy. Với cell thực tế có bàn, fixture, camera, gripper, safety zone, planner cần collision checking và planning scene. MoveIt 2 mô tả nó là nền tảng manipulation cho ROS 2, gồm motion planning, perception 3D, kinematics và control. Trong series này, ta sẽ không bắt đầu bằng MoveIt. Ta sẽ tự viết phiên bản nhỏ trước, rồi dùng MoveIt 2 ở bài motion planning để thấy thư viện lớn giải quyết cùng vấn đề ra sao.

Nguồn ảnh: MoveIt 2 documentation. Hybrid planning chia global planner và local planner để vừa lập kế hoạch tổng thể vừa phản ứng online.
3. Trajectory generator: biến path thành thời gian
Path chỉ nói robot đi qua đâu. Trajectory nói robot đi qua đó khi nào, với vận tốc và gia tốc nào. Đây là nơi giới hạn vận tốc, gia tốc và jerk xuất hiện. Nếu chỉ nội suy tuyến tính vị trí theo thời gian, robot có thể giật mạnh ở đầu và cuối chuyển động. Nếu dùng trapezoid velocity profile, gia tốc nhảy bậc. Nếu dùng S-curve hoặc jerk-limited profile, chuyển động mượt hơn và ít rung hơn.
Ruckig là một thư viện quan trọng ở tầng này. Tài liệu Ruckig mô tả nó như online trajectory generation, tính trajectory từ trạng thái hiện tại tới target với giới hạn velocity, acceleration và jerk. Điều này rất gần nhu cầu controller thực tế: mỗi chu kỳ nhận trạng thái mới, cập nhật trajectory và xuất setpoint tiếp theo.

Nguồn ảnh: Ruckig documentation. Một trajectory thật không chỉ có vị trí, mà còn có vận tốc, gia tốc và jerk.
4. Interpolator: lấy mẫu theo chu kỳ điều khiển
Servo loop không nhận cả file trajectory dài rồi tự hiểu. Nó cần setpoint tại từng tick: q_des(t), dq_des(t), đôi khi ddq_des(t) hoặc torque feedforward. Nếu controller chạy 500 Hz, mỗi 2 ms interpolator phải xuất một điểm. Nếu chạy 125 Hz như một số interface robot, mỗi 8 ms xuất một điểm. Lớp này phải cẩn thận với thời gian: trễ scheduler, network jitter, clock drift và missed cycle đều có thể làm robot giật.
5. Servo loop: feedback và safety
Servo loop là nơi lệnh mong muốn gặp trạng thái thật. Nếu encoder báo joint chưa tới vị trí, controller tăng command trong giới hạn cho phép. Nếu tracking error vượt ngưỡng, controller dừng. Nếu force/torque báo va chạm, controller giảm tốc hoặc protective stop. Nếu đang jogging gần singularity, controller scale velocity để tránh joint speed tăng vô hạn. Đây là lý do controller thật thường viết bằng C++: cần deterministic, ít allocation, dễ kiểm soát latency, dễ tích hợp Eigen, CMake và driver native.
6. Joint driver: nói chuyện với phần cứng
Driver là lớp gửi command tới robot: vị trí, vận tốc hoặc torque. Với UR robot, ur_rtde cung cấp C++ interface và Python binding để điều khiển và nhận dữ liệu qua RTDE. Với ROS 2, driver thường nối vào ros2_control và FollowJointTrajectory. Với robot công nghiệp đóng, bạn có thể chỉ có API vendor. Dù interface khác nhau, ý tưởng vẫn giống: controller không được gửi lệnh vượt giới hạn, không được giả định network luôn đúng, và phải có chiến lược dừng an toàn.
Triết lý dạy của series: 3 lớp cho mỗi khái niệm toán
Mỗi khái niệm quan trọng sẽ được học qua ba lớp, không nhảy thẳng vào thư viện.
Lớp 1: tính bằng tay để hiểu bản chất. Với FK, ta nhân từng ma trận transform của robot 2 hoặc 3 DOF. Với Jacobian, ta tính từng cột từ trục joint và vector tới end-effector. Với IK, ta giải tay robot phẳng 2 link trước khi nói tới 6 DOF. Mục tiêu không phải làm toán cho đẹp, mà là biết dấu âm dương, frame nào đang dùng, đơn vị radian hay degree, và vì sao singularity xảy ra.
Lớp 2: code from scratch. Sau khi hiểu công thức, ta viết C++ hoặc Python tối giản: vector joint, ma trận homogeneous, hàm FK, DLS IK, trajectory trapezoid, S-curve nhỏ. Code này không thay thế thư viện production, nhưng nó là kính hiển vi. Khi MoveIt hoặc Pinocchio trả kết quả lạ, bạn có baseline nhỏ để so sánh.
Lớp 3: dùng thư viện nổi tiếng và mới nhất. Khi đã hiểu bản chất, ta dùng Eigen, Pinocchio, KDL, Ruckig, TOPP-RA, MoveIt 2, pink, mink, cuRobo, ur_rtde. Lúc đó thư viện không còn là hộp đen. Bạn biết nó đang giải bài toán nào, input nào quan trọng, output nào phải validate.
Triết lý công cụ: C++ là chính, Python để prototype
Series này dùng hai ngôn ngữ với hai vai trò rõ ràng.
C++ là ngôn ngữ chính cho controller thật. Ta dùng CMake để build module rõ ràng, Eigen cho linear algebra, unit test để khóa hành vi, và cấu trúc dữ liệu không cấp phát lung tung trong vòng lặp servo. Với controller chạy gần hardware, C++ vẫn là lựa chọn thực dụng vì latency, ABI với driver, ROS 2 ecosystem và khả năng tối ưu.
Python là phòng thí nghiệm. NumPy, robotics-toolbox-python và matplotlib giúp bạn kiểm tra nhanh FK, IK, trajectory, plot joint velocity, nhìn singularity, so sánh với C++. Python không thay C++ ở tầng hard real-time, nhưng giúp bạn tìm lỗi thuật toán nhanh hơn nhiều. Một workflow tốt là: viết công thức trong notebook, plot kết quả, port sang C++, rồi dùng test so sánh số.
Simulation-first. Ta ưu tiên UR5/UR5e và Franka Panda vì model phổ biến, dễ tìm URDF, ví dụ nhiều. Chạy trong simulation trước, log trajectory, kiểm tra limit, kiểm tra frame. Sau đó mới nối robot thật qua driver, ví dụ ur_rtde cho Universal Robots. Nếu bạn chưa đo được trong simulation, đừng gửi lệnh cho robot thật.
Đọc thêm về hướng simulation và logging trong bài G1 MuJoCo telemetry: DDS và simulation-first. Dù bài đó nói về humanoid, tư duy log, replay và validate trước khi chạm hardware áp dụng trực tiếp cho robot arm.
Hệ sinh thái thư viện cập nhật tới tháng 6/2026
Bảng dưới đây tổng hợp các thư viện sẽ xuất hiện trong series. Version được kiểm tra từ GitHub, PyPI, ROS docs hoặc tài liệu chính thức tại thời điểm viết bài, ngày 13/06/2026.
| Thư viện | Tình trạng tháng 6/2026 | Dùng để làm gì | Bài trong series |
|---|---|---|---|
| Eigen | C++ linear algebra tiêu chuẩn de facto | Vector, matrix, transform, solver nhỏ | Bài 2, 3, 4, 5 |
| Pinocchio | 4.0.0 là latest; 3.9.0 là mốc 3.x gần nhất trước breaking changes | FK, Jacobian, dynamics, model từ URDF | Bài 4, 5, 6 |
| robotics-toolbox-python | 1.1.1 trên PyPI | Prototype FK/IK, plot robot, validate công thức | Bài 3, 4, 5 |
| Orocos KDL | KDL vẫn là thư viện C++/ROS lâu đời cho kinematics | Chain, FK solver, IK solver, PyKDL | Bài 4, 5 |
| Ruckig | Docs API 0.19.3; PyPI community package 0.17.3 | Online trajectory generation, jerk-limited profile | Bài 11, 13 |
| TOPP-RA / toppra | 0.6.8 trên PyPI, release 03/05/2026 | Time-optimal path parameterization theo constraint | Bài 9, 11, 12 |
| MoveIt 2 | Rolling package 2.14.1; binary cho Humble, Jazzy, Rolling | Planning scene, collision checking, OMPL, execution | Bài 14, 15 |
| pink | pin-pink 4.2.0, release 20/04/2026 |
Differential IK dựa trên Pinocchio, task và limit | Bài 5, 6, 13 |
| mink | 1.1.1, release 15/05/2026 | Differential IK trên MuJoCo, task/limit/collision | Bài 5, 7, 13 |
| NVIDIA cuRobo | v0.8.0/cuRoboV2 công bố tháng 4/2026 | GPU IK, collision, trajectory optimization, planning | Bài 14 |
| ur_rtde | 1.6.3, release 13/03/2026 | Điều khiển và nhận trạng thái UR qua RTDE | Bài 15 |

Nguồn ảnh: TOPP-RA documentation. Path parameterization biến đường hình học thành trajectory thỏa giới hạn vận tốc và gia tốc.
Một ví dụ đi xuyên stack
Giả sử bạn gọi:
robot.MoveL(
Pose::FromXYZRPY(0.45, -0.10, 0.32, 3.14, 0.0, 1.57),
Speed{0.25},
Accel{0.8}
);
Controller sẽ làm việc theo chuỗi:
- Kiểm tra pose mục tiêu có hợp lệ trong frame hiện tại không.
- Tạo đường thẳng Cartesian từ pose hiện tại tới pose mục tiêu.
- Chia đường thành các điểm nhỏ, ví dụ mỗi 2 mm hoặc theo curvature.
- Chạy IK cho từng điểm, chọn nghiệm liên tục với joint hiện tại.
- Kiểm tra joint limit, velocity limit, singularity và collision.
- Time-parameterize đường joint bằng profile vận tốc phù hợp.
- Mỗi chu kỳ servo, nội suy
q_des,dq_des, có thểddq_des. - Gửi command xuống driver, đọc feedback, kiểm tra lỗi.
- Nếu lỗi nhỏ, tiếp tục. Nếu lỗi lớn, giảm tốc hoặc dừng.
Nếu dùng VLA, policy có thể quyết định pose ở bước đầu. Nhưng từ bước 2 tới bước 9 vẫn là classical controller. Đây là lý do series này đáng học ngay cả khi bạn đang làm robot learning.
Khi nào dùng thư viện lớn, khi nào tự viết?
Tự viết mọi thứ để chạy production là rủi ro. Nhưng chỉ dùng thư viện mà không hiểu cũng rủi ro. Quy tắc thực dụng:
- Tự viết bản nhỏ cho FK, Jacobian, IK 2D, trapezoid profile và interpolation để học và test.
- Dùng Eigen thay vì tự viết matrix class.
- Dùng Pinocchio/KDL khi robot model phức tạp hoặc cần URDF.
- Dùng Ruckig/TOPP-RA khi cần trajectory đúng limit.
- Dùng MoveIt 2 khi cần planning scene, collision world, planner plugin và ROS 2 integration.
- Dùng cuRobo khi có GPU NVIDIA và cần planning nhanh trong môi trường có nhiều query.
- Dùng
ur_rtdehoặc driver vendor khi chạm robot thật, không tự reverse protocol.

Nguồn ảnh: NVIDIA Technical Blog. GPU motion generation giúp giảm thời gian tính toán ở các bài toán IK, collision và planning nhiều seed.
Checklist kiến thức sau bài này
Sau bài mở đầu, bạn nên nắm được:
MoveJ,MoveL,MoveC,Joglà lệnh ở tầng user API, không phải toàn bộ controller.- Motion planner tìm path hợp lệ theo constraint và collision.
- Trajectory generator thêm thời gian, vận tốc, gia tốc và jerk vào path.
- Interpolator lấy mẫu trajectory theo chu kỳ điều khiển.
- Servo loop dùng feedback để track setpoint và xử lý safety.
- Joint driver là nơi giao tiếp với robot thật hoặc simulator.
- C++ phù hợp cho controller thật; Python phù hợp cho prototype, plot và validate.
- Simulation-first giúp giảm rủi ro trước khi dùng UR5/Panda hoặc robot thật.
Bài tiếp theo sẽ đi vào setup C++/CMake/Eigen để ta có nền móng code sạch trước khi viết robot model, FK, IK và trajectory. Nếu bạn muốn đọc trước phần hệ thống lớn, xem MoveJ và MoveL hoặc motion planning trong roadmap.
Nguồn tham khảo
- MoveIt 2 documentation và MoveIt concepts
- Ruckig documentation và Ruckig PyPI
- TOPP-RA documentation và toppra PyPI
- Pinocchio releases
- OpenVLA paper
- NVIDIA cuRobo documentation và NVIDIA Technical Blog về cuRobo
- ur_rtde PyPI


