TwinCAT3 ST语言Modbus读写周期不一致问题及同步咨询
Modbus读写周期不一致问题排查与解决
问题描述
我在TwinCAT 3中使用ST语言编写了Modbus数据读写程序,任务周期设置为13ms。通过Wireshark日志观测发现,数据读取周期约为25ms,而写入周期约为50ms。程序代码如下:
IF NOT fbmbreadregs.bBusy THEN fbMBReadRegs( sIPAddr:= '192.168.2.20', nTCPPort:= 1504, nUnitID:= 16#FF, nQuantity:= 14, nMBAddr:= 0, cbLength:= SIZEOF(st_ModbusRead), pDestAddr:= ADR(st_ModbusRead), bExecute:= TRUE, tTimeout:= , bBusy=> , bError=> , nErrId=> , cbRead=> ); fbBasicPID( fSetpointValue:= 50, fActualValue:= st_ModbusRead.SOC, bReset:= FALSE, fCtrlCycleTime:=0.013 , fKp:= 0.00005, fTn:= 1, fTv:= 0, fTd:= 0, fCtrlOutput=> nFout, nErrorStatus=>); nPref:=(1/0.001)*(50-nFout); ELSE fbMBReadRegs(bExecute:= FALSE); END_IF IF NOT fbMBWriteRegs.bBusy THEN arrPQ[0]:=REAL_TO_WORD(ABS(nPref)); fbMBWriteRegs( sIPAddr:= '192.168.2.20', nTCPPort:= 1504, nUnitID:= 16#FF, nQuantity:= 5, nMBAddr:= 16#0014, cbLength:= SIZEOF(arrPQ), pSrcAddr:= ADR(arrPQ), bExecute:= TRUE, tTimeout:= , bBusy=> , bError=> , nErrId=> ); ELSE fbMBWriteRegs(bExecute:=FALSE); END_IF
请问为何二者周期存在差异?如何实现读写操作周期一致?
周期差异原因
- 异步功能块的串行执行逻辑:TwinCAT的Modbus TCP功能块是异步执行的,当前代码中读写逻辑串行绑定——读操作完成后才会触发PID计算,之后才尝试触发写操作,导致写操作的触发时机始终滞后于读操作,相当于读写请求排队执行,周期自然翻倍。
- 未设置超时参数:代码中
tTimeout参数为空,系统使用默认超时值(通常远大于13ms),若通信出现短暂延迟,功能块会等待超时后才重置bBusy,进一步拉长实际执行周期。 - 数据依赖导致触发延迟:写操作依赖读操作获取的
st_ModbusRead.SOC计算出的nPref,这强制写操作必须等待读操作和PID计算完成后才能触发,本质上让读写操作串行化,写周期至少是读周期的两倍。
实现读写周期一致的方案
方案1:并行触发读写操作(推荐)
调整逻辑,让读写请求在同一任务周期内独立触发,避免互相阻塞,仅在读操作完成时更新PID输入:
// 读操作逻辑:独立判断执行状态 IF NOT fbmbreadregs.bBusy THEN fbMBReadRegs( sIPAddr:= '192.168.2.20', nTCPPort:= 1504, nUnitID:= 16#FF, nQuantity:= 14, nMBAddr:= 0, cbLength:= SIZEOF(st_ModbusRead), pDestAddr:= ADR(st_ModbusRead), bExecute:= TRUE, tTimeout:= T#50MS, // 明确设置合理超时 bBusy=> , bError=> , nErrId=> , cbRead=> ); ELSE fbMBReadRegs(bExecute:= FALSE); END_IF // PID计算:仅在读操作完成时更新输入 IF NOT fbmbreadregs.bBusy THEN fbBasicPID( fSetpointValue:= 50, fActualValue:= st_ModbusRead.SOC, bReset:= FALSE, fCtrlCycleTime:=0.013 , fKp:= 0.00005, fTn:= 1, fTv:= 0, fTd:= 0, fCtrlOutput=> nFout, nErrorStatus=>); nPref:=(1/0.001)*(50-nFout); END_IF // 写操作逻辑:独立判断执行状态,不依赖读操作的bBusy IF NOT fbMBWriteRegs.bBusy THEN arrPQ[0]:=REAL_TO_WORD(ABS(nPref)); fbMBWriteRegs( sIPAddr:= '192.168.2.20', nTCPPort:= 1504, nUnitID:= 16#FF, nQuantity:= 5, nMBAddr:= 16#0014, cbLength:= SIZEOF(arrPQ), pSrcAddr:= ADR(arrPQ), bExecute:= TRUE, tTimeout:= T#50MS, // 明确设置合理超时 bBusy=> , bError=> , nErrId=> ); ELSE fbMBWriteRegs(bExecute:=FALSE); END_IF
方案2:使用同步Modbus功能块
如果不需要异步执行的灵活性,可以使用TwinCAT提供的同步Modbus功能块(如fbMBReadRegsSync、fbMBWriteRegsSync),这类功能块会在一个任务周期内完成通信(需确保网络延迟远小于13ms),严格遵循任务周期。注意同步块会阻塞任务执行,需谨慎设置超时时间,避免任务周期被拉长。
方案3:固定周期强制触发
通过周期计数器,强制每隔1个任务周期(13ms)触发一次读写请求,忽略临时的bBusy状态(需额外处理通信错误重试):
VAR nCycleCounter: UINT := 0; END_VAR nCycleCounter := nCycleCounter + 1; // 每1个周期触发读操作 IF nCycleCounter >= 1 THEN nCycleCounter := 0; fbMBReadRegs( sIPAddr:= '192.168.2.20', nTCPPort:= 1504, nUnitID:= 16#FF, nQuantity:= 14, nMBAddr:= 0, cbLength:= SIZEOF(st_ModbusRead), pDestAddr:= ADR(st_ModbusRead), bExecute:= TRUE, tTimeout:= T#50MS, bBusy=> , bError=> , nErrId=> , cbRead=> ); ELSE fbMBReadRegs(bExecute:= FALSE); END_IF // 同步触发写操作 IF nCycleCounter = 0 THEN arrPQ[0]:=REAL_TO_WORD(ABS(nPref)); fbMBWriteRegs( sIPAddr:= '192.168.2.20', nTCPPort:= 1504, nUnitID:= 16#FF, nQuantity:= 5, nMBAddr:= 16#0014, cbLength:= SIZEOF(arrPQ), pSrcAddr:= ADR(arrPQ), bExecute:= TRUE, tTimeout:= T#50MS, bBusy=> , bError=> , nErrId=> ); ELSE fbMBWriteRegs(bExecute:=FALSE); END_IF // PID计算逻辑保持不变 fbBasicPID( fSetpointValue:= 50, fActualValue:= st_ModbusRead.SOC, bReset:= FALSE, fCtrlCycleTime:=0.013 , fKp:= 0.00005, fTn:= 1, fTv:= 0, fTd:= 0, fCtrlOutput=> nFout, nErrorStatus=>); nPref:=(1/0.001)*(50-nFout);
内容的提问来源于stack exchange,提问作者juanda_
相关产品推荐
相关产品推荐

