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