Multi independent input timer - plc

Imagine that we have independent boolean variables that can occur independently. Now, if at least one of the variables occurs for a certain period of time, an alert will be activated. The common solution is that we use a timer for each variable.
Is there an optimal solution that can only be used with a single timer?
Example for 2 variables with 1 second as passed time:
VAR
Timer1,Timer2:TON;
bVar1,bVar2:BOOL;
tSetDelay:TIME:=T#1S;
Alarm:BOOL;
END_VAR
Timer1(IN:=bVar1,PT:=tSetDelay);
Timer2(IN:=bVar2,PT:=tSetDelay);
IF Timer1.Q RO Timer2.Q THEN
Alarm:=TRUE;
END_IF
If we use OR it won't be true
Timer(IN:=bVar1 OR bVar2,PT:=tSetDelay);
cause the vars may have overlap in the same tSetDelay time, it means that they may happen less than the delay, but the timer output be true.
In this example, we have only 2 variables, but if we have many more variables it will be more important to find a better solution.

It it not possible to manage this with a single timer.
Given the that you want to ensure that the triggering source is a single variable (without any logical interference from additional variables), each variable must be compared against it own history (timer).
Recommended path forward would be to build an Alarm function block that handles the majority of the logic here in a consistent manner. An example of this kind of function block is below:
fb_AlarmMonitor
VAR_INPUT
monitor : ARRAY [ 0..7 ] OF POINTER TO BOOL;
duration : TIME;
END_VAR
VAR_OUTPUT
alarm : BOOL;
END_VAR
VAR
timers : ARRAY [ 0..7 ] OF TON;
END_VAR
VAR_TEMP
i : UDINT;
END_VAR
alarm := false;
FOR i := 0 TO 7 BY 1 DO
// Only run process if linked boolean
IF monitor[ i ] <> 0 THEN
timers[ i ]( in := monitor[ i ]^,
pt := duration );
alarm := timers[ i ].Q OR alarm;
END_IF
END_FOR
Implementation
VAR
// Alarm flags
flag_1, flag_2, flag_3
: BOOL;
// Pointers to alarm flags
flag_array : ARRAY [ 0..7 ] OF POINTER TO BOOL
:=[ ADR( flag_1 ),
ADR( flag_2 ),
ADR( flag_3 ) ];
// Monitoring flags
monitor : fb_AlarmMonitor
:=( monitor := flag_array,
duration := T#10S );
END_VAR
monitor();

You cannot do this without individual timers. Here is how I would approach this.
Set global constant and variables
VAR_GLOBAL
glbEvents: ARRAY[1..c_EventsNum] OF stMyEvents; (* Events array *)
END_VAR
VAR_GLOBAL CONSTANT
c_EventsNum: INT := 3; (* Number of events *)
END_VAR
Now you can map glbEvents[1].State to inputs of PLC
Define new structure
TYPE stMyEvents : STRUCT
State : BOOL; (* Event current state *)
StateM : BOOL; (* Memmory of state in previouse cycle to get the trigger *)
Timer: TON := (PT := T#1S); (* Timer *)
END_STRUCT
END_TYPE
Create function
FUNCTION ProcessEvents : BOOL
VAR
iCount: INT; (* Counter *)
END_VAR
FOR iCount := 1 TO c_EventsNum DO
glbEvents[iCount].Timer(IN := glbEvents[iCount].State);
IF glbEvents[iCount].Timer.Q THEN
ProcessEvents := TRUE;
EXIT;
END_IF;
END_FOR;
END_FUNCTION
Implementation
PROGRAM PLC_PRG
VAR
xAlarm: BOOL; (* Alarm *)
END_VAR
IF ProcessEvents() THEN
// Alarm happened
END_IF;
END_PROGRAM
With this approach although you do not have 1 Timer, you have certain level of abstraction that makes it more flexible to support and modify.
But if you absolutely do not want to have so many TON timers, you can create your own timer. It will be single timer in one FB
VAR_GLOBAL
glbEvents: ARRAY[1..c_EventsNum] OF stMyEvents; (* Events array *)
END_VAR
VAR_GLOBAL CONSTANT
c_EventsNum: INT := 3; (* Number of events *)
END_VAR
TYPE stMyEvents : STRUCT
State : BOOL; (* Event current state *)
StateM : BOOL; (* Memmory of state in previouse cycle to get the trigger *)
TimeM: TIME; (* Memmory of event start time *)
END_STRUCT
END_TYPE
FUNCTION_BLOCK ProcessEvents
VAR
iCount: INT; (* Counter *)
END_VAR
VAR_OUTPUT
Q: BOOL; (* Impulse if alarm *)
END_VAR
Q := FALSE;
FOR iCount := 1 TO c_EventsNum DO
(* Get raising edge and save the timer *)
IF glbEvents[iCount].State AND NOT glbEvents[iCount].StateM THEN
glbEvents[iCount].TimeM := TIME();
END_IF;
glbEvents[iCount].StateM := glbEvents[iCount].State;
(* If event is low reset timer *)
IF NOT glbEvents[iCount].State THEN
glbEvents[iCount].TimeM := T#0S;
END_IF;
(* if more than a second make impuls on Q *)
IF (glbEvents[iCount].TimeM > T#0S) AND ((TIME() - glbEvents[iCount].TimeM) >= T#1S) THEN
Q := TRUE;
glbEvents[iCount].TimeM := T#0S;
END_IF;
END_FOR;
END_FUNCTION_BLOCK
PROGRAM PLC_PRG
VAR
fbeventProcess: ProcessEvents; (* function block *)
END_VAR
fbeventProcess();
IF fbeventProcess.Q THEN
// Alarm happened
END_IF;
END_PROGRAM

Related

flowing lights in structured text

I am very new to structured text, so pardon my simple question.
I am using OpenPLC to create this simple program. I have been following the example from the link below to create flowing lights simple program with structured text. In this video, they used 5LEDs and controlled it with case statements.
However, my question is, if my program needs to turn on 100 lights, how should I change the code?
Should I use for loops? How?
https://www.youtube.com/watch?v=PXnaULHpxC8&t=25s
Yes you can use for loops etc. to make the program more "dynamic".
Unfortunately most of the PLC's don't give you dynamic access to their digital outputs. This means that at the end you will have to write code that will translate the value from array (which you will be looping through) into digital outputs.
There are a few ways to do that. First let me show how you can create chasing light for up to 16.
PROGRAM PLC_PRG
VAR
iNumOfLights : INT := 6;
fbCounter : CTU := ;
fbTicker : BLINK := (ENABLE := TRUE, TIMELOW := T#100MS, TIMEHIGH := T#1S);
wOut: WORD;
END_VAR
fbTicker();
fbCounter(CU := fbTicker.OUT, RESET := fbCounter.Q, PV := iNumOfLights);
wOut := SHL(2#0000_0000_0000_0001, fbCounter.CV);
A := wOut.0;
B := wOut.1;
C := wOut.2;
D := wOut.3;
E := wOut.4;
F := wOut.5;
G := wOut.6;
END_PROGRAM
Or if you know output address you can do it directly to outputs.
PROGRAM PLC_PRG
VAR
iNumOfLights : INT := 6;
fbCounter : CTU := ;
fbTicker : BLINK := (ENABLE := TRUE, TIMELOW := T#100MS, TIMEHIGH := T#1S);
wOut AT %QB0.1: WORD;
END_VAR
fbTicker();
fbCounter(CU := fbTicker.OUT, RESET := fbCounter.Q, PV := iNumOfLights);
wOut := SHL(2#0000_0000_0000_0001, fbCounter.CV);
END_PROGRAM
You can also change type of chasing lights by something like.
IF fbCounter.CV = 0 THEN
wOut := 0;
END_IF;
wOut := wOut OR SHL(2#0000_0000_0000_0001, fbCounter.CV);
Now what is behind this. SHl operator will move 1 to the left on set number. For example SHL(2#0000_0000_0000_0001, 3) will result in 2#0000_0000_0000_1000. So we assign it to wOut and then access individual bits by wOut.[n].

Is it possible to indirectly index an array in structured text (IEC 61131-3 standard)

I have an array of a structure: myStructure[0..100]
I would like to index that structure by name.
It works by giving each index a name:
P101_AI := 9
P102_AI := 10
P103_AI := 11
P104_AI := 12
So indexing a member in the structure: myStructure[P103_AI].value (i.e. indexing myStructure[11].value)
However, is it possible to index this indirectly?
i.e. if delcaring TempString : STRING[30];
altering TempString at runtime to index the array.
Here is some pseudocade to describe what I would like to do:
FOR i:=101 TO 104 DO
TempString := CONCAT('P',i);
TempString := CONCAT(TempString,'_AI');
MyStructure[ indirect(TempString)].value := 'some value';
END_FOR;
What about creating an enum?
{attribute 'qualified_only'}
TYPE E_AnalogInput :
(
P101_AI := 9,
P102_AI,
P103_AI,
P104_AI
);
END_TYPE
Then you can declare:
analogInputs : ARRAY[E_AnalogInput.P101_AI..E_AnalogInput.P104_AI] OF INT;
Running a for loop:
FOR inputCount:=E_AnalogInput.P101_AI TO E_AnalogInput.P104_AI BY 1 DO
//Do something
END_FOR
Hope this helps
I would use pointers and mapping. First, change your structure to the pointer.
TYPE MyType: STRUCT
input: POINTER TO INT;
value: INT;
// other properties
END_STRUCT
END_TYPE
Then, create a global array.
VAR_GLOBAL
MyStructure: ARRAY[1..100] OR MyType;
END_VAR
Now in a program create one time running code.
PROGRAM PLC_PRG:
VAR
xInit:= FALSE;
END_VAR
IF NOT xInit THEN
xInit := TRUE;
mMap();
END_IF
END_PROGRAM
Now in a method or action mMap do this for every array element.
MyStructure[1].input:= ADR(AI_Name);
MyStructure[2].input:= ADR(P102_AI);
MyStructure[3].input:= ADR(%ID0.1);
I used 3 different ways to bind pointer. The order is not important I think. Then in a program, you can do this.
FOR i := 1 TO 100 DO
MyStructure[i].value := 'MyStructure[i].input^;
END_FOR;
This is how i solved it.
TYPE infoType: STRUCT
name: STRING[20];
END_STRUCT
END_TYPE
TYPE sensorType: STRUCT
value: INT;
info: infoType;
END_STRUCT
END_TYPE
TYPE IO_Type: STRUCT
AI: ARRAY[1..100] OF sensorType;
END_STRUCT
END_TYPE
TYPE E_AnalogInput :
(
P101_AI := 9,
P102_AI,
P103_AI,
P104_AI
);
END_TYPE
PROGRAM PLC_PRG:
VAR
IOs: IO_Type;
END_VAR
IOs.AI[P101_AI].info.name := 'P101_AI';
FOR i:=101 TO 104 DO
TempString := CONCAT('P',i);
TempString := CONCAT(TempString,'_AI');
FOR i:=0 TO SIZE_OF(ADR(IOs.AI)) / SIZE_OF(ADR(IOs.AI[0])) DO
IF TempString = IOs.AI[i].info.name THEN
IOs.AI[i].value := 123; // Some value
EXIT;
END_IF;
END_FOR;
END_FOR;
END_PROGRAM

Delay Timer in Structured Text

I have Just started working on PLC using Structured text, I have to store values in Array of Temperature variable after delay of 1 min every time but i am not able to do that.
FOR i := 0 TO 5 DO
Temp[i] := tempsensor;
END_FOR;
This is kind a pseudo code.
I just need to bring in the delay in the loop that after every 1 min it could read the value and store it in the array location.
Even if there is any other way then I will really appreciate that.
Try this
VAR
i:INT;
Temp: ARRAY[0..10000] OF LREAL;
delayTimer: TON;
END_VAR
delayTimer(IN := not delayTimer.Q, PT := T#1m);
IF delayTimer.Q THEN
Temp[i] := tempsensor;
i := i + 1;
IF i > 10000 THEN
i := 0;
END_IF;
END_IF;
After 1 minute it will record 1 temperature value and index the array. If it reaches the end of the array it will start to write over at the beginning.
Once every minute you cycle through array and set values.
VAR
i: INT := 1; (* Cycle number *)
temp: ARRAY[1..5] OF REAL; (* Array of temperatures *)
ton1: TON; (* Timer *)
END_VAR
ton1(IN := NOT ton1.Q, PT := T#1m);
IF ton1.Q THEN
temp[i] := tempsensor;
IF i >= 5 THEN i := 1 ELSE i := i + 1 END_IF;
END_IF;

How to fix a bug on my queue of devices in PLC

I am trying to create a simple queue in .st with 6 devices that should be turned on and off in the queue order only the ones that are available should be connected. For example I did a test with 6 devices available and then I was unavailable one by one but always the last one does not turn off at the output and leaves the program stopped.
I use the OpenPCS IDE of infoteam.
VAR_INPUT
ENABLE : BOOL ;
STATE_DEVICE1 : BOOL ;
STATE_DEVICE2 : BOOL ;
STATE_DEVICE3 : BOOL ;
STATE_DEVICE4 : BOOL ;
STATE_DEVICE5 : BOOL ;
STATE_DEVICE6 : BOOL ;
NUMBER_DEVICES : USINT ;
POWER_REQUEST : USINT ;
END_VAR
VAR_OUTPUT
REQUEST_DEVICE1 : BOOL ;
REQUEST_DEVICE2 : BOOL ;
REQUEST_DEVICE3 : BOOL ;
REQUEST_DEVICE4 : BOOL ;
REQUEST_DEVICE5 : BOOL ;
REQUEST_DEVICE6 : BOOL ;
END_VAR
VAR
STATE_DEVICES_ARR : ARRAY[1..6] OF BOOL ;
REQUEST_DEVICES_ARR : ARRAY[1..6] OF BOOL ;
NUMBER_DEVICES_STATE : USINT ;
NUM_DEV_REAL : USINT ;
NUM_DEV_ON : USINT ;
DEVICES_TO_ON : USINT ;
DEVICES_TO_OFF : USINT ;
P_ON : USINT := 0 ;
P_OFF : USINT := 0 ;
COUNT : USINT ;
END_VAR
IF ENABLE = TRUE THEN
STATE_DEVICES_ARR[1] := STATE_DEVICE1;
STATE_DEVICES_ARR[2] := STATE_DEVICE2;
STATE_DEVICES_ARR[3] := STATE_DEVICE3;
STATE_DEVICES_ARR[4] := STATE_DEVICE4;
STATE_DEVICES_ARR[5] := STATE_DEVICE5;
STATE_DEVICES_ARR[6] := STATE_DEVICE6;
NUM_DEV_ON := 0;
FOR COUNT := 1 TO 6 DO
IF STATE_DEVICES_ARR[COUNT] = FALSE THEN
REQUEST_DEVICES_ARR[COUNT] := FALSE;
END_IF;
IF STATE_DEVICES_ARR[COUNT] = TRUE THEN
NUMBER_DEVICES_STATE := NUMBER_DEVICES_STATE + 1;
END_IF;
IF REQUEST_DEVICES_ARR[COUNT] = TRUE THEN
DEVICES_TO_ON := DEVICES_TO_ON + 1;
END_IF;
END_FOR;
IF POWER_REQUEST > NUM_DEV_ON THEN
DEVICES_TO_ON := POWER_REQUEST-NUM_DEV_ON;
DEVICES_TO_OFF := 0;
END_IF;
IF POWER_REQUEST < NUM_DEV_ON THEN
DEVICES_TO_ON := 0;
DEVICES_TO_OFF := NUM_DEV_ON-POWER_REQUEST;
END_IF;
IF POWER_REQUEST = NUM_DEV_ON THEN
DEVICES_TO_ON := 0;
DEVICES_TO_OFF := 0;
END_IF;
IF NUMBER_DEVICES_STATE = 0 THEN
DEVICES_TO_ON := 0;
END_IF;
(*===============================================================================================================*)
(*switches the devices on or off according to FIFO logic.*)
(*===============================================================================================================*)
IF DEVICES_TO_ON > 0 THEN (* check if a device was requested to connect*)
WHILE DEVICES_TO_ON > 0 DO (* as long as there are devices to be connected *)
P_ON := P_ON + 1; (* increase the "pointer" connect devices *)
IF P_ON > 6 THEN (* check if the pointer position is at the end of the device queue *)
P_ON :=1; (* if it is at the end, it returns to the start *)
END_IF;
IF STATE_DEVICES_ARR[P_ON] = TRUE THEN (* check if the device is available to be connected *)
REQUEST_DEVICES_ARR[P_ON] := TRUE; (* connect the device of position P_ON *)
DEVICES_TO_ON := DEVICES_TO_ON-1; (* decrements the number of devices to be connected *)
END_IF;
END_WHILE;
END_IF;
IF DEVICES_TO_OFF > 0 THEN (* check if you are asked to disconnect from some device *)
WHILE DEVICES_TO_OFF > 0 DO (* as long as there are devices to be switched off *)
P_OFF := P_OFF + 1; (* increments the "pointer" to turn off devices *)
IF P_OFF > 6 THEN (* check if the pointer position is at the end of the device queue *)
P_OFF :=1; (* check if the pointer position is at the end of the device queue *)
END_IF;
IF STATE_DEVICES_ARR[P_OFF] = TRUE THEN (* check if the device is available to be switched off *)
REQUEST_DEVICES_ARR[P_OFF] := FALSE; (* disconnect device from position P_OFF ​​*)
DEVICES_TO_OFF := DEVICES_TO_OFF-1; (* decrements the number of devices to be disconnected *)
END_IF;
END_WHILE;
END_IF;
(* I THINK THE BUG WAS HERE *)
REQUEST_DEVICE1 := REQUEST_DEVICES_ARR[1];
REQUEST_DEVICE2 := REQUEST_DEVICES_ARR[2];
REQUEST_DEVICE3 := REQUEST_DEVICES_ARR[3];
REQUEST_DEVICE4 := REQUEST_DEVICES_ARR[4];
REQUEST_DEVICE5 := REQUEST_DEVICES_ARR[5];
REQUEST_DEVICE6 := REQUEST_DEVICES_ARR[6];
END_IF;
IF ENABLE = FALSE THEN
REQUEST_DEVICE1 := FALSE;
REQUEST_DEVICE2 := FALSE;
REQUEST_DEVICE3 := FALSE;
REQUEST_DEVICE4 := FALSE;
REQUEST_DEVICE5 := FALSE;
REQUEST_DEVICE6 := FALSE;
END_IF;
;
There are many things to improve in your code. For instance:
IF REQUEST_DEVICES_ARR[COUNT] = TRUE THEN
DEVICES_TO_ON := DEVICES_TO_ON + 1;
END_IF;
This is senseless because right after it you override DEVICES_TO_ON and do not use it. So why would you set it?
Or you do this
IF POWER_REQUEST > NUM_DEV_ON THEN
DEVICES_TO_ON := POWER_REQUEST-NUM_DEV_ON;
DEVICES_TO_OFF := 0;
END_IF;
But nowhere before you set NUM_DEV_ON.
Or you have an input variable NUMBER_DEVICES but nowhere used in the code.
But Generally, you've chosen the wrong approach to the problem.
So, first of all, you have to create a type
TYPE MY_DEVICE: STRUCT
Available: BOOL; (* If a device is available *)
State: BOOL; (* Current device state *)
Queue: BOOL; (* What to do with device *)
END_STRUCT
END_TYPE
Then set global variables
VAR_GLOBAL
garDevices: ARARY[1.._DEVICE_NUM] OF MY_DEVICE; (* Comment *)
END_VAR
VAR_GLOBAL CONSTANT
_DEVICE_NUM: USINT := 6; (* Comment *)
END_VAR
This way you can change the number of devices by simply changing _DEVICE_NUM constant without changing the rest of the code.
Now your function
FUNCTION QUEUE_DEVICES: BOOL
VAR_INPUT
ENABLE : BOOL;
POWER_REQUEST : USINT;
END_VAR
VAR
iDeviceOnOff: INT;
usiCount: USINT;
usiCountOnDevices: USINT;
END_VAR
(* If not enabled, set all devices to turn off and quite function *)
IF NOT ENABLE THEN
FOR usiCount TO _DEVICE_NUM DO
garDevices[usiCount].Queue := FALSE;
END_FOR;
RETURN;
END_IF;
(* Count how many devices is on already *)
usiCountOnDevices := 0;
FOR usiCount := 1 TO _DEVICE_NUM DO
IF garDevices[usiCount].State THEN
usiCountOnDevices := usiCountOnDevices + 1;
END_IF;
END_FOR;
(* Find the difference between power request and power on.
Might be negative or positive *)
iDeviceOnOff := POWER_REQUEST - usiCountOnDevices;
FOR usiCount := 1 TO _DEVICE_NUM DO
(* If device is not available for turning on or off
continue to the other device *)
IF garDevices[usiCount].Available THEN
(* if iDeviceOnOff is positive, then we have to turn on devices *)
IF iDeviceOnOff > 0 AND NOT garDevices[usiCount].Queue THEN
garDevices[usiCount].Queue := TRUE;
iDeviceOnOff := iDeviceOnOff - 1;
END_IF;
(* if iDeviceOnOff is negative we have to turn OFF devices *)
IF iDeviceOnOff < 0 AND garDevices[usiCount].Queue THEN
garDevices[usiCount].Queue := FALSE;
iDeviceOnOff := iDeviceOnOff + 1;
END_IF;
(* If iDeviceOnOff is 0 means balance is reached *)
IF iDeviceOnOff = 0 THEN
EXIT;
END_IF;
END_IF;
END_FOR;
END_FUNCTION
Then, you can add some other tests at the end of the function. For example.
IF iDeviceOnOff > 0 THEN
_ERROR: = 'More power requested than available devices';
END_IF;
IF iDeviceOnOff < 0 THEN
_ERROR: = 'There is a power excess';
END_IF;
Programming in a PLC is a bit different than in a normal OS application where the application runs once. It looks to me like you have alot of while and for loops, and I don't think these would be necessary.
Looks, to me, like your program could benefit from the use of a state machine. You can also use enumerated types to improve readability of state machines. This is a simple concept used to control sequences of events in a PLC. Simple State Machine example:
PROGRAM MAIN
VAR
bPizzaToCook : BOOL;
bPizzaCooking : BOOL;
bPizzaCooked : BOOL;
bLoadPizza : BOOL;
bUnloadPizza : BOOL;
fb_t_CookTimer : TON;
iPizzasCooked : UDINT;
bBuzzer : BOOL;
iPizzaState : DINT;
sPizzaState : STRING;
END_VAR
IF bPizzaToCook AND NOT bPizzaCooking THEN
//simulates conveyor moving pizza into oven
bPizzaToCook := FALSE;
bPizzaCooking := TRUE;
END_IF
IF bLoadPizza THEN //pizza loaded onto conveyor
bLoadPizza := FALSE;
bPizzaToCook := TRUE;
END_IF
IF bUnloadPizza THEN //pizza unloaded off of conveyor
bUnloadPizza := FALSE;
bPizzaCooked := FALSE;
END_IF
CASE iOvenState OF
0 : //wait for pizza to cook
sPizzaState := ‘Waiting for pizza…’;
IF bPizzaCooking THEN
iPizzaState := 10;
END_IF
10: //cook the pizza (start the timer)
sPizzaState := ‘Baking Pizza…’
fb_t_CookTimer(INT := TRUE, PT := T#10s);
IF fb_t_CookTimer.Q THEN
fb_t_CookTimer(IN := FALSE);
iPizzaState := 20;
END_IF
20: //is there space to move pizza out of oven?
IF NOT bPizzaCooked THEN
bPizzaCooking := FALSE;
bPizzaCooked := TRUE;
bBuzzer := FALSE;
iPizzaState := 30;
ELSE //pizza burning
sPizzaState := ‘BURNING’;
bBuzzer := TRUE;
END_IF
30://pizza ready on conveyor
iPizzasCooked := iPizzasCooked + 1;
iPizzaState := 0; //reset state
ELSE
sPizzaState := ‘Invalid State!’;
END_CASE
Plenty of other state machine examples out there. This one is from https://www.youtube.com/watch?v=XmcXRZXPRWs

Learning Structured Text, function block to controll a "pusher"

I come from a background in PC-programming (VB/C# .Net and PHP) and am learning PLC programming in school. At a recent test we had we where to create a function block that would push an element of a production line and into a container.
Now, that is pretty simple, so I have added an Alarm to my code if the "pusher" (for lack of a better word) that will activate if the maneuver is not complete within 10 seconds.
My problem is that the pusher goes into an Alarm state immediately and it seems to be rather buggy :( So any answer would give some direction of how to structure my code better, or figure out why it goes into an alarm so fast...
(* INIT *)
(* Drive the pusher back if its out on init *)
IF M8002 THEN
SkyvTilbake := TRUE; (* My "Function"/Step to retract the pusher *)
Ferdig := FALSE; (*Let other components know that the pushers operation has completed *)
SkyverUtMotor := FALSE; (*This is the engine output for driving the pusher out *)
SkyverInnMotor := FALSE; (* This is the engine output for driving the pusher in *)
END_IF;
(* "Aktiver" is a input to activate the pusher *)
IF Aktiver THEN
SkyvTilbake := FALSE;
SkyvUt := TRUE; (* My "Function"/Step" to push the pusher out *)
Ferdig := FALSE;
END_IF;
(* Push out step *)
IF SkyvUt AND NOT SkyvTilbake AND NOT Alarm THEN
TON_1(IN:= SkyvUt ,PT:= AlarmTid ,Q:= Alarm ,ET:= TimeLeft );
SkyverUtMotor := TRUE;
(* When sensor out activates (input), retract the pusher *)
IF SensorUte THEN
SkyvUt := FALSE;
SkyvTilbake := TRUE;
SkyverUtMotor := FALSE;
END_IF;
(* retract the pusher as long as there are no alarms *)
ELSIF SkyvTilbake AND NOT Alarm THEN
TON_2(IN:= SkyvTilbake ,PT:= AlarmTid ,Q:= Alarm ,ET:= TimeLeft );
SkyverInnMotor := TRUE;
(* When it reach the normal position, activate "Ferdig" *)
IF SensorInne THEN
SkyverInnMotor := FALSE;
SkyvTilbake := FALSE;
Ferdig := TRUE;
END_IF;
END_IF;
(* When the alarm activates, stop all the engines *)
IF Alarm THEN
SkyverUtMotor := FALSE;
SkyverInnMotor := FALSE;
END_IF;
(* RESET ALARM *)
IF Reset THEN
Alarm := 0;
SkyvTilbake := TRUE;
END_IF;
PS. The test was in the middle of december, but I'm looking to learn more about structured text and designing PLC programs. Also the code is designed in to run in a Mitsubishi FXCPU, and written in GX Works 2
I don't see where you are actually setting the time duration for your timer ( PT:= AlarmTid ).
Also, as I am not familiar with Mitsubishi PLC's, do timers automatically reset? It looks like once your timer is on, it never turns off.
You nay also want to look into using 'CASE . . . WHERE', it tends to clean up the code.