Trong bài MoveJ và MoveL, ta đã xem robot đi từ điểm này sang điểm khác bằng quỹ đạo khớp hoặc đường thẳng TCP. Trong bài MoveC và MoveP, ta thêm cung tròn và đường quá trình nhiều điểm. Bài này đào sâu vào một ý tưởng nhỏ nhưng quyết định cảm giác "mượt" của robot công nghiệp: blending waypoint, hay bo góc giữa hai đoạn để robot không phải dừng hẳn tại mọi điểm trung gian.
Nếu bạn dạy robot đi qua chuỗi điểm P0 -> P1 -> P2 -> P3, cách đơn giản nhất là chạy từng đoạn độc lập: MoveL tới P1, dừng, MoveL tới P2, dừng, rồi tiếp tục. Cách này dễ kiểm soát, nhưng chậm và thường tạo chuyển động giật. Trong hàn, bơm keo, phun sơn, đánh bóng, scan camera hoặc pick-and-place tốc độ cao, dừng ở mỗi waypoint có thể làm hỏng quá trình: keo tụ ở góc, vệt sơn dày lên, lực đánh bóng tăng đột ngột, hoặc cycle time tăng quá nhiều.
Blending giải quyết bằng cách cho phép TCP không đi đúng qua waypoint danh nghĩa. Robot rời đoạn vào trước khi tới góc, đi qua một đoạn cong nhỏ, rồi nhập vào đoạn ra. Tham số thường gặp nhất là blend radius: bán kính vùng bo quanh waypoint. Radius càng lớn, robot càng giữ được tốc độ cao, nhưng đường đi càng lệch xa điểm góc ban đầu. Đó là đánh đổi cốt lõi của bài này: path deviation đổi lấy tốc độ và độ mượt.

1. Vì sao dừng hẳn ở mỗi waypoint lại chậm?
Hãy xét một robot đang đi theo đoạn thẳng P0 -> P1, rồi đổi hướng sang P1 -> P2. Nếu bắt buộc TCP chạm chính xác P1 và hai đoạn không thẳng hàng, vector vận tốc trước và sau waypoint có hướng khác nhau. Với cơ cấu thật, vận tốc không thể đổi hướng tức thời vì gia tốc bị giới hạn. Nếu controller vẫn muốn bảo đảm đi đúng qua điểm góc, cách an toàn nhất là giảm vận tốc về 0 tại P1, sau đó tăng tốc theo hướng mới.
Điều này tạo ba chi phí:
- Chi phí thời gian: mỗi waypoint có thêm pha giảm tốc và tăng tốc. Với 50 waypoint, tổng thời gian mất vào tăng/giảm tốc có thể lớn hơn thời gian chạy đều.
- Chi phí cơ khí: dừng - chạy liên tục làm motor, hộp số và cơ cấu truyền động chịu nhiều biến thiên lực hơn.
- Chi phí quá trình: tool vẫn hoạt động trong khi TCP chậm hoặc dừng. Với keo, hàn, sơn, mài, tốc độ không đều thường tạo chất lượng không đều.
Trong tài liệu Universal Robots, khi đặt blend radius, quỹ đạo được blend quanh waypoint để robot không phải dừng tại điểm đó. Tài liệu cũng nhấn mạnh blend giúp chương trình chạy nhanh hơn vì tránh giảm tốc và tăng tốc giữa các đoạn. Đây không phải mẹo riêng của một hãng; đó là hệ quả trực tiếp của động học: muốn đổi hướng ở vận tốc khác 0 thì phải dùng gia tốc hữu hạn trên một đoạn cong hữu hạn.
Với một cung tròn bán kính r và tốc độ tuyến tính v, gia tốc hướng tâm xấp xỉ:
a_c = v^2 / r
Nếu r nhỏ mà vẫn muốn giữ v lớn, gia tốc yêu cầu sẽ tăng nhanh. Nếu robot có giới hạn a_max, tốc độ tối đa qua góc bị chặn bởi:
v <= sqrt(a_max * r)
Đây là cách nhìn rất hữu ích khi chọn blend radius. Radius nhỏ giúp đường đi bám sát waypoint, nhưng ép robot giảm tốc nhiều. Radius lớn cho phép chạy nhanh hơn, nhưng TCP sẽ "cắt góc" xa hơn. Trong process path, bạn không chọn r theo cảm giác; bạn chọn nó dựa trên dung sai đường đi, giới hạn gia tốc, khoảng cách tới vật cản và yêu cầu chất lượng quá trình.
2. Blend radius là gì?
Với ba điểm liên tiếp P0, P1, P2, P1 là waypoint cần blend. Ta có hai đoạn:
- Đoạn vào:
P0 -> P1 - Đoạn ra:
P1 -> P2
Nếu đặt r = 0, robot phải đi tới đúng P1. Nếu đặt r > 0, controller dựng một đoạn cong nằm gần P1. Đường cong thường tiếp tuyến với đoạn vào và đoạn ra, nghĩa là hướng vận tốc tại điểm nhập blend khớp với hướng đoạn vào, và hướng vận tốc tại điểm thoát blend khớp với hướng đoạn ra. Khi tốc độ được giữ liên tục, vector vận tốc không nhảy gãy; robot đổi hướng dần dần.
Với blend cung tròn đơn giản, ta có thể tính hai điểm tiếp tuyến:
u = normalize(P1 - P0) # hướng đi vào góc
v = normalize(P2 - P1) # hướng đi ra khỏi góc
a = -u # tia từ góc quay ngược về P0
b = v # tia từ góc quay về P2
phi = angle(a, b) # góc trong tại waypoint
d = r / tan(phi / 2) # khoảng lùi từ waypoint tới điểm tiếp tuyến
T_in = P1 - u * d
T_out = P1 + v * d
T_in là điểm robot rời đoạn thẳng vào. T_out là điểm robot nhập đoạn thẳng ra. Giữa hai điểm đó là cung tròn bán kính r. Đường đi mới là:
P0 -> ... -> T_in -> arc(T_in, T_out) -> T_out -> ... -> P2
Robot không chạm P1. Sai lệch lớn nhất so với polyline gốc nằm trong vùng quanh góc. Trong nhiều API công nghiệp, blend radius được hiểu như vùng mà robot được phép bo quanh waypoint, không phải cam kết rằng mọi điểm trên cung cách waypoint đúng bằng r. Khi tự viết controller, bạn nên định nghĩa rõ: r là bán kính cung tròn hình học hay bán kính vùng cho phép quanh waypoint.
3. Khi nào blend không hợp lệ?
Blend không phải lúc nào cũng dùng được. Có ba điều kiện beginner phải kiểm tra:
Thứ nhất, đoạn kề phải đủ dài. Với công thức trên, khoảng lùi d không được lớn hơn chiều dài đoạn vào hoặc đoạn ra. Nếu P0-P1 quá ngắn, robot không có đủ không gian để rời đoạn thẳng trước khi tới góc. Quy tắc bảo thủ là d < 0.5 * min(len_in, len_out) khi có nhiều waypoint gần nhau.
Thứ hai, các blend liên tiếp không được chồng lên nhau. Universal Robots mô tả rõ rằng blend không thể overlap; nếu radius của waypoint trước và waypoint sau chồng lên nhau thì chương trình phải cảnh báo hoặc sửa radius. Trong controller tự viết, hãy làm điều tương tự: trước khi chạy robot thật, tính T_in/T_out cho toàn bộ chuỗi điểm và kiểm tra thứ tự dọc theo từng đoạn.

Thứ ba, path hình học hợp lệ chưa chắc robot đi được. Cartesian blend chỉ nói về TCP. Sau đó bạn vẫn cần IK, kiểm tra joint limit, singularity, collision và giới hạn tốc độ/gia tốc từng khớp. Ở gần singularity, một cung TCP rất hiền có thể tạo vận tốc khớp rất lớn. Vì vậy pipeline thực tế thường là:
waypoints Cartesian
-> dựng path đã blend
-> lấy mẫu theo độ dài cung
-> IK liên tục bằng seed từ mẫu trước
-> time parameterization theo giới hạn joint
-> kiểm tra lại sai số TCP, collision và limit
MoveIt/Pilz cũng tách rõ giới hạn Cartesian cho LIN/CIRC và kết quả cuối cùng là joint trajectory có vị trí, vận tốc, gia tốc và timestamp. Nếu motion plan lỗi vì vi phạm giới hạn joint, người dùng phải giảm scaling hoặc đổi path. Đây là bài học quan trọng: đừng chỉ nhìn đường TCP đẹp trên plot 2D rồi gửi thẳng xuống servo.

4. Prototype Python: dựng blend cung tròn và kiểm tra liên tục vận tốc
Prototype dưới đây chỉ dùng numpy và matplotlib. Nó dựng polyline 2D/3D, thay góc tại mỗi waypoint bằng cung tròn tiếp tuyến, lấy mẫu theo độ dài, rồi tính vận tốc xấp xỉ để kiểm tra hướng vận tốc trước và sau blend. Ta viết 2D cho dễ nhìn; cùng công thức hoạt động trong 3D nếu các điểm liên tiếp nằm trên cùng mặt phẳng cục bộ.
import numpy as np
import matplotlib.pyplot as plt
EPS = 1e-9
def normalize(x):
n = np.linalg.norm(x)
if n < EPS:
raise ValueError("Vector quá nhỏ")
return x / n
def angle_between(a, b):
c = np.clip(np.dot(a, b), -1.0, 1.0)
return np.arccos(c)
def rot90(x):
return np.array([-x[1], x[0]])
def line_intersection(p, dp, q, dq):
# p + s*dp = q + t*dq
A = np.column_stack([dp, -dq])
rhs = q - p
if abs(np.linalg.det(A)) < 1e-9:
raise ValueError("Hai đường gần song song")
s, _ = np.linalg.solve(A, rhs)
return p + s * dp
def blend_corner(p0, p1, p2, radius, ds=0.005):
p0, p1, p2 = map(lambda p: np.asarray(p, dtype=float), [p0, p1, p2])
u = normalize(p1 - p0)
v = normalize(p2 - p1)
# Góc trong giữa tia quay về p0 và tia đi tới p2.
a = -u
b = v
phi = angle_between(a, b)
if phi < np.deg2rad(5) or abs(np.pi - phi) < np.deg2rad(1):
return None
d = radius / np.tan(phi / 2.0)
len_in = np.linalg.norm(p1 - p0)
len_out = np.linalg.norm(p2 - p1)
if d > 0.45 * min(len_in, len_out):
raise ValueError(f"Blend radius quá lớn: cần lùi {d:.3f} m")
t_in = p1 - u * d
t_out = p1 + v * d
# Tâm là giao của hai đường vuông góc tại điểm tiếp tuyến.
n1 = rot90(u)
n2 = rot90(v)
candidates = [
line_intersection(t_in, n1, t_out, n2),
line_intersection(t_in, -n1, t_out, n2),
]
center = min(candidates, key=lambda c: abs(np.linalg.norm(c - t_in) - radius))
def theta(p):
r = p - center
return np.arctan2(r[1], r[0])
th0 = theta(t_in)
th1 = theta(t_out)
# Chọn chiều cung sao cho tiếp tuyến đầu gần hướng u.
def sample(th_start, th_end):
arc_len = abs(th_end - th_start) * radius
n = max(3, int(np.ceil(arc_len / ds)) + 1)
th = np.linspace(th_start, th_end, n)
pts = center + radius * np.column_stack([np.cos(th), np.sin(th)])
return pts
options = [
sample(th0, th1),
sample(th0, th1 + 2 * np.pi),
sample(th0, th1 - 2 * np.pi),
]
def tangent_error(pts):
tan0 = normalize(pts[1] - pts[0])
tan1 = normalize(pts[-1] - pts[-2])
return np.linalg.norm(tan0 - u) + np.linalg.norm(tan1 - v)
arc = min(options, key=tangent_error)
return t_in, t_out, center, arc
def blended_path(points, radius, ds=0.005):
points = [np.asarray(p, dtype=float) for p in points]
out = [points[0]]
for i in range(1, len(points) - 1):
p0, p1, p2 = points[i - 1], points[i], points[i + 1]
blend = blend_corner(p0, p1, p2, radius, ds)
if blend is None:
out.append(p1)
continue
t_in, t_out, center, arc = blend
out.append(t_in)
out.extend(arc[1:-1])
out.append(t_out)
out.append(points[-1])
return np.array(out)
def finite_difference_velocity(path, dt):
return np.diff(path, axis=0) / dt
polyline = np.array([
[0.00, 0.00],
[0.45, 0.00],
[0.65, 0.25],
[0.95, 0.25],
[1.05, -0.10],
])
path = blended_path(polyline, radius=0.08, ds=0.004)
dt = 0.01
vel = finite_difference_velocity(path, dt)
speed = np.linalg.norm(vel, axis=1)
fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(12, 4))
ax0.plot(polyline[:, 0], polyline[:, 1], "o--", label="waypoint gốc")
ax0.plot(path[:, 0], path[:, 1], "-", label="path đã blend")
ax0.axis("equal")
ax0.grid(True)
ax0.legend()
ax1.plot(speed)
ax1.set_title("Tốc độ xấp xỉ sau khi lấy mẫu đều")
ax1.set_xlabel("sample")
ax1.set_ylabel("|v|")
ax1.grid(True)
plt.tight_layout()
plt.show()
Điều cần quan sát không phải hình đẹp hay xấu, mà là tangent continuity. Tại T_in, cung tròn tiếp tuyến với đoạn vào; tại T_out, cung tròn tiếp tuyến với đoạn ra. Nếu ta giữ tốc độ scalar liên tục, vector vận tốc cũng liên tục theo hướng. Tuy nhiên gia tốc vẫn có bước nhảy vì curvature đổi từ 0 trên đoạn thẳng sang 1/r trên cung. Muốn mượt hơn nữa, bạn dùng clothoid, spline, hoặc time-parameterization giới hạn jerk. Với controller cổ điển, cung tròn + profile jerk-limited đã là một bước rất lớn so với dừng cứng tại mọi waypoint.
5. Chọn radius theo tốc độ, dung sai và vật cản
Một công thức thực dụng:
r_min = v_process^2 / a_allowed
Nếu process cần TCP chạy 0.25 m/s và bạn chỉ muốn dùng 1.0 m/s^2 gia tốc ngang tại góc, bán kính tối thiểu xấp xỉ:
r_min = 0.25^2 / 1.0 = 0.0625 m
Tức là khoảng 6.25 cm. Nếu chi tiết chỉ cho phép lệch đường 2 mm, bạn không thể giữ tốc độ đó qua một góc 90 độ bằng cung tròn đơn giản. Bạn phải giảm tốc, đổi path, dùng nhiều waypoint mịn hơn, hoặc chấp nhận dừng có kiểm soát.
Bảng chọn nhanh:
| Mục tiêu | Radius nhỏ | Radius lớn |
|---|---|---|
| Bám waypoint chính xác | Tốt hơn | Kém hơn |
| Giữ tốc độ cao | Kém hơn | Tốt hơn |
| Gia tốc hướng tâm | Cao hơn ở cùng tốc độ | Thấp hơn ở cùng tốc độ |
| Nguy cơ cắt gần vật cản | Thấp hơn | Cao hơn |
| Cycle time | Dài hơn | Ngắn hơn |
Trong production, radius không nên là hằng số toàn cục duy nhất. Góc gần fixture hoặc chi tiết nên dùng radius nhỏ. Đoạn free-space có thể dùng radius lớn để giảm cycle time. Waypoint có yêu cầu chính xác như điểm pick, điểm place, điểm bắt đầu hàn, điểm kết thúc hàn thường đặt r = 0 hoặc rất nhỏ.
6. C++: geometric blender tối giản
Đoạn C++ sau là skeleton cho phần hình học. Nó nhận nhiều waypoint 2D để dễ đọc, nhưng cấu trúc tương tự cho Eigen::Vector3d khi bạn dựng mặt phẳng blend cục bộ. Phần IK và collision không nằm trong ví dụ này.
#include <Eigen/Dense>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <stdexcept>
#include <vector>
using Vec2 = Eigen::Vector2d;
constexpr double kPi = 3.14159265358979323846;
struct BlendResult {
Vec2 t_in;
Vec2 t_out;
Vec2 center;
std::vector<Vec2> arc;
};
static Vec2 normalize(const Vec2& x) {
const double n = x.norm();
if (n < 1e-9) throw std::runtime_error("zero-length vector");
return x / n;
}
static Vec2 rot90(const Vec2& x) {
return Vec2{-x.y(), x.x()};
}
static Vec2 intersectLines(const Vec2& p, const Vec2& dp,
const Vec2& q, const Vec2& dq) {
Eigen::Matrix2d A;
A.col(0) = dp;
A.col(1) = -dq;
const double det = A.determinant();
if (std::abs(det) < 1e-9) throw std::runtime_error("parallel lines");
Eigen::Vector2d st = A.colPivHouseholderQr().solve(q - p);
return p + st.x() * dp;
}
static BlendResult blendCorner(const Vec2& p0, const Vec2& p1, const Vec2& p2,
double radius, double ds) {
const Vec2 u = normalize(p1 - p0);
const Vec2 v = normalize(p2 - p1);
const Vec2 a = -u;
const Vec2 b = v;
const double dot = std::clamp(a.dot(b), -1.0, 1.0);
const double phi = std::acos(dot);
const double d = radius / std::tan(phi / 2.0);
if (d > 0.45 * std::min((p1 - p0).norm(), (p2 - p1).norm())) {
throw std::runtime_error("blend radius too large for neighboring segments");
}
BlendResult result;
result.t_in = p1 - u * d;
result.t_out = p1 + v * d;
const Vec2 c1 = intersectLines(result.t_in, rot90(u), result.t_out, rot90(v));
const Vec2 c2 = intersectLines(result.t_in, -rot90(u), result.t_out, rot90(v));
result.center = (std::abs((c1 - result.t_in).norm() - radius) <
std::abs((c2 - result.t_in).norm() - radius)) ? c1 : c2;
auto theta = [&](const Vec2& p) {
Vec2 r = p - result.center;
return std::atan2(r.y(), r.x());
};
double th0 = theta(result.t_in);
double th1 = theta(result.t_out);
double delta = th1 - th0;
while (delta > kPi) delta -= 2.0 * kPi;
while (delta < -kPi) delta += 2.0 * kPi;
const int n = std::max(3, static_cast<int>(std::ceil(std::abs(delta) * radius / ds)) + 1);
result.arc.reserve(n);
for (int i = 0; i < n; ++i) {
const double s = static_cast<double>(i) / static_cast<double>(n - 1);
const double th = th0 + s * delta;
result.arc.push_back(result.center + radius * Vec2{std::cos(th), std::sin(th)});
}
return result;
}
Trong code thật, bạn nên tách ba lớp:
PathBlender: nhận waypoint Cartesian, radius từng waypoint, trả path đã blend.IKSampler: lấy mẫu path, giải IK liên tục, trả chuỗi joint waypoint.TimeParameterizer: gán thời gian, vận tốc, gia tốc, jerk theo limit.
Tách như vậy giúp bạn thay phần thời gian bằng Ruckig, TOTG, Pilz, hoặc thư viện nội bộ mà không phá hình học path.
7. Tích hợp Ruckig cho quỹ đạo nhiều waypoint
Ruckig là thư viện online trajectory generation cho robot và máy. Theo tài liệu, nó nhận current state, target state và giới hạn vận tốc/gia tốc/jerk, rồi cập nhật trạng thái mới trong control loop. Với target đơn, Ruckig rất hợp để tạo chuyển động jerk-limited từ trạng thái hiện tại tới target. Với intermediate waypoints, tài liệu Ruckig nêu rõ bản Pro hỗ trợ tính local đầy đủ; bản Community có thể chuyển sang cloud API khi dùng intermediate_positions, không phù hợp real-time cục bộ. Vì vậy trong dự án tự học, hãy dùng một trong hai cách:
- Cách local đơn giản: bạn tự dựng path blended, giải IK thành các sample, rồi gọi Ruckig từng section nhỏ với target kế tiếp và vận tốc target ước lượng khác 0. Cách này không tối ưu toàn cục, nhưng chạy local và dễ hiểu.
- Cách Pro/multi-waypoint: đưa các joint waypoint đã lọc vào
input.intermediate_positionsđể Ruckig tính path và time parameterization chung. Cách này gọn hơn nhưng phụ thuộc license/tính năng.
Skeleton dưới đây minh họa cách local từng target. Nó giả sử bạn đã có joint_path, mỗi phần tử là std::array<double, 6>. Để giữ bài ngắn, phần tính vận tốc target dùng finite difference đơn giản; production code nên giới hạn norm, lọc waypoint quá gần và kiểm tra validate_input.
#include <ruckig/ruckig.hpp>
#include <array>
#include <iostream>
#include <vector>
using Joint = std::array<double, 6>;
Joint estimateVelocity(const Joint& prev, const Joint& next, double dt) {
Joint v{};
for (size_t i = 0; i < 6; ++i) {
v[i] = (next[i] - prev[i]) / dt;
}
return v;
}
void runRuckigSections(const std::vector<Joint>& joint_path) {
using namespace ruckig;
Ruckig<6> otg{0.001}; // chu kỳ servo 1 ms
InputParameter<6> input;
OutputParameter<6> output;
input.current_position = joint_path.front();
input.current_velocity = {0, 0, 0, 0, 0, 0};
input.current_acceleration = {0, 0, 0, 0, 0, 0};
input.max_velocity = {1.5, 1.5, 1.5, 2.0, 2.0, 2.5};
input.max_acceleration = {3.0, 3.0, 3.0, 4.0, 4.0, 5.0};
input.max_jerk = {20.0, 20.0, 20.0, 30.0, 30.0, 40.0};
for (size_t k = 1; k < joint_path.size(); ++k) {
input.target_position = joint_path[k];
if (k + 1 < joint_path.size()) {
input.target_velocity = estimateVelocity(joint_path[k - 1], joint_path[k + 1], 0.05);
} else {
input.target_velocity = {0, 0, 0, 0, 0, 0};
}
input.target_acceleration = {0, 0, 0, 0, 0, 0};
auto result = Result::Working;
while ((result = otg.update(input, output)) == Result::Working) {
// robot.writeJointPosition(output.new_position);
output.pass_to_input(input);
}
if (result < Result::Working) {
throw std::runtime_error("Ruckig failed at section " + std::to_string(k));
}
output.pass_to_input(input);
}
}
Điểm quan trọng: nếu bạn đặt target_velocity = 0 ở mọi waypoint, bạn lại quay về hành vi dừng tại từng sample. Muốn đa-waypoint mượt, các waypoint trung gian phải có vận tốc đi qua hợp lý, hoặc bạn dùng API intermediate waypoint để thư viện giải đồng thời. Nhưng vận tốc target không được tùy tiện; nó phải cùng hướng path, không vượt giới hạn khớp, và không làm robot cắt path quá xa.
8. CMakeLists và build
Một project tối giản có thể dùng FetchContent để lấy Ruckig và Eigen. Với môi trường sản xuất, bạn nên pin tag cụ thể, mirror dependency nội bộ và build offline. Nhưng cho tutorial:
cmake_minimum_required(VERSION 3.20)
project(blended_arm_path LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.4.0
)
FetchContent_Declare(
ruckig
GIT_REPOSITORY https://github.com/pantor/ruckig.git
GIT_TAG v0.14.0
)
FetchContent_MakeAvailable(eigen ruckig)
add_executable(blended_arm_path main.cpp)
target_link_libraries(blended_arm_path PRIVATE Eigen3::Eigen ruckig::ruckig)
target_compile_options(blended_arm_path PRIVATE -Wall -Wextra -Wpedantic)
Build:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
./build/blended_arm_path
Nếu bạn dùng Eigen vector trực tiếp với Ruckig, tài liệu Ruckig yêu cầu include Eigen trước ruckig/ruckig.hpp và dùng template ruckig::EigenVector. Đây là cách sạch hơn khi phần còn lại của controller đã dùng Eigen.
9. Checklist trước khi chạy robot thật
Trước khi gửi quỹ đạo blended xuống robot thật, hãy kiểm tra:
- Waypoint không trùng nhau và không tạo đoạn quá ngắn.
- Blend radius không làm
dvượt chiều dài đoạn kề. - Các blend liên tiếp không overlap.
- Path blended không cắt qua fixture, chi tiết, vùng cấm hoặc singularity.
- IK liên tục, không nhảy nhánh khớp giữa hai sample.
- Tốc độ/gia tốc/jerk từng khớp nằm trong giới hạn.
- Waypoint cần chính xác tuyệt đối được đặt
r = 0. - Tool process được đồng bộ với vùng blend: bật/tắt keo, hàn, sơn không nên xảy ra trong đoạn bị cắt góc nếu process yêu cầu đúng vị trí.
Nếu đang làm bài tập, chỉ cần plot path và tốc độ. Nếu đang làm cell robot thật, phải thêm collision checking, emergency stop, watchdog servo, logging và replay ở tốc độ thấp. Blending làm robot nhanh và mượt hơn, nhưng cũng làm nó đi khác polyline gốc. Chính sự "khác" đó là điều bạn phải kiểm soát.
Khi đưa controller vào vận hành xa hoặc nhiều cell, phần telemetry cũng quan trọng như thuật toán. Bạn có thể tham khảo thêm bài Monitor ROS 2 robots remotely để thiết kế luồng trạng thái, log và cảnh báo; nếu đang so sánh nền tảng giám sát thương mại, bài Formant alternatives đưa ra bối cảnh khác ngoài series controller này.
10. Kết luận
Blending waypoint là cầu nối giữa trajectory lý thuyết và robot công nghiệp chạy nhanh. Một waypoint sharp corner ép robot giảm tốc hoặc dừng vì vector vận tốc phải đổi hướng. Blend radius cho robot một đoạn cong để đổi hướng dần dần, nhờ đó giữ tốc độ tốt hơn. Đổi lại, TCP không đi đúng qua waypoint danh nghĩa và có thể lệch gần vật cản hơn.
Prototype Python giúp bạn thấy hình học: tìm điểm tiếp tuyến, dựng cung tròn, lấy mẫu và kiểm tra liên tục vận tốc. Skeleton C++ cho thấy cách đóng gói geometric blender, rồi dùng Ruckig để tạo quỹ đạo jerk-limited. Khi ghép vào controller đầy đủ, hãy nhớ thứ tự: path hình học trước, IK liên tục sau, time parameterization cuối cùng. Làm đúng thứ tự này, bạn sẽ có chuyển động đa-waypoint mượt mà không cần dừng cứng ở từng điểm.
Tài liệu tham khảo
- Universal Robots PolyScope manual: Blend Parameters
- MoveIt/Pilz Industrial Motion Planner documentation
- Ruckig documentation: Tutorial and Intermediate Waypoints
- Ruckig examples
- Turning Paths Into Trajectories Using Parabolic Blends


