Skip to content

Commit 38b5b3c

Browse files
authored
docs: add guide for creating plc libraries (#1213)
* add guide for developing libraries for plc with c/cpp
1 parent b435e48 commit 38b5b3c

File tree

2 files changed

+340
-0
lines changed

2 files changed

+340
-0
lines changed

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [Writing ST Programs]()
99
- [Libraries](libraries.md)
1010
- [External Functions](libraries/external_functions.md)
11+
- [API guidelines](./libraries/api_lib_guide.md)
1112
- [Using in external programs]()
1213

1314
- [POUs](./pous.md)

book/src/libraries/api_lib_guide.md

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
# API guidelines
2+
3+
## 1. Introduction
4+
5+
- **Purpose**: Provide plc library developers with information how an interface for an application written in `IEC61131-3` should be designed and why.
6+
- **Scope**: This guideline applies to all developers writing libraries for
7+
use in IEC61131-3 applications.
8+
9+
## 2. API Guidelines
10+
11+
### 2.1 `VAR_IN_OUT` instead of pointers
12+
13+
If a function takes a parameter for the purpose of reading and writing to it
14+
a `VAR_IN_OUT` can be used instead of a pointer in the `VAR_INPUT`.
15+
16+
### 2.2 `FUNCTION` and `FUNCTION_BLOCK`
17+
18+
`FUNCTION` and `FUNCTION_BLOCK` have similar properties, but they have fundamentally different representation in the compiler.
19+
A `FUNCTION` is defined in a similar manner to a `C` function:
20+
- It has no backing struct
21+
- values defined inside it will only persist for the duration of the function call
22+
23+
Example:
24+
25+
```iecst
26+
FUNCTION myFunc : DINT
27+
VAR_INPUT
28+
x : DINT;
29+
END_VAR
30+
END_FUNCTION
31+
```
32+
```c
33+
int32_t myFunc(int32_t);
34+
```
35+
36+
In contrast, a `FUNCTION_BLOCK` is backed by a struct and is globally accessible by a defined instance.
37+
To declare a `FUNCTION_BLOCK`, a backing struct has to be declared and passed as a reference to the function block implementation.
38+
39+
```iecst
40+
FUNCTION_BLOCK myFb
41+
VAR_INPUT
42+
x : DINT;
43+
END_VAR
44+
END_FUNCTION_BLOCK
45+
```
46+
47+
```c
48+
typedef struct {
49+
int32_t x;
50+
} myFunctStr;
51+
52+
void myFb(myFunctStr*);
53+
```
54+
55+
56+
57+
#### 2.2.1 Parameters
58+
59+
`FUNCTION` and `FUNCTION_BLOCK` may define input parameters. These are passed using the `VAR_INPUT` or `VAR_IN_OUT` blocks.
60+
The difference between the two blocks is how the values are passed.
61+
A `VAR_INPUT` variable is passed _by value_, while a `VAR_IN_OUT` variable is passed _by reference_.
62+
In general, it is recommended to use a `VAR_IN_OUT` for data that needs to be both read and written, while `VAR_INPUT` should be reserved to read only values.
63+
64+
> **_NOTE:_** In `FUNCTION`s complex datatypes are handled as pointers. They are however copied and changes in the function will have no effect on the actual variable.
65+
66+
Examples:
67+
68+
`FUNCTION`:
69+
70+
```iecst
71+
FUNCTION myFunc : DINT
72+
VAR_INPUT
73+
myInt : DINT;
74+
myString : STRING[255];
75+
END_VAR
76+
77+
VAR_INPUT
78+
myRefStr : STRING;
79+
END_VAR
80+
81+
VAR_IN_OUT
82+
myInOutInt : DINT;
83+
END_VAR
84+
END_FUNCTION
85+
```
86+
87+
```c
88+
int32_t myFunc(int32_t myInt, char* myString, char* myRefStr, int32_t* myInOutInt);
89+
```
90+
91+
`FUNCTION_BLOCK`:
92+
93+
94+
```iecst
95+
FUNCTION_BLOCK myFb
96+
VAR_INPUT
97+
myInt : DINT;
98+
myString : STRING[255];
99+
END_VAR
100+
101+
VAR_IN_OUT
102+
myInOutInt : DINT;
103+
END_VAR
104+
END_FUNCTION_BLOCK
105+
```
106+
107+
```c
108+
109+
typedef struct {
110+
int32_t myInt;
111+
char myString[256];
112+
int32_t* myInOutInt;
113+
} myFbStruct
114+
115+
void myFb(myFbStruct* myFbInstance);
116+
```
117+
118+
119+
#### 2.2.2 Private members
120+
121+
A `FUNCTION_BLOCK` often requires local (private) members to hold data across executions. These members have to be declared in the struct.
122+
As a side effect, these variables are visible to the users.
123+
124+
For example:
125+
126+
```
127+
FUNCTION_BLOCK Count
128+
VAR
129+
current : DINT;
130+
END_VAR
131+
END_FUNCTION_BLOCK
132+
```
133+
134+
```c
135+
136+
typedef struct {
137+
int32_t current;
138+
} CountStruct;
139+
140+
void Count(CountStruct* countInst) {
141+
countInst->current = countInst->current + 1;
142+
}
143+
```
144+
145+
146+
147+
#### 2.2.3 Return values
148+
149+
A `FUNCTION` defines a return value in the signature, while a `FUNCTION_BLOCK` relies on `VAR_OUTPUT` definitions.
150+
151+
Example:
152+
```iecst
153+
FUNCTION myFunc : DINT
154+
VAR_INPUT
155+
x : DINT;
156+
END_VAR
157+
VAR_IN_OUT
158+
y : DINT;
159+
END_VAR
160+
END_FUNCTION
161+
```
162+
163+
The C interface would look like:
164+
165+
```c
166+
int32_t myFunc(int32_t x, int32_t* y);
167+
```
168+
169+
170+
The return type for a function can also include complex datatypes, such as strings, arrays and structs.
171+
Internally, complex return types are treated as reference parameters (pointers).
172+
173+
For complex return types, the function signature expects the return value as the first parameter.
174+
175+
Example:
176+
177+
```iecst
178+
FUNCTION myFunc : STRING
179+
VAR_INPUT
180+
x : DINT;
181+
END_VAR
182+
VAR_IN_OUT
183+
y : DINT;
184+
END_VAR
185+
END_FUNCTION
186+
```
187+
188+
The C interface would look like:
189+
190+
```c
191+
void myFunc(char* out, int32_t x, int32_t* y);
192+
```
193+
194+
A `FUNCTION_BLOCK` should use `VAR_OUTPUT` for return values. Avoid using a
195+
pointer in the `VAR_INPUT` as a return value.
196+
197+
Example:
198+
199+
```iecst
200+
FUNCTION_BLOCK myFb
201+
VAR_INPUT
202+
x : DINT;
203+
END_VAR
204+
VAR_IN_OUT
205+
y : DINT;
206+
END_VAR
207+
VAR_OUTPUT
208+
myOut: DINT;
209+
myOut2: STRING[255];
210+
END_VAR
211+
END_FUNCTION
212+
```
213+
214+
The C interface would look like:
215+
216+
```c
217+
typedef struct {
218+
int32_t x;
219+
int32_t* y;
220+
int32_t myOut;
221+
char myOut2[256];
222+
223+
} myFbStruct;
224+
225+
void myFb(myFbStruct* myFbInst);
226+
```
227+
228+
### 2.2.4 When to use a `FUNCTION` vs. `FUNCTION_BLOCK`
229+
230+
A `FUNCTION` can be well integrated into the API because of its return value which
231+
can be nested into expressions. They however don't keep data over subsequent
232+
executions. If you need to store static data use a `FUNCTION_BLOCK` or use
233+
`VAR_IN_OUT`.
234+
235+
236+
> **_NOTE:_** Do not use `PROGRAM`s in your libraries
237+
> `PROGRAM`s have static instances. These are reserved for applications and should
238+
> not be used in libraries.
239+
240+
### 2.3 Datatypes
241+
242+
The IEC61131-3 Standard defines several datatypes with their intended uses.
243+
To stay standard compliant, an API/Library should try and follow these guidelines.
244+
245+
### 2.3.1 Type sizes
246+
247+
Datatypes are generally convertable to `C` equivalent. With the compiler defaulting to 64bit, some sizes were also fixed to 64bit.
248+
249+
Below is a table of types and how they can be used from `C`
250+
251+
| type | c equivalent | size | comment |
252+
| --------------- | ------------ | ---- | ---------------------------------------------------------- |
253+
| BOOL | bool | 8 | |
254+
| BYTE | uint8_t | 8 | intended to be used as bit sequence and not as a number |
255+
| SINT | int8_t | 8 | |
256+
| USINT | uint8_t | 8 | |
257+
| WORD | uint16_t | 16 | |
258+
| INT | int16_t | 16 | |
259+
| UINT | uint16_t | 16 | |
260+
| DINT | int32_t | 32 | |
261+
| DWORD | uint32_t | 32 | |
262+
| UDINT | uint32_t | 32 | |
263+
| LINT | int64_t | 64 | |
264+
| LWORD | uint64_t | 64 | |
265+
| ULINT | uint64_t | 64 | |
266+
| REAL | float_t | 32 | |
267+
| LREAL | double_t | 64 | |
268+
| TIME | time_t | 64 | Note that all time and date types are 64 bit |
269+
| LTIME | time_t | 64 | |
270+
| DATE | time_t | 64 | |
271+
| LDATE | time_t | 64 | |
272+
| DATE_AND_TIME | time_t | 64 | |
273+
| LDATE_AND_TIME | time_t | 64 | |
274+
| DT | time_t | 64 | |
275+
| LDT | time_t | 64 | |
276+
| TIME_OF_DAY | time_t | 64 | |
277+
| LTIME_OF_DAY | time_t | 64 | |
278+
| TOD | time_t | 64 | |
279+
| LTOD | time_t | 64 | |
280+
| POINTER TO type | \*type | 64 | The Pointer size is equivalent to `LWORD` and not `DWORD` |
281+
| REF_TO type | \*type | 64 | Prefer this type to `POINTER TO` for standard compliance |
282+
| STRING | uint8_t[] | var | UTF-8 String, null terminated. Default is 80 chars + 1 termination byte |
283+
| WSTRING | uint16_t[] | var | UTF-16 (wide) String, null terminated. Default is 80 chars + 1 termination byte |
284+
285+
### 2.3.2 Using Types in interfaces
286+
287+
When deciding on a type to use for a `FUNCTION`, `FUNCTION_BLOCK`, or `STRUCT` use a type that reflects the intention of the API:
288+
- A bit sequence should be in a BIT type like `WORD` and not in a numeric type like `INT`.
289+
- A variable representing a time should be stored in the appropriate time type and not an `LINT` or `LWORD`
290+
- A pointer should be stored as a `REF_TO` and not as an `LWORD` where possible.
291+
- `(W)STRING`s and `ARRAY`s stored in `VAR`, `VAR_INPUT`, and `VAR_OUTPUT` sections of `FUNCTION_BLOCK`s are stored in the `FUNCTION_BLOCK`, and are passed by value.
292+
- A `VAR_IN_OUT` block can be used to force a type to be passed as a pointer. Note that `VAR_IN_OUT` is a read-write variable and changes to the parameter will change it for the caller.
293+
- `FUNCTION`s expecting an `ARRAY` parameter can use the `ARRAY[*]` syntax (Variable sized array). The same functionality will be available for `STRING`. It is however not yet implemented.
294+
295+
### 2.4 Struct alignment
296+
297+
Struct alignment in plc follows the default behaviour of `C`.
298+
When developing a library in `C` a normal struct can be declared.
299+
In langugages other than `C` the struct has to be `C` compatible. For example in `rust` the `#[repr(C)]` can be used to make the struct `C` compatible.
300+
301+
Example:
302+
303+
```iecst
304+
TYPE myStruct:
305+
STRUCT
306+
x : DINT;
307+
y : REF_TO DINT;
308+
z : ARRAY[0..255] OF BYTE;
309+
END_STRUCT
310+
END_TYPE
311+
```
312+
313+
The `C` struct would look like:
314+
315+
```c
316+
317+
typedef struct {
318+
int32_t x;
319+
int32_t* y;
320+
char z[256];
321+
322+
} myStruct;
323+
324+
```
325+
326+
The `rust` struct would look like
327+
```rust
328+
#[repr(C)]
329+
pub struct myStruct {
330+
x: i32,
331+
y: *mut i32,
332+
z: [c_char; 256],
333+
}
334+
```
335+
336+
### 2.5 `FUNCTION_BLOCK` initialization
337+
Not yet implemented.
338+
339+

0 commit comments

Comments
 (0)