ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于迭代学习控制的机器人双臂协调MATLAB仿真实践

基于迭代学习控制的机器人双臂协调MATLAB仿真实践 搞双臂协调控制这件事我最初是被一个实验逼上梁山的。单臂轨迹跟踪做得再顺一旦让两条机械臂共同夹持一个刚性负载就会出现各种“默契度”问题——左臂到位了右臂还在赶右臂修正了左臂又被带偏。当时正好在调研迭代学习控制ILCIterative Learning Control发现这个思路特别契合“同一个动作反复做、越做越准”的场景于是就在MATLAB里搭了一个双臂迭代学习控制的仿真工程。整个过程走下来踩了不少坑也把ILC从原理到工程落地的关键环节摸了一遍这里分享给同样在做机器人控制仿真的朋友。这篇文章适合这几类人刚接触迭代学习控制、想找个具体案例入手的在做单臂控制但后续需要扩展到多机协调的以及纯粹想看看MATLAB里如何把“迭代学习”这个抽象概念变成能跑的仿真代码的。我会从为什么选双臂场景切入给出建模思路、ILC控制律设计、参数整定经验再重点分享仿真中那些“合法但不理想”的现象到底怎么排查。1. 为什么是“双臂迭代学习”一个被实验逼出来的选择1.1 双臂协调控制的难点不在“多一个臂”而在约束很多人觉得双臂控制无非是两套单臂控制拼在一起这个理解在简单场合能成立但一旦双臂需要协同搬运同一物体问题就变味了。想象两个人抬一张桌子如果两个人各自只管往前走不管同伴的速度桌子一定会歪如果两个人用力方向不一致桌角就会别着劲。工业机器人的双臂系统也一样——两条机械臂的末端通过刚性负载形成运动学闭链两个末端的位置、姿态不是独立的而是被负载的几何尺寸约束住了。这个约束让系统的有效自由度下降也让控制目标从“单一末端轨迹跟踪”变成了“两个末端同时跟踪且保持相对关系不变”。用控制理论的语言说这是典型的欠驱动约束系统或者说带闭链约束的多体系统。直接套用单臂的逆动力学控制很容易出现内部力失衡轻则轨迹偏差重则损坏抓持工件。这也是为什么做双臂仿真时不能简单写两个独立的PD控制器就完事。1.2 ILC的核心思想不是“当场纠错”而是“事后补课”传统反馈控制比如PID的工作方式可以概括为“兵来将挡”——当前时刻有误差当前时刻就加大控制量把误差压下去。这种方式的问题是它永远在应对已经发生的误差对系统的重复性特征视而不见。迭代学习的思路完全不同。它基于一个很朴素的观察如果同一个机器人、在同一条产线上、执行同一个动作一百次那么第一次犯的错和第二次犯的错是有相关性的。既然如此为什么不把第一次运行中记录下来的误差保存下来用来修正第二次的控制输入这就是ILC的本质——利用历史批次iteration/trial的误差信息逐批次修正控制信号让跟踪误差随着迭代次数增加而不断减小。用大白话说ILC就是“熟能生巧”。射手反复拉弓射箭每一箭都会根据上一箭的落点去调整瞄准机器人反复做同一个轨迹每一次都根据上一次的轨迹偏差去调整前馈力矩。它和“PID当场纠错”的差别在于PID用的是此刻的误差ILC用的是上一次的误差而且这个误差会被编码成对未来控制输入的修正量。1.3 为什么双臂场景特别适合ILC我选择双臂作为ILC的验证场景不是因为双臂非用ILC不可而是因为双臂协调任务天然具备ILC发挥优势的三个前提第一任务重复性高。工业场景中的搬运、装配、喷涂本质上都是循环执行的固定轨迹任务同一个程序每天运行成千上万次。这正是ILC的主场。第二模型不确定性明显。双臂系统的动力学耦合强摩擦、间隙、负载变化带来的模型误差比单臂严重得多。ILC不依赖精确模型它靠“前次误差修正”来逼近理想控制输入这一点在实际系统中非常宝贵。第三双臂协调中的相位同步问题传统反馈控制很难根治。误差存在时左臂和右臂的滞后相位不同会导致负载偏转。ILC通过批次修正可以同时压低两臂的轨迹误差而且误差是逐次递减的能观察到明确的收敛趋势非常适合作仿真演示。所以这个项目的定位就清晰了不追求建模精度不搞复杂的力/位混合控制而是把双臂协调中“轨迹跟踪误差”这个核心矛盾拿出来用ILC去解决让读者看到一个完整的“建模—控制律设计—迭代仿真—结果分析”链路。2. MATLAB建模从动力学方程到可迭代的仿真环境2.1 双臂动力学模型怎么搭才“够用”既然标题是“分享一个简单的迭代学习机器人双臂控制”那建模就不能太复杂否则会喧宾夺主。我采用的是比较经典的两连杆刚性机械臂模型每臂两个旋转关节末端固连一个刚性负载形成主从式闭链结构。单臂的动力学方程可以写成标准形式M(q) * qdd C(q, qd) * qd G(q) tau其中 M(q) 是惯性矩阵C(q, qd) 是科氏力/离心力矩阵G(q) 是重力项tau 是关节驱动力矩。每臂两连杆的参数包括杆长、杆质量、质心位置、转动惯量这些参数在MATLAB中以结构体或者矩阵形式预先定义好即可。双臂模型的关键在于末端约束的处理。为了不让闭链约束的求解把问题复杂化我采用主从式方案主臂按照期望轨迹运动从臂通过闭链约束反解出期望轨迹从臂末端始终对准负载的另一端然后两条臂各自在关节空间做ILC控制。这种处理方式的物理意义是负载在理想情况下没有变形所以从臂的期望轨迹完全由主臂的运动和负载尺寸决定。这么做的合理性在于它把一个闭环耦合问题拆成了两个相对独立的迭代学习问题。ILC本身不要求对象模型绝对精确所以这种“近似解耦”在工程上完全可行仿真结果也能反映出收敛趋势。2.2 MATLAB里怎么把“迭代”这件事表达出来ILC的仿真核心是一个双层循环内层是单次运行用ode45对一个运行周期内的动力学方程做数值积分外层是迭代批次每次迭代结束后更新控制输入。伪代码大致是% 离散时间点一个运行周期 t 0:Ts:T_end; N length(t); % 初始控制输入第一次迭代用零输入或PD反馈前馈 u_history zeros(2, N); for k 1:N_iterations % 重置初始状态 q0 q_init; dq0 dq_init; % 单次轨迹运行求解动力学方程 [t_out, state] ode45((t, x) armDynamics(t, x, u_history, traj), t, [q0; dq0]); % 提取关节角轨迹计算跟踪误差 q_traj state(:, 1:2); qd_ref traj_qd(t); e_k q_ref(t) - q_traj; % 迭代学习更新下一批次的控制输入 u_history u_history Gamma * e_k; % 保存本批次的最大误差用于观察收敛情况 err_max(k) max(abs(e_k)); end注意这里的核心是u_history Gamma * e_k这一行的更新公式它就是ILC的外环修正环节。每一次迭代结束后控制输入都被“加”上了一个与误差成比例的修正量这就是P型ILC中最基本的更新律。当然实际跑的时候有几个细节需要处理一是armDynamics里要读当前时刻的控制输入所以控制输入矩阵要能被插值函数访问到二是如果用了ode45积分步长可能不均匀需要把状态变量插值回统一的时间网格上再计算误差三是从臂和主臂的误差要分别存储方便后面单臂级别的收敛曲线绘制。2.3 期望轨迹设计并不是越复杂越能说明问题我设计的期望轨迹是让主臂末端画一个圆弧频率不能太高。原因是ILC的收敛性分析往往建立在系统满足一定连续性的假设上轨迹频率过高会激励出更强的非线性动态导致学习增益的整定窗口被大幅压缩。具体参数大概是主臂两个关节用五次多项式插值从起始点运动到终止点运动周期设为2秒。负载长度设定为主臂末端和从臂末端的期望距离恒定在15厘米。这样从臂的期望轨迹可以直接通过几何关系解出来不需要额外做逆动力学的数值迭代。这里有一个容易被忽略的细节期望轨迹不仅要给出关节角的位置轨迹还要给出速度轨迹因为ILC更新律中的误差如果只取位置量学习增益的调节会变得很钝如果同时利用位置误差和速度误差就是PD型ILC收敛速度会明显加快。所以在轨迹生成时就计算好参考速度曲线后面用起来会省事很多。3. 迭代学习律的选型与调参P型、PD型到底该信谁3.1 控制律的基本形式从P型ILC说起迭代学习控制的更新律有很多流派但基础框架都是这样u_{k1}(t) u_k(t) L(e_k(t))其中L(e_k(t))是对误差的某种线性或非线性映射。最常见的P型ILC写成u_{k1}(t) u_k(t) Gamma * e_k(t)这里的Gamma是学习增益矩阵它是一个对角阵每臂各关节对应一个增益。P型ILC之所以常用是因为它结构简单、计算量小、收敛条件直观——只要系统相对阶为一即控制输入直接影响状态的一阶导数且Gamma选择满足一定的频谱条件迭代误差就会收敛。我在仿真中的初版控制律就是纯P型。实际跑出来的结果是主臂关节角误差在迭代到第6到8批次时开始显著下降但前两批次误差反而比零输入时还大。这个现象在ILC里面很常见原因在于初始控制输入和真实理想控制输入之间的差距太大第一轮修正方向虽然对但幅度过大导致过冲。解决思路不是去掉P型项而是把增益调小用更多批次去逼近。3.2 PD型ILC什么时候值得把速度误差加进来纯P型ILC的收敛速度不够快尤其是在轨迹跟踪任务的末端阶段误差衰减会出现明显的平台期。我后来把更新律升级为PD型u_{k1}(t) u_k(t) Gamma_p * e_k(t) Gamma_d * de_k(t)其中de_k(t)是误差的导数项对应的是上一批次中关节角速度误差的负值。我观察到加入速度误差项之后最大跟踪误差的收敛批次从第12次提前到了第7次左右末段的抖振也减轻了。不过PD型ILC不是免费的午餐。它对测量噪声更敏感因为误差导数的计算会放大高频噪声。在纯仿真环境里这个问题不明显因为数值积分本身比较“干净”但如果你拿着这套代码去硬件上跑速度项很可能会让控制信号变得毛糙。所以我的建议是仿真阶段可以用PD型快速看到收敛趋势实机验证时再评估是否需要退回到P型或者加低通滤波。下面是我在仿真中摸索出来的一组参考参数针对我这样的双臂模型控制律学习增益收敛批次备注P型ILCGamma_p 0.6 * eye(2)约12批可靠但偏慢PD型ILCGamma_p 0.4 * eye(2), Gamma_d 0.15 * eye(2)约7批收敛快对噪声敏感带线性衰减的P型衰减系数0.95约15批适合噪声较大场景注意以上增益是针对我的模型参数整定出来的你的模型刚度、负载质量、轨迹频率不同增益需要重新试凑。整定的基本思路是从很小的增益开始比如0.1观察最大误差随迭代次数的变化曲线如果误差单调下降就适度增大增益如果出现发散或者振荡立刻减小增益。3.3 一个容易忽略的要点初始控制输入不能乱给ILC的初代控制输入u_0(t)不是随便填的。如果给零输入第一代系统的轨迹会严重偏离期望轨迹虽然ILC理论上能从任意初始输入收敛到理想输入但实际中大幅偏差可能让系统进入非线性区导致收敛困难甚至发散。更稳妥的做法是先跑一个传统的计算力矩控制器或PD控制器把它的输出作为ILC的初始控制输入。也就是说先用常规反馈控制把系统“稳住”再用ILC在“稳”的基础上做前馈修正。这就是业界常说的“反馈前馈”混合框架。我的做法是第一轮用PD控制跑一个周期把该周期的力矩序列作为u_0。这样第一代ILC的误差已经比较小后续迭代可以从一个比较合理的起点开始优化。如果你直接给零输入大概率会看到前几批次的误差曲线非常难看甚至超出坐标轴范围这不是ILC算法错了而是初始输入太激进。4. 收敛曲线之外的细节仿真中那些“合法但不理想”的现象4.1 正常收敛长什么样才能判断代码没有写错跑通ILC之后第一件事不是调参而是确认收敛趋势是正确的。我以最大绝对关节角误差为指标画出它随迭代批次变化的曲线。正常的形态是前几批次误差下降不明显甚至有小幅波动中间阶段快速下降最后进入平台期误差不再明显变化。这个“平台期”并不是ILC失效了而是系统在重复运行中还存在非重复性因素——包括数值积分的误差、参考轨迹插值的误差、以及ILC增益无法完全补偿的高频成分。在我的仿真中平台期误差约为初始误差的7%左右。如果平台期误差过高比如超过20%就要检查是不是学习增益太小或者轨迹频率太高导致的残余误差。我把正常收敛的形态拆出来给读者参考这样你跑出自己的曲线时能马上判断对错。4.2 现象一误差曲线先发散后收敛是什么在捣鬼我第一次把ILC跑通时误差曲线前十几次迭代不降反升一度以为自己公式写错了。后来排查发现问题出在采样时间上。ILC的更新律虽然是连续时间的但实际代码里控制输入是按离散时间网格存储的ode45在每个时间步之间会对控制输入做插值。如果采样周期Ts取得太大比如0.1秒控制输入的时间分辨率太低相当于每个批次都在用一个“粗略版”的控制信号误差里面混入了大量的插值失真。这种失真对ILC的收敛性是个负面扰动表现为前几批次的误差不降反升。解决方法是缩小采样时间到0.01秒控制输入的时间分辨率提高了ILC的收敛曲线恢复正常的单调下降形态。这个经验告诉我们ILC是一个对时间离散化非常敏感的算法采样周期不能只考虑数值仿真的稳定性还要满足“控制输入足以表达期望轨迹细节”这个工程条件。4.3 现象二双臂各自都收敛了但相对位置仍在漂移这是双臂场景里最微妙的问题。单独看主臂和从臂的关节角误差曲线都能收敛但如果把两个末端之间的距离随时间变化的曲线画出来会发现它在期望值15厘米附近有缓慢的漂移。原因在于两条臂的ILC更新是完全独立的它们各自收敛到的“理想控制输入”并不保证相对约束误差也同步收敛。主臂的残余误差和从臂的残余误差在方向上可能不完全对齐导致末端相对位置随时间缓慢变化。这个问题的处理方式不复杂在从臂的控制输入更新时额外加入一个关于相对位姿误差的修正项。具体操作是把“主臂期望位置与从臂当前末端位置之差”映射到从臂关节空间作为附加的前馈修正项叠加到ILC更新之后的控制输入上。这么改完之后末端距离的漂移幅度下降了大约60%。这也算是一个结合双臂场景才能发现的ILC工程问题——单臂ILC里面根本不会出现“相对误差漂移”这个概念这也说明了做系统级仿真时不能只看单臂指标还必须盯着系统级指标。4.4 现象三初始状态偏移对ILC的影响比想象中更大ILC的收敛性分析通常假设每次运行开始时系统状态都严格重置到同一初值。我在仿真中发现如果每次迭代前重置初始状态时存在一个很小的偏差比如关节角偏了0.01弧度最终收敛误差就不是趋于零而是趋于一个与初值偏差相关的稳定值。这说明ILC会对“初值重复性”提出比较苛刻的要求。在MATLAB仿真中这个问题可以通过把初始状态严格定义为常量来规避。但到了真实机器人上每次启动的重复定位精度不可能为零这会是ILC落地硬件时的主要障碍之一。如果你在仿真中想模拟这种真实情况可以在迭代循环里给初始状态加一个小的随机扰动然后观察平台的误差水平。我用均值为0、标准差0.005弧度的扰动测试过收敛后的平台误差大概是理想情况的4倍左右这提示我们在实机部署时初值重复性是一个需要专门做标定和补偿的环节不能直接拿仿真参数硬套。4.5 给排查过程画一条可复用的思路链路如果你也遇到了“ILC仿真结果怪怪的”情况建议按这个顺序排查确认单次运行第一轮迭代本身不发散。如果第一轮轨迹就差到离谱问题不在ILC而在动力学模型或者初始控制输入。确认误差计算用的参考轨迹和实际轨迹在同一时间网格上。插值不一致会产生虚假误差。逐步减小学习增益。很多人一上来就把增益调到很大误差反而振荡这是一种“ILC式的不稳定”调小增益通常能解决。确认采样周期足够小。判断标准是期望轨迹上最快的运动成分一个周期内至少要有20个采样点以上。只有在单臂指标都正常之后再去检查双臂相对指标不要一开始就同时调两个臂的增益那样排查起来完全没有头绪。5. 仿真代码结构里值得借鉴的三个小设计很多初学者写ILC仿真喜欢把整个迭代过程、动力学函数、轨迹生成全部塞进一个主脚本里。这样跑通没问题但一旦要调整参数或者换轨迹代码改起来非常痛苦。我分享一下自己在项目里用的结构设计不需要额外引入复杂的面向对象编程只是简单地把功能模块拆成几个函数。第一个设计是独立的轨迹生成函数。它负责把期望轨迹、期望速度、期望加速度全部算好输出一个结构体。ILC主循环和其他函数都只读取这个结构体不重复计算轨迹。这样换轨迹时只需要改这一个函数排查问题时也只需要对比这个函数的输出。第二个设计是动力学函数与ILC更新函数解耦。动力学函数只接受当前状态和控制函数句柄不管ILC怎么更新ILC更新函数只负责根据误差修改控制输入矩阵不管动力学怎么算。这样既可以方便地替换控制器从P型换成PD型也方便日后扩展成其他机器人模型。第三个设计是把参数集中放到一个param结构体里包括杆长、质量、采样时间、迭代次数、学习增益等。所有函数都以这个结构体为输入参数避免在多个函数里重复定义相同的常量。这样做的好处是调参时可以集中在一个文件里修改不用满项目去搜哪里有“0.6”这个数字。我曾经因为参数分散在不同脚本里调了Gamma忘了调采样时间结果浪费了一天排查时间现在全部集中管理后基本不会再犯这种低级错误。代码风格上我建议所有函数都加上简短的注释特别是ILC更新律那几行把公式的手写版本以注释形式贴出来。这不仅是给别人看的更是给自己三个月后看的。我做这个项目时有一段时间没碰代码重新打开后如果看到的是没有注释的“魔法矩阵”估计又要从头开始推导一遍。6. 从仿真到扩展我对这套方案的后续思考迭代学习双臂控制在MATLAB仿真里跑通只是第一步。我后来尝试过几个扩展方向这里简单聊聊方便你根据自己的需求继续深入。第一把PD型ILC换成自适应ILC。自适应ILC能根据误差在线调整学习增益解决固定增益在误差较大时收敛慢、在误差较小时又容易振荡的问题。我用一个简单的一维增益调整规则做过实验收敛批次比固定增益PD型ILC快了将近30%。实现难度不大在现有的更新律上增加一个增益调整层即可。第二把双臂的主从式结构升级为对称式结构。主从式虽然建模简单但从臂完全依赖主臂的运动这个约束本身限制了系统性能。对称式双臂控制是两条臂各自承担一部分负载需要更仔细地设计协调策略比如加入力/位混合控制或者用分布式ILC。这个方向的文献不少可以在IEEE和Automatica上搜到很多理论分析。第三把MATLAB仿真移植到Simulink环境中。Simulink的优势在于可以方便地接入电机模型、减速器模型、饱和环节还能直接做硬件在环测试。ILC的迭代循环在Simulink里可以用while子系统实现配合to workspace模块记录误差曲线。如果你后续有项目要落地到真实机器人上Simulink是一个比纯脚本更接近实机环境的仿真平台。最后再说一个私人的建议做ILC仿真时别急着追求“一个周期就收敛”的极端效果。迭代学习的价值在于稳定可靠的逐次改进前几批次误差下降不明显反而是正常状态。真正值得关注的永远是“平台的误差水平”和“达到平台所需的迭代次数”这两个指标。如果你的平台误差低于任务需求迭代次数在可接受范围内这个ILC设计就算成功了。非要追求几批之内就收敛到零误差往往会把学习增益调得过大最后落得一个发散的下场得不偿失。
返回列表