TwinCAT 3 Task Start/Stop from PLC - plc

I need to run some code every time the PLC starts. This code should only be run once and then never again until the PLC is restarted. I initialize some global variables and validate the persistent data before allowing the main PLC to run. This is because the actions of the machine can be damaging if some of these variables are not setup correctly.
Is there a way to start/stop the other PLC tasks? I noticed TwinCAT doesn't support initialization and shutdown interrupts for PLC tasks.

TwinCAT has a 'PlcTaskSystemInfo' struct containing a boolean for FirstCycle. You can use that to run the initializing code only once.
VAR fbGetCurTaskIdx: GETCURTASKINDEX; (* Further example+explanation in Infosys *)
fbGetCurTaskIdx();
IF _TaskInfo[fbGetCurTaskIdx.index].FirstCycle THEN
(* Initialization code here *)
ELSE
(* Normal code here *)
END_IF;

I don't know of a way to start/stop individual PLC tasks. You can start/stop a runtime though.
But perhaps it can be as simple as this code below, which will only run when your PLC starts.
VAR initialized: BOOL := FALSE;
IF NOT initialized THEN
(* Run your initialization code here *)
initialized := TRUE;
END_IF
(* Rest of your program here *)
Edit:
I used a state machine inside the initialization code to help with the task allowed time issue.
Example:
VAR
Initialized : BOOL := FALSE;
Init_State : UINT := 0;
END_VAR
IF NOT Initialized THEN
(* Initialization State Machine *)
CASE Init_State OF
0: (* First step in initialization *)
Init_State := Init_State + 1;
1: (* Second step in initialization *)
Init_State := Init_State + 1;
.
.
.
n: (* Last step in initialization *)
Initialized := TRUE;
END_CASE
END_IF

Related

TwinCAT - How to measure program execution time?

I would like to measure the execution time of a structured text (ST) program. The task associated with the program is running at 10 ms.
How do I measure execution time?
You can use free TwinCAT library Tc2_Utilities that has a function block Profiler.
https://infosys.beckhoff.com/english.php?content=../content/1033/tcplclib_tc2_utilities/35053195.html&id=1344160655692967299
The "Profiler" function block can be used to allow the execution time of PLC code to be measured.
The Infosys page has an example code also:
VAR
Profiler1 : PROFILER;
END_VAR
Profiler1(START := TRUE, RESET := TRUE);
//Do something here
Profiler1(START := FALSE);
//Now Profiler1.Data has the execution time
Of course, you can use Profiler but just for demonstration purpose you can measure execution time like this.
PROGRAM PLC_PRG
VAR
tStart: TIME; (* Time program start *)
tWork : TIME; (* Execution time *)
END_VAR
(* First line of main program *)
tStart := TIME();
// Your program here
(* Last line of your program *)
tWork := TIME() - tStart;
END_PROGRAM

Store variable value in each PLC cycle

Is it possible to store a variable value in each PLC cycle? I need the first 10 values each time to perform some calculations. I am using the OpenPCS platform and ST for programming.
You can create an array of values and then store as array values
VAR
aBuffer : ARRAY[1..32] OF WORD;
init:BOOL; (* Init array *)
rest:BOOL; (* Reset *)
val:WORD; (* Value *)
iCount:INT; (* Array index *)
END_VAR
VAR_TEMP
iTmp : INT;
END_VAR
iTmp := UINT_TO_INT(N) - 1;
IF NOT init OR rest THEN
init := TRUE;
FOR iCount := 1 TO iTmp DO
aBuffer[iCount] := val;
END_FOR;
END_IF
iCount := INC1(iCount, 32);
aBuffer[iCount] := val;
This is a code example that created 32 elements array and every new PLC cycle assign new element and rotates.
After that, you can calculate the average or min and max.
INC1 increments given value by one until it reaches 32 and then reset to 1.

TwinCAT CoE: Write to SDO

I'm pretty new to the EtherCAT/CANopen universe and trying to implement a custom slave.
The slave is passing conformance test so far and want to write to one of my Slave Data Objects, the slave is attached to a CX5120, which is found by the XAE and also shows the Slave device.
For that, I copied my ESI-file to the TwinCAT folder (C:\TwinCAT\3.1\Config\Io\EtherCAT).
I've created a small Structured Text PLC program that uses FB_EcCoESdoWrite to write data to address 0x607A. But when I set it active and try to connect, Visual Studio tells me that the device needs at least one Sync Master. Also, when setting bExecute to TRUE, I'm getting an error from the function. As far as I understand, I have to link variables between my ST program and the slave, but I don't see the need of linking variables because afaik the function call should manage the transmission? What are the steps to write to a SDO of an ESC? Can someone tell me what I'm missing or has a small example at hand?
PROGRAM MAIN
VAR
heartbeat : UINT;
fbSdoWrite : FB_EcCoESdoWrite;
sNetId : T_AmsNetId := '5.76.204.148.1.1'; (* NetId of EtherCAT Master *)
nSlaveAddr : UINT := 1001; (* Port Number of EtherCAT Slave *)
nIndex : WORD := 16#607A; (* CoE Object Index *)
nSubIndex : BYTE := 0; (* Subindex of CoE Object *)
nValue : UINT := 16#AAAA; (* variable to be written to the CoE Object *)
bExecute : BOOL; (* rising edge starts writing to the CoE Object *)
bError : BOOL;
nErrId : UDINT;
END_VAR
fbSdoWrite(
sNetId := sNetId,
nSlaveAddr := nSlaveAddr,
nIndex := nIndex,
nSubIndex := nSubIndex,
pSrcBuf := ADR(nValue),
cbBufLen := SIZEOF(nValue),
bExecute := bExecute
);
IF NOT fbSdoWrite.bBusy THEN
bExecute := FALSE;
IF NOT bError THEN
(* write successful *)
bError := FALSE;
nErrId := 0;
ELSE
(* write failed *)
bError := fbSdoWrite.bError;
nErrId := fbSdoWrite.nErrId;
END_IF
fbSdoWrite(bExecute := FALSE);
END_IF
Fixed problem by linking variable from PLC code to DevState-input of the device.
Linking to plain InfoData doesn't seem to work though.
You should assign a task to your devices who is responsible to read/write data. double click your master device, go to EtherCAT tab and click on Sync Unit Assignment
there select your terminals then available tasks and apply!

How to correctly override a persistent input variable on instanciation and call in a derived FUNCTION_BLOCK?

I have a input variable c_timTransmissionRate which is usually initalized using the persistent variable list. The default value for the persistent variable list in the function block TxRaw is then overriden by the instanciating function block ´TxDbc´. This again is application specific overriden in the persistent variable list itself.
In a few cases the value for c_timTransmissionRate is provided dynamically by the instanciating function block TxTsc1 during runtime.
My problem is that I currently always see the original TIME#0ms value in the debugger instead of the values given in the persistent variable list.
Extract of my current code:
FUNCTION_BLOCK TxRaw
VAR_INPUT PERSISTENT CONSTANT
c_timTransmissionRate : TIME := T#0ms;
END_VAR
(* instructions *)
END_FUNCTION_BLOCK
FUNCTION_BLOCK Tx
VAR
tx : TxRaw;
END_VAR
(* instructions *)
tx();
(* instructions *)
END_FUNCTION_BLOCK
FUNCTION_BLOCK TxTsc1 EXTENDS Tx
(* instructions *)
tx.c_timTransmissionRate := PI()*THUMB();
SUPER^();
END_FUNCTION_BLOCK
FUNCTION BLOCK TxDbc EXTENDS Tx
VAR
tx : TX := (c_timTransmissionRate := T#20ms);
END_VAR
(* instructions *)
SUPER^();
END_FUNCTION_BLOCK

How do two parallel processes execute this mutual exclusion code?

This is a solution for mutual exclusion problem from one of my exercises:
var blocked : array[0..1] of boolean; (* blocked is an array of two Boolean elements *)
turn : 0..1; (* turn can have value 0 or 1 *)
procedure P ( id : integer )
begin
repeat
blocked[id] := true;
while turn ≠ id do
begin
while blocked[1 – id] do; (* a do-nothing loop to implement busy waiting *)
turn := id
end;
< critical section >
blocked[id] := false;
< remainder of procedure >
until false
end;
begin (* main program *)
blocked[0] := false; blocked[1] := false;
turn := 0;
parbegin (* begin parallel execution of processes *)
P(0); P(1) (* invoke two concurrent processes, both to execute procedure P *)
Parend (* end parallel execution *)
end.
I am unable to understand when exactly a process executes particular statement in the code above. I know we can have multiple execution scenarios.
I want to know if this uncertainty of order of execution exists all the time.
And, is there any way to figure out what execution scenario will occur?
Thanks!