
1. 手动示教的困局为什么码垛场景下它越来越不够用干过现场调试的人都有体会码垛这活儿看着简单实际上是最容易把人耗死的一类应用。一个标准托盘少则三四层多则七八层每层几个到十几个点位你要是老老实实一个一个点去示教一个托盘下来就是几十个示教点。换一个垛型全部重来。客户说“把箱子从600×400换成500×350”你心里就一沉——又得重新示教一遍。问题的根子在于手动示教记录的是“位置”而不是“规律”。码垛的本质是高度重复的几何排列层与层之间有固定的偏移量列与列之间有固定的间距这些规律用示教点是表达不出来的。你示教了第一层的四个角点第二层、第三层的位置其实完全可以通过数学计算推出来但传统示教方式逼着你把每一个都点一遍。我见过太多现场一个码垛程序里塞了上百个示教点程序冗长不说维护起来简直是灾难。哪天机器人零点丢了或者夹具换了个长度所有点位全部作废。更别提有些工况要求混码——同一托盘上不同尺寸的箱子交替摆放手动示教基本没法优雅地处理。Offs函数就是来解决这个问题的。它的逻辑非常朴素你告诉机器人一个基准点再告诉它往X、Y、Z方向各偏移多少它自己算出目标位置。一个基准点加上若干组偏移参数就能覆盖整个码垛阵列。垛型变了改偏移参数就行不用重新示教。夹具换了改基准点就行偏移量岿然不动。这篇文章面向的是已经能跑通基本运动指令、但对码垛编程还停留在“一个点一个点教”阶段的同行。我会把Offs在码垛场景下的完整用法拆开讲清楚包括基准点怎么选、偏移量怎么算、RAPID代码怎么写、实际跑起来会遇到哪些坑。文章末尾会给出一个可以直接拿去改的完整码垛程序框架。2. Offs函数的底层逻辑它到底在坐标系里做了什么2.1 从robtarget的数据结构说起要理解Offs得先搞清楚ABB机器人的robtarget里到底存了什么。一个robtarget包含四个部分trans平移、rot姿态、robconf轴配置、extax外部轴。Offs函数只动trans这一块rot、robconf、extax原封不动地复制过去。trans本身是一个pos类型就是三个数值x、y、z单位毫米。Offs(p1, 100, 0, 50)的意思就是取p1的姿态和配置把它的x坐标加100z坐标加50生成一个新的robtarget。就这么简单。但这里有个关键点很多人没意识到Offs的偏移是在当前工件坐标系wobj下进行的不是在世界坐标系下。如果你在wobj0世界坐标系下用Offs那偏移方向就是世界坐标系的XYZ方向。如果你在自定义的工件坐标系下用Offs偏移方向就跟着工件坐标系走。这个特性在码垛场景下极其重要——当托盘摆放有角度时你只需要把工件坐标系对准托盘边缘之后所有的偏移量都可以按“沿着托盘长边”“沿着托盘短边”来思考不用去算世界坐标系下的分量。2.2 为什么Offs比RelTool更适合码垛ABB还有另一个类似的函数叫RelTool它是在工具坐标系下做偏移的。RelTool(p1, x, y, z)的偏移方向跟着当前TCP的姿态走。如果你的工具是竖直向下的RelTool的z方向就是竖直方向x和y方向跟着工具旋转。码垛场景下绝大多数时候工具姿态是固定的——吸盘或者夹爪始终朝下。这种情况下Offs和RelTool的效果可能差不多。但一旦你的工具需要倾斜比如从侧面伸进去抓取RelTool的偏移方向就会跟着倾斜而Offs始终保持在工件坐标系下的方向。码垛的排列逻辑是“沿着托盘方向”不是“沿着工具方向”所以Offs是更自然的选择。还有一个实际考量Offs的计算开销比RelTool小。在高速码垛循环里每个周期省几毫秒一天下来就是可观的产能差异。当然这个差异在大多数场景下不明显但能省则省。2.3 偏移量的计算从托盘尺寸到RAPID参数假设你有一个标准托盘1200×1000mm箱子尺寸是400×300mm要求每层摆两行三列箱子之间留20mm间隙。我们来算一下偏移量。第一列第一个箱子的放置点假设已经示教好了记为pBase。那么同一行第二个箱子的位置就是pBase往列方向偏移40020420mm。第三个再偏移420mm。换行的时候行方向偏移30020320mm列方向归零。用Offs表达就是! 第一行 Offs(pBase, 0, 0, 0) Offs(pBase, 420, 0, 0) Offs(pBase, 840, 0, 0) ! 第二行 Offs(pBase, 0, 320, 0) Offs(pBase, 420, 320, 0) Offs(pBase, 840, 320, 0)这里假设列方向是工件坐标系的X轴行方向是Y轴。实际项目中你需要根据工件坐标系的定义来对应。如果托盘是斜着放的你只需要把工件坐标系的一个轴对准托盘的列方向另一个轴对准行方向上面的偏移量就不用改。层与层之间的偏移更简单每层的高度就是箱子高度加上层间纸板厚度。假设箱子高200mm纸板厚5mm那第二层就是z方向偏移205mm第三层410mm以此类推。注意层高偏移一定要把纸板、隔板、托盘本身的厚度都算进去。我见过一个项目现场用的是塑料托盘比木托盘厚了15mm结果第一层就撞了。后来把托盘厚度加进基准点的z值里才解决。3. 基准点的选择策略选对了省一半事3.1 为什么基准点不能随便选基准点是整个偏移体系的锚点它的选择直接决定了后续偏移量的复杂程度。选得好偏移量全是整齐的整数选得不好每个偏移量都要带小数调试时改一个数牵动全身。最理想的基准点是托盘上第一个箱子的理论放置位置而且这个位置最好在托盘的某个角上。这样所有其他箱子的偏移量都是正数方向统一不容易搞混。如果你把基准点选在托盘中心那偏移量就有正有负写代码时容易漏掉负号。另一个考量是基准点要方便示教。如果基准点在托盘最外侧的角上机器人可能够不到——因为夹具本身有尺寸TCP能到达的位置和箱子实际放置位置之间有一个偏移。这种情况下你可以把基准点示教在机器人容易到达的位置然后在Offs里把这个夹具偏移补进去。3.2 基准点与工件坐标系的配合我的习惯是先建工件坐标系再示教基准点基准点在工件坐标系下示教。工件坐标系的原点设在托盘的某个角上X轴沿着托盘长边Y轴沿着短边Z轴竖直向上。这样基准点的坐标就是(某个值, 某个值, 某个值)偏移量直接按箱子间距来写。如果托盘摆放有旋转角度工件坐标系可以跟着旋转。示教工件坐标系时用三点法原点、X轴上一点、Y轴上一点。这样不管托盘怎么摆偏移量都不用改。有个细节工件坐标系的Z轴方向要和重力方向一致否则层高偏移会出问题。三点法示教时X轴和Y轴的点要尽量选得远一些这样坐标系的方向精度更高。我一般会在托盘上贴标记用尖头工具去点重复精度能控制在1mm以内。3.3 多托盘场景下的基准点管理实际产线经常有多个托盘——一个在码一个在等码满了叉车换走。这时候基准点怎么管理方案一每个托盘一个工件坐标系每个工件坐标系下示教一个基准点。程序里用wobj参数切换。优点是逻辑清晰缺点是示教工作量大。方案二只示教一个基准点其他托盘通过工件坐标系的偏移来定位。比如托盘间距是1500mm那第二个托盘的工件坐标系就是第一个的工件坐标系偏移(1500, 0, 0)。这样只需要示教一次基准点。方案三用托盘定位装置保证每个托盘的位置一致性所有托盘共用一个工件坐标系和一个基准点。这是最省事的但对机械定位精度要求高。我倾向于方案二兼顾灵活性和工作量。具体做法是在RAPID里定义多个wobj每个wobj的uframe基于第一个wobj的uframe用Offs计算出来。这样换托盘时只需要改一个偏移量。4. 完整RAPID代码拆解从单层到多层码垛4.1 程序框架与数据声明先看数据声明部分。我们需要定义基准点、工件坐标系、以及码垛的参数。MODULE PalletizingModule ! 工件坐标系托盘坐标系 PERS wobjdata wobjPallet : [FALSE, TRUE, , [[0,0,0],[1,0,0,0]], [[0,0,0],[1,0,0,0]]]; ! 基准点第一个箱子的放置位置在wobjPallet下示教 PERS robtarget pBase : [[0,0,0],[1,0,0,0],[0,0,0,0],[0,0,0,0]]; ! 码垛参数 CONST num nBoxL : 400; ! 箱子长度列方向 CONST num nBoxW : 300; ! 箱子宽度行方向 CONST num nBoxH : 200; ! 箱子高度 CONST num nGapL : 20; ! 列间隙 CONST num nGapW : 20; ! 行间隙 CONST num nGapH : 5; ! 层间纸板厚度 CONST num nCols : 3; ! 每层列数 CONST num nRows : 2; ! 每层行数 CONST num nLayers : 5; ! 层数 ! 计算出的偏移量 CONST num nOffL : nBoxL nGapL; ! 列偏移 420 CONST num nOffW : nBoxW nGapW; ! 行偏移 320 CONST num nOffH : nBoxH nGapH; ! 层偏移 205 ENDMODULE这里我把所有尺寸参数都定义成常量好处是改垛型时只改这几个数下面的逻辑代码完全不用动。nOffL、nOffW、nOffH是计算出来的偏移量用常量表达式写RAPID在编译时就会算好不占运行时间。提示PERS和CONST的区别要搞清楚。PERS是持久化变量断电后值还在适合存基准点这种需要示教的数据。CONST是编译期常量运行期间不能改适合存尺寸参数。如果你把基准点定义成CONST程序就没法在线示教了。4.2 单层码垛的循环逻辑单层码垛的核心是两层循环外层遍历行内层遍历列。PROC PalletizeOneLayer(num nLayerIndex) VAR num i; VAR num j; VAR robtarget pTarget; FOR i FROM 0 TO nRows - 1 DO FOR j FROM 0 TO nCols - 1 DO ! 计算目标位置 pTarget : Offs(pBase, j * nOffL, i * nOffW, nLayerIndex * nOffH); ! 移动到目标位置上方的安全高度 MoveJ Offs(pTarget, 0, 0, 100), v1000, z50, tool0 \WObj:wobjPallet; ! 直线下降到放置位置 MoveL pTarget, v300, fine, tool0 \WObj:wobjPallet; ! 这里插入夹具释放动作 ! ReleaseGripper; ! 直线上升回安全高度 MoveL Offs(pTarget, 0, 0, 100), v500, z50, tool0 \WObj:wobjPallet; ENDFOR ENDFOR ENDPROC这段代码有几个值得说的点。第一pTarget的计算用Offs一次性完成三个方向的偏移而不是嵌套调用Offs。嵌套调用也能实现但可读性差而且每次调用都有计算开销。一次性算完最干净。第二接近和离开用安全高度。直接从上方100mm处下降放完再升回去。这个100mm是经验值根据夹具高度和现场障碍物调整。如果托盘周围有护栏安全高度要高于护栏。第三MoveJ和MoveL的配合。在安全高度以上的移动用MoveJ速度快路径不要求精确。从安全高度到放置点是MoveL保证垂直下降避免夹具碰到已放置的箱子。放置点用fine确保精确到位。第四\WObj:wobjPallet不能漏。如果漏了Offs就在世界坐标系下计算偏移方向全错。这个错误很隐蔽因为程序能跑只是位置不对。4.3 多层码垛与层间过渡多层码垛就是在单层的基础上加一层循环同时处理层间的过渡动作。PROC PalletizeFullPallet() VAR num k; FOR k FROM 0 TO nLayers - 1 DO PalletizeOneLayer k; ! 层间过渡如果需要放纸板在这里插入 IF k nLayers - 1 THEN ! PlaceCardboard k; ! 等待纸板放置完成 WaitTime 2; ENDIF ENDFOR ENDPROC层间过渡看起来简单实际上是最容易出问题的地方。纸板放置后可能有翘曲导致下一层的实际高度比理论值高。如果层数多累积误差会越来越大。我的做法是每层的z偏移量不要用理论值累加而是在每层开始前用传感器测一下实际高度动态修正。如果现场没有测高传感器那至少要在程序里留一个手动修正的接口。比如加一个PERS num nLayerComp每层的z偏移加上这个补偿值。调试时根据实际码垛效果调整。另一个坑是纸板放置后机器人的下降位置要相应提高。因为纸板本身有厚度如果你还用原来的z偏移夹具会压到纸板上。正确的做法是把纸板厚度加进层偏移里这就是为什么nOffH nBoxH nGapHnGapH就是纸板厚度。4.4 混码场景的扩展思路混码——同一托盘上不同尺寸的箱子——是码垛里最复杂的场景。但用Offs的思路混码也可以处理得很优雅。核心思路是把托盘划分成若干区域每个区域有自己的基准点和偏移参数。比如托盘左半边放大箱右半边放小箱。大箱区域的基准点是pBaseL小箱区域的基准点是pBaseR。两个区域各自用自己的nOffL、nOffW。更复杂的情况是同一层里不同箱子交替排列。这时候可以用一个数组来定义每个位置的箱子类型和偏移量。CONST num nPattern{6} : [1, 2, 1, 2, 1, 2]; ! 1大箱2小箱 PROC PalletizeMixedLayer(num nLayerIndex) VAR num i; VAR robtarget pTarget; VAR num nOffsetX; nOffsetX : 0; FOR i FROM 1 TO 6 DO IF nPattern{i} 1 THEN pTarget : Offs(pBase, nOffsetX, 0, nLayerIndex * nOffH); nOffsetX : nOffsetX nOffL_Large; ELSE pTarget : Offs(pBase, nOffsetX, 0, nLayerIndex * nOffH); nOffsetX : nOffsetX nOffL_Small; ENDIF ! 放置动作... ENDFOR ENDPROC这个模式可以扩展到任意复杂的排列。关键是维护好nOffsetX这个累加变量它记录了当前箱子在列方向上的累计偏移。5. 现场调试中那些文档不会告诉你的坑5.1 工件坐标系的方向搞反了这是新手最常犯的错误。示教工件坐标系时X轴和Y轴的方向定义反了结果Offs的偏移方向跟预期差90度。程序跑起来箱子全摆到托盘外面去了。排查方法在示教器上手动移动机器人到基准点然后手动移动到一个偏移位置看机器人实际走的方向和你在代码里写的偏移方向是否一致。如果不一致检查工件坐标系的定义。预防措施在托盘上贴方向标记X轴用红色箭头Y轴用蓝色箭头。示教时对着标记点不容易搞错。5.2 层高累积误差导致撞机前面提过纸板翘曲、箱子高度不一致、托盘不平这些因素都会导致层高累积误差。层数少的时候看不出来层数一多就撞了。我的做法是在程序里加一个层高补偿变量每层码完后根据实际效果手动调整。如果发现某一层开始偏低就把补偿值调大。这个变量是PERS类型调试时在示教器上直接改不用重新下载程序。更彻底的方案是加一个测高传感器每层开始前测一下当前垛顶的高度自动计算补偿值。但这个方案成本高小项目不一定划算。5.3 Offs的偏移量超出机器人工作空间Offs只是数学计算它不会检查计算结果是否在机器人工作空间内。如果你算出来的目标点机器人够不到程序运行时会报“位置不可达”错误然后停机。预防方法在程序里加一个范围检查。用ReadMotor或CalcJointT检查目标点的关节角度是否在限位内。如果超出提前报警而不是等到运动指令执行时才报错。FUNC bool CheckReachable(robtarget pTarget) VAR jointtarget jt; VAR bool bOK; bOK : TRUE; jt : CalcJointT(pTarget, tool0 \WObj:wobjPallet); ! 检查各轴是否在限位内 IF jt.robax.rax_1 -170 OR jt.robax.rax_1 170 THEN bOK : FALSE; ENDIF ! 其他轴类似检查... RETURN bOK; ENDFUNC这个检查会增加一点计算时间但在调试阶段非常有用。正式生产时可以去掉或者只在第一个循环里检查。5.4 夹具偏移没有补偿Offs计算的是TCP的目标位置但箱子实际放置的位置和TCP之间有一个夹具偏移。比如吸盘吸住箱子后箱子底面比TCP低50mm。如果你直接让TCP到目标位置箱子就放高了50mm。解决方法在基准点示教时就考虑夹具偏移。示教时让TCP停在目标位置上方50mm处这样基准点本身就包含了夹具偏移。或者在Offs里把夹具偏移加进去Offs(pBase, x, y, z - 50)。我倾向于第一种方法因为基准点示教更直观。示教时看箱子实际放到位了就记下这个位置不用去算TCP和箱子底面的关系。5.5 速度参数设置不当导致箱子移位码垛时MoveL的下降速度不能太快。太快的话箱子在惯性作用下会移位尤其是轻箱子。我一般用v300到v500具体看箱子重量和摩擦系数。加速和减速参数也要注意。z50表示转弯区数据意思是机器人到达目标点前50mm就开始转弯不精确到位。放置点必须用fine确保精确到位。但接近点可以用z50提高效率。还有一个细节如果箱子之间间隙很小转弯区要设小一些否则机器人转弯时会碰到已放置的箱子。间隙20mm的话z设10mm比较安全。6. 从Offs到更高效的码垛方案6.1 用数组预计算所有点位如果码垛的点位是固定的可以在程序初始化时把所有目标点算好存到数组里运行时直接取用不用每次循环都调Offs。这样能省一点计算时间但更重要的是代码更清晰。VAR robtarget pAllTargets{15}; ! 3列×2行×5层30个点这里简化 PROC InitTargets() VAR num i, j, k; VAR num nIndex; nIndex : 1; FOR k FROM 0 TO nLayers - 1 DO FOR i FROM 0 TO nRows - 1 DO FOR j FROM 0 TO nCols - 1 DO pAllTargets{nIndex} : Offs(pBase, j * nOffL, i * nOffW, k * nOffH); nIndex : nIndex 1; ENDFOR ENDFOR ENDFOR ENDPROC这样主循环里只需要遍历数组逻辑更简单。而且数组里的点位可以在示教器上查看和修改方便调试。6.2 与视觉系统的配合如果来料位置不固定需要视觉系统定位Offs依然有用。视觉系统给出箱子在托盘坐标系下的位置你用Offs把这个位置和基准点结合起来算出最终放置点。! 视觉系统给出的偏移量 VAR num nVisionX; VAR num nVisionY; VAR num nVisionAngle; ! 计算最终放置点 pTarget : Offs(pBase, nVisionX, nVisionY, nLayerIndex * nOffH); ! 姿态需要根据角度调整 pTarget.rot : OrientZYX(nVisionAngle, 0, 0);注意姿态的调整不能用OffsOffs只改位置不改姿态。姿态要用OrientZYX或类似的函数重新计算。6.3 节拍优化的几个方向码垛的节拍直接影响产能。用Offs本身不会提升节拍但合理的程序结构可以。第一个方向是减少不必要的精确到位。只有放置点用fine其他点都用z转弯区。转弯区越大机器人越流畅节拍越快。第二个方向是优化路径。码完一列后不要回到安全高度再移动到下一列而是直接在安全高度上平移过去。这需要把安全高度的移动和水平移动合并。第三个方向是预读取下一个点位。在机器人还在移动时就计算好下一个目标点减少等待时间。RAPID是顺序执行的这个优化需要用到中断或后台任务比较复杂但效果明显。7. 一个可以直接复用的码垛程序模板把上面的内容整合起来给出一个完整的、可以直接改参数使用的码垛程序。这个模板假设托盘是规则的箱子尺寸一致层数固定。MODULE PalletizingTemplate ! 数据声明 PERS wobjdata wobjPallet : [FALSE, TRUE, , [[0,0,0],[1,0,0,0]], [[0,0,0],[1,0,0,0]]]; PERS robtarget pBase : [[0,0,0],[1,0,0,0],[0,0,0,0],[0,0,0,0]]; PERS robtarget pSafe : [[0,0,0],[1,0,0,0],[0,0,0,0],[0,0,0,0]]; CONST num nBoxL : 400; CONST num nBoxW : 300; CONST num nBoxH : 200; CONST num nGapL : 20; CONST num nGapW : 20; CONST num nGapH : 5; CONST num nCols : 3; CONST num nRows : 2; CONST num nLayers : 5; CONST num nOffL : nBoxL nGapL; CONST num nOffW : nBoxW nGapW; CONST num nOffH : nBoxH nGapH; PERS num nLayerComp : 0; ! 层高补偿 ! 主程序 PROC main() ! 初始化 ! 这里可以加回原点、开夹具等动作 ! 开始码垛 PalletizeFullPallet; ! 结束动作 ! 这里可以加回原点、关夹具等动作 ENDPROC ! 码整垛 PROC PalletizeFullPallet() VAR num k; FOR k FROM 0 TO nLayers - 1 DO PalletizeOneLayer k; IF k nLayers - 1 THEN ! 放纸板动作 WaitTime 2; ENDIF ENDFOR ENDPROC ! 码一层 PROC PalletizeOneLayer(num nLayerIndex) VAR num i; VAR num j; VAR robtarget pTarget; VAR num nZOffset; ! 计算当前层的z偏移包含补偿 nZOffset : nLayerIndex * nOffH nLayerComp * nLayerIndex; FOR i FROM 0 TO nRows - 1 DO FOR j FROM 0 TO nCols - 1 DO pTarget : Offs(pBase, j * nOffL, i * nOffW, nZOffset); ! 移动到安全位置 MoveJ Offs(pTarget, 0, 0, 100), v1000, z50, tool0 \WObj:wobjPallet; ! 下降到放置点 MoveL pTarget, v300, fine, tool0 \WObj:wobjPallet; ! 夹具释放 ! ReleaseGripper; WaitTime 0.3; ! 上升回安全高度 MoveL Offs(pTarget, 0, 0, 100), v500, z50, tool0 \WObj:wobjPallet; ENDFOR ENDFOR ENDPROC ENDMODULE这个模板的使用方法先把wobjPallet用三点法示教好再把pBase示教到第一个箱子的放置位置然后根据实际箱子尺寸修改那几个CONST常量。跑起来之后如果发现层高有偏差调整nLayerComp。注意模板里的WaitTime 0.3是夹具释放后的等待时间根据实际夹具调整。吸盘的话可以短一些机械夹爪要长一些确保完全松开再上升。8. 写在最后Offs不是万能药但码垛场景下它最趁手Offs函数本身很简单简单到很多人觉得它没什么可讲的。但正是这种简单让它在码垛这种高度规律性的场景下变得极其高效。一个基准点几个偏移参数就能描述整个垛型。改垛型不用重新示教改参数就行。这是手动示教永远做不到的。当然Offs也有它的边界。如果来料位置完全不固定需要视觉系统实时定位Offs只能作为最后一步的偏移计算。如果垛型极其复杂每个箱子位置都不一样那可能需要更复杂的算法甚至用外部计算机算好点位再传给机器人。但在80%的码垛场景下Offs加上合理的程序结构已经足够解决问题。我在实际项目里用这套思路做过不少码垛应用从简单的单列码垛到复杂的混码Offs都是核心工具。踩过的坑主要集中在工件坐标系的方向、层高累积误差、夹具偏移这几个方面。这些问题在文档里往往一笔带过但现场调试时能卡你半天。希望这篇文章把这些坑都摊开讲清楚了让你少走点弯路。如果你正在做一个码垛项目不妨先把基准点和工件坐标系搞扎实然后用Offs把第一层跑通。第一层没问题了多层就是加个循环的事。混码的话先把区域划分清楚每个区域独立处理。一步一步来码垛没那么可怕。