ARTICLE DETAIL

资讯详情

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

信捷XDH与EtherCAT多轴运动控制:C语言风格封装实战

信捷XDH与EtherCAT多轴运动控制:C语言风格封装实战 如果你手头刚好有一台信捷XDH的PLC又接过那种需要带五六根轴的非标设备我猜你大概率经历过这样的场景梯形图画到眼睛发花运动控制功能块挂了一排换一个轴型就要从头改逻辑。这篇文章就是围绕这个痛点来的——用EtherCAT总线把多台伺服挂到信捷XDH上再用C语言那套封装思维把运动控制代码重构成好维护、能复用的模块。干这行的人都清楚多轴控制本身不难难的是把代码写得不乱、改起来不头大。我最早接触信捷XDH是因为一个客户的设备需要四轴联动同时还要跑一个视觉定位系统。当时同行给出的方案五花八门有脉冲输出的有走Modbus的也有直接堆功能块的。后来我选了EtherCAT总线方案再把控制逻辑全部用类似C语言的风格重新封装了一层。项目做完之后设备调试时间比预想少了三分之一后面换电机型号、加轴数基本没有伤筋动骨。这篇文章就把这个过程中的选型思路、功能码配置、封装技巧和实战排坑整理出来你拿去能直接套用就不用再从头踩一遍了。1. 方案选型为什么用信捷XDH加EtherCAT做多轴控制1.1 信捷XDH在运动控制上的定位信捷XDH系列PLC在国产品牌里属于比较能打的运动控制型控制器。它不只是传统的逻辑控制PLC本身带很强的运动控制指令集支持多轴插补、电子凸轮、高速定位这些功能。尤其在EtherCAT主站这块XDH把总线和运动控制做进了同一个系统里编程时不需要额外挂上位机或独立的运动控制卡一台PLC就能把逻辑控制、运动控制、通信全部扛下来。实际使用中我比较喜欢它的一点是编程软件XDPPro对IEC 61131-3标准的支持比较完整ST语言、梯形图、功能块图都可以混用。这意味着你完全可以用面向过程的C语言思维来组织代码而不是被梯形图的网络结构困住。对于写过C代码的人来说XDH的ST语言几乎没有学习成本变量声明、赋值、条件判断、循环语法和C非常接近。1.2 EtherCAT总线在多轴设备里的三个关键优势EtherCAT之所以适合多轴运动控制最核心的是它的分布式时钟DC同步机制。普通脉冲控制模式下每根轴是独立接收脉冲各轴之间的同步完全靠PLC扫描周期来保证轴数一多误差就会放大。EtherCAT则是在数据链路层就做了时钟同步多根轴可以做到亚微秒级的同步误差这对四轴联动、飞拍、轨迹插补来说非常关键。第二个优势是拓扑灵活、接线少。我用XDH通过EtherCAT总线串联了六台伺服驱动器整个接线就是一根网线从头串到尾不用像脉冲方式那样每台驱动器都要拉脉冲、方向、编码器反馈一堆线。对于设备调试来说省掉的不是一根线而是大量查线、换线的时间成本。第三个优势是诊断信息丰富。每一个EtherCAT从站都能反馈状态字、报警码、实际位置总线的通信状态、丢帧计数也都能通过主站读取。设备现场有问题时我能在编程软件里直接看到是哪根轴报警、哪个从站掉线不用再拿万用表对着端子排一个个量。1.3 这套方案适合什么场景如果你做的是三轴以上的贴装设备、搬运机械手、小型加工专机或者高速分拣线非常推荐用XDH加EtherCAT的方案。如果是两轴以内、精度要求不高的简单定位用脉冲控制确实成本更低没必要上总线。但只要是超过四轴或者设备将来有扩展的可能性总线方案一定是更明智的选择。我做过的项目里有一个比较典型的场景设备一共六根伺服轴其中两根负责XY平台定位四根负责两个同步工位的升降和夹紧。因为XY平台需要视觉定位后快速移动到目标点四根升降轴又要和主输送线保持同步用脉冲方案光算脉冲频率和加减速时间就要算半天换到EtherCAT之后总线周期设为1ms所有轴都在同一个时间基准下刷新定位和同步的逻辑变得非常清爽。2. 运动控制功能码与总线基础配置2.1 核心运动控制功能块的作用与选择信捷XDH的运动控制指令基本遵循PLCopen规范用的时候就像在调用函数一样。每个功能块会帮我们完成轴选择、模式设置、使能、限位检测等底层动作但我们自己要知道每一条指令适合什么场景否则程序逻辑会乱。我用得最多的几个功能块是功能块作用典型场景MC_Power伺服使能/去使能上电后统一使能所有轴MC_Home回原点设备启动时找零位MC_MoveAbsolute绝对定位按绝对坐标移动到指定位置MC_MoveRelative相对定位从当前位置移动指定距离MC_MoveVelocity恒速运动连续送料、点动、速度模式MC_Stop停止运动暂停或急停时使用MC_Reset报警复位排除故障后恢复轴状态需要注意的是功能块并不意味着直接往程序里一拖就行。每个功能块都有很多输入输出参数比如速度、加速度、减速度、方向、完成标志、错误代码。如果每个轴都单独写一段程序会很臃肿。正确的做法是把这几个功能块统一封装成可复用的接口外部逻辑只要传入轴号和目标位置不用关心功能块内部怎么工作。封装这件事我会在下一章详细说。2.2 轴参数配置中容易被忽视的细节除了功能块轴参数的配置也直接决定设备稳定性。加减速时间、软限位、反向间隙补偿、回零方向这些参数我习惯按轴建立一张参数表单独集中管理。这样有个极大的好处现场调试时改参数不需要去翻程序直接在数据块里找对应轴的配置项就行。加减速时间的设置要根据设备负载和实际工艺去权衡。加速度太高会导致机械冲击甚至触发电机过载报警加速度太低又会影响节拍。我一般的做法是先按电机额定转矩和负载惯量估算一个理论值再在实际运行中慢慢调。如果一台设备上多根轴的负载差异很大一定要给每根轴单独设参数不要图省事统一赋值。反向间隙补偿在机械传动结构里特别重要。伺服电机直连丝杆的情况下丝杆的反向间隙会导致定位方向改变后出现固定偏差。XDH支持按轴设置反向间隙补偿值补偿值一般通过百分表或激光干涉仪测出来。这个参数如果设置得当定位精度提升非常明显。2.3 EtherCAT站地址与从站映射的基本思路EtherCAT组态的第一步是从站配置。每台伺服驱动器作为从站都要有一个站地址这个地址必须和驱动器拨码或软件设定保持一致。配置完成后编程软件会自动扫描到所有从站。在XDPPro里从站配置界面会列出每个从站的输入输出映射包括控制字、状态字、目标位置、实际位置、目标速度、实际速度这些对象。刚开始做总线配置的朋友容易在PDO映射这里被绕晕。其实不用记太多寄存器地址核心就是搞清楚每个从站的输入输出区分别对应驱动器的哪些参数。控制字、目标位置、目标速度这些主站发给驱动器的数据是要写的状态字、实际位置、实际速度、报警码这些驱动器反馈给主站的数据是要读的。把这些映射关系理解成结构体成员变量编程的时候思路就会很顺。我习惯在组态完成后先把几个关键映射值手动写一遍测试程序确认总线通信无误再开始写正式的运动控制逻辑。不要一上来就追求完整程序总线层面的问题必须最先暴露和解决。2.4 总线周期与同步参数的设定原则EtherCAT总线周期决定了整个运动控制系统的刷新频率通常可以设置为1ms、0.5ms甚至更短。周期越短位置控制的响应越快同步性越好但同时会增加主站的运算负载和总线负载。对于大多数常见设备1ms总线周期已经足够用部分对轨迹要求极高的设备可以尝试0.5ms。同步参数的重点在DC模式。启用DC后从站之间通过分布式时钟保持同步所有轴在同一时间点采样反馈、输出控制。这样即使主站程序扫描周期略有波动各轴之间也不会出现明显的相对延迟。碰到过有的同行说EtherCAT轴间同步效果不稳定仔细一看是DC没有启用或者是周期配置和驱动器内部参数不匹配这个坑只要确认好DC模式就不会有。3. C语言风格的封装设计从Axis结构体到底层接口3.1 为什么要在PLC程序里用C语言的封装思维很多人一听“PLC里用C语言封装”会觉得奇怪PLC程序尤其是梯形图不都是一行一行逻辑吗实际不是。信捷XDH支持ST结构化文本ST的语法和C很像变量声明、if else、case、for循环都能用。真正阻碍我们把PLC程序写好的往往不是语言本身而是缺少结构化的设计思维。C语言里做封装核心就是三点用结构体组织数据、用函数封装操作、用接口隔离变化。这三个思想放到PLC程序里完全成立。轴号、位置、速度、状态这些数据可以放进结构体使能、回零、定位、停止这些动作可以封装成函数外部流程只需调用函数接口不需要关心内部使用了哪个功能块。这样做的直接好处就是设备工艺流程变了我只需要改流程层代码运动控制层几乎不用动。之前我见过一个同事写的程序六根轴的定位逻辑散落在好几个子程序里每次改一台设备的工艺就要把所有相关程序翻一遍既不安全也特别费时。用C语言思维重写之后运动控制部分收敛成一个控制模块外部调用只需要给参数程序的可读性和可维护性完全不一样。3.2 用结构体抽象出单轴对象在C语言里我们经常用结构体把一组相关的数据打包成一个对象。放到PLC里我习惯把每一根轴的全部运行时数据封装成一个结构体。虽然XDH的ST语法不完全支持指针和动态内存管理但定义结构体、声明结构体数组这些操作完全没问题。我一般在程序里这样定义轴对象TYPE Axis_Object : STRUCT AxisID : INT; // 轴号 Enable : BOOL; // 使能状态 HomeDone : BOOL; // 回零完成标志 TargetPos : DINT; // 目标位置单位脉冲或用户单位 CurrentPos : DINT; // 实际位置 CurrentVel : INT; // 实际速度 AlarmCode : WORD; // 报警代码 Busy : BOOL; // 运动执行中 Done : BOOL; // 运动完成 Error : BOOL; // 错误标志 END_STRUCT END_TYPE定义好结构体之后程序里的所有轴相关的数据都集中到这个结构体里。调试时打开变量监控表就能一眼看到每根轴当前在做什么状态是否正常非常方便。这种数据集中管理方式比散落各处的全局变量好太多。3.3 把轴初始化封装成一次调用设备上电后所有轴要依次完成参数加载、伺服使能、回零等一系列动作。如果不做封装流程程序里会堆满MC_Power、MC_Home这些功能块的调用看起来很乱。我把初始化过程封装成一个自定义函数块传入轴结构体函数内部自动完成伺服上电使能并等待使能完成。这里有一个非常关键的点MC_Power功能块的执行需要一个“持续使能”信号不能用脉冲形式的变量去触发。很多初学者在这里踩坑——用M变量按一下按钮去置位MC_Power的Enable输入结果伺服刚使能又立刻掉使能。正确做法是用结构体里的Enable状态去控制一旦条件成立就一直为TRUE直到需要下电时才有条件地清掉。初始化封装的另一个好处是方便做顺序控制。设备启动时我先初始化所有轴等待全部轴处于就绪状态后再开始执行工艺流程。这样流程层的代码非常干净不会出现某根轴还没准备好就开始运动的问题。3.4 运动指令的通用封装MoveTo就是这样写出来的在裸功能块层面MC_MoveAbsolute和MC_MoveRelative使用起来差别不小参数也比较多。为了让上层流程代码更简洁我封装了一个统一的轴定位函数。这个函数接收轴号、目标位置、速度、加减速等参数内部根据运动模式自动调用对应的功能块。用类C的伪代码来表示它的核心逻辑大概是这样的FUNCTION_BLOCK Axis_MoveTo VAR_INPUT Axis : REFERENCE TO Axis_Object; Target : DINT; Velocity : INT; Acc : INT; Dec : INT; END_VAR // 等待上一次运动结束后再启动新指令 IF Axis.Busy THEN RETURN; END_IF // 清除旧的执行状态 Axis.Done : FALSE; Axis.Error : FALSE; // 调用底层定位功能块 MC_MoveAbsolute( Axis : Axis.AxisID, Execute : TRUE, Position : Target, Velocity : Velocity, Acceleration : Acc, Deceleration : Dec, Done Axis.Done, Busy Axis.Busy, Error Axis.Error );封装之后工艺流程代码就变得非常简洁。比如设备运行到某个工位我只需要写“Axis_MoveTo(轴1, 5000, 600, 200, 200)”就能完成定位动作。不用关心MC_MoveAbsolute还是MC_MoveRelative也不用关心功能块内部有没有缓冲、要不要清错误。这就是接口隔离的意义。3.5 状态机化处理报警把异常收敛到一条链路多轴设备的异常处理是最容易写乱的。每根轴都有报警、超时、限位触发等异常如果每个地方都加一段告警处理程序会变得非常庞大且难以维护。我处理的方式是对所有轴做一个统一的报警状态机。每根轴的报警状态用一个枚举类型表示例如正常、报警、错误复位中等。程序周期循环时统一调用一个报警检测函数把轴当前状态、功能块错误码、伺服驱动器的报警字读回来映射到状态机里。一旦发现异常就把整个设备流程暂停通过触摸屏或上位机显示出具体的报警轴号和报警内容。这样设计的好处是异常处理逻辑集中在一个地方不会出现这台设备报警后不知道下一步做什么的尴尬情况。而且后续添加新轴时只需要在报警检测函数里加一行调用其他逻辑不用动。4. 多轴联动流程里的函数拆分与执行顺序4.1 把设备流程拆成“动作步骤”而不是“一个个功能块”多轴设备调试时最容易犯的错就是把工艺流程直接写成功能块串联先执行这个轴的定位完成后执行另一个轴的动作再同时执行几根轴的配合。这样写前期看着简单一旦轴数增加或者工艺调整代码就是一团乱麻。我更习惯把整机流程拆成一个个动作步骤每个步骤对应一个函数或者一个状态。比如“取料”、“搬运”、“放料”各自是一个阶段每个阶段内部再调用轴运动的封装函数。这样流程层只关心“做什么”不关心“怎么运动”运动层只关心“怎么运动”不关心“为什么这样运动”。分层清晰后哪怕换一个设备型号流程层代码直接复用运动层接口改动量极小。举个例子一台三轴点胶设备的核心流程可以拆成等待启动信号回零校准移动到拍照位视觉定位计算偏移移动到目标位执行点胶返回待机位。每一步都对应一个独立的STEPSTEP之间的切换条件清晰明确。这样写出来的程序后续维护的人一看就懂不需要在原作者的脑子里走一遍。4.2 四轴搬运工作站的一次代码演示下面我用一个四轴搬运工作站的简化逻辑展示封装之后的调流程层长什么样。假设四根轴分别是X轴、Y轴、Z轴和旋转轴设备要做的是从传送带上取料放到转盘上旋转180度后再取另一侧。流程层伪代码如下CASE Step OF 0: // 空闲状态等待启动 IF StartSignal THEN Step : 10; END_IF 10: // 回零初始化 Axis_HomeAll(); IF AllHomeDone THEN Step : 20; END_IF 20: // 运动到取料位 Axis_MoveTo(X轴, PickPosX, 800, 300, 300); Axis_MoveTo(Y轴, PickPosY, 800, 300, 300); Axis_MoveTo(Z轴, SafeZ, 500, 200, 200); IF AllMoveDone THEN Step : 30; END_IF 30: // 下降取料 Axis_MoveTo(Z轴, PickZ, 300, 150, 150); IF ZAxisDone THEN Wait 200ms; Step : 40; END_IF 40: // 抬起并搬运到转盘 Axis_MoveTo(Z轴, SafeZ, 500, 200, 200); Axis_MoveTo(X轴, RotateCenterX, 800, 300, 300); Axis_MoveTo(Y轴, RotateCenterY, 800, 300, 300); IF AllMoveDone THEN Step : 50; END_IF 50: // 放料并旋转 Axis_MoveTo(Z轴, PlaceZ, 300, 150, 150); RotateAxis 旋转至180度; IF AllDone THEN Step : 60; END_IF 60: // 返回待机位置 Axis_MoveTo(Z轴, SafeZ, 500, 200, 200); Axis_MoveTo(所有轴, HomePos, 800, 300, 300); IF AllMoveDone THEN Step : 0; END_IF END_CASE可以看出来整个流程层没有出现任何一个具体的运动功能块全部是对封装好函数的调用。这就是封装的最终效果流程极其清晰运动细节被隐藏在模块内部。后续若更换成别的型号的伺服只需要调整底层模块流程层一个字母都不用改。4.3 多轴同时启动与顺序动作的判断条件多轴控制里有一个容易混淆的地方多根轴同时启动和每根轴依次执行完再启动下一个这是两种完全不同的时序。封装函数时一定要把“启动”和“完成”两个概念分开。我的做法是每个轴自带的Done标志和Busy标志是内部管理的。流程层判断AllMoveDone就是轮询所有轴结构体里的Done是否全部为TRUE并且Busy全部为FALSE。这里有一个细节功能块的Done信号往往只保持一个扫描周期如果流程状态判断不及时可能会漏掉完成信号。我在封装时会给Done信号做置位保持直到上层逻辑确认已经收到再手动清除。这个细节如果不注意设备经常会出现莫名其妙的卡流程——明明轴已经到位但程序就是没有往下走。最后查下来基本就是完成信号丢失导致的。4.4 高速设备上的时序优化技巧如果设备节拍要求很高比如每分钟要完成几十次取放动作那运动控制的时序优化就很重要了。一个常用的技巧是使用“前瞻启动”在上一根轴还差一小段距离才到位时就提前启动下一根轴或者提前打开视觉拍照、气缸动作等并行逻辑。这样可以把机械等待时间压缩到最低整个周期大大缩短。这种前瞻逻辑的实现在封装层就是给每个运动函数增加一个“到位前提前量”参数。当轴的实际位置达到目标位置减去提前量时就把完成信号提前置位。实际测试中通过调整提前量设备节拍可以提升10%到20%。不过这个优化要建立在机械结构和伺服响应都稳定的前提下不能盲目追求极限速度否则会把小问题放大成大故障。5. 实战中跑不掉的坑总线掉站、回零漂移、报警处理5.1 EtherCAT从站掉线的排查与恢复多轴设备跑了一段时间后有可能会出现某个从站偶发掉线。遇到这种情况首先不要急着换硬件顺序排查这几个点检查网线两端接口是否松动确认线缆是否使用带屏蔽的工业级网线查看现场变频器或大功率电机启动时是否有强电磁干扰导致总线通信异常最终查看主站是否有报警历史记录。EtherCAT对线缆质量的要求比普通以太网高不少我在现场吃过一次亏用的是普通非屏蔽网线结果一开大功率伺服从站就掉线。换成屏蔽工业网线后问题彻底消失。如果你项目的设备周边有大功率变频器、电磁阀或者焊机这一步千万不能省。关于恢复处理我习惯在程序里做总线故障检测和自动恢复机制。发现某个从站掉线时先记录下来尝试重新进入OP状态把相关轴停下来避免位置丢失导致机械碰撞事故。如果自动恢复不成功亮灯并报警维护人员到现场处理。5.2 回零不稳定的常见原因和解决办法回零是多轴设备最基础的环节也是问题最多发的地方。常见的回零不稳定表现为每次原点位置偏差几丝到十几丝或者偶尔回零后设备姿态不对。造成这个问题的原因很多但概率最高的是零点信号处理不当和回零速度过快。零点信号最好使用常开型的接近开关或光电开关信号要经过硬接线滤波避免在原点附近反复抖动。如果回零速度太快感器扫过触发点的瞬间可能来不及停止从而造成位置过冲一次一个位置。我一般把回零速度设为正常运行速度的10%到20%并且分两段先高速找原点开关再低速找Z相脉冲或原点信号。XDH回零功能块一般支持带Z相锁存的方式。这种方式精度最高因为Z相脉冲对应电机转子的固定位置和丝杆的螺距误差无关只要机械连接可靠每次回零位置的一致性非常好。如果你做的设备对重复定位精度要求高尽量用这种方式。5.3 偶尔丢步或位置偏差问题竟然在加减速有段时间我调试的一台设备时不时会出现位置偏差但电机又没有任何报警。查了半天才发现是加减速时间设置得太短导致电机实际运动曲线超出了伺服驱动器的跟随能力。位置偏差不是每次都出现而是偶发性的非常难查。这种问题在总线控制下尤其隐蔽因为EtherCAT通信本身不会丢脉冲控制器发给驱动器的目标位置是完整的。但伺服驱动器的速度环、位置环参数如果配合不上就会出现实际位置和目标位置在动态状态下的跟随误差。时间长了累积误差就变成了位置偏差。解决方法是适当拉长加减速时间同时优化伺服驱动器的位置环增益。此外机械间隙也是位置偏差的重要来源。如果设备是齿轮传动或者皮带传动定期检查张紧度必要时在轴参数里加上反向间隙补偿。有些设备跑一段时间后传动部件松动同样会导致偏差这种问题靠调参数是无法根治的必须做机械检修。5.4 功能块不触发和流程卡死的另类原因有时候程序逻辑看着完全正确但运动就是不启动或者流程走到了某一步突然不走了。除了常见的变量写错、功能块输入没接对之外有两个比较隐蔽的原因值得注意。一是功能块的实例使用重复。在ST语言里多次调用同一个功能块时要使用不同的实例名。如果两次调用用了同一个实例第一次调用的内部状态就会污染第二次调用导致运动指令无法正常触发。我建议把每个运动功能块的实例都纳入轴结构体来管理一个轴对应一套独立的实例从根上避免冲突。二是流程步骤里的扫描周期问题。PLC程序是周期扫描的如果某个等待条件在同一个周期内被多次改写会出现状态判断恰好错过的情况。解决办法是给流程步骤切换增加一个最小时间间隔或者用沿触发的信号来驱动状态跳转而不是直接比较瞬时值。5.5 多轴设备调试中的一些通用建议最后给几条通用性较强的建议可能不针对某一个具体报错但能帮你在现场少走弯路。调试多轴设备时我习惯先把所有轴的伺服使能打开用点动模式一根一根确认运动方向是否正确。方向错了在受控状态下发现还好说一旦写入正式程序后果可能很严重。每一根轴都确认完方向再做回零最后才做联动逻辑。修改任何运动参数前都要把原参数保存到项目文件里方便随时回滚。很多参数之间是有关联的比如速度改了可能加减速也要跟着微调。现场改来改去最后乱了套的情况我见过太多次。遇到比较难排查的问题不要一直猜把PLC内部变量和伺服状态全部录下来配合时间戳分析。XDPPro的监控功能足够强大现场配合在线监控能找到大多数问题。如果用监控都看不出来把日志发回办公室慢慢分析也比盲目改程序强得多。我做这套C语言封装的初衷其实就是为了少想点“轴现在到底在干什么”这种问题。结构体把轴的事归拢到一眼能看全的地方函数把运动指令和流程逻辑隔开状态机把异常收敛成一条可控的链路。设备多轴也好、现场调试压力大也好只要代码结构是清晰的问题出来的时候就不会手忙脚乱。如果你平时用梯形图写习惯了不妨在下一个多轴项目里试试ST加封装的方法。第一次写的时候可能觉得多写了很多结构但等你改第三个、第四个设备的时候就能体会到结构性代码带来的轻松感了。
返回列表