How can i declare or set an auto value changeable boolean variable in twincat 3 - twincat

I am new to TwinCAT programming. I want to set 4 boolean variables in TwinnCAT 3 and they will change value automatically after 150 milliseconds using structured text. How can I do that?

If you want to change values just for once after execute the timer, basicly you can use like this;
PROGRAM MAIN
VAR
bExecute : BOOL;
bVar1 : BOOL;
bVar2 : BOOL;
bVar3 : BOOL;
bVar4 : BOOL;
Timer1 : TON;
END_VAR
Define variables as above,
write your code as below;
Timer1(IN:= bExecute, PT:=T#150MS);
IF Timer1.Q THEN
bVar1 := NOT bVar1;
bVar2 := NOT bVar2;
bVar3 := NOT bVar3;
bVar4 := NOT bVar4;
bExecute := FALSE; // After executing, if you want to make false the bExecute variable and make Timer.IN false.
END_IF
For more information about Timers please visit here

Related

How to throw an exception in TwinCAT

When using object-oriented programming aspects with Beckhoff TwinCAT 3 I recently tried to implement some Assert-like function which should throw an exception in case a guard clause evaluates to false.
Something like this:
FUNCTION GuardI
VAR_INPUT
Condition : BOOL;
Format : T_MaxString;
Value : INT;
END_VAR
IF (NOT Condition) THEN
ADSLOGDINT(
msgCtrlMask := ADSLOG_MSGTYPE_ERROR OR ADSLOG_MSGTYPE_LOG,
msgFmtStr := Format,
dintArg := Value);
// throw exception here
END_IF
So - how would I have it throw an exception?
I don't think it is possible to throw an exception from a quick look at the documentation. Note that exception handling (__TRY, __CATCH) only works for TwinCAT >= 4024.0 and for 32-bit systems.
You can do this like:
Create a Function called ASSERT
FUNCTION ASSERT
VAR_INPUT
E: BOOL;
END_VAR
VAR
EXCEPTION_THROWN, PURR: INT;
END_VAR
// body
IF NOT E THEN
EXCEPTION_THROWN := 0 / PURR;
END_IF
END_FUNCTION
Once you do this it will throw an exception guaranteed by Beckhoff because of the division by zero you would invoke this function like this (be sure that your are within the scope of that function via namespace or library reference):
PROGRAM MAIN
VAR
MEOW: INT; // zero by default
CONDITION: BOOL; // false by default
END_VAR
// body
IF MEOW = 1 THEN
ASSERT(FALSE);
END_IF
// ...
ASSERT(CONDITION); // key off your condition true or false
END_PROGRAM
Once you set "MEOW" to "1" or your condition evaluates to "FALSE" This will produce a Core Dump and you can load this core dump and review the call stack.
Hope this helps.

Twincat 3: Giving names to array bits

Beginner in the whole PLC stuff, so corrections are welcome.
I am trying to tidy up my project and current situation is thus:
I receive 16 byte arrays from modbus. These act as buttons, lights, conveyors what have you in Factory IO.
GAB_FactoryIO_Inputs AT %I* : ARRAY [0..15] OF BYTE;
GAB_FactoryIO_Outputs AT %Q* : ARRAY [0..15] OF BYTE;
So instead of referring to "Start button" with its bit "IO.GAB_FactoryIO_Inputs[0].0" I made a clumsy conversion POU and GVL to go through each bit and give them a new name. So it currently it looks like 200 lines of this:
IO.iSensor10_Capa := IO.GAB_FactoryIO_Inputs[7].3;
IO.iSensor9_Capa := IO.GAB_FactoryIO_Inputs[7].4;
IO.iPositioner_Limit := IO.GAB_FactoryIO_Inputs[7].5;
IO.iPositioner_Clamped := IO.GAB_FactoryIO_Inputs[7].6;
IO.iPick2_Detected := IO.GAB_FactoryIO_Inputs[7].7;
IO.iPick2_MovX := IO.GAB_FactoryIO_Inputs[8].0;
IO.iPick2_MovZ := IO.GAB_FactoryIO_Inputs[8].1;
IO.iPick2_Rot := IO.GAB_FactoryIO_Inputs[8].2;
IO.iPick2_GripRot := IO.GAB_FactoryIO_Inputs[8].3;
And this
iPositioner_Limit : BOOL;
iPositioner_Clamped : BOOL;
iPick2_Detected : BOOL;
iPick2_MovX : BOOL;
iPick2_MovZ : BOOL;
iPick2_Rot : BOOL;
iPick2_GripRot : BOOL;
It all works as it should, but I cant help to feel its amateurish, unwieldy and slows things down.
I've read about structures, enumeration and alias, and thought structures would be my savior by handily arranging them inside "cabinets", like so:
stCNC.Button1
stCNC.Button3
stCNC.Sensor1
And hidden inside structures would be the conversion between stCNC Sensor1 = IO.GAB_FactoryIO_Inputs[9].4;
But for some reason that doesnt work at all.
I'm most likely going at it plain wrong angle, but have no idea what to look for next.
EDIT work in progress thus far. Seems like I got the hang of the basics.
#kolyur had simple enough example to follow, so I started from there, and progressed towards #Steve and #YAVA examples:
//sending inputs to GVL FactoryIO_Inputs AT %I* : ARRAY [0..15] OF BYTE;
fbMBReadInputs(pDestAddr := ADR(IO.FactoryIO_Inputs),
//in GVL IO
FactoryIO_Inputs AT %I* : U_UNION2;
//inside S_LIGHTS:
TYPE S_LIGHTS :
STRUCT
LIGHT0 : BIT;
LIGHT1 : BIT;
LIGHT2 : BIT;
LIGHT3 : BIT;
LIGHT4 : BIT;
LIGHT5 : BIT;
LIGHT6 : BIT;
LIGHT7 : BIT;
END_STRUCT
END_TYPE
//inside U_UNION1
TYPE U_UNION1 :
UNION
nArray : ARRAY[0..15] OF BYTE;
sName : S_NAME;
//Then instantiating in POU
VAR
sLights : S_LIGHTS;
---
sLights.LIGHT1 := TRUE;
You can try to use a UNION.
It is basically an "overlay" variable you can place over another variable.
TYPE Test :
UNION
nARRAY : ARRAY[0..15] OF BYTE;
sHumanReadable : sStruct;
END_UNION
END_TYPE
UNION on Infosys
A structure could help if you make use of the BIT datatype. BITs address individual bits (unlike BOOLs which require a whole byte) but they can only be used within structures.
TYPE Test
STRUCT
button1 : BIT;
button2 : BIT;
button3 : BIT;
button4 : BIT;
sensor1 : BIT;
sensor2 : BIT;
sensor3 : BIT;
sensor4 : BIT;
END_STRUCT
END_TYPE
This structure occupies one byte and you could make an array of them to potentially use in your Modbus routine instead of the byte arrays.
Instead of first linking each IO to a global variable list and then link them to a function block, you could link the symbols directly to an instance of a function block.
For example you make the following function block and put the hardware inputs in the VAR section or you could put them in VAR_INPUT. You could also collect the inputs into a struct as the others suggested and use this struct in the function block.
FUNCTION_BLOCK Picker
VAR
MoveX AT %I : BOOL;
MoveY AT %I : BOOL;
Rotate AT %I : BOOL;
GripRotate AT %I : BOOL;
END_VAR
Then in your program you make an instance of the picker
PROGRAM MAIN
VAR
picker1 : Picker;
END_VAR
And then from you system configuration you can link each terminal input directly to the VAR's inside picker1.

How to make inputs of a function block method optional?

When calling a method of a function block, is it possible to make certain input variables optional? If I call fbA.methA() without assignments for all input variables, TwinCAT throws an error: "Function methA requires exactly 'x' inputs." There are times when some inputs are unnecessary or irrelevant, but so far I've had to assign dummy values to those inputs to get the code to compile.
I don't think that that is possible. You could make extra methods which all call a base method.
For example:
FUNCTION_BLOCK Multiplier
METHOD Multiply : REAL
VAR_INPUT
number1 : REAL;
number2 : REAL;
END_VAR
METHOD MultiplyByTwo : REAL
VAR_INPUT
number : REAL;
END_VAR
MultiplyByTwo := Multiply(2, number);
That way you also reduce the number of inputs of your method, thereby making it easier to test and use.
You also could screen the parameters as they are passed in (still requires parameters but they have no meaning aka always pass "0").
FUNCTION_BLOCK CAT
METHOD DECIBELS: REAL
VAR_INPUT
MEOW, PURR: BOOL;
END_VAR
// body
DECIBELS := 0.0;
IF MEOW <> 0
DECIBELS := DECIBELS + 10.0;
END_IF;
IF PURR <> 0
DECIBELS := DECIBELS + 5.0;
END_IF;
END_METHOD
END_FUNCTION_BLOCK
you can invoke this like:
PROGRAM MAIN
VAR
C: CAT;
RESULT: ARRAY [1..4] OF REAL;
END_VAR
// body
RESULT[1] := C.DECIBELS(TRUE, TRUE); // will return 15.0
RESULT[2] := C.DECIBELS(TRUE, 0); // will return 10.0
RESULT[3] := C.DECIBELS(0, TRUE); // will return 5.0
RESULT[4] := C.DECIBELS(0, 0); // will return 0.0
END_PROGRAM
Hope this helps

How do I use PathCombine() from WinAPI in Pascal Script/Inno Setup?

I'm trying to figure out how to use WinAPI functions from Pascal Script/Inno Setup. I didn't find much code examples how to do it and I'm not a Pascal programmer. Here's what I did so far:
Importing the function
function PathCombine (
pszPathOut : PChar;
pszPathIn : PChar;
pszMore : PChar
) : PChar;
external 'PathCombineA#Shlwapi.dll stdcall';
and using it like this:
function InitializeSetup(): Boolean;
var
a, b,c : PChar;
s : string;
begin
SetLength(s, 256); { soon it gets working I'll switch to use MAX_PATH instead of }
a := 'C:';
b := 'one\two';
c := PathCombine(s, a, b);
MsgBox(s, mbInformation, MB_OK);
end;
The output is this:
The expected output is:
C:\one\two
I'm pretty sure I'm accessing garbage values in memory but I don't know why, how do I fix this?
You didn't specify if you are using Ansi or Unicode version of Inno Setup.
But this should work in either version:
function PathCombine(
pszPathOut : PAnsiChar;
pszPathIn : PAnsiChar;
pszMore : PAnsiChar
) : PAnsiChar; external 'PathCombineA#Shlwapi.dll stdcall';
function InitializeSetup(): Boolean;
var
a, b, c: AnsiString;
begin
SetLength(c, 256); { soon it gets working I'll switch to use MAX_PATH instead of }
a := 'C:';
b := 'one\two';
PathCombine(c, a, b);
MsgBox(c, mbInformation, MB_OK);
Result := True;
end;
Though I strongly encourage you to use Unicode version of Inno Setup and PathCombineW instead.
function PathCombine(
pszPathOut : string;
pszPathIn : string;
pszMore : string
) : Cardinal; external 'PathCombineW#Shlwapi.dll stdcall';
function InitializeSetup(): Boolean;
var
a, b, c: string;
begin
SetLength(c, 256); { soon it gets working I'll switch to use MAX_PATH instead of }
a := 'C:';
b := 'one\two';
PathCombine(c, a, b);
MsgBox(c, mbInformation, MB_OK);
Result := True;
end;
Note that Inno Setup lacks PWideChar type. While it can marshal string to LPTSTR (PWideChar) function arguments, it cannot marshal LPTSTR return value. So I've used Cardinal for return type. It has the same size as pointer (to char), so a stack will match. And we do not actually need the returned value.
I think (although I haven't worked with Pascal/Delphi for a while) that the problem is that C "strings" (char *) are 0 index based, while Pascal strings are 1 index based (byte 0 is used to store the length).
So, if you declare your s variable as:
s: array[0..255] of Char; //Don't forget to change it to MAX_PATH afterwards
it should work. Also use the PathCombine function like this:
PathCombine(s, a, b);
There's no need to assign its result (which is the same as s) to another variable (that you aren't going to use anyway).

How do you associate an object to a TGridColumns object

I am running Lazarus 0.9.30.
I have a standard TStringGrid on a form and have a function that dynamically adds TGridColumns objects to it at run time. I have a collection of objects that contain all the attributes of each column (that I read out of a file at run time), and I want to associate each object with its corresponding column header.
I have tried the code below but at run time when I try to access the object behind the column header object, I get a 'nil object returned. I suspect the reason this is occurring is that the grid cell (that holds the column title) is blank, and you can't associate objects with grid cells that are empty.
type
TTmColumnTitles = class(TTmCollection)
public
constructor Create;
destructor Destroy; override;
function stGetHint(anIndex : integer) : string;
end;
type
TTmColumnTitle = class(TTmObject)
private
FCaption : string;
FCellWidth : integer;
FCellHeight : integer;
FFontOrientation : integer;
FLayout : TTextLayout;
FAlignment : TAlignment;
FHint : string;
procedure vInitialise;
public
property stCaption : string read FCaption write FCaption;
property iCellWidth : integer read FCellWidth write FCellWidth;
property iCellHeight : integer read FCellHeight write FCellHeight;
property iFontOrientation : integer read FFontOrientation write FFontOrientation;
property Layout : TTextLayout read FLayout write FLayout;
property Alignment : TAlignment read FAlignment write FAlignment;
property stHint : string read FHint write FHint;
constructor Create;
destructor Destroy; override;
end;
procedure TTmMainForm.vLoadGridColumnTitles
(
aGrid : TStringGrid;
aCollection : TTmColumnTitles
);
var
GridColumn : TGridColumn;
aColumnTitle : TTmColumnTitle; //Just a pointer!
anIndex1 : integer;
anIndex2 : integer;
begin
for anIndex1 := 0 to aCollection.Count - 1 do
begin
aColumnTitle := TTmColumnTitle(aCollection.Items[anIndex1]);
GridColumn := aGrid.Columns.Add;
GridColumn.Width := aColumnTitle.iCellWidth;
GridColumn.Title.Font.Orientation := aColumnTitle.iFontOrientation;
GridColumn.Title.Layout := aColumnTitle.Layout;
GridColumn.Title.Alignment := aColumnTitle.Alignment;
GridColumn.Title.Caption := aColumnTitle.stCaption;
aGrid.RowHeights[0] := aColumnTitle.iCellHeight;
aGrid.Objects[anIndex1, 0] := aColumnTitle;
end; {for}
end;
Just assigning an object to the Objects property isn't enough. You have to draw the title caption from that object yourself in an OnDrawCell event handler, or assign the Cells property as well.
and you can't associate objects with grid cells that are empty
Yes you can. The string and the object of one cell 'work' independent of each other.
So it should be:
for anIndex2 := 0 to aGrid.ColCount - 1 do
begin
aColumnTitle := aCollection.Items[anIndex2]; // Is aCollection.Count in sync
// with aGrid.ColCount??
aGrid.Cells[anIndex2, 0] := aColumnTitle.Caption;
aGrid.Objects[anIndex2, 0] := aColumnTitle;
end;

Resources