C Function
Usage of C code in models.
blockType: CFunction
Path in the library:
|
Description
The C Function block allows Engee models to use C code and shared libraries that have a C API.
When connecting a dynamic library that uses internal static variables, you should make sure that it provides an API for initialising/deinitialising these variables. The corresponding functions should be called in the StartCode and TerminateCode tabs of the source code editor. Otherwise, static variables will retain their last values between simulations, which may lead to incorrect results when the model is rerun.
|
Usage
To integrate source C code into an Engee model, you must:
-
Add to the model the block C Function from the Basic/User Functions section of the block library;
-
Click on the Edit Source Code button and the source code editor will be opened.
Source code editor
The source code editor workspace consists of four tabs:
-
OutputCode
- contains code executed at each step of the model calculation; -
StartCode
- contains code executed once when the model is initialised; -
TerminateCode
- contains code executed once when the model is stopped; -
SharedCode
- contains code that allows exchanging global (shared) variables and functions between instances of the block C Function.
C syntax highlighting is performed in each of the sections.
The OutputCode
section contains a block of special comments that are important for the simulator and the code generator.
Comments allow you to associate variables in the source code with signals (inputs and outputs) of the block C Function, as well as to specify additional information necessary for the assembly of the code and its usage in the model.
Features of C code generation from the block C Function
The C Function block supports C code generation. When generating code, the C Function block is turned into functions that wrap the contents of all four sections of the source code.
At this time, the C code generator does not provide additional information to the build system based on the directives of the C Function block . The directives are specified in the Build options section of the block settings:
Block usage reuse
The SharedCode
tab of the source code of the C Function block allows you to specify the common code available for sharing data and functions between instances of the C Function blocks with the same name in the parameters Function name.
To use the SharedCode
functionality, you need to create a block C Function, name it in the parameters Function name, and then save that block to user library . The block can then be added to the model in several places where it will retain access to common data and functions. As a result, modelling creates only one common shared library and one set of functions that is used by all instances of the block C Function.
When generating code for blocks with the same name in the Function name parameters, only the shared functions init
, step
and term
are created, and the code from the SharedCode
tab is added to the project once. This simplifies code management and provides access to common data in all instances of the block C Function.
Working variables
The WorkVariables tab of the C Function block allows you to set static working variables that remain specific to each block instance, even if multiple blocks have the same value for the parameters Function name. These working variables are similar to parameters, but differ in their ability to change their values from the block’s source code at runtime. This approach allows you to store and modify data that is only used within a particular block instance.
For each working variable, the WorkVariables tab specifies Name and Size in bytes. Within the source code, these variables are accessible via a pointer to the Work
structure, which is present in the interfaces of all three block functions: step
, init
, and term
. Example:
Alignment is used here in the source code, where the is_aligned
macro checks that the variables are aligned correctly. The variables work_variable1
and work_variable3
are predefined on the tab WorkVariables and are available through the structure Work
.
Working with arrays
When data is passed from Julia to C code, it is important to consider the differences in indexing and array structure. This is relevant for multidimensional arrays, which can be transposed (change the order of the indexes) during transfer. Below are the main points to consider when working with arrays:
For more information about working with arrays.
-
One-dimensional arrays - when passing a one-dimensional array from Julia to C, the array retains its structure. Indexing in C starts from
0
, so when accessing array elements you need to take this shift into account. For example, if a one-dimensional array is written as a =[1, 2, 3]
in the block parameter C Function, then in C:#include <stdio.h> int main() { int a[3] = {1, 2, 3}; printf("%d", a[2]); // Выведет 3 return 0; }
In this example,
a[2]
is invoked to get the value of the array element with index2
, which will return the value3
. -
Multidimensional Arrays - Multidimensional arrays can change index order when passed from Julia to C. In C code, the array will be transposed, which means that when accessing elements of the array, the change in index order must be accounted for. For example, there is an array in Julia:
a = [1 2 3; 4 5 6] #Julia интерпретирует это как матрицу 2x3 2x3 Matrix{Int64} 1 2 3 4 5 6 #доступ к элементу массива a [1,2] 2
Then in C:
#include <stdio.h> int main() { int a[3][2] = { {1, 4}, {2, 5}, {3, 6} }; printf("%d", a[1][0]); // Выведет 2 return 0; }
The array
[1 2 3; 4 5 6]
in Julia becomes the arraya[2][3]
in C, where the first index points to a row and the second index points to a column. In this C example, the indexinga[1][0]
is used to get an element with value2
. Consequently, the array in C actually stores the elements of the Julia transposed array, which must be taken into account when accessing the elements.
When working with three-dimensional and more complex arrays, the indexing can also change. For example, if an array in Julia has dimension [2, 3, 4]
, in C it will be represented as [4][3][2]
. This must be taken into account when referring to array elements in C code.
Example of a three-dimensional array in Julia:
a = reshape(1:24, 2, 3, 4)
[:, :, 1] =
1 3 5
2 4 6
[:, :, 2] =
7 9 11
8 10 12
[:, :, 3] =
13 15 17
14 16 18
[:, :, 4] =
19 21 23
20 22 24
println(a[1, 2, 3]) # Выведет 13
IN S:
#include <stdio.h>
int main() {
int a[4][3][2] = {
{
{1, 4},
{2, 5},
{3, 6}
},
{
{7, 10},
{8, 11},
{9, 12}
},
{
{13, 16},
{14, 17},
{15, 18}
},
{
{19, 22},
{20, 23},
{21, 24}
}
};
printf("%d", a[2][1][0]); // Выведет 13
return 0;
}
The general rule for converting indexes from Julia to C is to change the order:
This means that the indices in C are written in reverse order. |
Working with buses
The C Function block supports bus operation both on the input/output ports and in the block parameters. The specifics of bus configuration and usage are described below:
Bus on ports
To set a bus on an input or output port:
-
In the Type parameters of the corresponding port (Ports tab in the block settings), select the
BusSignal
type. -
The Input bus type (or Output bus type) parameters will then automatically take the form:
BusSignal{(), Tuple(), ()}
This is where it should be stated:
-
Bus type explicitly, e.g.
BusSignal{(:s1, :s2), Tuple(Float64, Int54), ), (}
.:s1
,:s2
- names of signals in the bus;Tuple(Float64, Int64)
- their types;), (
- their dimensions; -
Or the name of a variable containing the description of the bus type defined in the Engee workspace (in the variables window) or in C code.
-
All bus types must be specified explicitly - this is a feature of the C Function block . Bus names should not contain spaces as they are used in C code where spaces in variable names are not allowed. |
Tyres in parameters
If the bus is used as a parameter:
-
In the Value field, the tyre must be specified as a named tuple, e.g.:
(s1 = 4.4, s2 = (b1 = 5, b))
-
If the parameters are named, for example,
p
, then in C code (source code of the block C Function) you can refer to bus elements as structures:output1 = input1 * (p.s1 + p.s2.b1);
Main
Number of input ports -
number of input ports
1 (By default)
.
Details
Specifies the number of input ports of the block. The value of the parameters Number of input ports will correspond to the number of input ports.
Number of output ports -
number of output ports
1 (By default)
.
Details
Specifies the number of output ports of the block. The value of the parameter Number of output ports will correspond to the number of output ports.
Function name -
identifier of the common code for several blocks
no (By default)
Details
Specify a string value to merge the code of blocks C Function. Blocks with the same name in the Function name parameters will use a common code and share data through the source code tab SharedCode
of the C Function block .
If the field is left blank, the selected C Function block will remain standalone (by default).
In the Function name parameters you can specify both absolute path (starts with It is also acceptable to specify only the file name: in this case Engee first looks for the file in the current directory, and then in the search paths described in Engee path editor. |
Sample time -
interval between calculation steps
-1 (By default)
Details
Specify the interval between calculation steps as a non-negative number. To inherit a calculation step, set this parameter to -1
.
Ports
Input
Input port 1 -
input port #1
scalar
| vector
| matrix
Details
Fill in/set up the following fields to operate the input port:
-
Label - name (text label) of the input port. By default, the name cell is not filled in (name is not set).
-
Variable name - name of the input port variable in the source C code. By default
input1
./* Этот код вызывается на каждом шаге расчета модели */ output1 = input1 * param1;
where
input1
is the name of the input port variable;output1
is the name of the output port variable;param1
is the name of the parameters variable. -
Type - data type of the input port variable in the source C code. The data type of the variable must coincide with the data type of the signal. Basic C language data types are supported:
double (by default)
|float
|int8_t
|uint8_t
|int16_t
|uint16_t
|int32_t
|uint32_t
|int64_t
|uint64_t
|int128_t
|uint128_t
|bool
|BusSignal
. -
Size - input signal dimension
1 (by default)
.The Size field uses Julia notation where the size is written as a tuple, e.g. (2, 3, 4)
. However, when working with this port in C code, you must take into account the change in index order. This means that if a port is specified with the dimension(2, 3, 4)
, then in C code the variable will have the dimension[4][3][2]
. This rule is similar to the way parameters work: index order is transposed in C compared to Julia.
Output
Output port 1 -
output port #1
scalar
| vector
| matrix
Details
Fill in/set up the following fields to operate the output port:
-
Label - name (text label) of the output port. By default, the name cell is not filled in (name is not set).
-
Variable name - the name of the output port variable in the source C code. By default
output1
./* Этот код вызывается на каждом шаге расчета модели */ output1 = input1 * param1;
where
output1
is the name of the output port variable;input1
is the name of the input port variable;param1
is the name of the parameters variable. -
Type - data type of the output port variable in the source C code. The data type of the variable must coincide with the data type of the signal. Basic C language data types are supported:
double (by default)
|float
|int8_t
|uint8_t
|int16_t
|uint16_t
|int32_t
|uint32_t
|int64_t
|uint64_t
|int128_t
|uint128_t
|bool
|BusSignal
. -
Size - dimension of the output signal
1 (by default)
.The Size field uses Julia notation where the size is written as a tuple, e.g. (2, 3, 4)
. However, when working with this port in C code, you must take into account the change in index order. This means that if a port is specified with the dimension(2, 3, 4)
, then in C code the variable will have the dimension[4][3][2]
. This rule is similar to the way parameters work: index order is transposed in C compared to Julia.
Parameters
Number of parameters -
specify the number of parameters
1 (By default)
Details
Number of parameters used in the block.
Parameter 1 -
defines parameters as a variable
1 (By default)
Details
Defines the parameters as a variable for usage in source code:
-
Name - parameter name, by default
param1
. Can be changed. New parameters are namedparam2
and then in ascending order. -
Value - parameter value, by default
0
. Can be changed. The value of the new parameters is also zero by default.
For parameters to work, go to the block code editor C Function (Edit Source Code) to the OutputCode
tab and enter the name of the required parameter. By default:
/* Этот код вызывается на каждом шаге расчета модели */
output1 = input1 * param1;
where input1
is the input signal variable; output1
is the output signal variable.
WorkVariables
Number of work variables -
number of operating variables
0 (By default)
.
Details
The number of working variables used in the block to store data specific to each instance. Working variables are accessible through the Work
structure and can be used in init
, step
and term
functions to write and read data.
Each variable is set with the parameters Name and Size in bytes, which allows storing data of different types. If a variable is assigned a larger size, it can be used as an array.
WorkVariable 1 -
defines a work variable
1 (By default)
Details
Defines a working variable for usage in the source code:
-
Name - parameter name, by default
work_variable1
. Can be changed. New work variables are namedwork_variable2
and then in ascending order. -
Size in bytes - the amount of memory allocated for the variable, in bytes. This parameter allows you to specify the size of the variable, whether it is a scalar or an array.
To work variables, you must go to the code editor of the block C Function (Edit Source Code) to the OutputCode
tab and enter the name of the variable.
Build options
Source files -
connecting source code files
no (By default)
Details
Used to connect additional source code files. Must include the path and filename along with the extension. For example:
/user/project/src/example.c
Include directories - defining the path to the header file directory
none (By default)
Details
Used to define a path to a directory containing header files. For example:
/user/project/include
Library directories -
library catalogue path definition
none (By default)
Details
Used to define a path to a directory containing shared libraries. For example:
/user/project/third_party
Headers -
header file connection
stdint.h math.h (by default)
Details
Used to connect header files. Must include the name of the header file along with the extension. For example:
example.h
Defines -
definition of additional directives #define
none (By default)
Details
Used to define additional #define directives. For example:
defines LOWER=0 UPPER=300 STEP=20
Libraries -
library connection
no (By default)
.
Details
Used to connect libraries. Must include the library name along with the extension. For example:
libexample.so
The actual data type, as well as the support for possible data types, depends on the user code within the block. |