干设备调试这几年,我越来越离不开Codesys里的软运动控制。最近用SMC_FreeEncoder给ECAT轴搭了一个虚拟手轮调试工具,彻底告别了笨重的物理手轮和按钮点动,今天把整个思路和实操过程完整复盘一遍。这个方案对那些经常做单机调试、对刀对基准、机械验机的朋友尤其有用,不管你是刚接触软运动控制,还是已经在工程上用过手轮,都能从中找到可以直接落地的东西。
1. 虚拟手轮调试工具的价值定位与方案选型
1.1 设备调试台上缺少一个“顺手的旋钮”
做过现场调试的人都懂这种尴尬:设备出货前,操作员想在触摸屏上动轴,但按钮点动永远只有固定速度,太快怕撞件,太慢等到心慌。有的设备机械结构复杂,对刀、对基准、检查正负方向这些精细操作,靠按钮根本做不出“手感”。
物理手轮确实好用,但问题也不少。一台设备如果每个需要手动操作的轴都配一个物理手轮,成本、安装空间、接线量都上去了。增量编码器手轮要接A/B相脉冲,还要考虑5V/24V电平匹配,有的手轮自带零点信号,接线稍不注意就丢脉冲。更别提有些机柜已经塞得满满当当,根本没地方再开一个手轮安装孔。
我之前也被这个问题逼得没办法,试过用触摸屏上的“软手轮”控件,但触摸屏没有真实的旋转反馈,操作员根本不知道当前转到哪一格,误触风险也大。后来在Codesys里用SMC_FreeEncoder做了个纯软件虚拟手轮,效果出乎意料地好,调试效率提升了一大截。
SMC_FreeEncoder能在软PLC内部模拟出一个增量编码器的输出信号,不占用任何物理IO,也不需要额外接线,只要在程序里触发一下,它就能按设定速度、加速度“转”起来,并实时输出当前编码器位置。配合SoftMotion的轴跟随功能,可以把ECAT轴变成被虚拟手轮驱动的从轴,实现真正意义上的“虚拟手轮”。
1.2 为什么是SMC_FreeEncoder而不是其它方案
可能有人会问,Codesys里有MC_MoveJog,直接让轴点动不就行了吗?为什么还要绕一圈用虚拟编码器?
MC_MoveJog确实能做手动点动,但它本质上是一个速度指令,轴按设定速度持续运动。手轮调试的核心诉求不是“以一个速度跑”,而是“手轮转多少,轴就走多少,转得快就走得快,停下来就立刻停下”。这种位置跟随关系,用Jog很难实现,而且Jog的点动速度是固定值,没有手轮那种“微调一格”的精细感。
还有人会想到用硬件高速计数模块接物理手轮。这个方案能用,但成本高、需要额外配置,而且我要在软件里做倍率切换、正反向控制、限位互锁,全部得写一堆逻辑,灵活性远不如纯软件方案。
SMC_FreeEncoder在这个问题上是比较优雅的解法。它输出的是一路“虚拟编码器信号流”,SoftMotion的轴功能块可以直接把它当成真实编码器的位置来用。我们只需要在程序里调用一次,它就会在后台持续生成脉冲计数值,不占物理接口、没有接线衰减、可反复启停且不丢位置。
用个生活类比来说,SMC_FreeEncoder就像一个“虚拟信号发生器”,专门给运动控制发脉冲。真实手轮是物理设备,手一转动就有A/B相脉冲进来;虚拟手轮则是在程序内部模拟出这些脉冲,再通过SoftMotion的位置跟随功能块,把它“喂”给ECAT轴。结果就是:手轮转一圈、脉冲数增加多少、轴跟着走多远,全部由参数控制,调试起来非常灵活。
2. SMC_FreeEncoder功能块内部机制与关键参数
2.1 引脚功能的完整解读
SMC_FreeEncoder这个功能块上手并不复杂,但几个关键参数如果不理解透,很容易出现“手轮转了但轴不动”或者“一启动轴就飞车”的情况。以我常用的版本为例,核心引脚大致如下:
| 参数名 | 类型 | 作用说明 |
|---|---|---|
| bExecute | BOOL | 启动虚拟编码器。上升沿触发启动,置FALSE时停止计数 |
| bAccEnable | BOOL | 是否启用加减速斜坡。TRUE时按设定的加速度平滑提速/减速 |
| udiAcceleration | UDINT | 加速度值,决定速度变化的快慢。单位需根据库版本换算,通常按转/分钟² |
| udiInstantSpeed | UDINT | 目标转速,单位通常为转/分钟(rpm) |
| udiResolution | UDINT | 编码器分辨率,即每转输出的脉冲数,相当于物理编码器的PPR |
| udiCurrentPosition | UDINT | 当前编码器位置计数值,程序里读这个值就知道手轮“转了多少” |
需要注意,不同Codesys版本和SoftMotion库版本里,个别引脚名称或单位可能有差异。比如有的版本把udiInstantSpeed叫udiSpeed,有的版本输出位置变量叫udiCurrentPosition而不是udiPosition。这类细节建议以你本机安装的库文件说明为准,但整体逻辑是一致的。
在实际使用中,bExecute是比较容易出错的地方。如果只是给它一个很窄的脉冲(TRUE保持一个周期就变FALSE),功能块会认为是一次性执行请求,可能还没转到目标速度就停了。所以如果你拿按钮控制启动,需要把按钮信号直接接到bExecute上,保持持续使能,而不是做上升沿触发。
2.2 从“信号发生器”角度理解工作流程
理解SMC_FreeEncoder的底层行为,我觉得把它当成“信号发生器”最清晰。启动后,它内部会有一个计数器从0开始累加,累加速度由udiInstantSpeed和udiResolution共同决定,每秒钟增加的脉冲数就等于转速乘以每转脉冲数再除以60。
如果不启用bAccEnable,计数器从0立刻跳到目标速度,相当于手动开关一开就全速转,这样轴会有冲击。如果启用bAccEnable,它会按照udiAcceleration设定的斜率,从0逐渐加速到目标速度,停下来时也一样,这样模拟出来的手轮手感更像真实设备。
它输出的udiCurrentPosition本质上是“手轮已经转过的累计脉冲数”。这个值只增不减(当然也可以反转方向,看库版本支持),类型是UDINT无符号整数,意味着它会在到达最大值后回绕为0。真实物理编码器也有类似问题,但调试时如果不处理回绕,位置跟随逻辑就可能出大问题。后面第3章我会讲怎么处理这个坑。
还有一点要特别说清楚:SMC_FreeEncoder并不读取物理位置,它只是一个开环的位置指令发生器。如果轴被机械卡住、被限位挡住或者伺服报错,虚拟手轮这边是不知道的,它的脉冲数还在继续涨。所以使用虚拟手轮调试时,软件限位、急停回路、使能互锁这些东西一个都不能少,这个后面实操部分还会展开。
2.3 编码器分辨率、速度、位置换算的“算账”方法
既然手轮的本质是脉冲,那么手轮转一圈轴走多远,就取决于分辨率、机械传动比、丝杠导程以及比例因子的综合换算。
先建立一个基本公式。虚拟手轮的输出脉冲频率为:
脉冲频率(pps)= udiInstantSpeed(rpm)× udiResolution(脉冲/转)/ 60如果轴侧每转对应的机械位移是D(比如丝杠导程10mm,减速比1:1,则D=10mm/转),那么在1:1跟随模式下,手轮转一圈,轴的理论位移是:
轴位移 = (手轮脉冲数 / 分辨率)× D × fPositionScale举个例子:udiResolution=1000,手轮转了5000个脉冲,也就是5圈。轴侧丝杠导程10mm,fPositionScale=0.1,那么轴实际走了:
手轮转数 = 5000 / 1000 = 5转 轴位移 = 5 × 10mm × 0.1 = 5mm这就是一个典型的“手轮转5圈,轴走5mm”的微调场景,非常适合对刀、对基准这类精细操作。如果你希望手轮转1圈轴走1mm,就把fPositionScale设为0.1;希望手轮转1圈轴走10mm,就设为1.0。倍率切换的本质其实就是修改这个比例因子。
实际操作中我的经验是:分辨率不要设得太小,也不要太大。设太小(比如100),手轮位置每跳一格的步距太大,调起来一格一格地冲;设太大(比如10000),同样的手轮转动圈数下脉冲数太多,如果不小心倍率切到100x,轴会瞬间飞出去。我一般起步先用1000左右,倍率从1x开始验证,确认方向、行程、限位都没问题,再逐步切到10x、100x。
3. 完整实操:建工程、写程序、接轴联动
3.1 工程初始化与SoftMotion环境准备
在开始写代码之前,先把工程环境搭好。我这里用的是Codesys 3.5 SP系列,需要先在包管理器里安装SoftMotion相关库。不同版本名字可能略有差异,常见的是SoftMotion或者SoftMotion SoftMotion CNC,安装后才能在设备树里添加运动控制相关的轴对象。
如果你现在手头没有真实ECAT轴,也可以先用仿真轴跑通逻辑。Codesys的PLC仿真模式配合SoftMotion的虚拟轴,能完成整个虚拟手轮的逻辑验证,这一点非常方便。等逻辑验证没问题了,再接上真实EtherCAT主站和伺服轴。
以真实ECAT轴为例,大致步骤如下:
- 在设备树中添加EtherCAT主站,扫描并添加从站设备。
- 在运动控制组里新建一个轴,轴类型选择与驱动器匹配的ECAT轴类型,比如AXIS_REF_ECAT。
- 配置轴的参数,包括电机每转脉冲数、电机最大转速、加减速时间、软件正负限位、回零方式等。
- 设置任务周期。EtherCAT同步周期通常设1ms或2ms,SoftMotion的运动控制任务也建议工作在同样的周期下,避免数据不同步。
- 在PLC程序中声明轴变量为
AXIS_REF_ECAT,并绑定到设备树的轴对象。
这里有个新手容易踩的坑:SoftMotion轴如果没有完成基本的轴参数配置,比如每转脉冲数没有填,或者使能逻辑没写,后面SMC_FollowPosition就算被调用,轴也不会动。我的习惯是先写一个最基础的单轴使能程序,让轴能正常使能和回零,再开始接虚拟手轮。
3.2 核心ST代码实现虚拟手轮信号源
下面直接给出一份完整的核心代码。这段代码实现了虚拟手轮信号源的启动、倍率切换、增量位置累加和轴跟随。变量声明如下:
VAR_GLOBAL // 虚拟手轮控制 bWheelEnable : BOOL := FALSE; // 手轮总使能 bWheelButtonOn : BOOL := FALSE; // 手轮启动按钮(保持型) bAccEnable : BOOL := TRUE; // 是否启用加减速斜坡 udiAcceleration : UDINT := 300; // 加速度设定 udiInstantSpeed : UDINT := 30; // 目标转速 rpm udiResolution : UDINT := 1000; // 每转脉冲数 // 倍率切换 iMul : INT := 1; // 当前倍率 1/10/100 fMulScale : LREAL := 1.0; // 倍率对应的比例因子 // 虚拟手轮状态 fbVirtualWheel : SMC_FreeEncoder; udiWheelPos : UDINT; // 当前手轮位置脉冲 udiLastWheelPos : UDINT; // 上次扫描周期的手轮位置 diDeltaPulses : DINT; // 本次周期手轮增量脉冲数 fRefPosSum : LREAL := 0.0; // 轴目标位置累加值 // 轴跟随 fbFollowPos : SMC_FollowPosition; AxisECAT : AXIS_REF_ECAT; END_VAR主程序调用逻辑:
// 1. 启动虚拟手轮编码器 fbVirtualWheel( bExecute := bWheelButtonOn, bAccEnable := bAccEnable, udiAcceleration := udiAcceleration, udiInstantSpeed := udiInstantSpeed, udiResolution := udiResolution, udiCurrentPosition => udiWheelPos ); // 2. 计算手轮增量,处理UDINT回绕 IF udiWheelPos >= udiLastWheelPos THEN diDeltaPulses := DINT_TO_LREAL(udiWheelPos - udiLastWheelPos); ELSE // 计数器回绕处理,这里按最大值1000000为例 // 实际应根据UDINT最大值调整 diDeltaPulses := (UDINT_TO_LREAL(udiWheelPos) + 1000000.0 - UDINT_TO_LREAL(udiLastWheelPos)); END_IF udiLastWheelPos := udiWheelPos; // 3. 累加目标位置,乘以倍率因子 // 除以1000是为了把脉冲换算成“手轮转数”,再乘以机械导程10mm IF bWheelEnable THEN fRefPosSum := fRefPosSum + (LREAL_TO_REAL(diDeltaPulses) / 1000.0 * 10.0 * fMulScale); END_IF // 4. 用SMC_FollowPosition驱动ECAT轴 fbFollowPos( bExecute := bWheelEnable, fReferencePosition := fRefPosSum, fPositionScale := 1.0, fVelocityScale := 1.0, fAccelerationScale := 1.0, Axis := AxisECAT );这段代码有几个关键设计点要重点解释。首先是增量式累加,我没有直接把udiWheelPos丢给SMC_FollowPosition,而是先算出手轮当前周期增加了多少脉冲,再累加到fRefPosSum里。这样做的好处是:切换倍率时不会造成位置跳变。如果直接用绝对位置乘以倍率,一倍率从1x切到10x,参考位置立刻变成原来的10倍,轴会瞬间冲出去,非常危险。改用增量累加后,切换倍率只影响后续移动的步距,当前轴位置完全不受影响。
其次是除以1000和乘以10的关系。这里/1000是把脉冲数转换回手轮转数,因为分辨率是1000脉冲/转;*10是机械导程10mm。这两步合起来,手轮转1圈,fRefPosSum增加10mm,正好对应轴侧的机械位移。如果你的设备机械传动比不是1:1,或者导程不同,记得改这两个数值。
3.3 让虚拟手轮驱动ECAT轴的两条联动路径
把SMC_FreeEncoder生成的脉冲转化为轴运动,常见有两条路径。我上面用的是SMC_FollowPosition绝对位置跟随,这是最常见的一种,适合“手轮转多少,轴走多少”的位置同步场景。它的好处是SoftMotion内部会按照参考位置的变化自动规划速度,比例因子可以灵活调整,代码量也少。
第二条路径是先把虚拟手轮的增量换算成速度值,然后调用MC_MoveVelocity或者自定义的速度斜坡去驱动轴。这条路径适合那些不想用SMC_FollowPosition、希望通过速度环手动控制轴的场景。比如你希望在手轮转动的同时,根据某些工艺条件动态调整速度上下限,或者需要在手轮模式和其他运动模式之间同时做复杂的切换,用速度驱动会更灵活。
不过我的实际项目经验是:如果只是单纯做手动调试手轮,SMC_FollowPosition的绝对位置跟随更省心,因为它天然支持位置闭环,手轮停了轴就停,中间不会累积误差。速度驱动方式一旦速度换算有偏差,轴会慢慢漂移,必须额外做位置校验。
还有一些老项目里可能用到SMC_Interpolator或者CNC插补器来接收外部位置参考。理论上也能接虚拟手轮,但我个人觉得杀鸡用牛刀了,SMC_FollowPosition完全够用,代码可读性也更好,后续维护的人不容易看懵。
3.4 倍率切换与限位保护的设计细节
倍率切换是最容易出问题的地方。我见过同行把倍率切到100x后轴直接撞限位,就是因为切换逻辑没有处理好位置基准。用增量累加的方式后,倍率切换变得非常简单和安全:只需要改变fMulScale的值,后续每周期累加的位置增量就会自动按新倍率走,而轴当前的位置不会跳变。
HMI上的倍率按钮逻辑可以这样写:
// 倍率切换 IF bMul1 THEN iMul := 1; ELSIF bMul10 THEN iMul := 10; ELSIF bMul100 THEN iMul := 100; END_IF fMulScale := INT_TO_LREAL(iMul);同时要在程序里做一个速度上限保护。不管倍率是多少,轴的指令速度不能超过机械允许的最大值。SoftMotion的轴参数里有一个最大速度设定,这个一定要设好,相当于在系统层面给轴上了保险。我在倍率逻辑里还会加一道软件限制:如果计算出来的目标增量超过某个阈值,就直接把它截断,宁可让操作员觉得手轮“变钝”了,也不能让轴飞出去。
限位保护方面,轴的软件正负限位必须配置正确。当轴运动到软件限位附近时,SMC_FollowPosition会怎样表现,不同版本可能不太一样。我遇到过的情况是:轴到达限位后不再动作,但fRefPosSum还在继续增大,导致限位解除瞬间轴猛冲一下。这个问题需要单独处理,最简单的办法是在累加目标位置之前判断轴是否在限位附近,如果在,就不累加增量,或者同时把fRefPosSum同步到当前轴位置值。
另外,急停信号要参与虚拟手轮使能链。急停按下时,不仅伺服要掉使能,还要把bWheelEnable置FALSE,同时把fRefPosSum重新同步为轴当前位置,避免急停恢复后出现位置突变。
4. 实测排查:虚拟手轮调试中的高频问题
4.1 功能块启动了,轴却没有动
这是很多人第一次接虚拟手轮时遇到的首个问题。功能块状态看着正常,udiWheelPos也在变化,但轴就是纹丝不动。排查思路按下面的顺序来:
第一步,检查SMC_FollowPosition的bExecute是不是持续为TRUE。如果只用了一个上升沿信号,它可能只执行了一个周期就中止了。第二步,看轴是否处于使能状态。SoftMotion的轴必须被正确使能,SMC_FollowPosition发出的位置指令才能被真正执行。第三步,检查SMC_FollowPosition的fPositionScale、fVelocityScale、fAccelerationScale有没有设成0。这三个比例因子如果是0,轴接收到参考位置也是0,自然不动。第四步,在线监控fbFollowPos.bBusy,如果bBusy为FALSE且没有报错,说明功能块根本没被触发,需要回头查bExecute的信号链。
还有一种情况:虚拟手轮位置确实在变,但增量太小,累加到fRefPosSum后根本不足以让轴产生肉眼可见的位移。比如倍率还在1x、丝杠导程也是1mm,手轮转一圈轴才走1mm,在屏幕上看起来就像没动。这时候先把倍率临时切到10x,或者把机械导程换算值调大一点,确认逻辑通了你再切回来。
4.2 方向反了、倍率不对、轴飞车
方向反是最容易发现的异常。手轮往一个方向转,轴却往反方向跑。这种情况通常有两个处理点:一是在SMC_FollowPosition里把fPositionScale或者fVelocityScale设成负数,直接反转指令方向;二是查看轴参数的电机旋转方向设置,把电机方向反过来。我推荐在轴参数层面改方向,因为这样所有运动控制指令的方向都统一了,不会出现手轮和回零方向不一致的奇怪现象。
倍率不对和轴飞车往往是同一个原因。最常见的是udiResolution和位置换算公式里的分辨率不一致。比如功能块里分辨率设了1000,但位置累加时除以的是100,那手轮每转一圈,累加的位置就变成实际位置的10倍,等于无形中多了一个10倍速,再加上倍率切到100x,不飞车才怪。我自己踩过这个坑,所以现在写代码有个习惯:所有换算相关的常量尽量不放魔法数字,全部定义成全局变量,并且在HMI上做一个“位置换算系数显示”,方便现场对照。
如果轴已经在飞车的边缘,最有效的止损动作是:先断开bWheelEnable(停止累加),再让轴停止使能。所以现场调试时,我总会在HMI上放一个很大的“停止并清使能”按钮,位置放在操作员最容易按到的地方。
4.3 与回零、限位、正方向使能冲突
设备上不会只有虚拟手轮一个功能,它还要和回零、限位、自动程序这些逻辑共存。最常见的冲突是:轴还没回零,操作员就想用手轮把轴挪到一个安全位置。如果程序中直接允许,可能轴一使能就报错,因为SoftMotion很多轴模式下要求先回零。
我的做法是:把虚拟手轮视为“手动模式”的一个子状态。只有当设备处于手动模式、轴已回零、没有急停、没有运行中的自动程序时,虚拟手轮才允许切换使能。回零操作和虚拟手轮之间加互锁,回零过程中强制关掉虚拟手轮。
还有一个容易被忽略的冲突是手轮位置累加值和限位的互相干扰。前面讲过,轴撞软件限位后,fRefPosSum可能还继续增大,解除限位时轴会猛冲。解决思路有两种:一种是在轴限位报警触发时,把fRefPosSum同步为轴当前实际位置,同时清零虚拟手轮增量累积的偏差;另一种是每次累加前先检查轴的当前位置,如果已经接近限位,就只允许向着远离限位的方向累加。
这些逻辑虽然看起来多,但实际代码量不大,只要在编写虚拟手轮程序时把安全互锁当成第一优先级,后续调试会省掉很多麻烦。
5. 把虚拟手轮玩出花:扩展成调试平台
5.1 虚拟手轮与HMI动画联动做可视化反馈
虚拟手轮既然是一个软件信号,那它就不仅能驱动轴,还能同时驱动HMI上的动画。我习惯在触摸屏上画一个转盘,把SMC_FreeEncoder的udiWheelPos取模后映射为转盘的旋转角度。这样操作员能看到虚拟手轮正在“转”的动画效果,即使在没有物理手轮的情况下,也能直观感受到手柄的转动方向和速度变化。
HMI上还可以同时显示当前倍率、目标速度、手轮位置脉冲数、轴实际位置。这样调试的时候,操作员和旁边的人都能很清楚地看到手轮操作和轴运动的对应关系。尤其在对刀、对基准这种需要两人配合的场合,一个人操作虚拟手轮,另一个人看着HMI报位置,效率比纯按钮点动高得多。
5.2 虚拟手轮作为运动控制基准源做仿真与测试
虚拟手轮还有一个很实用的场景:在没有真实物理轴的环境下,验证运动控制逻辑。把ECAT轴换成仿真轴,SMC_FreeEncoder的脉冲源完全可以作为运动控制算法的输入基准,用来验证位置跟随效果、倍率切换逻辑、限位互锁逻辑等等。
这时候虚拟手轮实际上是承担了一个“测试信号发生器”的角色,你可以用它在实验室里模拟各种操作习惯,比如快速转、慢速微调、突然反向、持续低速运行,观察轴跟随是否平滑、是否会累积误差、会不会触发保护。这些问题如果等到设备上电后再发现,现场会很狼狈,但用虚拟手轮在办公室就能提前暴露一大部分。
如果再把Codesys自带的3D仿真视图加进来,甚至可以做一套简单的数字孪生调试。轴的运动在3D视图里实时可见,操作方式和现场一模一样,用来做培训、方案演示、客户验收都很有效果。
5.3 与其它技术栈结合做监控与数据采集
虚拟手轮产生的都是实打实的PLC变量,这意味着可以把它接入到各种数据采集和监控系统里。比如使用PLC-Recorder这类工具读取Codesys的实时变量,把操作员的每一次手轮操作、轴的位置变化、倍率切换时间戳全部记录下来。后续分析是操作不当导致的撞机,还是程序逻辑有缺陷,就有客观数据可依,而不是靠口头扯皮。
我还在一些项目里尝试过把底层通信进一步打通。ECAT总线上的轴状态、虚拟手轮信号、IO状态,都可以通过网关汇聚到一个统一的监控平台。比如用libevent这类事件库做CAN通信和EtherCAT数据的统一接入,把虚拟手轮的操作记录和驱动器报警数据放到同一个时间轴上分析,排查问题会更快。
不过这里必须提醒一句:虚拟手轮始终是调试辅助工具,它不能替代安全回路。设备的安全功能,比如急停、安全门锁、安全限位,必须是物理回路保证的,绝不能因为程序里做了“限位保护”就省掉硬件的安全措施。这个红线任何时候都不能碰。
最后再分享一个实操中的小习惯
自从用了虚拟手轮之后,我每次调试新设备都会先花十分钟把虚拟手轮跑通,再去做其他运动控制逻辑。因为手轮代表了最底层的位置操作需求,如果手轮能顺滑地驱动ECAT轴,说明轴配置、使能、限位、通讯这些基础环节都已经健康了,后续写自动程序会顺手很多。
还有一点,从现场换回办公室调试的时候,虚拟手轮能让你迅速定位故障到底是在机械侧、伺服侧还是逻辑侧。曾经有一台设备在客户现场反映“手动点动偶尔会抖”,接线和参数反复排查无果,最后我用虚拟手轮加上PLC-Recorder同步采集位置指令和实际位置,发现是驱动器内部滤波参数偏大导致的位置环滞后,问题不到半小时就定位了。
所以虚拟手轮不只是替代一个物理硬件,它本质上是一个强大的诊断入口。关键参数的换算公式、倍率切换的安全细节、位置回绕的处理,这套逻辑搞明白之后,你会发现自己对Codesys软运动控制的理解也上了一个台阶。