简介:TwinCAT外部设定值S型加减速规划资源包,面向工业自动化与运动控制开发者,解决电机启停冲击大、运行中变更目标位置时难以平稳调速的问题。基于Beckhoff TwinCAT平台,重点演示外部设定值发生器如何根据上位机指令动态计算S型速度曲线。压缩包共98个文件,主体为54个compiled-library编译库和11个tcpou程序组织单元,另含Scope测量工程、tsproj TwinCAT工程、TMC运动控制文件等调试与配置内容,整体仅8.26MB,便于快速下载部署。该资源已有944人学习。借助其中的PLC逻辑、速度曲线生成模块与实际工程文件,可完整掌握外部设定值从接收、S型曲线建模到实时下发驱动器的实现链路,掌握编码器反馈闭环与异常停机处理思路,适合需要提升定位精度和动态响应能力的TwinCAT中高级开发者。
1. 外部设定值做S型加减速:为什么PLC侧规划比驱动器内规划更可控
做运动控制的人应该都有过这种经历:直接给驱动器一个目标速度,设备启停总带那么一下「咯噔」,导轨振动、工件抖动、步进电机丢步,问题往往不在伺服本身,而在加减速曲线。TwinCAT 里如果用外部设定值方式做 S 型加减速,把速度规划放到 PLC 侧、每个周期往驱动器送一个目标位置,治的就是这个病。相比驱动器内部的梯形加减速,PLC 侧做规划的好处是曲线形状完全可控,S 型曲线的加加速度(Jerk)是有限值,机械冲击小得多;而且不依赖驱动器品牌,换个驱动器只要外部设定值接口一样,规划代码原样复用。这套做法适合做点对点定位、龙门同步、印刷平台这类对启停平稳性有要求的设备,熟悉 ST 语言就能落地。
2. 七段式S曲线的边界条件:把速度、加速度、加加速度的预算先算清楚
2.1 梯形加减速的痛点与S型的本质
梯形加减速速度曲线是直线段拼的:加速段速度线性上升,加速度恒定。加速度从 0 瞬间跳到最大值、再从最大值瞬间跳回 0,这一跳在力学上就是无限大的加加速度。机械系统都有柔性,遇到这个冲击就会振动,只是有些设备刚性大、看不明显;刚性差一点的,负载越大越明显。S 型加减速的本质,是让加速度本身也按一定斜率变化,这个斜率就是加加速度(Jerk),单位 mm/s³。加加速度不为无穷大,机械受到的冲击就是有限的,听感、手感、振动曲线都会有本质区别。
所以在做 S 型规划之前,先要理解一个概念:S 型曲线严格说分七段——加加速、匀加速、减加速、匀速、减加速、匀减速、减减速。前四段把速度从 0 加到位移中点的最大速度,后三段镜像减速。每段时间长短由三个预算决定:最大速度 Vmax、最大加速度 Amax、加加速度 Jerk。这三个值一旦定了,整条曲线能跑多远、各段占多长,全部由计算确定,没有「凭感觉调」的余地。这也是为什么我建议先在纸上把公式推一遍,再把公式写进代码,而不是直接在 PID 参数上乱试。
2.2 七段时间划分与关键公式
设加加速度为 J、最大加速度为 A、最大速度为 V,那么加速度从 0 升到 A 需要时间 T1 = A/J,这一段叫加加速段。从 A 降回 0 也需要同样的时间,这就是第三段减加速段,时间 T3 = T1。中间如果需要保持最大加速度,则第二段匀加速时间 T2 = V/A - A/(2J)。注意这个式子的物理意义:如果 V 不够大,T2 会变成负数,说明根本到不了最大速度——这就是后面要处理的短行程分支。
有了 T1、T2、T3,可以把七段时间全部算出来:第 1 段 T1,第 2 段 T2,第 3 段 T3,第 4 段匀速 T4,第 5、6、7 段分别等于 T3、T2、T1。关键是先算临界位移,也就是刚好能加速到 V 又减速回 0 的最小距离:
| 参数 | 公式 | 说明 |
|---|---|---|
| 加加速段时间 T1 | A / J | 加速度线性上升到 A |
| 匀加速段时间 T2 | V/A - A/(2J) | 若为负说明距离不足 |
| 临界位移 Smin | V²/A + V·A/J | 完整七段的最小距离 |
| 匀速段时间 T4 | (D - Smin) / V | D 为总位移 |
这个 Smin 是我做规划时最先算的一个值,它决定了走完整七段还是走降速分支。很多初学的同事把 J 设得很大,以为 S 曲线就是「把速度调圆一点」,结果 T1 变成零点几毫秒,加速度曲线几乎还是直角,等于没做。J 的值决定了变加速度的时间尺度,太小则曲线太陡,太大则加减速过程拖太长。设备上一般先从机械允许的冲击反推 J,比如负载 20kg、允许加加速度上限 2000mm/s³,这个值写在需求里比写在代码里更靠谱。
2.3 两个决定性参数:临界位移与降速分支
临界位移 Smin 是整篇文章最重要的一个数。实际项目里最常见的问题是:目标位置只有 2mm,Vmax 设了 300mm/s,Amax 设了 1500mm/s²,J 设了 5000mm/s³,算出来 Smin = 300²/1500 + 300×1500/5000 = 60 + 90 = 150mm。要走 2mm 却按 150mm 的曲线去规划,结果必然是在加速段都没走完就该减速了,时间表根本排不开。
处理方法是:当实际距离 D 小于 Smin 时,不能继续用原 Vmax,要反算一个实际能达到的峰值速度 V_eff。解一元二次方程 V²/A + V·A/J = D,得到:
V_eff = ( -A²/J + sqrt( A⁴/J² + 4·A·D ) ) / 2
这个速度会明显小于设定 Vmax。曲线变成五段:加加速、匀加速(时间变短)、减加速到峰值,然后镜像减速,没有匀速段。更极端的情况,如果距离小到连 Amax 都达不到,那退化成纯加加速+减加速两段,加速度峰值小于 Amax。现场我一般把这种极小距离交给驱动器自带的平滑功能兜底,PLC 侧只做完整七段和五段两种 profile,逻辑简单、不容易出边界问题。
3. 在TwinCAT里写一个可复用的S型规划FB:核心代码与参数说明
3.1 FB接口设计:输入输出要按什么单位、什么周期
在 TwinCAT 3 里我习惯把规划器封装成一个 Function Block,叫 FB_SCurveExt。输入输出全部用 LREAL,单位统一为 mm、mm/s、mm/s²、mm/s³。周期时间作为输入参数传入,默认 1ms——这个设计是为了让 FB 不依赖具体任务周期,万一调试时把任务周期改成 0.5ms 或 2ms,不需要改内部代码,只需要把实际周期传进来。很多翻车现场就是代码里写死 0.001,改了任务周期后规划位置全部失真。
FB 的接口这样设计:
FUNCTION_BLOCK FB_SCurveExt VAR_INPUT bExecute : BOOL; // 上升沿触发一次完整规划 rStart : LREAL; // 起点位置,绝对坐标,mm rTarget : LREAL; // 终点位置,绝对坐标,mm rVmax : LREAL; // 最大速度 mm/s rAmax : LREAL; // 最大加速度 mm/s^2 rJerk : LREAL; // 加加速度 mm/s^3 fCycle : LREAL := 0.001; // 调用周期 s,默认 1ms END_VAR VAR_OUTPUT rSetpoint : LREAL; // 当前位置设定值,每周期刷新 bBusy : BOOL; // 规划执行中 bDone : BOOL; // 规划完成 rVelAct : LREAL; // 规划速度,监控用 rAccAct : LREAL; // 规划加速度,监控用 END_VAR从使用者的角度看,这个 FB 的行为是:bExecute 给一个上升沿,rSetpoint 就从 rStart 开始按 S 曲线走向 rTarget,bDone 置位后设定值保持终点。整个过程不需要外部去管它内部状态。唯一要注意的是 bExecute 必须在 bBusy=FALSE 时才能触发,规划中改目标位置属于进阶话题,后面单独讲。
3.2 完整七段路径:分段时间表与每周期增量输出
核心是一个私有方法,负责根据位移距离计算七段时间。我把距离判断和降速分支都放在这里:
METHOD PRIVATE PrepareProfile VAR_INPUT d : LREAL; // 本次运动距离,恒为正,mm v : LREAL; // 允许最大速度 mm/s a : LREAL; // 允许最大加速度 mm/s^2 J : LREAL; // 加加速度 mm/s^3 END_VAR VAR T1, T2, T3, T4 : LREAL; sMin : LREAL; vEff : LREAL; END_VAR // 完整七段所需的最小距离 sMin := v*v/a + v*a/J; IF d >= sMin THEN // 距离足够,走完整七段 T1 := a/J; T3 := T1; T2 := v/a - a/(2.0*J); T4 := (d - sMin)/v; aPhaseTime[1] := T1; aPhaseTime[2] := T2; aPhaseTime[3] := T3; aPhaseTime[4] := T4; aPhaseTime[5] := T3; aPhaseTime[6] := T2; aPhaseTime[7] := T1; ELSE // 距离不足,降速重算:解 V^2/a + V*a/J = d 的方程 vEff := (-a*a/J + SQRT(POWER(a,4)/(J*J) + 4.0*a*d)) * 0.5; T1 := a/J; T3 := T1; T2 := vEff/a - a/(2.0*J); IF T2 < 0.0 THEN T2 := 0.0; // 极短距离下连匀加速段都没有 END_IF aPhaseTime[1] := T1; aPhaseTime[2] := T2; aPhaseTime[3] := T3; aPhaseTime[4] := 0.0; aPhaseTime[5] := T3; aPhaseTime[6] := T2; aPhaseTime[7] := T1; END_IF这里有个细节容易看漏:短距离分支的 vEff 公式假设加速度能到 Amax,只降速度;如果距离实在太小,T2 会被截成 0,此时实际加速度峰值达不到 Amax,但作为工程近似是够用的。完整七段里 T2 用的是 Vmax/A - A/(2J),这个式子里的 A/(2J) 是加速度从 0 升到 A 期间走过的路程折算,很多从梯形加减速转过来的人会漏掉这个修正项,导致实际位移比设定多一块,末尾要么过冲要么急停。
3.3 周期执行体:Jerk驱动数值积分
有了分段时间,剩下的工作就是把这条曲线一周期一周期地走出来。我用的方法是直接对 Jerk 数值积分:每周期先根据当前在第几段确定 Jerk 的符号,然后更新加速度、速度、位移。代码:
rtTrig(CLK := bExecute); IF rtTrig.Q THEN rDist := rTarget - rStart; // 带符号距离,决定运动方向 rRel := 0.0; // 相对位移,恒为正累计 rVel := 0.0; rAcc := 0.0; iPhase := 1; rPhaseTime := 0.0; PrepareProfile(ABS(rDist), rVmax, rAmax, rJerk); bDone := FALSE; bBusy := TRUE; END_IF dyT := fCycle; IF bBusy THEN // 根据当前段选择本周期 Jerk 符号 CASE iPhase OF 1: rJerkNow := rJerk; // 加加速:加速度上升 2: rJerkNow := 0.0; // 匀加速 3: rJerkNow := -rJerk; // 减加速:加速度回零 4: rJerkNow := 0.0; // 匀速 5: rJerkNow := -rJerk; // 减加速:加速度反向建立 6: rJerkNow := 0.0; // 匀减速 7: rJerkNow := rJerk; // 减减速:加速度回到零 END_CASE rAcc := rAcc + rJerkNow * dyT; // 更新加速度 rVel := rVel + rAcc * dyT; // 更新速度 rRel := rRel + rVel * dyT; // 更新位置(梯形积分) rVelAct := rVel; // 供监控显示 rAccAct := rAcc; // 段切换:时间到就进下一段 rPhaseTime := rPhaseTime + dyT; IF rPhaseTime >= aPhaseTime[iPhase] THEN rPhaseTime := rPhaseTime - aPhaseTime[iPhase]; iPhase := iPhase + 1; IF iPhase > 7 THEN rRel := ABS(rDist); // 消除积分残差 bBusy := FALSE; bDone := TRUE; END_IF END_IF // 根据方向输出带符号设定值 IF rDist >= 0.0 THEN rSetpoint := rStart + rRel; ELSE rSetpoint := rStart - rRel; END_IF END_IF这个执行体的好处是:不管分段时间表是怎么算出来的,执行逻辑都是同一套,不会因为 profile 是七段还是五段而出错。Jerk 在一个周期内固定,加速度曲线就是标准的折线,速度曲线是光滑的连续曲线。终点处强制把 rRel 置为总距离,目的是消除浮点积分残差——虽然每周期误差极小,但长时间往复运动会累积漂移,这个强制赋值相当于给位置误差一个兜底。
调用这个 FB 时还有一个常见误区:有人把 rStart 写成 PLC 实时读到的实际位置,而不是规划开始时的位置。外部设定值模式下,实际位置有跟踪误差,如果规划起点跟着实际位置飘,每周期都要重新算一遍 profile,曲线永远不稳定。正确做法是规划开始时把 rStart 锁定为当时的实际位置,之后不许改。
3.4 验证方法:先看曲线再接轴
代码写完不要直接接伺服,我先在 PLC 里跑一个开环测试:用一个周期任务调用 FB,输入一段 100mm 的距离,Vmax=200、Amax=1000、J=5000,然后把 rSetpoint、rVelAct、rAccAct 三个变量接到 TwinCAT Scope View 上。正常的 S 曲线速度应该是光滑的钟形,加速度应该是梯形,加加速度在每一段是恒定值。如果速度曲线上出现折角,说明 Jerk 符号选错或者分段时间算错;如果加速度曲线有毛刺,说明周期时间不稳定,得先解决任务配置。
这一步虽然看起来啰嗦,但能省下大量接线后的排查时间。我把 Scope 配置保存成了模板,新项目直接导入,改一下变量名就能用。波形验证通过后,再把 rSetpoint 接到外部设定值通道,进入下一步。
4. 外部设定值怎么接:把规划输出送进驱动器的三种路径
4.1 路径一:直接写驱动器的CSP目标位置
最直接的做法是把 rSetpoint 每个周期写到驱动器的周期同步位置模式(CSP)目标位置寄存器。TwinCAT 里通过 EtherCAT 从站映射区操作:规划 FB 输出连接到一个变量,这个变量映射到从站的输出区,每个周期由任务自动刷新。
// 1ms 任务内 fbScurve(bExecute := bStart AND NOT fbScurve.bBusy, rStart := rStartPos, rTarget := rTargetPos, rVmax := 200.0, rAmax := 1000.0, rJerk := 5000.0); // 输出映射到从站 CSP 目标位置 stServoOutput.nCspTargetPos := LREAL_TO_DINT(fbScurve.rSetpoint * 1000.0); // 单位 mm -> um注意单位换算:驱动器寄存器通常要求整数微米或脉冲单位,而规划器内部是浮点毫米,每次写出前要乘系数并取整。取整用 LREAL_TO_DINT 会截断,我一般先加 0.5 再转换,避免每周期都往下取整导致速度慢一点。这个路径适合驱动器本身不支持内部平滑、或者要求设定值完全由上位机决定的场合。缺点是每个周期都必须保证实时写入,一旦任务卡死,驱动器报跟随误差。
4.2 路径二:通过NC轴的外部设定值接口
如果项目里已经有 NC 轴,比如用了 TwinCAT NC PTP,可以不走 CSP 寄存器,而是把规划好的位置作为外部设定值叠加进 NC 轴。常见做法是使用叠加运动功能块,或者直接把 rSetpoint 写入轴的设定值通道,并关闭 NC 轴自身的加减速规划。
这条路径的坑在第 5 章会专门讲,这里先给结论:只要走 NC,就必须确认 NC 侧的速度限制、加速度限制、加加速度限制全部设置得比 PLC 侧规划值大得多,否则等于两条 S 曲线串在一起,输出变成「S 的 S」,动作拖沓不说,还容易在交接处产生速度抖动。我一般把 NC 侧的加加速度设成 PLC 侧的三倍以上,让 NC 只做跟随,不参与规划。
4.3 波形验收:用Scope确认S曲线没有「隐形折角」
接好之后先做低速小行程试跑,把 rSetpoint、驱动器实际位置、跟随误差三条曲线放到一个 Scope 里看。判断标准有三个:第一,实际位置曲线应该跟设定值重合,跟随误差不超过一个稳定的小范围;第二,速度曲线没有台阶或突变;第三,在运动结束段听不到机械「咯噔」声。
这里有个容易忽略的检查点——加速度曲线。外部设定值模式下,规划器输出的加速度曲线是折线,如果驱动器内部又做了一阶滤波,实际加速度会变圆,这是正常的;但如果加速度曲线上出现高频毛刺,问题不在规划器,而在周期抖动或 EtherCAT 同步性能。可以先用示波器看 Sync 信号和任务周期实际抖动,再回头查规划器。我见过不少项目为了这事反复调 Jerk 参数,最后发现是任务没设成固定周期,用的还是默认的「可被中断」类型。
5. 避坑排查:外部S型加减速最常见的五个翻车现场
5.1 双重规划叠罗汉
现象:接好线后第一次跑,轴动作明显「肉」,速度达不到设定值,加速过程拖得很长,波形上速度曲线起点有个小台阶。
原因:驱动器或 NC 轴内部也开了 S 型平滑。PLC 侧已经规划好一条 S 曲线,内部平滑又按自己的 Jerk 限制再修一次,等于两条曲线串联。
解决:确认驱动器工作在外部设定值模式时,内部加减速设为直通或极大值;NC 轴的加加速度设成 PLC 规划值的 3 倍以上,让它的时间常数远小于 PLC 侧,基本不参与整形。
5.2 短行程负时间炸数组
现象:目标距离只有几个毫米,一触发就报错,数组越界或者 FB 状态卡死。
原因:距离小于 Smin 时,如果不做降速分支,T2 会算出负值,数组下标和相位判断全部错乱。这是新手最先遇到也最烦的问题。
解决:在 PrepareProfile 里先算 Smin,距离不足时必须走降速分支,并判断 T2<0 时截成 0。现场我还会加一个超小距离保护:如果距离小于 0.1mm,直接不做 S 型规划,把设定值一步置到目标,等伺服跟随即可。
5.3 周期抖动造成加速度尖刺
现象:Scope 看 rAccAct 曲线,本应平滑的折线出现了密集毛刺,电机声音发毛,机械振动变大。
原因:FB 里用了固定的 fCycle,但任务实际周期不稳定;或者任务被其他高优先级任务打断,导致 dyT 与实际时间不一致。加速度尖刺直接等于冲击载荷。
解决:把规划 FB 放在独立的固定周期任务中,优先级调到最高;同时读 TwinCAT 的实际循环时间并滤波后参与积分,而不是用默认值 0.001。如果因为项目结构原因任务周期确实会波动,可以把规划器输出的位置设定值再过一层低通滤波,但要注意相位滞后不要太大。
5.4 浮点累计误差越跑越偏
现象:单次运行终点精度没问题,连续往复运行几百个循环后,轴的实际停止位置慢慢偏离目标。
原因:每周期做浮点积分,位置增量一直有舍入误差;修正段结束时虽然强制对齐,但之前在段切换时把残余时间带入了下一段,误差会以不同方式累积。
解决:除了终点强制对齐,还要把 rPhaseTime 的残余部分在段切换时保留并带入下一段(我代码里就是这么做的),保证七段总时间不漂移。如果驱动器寄存器只接受整数,用带舍入的转换而不是截断转换。
5.5 运行中改目标导致速度跳变
现象:轴还在运动中,工艺信号要求改到新目标位置,直接改 rTarget 后速度曲线出现突变,甚至反向瞬跳。
原因:外部设定值模式下,FB 处于 bBusy 状态时更新 rTarget,内部仍在执行旧 profile,两个距离冲突,输出位置不再是连续函数。
解决:运行中改目标必须先停止再重新规划。稳妥做法是:收到新目标后,让轴按当前规划走完剩下行程先停稳,再触发下一次 bExecute;如果工艺要求不停顿,就需要做一个「飞行改目标」的进阶功能,让当前速度、加速度作为新 profile 的初值,重新计算剩余距离的七段曲线。这个做法相对复杂,下一章单独说。
6. 进阶:运行中改目标的最稳处理与验收习惯
运行中改目标在印刷、贴装这类设备上很常见——前一个位置还没到,传感器就报了新的来料,停下来再走会掉节拍。最稳的处理不是往七段曲线中间硬插一段,而是把「当前速度」和「当前加速度」作为新规划的初值,从当前位置重新做一次完整规划。数学上,只要保证新 profile 在起点处的速度、加速度和当前位置正好等于旧曲线的瞬时值,拼接处就是连续的,实测速度曲线看不出拼接痕迹。
具体做法是把 FB 增加一组控制字:bAbort 上升沿时,锁存当前 rRel、rVel、rAcc 作为新起点状态,然后调用 PrepareProfile 计算从当前点移动到新目标的剩余距离。因为新起点加速度不是零,分段时间表的计算要复杂一些,我这里给一个工程上更常用的简化方案:收到改目标请求后,先生成一个「降速到零」的减速 profile,等轴停稳再走新目标。这个方案的代价是损失几百毫秒节拍,但胜在稳定,尤其适合对位置精度要求高的场合,因为它在数学上不需要推导带初速度的分段公式,出错概率大幅降低。
如果确实要做飞行改目标,我的习惯是做一个模拟验证脚本,Mathcad 或者 Excel 都行:把旧曲线和新曲线的速度、加速度在切换点对齐,画在同一张图上检查连续性。这事不能省,因为拼接点的速度差哪怕只是 1mm/s,在机械上都会表现为一次轻微冲击;拼接点加速度差则直接表现为一次加加速度尖峰,比速度差更伤机械。
回到这个 FB 本身,我还想强调一个验收习惯:每次在新设备上启用外部 S 型加减速,我会先跑三组测试——100mm 长行程看完整七段、5mm 中行程看降速五段、1mm 短行程看极短距离边界。三组波形全部合格,才允许接入实际工艺。从那以后,凡是启停有顿挫的设备,我都强制先跑这三组曲线再谈调参,很多时候问题根本不在 PID,而在 S 曲线没算对。希望帮到你。
本文还有配套的精品资源,点击获取