flowing lights in structured text - plc

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].

Related

Converting LREAL to binary and interpreting it as base 10 LINT

Running "CODESYS V3.5 SP16" here, I am trying to implement the hashcode algorithm mentioned in
Best implementation for hashCode method for a collection
and had to built my own solution to replicate Java's Float.floatToIntBits(f) which resulted in the following function
FUNCTION F_lrealToLintBits : LINT
VAR_INPUT
lrVal : LREAL;
END_VAR
VAR
arrBytes : ARRAY[0..7] OF BYTE; // LREAL contains 64 bits and each byte contains 8 bits
pVal : POINTER TO LREAL := ADR(arrBytes);
diIndx : DINT;
uiExpt : UINT := 0; // exponent goes from 0 to 63
END_VAR
pVal^ := lrVal; // maps LREAL to array of bytes
// little endian? cause it seems that least significant bit is at lowest address
FOR diIndx := LOWER_BOUND(arrBytes, 1) TO UPPER_BOUND(arrBytes, 1) BY 1 DO
// bit access seems to be manual only so no loops
IF arrBytes[diIndx].0 THEN
F_lrealToLintBits := F_lrealToLintBits + LREAL_TO_LINT(EXPT(2, uiExpt));
END_IF
uiExpt := uiExpt + 1; // have to increment exponent after every bit
IF arrBytes[diIndx].1 THEN
F_lrealToLintBits := F_lrealToLintBits + LREAL_TO_LINT(EXPT(2, uiExpt));
END_IF
uiExpt := uiExpt + 1; // have to increment exponent after every bit
IF arrBytes[diIndx].2 THEN
F_lrealToLintBits := F_lrealToLintBits + LREAL_TO_LINT(EXPT(2, uiExpt));
END_IF
uiExpt := uiExpt + 1; // have to increment exponent after every bit
IF arrBytes[diIndx].3 THEN
F_lrealToLintBits := F_lrealToLintBits + LREAL_TO_LINT(EXPT(2, uiExpt));
END_IF
uiExpt := uiExpt + 1; // have to increment exponent after every bit
IF arrBytes[diIndx].4 THEN
F_lrealToLintBits := F_lrealToLintBits + LREAL_TO_LINT(EXPT(2, uiExpt));
END_IF
uiExpt := uiExpt + 1; // have to increment exponent after every bit
IF arrBytes[diIndx].5 THEN
F_lrealToLintBits := F_lrealToLintBits + LREAL_TO_LINT(EXPT(2, uiExpt));
END_IF
uiExpt := uiExpt + 1; // have to increment exponent after every bit
IF arrBytes[diIndx].6 THEN
F_lrealToLintBits := F_lrealToLintBits + LREAL_TO_LINT(EXPT(2, uiExpt));
END_IF
uiExpt := uiExpt + 1; // have to increment exponent after every bit
IF arrBytes[diIndx].7 THEN
F_lrealToLintBits := F_lrealToLintBits + LREAL_TO_LINT(EXPT(2, uiExpt));
END_IF
uiExpt := uiExpt + 1; // have to increment exponent after every bit
END_FOR
Firstly, I would like to know if there is an existing function in some library that does this already?
I looked in many places including in the OSCAT "Basic, 3.31" library but could not find anything similar (even a set of functions that I could chain together would be OK).
Secondly, can bit access only be done manually?
I would prefer to use a loop but it seems that is not possible? Is there a less copy-and-paste method of accessing the bits that would involve automatically detecting the number of bits if the data type in the array changes from BYTE to something else (e.g. DWORD)?
Potential alternative
Seems like this is where unions come in handy, as mentioned in https://forge.codesys.com/forge/talk/Engineering/thread/02a65a50b2/#e5f4/1911/c524 where if the value does not come from an external source (i.e. no need to check for endian-ness), Float.floatToIntBits(f) would be as simple as
TYPE
U_lrealToRawLintBits :
UNION
lrVal : LREAL;
liVal : LINT;
END_UNION
END_TYPE
// supposed to replicate floatToIntBits() in Java at
// https://github.com/openjdk/jdk/blob/739769c8fc4b496f08a92225a12d07414537b6c0/src/java.base/share/classes/java/lang/Float.java#L775
FUNCTION F_lrealToLintBits : LINT
VAR_INPUT
lrVal : LREAL;
END_VAR
VAR
uLrealToLintBits : U_lrealToRawLintBits; // for interpreting LREAL as base 10 LINT
END_VAR
IF FPU.IsLRealPosInfinity(lrVal) THEN
F_lrealToLintBits := 2139095040; // corresponds to 0x7f800000
ELSIF FPU.IsLRealNegInfinity(lrVal) THEN
F_lrealToLintBits := 4286578688; // corresponds to 0xff800000
ELSIF FPU.IsLRealNaN(lrVal) THEN
F_lrealToLintBits := 2143289344; // corresponds to 0x7fc00000
ELSE
uLrealToLintBits.lrVal := lrVal;
F_lrealToLintBits := uLrealToLintBits.liVal;
END_IF
I think what you are trying to achieve is to take as input an LREAL but with the bits inside really being those of a LINT, but byte-swapped.
If so, there should be a more straightforward solution. Here is an example :
FUNCTION lreal_to_int64 : LINT
VAR_INPUT
value: LREAL;
value_is_little_endian: BOOL;
END_VAR
VAR
no_swap: BOOL;
source_bytes, target_bytes: POINTER TO BYTE;
value_as_lint: POINTER TO LINT;
END_VAR
{IF defined (IsLittleEndian)}
no_swap := value_is_little_endian;
{ELSE}
no_swap := NOT value_is_little_endian;
{END_IF}
IF
no_swap
THEN
value_as_lint := ADR(value);
lreal_to_int64 := value_as_lint^;
RETURN;
END_IF
target_bytes := ADR(lreal_to_int64);
source_bytes := ADR(value);
target_bytes[0] := source_bytes[7];
target_bytes[1] := source_bytes[6];
target_bytes[2] := source_bytes[5];
target_bytes[3] := source_bytes[4];
target_bytes[4] := source_bytes[3];
target_bytes[5] := source_bytes[2];
target_bytes[6] := source_bytes[1];
target_bytes[7] := source_bytes[0];
Programmatic access to bits is certainly possible using bitwise operators. Try something like this.
FUNCTION get_bit : BOOL
VAR_INPUT
value: LWORD;
bit_position: USINT;
END_VAR
VAR
shifted: LWORD;
END_VAR
shifted := SHR(value, bit_position);
get_bit := shifted.0;

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

Can't obtain solution for the variable gamma1, what's wrong?

I want to use the following code
f := sqrt(7200*8200)*10^6:
Ig := 0, 9330; Ug := 13.134:
f_gr := 10^10:
r_e := .8:
L_e := .5*10^(-9):
C_e := 2.5*10^(-12):
Cka := .4*10^(-12):
Ckp := .8*10^(-12):
L_b := .3*10^(-9):
I_0 := I*f*Ig*(cos(.4*f/f_gr)+I*sin(.4*f/f_gr))/(f_gr*gamma1):
I_e := I_0+Ig:
U_e := I_e*(r_e+I*(2*Pi*f*L_e)):
U_p := -I*(1-gamma1)*I_0/(2*Pi*f*C_e):
Uska := Ug+U_p:
Iska := I*(2*Pi*f*Cka*Uska):
Irb := I_0+Iska:
Urb := 2.8*Irb:
Uskp := Urb+Uska:
Iskp := I*(2*Pi*f*Ckp*Uskp):
Irk := Uskp/773.5:
Ib := Irb+Iskp+Irk:
Ulb := I*(2*Pi*f*L_b*Ib):
Uv := U_e+Ulb+Urb+U_p:
Ik := Ig-Iska-Iskp-Irk:
Uk := Ug-U_e:
to solve Re(Uk)*Re(Ik)+Im(Uk)*Im(Ik)=0.186 for gamma1 (I is for imaginary unit). But this doesn't work. I've tried to use brute force, assigning some numbers to gamma1 and then finding Re(Uk)*Re(Ik)+Im(Uk)*Im(Ik), but I can't obtain a number anyway. Could you help me?
Your code has an apparent mistake in the definition of Ig, to which you have assigned the expression sequence 0, 9330.
Perhaps you intended Ig:=0.9330 instead.
restart;
f := sqrt(7200*8200)*10^6:
Ig := 0.9330:
Ug := 13.134:
f_gr := 10^10:
r_e := .8:
L_e := .5*10^(-9):
C_e := 2.5*10^(-12):
Cka := .4*10^(-12):
Ckp := .8*10^(-12):
L_b := .3*10^(-9):
I_0 := I*f*Ig*(cos(.4*f/f_gr)+I*sin(.4*f/f_gr))/(f_gr*gamma1):
I_e := I_0+Ig:
U_e := I_e*(r_e+I*(2*Pi*f*L_e)):
U_p := -I*(1-gamma1)*I_0/(2*Pi*f*C_e):
Uska := Ug+U_p:
Iska := I*(2*Pi*f*Cka*Uska):
Irb := I_0+Iska:
Urb := 2.8*Irb:
Uskp := Urb+Uska:
Iskp := I*(2*Pi*f*Ckp*Uskp):
Irk := Uskp/773.5:
Ib := Irb+Iskp+Irk:
Ulb := I*(2*Pi*f*L_b*Ib):
Uv := U_e+Ulb+Urb+U_p:
Ik := Ig-Iska-Iskp-Irk:
Uk := Ug-U_e:
And now,
solve(Re(Uk)*Re(Ik)+Im(Uk)*Im(Ik)=0.186, {gamma1});
{gamma1 = -0.9296926790 - 0.6316639400 I}

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;

Delphi: Creating a TComboBox in a dynamically created form at runtime

Okay, I am working in a project that was originally done in D7. And I am doing double duty here as I am working on fixing bugs in the original code and attempting to port it over to XE3/4. Kinda hard when the original author used some none-open source kits for the project.
But anyways, the app is a scripting/macroing program. As part of the custome scripting/macroing language. There is a ability to create very simple basic forms for user input. The forms are created dynamically at runtime based on the script/macro the script/macro author has created. I have already fixed some bugs in the code for the creation of the forms. But, there is one that I just can not figure out.
When creating a TComboBox for the parent form and setting the Text property AT component creation. The text in the Text property is not displayed.
Here is the code to create the form:
procedure CreateForm(var wFrm: TForm; sName: String);
var
iLoop, iPos, iLen: Integer;
iFormHeight, iFormWidth: Integer;
lh, hresult1, hresult2: Integer;
sWork, sWork2, sLine, CmdName: String;
lstForm, lst: TStringList;
pnl: TPanel;
begin
iFormHeight := 80;
iFormWidth := 400;
hresult1 := 0;
lst := TStringList.Create;
iLoop := lstForms.IndexOf(Trim(UpperCase(sName)));
if iLoop < 0 then
begin
AbortError('Form "' + sName + '" could not be found!');
Exit;
end;
lstForm := TStringList(lstForms.Objects[iLoop]);
for iLoop := 0 to lstForm.Count - 1 do
begin
sLine := lstForm[iLoop];
iPos := Pos('=', sLine);
iLen := Length(sLine);
if iPos = 0 then
continue;
CmdName := Uppercase(Trim(Copy(sLine, 1, iPos - 1)));
sWork2 := Trim(Copy(sLine, iPos + 1, iLen));
if CmdName = 'FORMCAPTION' then
begin
with wfrm do
begin
Caption := Trim(Copy(sLine, iPos + 1, iLen));
Name := Trim(sName);
Height := iFormHeight;
Width := iFormWidth;
Tag := 10;
BorderStyle := bsSizeable;
BorderIcons := [biSystemMenu];
Position := poDesktopCenter;
pnl := TPanel.Create(wfrm);
with pnl do
begin
Parent := wfrm;
Caption := '';
Align := alBottom;
BevelInner := bvNone;
BevelOuter := bvNone;
Height := 30;
end;
with TButton.Create(wfrm) do
begin
Parent := pnl;
Caption := '&OK';
Default := True;
ModalResult := mrOK;
Left := 235;
Top := 0;
end;
with TButton.Create(wfrm) do
begin
Parent := pnl;
Caption := '&Cancel';
Cancel := True;
ModalResult := mrCancel;
Left := 310;
Top := 0;
end;
pnl := TPanel.Create(wfrm);
with pnl do
begin
Parent := wfrm;
Caption := '';
Align := alClient;
BevelInner := bvRaised;
BevelOuter := bvNone;
BorderWidth := 5;
end;
end;
end
else
begin
lst.Clear;
StringToList(sWork2, lst, ':');
if UpperCase(lst[0]) = 'EDITBOX' then
CreateEditBox
else if UpperCase(lst[0]) = 'CHECKBOX' then
CreateCheckBox
else if UpperCase(lst[0]) = 'COMBOBOX' then
CreateComboBox
else if UpperCase(lst[0]) = 'LABEL' then
CreateLabel;
end;
end;
with wfrm do
begin
if hresult1 > 1 then
hresult2 := 5
else
hresult2 := 9;
Tag := Tag + hresult2;
Height := Height + hresult2;
end;
lst.Free;
end;
And here is the specific code to create the TComboBox, w/ TLabel, for the form:
procedure CreateComboBox;
var
iPos: Integer;
begin
with TLabel.Create(wfrm) do
begin
Parent := pnl;
Caption := lst[1];
Left := 15;
if hresult1 > 1 then
hresult2 := 5 * hresult1
else
hresult2 := 3 * hresult1;
Top := wfrm.Tag + hresult2;
Name := 'lbl' + CmdName;
Width := 150;
WordWrap := True;
AutoSize := True;
lh := Height;
end;
hresult1 := Trunc(lh/13);
with TComboBox.Create(wfrm) do
begin
Parent := pnl;
Left := 170;
Width := 200;
if hresult1 > 1 then
hresult2 := 5 * hresult1
else
hresult2 := 3 * hresult1;
Top := wfrm.Tag + hresult2;
Style := csDropDownList;
Name := UpperCase(CmdName);
Text := 'Test Text';
sWork := lst[3];
lst.Clear;
StringToList(sWork, lst, ',');
for iPos := 0 to lst.Count - 1 do
lst[iPos] := lst[iPos];
Items.Assign(lst);
// ItemIndex := 0;
end;
wfrm.Tag := wfrm.Tag + ((hresult1 * 13)+ 13);
wfrm.Height := wfrm.Height + ((hresult1 * 13)+ 13);
TComboBox(wfrm
end;
NOTE: the above procedure is a child procedure of the CreateForm procedure.
The app uses TStringList lists to store the form definition at script/macro runtime. Then the above code retrieves that information to create to form when the author wants the form to be shown. And then creates the form and places the form object into another temporary TStringList list prior to being shown. This is done so that when the user runs the script/macro and enters the information/settings as requested in the form. The author may retrieve the requested information/settings from the form before the form is destroyed.
The form is deleted (if previously created) from tmp TStringList list, created, stored in tmp TStringList list, and shown modally with the following code:
iPos := lstForms.IndexOf(UpperCase(sWVar2));
if iPos < 0 then
begin
AbortError('Could not find form "' + Trim(sWVar2) + '" defined!');
Exit;
end;
iPos := lstFormsTMP.IndexOf(UpperCase(sWVar2));
if iPos > -1then
begin
TForm(lstFormsTMP.Objects[iPos]).Free;
lstFormsTMP.Delete(iPos);
frm.Free;
iPos := lstFormsTMP.IndexOf(UpperCase(sWVar2));
if iPos > -1 then
begin
AbortError('Form "' + Trim(sWVar2) + '" was not removed from the lstFormsTMP TStringList.');
Exit;
end;
end;
frm := TForm.Create(frmMain);
CreateForm(frm, sWVar2);
lstFormsTMP.AddObject(Uppercase(sWVar2), frm);
end;
iPos := lstFormsTMP.IndexOf(UpperCase(sWVar2));
if iPos < 0 then
begin
AbortError('Could not find form "' + Trim(sWVar2) + '" defined!');
Exit;
end;
hndHold := SwitchToHandle(frmMain.Handle);
try
Result := TForm(lstFormsTMP.Objects[iPos]).ShowModal = mrOK;
finally
SwitchToHandle(hndHold);
end;
With the above sets of code the form defined in the running script is created and shown, without to many bugs/errors. But, even though I have hardcoded the text for the TComboBox.Text property. It is not shown. Can anyone shed some lite on why this is the case for me? All other form components, TCheckBox, TEditBox, TLabel, are displayed without any issues, so far. It is just the TComboBox that is causing me to scratch my head in confusion.
NOTE: Eventually the TComboBox.Text property will be dynamically set based on the authors setting for that property in the form component's definition.
Thanks in advance.
EDITED 8/18/2013, to include the following:
The original code also includes the ability to save/load the form component's settings by way of the TIniFile object. The following code is used to save the setting for the TComboBox:
if frm.Components[i] is TCombobox then
iniWork.WriteString(frm.Name, TCombobox(frm.Components[i]).Name, TCombobox(frm.Components[i]).Text)
else
and the following to load the TComboBox setting:
if frm.Components[i] is TCombobox then
begin
TCombobox(frm.Components[i]).ItemIndex := TCombobox(frm.Components[i]).Items.IndexOf(
iniWork.ReadString(frm.Name, TCombobox(frm.Components[i]).Name, TCombobox(frm.Components[i]).Text));
end
With the above code it looks to me like the setting is being save from and loaded back into the TComboBox's Text property. Now when the TComboBox setting is loaded, the form is changed after it has been created and placed, as an object, into the tmp TStringList list and prior to being shown modally. Yet, when the form is shown the Text property, as set by the above load code above, is shown.
It is because of the above that I am confused. Why does it work at this point, after the form is created. Yet not when the form is created?
This is a drop down list because you set the style to csDropDownList. That means that the edit control of the combo box can only display items that are contained in its list control.
For a drop down list combo, setting the Text property has no effect. Instead of using the Text property, you should be specifying ItemIndex.