<?xml version="1.0" encoding="UTF-8"?>        <rss version="2.0"
             xmlns:atom="http://www.w3.org/2005/Atom"
             xmlns:dc="http://purl.org/dc/elements/1.1/"
             xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
             xmlns:admin="http://webns.net/mvcb/"
             xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
             xmlns:content="http://purl.org/rss/1.0/modules/content/">
        <channel>
            <title>
									twinControls Forum - Recent Posts				            </title>
            <link>https://twincontrols.com/community/</link>
            <description>twinControls  Discussion Board</description>
            <language>en-US</language>
            <lastBuildDate>Mon, 26 Oct 2026 13:07:01 +0000</lastBuildDate>
            <generator>wpForo</generator>
            <ttl>60</ttl>
							                    <item>
                        <title>RE: Inquiry: Actual Position Leading Target Position in TwinCAT MoveAbsolute</title>
                        <link>https://twincontrols.com/community/twincat-troubleshooting/inquiry-actual-position-leading-target-position-in-twincat-moveabsolute/#post-2373</link>
                        <pubDate>Thu, 15 Oct 2026 08:32:18 +0000</pubDate>
                        <description><![CDATA[Hi dxt123,

Just to be sure, do you see the same behavior with Move Relative and Move Velocity?
Can you run an oscilloscope trace directly in the drive to verify whether the same behavior...]]></description>
                        <content:encoded><![CDATA[<p>Hi dxt123,</p>
<div>
<p>Just to be sure, do you see the same behavior with Move Relative and Move Velocity?</p>
<p>Can you run an oscilloscope trace directly in the drive to verify whether the same behavior occurs there? It could be a drive setup issue.</p>
</div>
<p><br /><br />Br lvs</p>]]></content:encoded>
						                            <category domain="https://twincontrols.com/community/"></category>                        <dc:creator>lvs</dc:creator>
                        <guid isPermaLink="true">https://twincontrols.com/community/twincat-troubleshooting/inquiry-actual-position-leading-target-position-in-twincat-moveabsolute/#post-2373</guid>
                    </item>
				                    <item>
                        <title>Inquiry: Actual Position Leading Target Position in TwinCAT MoveAbsolute</title>
                        <link>https://twincontrols.com/community/twincat-troubleshooting/inquiry-actual-position-leading-target-position-in-twincat-moveabsolute/#post-2372</link>
                        <pubDate>Thu, 15 Oct 2026 02:02:14 +0000</pubDate>
                        <description><![CDATA[I am writing to inquire about an unusual issue we have encountered while controlling a servo motor using the TwinCAT system.
Currently, we are operating the motor by inputting the target po...]]></description>
                        <content:encoded><![CDATA[<p data-path-to-node="2">I am writing to inquire about an unusual issue we have encountered while controlling a servo motor using the TwinCAT system.</p>
<p data-path-to-node="3">Currently, we are operating the motor by inputting the target position and velocity into the MoveAbsolute function block. While monitoring the servo motor's actual position (<code data-path-to-node="3" data-index-in-node="174">GVL.Axis1.NcToPlc.ModuloActPos</code>) using the Scope function, we noticed a strange behavior: during the initial phase of the movement, the actual position lags behind the target position. However, after a certain point, the actual position starts to lead the target position by approximately 1 degree.</p>
<p data-path-to-node="4">Unlike a standard following error (lagging), it is difficult for us to pinpoint why the motor would move ahead of the target position. Therefore, we are reaching out for your assistance.</p>
<p data-path-to-node="5">To help with your investigation, I have attached the relevant test files. In the attached <code data-path-to-node="5" data-index-in-node="90">test_profile.csv</code> file, 'F' represents the velocity and 'U' represents the target position.</p>
<p data-path-to-node="6">We would greatly appreciate your insights into the potential causes of this issue and any recommendations on additional parameters or settings we should inspect.</p>
<p data-path-to-node="6">Additionally, I have provided the PLC code below.</p>
<p data-path-to-node="7">Thank you for your support.<br /><br /><br /></p>
<div>PROGRAM MAIN</div>
<div>VAR</div>
<div><span> Robot1: FB_FANUC_Basic;</span></div>
<div><span> bReset: BOOL;</span></div>
<div><span> RobotEnable, RobotFault: BOOL;</span></div>
<div> </div>
<div><span> // Buffer</span></div>
<div><span> send_single_buffer_v2 : ARRAY OF REAL;</span></div>
<div><span> goto_buffer : ARRAY OF REAL; // Buffer to move robot for manual</span></div>
<div> </div>
<div><span> send_buffer1_1_v2, send_buffer1_2_v2, send_buffer1_3_v2,</span></div>
<div><span> send_buffer2_1_v2, send_buffer2_2_v2, send_buffer2_3_v2: ARRAY OF REAL;</span></div>
<div> </div>
<div><span> // Function Block</span></div>
<div><span> fb_custom_service : FB_CUSTOM_SERVICE; // Send a Sequence Data to Robot</span></div>
<div> </div>
<div><span> fbMove1, fbMove2, fbHome : MC_MoveModulo; // Turn Table Motor Move, Only Turn Table Motor Move, Turn Table Motor Homing</span></div>
<div><span> fbMove : MC_MoveAbsolute;</span></div>
<div><span> fbMove_rev1, fbMove_rev2, fbMove_rot1, fbMove_rot2 : MC_MoveVelocity; // Revolution &amp; Rotation Motor Move</span></div>
<div> </div>
<div><span> fbReset : MC_Reset; // Motor Reset</span></div>
<div> </div>
<div><span> fbPower, fbPower_rev, fbPower_rot : MC_Power; // Motor Power On</span></div>
<div> </div>
<div><span> mc_readactpos : MC_ReadActualPosition; // Read Position of Turn Table Motor</span></div>
<div><span> mc_readactvel_rev, mc_readactvel_rot : MC_ReadActualVelocity; // Read Velocity of Revolution &amp; Rotation Motor</span></div>
<div> </div>
<div><span> mc_stop, mc_stop_rev, mc_stop_rot : MC_Stop; // Motor Stop</span></div>
<div><span> mc_setpos: MC_SetPosition;</span></div>
<div><span> bSetDone: BOOL;</span></div>
<div> </div>
<div><span> fbTimer_SetPos: TON;</span></div>
<div><span> bTimer: BOOL;</span></div>
<div><span> REdge_setdone: R_TRIG;</span></div>
<div> </div>
<div> </div>
<div><span> // Variables</span></div>
<div><span> // Bool</span></div>
<div><span> bExecute : BOOL; // fb_custom_service.Execute</span></div>
<div><span> motor_trig1, motor_trig2 : BOOL; // Signal for cross-action of Motors</span></div>
<div><span> bStopDone, bStopDone_rev, bStopDone_rot : BOOL; // Stopped Rising Edge Signal</span></div>
<div><span> bHomeDone : BOOL; // Hoiming Done Signal</span></div>
<div><span> // LREAL</span></div>
<div><span> nPos, nVel : LREAL; // Input Data of Turn Table Motor</span></div>
<div><span> fServoCurrPos, fServoCurrVel_rev, fServoCurrVel_rot : LREAL; // Output Data of the Motors</span></div>
<div><span> // INT</span></div>
<div><span> nState : INT:= 0;</span></div>
<div><span> nPointIdx : INT:= 1;</span></div>
<div> </div>
<div><span> trig_status : INT:= 0;</span></div>
<div><span> // fb_custom_service variables</span></div>
<div><span> nServiceCode : BYTE;</span></div>
<div><span> nClass, nInstance, nAttribute : WORD;</span></div>
<div> </div>
<div><span> nNumberofData : DINT;</span></div>
<div><span> nBufferID : INT;</span></div>
<div><span> pCurrentAddr : PVOID;</span></div>
<div><span> nCurrentSize : WORD;</span></div>
<div> </div>
<div><span> nErrId : UDINT;</span></div>
<div><span> error : STRING;</span></div>
<div> </div>
<div><span> // Rising Edge Trigger</span></div>
<div><span> REdge_first : R_TRIG;</span></div>
<div><span> REdge_StopDone : R_TRIG;</span></div>
<div><span> REdge_StopDone_rev : R_TRIG;</span></div>
<div><span> REdge_StopDone_rot : R_TRIG;</span></div>
<div><span> REdge_Restart : R_TRIG;</span></div>
<div><span> REdge_HomeDone : R_TRIG;</span></div>
<div><span> REdge_DO48 : R_TRIG;</span></div>
<div> </div>
<div><span> bWaitCheckPoint: BOOL;</span></div>
<div><span> nNextState: INT;</span></div>
<div><span> bRobotAck: BOOL;</span></div>
<div> </div>
<div><span> bInit : BOOL:= TRUE;</span></div>
<div><span> bFirstStart : BOOL:= TRUE;</span></div>
<div><span> bNextStepReq : BOOL;</span></div>
<div><span> nCntIdx : INT;</span></div>
<div> </div>
<div><span> fbScanTimer : TON;</span></div>
<div><span> fbSetOverride : MC_SetOverride;</span></div>
<div> </div>
<div><span> fRobotFirstPos: LREAL;</span></div>
<div><span> fRobotStartPos, fRobotTargetPos, fRobotActualPos,</span></div>
<div><span> fRobotTotalDist, fRobotRemainRatio,</span></div>
<div><span> fMotorStartPos, fMotorTargetPos, fMotorActualPos,</span></div>
<div><span> fMotorTotalDist, fMotorVirtualTarget : LREAL;</span></div>
<div> </div>
<div><span> fSampledTargetPos, fSampledActualPos : LREAL;</span></div>
<div> </div>
<div><span> fOutput : LREAL;</span></div>
<div><span> fCalculatedOverride : LREAL:= 0.0;</span></div>
<div> </div>
<div><span> nPos_X: UDINT:= 0;</span></div>
<div><span> nPos_Y: UDINT:= 0;</span></div>
<div><span> fBackupValue: LREAL;</span></div>
<div><span> fRelativePos: LREAL;</span></div>
<div><span> REdge_bStart: R_TRIG;</span></div>
<div> </div>
<div><span> fScaled_Kp: LREAL;</span></div>
<div><span> fDrivePosLoopGain: LREAL:= 32.0;</span></div>
<div><span> fPositionError: LREAL;</span></div>
<div> </div>
<div><span> bSequenceMode: BOOL:= FALSE;</span></div>
<div><span> nXorY: INT;</span></div>
<div><span> nRobotIdx: UDINT:=0;</span></div>
<div><span> nRobotIdx_int: INT;</span></div>
<div><span> frobot: LREAL;</span></div>
<div><span> nTrackingIdx: INT:= 1;</span></div>
<div><span> nTotalProcessCnt: DINT:= 1;</span></div>
<div>END_VAR</div>
<div> </div>
<div>Robot1.Update();</div>
<div>RobotEnable:= Robot1.qEnable;</div>
<div>RobotFault:= Robot1.qFault;</div>
<div> </div>
<div>CASE nBufferID OF</div>
<div><span> 1:</span></div>
<div><span> pCurrentAddr:= ADR(send_single_buffer_v2);</span></div>
<div><span> nCurrentSize:= SIZEOF(send_single_buffer_v2);</span></div>
<div><span> 2:</span></div>
<div><span> pCurrentAddr:= ADR(goto_buffer);</span></div>
<div><span> nCurrentSize:= SIZEOF(goto_buffer);</span></div>
<div><span> 11:</span></div>
<div><span> pCurrentAddr:= ADR(send_buffer1_1_v2);</span></div>
<div><span> nCurrentSize:= SIZEOF(send_buffer1_1_v2);</span></div>
<div><span> 12:</span></div>
<div><span> pCurrentAddr:= ADR(send_buffer1_2_v2);</span></div>
<div><span> nCurrentSize:= SIZEOF(send_buffer1_2_v2);</span></div>
<div><span> 13:</span></div>
<div><span> pCurrentAddr:= ADR(send_buffer1_3_v2);</span></div>
<div><span> nCurrentSize:= SIZEOF(send_buffer1_3_v2);</span></div>
<div><span> 21:</span></div>
<div><span> pCurrentAddr:= ADR(send_buffer2_1_v2);</span></div>
<div><span> nCurrentSize:= SIZEOF(send_buffer2_1_v2);</span></div>
<div><span> 22:</span></div>
<div><span> pCurrentAddr:= ADR(send_buffer2_2_v2);</span></div>
<div><span> nCurrentSize:= SIZEOF(send_buffer2_2_v2);</span></div>
<div><span> 23:</span></div>
<div><span> pCurrentAddr:= ADR(send_buffer2_3_v2);</span></div>
<div><span> nCurrentSize:= SIZEOF(send_buffer2_3_v2);</span></div>
<div><span> 99:</span></div>
<div><span> pCurrentAddr:= ADR(nNumberofData);</span></div>
<div><span> nCurrentSize:= SIZEOF(nNumberofData);</span></div>
<div>END_CASE</div>
<div> </div>
<div> </div>
<div>fb_custom_service(</div>
<div><span> sNetId:= '192.168.0.196.3.1', </span></div>
<div><span> sIPv4Addr:= '100.100.100.1', </span></div>
<div><span> bExecute:= bExecute, </span></div>
<div><span> nServiceCode:= nServiceCode, </span></div>
<div><span> nClass:= nClass, </span></div>
<div><span> nInstance:= nInstance, </span></div>
<div><span> nAttribute:= nAttribute, </span></div>
<div><span> pSrc:= pCurrentAddr, </span></div>
<div><span> nSrcDataLen:= nCurrentSize, </span></div>
<div>);</div>
<div> </div>
<div>error:= F_GET_ETHERNETIP_ERROR_TEXT(nErrorId:= nErrId);</div>
<div> </div>
<div> </div>
<div>fbPower(Axis:= GVL.Axis1, Enable:= TRUE, Enable_Positive:= TRUE, Enable_Negative:= TRUE, Override:= 100.0);</div>
<div>fbPower_rev(Axis:= GVL.Axis2, Enable:= TRUE, Enable_Positive:= TRUE, Enable_Negative:= TRUE, Override:= 100.0);</div>
<div>fbPower_rot(Axis:= GVL.Axis3, Enable:= TRUE, Enable_Positive:= TRUE, Enable_Negative:= TRUE, Override:= 100.0);</div>
<div>fbReset(Axis:= GVL.Axis1, Execute:= GVL.bReset);</div>
<div> </div>
<div>IF GVL.bReset THEN</div>
<div><span> nState:= 0;</span></div>
<div><span> GVL.bStart:= FALSE;</span></div>
<div><span> GVL.bReqUpdateLower:= FALSE;</span></div>
<div><span> GVL.bReqUpdateUpper:= FALSE;</span></div>
<div>END_IF</div>
<div> </div>
<div>CASE nState OF</div>
<div><span> 0:</span></div>
<div><span> IF GVL.bStart THEN</span></div>
<div><span> nPointIdx:= 1;</span></div>
<div><span> nState:= 10;</span></div>
<div><span> END_IF</span></div>
<div><span> 10:</span></div>
<div><span> fbMove1.Position:= GVL.aPathData.fPosition;</span></div>
<div><span> fbMove1.Velocity:= GVL.aPathData.fVelocity;</span></div>
<div><span> fbMove1.BufferMode:= MC_BufferMode.MC_Aborting;</span></div>
<div><span> fbMove1.Direction:= MC_Positive_Direction;</span></div>
<div><span> fbMove1.Execute:= TRUE;</span></div>
<div> </div>
<div><span> fbMove_rev1.Velocity:= GVL.aPathData.fVelocity2;</span></div>
<div><span> fbMove_rev1.BufferMode:= MC_BufferMode.MC_Aborting;</span></div>
<div><span> fbMove_rev1.Execute:= TRUE;</span></div>
<div> </div>
<div><span> fbMove_rot1.Velocity:= GVL.aPathData.fVelocity3;</span></div>
<div><span> fbMove_rot1.BufferMode:= MC_BufferMode.MC_Aborting;</span></div>
<div><span> fbMove_rot1.Execute:= TRUE;</span></div>
<div> </div>
<div><span> IF fbMove1.Active AND fbMove_rev1.Active AND fbMove_rot1.Active THEN</span></div>
<div><span> fbMove1.Execute:= FALSE;</span></div>
<div><span> fbMove_rev1.Execute:= FALSE;</span></div>
<div><span> fbMove_rot1.Execute:= FALSE;</span></div>
<div><span> nPointIdx:= nPointIdx + 1;</span></div>
<div><span> motor_trig1:= TRUE;</span></div>
<div><span> nState:= 20;</span></div>
<div><span> END_IF</span></div>
<div> </div>
<div><span> 20:</span></div>
<div><span> fbMove2.Position:= GVL.aPathData.fPosition;</span></div>
<div><span> fbMove2.Velocity:= GVL.aPathData.fVelocity;</span></div>
<div><span> fbMove2.BufferMode:= MC_BufferMode.MC_BlendingPrevious;</span></div>
<div><span> fbMove2.Direction:= MC_Positive_Direction;</span></div>
<div> </div>
<div><span> fbMove_rev2.Velocity:= GVL.aPathData.fVelocity2;</span></div>
<div><span> fbMove_rev2.BufferMode:= MC_BufferMode.MC_BlendingPrevious;</span></div>
<div> </div>
<div><span> fbMove_rot2.Velocity:= GVL.aPathData.fVelocity3;</span></div>
<div><span> fbMove_rot2.BufferMode:= MC_BufferMode.MC_BlendingPrevious;</span></div>
<div> </div>
<div><span> IF motor_trig1 THEN</span></div>
<div><span> IF GVL.aPathData.fVelocity &gt; 0.0 THEN</span></div>
<div><span> fbMove2.Execute:= TRUE;</span></div>
<div><span> fbMove_rev2.Execute:= TRUE;</span></div>
<div><span> fbMove_rot2.Execute:= TRUE;</span></div>
<div><span> ELSE</span></div>
<div><span> motor_trig1:= FALSE;</span></div>
<div><span> nState:= 99;</span></div>
<div><span> END_IF</span></div>
<div><span> END_IF</span></div>
<div><span> IF fbMove2.Active AND fbMove_rev2.Active AND fbMove_rot2.Active THEN</span></div>
<div><span> fbMove2.Execute:= FALSE;</span></div>
<div><span> fbMove_rev2.Execute:= FALSE;</span></div>
<div><span> fbMove_rot2.Execute:= FALSE;</span></div>
<div><span> motor_trig1:= FALSE;</span></div>
<div><span> nPointIdx:= nPointIdx + 1;</span></div>
<div><span> nTotalProcessCnt:= nTotalProcessCnt + 1;</span></div>
<div> </div>
<div><span> IF (nTotalProcessCnt &gt; 1) AND (((nTotalProcessCnt - 1) MOD 63) = 0) THEN</span></div>
<div><span> bWaitCheckPoint:= TRUE;</span></div>
<div><span> END_IF</span></div>
<div><span> IF nPointIdx = 501 THEN</span></div>
<div><span> GVL.bReqUpdateLower:= TRUE;</span></div>
<div><span> ELSIF nPointIdx &gt; GVL.nMaxPoints THEN</span></div>
<div><span> nPointIdx:= 1;</span></div>
<div><span> IF NOT GVL.bAllDataFinished THEN</span></div>
<div><span> GVL.bReqUpdateUpper:= TRUE;</span></div>
<div><span> END_IF</span></div>
<div><span> END_IF</span></div>
<div><span> IF nState &lt;&gt; 99 THEN</span></div>
<div><span> IF bWaitCheckPoint THEN</span></div>
<div><span> nNextState:= 30;</span></div>
<div><span> nState:= 40;</span></div>
<div><span> ELSE</span></div>
<div><span> motor_trig2:= TRUE;</span></div>
<div><span> nState:= 30;</span></div>
<div><span> END_IF </span></div>
<div><span> END_IF</span></div>
<div><span> ELSIF fbMove2.Error THEN</span></div>
<div><span> nState:= 900;</span></div>
<div><span> END_IF</span></div>
<div><span> 30:</span></div>
<div><span> fbMove1.Position:= GVL.aPathData.fPosition;</span></div>
<div><span> fbMove1.Velocity:= GVL.aPathData.fVelocity;</span></div>
<div><span> fbMove1.BufferMode:= MC_BufferMode.MC_BlendingPrevious;</span></div>
<div><span> fbMove1.Direction:= MC_Positive_Direction;</span></div>
<div> </div>
<div><span> fbMove_rev1.Velocity:= GVL.aPathData.fVelocity2;</span></div>
<div><span> fbMove_rev1.BufferMode:= MC_BufferMode.MC_BlendingPrevious;</span></div>
<div> </div>
<div><span> fbMove_rot1.Velocity:= GVL.aPathData.fVelocity3;</span></div>
<div><span> fbMove_rot1.BufferMode:= MC_BufferMode.MC_BlendingPrevious;</span></div>
<div> </div>
<div><span> IF motor_trig2 THEN</span></div>
<div><span> IF GVL.aPathData.fVelocity &gt; 0.0 THEN</span></div>
<div><span> fbMove1.Execute:= TRUE;</span></div>
<div><span> fbMove_rev1.Execute:= TRUE;</span></div>
<div><span> fbMove_rot1.Execute:= TRUE;</span></div>
<div><span> ELSE</span></div>
<div><span> motor_trig2:= FALSE;</span></div>
<div><span> nState:= 99;</span></div>
<div><span> END_IF</span></div>
<div><span> END_IF</span></div>
<div><span> IF fbMove1.Active AND fbMove_rev1.Active AND fbMove_rot1.Active THEN</span></div>
<div><span> fbMove1.Execute:= FALSE;</span></div>
<div><span> fbMove_rev1.Execute:= FALSE;</span></div>
<div><span> fbMove_rot1.Execute:= FALSE;</span></div>
<div><span> motor_trig2:= FALSE;</span></div>
<div><span> nPointIdx:= nPointIdx + 1;</span></div>
<div><span> nTotalProcessCnt:= nTotalProcessCnt + 1;</span></div>
<div> </div>
<div><span> IF (nTotalProcessCnt &gt; 1) AND (((nTotalProcessCnt - 1) MOD 63) = 0) THEN</span></div>
<div><span> bWaitCheckPoint:= TRUE;</span></div>
<div><span> END_IF</span></div>
<div><span> IF nPointIdx = 501 THEN</span></div>
<div><span> GVL.bReqUpdateLower:= TRUE;</span></div>
<div><span> ELSIF nPointIdx &gt; GVL.nMaxPoints THEN</span></div>
<div><span> nPointIdx:= 1;</span></div>
<div><span> IF NOT GVL.bAllDataFinished THEN</span></div>
<div><span> GVL.bReqUpdateUpper:= TRUE;</span></div>
<div><span> END_IF</span></div>
<div><span> END_IF</span></div>
<div><span> IF nState &lt;&gt; 99 THEN</span></div>
<div><span> IF bWaitCheckPoint THEN</span></div>
<div><span> nNextState:= 20;</span></div>
<div><span> nState:= 40;</span></div>
<div><span> ELSE</span></div>
<div><span> motor_trig1:= TRUE;</span></div>
<div><span> nState:= 20;</span></div>
<div><span> END_IF </span></div>
<div><span> END_IF</span></div>
<div><span> ELSIF fbMove1.Error THEN</span></div>
<div><span> nState:= 900;</span></div>
<div><span> END_IF</span></div>
<div> </div>
<div><span> 40:</span></div>
<div><span> IF Robot1._UO1.DO237 THEN</span></div>
<div><span> Robot1._UI1.DI44:= TRUE;</span></div>
<div><span> bRobotAck:= TRUE;</span></div>
<div><span> END_IF</span></div>
<div> </div>
<div><span> IF bRobotAck AND NOT Robot1._UO1.DO237 THEN</span></div>
<div><span> Robot1._UI1.DI44:= FALSE;</span></div>
<div><span> bRobotAck:= FALSE;</span></div>
<div><span> bWaitCheckPoint:= FALSE;</span></div>
<div> </div>
<div><span> IF nNextState = 20 THEN</span></div>
<div><span> motor_trig1 := TRUE;</span></div>
<div><span> ELSIF nNextState = 30 THEN</span></div>
<div><span> motor_trig2 := TRUE;</span></div>
<div><span> END_IF</span></div>
<div><span> nState := nNextState;</span></div>
<div><span> END_IF</span></div>
<div> </div>
<div><span> 99:</span></div>
<div><span> IF NOT fbMove1.Busy AND NOT fbMove2.Busy THEN</span></div>
<div><span> IF Robot1._UO1.DO237 THEN</span></div>
<div><span> Robot1._UI1.DI44:= TRUE;</span></div>
<div><span> ELSE</span></div>
<div><span> Robot1._UI1.DI44:= FALSE;</span></div>
<div><span> nState:= 0;</span></div>
<div><span> nPointIdx:= 1;</span></div>
<div><span> nTrackingIdx:= 1;</span></div>
<div> </div>
<div><span> motor_trig1:= FALSE;</span></div>
<div><span> motor_trig2:= FALSE;</span></div>
<div> </div>
<div><span> bWaitCheckPoint:= FALSE;</span></div>
<div><span> bRobotAck:= FALSE;</span></div>
<div><span> nNextState:= 0; </span></div>
<div> </div>
<div><span> fbMove1.Execute:= FALSE;</span></div>
<div><span> fbMove2.Execute:= FALSE;</span></div>
<div><span> fbMove_rev1.Execute:= FALSE;</span></div>
<div><span> fbMove_rev2.Execute:= FALSE;</span></div>
<div><span> fbMove_rot1.Execute:= FALSE;</span></div>
<div><span> fbMove_rot2.Execute:= FALSE;</span></div>
<div> </div>
<div><span> GVL.bStart:= FALSE;</span></div>
<div><span> IF NOT GVL.bStop_rev THEN</span></div>
<div><span> GVL.bStop_rev:= TRUE;</span></div>
<div><span> END_IF</span></div>
<div><span> IF NOT GVL.bStop_rot THEN</span></div>
<div><span> GVL.bStop_rot:= TRUE;</span></div>
<div><span> END_IF</span></div>
<div><span> bSequenceMode:= FALSE;</span></div>
<div><span> END_IF</span></div>
<div><span> END_IF</span></div>
<div><span> 900:</span></div>
<div><span> IF NOT fbMove1.Busy AND NOT fbMove2.Busy THEN</span></div>
<div><span> GVL.bStart:= FALSE;</span></div>
<div><span> nState:= 0;</span></div>
<div><span> nPointIdx:= 1;</span></div>
<div><span> nCntIdx:= 0;</span></div>
<div><span> bSequenceMode:= FALSE;</span></div>
<div><span> END_IF</span></div>
<div> </div>
<div>END_CASE</div>
<div> </div>
<div>fbMove1(Axis:= GVL.Axis1);</div>
<div>fbMove2(Axis:= GVL.Axis1);</div>
<div>fbMove_rev1(Axis:= GVL.Axis2);</div>
<div>fbMove_rev2(Axis:= GVL.Axis2);</div>
<div>fbMove_rot1(Axis:= GVL.Axis3);</div>
<div>fbMove_rot2(Axis:= GVL.Axis3);</div>
<div> </div>
<div>IF GVL.bReqUpdateLower AND GVL.bUpdateDone THEN</div>
<div><span> GVL.bReqUpdateLower:= FALSE;</span></div>
<div>END_IF</div>
<div>IF GVL.bReqUpdateUpper AND GVL.bUpdateDone THEN</div>
<div><span> GVL.bReqUpdateUpper:= FALSE;</span></div>
<div>END_IF</div>
<div> </div>
<div>REdge_first(CLK:= MAIN.Robot1._UO1.DO45);</div>
<div>IF REdge_first.Q THEN</div>
<div><span> GVL.bStart:= TRUE;</span></div>
<div>END_IF</div>
<div> </div>
<div>fbMove(</div>
<div><span> Axis:= GVL.Axis1,</span></div>
<div><span> Execute:= GVL.bStart2,</span></div>
<div><span> Position:= nPos,</span></div>
<div><span> Velocity:= nVel</span></div>
<div>);</div>
<div> </div>
<div>fbHome(</div>
<div><span> Axis:= GVL.Axis1, </span></div>
<div><span> Execute:= GVL.bHome, </span></div>
<div><span> Position:= 0.0, </span></div>
<div><span> Velocity:= 10, </span></div>
<div><span> Direction:= MC_Direction.MC_Shortest_Way, </span></div>
<div><span> Done=&gt; bHomeDone, </span></div>
<div>);</div>
<div> </div>
<div>mc_readactpos(</div>
<div><span> Axis:= GVL.Axis1, </span></div>
<div><span> Enable:= TRUE,  </span></div>
<div><span> Position=&gt; fServoCurrPos</span></div>
<div>);</div>
<div> </div>
<div>mc_readactvel_rev(</div>
<div><span> Axis:= GVL.Axis2, </span></div>
<div><span> Enable:= TRUE, </span></div>
<div><span> ActualVelocity=&gt; fServoCurrVel_rev</span></div>
<div>);</div>
<div> </div>
<div>mc_readactvel_rot(</div>
<div><span> Axis:= GVL.Axis3, </span></div>
<div><span> Enable:= TRUE, </span></div>
<div><span> ActualVelocity=&gt; fServoCurrVel_rot</span></div>
<div>);<span> </span></div>
<div> </div>
<div>mc_stop(</div>
<div><span> Axis:= GVL.Axis1, </span></div>
<div><span> Execute:= GVL.bStop, </span></div>
<div><span> Done=&gt; bStopDone, </span></div>
<div>);</div>
<div> </div>
<div>mc_stop_rev(</div>
<div><span> Axis:= GVL.Axis2, </span></div>
<div><span> Execute:= GVL.bStop_rev,  </span></div>
<div><span> Done=&gt; bStopDone_rev, </span></div>
<div>);</div>
<div> </div>
<div>mc_stop_rot(</div>
<div><span> Axis:= GVL.Axis3, </span></div>
<div><span> Execute:= GVL.bStop_rot, </span></div>
<div><span> Done=&gt; bStopDone_rot, </span></div>
<div>);</div>
<div> </div>
<div>mc_setpos(</div>
<div><span> Axis:= GVL.Axis1,</span></div>
<div><span> Position:= 0.0,</span></div>
<div><span> Done=&gt; bSetDone</span></div>
<div>);</div>
<div> </div>
<div>REdge_HomeDone(CLK:= bHomeDone);</div>
<div>IF REdge_HomeDone.Q THEN</div>
<div><span> GVL.bHome:= FALSE;</span></div>
<div><span> bTimer:= TRUE;</span></div>
<div>END_IF</div>
<div>fbTimer_SetPos(IN:= bTimer, PT:= T#1S);</div>
<div>IF fbTimer_SetPos.Q THEN</div>
<div><span> mc_setpos.Execute:= TRUE;</span></div>
<div>END_IF</div>
<div>REdge_setdone(CLK:= bSetDone);</div>
<div>IF REdge_setdone.Q THEN</div>
<div><span> mc_setpos.Execute:= FALSE;</span></div>
<div><span> bTimer:= FALSE;</span></div>
<div>END_IF</div>
<div> </div>
<div>REdge_StopDone(CLK:= bStopDone);</div>
<div>IF REdge_StopDone.Q THEN</div>
<div><span> GVL.bStart := FALSE;</span></div>
<div><span> GVL.bStart2 := FALSE;</span></div>
<div><span> IF motor_trig1 THEN</span></div>
<div><span> trig_status:= 1;</span></div>
<div><span> motor_trig1:= FALSE;</span></div>
<div><span> fbMove2.Execute:= FALSE;</span></div>
<div><span> fbMove_rev2.Execute:= FALSE;</span></div>
<div><span> fbMove_rot2.Execute:= FALSE;</span></div>
<div><span> ELSIF motor_trig2 THEN</span></div>
<div><span> trig_status:= 2;</span></div>
<div><span> motor_trig2:= FALSE;</span></div>
<div><span> fbMove1.Execute:= FALSE;</span></div>
<div><span> fbMove_rev1.Execute:= FALSE;</span></div>
<div><span> fbMove_rot1.Execute:= FALSE;</span></div>
<div><span> END_IF</span></div>
<div><span> GVL.bStop:= FALSE;</span></div>
<div>END_IF</div>
<div> </div>
<div>REdge_StopDone_rev(CLK:= bStopDone_rev);</div>
<div>IF REdge_StopDone_rev.Q THEN</div>
<div><span> GVL.bStop_rev:= FALSE;</span></div>
<div>END_IF</div>
<div> </div>
<div>REdge_StopDone_rot(CLK:= bStopDone_rot);</div>
<div>IF REdge_StopDone_rot.Q THEN</div>
<div><span> GVL.bStop_rot:= FALSE;</span></div>
<div>END_IF</div>
<div> </div>
<div>REdge_Restart(CLK:= GVL.bRestart);</div>
<div>IF REdge_Restart.Q THEN</div>
<div><span> GVL.bStart:= TRUE;</span></div>
<div><span> IF trig_status = 1 THEN</span></div>
<div><span> motor_trig1:= TRUE;</span></div>
<div><span> ELSIF trig_status = 2 THEN</span></div>
<div><span> motor_trig2:= TRUE;</span></div>
<div><span> END_IF</span></div>
<div><span> GVL.bRestart:= FALSE;</span></div>
<div>END_IF</div>
<div> </div>
<div>nPos_X.0:= Robot1._UO1.Xl0;</div>
<div>nPos_X.1:= Robot1._UO1.Xl1;</div>
<div>nPos_X.2:= Robot1._UO1.Xl2;</div>
<div>nPos_X.3:= Robot1._UO1.Xl3;</div>
<div>nPos_X.4:= Robot1._UO1.Xl4;</div>
<div>nPos_X.5:= Robot1._UO1.Xl5;</div>
<div>nPos_X.6:= Robot1._UO1.Xl6;</div>
<div>nPos_X.7:= Robot1._UO1.Xl7;</div>
<div>nPos_X.8:= Robot1._UO1.Xl8;</div>
<div>nPos_X.9:= Robot1._UO1.Xl9;</div>
<div>nPos_X.10:= Robot1._UO1.Xl10;</div>
<div>nPos_X.11:= Robot1._UO1.Xl11;</div>
<div>nPos_X.12:= Robot1._UO1.Xl12;</div>
<div>nPos_X.13:= Robot1._UO1.Xl13;</div>
<div>nPos_X.14:= Robot1._UO1.Xl14;</div>
<div>nPos_X.15:= Robot1._UO1.Xl15;</div>
<div>nPos_X.16:= Robot1._UO1.Xh0;</div>
<div>nPos_X.17:= Robot1._UO1.Xh1;</div>
<div>nPos_X.18:= Robot1._UO1.Xh2;</div>
<div>nPos_X.19:= Robot1._UO1.Xh3;</div>
<div>nPos_X.20:= Robot1._UO1.Xh4;</div>
<div>nPos_X.21:= Robot1._UO1.Xh5;</div>
<div>nPos_X.22:= Robot1._UO1.Xh6;</div>
<div>nPos_X.23:= Robot1._UO1.Xh7;</div>
<div> </div>
<div>nPos_Y.0:= Robot1._UO1.Yl0;</div>
<div>nPos_Y.1:= Robot1._UO1.Yl1;</div>
<div>nPos_Y.2:= Robot1._UO1.Yl2;</div>
<div>nPos_Y.3:= Robot1._UO1.Yl3;</div>
<div>nPos_Y.4:= Robot1._UO1.Yl4;</div>
<div>nPos_Y.5:= Robot1._UO1.Yl5;</div>
<div>nPos_Y.6:= Robot1._UO1.Yl6;</div>
<div>nPos_Y.7:= Robot1._UO1.Yl7;</div>
<div>nPos_Y.8:= Robot1._UO1.Yl8;</div>
<div>nPos_Y.9:= Robot1._UO1.Yl9;</div>
<div>nPos_Y.10:= Robot1._UO1.Yl10;</div>
<div>nPos_Y.11:= Robot1._UO1.Yl11;</div>
<div>nPos_Y.12:= Robot1._UO1.Yl12;</div>
<div>nPos_Y.13:= Robot1._UO1.Yl13;</div>
<div>nPos_Y.14:= Robot1._UO1.Yl14;</div>
<div>nPos_Y.15:= Robot1._UO1.Yl15;</div>
<div>nPos_Y.16:= Robot1._UO1.Yh0;</div>
<div>nPos_Y.17:= Robot1._UO1.Yh1;</div>
<div>nPos_Y.18:= Robot1._UO1.Yh2;</div>
<div>nPos_Y.19:= Robot1._UO1.Yh3;</div>
<div>nPos_Y.20:= Robot1._UO1.Yh4;</div>
<div>nPos_Y.21:= Robot1._UO1.Yh5;</div>
<div>nPos_Y.22:= Robot1._UO1.Yh6;</div>
<div>nPos_Y.23:= Robot1._UO1.Yh7;</div>
<div> </div>
<div>IF nXorY = 0 THEN</div>
<div><span> fRobotActualPos:= UDINT_TO_LREAL(nPos_X) / 1000.0;</span></div>
<div><span> IF Robot1._UO1.X_Check THEN</span></div>
<div><span> fRobotActualPos:= fRobotActualPos * (-1);</span></div>
<div><span> END_IF</span></div>
<div>ELSIF nXorY = 1 THEN</div>
<div><span> fRobotActualPos:= UDINT_TO_LREAL(nPos_Y) / 1000.0;</span></div>
<div><span> IF Robot1._UO1.Y_Check THEN</span></div>
<div><span> fRobotActualPos:= fRobotActualPos * (-1);</span></div>
<div><span> END_IF</span></div>
<div>END_IF</div>
<div> </div>
<div>REdge_bStart(CLK:= Robot1._UI1.UI09_RSR1);</div>
<div>IF REdge_bStart.Q THEN</div>
<div><span> fBackupValue:= fRobotActualPos;</span></div>
<div><span> bSequenceMode:= TRUE;</span></div>
<div> </div>
<div><span> (*fRobotTargetPos := fRobotFirstPos; </span></div>
<div><span> fRobotStartPos  := fRobotFirstPos;</span></div>
<div><span> fMotorTargetPos := 0.0;</span></div>
<div><span> fMotorStartPos  := 0.0;*)</span></div>
<div> </div>
<div><span> nTotalProcessCnt:= 1;</span></div>
<div><span> nTrackingIdx:= 1;</span></div>
<div><span> fRobotTargetPos:= GVL.aPathData.fRobot_Pos;</span></div>
<div><span> fRobotStartPos:= fRobotFirstPos;</span></div>
<div><span> fMotorTargetPos:= GVL.aPathData.fPosition;</span></div>
<div><span> fMotorStartPos:= 0.0;</span></div>
<div> </div>
<div><span> fRobotTotalDist := 0.0;</span></div>
<div><span> fRobotRemainRatio := 0.0;</span></div>
<div> </div>
<div><span> fSampledTargetPos := GVL.Axis1.NcToPlc.ModuloActPos; </span></div>
<div><span> fPositionError := 0.0;</span></div>
<div><span> fOutput := 0.0;</span></div>
<div> </div>
<div><span> fCalculatedOverride := 0.8;</span></div>
<div>END_IF</div>
<div>fRelativePos:= fRobotActualPos - fBackupValue;</div>
<div> </div>
<div>IF bSequenceMode THEN</div>
<div><span> IF ABS(fRelativePos) &gt;= ABS(fRobotTargetPos) THEN</span></div>
<div><span> nTrackingIdx:= nTrackingIdx + 1;</span></div>
<div><span> IF nTrackingIdx &gt; GVL.nMaxPoints THEN</span></div>
<div><span> nTrackingIdx:= 1;</span></div>
<div><span> fRobotTargetPos:= GVL.aPathData.fRobot_Pos;</span></div>
<div><span> fRobotStartPos:= GVL.aPathData.fRobot_Pos;</span></div>
<div><span> fMotorTargetPos:= GVL.aPathData.fPosition;</span></div>
<div><span> fMotorStartPos:= GVL.aPathData.fPosition;</span></div>
<div><span> ELSE</span></div>
<div><span> fRobotTargetPos:= GVL.aPathData.fRobot_Pos;</span></div>
<div><span> fRobotStartPos:= GVL.aPathData.fRobot_Pos;</span></div>
<div><span> fMotorTargetPos:= GVL.aPathData.fPosition;</span></div>
<div><span> fMotorStartPos:= GVL.aPathData.fPosition;</span></div>
<div><span> END_IF</span></div>
<div><span> END_IF</span></div>
<div>END_IF</div>
<div> </div>
<div>fbScanTimer(IN:= NOT fbScanTimer.Q, PT:= T#10MS);</div>
<div>IF fbScanTimer.Q THEN</div>
<div><span> fRobotTotalDist:= fRobotTargetPos - fRobotStartPos;</span></div>
<div><span> IF fRobotTotalDist &lt;&gt; 0 THEN</span></div>
<div><span> fRobotRemainRatio:= (fRobotTargetPos - fRelativePos) / fRobotTotalDist;</span></div>
<div><span> ELSE</span></div>
<div><span> fRobotRemainRatio:= 0.0;</span></div>
<div><span> END_IF</span></div>
<div> </div>
<div><span> IF fMotorTargetPos = 0 THEN</span></div>
<div><span> fMotorTargetPos:= 360;</span></div>
<div><span> END_IF</span></div>
<div><span> fMotorTotalDist:= fMotorTargetPos - fMotorStartPos;</span></div>
<div><span> fMotorVirtualTarget:= fMotorTargetPos - (fMotorTotalDist * fRobotRemainRatio);</span></div>
<div> </div>
<div><span> fSampledTargetPos:= fMotorVirtualTarget;</span></div>
<div><span> fSampledActualPos:= GVL.Axis1.NcToPlc.ModuloActPos;</span></div>
<div>END_IF</div>
<div> </div>
<div>fScaled_Kp:= fDrivePosLoopGain / 500.0;</div>
<div>fPositionError:= fSampledTargetPos - fSampledActualPos;</div>
<div> </div>
<div>fOutput:= fPositionError * fScaled_Kp;</div>
<div> </div>
<div>fCalculatedOverride:= LIMIT(0.5, (0.8 + fOutput), 1.0);<span> </span></div>
<div> </div>
<div>nRobotIdx.0:= Robot1._UO1.DO221;</div>
<div>nRobotIdx.1:= Robot1._UO1.DO222;</div>
<div>nRobotIdx.2:= Robot1._UO1.DO223;</div>
<div>nRobotIdx.3:= Robot1._UO1.DO224;</div>
<div>nRobotIdx.4:= Robot1._UO1.DO225;</div>
<div>nRobotIdx.5:= Robot1._UO1.DO226;</div>
<div>nRobotIdx.6:= Robot1._UO1.DO227;</div>
<div>nRobotIdx.7:= Robot1._UO1.DO228;</div>
<div>nRobotIdx.8:= Robot1._UO1.DO229;</div>
<div>nRobotIdx.9:= Robot1._UO1.DO230;</div>
<div>nRobotIdx.10:= Robot1._UO1.DO231;</div>
<div>nRobotIdx.11:= Robot1._UO1.DO232;</div>
<div>nRobotIdx.12:= Robot1._UO1.DO233;</div>
<div>nRobotIdx.13:= Robot1._UO1.DO234;</div>
<div>nRobotIdx.14:= Robot1._UO1.DO235;</div>
<div>nRobotIdx.15:= Robot1._UO1.DO236;</div>
<div> </div>
<div>nRobotIdx_int:= UDINT_TO_INT(nRobotIdx);</div>
<div>IF nRobotIdx_int &gt;= 1 AND nRobotIdx_int &lt;= GVL.nMaxPoints THEN</div>
<div>    frobot := GVL.aPathData.fRobot_Pos;</div>
<div>ELSE</div>
<div>    frobot := 0.0;</div>
<div>END_IF</div>
<div> </div>]]></content:encoded>
						                            <category domain="https://twincontrols.com/community/"></category>                        <dc:creator>dxt123</dc:creator>
                        <guid isPermaLink="true">https://twincontrols.com/community/twincat-troubleshooting/inquiry-actual-position-leading-target-position-in-twincat-moveabsolute/#post-2372</guid>
                    </item>
				                    <item>
                        <title>TwinCAT Scope Missing Samples When Capturing EtherCAT PDOs</title>
                        <link>https://twincontrols.com/community/twincat-knowledgebase/twincat-scope-missing-samples-when-capturing-ethercat-pdos/#post-2371</link>
                        <pubDate>Fri, 03 Jul 2026 06:57:38 +0000</pubDate>
                        <description><![CDATA[&nbsp;
I am using TwinCAT Scope to capture EtherCAT PDO data. After enabling Distributed Clocks (DC) with a 1 ms cycle time, some data samples appear to be missing. The EtherCAT slave is tr...]]></description>
                        <content:encoded><![CDATA[<p>&nbsp;</p>
<div>I am using TwinCAT Scope to capture EtherCAT PDO data. After enabling Distributed Clocks (DC) with a 1 ms cycle time, some data samples appear to be missing. The EtherCAT slave is transmitting the PDO data correctly, and the data exchange seems to be functioning properly. I am sending a counter value from the slave that increments by 1 in every cycle. However, when monitoring the PDO in TwinCAT Scope, some consecutive samples show the same counter value, making it appear that certain samples are not being captured. Could this be a limitation of TwinCAT Scope or a configuration issue ?</div>]]></content:encoded>
						                            <category domain="https://twincontrols.com/community/"></category>                        <dc:creator>ajaya97</dc:creator>
                        <guid isPermaLink="true">https://twincontrols.com/community/twincat-knowledgebase/twincat-scope-missing-samples-when-capturing-ethercat-pdos/#post-2371</guid>
                    </item>
				                    <item>
                        <title>MC_CamIn - velocity of master axis not reaching maximal values</title>
                        <link>https://twincontrols.com/community/twincat-troubleshooting/mc_camin-velocity-of-master-axis-not-reaching-maximal-values/#post-2332</link>
                        <pubDate>Fri, 24 Apr 2026 10:11:03 +0000</pubDate>
                        <description><![CDATA[Hello,
We have a machine with spindle and a &#039;support&#039; axis. It was programmed to move coupled using MC_CamIn for the main automatic cycle and normal absolute movements for positioning and a...]]></description>
                        <content:encoded><![CDATA[<p>Hello,</p>
<p>We have a machine with spindle and a 'support' axis. It was programmed to move coupled using MC_CamIn for the main automatic cycle and normal absolute movements for positioning and auxilary movements.</p>
<p>When we position the spindle and/or the support X axis using the normal movements both reach our set/desired velocities i.e. 3000mm/s. </p>
<p>However when we run the 'main' automatic cycle using MC_CamIn where spindle is the master axis and X is our slave axis (using a table for positions for both) the spindle only reaches 719.36mm/s (always, if the movement is long enough it reaches it fast and caps at this specific value). </p>
<p>We cannot find how the MC_CamIn is calculating the velocity? From which settings does it get it?</p>
<p>The distance for Spindle is long enough it reaches the velocity in about 2 revolutions, and than runs 8 more revolutions with this specific yet low velocity. Our support axis X only makes small jumps of 1-2mm.</p>
<p>We tried different sets of maximum velocity/ reference velocity / acceleration /deceleration and jerk for both the spindle and x axis from the drive parameters. Even set it up 10x times higher, however the effect is always the same Spindle runs at 719.36 max.</p>
<p>This only occurs with MC_CamIn the normal motion works fine and spindle as well as X axis reach our set velocity i.e. 3000mm/s</p>
<p>Can anyone help with some insider info from where or how does the MC_CamIn calculate its movement velocity? We cannot find any place it would be declared or set. We have our position table for master and slave, the maximal velocity etc from drive parameters, so how can we increase master axis velocity?</p>
<p>&nbsp;</p>
<p>Thanks in advance!</p>
<p>&nbsp;</p>]]></content:encoded>
						                            <category domain="https://twincontrols.com/community/"></category>                        <dc:creator>stavi</dc:creator>
                        <guid isPermaLink="true">https://twincontrols.com/community/twincat-troubleshooting/mc_camin-velocity-of-master-axis-not-reaching-maximal-values/#post-2332</guid>
                    </item>
				                    <item>
                        <title>RE: Twincat HMI Trend line chart error:1538/0x602 (Anyone seen this before?)</title>
                        <link>https://twincontrols.com/community/twincat-hmi-troubleshooting/twincat-hmi-trend-line-chart-error1538-0x602-anyone-seen-this-before/#post-2331</link>
                        <pubDate>Wed, 22 Apr 2026 08:53:45 +0000</pubDate>
                        <description><![CDATA[The problem was due to dynamic width. Set a min width will resolve the issue.]]></description>
                        <content:encoded><![CDATA[<p>The problem was due to dynamic width. Set a min width will resolve the issue.</p>]]></content:encoded>
						                            <category domain="https://twincontrols.com/community/"></category>                        <dc:creator>Ed</dc:creator>
                        <guid isPermaLink="true">https://twincontrols.com/community/twincat-hmi-troubleshooting/twincat-hmi-trend-line-chart-error1538-0x602-anyone-seen-this-before/#post-2331</guid>
                    </item>
				                    <item>
                        <title>Twincat HMI Trend line chart error:1538/0x602 (Anyone seen this before?)</title>
                        <link>https://twincontrols.com/community/twincat-hmi-troubleshooting/twincat-hmi-trend-line-chart-error1538-0x602-anyone-seen-this-before/#post-2330</link>
                        <pubDate>Wed, 22 Apr 2026 08:26:50 +0000</pubDate>
                        <description><![CDATA[Hi all,
 I’m running into an issue with the Trend Line Chart control in TwinCAT HMI where the element consistently throws a “Range mismatch” error at runtime. The chart loads, but as soon a...]]></description>
                        <content:encoded><![CDATA[<p>Hi all,</p>
<p> <br />I’m running into an issue with the Trend Line Chart control in TwinCAT HMI where the element consistently throws a “Range mismatch” error at runtime. The chart loads, but as soon as data is pushed into it, the error appears and the trend fails to display correctly.<br /><br />I've attached a screenshot of the error.</p>
949
<p><br />I’ve already reached out to Beckhoff support, but they weren’t able to identify the cause either.<br /><br />Has anyone experienced this behaviour before, or found a workaround/fix? <br />Any insights, examples, or debugging tips would be greatly appreciated.<br /><br />Many thanks in advance.</p>
<p>Ed</p>
<p>&nbsp;</p>]]></content:encoded>
						                            <category domain="https://twincontrols.com/community/"></category>                        <dc:creator>Ed</dc:creator>
                        <guid isPermaLink="true">https://twincontrols.com/community/twincat-hmi-troubleshooting/twincat-hmi-trend-line-chart-error1538-0x602-anyone-seen-this-before/#post-2330</guid>
                    </item>
				                    <item>
                        <title>RE: TwinCAT 3.1 (Build 4026) &amp; Git – Online Change fails after cloning project (mapping mismatch)</title>
                        <link>https://twincontrols.com/community/twincat-troubleshooting/twincat-3-1-build-4026-git-online-change-fails-after-cloning-project-mapping-mismatch/#post-2329</link>
                        <pubDate>Mon, 20 Apr 2026 15:52:18 +0000</pubDate>
                        <description><![CDATA[Beckhoff has a recommended .gitignore setup. 
Beckhoff Information System - English
&nbsp;]]></description>
                        <content:encoded><![CDATA[<p>Beckhoff has a recommended .gitignore setup. </p>
<p><a href="https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_sourcecontrol/14604066827.html&amp;id=">Beckhoff Information System - English</a></p>
<p>&nbsp;</p>]]></content:encoded>
						                            <category domain="https://twincontrols.com/community/"></category>                        <dc:creator>srinath.ramachandran</dc:creator>
                        <guid isPermaLink="true">https://twincontrols.com/community/twincat-troubleshooting/twincat-3-1-build-4026-git-online-change-fails-after-cloning-project-mapping-mismatch/#post-2329</guid>
                    </item>
				                    <item>
                        <title>Real Industrial OOP Examples in TwinCAT — Public Projects</title>
                        <link>https://twincontrols.com/community/lounge/real-industrial-oop-examples-in-twincat-public-projects/#post-2314</link>
                        <pubDate>Mon, 23 Feb 2026 14:54:49 +0000</pubDate>
                        <description><![CDATA[Hi everyone,I’ve been reading a lot of the great content here about OOP and design patterns in TwinCAT — thanks to everyone who’s shared examples and explanations. Many of them do a nice job...]]></description>
                        <content:encoded><![CDATA[<p data-start="252" data-end="569">Hi everyone,<br data-start="264" data-end="267" />I’ve been reading a lot of the great content here about OOP and design patterns in TwinCAT — thanks to everyone who’s shared examples and explanations. Many of them do a nice job of explaining principles in a structured (academic) way, but I’m missing something important: <strong data-start="540" data-end="568">real industrial examples</strong>.</p>
<p data-start="1731" data-end="1795">I know that most real project source code is proprietary and can’t be shared publicly — that makes perfect sense. Still, I’m wondering if anyone <strong data-start="641" data-end="710">knows of a publicly available TwinCAT project from a real machine</strong>, or if someone would be willing to <strong data-start="746" data-end="807">show a simplified/cleaned-up version of their own project</strong> (with company IP removed) to illustrate how OOP is applied in practice (including HMI SCADA integration).</p>
<p data-start="1731" data-end="1795">Also — a related question: <strong data-start="1179" data-end="1243">have you used the SPT framework from Beckhoff USA</strong> in your projects? If so, how did it fit into your OOP structure and workflow?</p>]]></content:encoded>
						                            <category domain="https://twincontrols.com/community/"></category>                        <dc:creator>mircopolo</dc:creator>
                        <guid isPermaLink="true">https://twincontrols.com/community/lounge/real-industrial-oop-examples-in-twincat-public-projects/#post-2314</guid>
                    </item>
				                    <item>
                        <title>Advanced Interfaces: Functional Interfaces - Part 2 (Context &amp; Capturing)</title>
                        <link>https://twincontrols.com/community/twincat-knowledgebase/advanced-interfaces-functional-interfaces-part-2-context-capturing/#post-2305</link>
                        <pubDate>Wed, 31 Dec 2025 07:02:09 +0000</pubDate>
                        <description><![CDATA[In Part 1, we explored the concept of functional interfaces, where we used an interface with a single abstract method to pass logic around like a variable. We looked at a pure Aggregate  fun...]]></description>
                        <content:encoded><![CDATA[<p>In <a href="https://twincontrols.com/community/postid/2304/" target="_blank" rel="noopener">Part 1</a>, we explored the concept of <strong>functional interfaces</strong>, where we used an interface with a single abstract method to pass logic around like a variable. We looked at a pure <strong><em><span style="color: #000000">Aggregate</span></em></strong>  function that summed or multiplied values, or returned the largest value in an array.</p>
<p>Those examples were "stateless", meaning that the operations depended only on the arguments passed to them (<strong><em><span style="color: #000000">a</span></em></strong> and <strong><em><span style="color: #000000">b</span></em></strong> in <strong><em><span style="color: #000000">(a, b) =&gt; &lt;expression&gt;</span></em></strong>). In the real world, however, logic rarely exists in a vacuum. Functions often need <strong>context</strong>: thresholds, configuration values, or system parameters that aren't part of the function arguments themselves.</p>
<div>
<div><span>In modern languages like C#, Java, Python, and C++, this is solved through a concept called </span><span><strong>closures</strong></span><span> or </span><span><strong>lambda capturing</strong></span><span>. This is when a function "captures" variables from its surrounding scope so it can use them later, even after the original scope has ended.</span></div>
</div>
<div> </div>
<div>
<div>
<div><span>Here's an example in C++ that demonstrates capturing:</span></div>
<div>
<pre contenteditable="false">#include &lt;iostream&gt;

int main() 
{
    int scale = 10;
    
    // Capture by value (copy)
    auto scaleByValue = (int a, int b) { return (a + b) * scale; };

    // Capture by reference (observes changes)
    auto scaleByRef = (int a, int b) { return (a + b) * scale; };

    scale = 20;

    std::cout &lt;&lt; scaleByValue(1, 2) &lt;&lt; std::endl; // 30 (uses original scale = 10)
    std::cout &lt;&lt; scaleByRef(1, 2) &lt;&lt; std::endl;   // 60 (uses updated scale = 20)

    return 0;
}</pre>
</div>
</div>
</div>
<div>
<div>
<div><span>In this example:</span></div>
<div><span>- </span><span><strong><em><span style="color: #000000">scaleByValue</span></em></strong></span><span> captures </span><span><strong><em><span style="color: #000000">scale </span></em></strong></span><span><strong>by value</strong></span><span>, storing a copy of </span><span><strong><em><span style="color: #000000">10</span></em></strong></span><span> that won't change even when the original variable is modified.</span></div>
<div><span>- </span><span><strong><em><span style="color: #000000">scaleByRef</span></em></strong></span><span> captures </span><span><strong><em><span style="color: #000000">scale </span></em></strong></span><span><strong>by reference</strong></span><span>, observing changes to the original variable.</span></div>
<br />
<div><span>This flexibility allows functions to carry context with them, making them far more powerful and adaptable.</span></div>
<div> </div>
</div>
</div>
<div>
<h2 class="code-line" dir="auto" data-line="30"><span style="font-size: 18pt">The Challenge in Structured Text</span></h2>
</div>
<div>
<div>
<div> </div>
<div><span>Unfortunately, as discussed in <a href="https://twincontrols.com/community/postid/2304/" target="_blank" rel="noopener">Part 1</a></span><span>, ST doesn't support closures or lambda capturing. We can't create inline functions that automatically capture surrounding variables. However, we can achieve similar functionality using two approaches:</span></div>
</div>
</div>
<ol>
<li><strong>Function blocks with state</strong> - storing context as member variables</li>
<li>
<div><span><strong>Fluent interfaces</strong></span><span> - chainable methods that configure and return the function block</span></div>
</li>
</ol>
<p>The fluent interface approach is particularly elegant, as it allows us to configure operations inline, similar to how we'd use closures in modern languages.</p>
<p>&nbsp;</p>
<h2 class="code-line" dir="auto" data-line="30"><span style="font-size: 18pt">Approach 1: Direct Configuration</span></h2>
<p>&nbsp;</p>
<div>
<div><span>Let's start with the straight-forward approach of configuring function blocks before use:</span></div>
</div>
<pre contenteditable="false">{attribute 'no_explicit_call' := 'calling this function block directly is not allowed'}
FUNCTION_BLOCK FB_SumIfInRangeFunction IMPLEMENTS I_BiFunction
    VAR_INPUT
        nLowerThreshold,
        nUpperThreshold : INT;
    END_VAR

    METHOD Apply : INT
        VAR_INPUT
            nA, nB : INT;
        END_VAR

        // Only add nB if it's within the range
        IF nB &gt;= nLowerThreshold AND nB &lt;= nUpperThreshold THEN
            Apply := nA + nB;
        ELSE
            Apply := nA;
        END_IF
    END_METHOD
END_FUNCTION_BLOCK</pre>
<p>&nbsp;</p>
<h4 id="example-usage" class="code-line" dir="auto" data-line="166"><span style="font-size: 14pt">Example Usage</span></h4>
<p>&nbsp;</p>
<pre contenteditable="false">PROGRAM P_Example2_Direct
VAR
    arNumbers       : ARRAY OF INT := ;
    fbSum           : FB_SumIfInRangeFunction;
    nFilteredSum    : INT;
END_VAR

// Configure the thresholds
fbSum.nLowerThreshold := 10;
fbSum.nUpperThreshold := 20;

// Apply operation
nFilteredSum := P_Operations.Aggregate(arNumbers, fbSum);
// Result: 15 + 12 + 19 = 46 (8 and 23 are excluded)</pre>
<p>&nbsp;</p>
<div>
<div><span>This works, but it's limiting and requires separate configuration steps. We can do better.</span></div>
</div>
<p>&nbsp;</p>
<h2 class="code-line" dir="auto" data-line="30"><span style="font-size: 18pt">Approach 2: Fluent Interface (Method Chaining)</span></h2>
<p>&nbsp;</p>
<p>A <strong>fluent interface</strong> allows us to chain method calls together, configuring the function block inline. The key is that configuration methods return a reference to the function block itself (<span><strong><em><span style="color: #000000">THIS^</span></em></strong></span>), enabling multiple calls to be chained together.</p>
<div><br />
<div><span>By defining separate interfaces for different configuration capabilities, we can create a flexible and type-safe fluent API that guides developers toward correct usage.</span></div>
<div> </div>
<div>First, let's define interfaces that represent different configuration capabilities:</div>
</div>
<pre contenteditable="false">INTERFACE I_GreaterThanCondition
    METHOD AnyGreaterThan : I_BiFunction
        VAR_INPUT
            nThreshold : INT;
        END_VAR
    END_METHOD
END_INTERFACE</pre>
<pre contenteditable="false">INTERFACE I_LessThanCondition
    METHOD AnyLessThan : I_BiFunction
        VAR_INPUT
            nThreshold : INT;
        END_VAR
    END_METHOD
END_INTERFACE</pre>
<pre contenteditable="false">INTERFACE I_InRangeCondition
    METHOD AnyInBetween : I_BiFunction
        VAR_INPUT
            nLowerThreshold, nUpperThreshold : INT;
        END_VAR
    END_METHOD
END_INTERFACE</pre>
<div>
<div><span>Now we can implement a function block that supports multiple configuration interfaces: </span></div>
</div>
<pre contenteditable="false">{attribute 'no_explicit_call' := 'calling this function block directly is not allowed'}
FUNCTION_BLOCK FB_SumFunction 
IMPLEMENTS I_BiFunction, I_GreaterThanCondition, I_LessThanCondition, I_InRangeCondition
    VAR_STAT CONSTANT
        _nMIN_INT : INT := -32768;
        _nMAX_INT : INT := 32767;
    END_VAR
    VAR
        _nLowerThreshold : INT := _nMIN_INT;
        _nUpperThreshold : INT := _nMAX_INT;;
    END_VAR
    
    // Sets the minimum threshold (inclusive)
    // Values must be GREATER THAN OR EQUAL to this threshold
    METHOD AnyGreaterThan : I_BiFunction
        VAR_INPUT
            nThreshold : INT;
        END_VAR
        AnyGreaterThan   := THIS^;

        _nLowerThreshold := nThreshold;
        _nUpperThreshold := _nMAX_INT;
    END_METHOD

    // Sets the maximum threshold (inclusive)
    // Values must be LESS THAN OR EQUAL to this threshold
    METHOD AnyLessThan : I_BiFunction
        VAR_INPUT
            nThreshold : INT;
        END_VAR
        AnyLessThan      := THIS^;

        _nLowerThreshold := _nMIN_INT;
        _nUpperThreshold := nThreshold;
    END_METHOD

    // Sets the lower and upper threshold (inclusive)
    // Values must be BETWEEN these thresholds
    METHOD AnyInBetween : I_BiFunction
        VAR_INPUT
            nLowerThreshold, nUpperThreshold : INT;
        END_VAR
        AnyInBetween     := THIS^;

        _nLowerThreshold := nLowerThreshold;
        _nUpperThreshold := nUpperThreshold;
    END_METHOD

    // Resets the configuration so that all values are allowed
    METHOD Everything : I_BiFunction
        VAR_INPUT
        END_VAR
        Everything := THIS^;

        _nLowerThreshold := _nMIN_INT;
        _nUpperThreshold := _nMAX_INT;
    END_METHOD

    METHOD Apply : INT
        VAR_INPUT
            nA, nB : INT;
        END_VAR
        // Only add nB if it's within the configured range
        IF nB &gt;= _nLowerThreshold AND nB &lt;= _nUpperThreshold THEN 
            Apply := nA + nB;
        ELSE
            Apply := nA;
        END_IF
    END_METHOD
END_FUNCTION_BLOCK</pre>
<p>&nbsp;</p>
<h4 id="example-usage" class="code-line" dir="auto" data-line="166"><span style="font-size: 14pt">Example Usage</span></h4>
<p>&nbsp;</p>
<div>
<div><span>Now we can configure and use operations in clean, readable expressions:</span></div>
<div>
<pre contenteditable="false">ROGRAM P_Example2_Fluent
VAR
    arNumbers        : ARRAY OF INT := ;
    fbSum            : FB_SumFunction;
    nFilteredSum1,
    nFilteredSum2,
    nFilteredSum3    : INT;
END_VAR

// Configure and apply operations inline
nFilteredSum1 := P_Operations.Aggregate(arNumbers, fbSum.AnyLessThan(20));
// Result: 15 + 8 + 12 + 19 = 54 (23 is excluded)

nFilteredSum2 := P_Operations.Aggregate(arNumbers, fbSum.AnyGreaterThan(10));
// Result: 15 + 23 + 12 + 19 = 69 (8 is excluded)

nFilteredSum3 := P_Operations.Aggregate(arNumbers, fbSum.AnyInBetween(10, 20));
// Result: 15 + 12 + 19 = 46 (8 and 23 are excluded)

nSum          := P_Operations.Aggregate(arNumbers, fbSum.Everything());
// Result: 15 + 8 + 23 + 12 + 19 = 77 (all values are included)</pre>
</div>
</div>
<p>&nbsp;</p>
948
<p>&nbsp;</p>
<h4 id="example-usage" class="code-line" dir="auto" data-line="166"><span style="font-size: 14pt">Benefits of the Fluent Approach</span></h4>
<p>&nbsp;</p>
<div>
<div>
<div>
<div><span>The fluent interface pattern provides several advantages:</span></div>
<br />
<ol>
<li><span><strong>Readability</strong></span><span>: Configuration reads like natural language</span></li>
<li><span><strong>Type Safety</strong></span><span>: Each interface clearly defines what operations are available</span></li>
<li><span><strong>Inline Configuration</strong></span><span>: No need for separate setup steps</span></li>
<li><span><strong>Discoverability</strong></span><span>: Separate interfaces guide developers through available options</span></li>
<li><span><strong>Immutability Simulation</strong></span><span>: Each call appears to create a configured version without mutating the original (though technically it does modify the instance)</span></li>
<li><span><strong>Composability</strong></span><span>: Easy to build complex behaviors from simple building blocks</span></li>
</ol>
</div>
</div>
</div>
<div> </div>
<div>
<h2 class="code-line" dir="auto" data-line="30"><span style="font-size: 18pt">Real World Applications</span></h2>
</div>
<div>
<div>
<div> </div>
</div>
</div>
<div>
<div><span>Context-aware operations with fluent interfaces are invaluable across many automation scenarios:</span></div>
</div>
<p><strong>Data Transformation &amp; Mapping</strong></p>
<pre contenteditable="false">// Transform raw ADC values to engineering units with calibration offset
fCalibratedTemps := P_DataTransform.Map(
    nRawSensorData,
    fbADCToTemp.WithOffset(nZeroOffset).WithGain(fCalibrationGain)
);</pre>
<p><strong>Filtering &amp; Selection</strong></p>
<pre contenteditable="false">// Select production batches exceeding quality threshold
nCount := P_DataFilter.Filter(
    arAllBatches,
    fbQualityCheck
        .AnyGreaterThan(nPremiumThreshold)
        .WithMargin(fSafetyMargin),
    arPremiumBatches
);

// Find active alarms above critical severity
nCount := P_DataFilter.Filter(
    arAlarmList,
    fbAlarmFilter
        .AnyGreaterThan(E_Severity.High)
        .IsActive()
        .NotAcknowledged(),
    arCriticalAlarms
);</pre>
<p><strong>Validation &amp; Predicates</strong></p>
<pre contenteditable="false">// Verify all safety sensors are within safe range
bSystemSafe := P_SafetyCheck.Every(
    arSafetySensors,
    fbSafetyValidator
        .AnyInBetween(nSafeMin, nSafeMax)
        .WithHysteresis(nHysteresisBand)
);</pre>
<p><strong>Sorting &amp; Ranking</strong></p>
<pre contenteditable="false">// Sort machines by efficiency, excluding standby units
P_EquipmentSort.Sort(
    arMachines,
    fbEfficiencyCompare.AnyGreaterThan(nMinEfficiency).ByDescending()
);

// Rank production runs by yield within quality band
P_BatchSort.Sort(
    arProductionRuns,
    fbYieldCompare.AnyInBetween(nMinQuality, nMaxQuality).ByYield()
);</pre>
<p><strong>Search &amp; Analysis</strong></p>
<pre contenteditable="false">// Find first sensor reading exceeding alarm threshold
ipFirstAlarm := P_DataSearch.Find(
    arSensorReadings,
    fbThresholdCheck.AnyGreaterThan(nAlarmThreshold).Confirmed(T#2s)
);

// Identify optimal batch by cost-to-quality ratio
ipBestBatch := P_BatchAnalysis.FindOptimal(
    arBatches,
    fbCostAnalysis.AnyGreaterThan(nMinYield).WithQualityWeight(0.6)
);</pre>
<p><strong>Statistical Aggregation</strong></p>
<pre contenteditable="false">// Calculate weighted average excluding statistical outliers
fWeightedAvg := P_Statistics.Aggregate(
    arMeasurements,
    fbWeightedAvg
        .AnyInBetween(fMean - 3*fStdDev, fMean + 3*fStdDev)
        .WithWeights(arWeights)
);

// Compute RMS vibration in bearing frequency range
fBearingVibration := P_VibrationAnalysis.Aggregate(
    arFFTData,
    fbRMS
        .AnyInBetween(fBearingFreqLow, fBearingFreqHigh)
        .WithHanningWindow()
);</pre>
<div>
<div><span>The key advantage across all these scenarios is that </span><span><strong>different functional interfaces</strong></span><span> (</span><span><strong><em><span style="color: #000000">Map</span></em></strong></span><span>, </span><span><strong><em><span style="color: #000000">Filter</span></em></strong></span><span>, </span><span><strong><em><span style="color: #000000">Every</span></em></strong></span><span>, </span><span><strong><em><span style="color: #000000">Some</span></em></strong></span><span>, </span><span><strong><em><span style="color: #000000">None</span></em></strong></span><span>, </span><span><strong><em><span style="color: #000000">Sort</span></em></strong></span><span>, </span><span><strong><em><span style="color: #000000">Find</span></em></strong></span><span>, </span><span><strong><em><span style="color: #000000">Aggregate</span></em></strong></span><span>) provide the appropriate abstraction for each use case, while fluent configuration makes the code expressive and maintainable.</span></div>
</div>
<div> </div>
<div>
<h2 class="code-line" dir="auto" data-line="30"><span style="font-size: 18pt">Conclusion</span></h2>
</div>
<div>
<div>
<div> </div>
</div>
</div>
<div>
<div><span>While Structured Text doesn't support closures or lambda capturing, we can achieve remarkably similar functionality using function blocks with fluent interfaces. By:</span></div>
<br />
<ol>
<li><span><strong>Storing context as member variables</strong></span><span> within function blocks</span></li>
<li><span><strong>Exposing configuration through dedicated interfaces</strong></span><span> for type safety and discoverability</span></li>
<li><span><strong>Returning references to the functional interface</strong></span><span> (</span><span><strong><em><span style="color: #000000">THIS^</span></em></strong></span><span>) to enable inline configuration</span></li>
</ol>
<br />
<div><span>We create expressive, readable code that rivals modern functional programming languages. The fluent interface pattern provides:</span></div>
<br />
<ul>
<li><span><strong>Inline configuration</strong></span><span> that mimics closure capturing</span></li>
<li><span><strong>Type-safe composition</strong></span><span> through interface segregation</span></li>
<li><span><strong>Natural, readable syntax</strong></span><span> that expresses intent clearly</span></li>
<li><span><strong>Flexible, reusable components</strong></span><span> that adapt to different contexts</span></li>
</ul>
<br />
<div><span>This approach bridges the gap between traditional PLC programming and modern functional paradigms, bringing greater modularity, maintainability, and expressiveness to industrial automation code without sacrificing the determinism and reliability that automation systems demand.</span></div>
<br />
<div><span>In Part 3, we'll explore how to extend this pattern further with </span><span><strong>function composition</strong></span><span> and </span><span><strong>higher-order functions</strong></span><span>, building even more powerful abstractions that allow us to combine simple operations into complex processing pipelines.</span></div>
</div>]]></content:encoded>
						                            <category domain="https://twincontrols.com/community/"></category>                        <dc:creator>FisoThemes</dc:creator>
                        <guid isPermaLink="true">https://twincontrols.com/community/twincat-knowledgebase/advanced-interfaces-functional-interfaces-part-2-context-capturing/#post-2305</guid>
                    </item>
				                    <item>
                        <title>Advanced Interfaces: Functional Interfaces - Part 1</title>
                        <link>https://twincontrols.com/community/twincat-knowledgebase/advanced-interfaces-functional-interfaces-part-1/#post-2304</link>
                        <pubDate>Wed, 24 Dec 2025 03:53:36 +0000</pubDate>
                        <description><![CDATA[Lately, I’ve been thinking a lot about how to abstract complexity and improving the flexibility of code in industrial automation systems. With systems growing in size and complexity, it&#039;s im...]]></description>
                        <content:encoded><![CDATA[<p class="code-line" dir="auto" data-line="2">Lately, I’ve been thinking a lot about how to abstract complexity and improving the flexibility of code in industrial automation systems. With systems growing in size and complexity, it's important to find a balance between control and modularity. I’ve been exploring different approaches to achieve this, and I decided to start by talking about<span> </span><strong>functional interfaces</strong><span> </span>- a powerful tool that can help you break down large systems into smaller, reusable components.</p>
<p class="code-line" dir="auto" data-line="4">Many modern programming languages, like Rust, Java, Python, or C#, have what is known as<span> </span><strong>first-class functions</strong>. These are functions that can be passed around just like variables, meaning they can be assigned to variables, passed as arguments to other functions, or even returned as values from functions. This flexibility allows for more dynamic and reusable code. This is the fundamental concept behind<span> </span><strong>lambdas</strong>,<span> </span><strong>closures</strong>, and<span> </span><strong>anonymous functions</strong>, which are compact ways to define behaviour on the fly without needing to declare a full function or method. By embedding logic directly where it’s needed, they allow developers to write cleaner, more concise, and more modular code.</p>
<p class="code-line code-active-line" dir="auto" data-line="6">Here’s an example of an anonymous function in C# that reduces a list of numbers:</p>
<pre contenteditable="false">using System;
using System.Linq;

int[] numbers = {2, 1, 3, 5, 4};

// Using an anonymous function to sum the array
int sum = numbers.Aggregate((a, b) =&gt; a + b);

// Using an anonymous function to get the product of the array
int product = numbers.Aggregate((a, b) =&gt; a * b);

// Output: 15
Console.WriteLine(sum);

// Output: 120
Console.WriteLine(product);</pre>
<p dir="auto" data-line="6"> </p>
<p class="code-line" dir="auto" data-line="26">In this example, the <strong><em><span style="color: #000000">Aggregate</span></em></strong> method uses a lambda expression to define how two numbers should be combined, either summing (<strong><em><span style="color: #000000">(a, b) =&gt; a + b</span></em></strong>) or multiplying (<strong><em><span style="color: #000000">(a, b) =&gt; a * b</span></em></strong>) the values. The power here is that the <strong><em><span style="color: #000000">Aggregate</span></em></strong> <strong>method itself is decoupled from the specific operation</strong>. It doesn’t care whether you’re summing or multiplying numbers. This makes the logic highly reusable and flexible, allowing the function to process data in different ways based on the behaviour you pass in.</p>
<p class="code-line" dir="auto" data-line="28">Unfortunately, Structured Text (ST) doesn’t have first-class functions (<a href="https://stackoverflow.com/questions/43139073/how-to-create-pointer-to-function-in-codesys-v3" data-href="https://stackoverflow.com/questions/43139073/how-to-create-pointer-to-function-in-codesys-v3">sort of...</a>), but we can still introduce some of these principles through functional interfaces. Although ST doesn’t support lambdas, closures, or anonymous functions, functional interfaces can give us a way to mimic some of this functionality.</p>
<h2 class="code-line" dir="auto" data-line="30"><span style="font-size: 18pt">What is a Functional Interface?</span></h2>
<p class="code-line" dir="auto" data-line="32">A functional interface is an interface that contains exactly<span> </span><strong>one</strong><span> </span>abstract method. This single-method contract allows for a concise and focused interaction between components, making it ideal for situations where you need to define a single behaviour or action. While functional interfaces can have other members like default methods or constants (these aren't supported in ST), they are designed around the core idea of<span> </span><strong>one abstract method</strong>, ensuring they remain lightweight and purpose-driven.</p>
<p class="code-line" dir="auto" data-line="34">Here's an example of a functional interface in Java:</p>
<pre contenteditable="false">@FunctionalInterface
public interface BiFunction&lt;T, U, R&gt; {
     R apply(T t, U u); 
}</pre>
<p class="code-line" dir="auto" data-line="34"> </p>
<p class="code-line" dir="auto" data-line="42">This<span> <strong><em><span style="color: #000000">BiFunction</span></em></strong><b><i> </i></b></span>interface represents a function that accepts two arguments (<span><strong><em><span style="color: #000000">T</span></em></strong><b><i> </i></b></span><b><i> </i></b>and<span> <b><i><strong><em><span style="color: #000000">U</span></em></strong> </i></b></span>) and returns a result (<b><i><span><strong><em><span style="color: #000000">R</span></em></strong></span></i></b>). The <strong><em><span style="color: #000000">Apply</span></em></strong><span><b><i> </i></b></span>method is the single abstract method that makes this a functional interface.</p>
<p class="code-line" dir="auto" data-line="44">Here's an example of using this interface:</p>
<pre contenteditable="false">import java.util.function.BiFunction;

public class Main
{
    public static void main(String[] args) {
        // Define a BiFunction that adds two integers
        BiFunction&lt;Integer, Integer, Integer&gt; foo = (a, b) -&gt; a + b;

        // Invoke the BiFunction to add two integers
        Integer result = foo.apply(2, 3);

        // Output: 5
        System.out.println(result);
    }
}</pre>
<p class="code-line" dir="auto" data-line="34"><br /><span>In this example, we create a <strong><em><span style="color: #000000">BiFunction</span></em></strong><b><i> </i></b>to add two integers. The lambda expression <strong><em><span style="color: #000000">(a, b) =&gt; a + b </span></em></strong></span><span>defines the behaviour, and the <b><i>apply </i></b></span><span>method calls the function with the specified arguments.<br /></span></p>
<h2 id="application-in-industrial-automation" class="code-line" dir="auto" data-line="65"><span style="font-size: 18pt">Application in Industrial Automation</span></h2>
<p class="code-line" dir="auto" data-line="67">In<span> </span><strong>automation systems</strong>, functional interfaces can be used to define well-scoped behaviours that can be applied across different components or modules. By encapsulating specific functionality within a clear, single-purpose interface, functional interfaces help make systems easier to maintain, extend, and understand. This modularity also enables greater flexibility when it comes to introducing new behaviours or modifying existing ones without affecting the overall system.</p>
<p class="code-line" dir="auto" data-line="69">In this section, I’ll walk you through a simple example that demonstrate the use of functional interfaces in Codesys/TwinCAT's implementation of the IEC 61131-3 Structured Text language. I’ll be replicating the aggregate function discussed earlier <span>to perform operations like summing and finding the maximum in an array of numbers.</span></p>
<h4 dir="auto" data-line="75"> </h4>
<h4 id="step-1-define-the-functional-interface" class="code-line" dir="auto" data-line="75"><span style="font-size: 14pt">Define the Functional Interface</span></h4>
<h4 dir="auto" data-line="75"> </h4>
<p class="code-line" dir="auto" data-line="34"><span>First, let's define the <strong><em><span style="color: #000000">I_BiFunction</span></em></strong><b><i> </i></b>interface that will serve as a blueprint for any function that performs an operation on two integers and returns an integer result:<br /></span></p>
<pre contenteditable="false">INTERFACE I_BiFunction
    METHOD Apply : INT 
    VAR_INPUT 
        nA, nB : INT;
    END_VAR 
END_INTERFACE</pre>
<p class="code-line" dir="auto" data-line="34"><span>This interface contains a single method, <strong><em><span style="color: #000000">Apply</span></em></strong><b><i> </i></b>, which takes two integers as inputs (<strong><em><span style="color: #000000">nA</span></em></strong><b><i> </i></b><b><i>, <strong><em><span style="color: #000000">nB</span></em></strong> </i></b>) and returns an integer result.</span></p>
<h4 dir="auto" data-line="75"> </h4>
<h4 class="code-line" dir="auto" data-line="75"><span style="font-size: 14pt">Implement a Method that Utilises the Functional Interface</span></h4>
<h4 dir="auto" data-line="75"> </h4>
<p class="code-line" dir="auto" data-line="34"><span>Next, we’ll create the <strong><em><span style="color: #000000">P_Operations</span></em></strong><b><i> </i></b> program that contains a method called <strong><em><span style="color: #000000">Aggregate</span></em></strong>. This method will iterate over an array of integers and apply the operation defined by the <strong><em><span style="color: #000000">I_BiFunction</span></em></strong><b><i> </i></b>interface to aggregate the values in the supplied array.<br /></span></p>
<pre contenteditable="false">{attribute 'no_explicit_call' := 'calling this program directly is not allowed'}
PROGRAM P_Operations
    METHOD Aggregate : INT
    VAR_IN_OUT CONSTANT
        {warning disable C0228}
        arNumbers : ARRAY  OF INT;
    END_VAR
    VAR_INPUT
        ipOperation : I_BiFunction;
    END_VAR
    VAR
        i : __XINT;
    END_VAR

    // Exit the function call if the operation is not supplied.
    IF ipOperation = 0 THEN RETURN; END_IF

    // Initialise the result with the first element of the array.
    Aggregate := arNumbers;

    // Loop through the array and apply the operation defined by the interface.
    FOR i := LOWER_BOUND(arNumbers, 1) + 1 TO UPPER_BOUND(arNumbers, 1) DO
        Aggregate := ipOperation.Apply(Aggregate, arNumbers);
    END_FOR

    END_METHOD
END_PROGRAM</pre>
<h4 id="explanation" class="code-line" dir="auto" data-line="121">Explanation:</h4>
<ul class="code-line" dir="auto" data-line="123">
<li class="code-line" dir="auto" data-line="123">
<p class="code-line" dir="auto" data-line="123">The <span><strong><em><span style="color: #000000">ipOperations</span></em></strong><b><i> </i></b> </span>input parameter is an instance of the <span><strong><em><span style="color: #000000">I_BiFunction</span></em></strong><b><i> </i></b></span>interface, which defines the operation to be applied (such as summing or multiplying).</p>
</li>
<li class="code-line" dir="auto" data-line="125">
<p class="code-line" dir="auto" data-line="125">The method initialises the result (<span><strong><em><span style="color: #000000">Aggregate</span></em></strong></span>) with the first element of the array.</p>
</li>
<li class="code-line" dir="auto" data-line="127">
<p class="code-line" dir="auto" data-line="127">It then iterates through the rest of the array, applying the operation (via <span><strong><em><span style="color: #000000">ipOperations.Apply(...)</span></em></strong></span>) to the running result and each element of the array.</p>
</li>
</ul>
<h4 dir="auto" data-line="75"> </h4>
<h4 class="code-line" dir="auto" data-line="75"><span style="font-size: 14pt">Implementing the Operations</span></h4>
<h4 dir="auto" data-line="75"> </h4>
<p><span>Now, let’s implement specific operations by creating function blocks that implement the <strong><em><span style="color: #000000">I_BiFunction</span></em></strong><b><i> </i></b> interface. We will use these to perform different actions on the array elements.</span></p>
<h4 id="product-operation" class="code-line" dir="auto" data-line="133">Product Operation</h4>
<pre contenteditable="false">{attribute 'no_explicit_call' := 'calling this function block directly is not allowed'}
FUNCTION_BLOCK FB_ProductFunction IMPLEMENTS I_BiFunction
    METHOD Apply : INT
    VAR_INPUT
        nA, nB : INT;
    END_VAR

    // Return the product of two values
    Apply := nA * nB;

    END_METHOD
END_FUNCTION_BLOCK</pre>
<h4 id="maximum-value-operation" class="code-line" dir="auto" data-line="150">Maximum Value Operation</h4>
<pre contenteditable="false">{attribute 'no_explicit_call' := 'calling this function block directly is not allowed'}
FUNCTION_BLOCK FB_MaximumValueFunction IMPLEMENTS I_BiFunction
    METHOD Apply : INT
    VAR_INPUT
        nA, nB : INT;
    END_VAR

    // Return the maximum of two values
    Apply := MAX(nA, nB);

    END_METHOD
END_FUNCTION_BLOCK</pre>
<h4 dir="auto" data-line="75"> </h4>
<h4 id="example-usage" class="code-line" dir="auto" data-line="166"><span style="font-size: 14pt">Example Usage</span></h4>
<h4 dir="auto" data-line="75"> </h4>
<p class="code-line" dir="auto" data-line="34"><span>Now that we've defined the functional interface and its implementations, we can use the <strong><em><span style="color: #000000">P_Operations</span></em></strong><b><i> </i></b>program to apply different operations to an array of numbers.<br /></span></p>
<pre contenteditable="false">PROGRAM P_Example1
VAR
    nProduct, 
    nLargest    : INT;
    arNumbers   : ARRAY OF INT := ;
    fbProduct   : FB_ProductFunction;
    fbLargest   : FB_MaximumValueFunction;
END_VAR

nProduct := P_Operations.Aggregate(arNumbers, fbProduct);
nLargest := P_Operations.Aggregate(arNumbers, fbLargest);</pre>
<p class="code-line" dir="auto" data-line="34"><span> </span></p>
947
<h4 dir="auto" data-line="75"> </h4>
<h4 id="step-1-define-the-functional-interface" class="code-line" dir="auto" data-line="75"><span style="font-size: 14pt">Real World Applications</span></h4>
<div>
<div> </div>
<div><span>Here are some practical examples of how functional interfaces shine in industrial automation:</span></div>
</div>
<div> </div>
<div><strong>Data Processing</strong></div>
<div>
<pre contenteditable="false">// Transform sensor data through different conversion functions
fCelsius    := P_DataTransform.Map(nRawSensorData, fbADCToCelsius);
fFahrenheit := P_DataTransform.Map(nRawSensorData, fbADCToFahrenheit);</pre>
</div>
<div> </div>
<div><strong>Batch Operations</strong></div>
<div>
<pre contenteditable="false">// Apply different operations to production batches
bAllPassed := P_BatchProcessor.Every(arQualityChecks, fbMeetsStandard);
bAnyFailed := P_BatchProcessor.Some(arQualityChecks, fbBelowThreshold);</pre>
</div>
<div> </div>
<div><strong>Validation and Filtering</strong></div>
<div>
<pre contenteditable="false">// Filter arrays based on different validation criteria
// Filter(&lt;source&gt;, &lt;predicate&gt;, &lt;destination&gt;) -&gt; &lt;Count&gt;
nCount := P_DataFilter.Filter(arSensorData, fbWithinTolerance, arValidReadings);
nCount := P_DataFilter.Filter(arSensorData, fbOutsideExpected, arOutliers);
nCount := P_DataFilter.Filter(arAlarms, fbHighSeverity, arCritical);</pre>
</div>
<div> </div>
<div><strong>Validation and Filtering</strong></div>
<div>
<pre contenteditable="false">// Sort machine data by different criteria
P_DataSort.Sort(arMachines, fbByEfficiency);
P_DataSort.Sort(arMachines, fbByUptime);
P_DataSort.Sort(arMachines, fbByProductionCount);</pre>
</div>
<p class="code-line" dir="auto" data-line="34">The key advantage here is that the processing logic (<strong><em><span style="color: #000000">Aggregate</span></em></strong>, <strong><em><span style="color: #000000">Map</span></em></strong>, <strong><em><span style="color: #000000">Filter</span></em></strong>, etc.) remains unchanged while the behaviour is completely customisable through different function block implementations.<span></span></p>
<h2 id="application-in-industrial-automation" class="code-line" dir="auto" data-line="65"><span style="font-size: 18pt">Conclusion</span></h2>
<p class="code-line" dir="auto" data-line="220">Functional interfaces bring a powerful abstraction mechanism to Structured Text programming. While ST lacks first-class functions, lambdas, and closures, functional interfaces allow us to:</p>
<ol class="code-line" dir="auto" data-line="222">
<li class="code-line" dir="auto" data-line="222">
<p class="code-line" dir="auto" data-line="222"><strong>Decouple algorithms from operations</strong>: The <strong><em><span style="color: #000000">Aggregate</span></em></strong> method doesn't know or care whether it's summing, multiplying, or finding maximums. It just applies the provided operation.</p>
</li>
<li class="code-line" dir="auto" data-line="224">
<p class="code-line" dir="auto" data-line="224"><strong>Increase code reusability</strong>: Write the processing logic once, then reuse it with countless different behaviors by simply implementing new function blocks.</p>
</li>
<li class="code-line" dir="auto" data-line="226">
<p class="code-line" dir="auto" data-line="226"><strong>Improve testability</strong>: Each operation is encapsulated in its own function block, making it easy to test in isolation.</p>
</li>
<li class="code-line" dir="auto" data-line="228">
<p class="code-line" dir="auto" data-line="228"><strong>Enhance maintainability</strong>: Changes to specific operations don't affect the core processing logic, and vice versa.</p>
</li>
<li class="code-line" dir="auto" style="text-align: left" data-line="230">
<p class="code-line" dir="auto" data-line="230"><strong>Create more expressive code</strong>: The intent becomes clear when you read <strong><em><span style="color: #000000">P_Operations.Aggregate(arNumbers, fbProduct) </span></em></strong>versus a hardcoded multiplication loop.</p>
</li>
</ol>
<p class="code-line" dir="auto" data-line="230">While we've focused on simple, stateless operations in this part, functional interfaces become even more powerful when combined with context and state. In <a href="https://twincontrols.com/community/postid/2305/" target="_blank" rel="noopener">Part 2</a>, we'll explore how to add <strong>context and capturing</strong> to our functional interfaces, allowing operations to carry configuration and state-mimicking the closure behaviour found in modern programming languages.</p>
<div>
<div><span>This approach bridges the gap between traditional PLC programming and modern software engineering practices, allowing for greater flexibility and maintainability in industrial automation systems without sacrificing the determinism and reliability that automation demands.</span></div>
</div>]]></content:encoded>
						                            <category domain="https://twincontrols.com/community/"></category>                        <dc:creator>FisoThemes</dc:creator>
                        <guid isPermaLink="true">https://twincontrols.com/community/twincat-knowledgebase/advanced-interfaces-functional-interfaces-part-1/#post-2304</guid>
                    </item>
							        </channel>
        </rss>
		