简介:围绕协作机器人控制与安全协作主题,这份《协作机器人控制方案》PDF面向上位机与运动控制方向的工程师、机器人专业师生,集中解答力矩感知与碰撞规避、示教模式实现、多轴同步与运动学解算等实际问题。压缩包内仅含1个PDF文档,体积约157KB,内容以Elmo驱动器与多轴运动控制器P-MAS的方案讲解为主,便于快速通读。文档详述力矩传感器布置与异常扭矩自动停机逻辑、循环同步位置/速度/力矩三种模式的切换、双闭环控制与19位绝对值编码器反馈、EtherCAT总线250微秒同步16轴的实现方式,并介绍P-MAS内置的Cartesian、SCARA、Delta等运动学功能以及EASII软件的伺服整定思路,可作为关节驱动选型与柔顺控制开发的系统级参考。目前已有393人学习下载。
1. 协作机器人控制方案要解决的不是“让机械臂动起来”
一台六轴协作臂上电之后按点位走完一段轨迹,门槛其实不高;真正麻烦的是旁边站着人、工件每批位置都差几毫米、末端还挂着力传感器,位置、速度、力矩三条链路要在毫秒级同时闭环,任何一条超限都要在安全响应窗口内把整机停住。协作机器人控制方案讲的就是这件事:它不是某一颗伺服驱动器的事,也不是上位机轨迹规划器单独能扛下来的,而是从伺服电流环、关节力矩估计、笛卡尔空间阻抗,一路铺到双臂相对位姿约束和安全监控的分层设计。把这套东西落到文档里能交付、落到代码里能复现、落到现场能整定,才算把协作机器人控制方案讲清楚。下面按分层架构、空间控制实现、双臂松协调、参数验证四段展开。
2. 协作机器人控制方案的分层架构与实时性预算
很多方案文档写崩,是因为把“机器人能走轨迹”和“控制环路设计”混成一件事。协作机器人要在人机共融环境里工作,安全响应时间会被压到几十毫秒量级,这就逼着你必须把控制回路按实时性分层,而不是一股脑塞进一个线程。
2.1 从伺服环到应用环的四层拆解
常见切法是把控制栈切成四层:伺服层跑电流环和速度环,关节层做位置/力矩指令到关节量的映射,任务层负责笛卡尔空间的阻抗或导纳运算,应用与安全层管状态机、IO 和安全逻辑。层与层的周期差一个数量级,是稳定性的前提。
| 层级 | 典型周期 | 输入 | 输出 | 周期选错的后果 |
|---|---|---|---|---|
| 伺服层 | 125 μs ~ 1 ms | 力矩/电流指令 | 电机相电流 | 高频啸叫、电流纹波大 |
| 关节控制层 | 1 ~ 2 ms | 关节位置或力矩目标 | 关节指令 | 关节跟随滞后、拖动发涩 |
| 任务层(笛卡尔) | 2 ~ 10 ms | 末端位姿、六维力 | 关节目标 | 力控发飘、接触面振荡 |
| 应用与安全层 | 10 ~ 100 ms | IO、状态机事件 | 使能/停止信号 | 安全响应超时、停机距离超标 |
把任务层的阻抗运算塞进 1 ms 环并不会让它更“实时”,反而会因为雅可比求逆和 SVD 的开销撑爆预算,导致关节层跟着抖动。反过来,把关节限幅放到应用层也会出问题:限幅一旦只跑 50 Hz,中间的 49 个周期就是你放弃控制的窗口。
2.2 实时周期、总线与任务调度的预算分配
工业现场跑得最多的组合是 EtherCAT 加 PREEMPT_RT 补丁的 Linux,总线周期设成 1 ms 或 500 μs,DC 同步打开。抖动预算要留够,伺服层一般要求小于周期 5%,关节层小于 10%。这不是玄学:协作机器人做零力拖动示教时,人手的输出力本来就在 10 N 量级,抖动一旦大到让力矩估计产生 2 N 以上的假信号,用户就会觉得机械臂“有黏滞感”。
我一般会把任务层的控制频率设成关节层的整数分之一,比如关节层 1 kHz、任务层 100 Hz、安全层 20 Hz,这样层间同步靠计数器节拍走,不用锁。任务层每次重算一次雅可比和阻抗加速度,中间 9 个周期用插值出的关节目标顶上去。
提示:总线 DC 同步偏移不要设成 0,留 10% 周期给从站处理,否则从站偶发丢帧时抖动量会直接翻倍。
2.3 一个最小可跑的关节控制回环
先把最里层跑通,再往上叠笛卡尔控制。下面这段是关节控制回环的骨架,不依赖具体厂商 SDK,接口按常见协作臂的抽象来写。
import time CTRL_DT = 0.001 # 关节层周期 1 ms TASK_DECIM = 10 # 任务层每 10 个关节周期跑一次 class JointLoop: def __init__(self, servo, robot, safety): self.servo = servo # 伺服总线客户端,提供读写关节状态 self.robot = robot # 运动学/动力学模型 self.safety = safety # 安全监控,含碰撞与超速判断 self.tick = 0 self.q_task = None # 任务层下发的关节目标缓存 def step(self, q_ref, dq_limit=1.5, tau_limit=40.0): q, dq = self.servo.read_joint_state() # 先按速度限幅推算出本周期允许的位移,再限力矩 q_ref = self.robot.clamp_joint_step(q_ref, q, dq_limit * CTRL_DT) tau = self.robot.compute_torque(q, dq, q_ref) tau = self.robot.clamp_torque(tau, tau_limit) if not self.safety.check(q, dq, tau): self.servo.disable() return False self.servo.write_torque(tau) self.tick += 1 return True def run(self): while True: t0 = time.perf_counter() if self.tick % TASK_DECIM == 0: self.q_task = self.robot.solve_task_target() self.step(self.q_task) # 忙等补偿,避免 sleep 造成的周期抖动 while time.perf_counter() - t0 < CTRL_DT: pass逻辑是:读取关节状态,先做速度限幅,再算力矩并限幅,送安全监控,最后写力矩。dq_limit直接对着 ISO/TS 15066 里协作模式的速度限制来设;tau_limit要和碰撞检测阈值联动,设得太松碰撞检不出来,设得太紧正常抓取都会被当异常。
注意:忙等补偿只是样例写法,量产代码里应该交给实时线程和优先级继承机制处理,用
sleep会引入百微秒级抖动。
3. 关节空间与笛卡尔空间的协作控制实现
分层跑通之后,真正决定手感的两个环节是关节空间的位置指令链路和笛卡尔空间的力控回路。
3.1 关节空间位置控制的限幅链路
关节空间的位置控制看起来最无聊,但协作场景里出问题最多。一条链路至少要经过四道处理:关节软限位、速度梯形规划、加速度平滑、力矩前馈。软限位取机械限位向内缩 3°~5°,因为协作臂多用谐波减速器,末端撞限位时反弹力矩比刚性关节大。
限幅顺序不能反。速度限幅必须在加速度平滑之前,否则会出现“先平滑到超速、再被硬切”的台阶,关节会在那一帧明显抖动。下面这段是对关节目标序列做限幅处理的最小实现:
import numpy as np def limit_joint_trajectory(q_traj, q_min, q_max, dq_max, ddq_max, dt=0.001): q_traj = np.clip(q_traj, q_min, q_max) # 1. 软限位 n = len(q_traj) dq = np.gradient(q_traj, dt, axis=0) # 2. 反算速度 scale = np.minimum(1.0, dq_max / (np.abs(dq) + 1e-9)) for i in range(1, n): # 3. 按比例缩速度 q_traj[i] = q_traj[i-1] + (q_traj[i] - q_traj[i-1]) * np.min(scale[i]) ddq = np.gradient(np.gradient(q_traj, dt, axis=0), dt, axis=0) for i in range(2, n): # 4. 加速度一阶低通 a = np.clip(ddq[i], -ddq_max, ddq_max) q_traj[i] = 2*q_traj[i-1] - q_traj[i-2] + a * dt * dt return q_trajq_min/q_max是软限位,dq_max决定示教时的最快拖动速度,ddq_max决定启停时的柔顺程度。第四步那个二阶差分形式,本质是把加速度当作输入重构位置,比直接滤波位置更不容易引入相位滞后。
3.2 笛卡尔空间阻抗控制的落地写法
协作机器人的力控主流是阻抗和导纳两条路。阻抗控制是把运动偏差映射成输出力,导纳控制是把外力和期望位置反解成新的位置指令。协作臂上做人工引导和柔性装配,导纳形式更好用,因为它和位置型伺服接口天然兼容。
导纳环的离散形式一般写成:
$$ M,\ddot{x} + D,\dot{x} + K,(x - x_0) = F_{ext} $$
其中M是期望惯量,D是阻尼,K是刚度,x_0是虚拟平衡位置。
import numpy as np class CartesianAdmittance: def __init__(self, M, D, K, dt=0.01, dx_limit=0.02): self.M = np.diag(M) # 期望惯量,单位 kg self.D = np.diag(D) # 阻尼,单位 N·s/m self.K = np.diag(K) # 刚度,单位 N/m self.dt = dt self.dx_limit = dx_limit # 单周期修正位移上限,抑制发散 self.x0 = None def reset(self, x0): self.x0 = np.asarray(x0, dtype=float) self.x = self.x0.copy() self.xd = np.zeros_like(self.x) def step(self, f_ext): # 解加速度:M*xdd = F_ext - D*xd - K*(x - x0) xdd = np.linalg.solve(self.M, f_ext - self.D @ self.xd - self.K @ (self.x - self.x0)) self.xd = self.xd + xdd * self.dt dx = np.clip(self.xd * self.dt, -self.dx_limit, self.dx_limit) self.x = self.x + dx return self.x关键点有三个。np.linalg.solve而不是求逆,是为了避免M选成非对角时数值病态;dx_limit是发散保护,少了它一旦外力突增、刚度又设得偏小,位置指令会在几个周期内飘出笛卡尔工作空间;reset要在每次切换控制模式时调用,把积分状态清掉。
3.3 阻抗与导纳参数的物理含义和整定表
参数整定不要凭手感,先按物理量估初值:阻尼比取临界阻尼的 0.7~1.2 倍,K按你希望末端在多少牛顿下产生多大位移来定。
| 参数 | 物理含义 | 常见起始值 | 调大后的现象 | 调小后的现象 |
|---|---|---|---|---|
| M | 期望惯量 | 2 ~ 5 kg | 抗扰动强,手感发沉 | 轻快,但容易抖 |
| D | 阻尼 | 临界阻尼的 0.7 ~ 1.2 倍 | 动作迟钝,响应慢 | 接触面高频振荡 |
| K | 刚度 | 200 ~ 800 N/m | 跟踪准,但硬碰硬 | 柔顺,精度差 |
| dx_limit | 单周期修正上限 | 0.01 ~ 0.03 m | 应变快,发散风险高 | 抗冲击,响应滞后 |
整定的顺序是:先固定M,用D = 2*sqrt(K*M)算出临界阻尼,截取 0.7 到 1.2 倍之间;再调K直到末端在 10 N 外力下位移量等于你期望的手感;最后把dx_limit放到比单周期理论位移大 3~5 倍的位置,用来兜底异常工况。
4. 双臂协作机器人松协调运动的控制方案落地
单臂控制跑通之后,双臂协作机器人松协调运动是另一个层级的问题。它和紧协调最大的差别是:松协调不做闭链约束求解,两条臂各自控位置,通过力/位姿误差做同步纠偏。
4.1 松协调和紧协调的边界判断
紧协调是把两条臂和被搬运的刚体看作一个闭环机构,闭链约束要求在控制周期内求解。松协调是两条臂各自保留完整的控制回路,用一个软约束把相对位姿误差压住。选择标准很具体:
- 被搬运对象的刚度与你期望阻抗刚度的比值,超过 10 倍时倾向走紧协调;
- 对象在搬运中允许发生 1 mm 以上的形变,走松协调更合适;
- 两臂本体安装基座不是刚性连接(比如分别装在两台 AMR 上),基本只能走松协调;
- 控制周期里留不出闭链求解的时间预算,也只能松协调。
法奥协作机器人这类轻量臂,关节刚度和反馈分辨率决定了它做紧协调时闭链约束的误差会直接传递到工件上,反过来松协调的软约束更适合它。
4.2 相对位姿约束怎么塞进控制回路
松协调的数学表达很干净:定义两臂末端的相对位姿T_rel = inv(T_l) @ T_r,期望是T_rel_des,把偏差转成作用于两臂的虚拟力。
import numpy as np class LooseCoordination: def __init__(self, arm_l, arm_r, K_sync=50.0, err_thres=0.01): self.arm_l = arm_l self.arm_r = arm_r self.K_sync = K_sync # 同步虚拟刚度 N/m self.err_thres = err_thres # 一致误差阈值 m def _pose_error(self, T_a, T_b): d_pos = T_b[:3, 3] - T_a[:3, 3] # 姿态误差取旋转向量形式,简单起见只取 zyx 欧拉角差 d_rot = np.array([ np.arctan2(T_b[2,1], T_b[2,2]) - np.arctan2(T_a[2,1], T_a[2,2]), -np.arcsin(np.clip(T_b[2,0], -1, 1)) + np.arcsin(np.clip(T_a[2,0], -1, 1)), np.arctan2(T_b[1,0], T_b[0,0]) - np.arctan2(T_a[1,0], T_a[0,0]) ]) return np.hstack([d_pos, d_rot]) def step(self, T_rel_des): T_l, T_r = self.arm_l.get_pose(), self.arm_r.get_pose() T_rel = np.linalg.inv(T_l) @ T_r err = self._pose_error(T_rel, T_rel_des) w = self.K_sync * err if np.linalg.norm(err[:3]) > self.err_thres: # 超出阈值时双臂各自保持,等待重规划 self.arm_l.hold() self.arm_r.hold() self.arm_l.apply_wrench(-w) self.arm_r.apply_wrench(w) return errK_sync设得越大,松协调表现越接近紧协调,但激振风险同步升高。err_thres是一个安全口袋,超过它说明两臂姿态已经偏离到有碰撞风险,先 hold 再让上层重规划,比在控制器里硬拉更安全。
4.3 抓取搬运场景中同步误差的处理
抓取搬运里最常见的同步误差触发点是起停瞬间。两臂各自的加减速规划不同步,位置偏差会在 30~80 ms 内累积到 3~5 mm。处理办法不是把两臂的轨迹强绑成一个,而是在轨迹层做时间对齐:给两臂生成同一条时间轴,但每臂的关节轨迹分别做速度和加速度限幅,误差反馈给总线节拍而不是直接给位置修正。
实测里比较稳的一组做法是:轨迹层周期 20 ms,同步误差在 5 ms 内插值一次,插入到阻抗环之前,让阻抗环从外部只看到平滑后的目标。这样即使在起停瞬间出现 2 mm 的瞬时偏差,经过阻抗环后末端实际偏移也压到 0.5 mm 以内,视觉系统基本看不出来。
提示:同步误差不要直接塞给关节位置修正,那是紧协调的做法,松协调里这么写等同于绕过了两臂各自的限幅链路。
5. 法奥协作机器人控制方案的参数验证与现场排错
前面四层铺完之后,剩下的工作是验证参数是否落在合理区间。下面两个方法我在法奥协作机器人上用过,也适用于大多数支持关节力矩读取的协作臂。
5.1 用阶跃响应验证阻抗参数
给末端施加一个 5 mm 的阶跃位置目标,同时读取六维力,画出从位置起始到稳定在 ±0.2 mm 范围内的响应曲线。判定标准:
| 指标 | 理想区间 | 超限时的处理 |
|---|---|---|
| 上升时间 | 0.15 ~ 0.4 s | 偏大把 K 调高,偏小把 M 调大 |
| 超调量 | < 15% | 超限先把 D 增大 1.3 倍 |
| 稳定时间 | < 0.6 s | 调不下来检查 dx_limit 是否太小 |
| 稳态力波动 | < 0.5 N | 偏大检查关节力矩估计算法是否带滤波 |
采集时把力传感器的采样率设到控制周期的 4 倍以上,否则欠采样会把实际振荡当成噪声。
5.2 松协调同步误差的观测与收敛验证
松协调的验证更直接:让两臂夹着一个刚性工装从 A 点搬到 B 点,记录err的 2-范数随时间的变化。收敛好的曲线应该在起停后 2~3 个任务周期内回到 1 mm 以内,稳态值稳定在 0.3~0.8 mm。三个常见异常:
- 稳态误差一直停在 2 mm 以上:
K_sync太小,或者两臂基座标定本身偏差偏大; - 起停瞬间冲到 5 mm 以上:轨迹层时间对齐没做,或
err_thres触发过晚; - 误差曲线持续振荡不收敛:两条臂的阻抗环
D不一致,先做单臂阶跃响应整定再返回本项。
收敛曲线工具用不着专门写上位机,一条matplotlib加实时读回的关节状态就能打出来。做完整定之后,记得把K_sync、err_thres和两臂的阻抗参数写回配置文件,并在 CI 里加一个能跑单臂阶跃响应的冒烟用例,让参数漂移在改代码时就能被发现。
本文还有配套的精品资源,点击获取