从一次报错到深入理解:CODESYS电子凸轮挺杆 SMC_CamRegister 完整指南
错误629的根因分析、三种解决方案对比与虚轴场景最优实践
前言:从一个实际报错说起
在做 CODESYS SoftMotion 运动控制项目时,主轴电子凸轮配置了15组挺杆(Tappet)点,其中部分挺杆的触发位置相同。运行时系统报出错误:
SMC_ERROR.SMC_CI_TOO_MANY_TAPPETS_PER_CYCLE (#629) 描述:Too many tappets became active during one cycle(一个周期内激活的挺杆数量过多)
正是这个错误,让我开始深入研究 SMC_CamRegister 功能块,并最终找到了适合自己项目的解决方案。本文将完整记录这次排查过程,从错误根因到底层原理,从官方功能块对比到自封装实现,希望对遇到同样问题的朋友有所帮助。
一、电子凸轮与挺杆(Tappet)基础
1.1 什么是挺杆
挺杆(Tappet)本质是一个位置触发的数字开关点。在机械凸轮机构中,挺杆随凸轮轮廓的起伏而上下运动,从而控制开关的通断。在 CODESYS 电子凸轮中,这一概念被数字化——通过在凸轮表中定义挺杆点,当主轴运行到指定位置时,自动触发对应的开关动作。
挺杆的核心配置要素包括:
- 跟踪ID(Track ID):挺杆点的标识号,每个ID对应一个独立的开关输出
- 触发位置(X):主轴运行到该位置时触发动作
- 触发方向:正向通过(位置增大)或反向通过(位置减小)
- 挺杆动作:打开(ON)/ 关闭(OFF)/ 反转(Invert)
注意:挺杆的动作与凸轮曲线无关,只与主轴运行的位置有关。修改凸轮曲线不会改变挺杆的触发位置。
图1:电子凸轮与挺杆工作原理示意图
1.2 挺杆的典型应用场景
- 胶枪控制:主轴到达特定位置时开启/关闭胶枪,支持死区补偿提前触发
- 气缸动作:主轴在各工位触发气缸伸缩
- 飞剪/追剪:在特定位置触发切刀动作
- 指示灯/报警:主轴运行到某些位置点亮指示灯或触发报警
- 多工位集中管理:通过数组一次获取所有开关点状态
二、SMC_CamRegister 功能块详解
SMC_CamRegister 是 CODESYS SoftMotion(SM3_Basic 库)中的一个功能块,用于电子凸轮挺杆控制。它可以一次性读取凸轮表中定义的所有挺杆点的状态,输出为一个 BOOL 数组。
2.1 功能块接口
| 作用域 | 名称 | 类型 | 说明 |
|---|---|---|---|
| InOut | Master | AXIS_REF_SM3 | 主轴参考 |
| InOut | CamTable | MC_CAM_REF | 含挺杆描述的凸轮表 |
| InOut | bTappet | ARRAY[1..MAX] OF BOOL | 所有挺杆点的输出状态数组 |
| Input | Enable | BOOL | TRUE时启动功能块 |
| Input | MasterOffset | LREAL | 主轴位置偏移量(默认0) |
| Input | MasterScaling | LREAL | 主轴缩放因子(默认1) |
| Input | TappetHysteresis | LREAL | 挺杆迟滞区间,防止抖动误触发 |
| Input | DeadTimeCompensation | LREAL | 死区时间补偿(秒),通过线性外推预估主轴位置 |
| Output | Busy | BOOL | 功能块执行中 |
| Output | Error | BOOL | 发生错误 |
| Output | ErrorID | SMC_ERROR | 错误码 |
| Output | EndOfProfile | BOOL | 凸轮周期结束脉冲 |
2.2 与 SMC_GetTappetValue 的区别
| 对比项 | SMC_CamRegister | SMC_GetTappetValue |
|---|---|---|
| 读取方式 | 一次读取全部挺杆状态,输出为数组 | 一次只能读取一个挺杆,需实例化多个功能块 |
| ID关联 | 数组索引直接对应挺杆ID | 通过iID参数指定 |
| 适用场景 | 集中管理多个挺杆,状态读取高效 | 少量挺杆,简单使用 |
| 推荐度 | 兼容性保留,新项目建议用更高级功能块 | 仅保留兼容,不推荐新项目 |
2.3 关键参数详解
TappetHysteresis(迟滞):当主轴位置在挺杆触发点附近抖动(如编码器信号抖动)时,若不加迟滞,挺杆会反复通断。设置正值迟滞后,主轴必须离开触发点超过该值才能再次触发。
DeadTimeCompensation(死区补偿):例如胶枪从接收到信号到实际出胶有50ms延迟,设置 DeadTimeCompensation := 0.05,功能块会提前触发挺杆,补偿该延迟。
MasterOffset / MasterScaling:前者对主轴位置整体加偏移,后者乘缩放系数,常用于主轴与凸轮表之间的单位换算或位置微调。
三、错误629:SMC_CI_TOO_MANY_TAPPETS_PER_CYCLE 深度分析
3.1 错误信息
| 项目 | 内容 |
|---|---|
| 错误号 | 629 |
| 枚举名 | SMC_CI_TOO_MANY_TAPPETS_PER_CYCLE |
| 所属模块 | MC_CamIn(前缀SMC_CI_) |
| 描述 | Too many tappets became active during one cycle(一个周期内激活的挺杆数量过多) |
3.2 错误根因
当主轴运行到某个位置时,凸轮表中在该位置附近配置的多个挺杆点同时被触发,且触发数量超过了 MC_CamIn 单个周期能处理的上限,就会报这个错。
实际项目中的情况:
- 配置了15组挺杆点
- 其中部分挺杆的启动位置是一样的(位置重合)
- MC_CamIn 内部的挺杆处理有单周期上限(实测约3组)
- 当多个挺杆在同一周期同时触发时,超过3组就报错629
图2:SMC_CI_TOO_MANY_TAPPETS_PER_CYCLE(错误629)触发场景——多组挺杆位置重合导致单周期激活数超限
3.3 SMC_CamRegister 为什么能解决这个问题
关键在于两者的处理架构不同:
| 方式 | 说明 | 问题 |
|---|---|---|
| MC_CamIn 内置处理挺杆 | 挺杆状态由MC_CamIn内部管理,有单周期激活数限制 | 报629错误 |
| SMC_CamRegister 独立读取 | 不依赖MC_CamIn内部处理,独立读取CamTable中所有挺杆状态输出到bTappet[]数组 | 可绕过MC_CamIn的周期限制 |
核心区别:MC_CamIn把凸轮跟随和挺杆处理打包在一起,挺杆激活有内置上限;SMC_CamRegister是独立的功能块,只负责读挺杆状态,与MC_CamIn各自的限制互不干扰。
四、解决方案一:分离凸轮跟随与挺杆读取
解决思路:将凸轮跟随和挺杆读取分离。MC_CamIn只负责电子凸轮跟随,SMC_CamRegister独立处理全部15组挺杆。
4.1 变量声明
PROGRAM Main
VAR
// 轴
MasterAxis : AXIS_REF_SM3;
SlaveAxis : AXIS_REF_SM3;
// 凸轮表
CamTable : MC_CAM_REF;
// ===** MC_CamIn:只负责凸轮跟随 **=**
CamIn : MC_CamIn;
xExecute : BOOL;
bInSync : BOOL;
bCamBusy : BOOL;
bCamError : BOOL;
eCamErrorID : SMC_ERROR;
// **=** SMC_CamRegister:独立处理15组挺杆 **===
CamRegister : SMC_CamRegister;
xEnable : BOOL := TRUE;
bTappetStates : ARRAY[1..15] OF BOOL; // 15组挺杆状态
// 15组挺杆对应的控制输出
bTappet_01 : BOOL;
bTappet_02 : BOOL;
// ... 直到 bTappet_15
// 错误
bRegError : BOOL;
eRegErrorID : SMC_ERROR;
END_VAR
4.2 主程序逻辑
// 第一步:MC_CamIn 只做电子凸轮跟随
CamIn(
Master := MasterAxis,
Slave := SlaveAxis,
Execute := xExecute,
CamTableID := CamTable,
MasterAbsolute := TRUE,
SlaveAbsolute := TRUE,
MasterOffset := 0,
SlaveOffset := 0,
MasterScaling := 1,
SlaveScaling := 1,
StartMode := MC_CAM_START_MODE.CURRENT,
BufferMode := MC_BUFFER_MODE.Aborting,
InSync => bInSync,
Busy => bCamBusy,
Error => bCamError,
ErrorID => eCamErrorID
);
// 第二步:SMC_CamRegister 独立读取15组挺杆
CamRegister(
Master := MasterAxis,
CamTable := CamTable,
Enable := xEnable,
MasterOffset := 0,
MasterScaling := 1,
TappetHysteresis := 0.2, // 加迟滞,防抖动误触发
DeadTimeCompensation := 0,
Busy => ,
Error => bRegError,
ErrorID => eRegErrorID,
EndOfProfile => ,
bTappet => bTappetStates // 一次读出全部15组
);
// 第三步:映射到具体控制信号
bTappet_01 := bTappetStates[1];
bTappet_02 := bTappetStates[2];
// ...
bTappet_15 := bTappetStates[15];
4.3 关键注意事项
- 加TappetHysteresis(迟滞):部分挺杆位置重合时,主轴稍微抖动就可能同时触发多个,加迟滞可以减少同一周期内的激活数
- 凸轮表挺杆ID必须对应:确保15组挺杆的Track ID分别为1~15,bTappetStates[1]到bTappetStates[15]才能正确对应
- 逐步测试验证:先用3组挺杆确认配合正常,再逐步增加到15组
五、官方推荐:MC_DigitalCamSwitch 功能块对比
在CODESYS官方文档中,SMC_CamRegister 和 SMC_GetTappetValue 被标注为”仅保留兼容”,官方建议新项目使用更高级的功能块,其中 MC_DigitalCamSwitch 是推荐选择之一。
5.1 官方功能块对比表
| 功能 | MC_DigitalCamSwitch | SMC_CamRegister |
|---|---|---|
| 官方定位 | 当前和未来项目推荐使用 | 仅保留兼容,不推荐新项目 |
| 多开关点 | 支持(最多32个Tracks) | 仅读取凸轮表中已定义的挺杆 |
| 开/关补偿 | 支持(通过外推法) | 无 |
| 预测(Forecast) | 无(仅外推) | 无 |
| 高精度(亚周期) | HighPrecision版本支持 | 周期级精度 |
| 数据基准 | 设定位置或实际位置 | 设定位置或实际位置 |
| 单周期激活限制 | 无明确限制 | 无明确限制 |
5.2 MC_DigitalCamSwitch 的额外优势
(1)开/关补偿:每个Track可以独立设置开补偿和关补偿(时间单位),比如胶枪提前50ms触发:
TrackOptions.OnCompensation[1] := T#50MS; // 提前开
TrackOptions.OffCompensation[1] := T#50MS; // 提前关
(2)区间开关:每个Track可以设置区间(起始位置~结束位置),而不只是单点触发:
// Track1:主轴位置 90~120 之间输出 ON
Switch01.FirstOnPosition := 90;
Switch01.LastOnPosition := 120;
(3)正反向独立控制:AxisDirection: 0=双向, 1=正向, 2=反向
5.3 能力对比总表
图5:四种挺杆处理方案能力对比表
六、底层原理:能否通过PDO自行封装?
6.1 MC_DigitalCamSwitch 底层做了什么
从底层架构来看,MC_DigitalCamSwitch的工作分为三层:
第1层 — 配置下发(Enable上升沿时):所有开关配置(Switches、TrackOptions、Outputs)被一次性传送到处理单元。
第2层 — 实时位置比较(每个总线周期):读取轴实际位置和速度,遍历所有Track进行位置比较,如果启用补偿则通过速度外推预测到达时间,提前触发输出。
第3层 — 输出执行:标准方式由SoftMotion在PLC侧计算后通过总线写远程IO;高性能方式则将配置下发给驱动器,由驱动器内部在本地硬件上直接切换输出(微秒级响应)。
6.2 自封装 vs 官方功能块
| 功能 | 自封装(PDO方式) | MC_DigitalCamSwitch | 差距 |
|---|---|---|---|
| 位置比较 | 完全能做到 | 完全能做到 | 无差距 |
| 多Track | 循环遍历即可 | 最多32个 | 无差距 |
| 迟滞(Hysteresis) | 加状态保持即可 | 支持 | 无差距 |
| 正反向控制 | 判断方向符号 | 支持 | 无差距 |
| 开/关补偿 | 能做,精度受限 | 更准确(内核数据) | 有差距 |
| 高精度(亚周期) | 做不到 | HighPrecision版支持 | 根本差距 |
| 驱动器硬件直接输出 | 做不到 | 部分实现支持 | 根本差距 |
核心差距在于:MC_DigitalCamSwitch集成在SoftMotion内核中,能访问轨迹规划器的内部数据(加速度、加加速度),外推精度更高。而自封装只能依赖当前速度的线性假设。
七、关键转折:主轴是虚轴!
前面分析的PDO延迟、亚周期精度、驱动器硬件比较——如果主轴是虚轴,这些全都不适用了!
虚轴的实际位置由SoftMotion内核内部计算,直接存在AXIS_REF_SM3结构体的内存中。没有PDO,没有总线延迟,没有硬件参与。
| 之前担心的点 | 虚轴下的真相 |
|---|---|
| PDO读取延迟 | 不存在 — 位置就在内存里,直接访问 |
| 亚周期精度 | 不存在 — 虚轴位置也是每个PLC周期更新一次 |
| 驱动器硬件比较 | 不存在 — 没有物理驱动器 |
| 外推精度受总线影响 | 不存在 — 无总线 |
结论:在虚轴场景下,自封装和官方功能块的精度基础完全一样,因为处理都在同一个PLC周期内、访问同一份内存数据。
图4:虚轴数据流——为什么自封装方案在虚轴场景下最优
八、最终方案:自封装挺杆控制功能块
既然主轴是虚轴,所有数据本来就在PLC的内存空间里,我们完全可以自封装一个简单的功能块,干净利落绕过629错误,且没有任何数量限制。
图3:三种挺杆处理方案架构对比
8.1 数据类型定义
// 挺杆动作类型
TYPE EnumTappetAction :
(
eSwitchON, // 到达位置时切换到 ON
eSwitchOFF, // 到达位置时切换到 OFF
eToggle // 到达位置时取反(Invert)
) := eSwitchON;
END_TYPE
// 触发方向
TYPE EnumTappetDirection :
(
eBoth, // 正反向都触发
ePositive, // 仅正向(主轴位置增大)
eNegative // 仅反向(主轴位置减小)
) := eBoth;
END_TYPE
// 每个挺杆点的配置
TYPE TappetConfigType :
STRUCT
// 基本信息
bEnable : BOOL; // TRUE: 启用该挺杆
sDescription : STRING(80); // 描述,如"胶枪"、"气缸1"
// 触发位置
fOnPosition : LREAL; // 触发位置(主轴坐标)
fOffPosition : LREAL; // 关闭位置(区间型时使用)
// 动作方式
eAction : EnumTappetAction; // ON / OFF / 取反
eDirection : EnumTappetDirection; // 正/反/双向
// 保护
fHysteresis : LREAL; // 迟滞,防止抖动误触发
// 单次触发模式(可选)
bOneShot : BOOL; // 每个周期只触发一次
END_STRUCT
END_TYPE
8.2 各字段说明
| 字段 | 作用 | 为什么需要 |
|---|---|---|
| fOnPosition | 触发位置 | 核心字段——你之前说”几组启动位置一样”指的就是这个值 |
| fOffPosition | 关闭位置 | 区间开关时使用(如位置90~120输出ON) |
| eAction | 动作方式 | 对应凸轮表挺杆的:打开=ON / 关闭=OFF / 反转=Invert |
| eDirection | 触发方向 | 主轴会反转时,防止反向运动误触发 |
| fHysteresis | 迟滞区间 | 主轴在触发点附近抖动时防止反复通断 |
| bOneShot | 单次触发 | 到位置触发一次动作,而不是保持状态 |
8.3 功能块实现
FUNCTION_BLOCK MyTappetController
VAR_INPUT
// 直接从虚轴结构体读位置(不需要PDO!)
fMasterPosition : LREAL; // MasterAxis.NcToPlc.ActPos
fMasterVelocity : LREAL; // MasterAxis.NcToPlc.ActVel
bEnable : BOOL;
END_VAR
VAR_IN_OUT
TappetConfig : ARRAY[1..15] OF TappetConfigType;
END_VAR
VAR_OUTPUT
bTappetOut : ARRAY[1..15] OF BOOL;
END_VAR
VAR
bPrevState : ARRAY[1..15] OF BOOL;
fPrevPosition : LREAL;
bDirectionPositive : BOOL;
END_VAR
// 每个周期执行
IF bEnable THEN
// 判断运动方向
bDirectionPositive := (fMasterPosition >= fPrevPosition);
fPrevPosition := fMasterPosition;
FOR i := 1 TO 15 DO
IF TappetConfig[i].bEnable THEN
// 方向检查
IF (TappetConfig[i].eDirection = ePositive AND NOT bDirectionPositive)
OR (TappetConfig[i].eDirection = eNegative AND bDirectionPositive) THEN
CONTINUE;
END_IF;
// 位置比较 + 迟滞
IF fMasterPosition >= TappetConfig[i].fOnPosition
AND fMasterPosition <= (TappetConfig[i].fOnPosition
+ TappetConfig[i].fHysteresis) THEN
CASE TappetConfig[i].eAction OF
eSwitchON: bTappetOut[i] := TRUE;
eSwitchOFF: bTappetOut[i] := FALSE;
eToggle: bTappetOut[i] := NOT bPrevState[i];
END_CASE
END_IF;
// 区间型:超出区间时关闭
IF TappetConfig[i].fOffPosition > TappetConfig[i].fOnPosition THEN
IF fMasterPosition > TappetConfig[i].fOffPosition THEN
bTappetOut[i] := FALSE;
END_IF;
END_IF;
bPrevState[i] := bTappetOut[i];
END_IF;
END_FOR;
END_IF
8.4 配置示例
// 配置15组挺杆
TappetConfig[1].bEnable := TRUE;
TappetConfig[1].sDescription := '胶枪开';
TappetConfig[1].fOnPosition := 90; // 90位置开胶枪
TappetConfig[1].fOffPosition := 120; // 120位置关胶枪
TappetConfig[1].eAction := eSwitchON;
TappetConfig[1].fHysteresis := 0.5;
TappetConfig[2].bEnable := TRUE;
TappetConfig[2].sDescription := '气缸顶出';
TappetConfig[2].fOnPosition := 90; // 和胶枪同位置触发
TappetConfig[2].eAction := eToggle; // 每次到90就取反
TappetConfig[2].fHysteresis := 0.2;
// ... 配置到 TappetConfig[15]
8.5 调用方式
// 主程序中调用
MyTappetController(
fMasterPosition := MasterAxis.NcToPlc.ActPos, // 直接读虚轴内存
fMasterVelocity := MasterAxis.NcToPlc.ActVel,
bEnable := TRUE,
TappetConfig := TappetConfig,
bTappetOut => bTappetOut
);
// 使用输出
bGlueGun := bTappetOut[1];
bCylinder := bTappetOut[2];
// ...
九、方案选型总结
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 虚轴 + 多组挺杆 + 位置可重合 | 自封装功能块 | 虚轴数据在内存中,自封装无限制,精度与官方一致 |
| 实轴 + 精度要求不高(几ms够用) | MC_DigitalCamSwitch | 官方推荐,功能丰富,支持开/关补偿 |
| 实轴 + 需亚周期精度 | MC_DigitalCamSwitch_HighPrecision | 依赖驱动器硬件位置比较,微秒级响应 |
| 已有项目兼容维护 | SMC_CamRegister | 兼容性保留,可正常使用但不推荐新项目 |
自封装方案的核心优势:
- 彻底绕开
SMC_CI_TOO_MANY_TAPPETS_PER_CYCLE— 自己写的循环,多少组都不限制 - 不依赖凸轮表 — 挺杆位置直接在代码里配,修改方便
- 代码透明 — 出了问题自己就能排查
- 性能足够 — 虚轴场景下与官方功能块精度无差别
结语
回顾整个排查过程:从一次报错(629)出发,了解了 SMC_CamRegister 功能块的接口和用法,分析了错误根因(MC_CamIn单周期挺杆激活数限制),对比了官方推荐的 MC_DigitalCamSwitch,深入解析了底层处理原理,最终在”主轴是虚轴”这一关键信息下,找到了最优解——自封装功能块。
这个过程的核心方法论是:遇到问题不要只停留在”怎么解决”,而要追问”为什么会出现这个问题""官方方案底层是怎么做的""我的场景有什么特殊性”。 只有理解了底层原理,才能做出最优的技术选型。
希望这篇文章对正在做 CODESYS 电子凸轮项目的朋友有所帮助。如果有疑问或更好的方案,欢迎交流讨论。
— END —