The third edition of IEC 61131-3 introduces the concept of namespaces. Namespaces group elements such as variables, function blocks, data types and libraries into coherent units. This means that elements are no longer identified solely using their names, but additionally using the associated namespace.
I first started looking at this issue in late 2010 in CoDeSys V3. The following examples have been created using TwinCAT 3 and illustrate the various areas in which namespaces have an effect.
Libraries
Identifiers for function blocks, functions and data types must be unambiguous. This applies equally to elements loaded into a project from a library. The use of multiple PLC libraries from different sources is a common cause of naming conflicts. To avoid this, library developers generally insert a prefix in front of each POU or data type identifier (e.g. MC_MoveAbsolute). The aim here is to minimise the likelihood of a naming conflict.
In TwinCAT 3 this problem is resolved by using namespaces. A namespace can be defined as part of the project information for each PLC project (and also therefore PLC libraries). Namespaces are not case sensitive.
Very often, a library’s namespace will simply be the name of the library (but note, however, that this is not the case in this example). If the namespace field is left blank, the namespace defaults to the library name (in the Title field).
If libraries containing elements with identical names are added to a PLC project, namespaces can be used to resolve this naming conflict.
Example:
Two PLC libraries both containing an FB_Foo component are added to a project. Any attempt to create an instance of this component will necessarily throw up an error message. For the compiler, it is not clear which of the identically-named components from the two libraries should be used.
Adding in the namespace for the relevant library allows this conflict to be resolved.
If no namespace is entered, the compiler will first search within the local PLC project. It is therefore possible to instance an FB_Foo POU contained within the current PLC project. In this case, whether or not a namespace has been entered in the project information panel is irrelevant.
PROGRAM MAIN VAR fbFoo1 : MyNamespace01.FB_Foo; fbFoo2 : MyNamespace02.FB_Foo; fbFoo3 : FB_Foo; END_VAR
init_namespace attribute
The init_namespace attribute is a useful tool. If a STRING or WSTRING type variable has this attribute set, the variable is initialised with the name of the current namespace.
FUNCTION_BLOCK PUBLIC FB_Foo VAR_INPUT END_VAR VAR_OUTPUT {attribute 'init_namespace'} sNamespace : STRING; END_VAR VAR END_VAR
This permits the relevant namespace to be determined at runtime:
Sample 1 (TwinCAT 3.1) on GitHub
Modifying namespaces
The namespace for a library is also shown in the reference properties:
Should a namespace be ambiguous, it can be modified in these reference properties.
This makes it possible for a project to load multiple versions of the same library.
Modifying the namespace enables the required elements from different library versions to be addressed directly.
PROGRAM MAIN VAR eAdsErr01 : Tc2_System_A.E_AdsErr; eAdsErr02 : Tc2_System_B.E_AdsErr; END_VAR
Although the E_AdsErr enum has the same structure in both PLC libraries, they are two different data types. In the above example, this means that the eAdsErr01 and eAdsErr02 variables are not mutually compatible.
eAdsErr01 := eAdsErr02;
This assignment is rejected by the compiler with the following error message:
Implicit conversion from one enumeration type (E_ADSERR
(tc2_system, 3.4.14.0 (beckhoff automation gmbh))) to
another (E_ADSERR (tc2_system, 3.4.13.0 (beckhoff automation
gmbh)))
Global Variable List
A Global Variable List (GVL) is used to declare global variables. The name of the GVL must be unambiguous within the project. The name is not case sensitive. Each GVL is a self-enclosed space (namespace) in which global variables can be declared independently of other GVLs.
In the following example, we declare three variables. All three are called var01. Two are declared in separate GVLs (GVL01 and GVL02), the third locally in MAIN.
To allow these variables to be addressed unambiguously, the name of the GVL must additionally be placed in front of the variable name, separated by a dot.
GVL01.var01 := 1; // global variable in GVL01 GVL02.var01 := 2; // global variable in GVL02 var01 := 3; // local variable in MAIN
If the variable var01 exists only in a single GVL, there is no need to prefix the variable name with the GVL. If, however, there is also a local variable with the same name, then the GVL name does have to be included. Alternatively, you can also prefix the variable name with just a dot on its own. This designates the global namespace.
.var01 := 1; // global variable var01 := 3; // local variable
The dot in front of the variable name indicates that we wish to access the global variable called var01. In this case, however, the variable must again be present in just a single GVL.
qualified_only attribute
The name of the GVL should always be used, even where it is not strictly required. If the qualified_only attribute is present in the GVL, an error message will be output if an attempt is made to address a variable without prefixing it with the GVL name.
{attribute 'qualified_only'} VAR_GLOBAL var01 : INT; var02 : INT; END_VAR
It is therefore always necessary to include the name of the GVL when addressing a global variable. This makes the program easier to read and makes it easier to avoid side effects between local and global variables.
Sample 2 (TwinCAT 3.1) on GitHub
GVLs in libraries
If a GVL is in a library, in addition to the GVL name it is also possible to use the namespace of the library.
In the following example, three variables, all called var01, are declared in different places:
To address the required variable, the library’s namespace can be used in addition to the GVL name:
MyNamespace01.GVL01.var01 := 1; // global variable of the GVL inside the library GVL01.var01 := 2; // global variable of the local GVL var01 := 3; // local variable
If the qualified_only attribute is not used in the local GVL, the GVL can be omitted:
MyNamespace01.GVL01.var01 := 1; // global variable of the GVL inside the library .var01 := 2; // global variable of the local GVL var01 := 3; // local variable
Similarly, the GVL name for the library can also be omitted if the qualified_only attribute is not used in that GVL.
MyNamespace01.var01 := 1; // global variable of the GVL inside the library .var01 := 2; // global variable of the local GVL var01 := 3; // local variable
If the GVL names are unambiguous, the namespace can also be omitted:
GVL01.var01 := 1; // global variable of the GVL of the library .var01 := 2; // global variable of the local GVL var01 := 3; // local variable
The final example illustrates the point that omitting namespaces reduces readability of the program. It is harder to identify where the referenced variables are declared. Consequently, it is best to include the qualified_only attribute in every GVL and to use the namespace when using elements from libraries.
The namespace should also be entered when defining function blocks derived from function blocks from a library and when using interfaces from a library:
FUNCTION_BLOCK FB_MyFoo EXTENDS MyNamespace01.FB_Foo IMPLEMENTS MyNamespace01.I_Foo VAR_INPUT END_VAR VAR_OUTPUT END_VAR VAR END_VAR
The dialog for creating new FBs supports this process:
Sample 3 (TwinCAT 3.1) on GitHub
Enumerations
When accessing an enumerator from an enumeration, the relevant enumerator can be used directly. To enable enumerated constants to be addressed unambiguously, the type name can also be added.
TYPE E_Foo01 : ( eNoError := 0, eErrorA := 1, eErrorB := 2 ); END_TYPE PROGRAM MAIN VAR eFoo01 : E_Foo01; END_VAR eFoo01 := eErrorA; eFoo01 := E_Foo01.eErrorA;
If the identifier for an enumerated constant is found in multiple type definitions, the type name must be used when addressing it. This allows constants with the same name to be used in multiple enumerations. The result is that it is no longer necessary to add a prefix to every enumerated constant to avoid naming conflicts.
TYPE E_Foo01 : ( eNoError := 0, eErrorA := 1, eErrorB := 2 ); END_TYPE TYPE E_Foo02 : ( eNoError := 0, eError1 := 1, eError2 := 2 ); END_TYPE PROGRAM MAIN VAR eFoo01 : E_Foo01; eFoo02 : E_Foo02; END_VAR eFoo01 := E_Foo01.eNoError; eFoo02 := E_Foo02.eNoError;
If the type definition is in a library, you can also additionally use the library namespace:
eFoo01 := MyNamespace01.E_Foo01.eNoError;
qualified_only attribute
Similarly to GVLs, the qualified_only attribute can be used to force the use of the type name:
{attribute 'qualified_only'} TYPE E_Foo01 : ( eNoError := 0, eErrorA := 1, eErrorB := 2 ); END_TYPE
Sample 4 (TwinCAT 3.1) on GitHub
strict attribute
The strict attribute is not directly relevant to namespaces, but can be used to improve program readability. We will therefore consider it briefly here.
A variable with an enumerated type is treated internally as an INT. Consequently, arithmetic operations can be carried out on enumerations and values can be assigned which are not defined by the enumeration. The following example assigns the variable eFoo01 the value 3. The enumeration E_Foo01 does not, however, include this value.
eFoo01 := E_Foo01.eErrorA + 2;
If the strict attribute is used in the definition of an enumerated type, variables of this type are only able to take values specified by the enumerators.
{attribute 'strict'} TYPE E_Foo01 : ( eNoError := 0, eErrorA := 1, eErrorB := 2 ); END_TYPE
The strict attribute also means that it is no longer possible to perform arithmetic operations which would result in values which are not specified by the enumerators. This affects the following operations:
Assignment of constants which are not specified by the enumerators
eFoo01 := 5;
Arithmetic operations with enumerators
eFoo01 := E_Foo01.eNoError + E_Foo01.eErrorA;
Assignment of variables which are not of the enumerated type
nVar := 1; // variable nVar is a INT eFoo01 := nVar;
Summary
Namespaces represent a useful mechanism for avoiding naming conflicts. They remove the need to add prefixes to function and data type identifiers. This makes element names more compact and makes code more readable.
Thx. Here are some other resources:
AllTwinCAT (Jakob Sagatowski): http://alltwincat.com
CODESYS-Blog (Matthias Gehring): https://www.codesys-blog.com
Contact and Coil (Scott Whitlock): http://www.contactandcoil.com
Got TwinCAT: https://gotwincat.blogspot.com
PLCCoder.com (Gerhard Barteling): https://www.plccoder.com
Red Rock Controls (Roger Christopher): https://redrockcontrolscouk.wordpress.com
Hi, thank you very much for this post !
I have created function blocks that work properly and are very useful to me. So I decided to create a library. I have a problem with a function block that uses a structure that must be adaptable to the project. I give you an example (this is not my real function block):
FUNCTION_BLOCK FB_xxx
VAR_INPUT
xTrigger : BOOL;
Empty : ST_xxx;
END_VAR
VAR_IN_OUT
aMemory : ARRAY [1..2] OF ST_xxx;
END_VAR
Inside the FB, I transfer aMemory[1] to aMemory[2], but the structure ST_xxx could be different dependid the project
If ST_xxx is declared in the library, iit is not possible to declare the variables inside the structure.
Can you help me ?
David
Hi David,
Take a look to the datatype T_Arg. This will help you to handle the pointer.
Use ARRAY OF T_Arg instead of ARRAY OF ST_xxx. With the F_ARGCPY you can copy the data from one array to the other.
FUNCTION_BLOCK FB_Foo
VAR_INPUT
END_VAR
VAR_IN_OUT
aMemory : ARRAY [1..2] OF T_Arg;
END_VAR
IF (aMemory[1].eType = E_ArgType.ARGTYPE_BIGTYPE) THEN
F_ARGCPY(FALSE, aMemory[2], aMemory[1]);
END_IF
Now it’s possible to call FB_Foo with different types of structures.
Example: Define two structures:
TYPE ST_A :
STRUCT
a : BYTE;
b : INT;
END_STRUCT
END_TYPE
TYPE ST_B :
STRUCT
c : REAL;
d : UDINT;
END_STRUCT
END_TYPE
Declare in MAIN the FB and an array of ST_A and an array of ST_B.
PROGRAM MAIN
VAR
fbFoo : FB_Foo();
aMemoryA : ARRAY [1..2] OF ST_A := [(a := 1, b := 300), (a := 0, b := 0)];
aMemoryB : ARRAY [1..2] OF ST_B := [(c := 1.23, d := 70000), (c := 0, d := 0)];
aMemory : ARRAY [1..2] OF T_Arg;
END_VAR
This will call the FB with the data from ST_A:
aMemory[1] := F_BIGTYPE(ADR(aMemoryA[1]), SIZEOF(aMemoryA[1]));
aMemory[2] := F_BIGTYPE(ADR(aMemoryA[2]), SIZEOF(aMemoryA[2]));
fbFoo(aMemory := aMemory);
The variable aMemoryA[2] has now the data of aMemoryA[1].
And this calls the FB with the data from ST_B:
aMemory[1] := F_BIGTYPE(ADR(aMemoryB[1]), SIZEOF(aMemoryB[1]));
aMemory[2] := F_BIGTYPE(ADR(aMemoryB[2]), SIZEOF(aMemoryB[2]));
fbFoo(aMemory := aMemory);
The variable aMemoryB[2] has now the data of aMemoryB[1].
An other solution could be the datatype PVOID. PVOID is a pointer an independent data type. In that case you must working with MEMCPY inside the FB. T_ARG will help you to handle pointer, because contains the pointer to the data and the length of the data. You will find more information about T_Arg in:
https://stefanhenneken.wordpress.com/2018/07/04/iec-61131-3-the-generic-data-type-t_arg
I hope, this will help you to find a solution for you problem.
Regards
Stefan
Wow Thank you !
I will test all this and I will tell you what is best in my application