How to determine the maximum nth occurrence of an event at a given duration? - twincat

How can it be determined that the time interval of the nth event of an event is not more than a certain period of time?
For example, an event can occur up to ‍5 ti‍mes every 10 minutes.
In STL we can use this
VAR
counter:CTU;
timer:TON;
Event:BOOL;
bMaxEventHappend:BOOL;
tElapsedTime:TIME;
END_VAR
counter(CU:=Event);
IF counter.CV=1 THEN
timer(IN:=TRUE);
END_IF
IF counter.CV=5 THEN
bMaxEventHappend:=TRUE;
counter(Reset:=TRUE);
END_IF
//resetProcess
IF counter.CV=1 AND timer.et>=T#10M THEN
timer(IN:=FALSE);
counter(Reset:=TRUE);
ELSIF counter.CV=2 THEN
tElapsedTime:=timer.et;
IF timer.ET-tElapsedTime >=T#10M THEN
timer(IN:=FALSE);
counter(Reset:=TRUE);
END_IF
ELSIF counter.CV=3 THEN
tElapsedTime:=tElapsedTime+timer.et;
IF timer.ET-tElapsedTime >=T#10M THEN
timer(IN:=FALSE);
counter(Reset:=TRUE);
END_IF
ELSIF counter.CV=4 THEN
tElapsedTime:=tElapsedTime+timer.et;
IF timer.ET-tElapsedTime >=T#10M THEN
timer(IN:=FALSE);
counter(Reset:=TRUE);
END_IF
ELSIF counter.CV=5 THEN
tElapsedTime:=tElapsedTime+timer.et;
IF timer.ET-tElapsedTime >=T#10M THEN
timer(IN:=FALSE);
counter(Reset:=TRUE);
END_IF
END_IF
This method does not seem to be optimal for achieving the desired.
Is there another optimal method?
Any help would be appreciated.

Unfortunately, I was not able to understand what you are trying to accomplish exactly. If my answer does not give your ideas, just update your question and explain what you want to do.
TYPE MY_EVENT : STRUCT
TimeStart : TIME; (* Time when event was started *)
TimeEnd : TIME; (* Time when event was ended *)
TimeWorked: TIME; (* Time that event was working *)
Index: USINT; (* Index in array *)
END_STRUCT
END_TYPE
PROGRAM PLC_PRG
VAR
arEvents: ARARY[1..5] OF MY_EVENTS; (* Last 5 events *)
xEventStart: BOOL; (* Event started *)
xEventStartM: BOOL; (* Memmory of event started for edge detection *)
stEvent: MY_EVENT; (* Current event *)
usiCount: USINT := 1; (* Current index *)
usiFor: USINT := 1; (* FOR iterator *)
stSearchEvent: MY_EVENT; (* Searched event *)
END_VAR
(* Raising edge of event. Event starts *)
IF xEventStart AND NOT xEventStartM THEN
stEvent.TimeStart := TIME();
END_IF;
(* Falling edge of event. Event ends *)
IF NOT xEventStart AND xEventStartM THEN
(* Finalize event *)
stEvent.TimeEnd := TIME();
stEvent.Index := usiCount;
stEvent.TimeWorked := (stEvent.TimeEnd - stEvent.TimeStart);
(* Save event in array *)
arEvents[usiCount] := stEvent;
IF usiCount = 5 THEN
usiCount := 1;
ELSE
usiCount := usiCount + 1;
END_IF;
stEvent.TimeEnd := T#0S;
stEvent.TimeStart := T#0S;
stEvent.TimeWorked := T#0S;
END_IF;
xEventStartM := xEventStart;
(* Current event is longer than 1 minute *)
IF (TIME() - stEvent.TimeStart) > T#1M THEN
// Do something
END_IF;
(* Get longest event out of last 5 events *)
stSearchEvent.TimeWorked := T#0S;
FOR usiFor TO 5 DO
IF (stSearchEvent.TimeWorked < arEvents[usiFor].TimeWorked) THEN
stSearchEvent := arEvents[usiFor];
END_IF;
END_FOR;
END_PROGRAM
This is an example how to store last 5 events in array and then how to know that current event is too long and how to find longest event in array.

Finally, I found this method for this problem:
PROGRAM MAIN
VAR
trigger:r_Trig();
event: BOOL;
tDuration:TIME:=T#10M;
tTimeInit: TIME:=TIME()-tDuration;
aTime:ARRAY[1..5] OF TIME:=[tTimeInit,tTimeInit,tTimeInit,tTimeInit,tTimeInit];
alarm:BOOL;
i:INT;
END_VAR
trigger(clk:=event);
IF trigger.Q THEN
IF (TIME()-aTime[i+1]<tDuration) THEN
alarm:=TRUE;
END_IF
aTime[i+1]:=TIME();
i:=(i+1) MOD 5;
END_IF
and seems it works(have been tested for tDuration:TIME:=T#10s;)

Related

Concatenate variable

I need to concatenate a string and an integer and a string into a variable - in this case an input.
The inputs are named as following:
DI_Section1_Valve AT %I*: BOOL;
DI_Section2_Valve AT %I*: BOOL;
DI_Section3_Valve AT %I*: BOOL;
Now, I want to loop through these (this is just a short example):
For i:= 1 TO 3 DO
Var[i] := NOT CONCAT(CONCAT('DI_Section', INT_TO_STRING(i)), '_Valve')
END_FOR
But by concatenating strings, I'll end up with a string, eg. 'DI_Section1_Valve', and not the boolean variable, eg. DI_Section1_Valve.
How do I end up with the variable instead of just the string?
Any help is appreciated, thanks in advance.
/Thoft99
That technique is called variable variables. Unfortunately this is not available in ST. There are few ways around it. As I understand Twincat 3 is based on Codesys 3.5. That means UNION is available. There is also a trick that references to BOOL does not work as expected and one reference take one byte. So you cannot place them in order, so you need to know BYTE address of input module.
TYPE DI_Bits : STRUCT
DI_Section1_Valve : BIT; (* Bit 1 *)
DI_Section2_Valve : BIT; (* Bit 2 *)
DI_Section3_Valve : BIT; (* Bit 3 *)
DI_Section4_Valve : BIT; (* Bit 4 *)
DI_Section5_Valve : BIT; (* Bit 5 *)
DI_Section6_Valve : BIT; (* Bit 6 *)
DI_Section7_Valve : BIT; (* Bit 7 *)
DI_Section8_Valve : BIT; (* Bit 8 *)
END_STRUCT
END_TYPE
TYPE DI_Sections : UNION
ByName : DI_Bits;
ByID : ARRAY[1..8] OF BOOL; (* Comment *)
ByMask: BYTE;
END_UNION
END_TYPE
PROGRAM PLC_PRG
VAR
DIS AT %IB0.0: DI_Sections; (* Our union *)
xOut : BOOL;
END_VAR
xOut := DIS.ByID[1];
xOut := DIS.ByName.DI_Section1_Valve;
DIS.ByID[5] := TRUE;
// Now DIS.ByName.DI_Section5_Valve also EQ TRUE
END_PROGRAM
You can't access a variables by using strings. What you can do instead is manually create an array of pointers to the values you want to index, for example:
DI_Section1_Valve AT %I*: BOOL;
DI_Section2_Valve AT %I*: BOOL;
DI_Section3_Valve AT %I*: BOOL;
DI_Section_Valves: ARRAY [1..3] OF POINTER TO BOOL
:= [ADR(DI_Section1_Valve), ADR(DI_Section2_Valve), ADR(DI_Section3_Valve)];
FOR i:= 1 TO 3 DO
out[i] := DI_Section_Valves[i]^;
END_FOR
EDIT: Alternatively, you could create a function that does this:
METHOD Get_DI_Selection_Valve : BOOL
VAR_INPUT
i: INT;
END_VAR
CASE i OF
1: Get_DI_Selection_Valve := DI_Section1_Valve;
2: Get_DI_Selection_Valve := DI_Section2_Valve;
3: Get_DI_Selection_Valve := DI_Section3_Valve;
END_CASE
FOR i:= 1 TO 3 DO
out[i] := Get_DI_Selection_Valve(i);
END_FOR
The idea is the same though

CODESYS problems with edge detection (bounce)

I have a problem with my CODESYS program. I have three buttons, which are defined as input. For each button a number is stored. For example the number 1.
I have now created a program, which recognizes an edge on the button and stores the numerical value (2) of the button in an array. If you now press another button with the value (3), the value is also stored in a variable again. The two variables should be added together. 2 + 3 = 23. In my program I have the problem that if I press the button tester with the value 2, I get 22. This is wrong. I think the problem is due to the bruise of the push button. Several edges are detected. So I wanted to solve this software technically with a delay. Do you have any idea how I could program that?
CODE:
IF (PLC_PRG.calls[5].gpio = TRUE) THEN // edge detection on gpio
IF (counter = 0) THEN // counter for the first value
floorstorage2[0] := PLC_PRG.calls[5].message.floorstore[5]; // save button value in the array to calculate the total
counter := 1;
ELSE
floorstorage2[1] := PLC_PRG.calls[5].message.floorstore[5]; // save button value in the array to calculate the total
counter := 0;
END_IF
END_IF
IF (PLC_PRG.calls[6].gpio = TRUE) THEN // edge detection on gpio
IF (counter = 0) THEN // counter for the first value
floorstorage2[0] := PLC_PRG.calls[6].message.floorstore[6]; // save button value in the array to calculate the total
counter := 1;
ELSE
floorstorage2[1] := PLC_PRG.calls[6].message.floorstore[6]; // save button value in the array to calculate the total
counter := 0;
END_IF
END_IF
IF (PLC_PRG.calls[7].gpio = TRUE) THEN // edge detection on gpio
IF (counter = 0) THEN // counter for the first value
floorstorage2[0] := PLC_PRG.calls[7].message.floorstore[7]; // save button value in the array to calculate the total
counter := 1;
ELSE
floorstorage2[1] := PLC_PRG.calls[7].message.floorstore[7]; // save button value in the array to calculate the total
counter := 0;
END_IF
END_IF
GlobalVar.floorstorage := concat(floorstorage2[0],floorstorage2[1]); // Total of value 1 and value 2 (1 + 2 = 12)
You need to implement edge detection. Here is a code template you can use:
// Generate Oneshot Signal
VAR_INPUT
SIGNAL : BOOL; // Input
END_VAR
VAR
LATCH_SIGNAL : BOOL; // Latch
END_VAR
VAR_TEMP
OS_P_SIGNAL : BOOL; // Oneshot - Rising edge detection
OS_N_SIGNAL : BOOL; // Oneshot - Falling edge detection
END_VAR
//Code - Rising edge detection
OS_P_SIGNAL := SIGNAL AND NOT LATCH_SIGNAL;
LATCH_SIGNAL := SIGNAL;
//Code - Falling edge detection
OS_N_SIGNAL := NOT SIGNAL AND NOT LATCH_SIGNAL;
LATCH_SIGNAL := NOT SIGNAL;
I do not see any edge detection here
IF (PLC_PRG.calls[5].gpio = TRUE) THEN // edge detection on gpio
IF (counter = 0) THEN // counter for the first value
floorstorage2[0] := PLC_PRG.calls[5].message.floorstore[5]; // save button value in the array to calculate the total
counter := 1;
ELSE
floorstorage2[1] := PLC_PRG.calls[5].message.floorstore[5]; // save button value in the array to calculate the total
counter := 0;
END_IF
END_IF
You check if the button is pressed. It means while you are holding this button this condition is true. It means that counter change it's state on every PLC cycle while you are holding the button. It means that if when you release button PLC made even number of cycles nothing will change and if odd number f cycles it will change. Here is how you detect a rising edge.
VAR
xSignal, xSignalM: BOOL;
END_VAR
IF xSignal AND NOT xSignalM THEN
// Raising edge is here
END_IF
xSignalM := xSignal;
This way condition will work only one PLC cycle and everything will be ok. So your code would look like this.
VAR
M1, M1, M3: BOOL;
END_VAR
IF (PLC_PRG.calls[5].gpio AND NOT M1) THEN // edge detection on gpio
IF (counter = 0) THEN // counter for the first value
floorstorage2[0] := PLC_PRG.calls[5].message.floorstore[5]; // save button value in the array to calculate the total
counter := 1;
ELSE
floorstorage2[1] := PLC_PRG.calls[5].message.floorstore[5]; // save button value in the array to calculate the total
counter := 0;
END_IF
END_IF
M1 = PLC_PRG.calls[5].gpio;
IF (PLC_PRG.calls[6].gpio AND NOT M2) THEN // edge detection on gpio
IF (counter = 0) THEN // counter for the first value
floorstorage2[0] := PLC_PRG.calls[6].message.floorstore[6]; // save button value in the array to calculate the total
counter := 1;
ELSE
floorstorage2[1] := PLC_PRG.calls[6].message.floorstore[6]; // save button value in the array to calculate the total
counter := 0;
END_IF
END_IF
M2 = PLC_PRG.calls[6].gpio;
IF (PLC_PRG.calls[7].gpio AND NOT M3) THEN // edge detection on gpio
IF (counter = 0) THEN // counter for the first value
floorstorage2[0] := PLC_PRG.calls[7].message.floorstore[7]; // save button value in the array to calculate the total
counter := 1;
ELSE
floorstorage2[1] := PLC_PRG.calls[7].message.floorstore[7]; // save button value in the array to calculate the total
counter := 0;
END_IF
END_IF
M3 = PLC_PRG.calls[7].gpio;
Or you can use R_TRIG
VAR
RT1:R_TRIG;
END_VAR
R1(CLK := PLC_PRG.calls[5].gpio);
IF (R1.Q) THEN
IF (counter = 0) THEN
floorstorage2[0] := PLC_PRG.calls[5].message.floorstore[5];
counter := 1;
ELSE
floorstorage2[1] := PLC_PRG.calls[5].message.floorstore[5];
counter := 0;
END_IF
END_IF
I would implement the whole logic in a more object oriented way.
First, we define a Button.
Declaration part:
FUNCTION_BLOCK FB_Button
VAR
bSignal AT%I* : BOOL;
IButtonHandler : I_ButtonHandler;
fbPushTimer : TON;
fbTrig : R_TRIG;
sValue : STRING;
END_VAR
Implementation part:
IF IButtonHandler = 0
THEN
RETURN;
END_IF
fbPushTimer(IN:= bSignal, PT:=T#50MS);
fbTrig(CLK:=fbPushTimer.Q);
IF fbTrig.Q
THEN
IButtonHandler.onPush(sValue);
END_IF
The button has four properties:
1st Property:
Declaration part:
PROPERTY getValue : String
Implementation part:
getValue := sValue;
2nd Property:
Declaration part:
PROPERTY isPushed : BOOL
Implementation part:
isPushed := bSignal;
3rd Property:
Declaration part:
PROPERTY setPushHandler : I_ButtonHandler
Implementation part:
IButtonHandler := setPushHandler;
4th Property:
Declaration part:
PROPERTY setValue : String
Implementation part:
sValue := setValue;
Then we define the interface.
INTERFACE I_ButtonHandler
and add the interface method:
METHOD onPush
VAR_INPUT
sValue : STRING;
END_VAR
At last we define the handler.
Declaration part:
FUNCTION_BLOCK FB_ButtonHandler IMPLEMENTS I_ButtonHandler
VAR_OUTPUT
floorstorage : STRING;
END_VAR
The handler has two methods:
1st Method:
Declaration part:
METHOD onPush
VAR_INPUT
sValue : STRING;
END_VAR
Implementaion part:
floorstorage := concat(floorstorage,sValue);
2nd Method:
Declaration part:
METHOD reset
Implementation part:
floorstorage := '';
Now we need to init the buttons and call them in main.
Main declaration part:
PROGRAM MAIN
VAR
aButtons : ARRAY[1..10] OF FB_Button;
fbButtonHandler : FB_ButtonHandler;
i : UINT;
bInit : BOOL;
END_VAR
Implementation part:
IF NOT bInit
THEN
FOR i := 1 TO 10 DO
aButtons[i].setPushHandler := fbButtonHandler;
aButtons[i].setValue := UINT_TO_STRING(i);
END_FOR
bInit := TRUE;
END_IF
FOR i := 1 TO 10 DO
aButtons[i]();
END_FOR
You can choose your own button value depending on the button.
For simplicity, I assigned the loop index as the value of the button.
Each time the button is pushed, the method onPush gets called only once after 50ms.
When you want to access the value of floorstorage you simply call fbButtonHandler.floorstorage to assign it to another var.
By doing so you reach a stronger encapsulation and data protection for your variable than declaring it as global.

Optimize a perfect number check to O(sqrt(n))

Part of the program I have checks if an input number is a perfect number. We're supposed to find a solution that runs in O(sqrt(n)). The rest of my program runs in constant time, but this function is holding me back.
function Perfect(x: integer): boolean;
var
i: integer;
sum: integer=0;
begin
for i := 1 to x-1 do
if (x mod i = 0) then
sum := sum + i;
if sum = x then
exit(true)
else
exit(false);
end;
This runs in O(n) time, and I need to cut it down to O(sqrt(n)) time.
These are the options I've come up with:
(1) Find a way to make the for loop go from 1 to sqrt(x)...
(2) Find a way to check for a perfect number that doesn't use a for loop...
Any suggestions? I appreciate any hints, tips, instruction, etc. :)
You need to iterate the cycle not for i := 1 to x-1 but for i := 2 to trunc(sqrt(x)).
The highest integer divisor is x but we do not take it in into account when looking for perfect numbers. We increment sum by 1 instead (or initialize it with 1 - not 0).
The code if (x mod i = 0) then sum := sum + i; for this purpose can be converted to:
if (x mod i = 0) then
begin
sum := sum + i;
sum := sum + (x div i);
end;
And so we get the following code:
function Perfect(x: integer): boolean;
var
i: integer;
sum: integer = 1;
sqrtx: integer;
begin
sqrtx := trunc(sqrt(x));
i := 2;
while i <= sqrtx do
begin
if (x mod i = 0) then
begin
sum := sum + i;
sum := sum + (x div i) // you can also compare i and x div i
//to avoid adding the same number twice
//for example when x = 4 both 2 and 4 div 2 will be added
end;
inc(i);
end;
if sum = x then
exit(true)
else
exit(false);
end;

How to find the second largest value with Pascal

I have a Problem to Show the second largest value.
Here is the Code
program testeFeldZweitMax (input, output);
{ testet die Funktion FeldZweitMax }
const
FELDGROESSE = 10;
type
tIndex = 1..FELDGROESSE;
tFeld = array [tIndex] of integer;
var
Feld : tFeld;
i : integer;
function FeldZweitMax (var inFeld : tFeld) : integer;
var
Maximum: integer;
j : tIndex;
begin
Maximum := inFeld[1];
for j := 2 to FELDGROESSE do
if inFeld[j] > Maximum then
Maximum := inFeld[j];
FeldZweitMax := Maximum
end;
begin { Testprogramm }
writeln('Bitte geben Sie ', FELDGROESSE, ' Zahlen ein:');
for i := 1 to FELDGROESSE do
read (Feld [i]);
writeln('Die zweitgroesste Zahl ist ', FeldZweitMax (Feld), '.');
end. { testeFeldZweitMax }
As you can see the Code Show me only the largest value. I Need some help with showing the second largest value.
var
Maximum, ZweitMax: integer;
j : tIndex;
begin
Maximum := inFeld[1];
ZweitMax := inFeld[2];
for j := 1 to FELDGROESSE do
begin
if inFeld[j] < Maximum then
inFeld[j] := Maximum;
Maximum := ZweitMax;
ZweitMax := inFeld[j];
FeldZweitMax := ZweitMax
end
end;
It doesn't work perfectly. Some suggestions for me?
Consider that you have (at some point) the values Maximum > ZweitMax (f.ex. 5 and 2 respectively).
The next value (x) to evaluate might be
a) x > Maximum
b) x > ZweitMax (but less than Maximum)
c) x < ZweitMax
In case a) Maximum should become x and ZweitMax should become previous Maximum
In case b) Maximum should remain and ZweitMax should become x
In case c) no change to Maximum and ZweitMax (IOW, no action required)
A couple of hints:
Initialize both Maximum and ZweitMax to the smallest possible value (according to the type) before you start to evaluate the actual inputted values.
In case a) set ZweitMax to previous Maximum before assigning the new value to Maximum.

How to write the Max Heap code without recursion

I have written the MAX-HEAPIFY(A,i) method from the introduction to algorithms book. Now I want to write it without recursion using while loop. Can you help me please?
You can use while loop with condition your i <= HEAPSIZE and using all other same conditions , except when you find the right position just break the loop.
Code:-
while ( i < = heapsize) {
le <- left(i)
ri <- right(i)
if (le<=heapsize) and (A[le]>A[i])
largest <- le
else
largest <- i
if (ri<=heapsize) and (A[ri]>A[largest])
largest <- ri
if (largest != i)
{
exchange A[i] <-> A[largest]
i <- largest
}
else
break
}
The solution above works but I think that following code is closer to the recursive version
(* Code TP compatible *)
const maxDim = 1000;
type TElem = integer;
TArray = array[1..maxDim]of TElem
procedure heapify(var A:TArray;i,heapsize:integer);
var l,r,largest,save:integer;
temp:TElem;
(*i - index of node that violates heap property
l - index of left child of node with index i
r - index of right child of node with index i
largest - index of largest element of the triplet (i,l,r)
save - auxiliary variable to save the value of i
temp - auxiliary variable used for swapping *)
begin
repeat
l:=2*i;
r:=2*i + 1;
if(l <= heapsize) and (A[l] > A[i]) then
largest:=l
else
largest:=i;
if(r <= heapsize) and (A[r] > A[largest]) then
largest:=r;
(*Now we save the value i to check properly the termination
condition of repeat until loop
The value of i will be modified soon in the if statement *)
save:=i;
if largest <> i then
begin
temp:=A[i];
A[i]:=A[largest];
A[largest]:=temp;
i:=largest;
end;
until largest = save;
(*Why i used repeat until istead of while ?
because body of the called procedure will be executed
at least once *)
end;
One more thing, in Wirth's Algorithms + Data Structures = Programs
can be found sift procedure without recursion but we should introduce boolean variable or break to eliminate goto statement

Resources