只校验起点 / 终点逆解不够,必须对整条路径做采样可达性校验 + 轨迹插值约束 + 运动学边界限制,分「前置可达性筛查→轨迹插值约束→逐点 IK 校验→分段重规划 / 避奇异」四层落地,适配你当前笛卡尔 Jog 伺服控制架构。
一、先明确:点位姿不可达的 4 类根源
- 工作空间边界超限:X/Y/Z 超出机械臂最大伸展 / 最小收缩;
- 关节极限:单关节角度超过正负限位;
- 奇异位形:腕奇异 / 肩奇异 / 肘奇异,IK 无穷多解或无解、雅克比奇异速度爆炸;
- 自碰撞 / 环境碰撞:位姿数学上 IK 有解,但机械本体干涉。
想要整条轨迹全部可达,必须从目标点位筛选、插值方式、逐点校验、分段修复全链路约束。
二、步骤 1:目标起点 / 终点预校验(前置过滤无效路点)
在规划轨迹前,先对所有离散路点 P0,P1,P2...Pn 单独做可达检测,直接剔除完全无解的目标。
1. 单点位姿可达检测函数
输入位姿 (X,Y,Z,R,P,Y),输出:是否存在至少一组合法关节解 逻辑:
cpp
运行
// 返回true=该位姿存在有效IK解,所有关节在限位内
bool isPoseReachable(double x,double y,double z,double r,double p,double yaw)
{
std::vector<double> joints = kin_.ik(x,y,z,r,p,yaw);
// 1. 判断IK是否返回有效解(自定义标识:如全部NaN代表无解)
bool ik_valid = true;
for(auto j : joints){
if(std::isnan(j)) { ik_valid = false; break; }
}
if(!ik_valid) return false;
// 2. 校验每个关节是否在硬件限位内
std::vector<std::pair<double,double>> joint_limits = {
{-M_PI, M_PI}, // JM0
{-M_PI*0.8, M_PI*0.8}, // JM1-2
{-M_PI*0.7, M_PI*0.7}, // JM4-3
{-M_PI, M_PI}, // JM4
{-M_PI/2, M_PI/2}, // JM5
{-M_PI, M_PI} // YB
};
for(size_t i=0;i<6;i++){
double j = joints[i];
auto [min,max] = joint_limits[i];
if(j < min - 1e-3 || j > max + 1e-3)
return false;
}
// 3. 可选:雅克比判奇异(条件数过大视为接近奇异,禁用)
Eigen::MatrixXd J = kin_.jacobian(joints);
double cond = cond(J);
const double MAX_COND = 200.0; // 阈值根据机械臂标定
if(cond > MAX_COND) return false;
// 4. 可选碰撞检测:导入碰撞模型,检测本体/环境干涉
// if(checkCollision(joints)) return false;
return true;
}
使用:所有路径关键点先调用该函数,直接抛弃完全不可达路点,重新设置目标。
三、步骤 2:笛卡尔轨迹插值方式(保证中间位姿平滑且不飞出工作空间)
两点之间不能直接线性插值 XYZ+RPY,容易中间穿越不可达区域,分两种安全插值方案:
方案 A:线性笛卡尔插值(短距离、无奇异场景推荐)
起点S(xs,ys,zs,rs,ps,ys),终点T(xt,yt,zt,rt,pt,yt) 参数化 s∈0,1,采样步长Δs(稠密采样,0.01~0.05)
⎩⎨⎧X(s)=xs+s(xt−xs)Y(s)=ys+s(yt−ys)Z(s)=zs+s(zt−zs)
姿态用四元数球面线性插值 Slerp,禁止直接线性插值 RPY(会万向锁、姿态跳变): Q(s)=Slerp(Qs,Qt,s) 再将四元数转回 RPY 用于 IK 求解。
方案 B:圆弧 / 样条插值(长路径、焊接打磨连续轨迹)
直线插值容易穿过奇异 / 边界,改用空间圆弧、B 样条平滑过渡,约束轨迹全程在工作空间内部。
关键采样规则(保证不漏过不可达中间点)
- 短段(两点距离 < 0.1m):步长 0.05
- 长段(>0.1m):步长 0.02 步长越小,越不容易漏掉临界不可达点位;代价是计算量上升。
四、步骤 3:整条路径逐采样点 IK 校验(核心:确保轨迹上所有点可达)
两点插值生成稠密采样点序列 {Pose(s0),Pose(s1),...,Pose(sk)},逐个执行可达检测:
- 任意一个采样点返回
isPoseReachable=false→ 整条路径作废; - 处理策略三选一: 1)缩短分段:把原路径拆成两段,重新规划中间过渡点; 2)偏移目标点:微调终点 X/Y/Z/RPY,避开奇异 / 边界; 3)切换关节解分支:IK 多解机械臂更换一组关节解,绕开奇异。
完整路径校验伪代码
cpp
运行
// 输入起点、终点,返回true:整条直线路径全部点位可达
bool checkCartesianPathFull(Eigen::VectorXd start_pose, Eigen::VectorXd target_pose)
{
// 1. 起点终点先单独校验
if(!isPoseReachable(start_pose) || !isPoseReachable(target_pose))
return false;
// 2. 生成稠密插值采样点
const double STEP = 0.02;
for(double s = STEP; s < 1.0; s += STEP)
{
Eigen::VectorXd mid_pose = interpolateSlerp(start_pose, target_pose, s);
if(!isPoseReachable(mid_pose))
{
RCLCPP_ERROR(rclcpp::get_logger("traj_check"),
"路径中间s=%.2f位姿不可达,路径失效", s);
return false;
}
}
return true;
}
五、步骤 4:奇异规避 + 关节空间约束(从根源避免中间点无解)
1. 雅克比奇异判定与规避
机械臂三类奇异:
- 肩奇异:第一关节腕部共线;
- 肘奇异:臂完全伸直 / 折叠;
- 腕奇异:三个腕关节轴线共线。 处理手段:
- 轨迹规划时设置雅克比条件数阈值,插值点接近奇异直接判定路径失效;
- 若必须经过该区域:增加中间过渡点位姿,抬高 / 压低 Z 轴、微调姿态绕开奇异;
- IK 多解机械臂切换关节构型(肘部上翻 / 下翻)。
2. 关节空间连续性约束(防止插值后关节跳变、超限)
笛卡尔插值后每组位姿存在多组 IK 解,必须保证整条路径关节角度连续无跳变: 流程:
- 起点计算全部 IK 解集合 SolS={Js1,Js2,...};
- 终点计算全部 IK 解集合 SolT={Jt1,Jt2,...};
- 遍历所有解配对,找到一组:全程插值后关节变化连续、无超限、无奇异;
- 丢弃会导致关节超过限位、突变 180° 的解组合。
示例:腕关节存在 ±π 两组解,若直接切换会瞬间超加速度报警,轨迹直接判定不可用。
六、步骤 5:分层轨迹规划架构(适配你现有 ROS2 Jog 控制)
分层 1:路点规划层(离线 / 预规划)
- 录入任务离散关键点;
- 逐个
isPoseReachable筛除无效点; - 相邻两点生成稠密笛卡尔插值;
- 逐采样点校验可达性,失效则分段重规划;
- 输出一条全点可达的稠密位姿轨迹数组。
分层 2:速度 / 加减速轨迹平滑层(时间插值)
对已校验安全的位姿序列做时间参数化(梯形 / S 曲线速度轮廓),约束:
- 最大笛卡尔线速度、角速度;
- 关节最大加速度、速度; 输出每个控制周期(100Hz)的目标笛卡尔位姿。
分层 3:伺服实时执行层(你现有代码)
每个周期取规划好的目标位姿→IK 得到目标关节→速度闭环 Jog 下发; 优势:所有中间点位提前离线校验,运行时不会出现中途 IK 无解、机械臂卡死。
七、运行时兜底策略(动态笛卡尔目标场景,无预规划)
如果你需要动态调用setCartesianTarget()实时随机给目标,无法离线预规划整条路径:
- 分段渐进移动:不直接一步到目标,拆分多段小步笛卡尔轨迹;
- 每一小段先执行
checkCartesianPathFull校验; - 小段可达才执行运动,不可达直接报错并停止;
cpp
运行
// 动态目标分段平滑逼近示例
void safeMoveToCartesian(double tx,double ty,double tz,double tr,double tp,double tyaw)
{
// 获取当前末端位姿FK
auto curr_pose = kin_.fk(current_joints_);
Eigen::VectorXd start(curr_pose.begin(),curr_pose.end());
Eigen::VectorXd target({tx,ty,tz,tr,tp,tyaw});
// 拆分5段渐进运动
int seg_num = 5;
for(int seg=1;seg<=seg_num;seg++)
{
double s_seg = (double)seg / seg_num;
Eigen::VectorXd mid_target = interpolateSlerp(start, target, s_seg);
// 校验本段路径是否全部可达
if(!checkCartesianPathFull(start, mid_target))
{
RCLCPP_ERROR(get_logger(), "第%d段路径不可达,运动终止", seg);
return;
}
// 执行本段笛卡尔运动
setCartesianTarget(mid_target[0],mid_target[1],mid_target[2],
mid_target[3],mid_target[4],mid_target[5]);
// 等待本段运动完成(内部自动到达退出笛卡尔模式)
while(cartesian_mode_active_) rclcpp::spin_some(this->get_node_base_interface());
// 更新起点为本段终点
start = mid_target;
}
}
八、常见踩坑点与优化
-
只校验起点终点,不校验中间插值点 直线中间极易穿过工作空间边界 / 奇异,运行中途 IK 无解,机械臂抖动。 修复:稠密采样逐点校验。
-
RPY 线性插值而非 Slerp 姿态插值出现万向锁,中间位姿姿态突变、IK 无解。 修复:四元数 Slerp 球面插值姿态。
-
IK 只取单组解,不遍历多解 某一组解路径超限,另一组解完全可行但未使用。 修复:枚举全部 IK 解,选连续无奇异分支。
-
未设置雅克比奇异阈值 路径数学 IK 有解,但接近奇异时末端微小位移对应关节巨大速度,伺服超速报警。 修复:增加条件数判定过滤奇异区域。
九、极简落地流程总结
- 筛选所有目标路点,单个点位 IK + 关节限位 + 奇异预校验;
- 相邻点位用 Slerp 姿态插值生成稠密中间采样;
- 逐采样点检测可达,任意点失效则分段 / 微调目标;
- 筛选连续关节解分支,保证全程关节无跳变超限;
- 时间域加减速平滑生成周期目标位姿;
- 实时伺服分段执行,动态目标采用渐进分段校验兜底。 按这套流程生成的轨迹,路径上每一个插值点位姿都存在合法、连续、无奇异、无碰撞的关节解。