Integration of C code in Engee model using C Function block
This example shows different ways to integrate C code into the Engee model using the C Function block.
Before we start, let's connect the necessary libraries:
using Plots
using MATLAB
plotlyjs();
Use the macro @__DIR__
to find out the folder where the interactive script lies:
demoroot = @__DIR__
Basics of Using the C Function Block
Let's look at simple examples of using the C Function block to illustrate the basics of integrating C code into the Engee model.
1. Working with multidimensional signals, passing parameters and using #define directive
In the model cCodeIntegration_basics.engee
we have added the block C Function, which inputs a sinusoidal signal in
:
Suppose we want to increase the amplitude of the sinusoid in
by a factor of gain
and shift it by a value of bias
. That is, the output signal should be determined by the formula out = gain * in + bias
.
Let's go to the settings of the C Function block, where in the Ports
tab we will familiarise ourselves with the definition of the block input and output. This is where the source code variables are linked to the ports of the C Function block:
- The scalar variable
x
of typedouble
is bound to the first input portin
; - Vector (3 elements) variable
y
of typedouble
is bound to the first output portout
.

The gain
array is also defined in the settings of the C Function block ( Parameters
tab ):

It is worth noting that once a variable is passed as a parameter, it becomes:
- Global -
gain
can be accessed from any tab of the source code editor; - With the qualifier
const
- an error will be received when trying to change the value ofgain
.
The offset value bias
is determined by the identifier #define bias
, which is set in the tab Build options
:

In this example, the offset is 5
.
Let's go to Main
of the C Function block settings and click the "Edit Source Code" button. In the opened source code editor on the tab OutputCode
the code executed at each step of the model calculation is given:

In the loop for
(for each of the three output signals), the value of the variable y
is determined at each step of the model calculation.
Let's simulate the system:
cCodeIntegration_basics = engee.load("$demoroot/models/cCodeIntegration_basics.engee", force = true)
simulationResults_basics = engee.run(cCodeIntegration_basics)
Let's close the model:
engee.close(cCodeIntegration_basics, force = true);
Import the results of the simulation:
cCodeIntegration_basics_t = simulationResults_basics["out"].time;
cCodeIntegration_basics_y1 = [y[1] for y in simulationResults_basics["out"].value];
cCodeIntegration_basics_y2 = [y[2] for y in simulationResults_basics["out"].value];
cCodeIntegration_basics_y3 = [y[3] for y in simulationResults_basics["out"].value];
Plot the graph:
plot(cCodeIntegration_basics_t, cCodeIntegration_basics_y1)
plot!(cCodeIntegration_basics_t, cCodeIntegration_basics_y2)
plot!(cCodeIntegration_basics_t, cCodeIntegration_basics_y3)
title!("Выходной сигнал блока C Function <br> (Многомерные сигналы, параметры и #define)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
2. Using integer data types and static variables
In the model cCodeIntegration_integers.engee
we implement a simple counter using the C Function block, the output of which is a positive integer denoting the number of the current iteration:
Let's analyse the contents of the Ports
and Build Options
tabs in the C Function block settings:


The names of integer data types in the C Function block are defined according to the header file <stdint.h>
of the C language standard library. This file is connected in the line Headers
of the tab Build options
.
The tab Ports
binds a 64-bit variable without the sign out
(the type is set as uint64_t
, not long
, see the previous paragraph about <stdint.h>
) of the source code to the first output port of the C Function block.
The Output code
tab of the source code editor of the C Function block contains the code executed at each step of the simulation:

The static variable counter
is initialised to 0 in the first step of the simulation, after which at each iteration its value is incremented by 1
and assigned to the variable out
.
Let's simulate the system:
cCodeIntegration_integers = engee.load("$demoroot/models/cCodeIntegration_integers.engee", force = true)
simulationResults_integers = engee.run(cCodeIntegration_integers)
Let's close the model:
engee.close(cCodeIntegration_integers, force = true);
Import the results of the simulation:
cCodeIntegration_integers_t = simulationResults_integers["counter"].time;
cCodeIntegration_integers_y = simulationResults_integers["counter"].value;
Plot the graph:
plot(cCodeIntegration_integers_t, cCodeIntegration_integers_y, legend = false)
title!("Выходной сигнал блока C Function <br> (Целочисленные типы и статические переменные)")
xlabel!("Время, [с]")
ylabel!("Номер итерации")
For more information about the purpose of the source code editor tabs, plotting options, and available data types, see in the documentation Engee.
Integrating external source code
Task statement
Let's consider a more interesting engineering problem.
Suppose we need to control a stable second-order object that is subject to external disturbances, given the measurement noise. Consequently, we need to solve the problem of filtering the output signal of the control object, and fortunately, Engee has a tool that is perfect for this!
The files alphabetafilter.c
and alphabetafilter.h
offer a C implementation of the alpha-beta filter.
The contents of the file alphabetafilter.c
:
#include "alphabetafilter.h"
#include <math.h>
double dt = 0.01;
static double xk_1, vk_1;
Parameters determineAlphaBeta(double processNoiseVariance, double measurementNoiseVariance)
{
double lambda, r;
Parameters p;
lambda = (processNoiseVariance * pow(dt, 2)) / measurementNoiseVariance;
r = (4 + lambda - sqrt(8 * lambda + pow(lambda, 2))) / 4;
p.alpha = 1 - pow(r, 2);
p.beta = 2 * (2 - p.alpha) - 4 * sqrt(1 - p.alpha);
return p;
}
double alphaBetaFilter(double value, Parameters p)
{
double xk, vk, rk;
xk = xk_1 + (vk_1 * dt);
vk = vk_1;
rk = value - xk;
xk += p.alpha * rk;
vk += (p.beta * rk) / dt;
xk_1 = xk;
vk_1 = vk;
return xk;
}
void initialiseStaticVariables()
{
xk_1 = 0;
vk_1 = 0;
}
The contents of the file alphabetafilter.h
:
typedef struct {
double alpha;
double beta;
} Parameters;
Parameters determineAlphaBeta(double, double);
double alphaBetaFilter(double, Parameters);
void initialiseStaticVariables();
Integrate this source code into our Engee model.
Analysis of the model
Model cCodeIntegration_source.engee
:
This model consists of:
- The control object
Plant
; - Subsystem realising the second-order PID controller
Controller
; - Blocks Band-Limited White Noise
ProcessNoise
andMeasurementNoise
, modelling the external influence and measurement noise; - Block C Function
Filter
, realising the alpha-beta filter.
The parameter Sample Time
of the block Filter
is equal to the step of the model solver Ts
:

Let's analyse the contents of the Build options
tab of the Filter
block settings:

In the rows of this tab:
Source files
- connected source code filealphabetafilter.c
;Iinclude directories
- the path to the folder containing the header file is set;Headers
- connected header filealphabetafilter.h
.
In the Ports
tab of block settings source code variables are linked to input and output ports of the block Filter
.
In the source code of the Filter
block, the OutputCode
tab contains cyclically executable code:

At each step of the model calculation, the functions determineAlphaBeta()
and alphabetafilter()
are called sequentially, after which the filtered value is stored in the variable out
, corresponding to the output port x
of the block Filter
.
Simulation start
Let's simulate the system:
cCodeIntegration_source = engee.load("$demoroot/models/cCodeIntegration_source.engee", force = true)
simulationResults_source = engee.run(cCodeIntegration_source)
Let's close the model:
engee.close(cCodeIntegration_source, force = true);
Simulation results
Import simulation results:
cCodeIntegration_source_noisy_t = simulationResults_source["noisy"].time;
cCodeIntegration_source_noisy_y = simulationResults_source["noisy"].value;
cCodeIntegration_source_filtered_t = simulationResults_source["filtered"].time;
cCodeIntegration_source_filtered_y = simulationResults_source["filtered"].value;
Plot the graph:
plot(cCodeIntegration_source_noisy_t, cCodeIntegration_source_noisy_y, label = "Исходный сигнал")
plot!(cCodeIntegration_source_filtered_t, cCodeIntegration_source_filtered_y, label = "Отфильтрованный сигнал")
plot!(legend = :bottomright)
title!("Альфа-бета фильтр <br> (Интеграция внешнего исходного кода)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
Integration of static and dynamic libraries
Task Statement
Often source code is not supplied explicitly, but as part of static or dynamic libraries. C API libraries integrate into Engee models as easily as external source code!
Let's compile a static (alphabetafilter.a
) and dynamic (alphabetafilter.so
) library from the files alphabetafilter.c
and alphabetafilter.h
using MakeFile
.
The contents of the file Makefile
:
all: alphabetafilter.a alphabetafilter.so
alphabetafilter.o: alphabetafilter.c
$(CC) -c -fPIC $^ -o $@
alphabetafilter.a: alphabetafilter.o
ar rcs $@ $^
alphabetafilter.so: alphabetafilter.o
$(CC) -shared $^ -o $@
clean:
rm -f *.o *.a *.so
Let's start compiling the libraries:
run(`make -C $demoroot/source`)
Integrate the static and dynamic library into our Engee model.
Static library: Analyse the model
By its structure, the model cCodeIntegration_library_static.engee
does not differ from the previously considered cCodeIntegration_source.engee
; only the directives on the Build options
tab of the Filter
block settings differ. Let's analyse its contents:

The following construction options are defined here:
Include directories
- the path to the folder containing the header file is specified;Library directories
- the path to the folder containing static library is set;Headers
- connected header filealphabetafilter.h
;Libraries
- static libraryalphabetafilter.a
is connected.
Static library: Start simulation
Let's simulate the system:
cCodeIntegration_library_static = engee.load("$demoroot/models/cCodeIntegration_library_static.engee", force = true)
simulationResults_library_static = engee.run(cCodeIntegration_library_static)
Let's close the model:
engee.close(cCodeIntegration_library_static, force = true);
Static Library: Modelling Results
Import the simulation results:
cCodeIntegration_library_static_noisy_t = simulationResults_library_static["noisy"].time;
cCodeIntegration_library_static_noisy_y = simulationResults_library_static["noisy"].value;
cCodeIntegration_library_static_filtered_t = simulationResults_library_static["filtered"].time;
cCodeIntegration_library_static_filtered_y = simulationResults_library_static["filtered"].value;
Plot the graph:
plot(cCodeIntegration_library_static_noisy_t, cCodeIntegration_library_static_noisy_y, label = "Исходный сигнал")
plot!(cCodeIntegration_library_static_filtered_t, cCodeIntegration_library_static_filtered_y, label = "Отфильтрованный сигнал")
plot!(legend = :bottomright)
title!("Альфа-бета фильтр <br> (Интеграция статической библиотеки)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
Dynamic Library: Model Analysis
By its structure, the model cCodeIntegration_library_dynamic.engee
does not differ from the previously considered cCodeIntegration_source.engee
; only the directives on the Build options
tab of the Filter
block settings differ. Let's analyse its contents:

The following construction options are defined here:
Include directories
- the path to the folder containing the header file is specified;Library directories
- the path to the folder containing the dynamic library is set;Headers
- the header filealphabetafilter.h
is connected;Libraries
- connected dynamic libraryalphabetafilter.so
.
In the Start code tab of the source code editor of the Filter
block, the function initializeStaticVariables()
is called:
The function initializeStaticVariables()
is declared in the file alphabetafilter.h
and is intended to initialise the static variables xk_1
and vk_1
, used in the dynamic library. This is necessary so that when the simulation is restarted, the variables do not save their last calculated value, but are reset to the initial value (in this case to 0
).
Dynamic Library: Running a Simulation
Let's simulate the system:
cCodeIntegration_library_dynamic = engee.load("$demoroot/models/cCodeIntegration_library_dynamic.engee", force = true)
simulationResults_library_dynamic = engee.run(cCodeIntegration_library_dynamic)
Let's close the model:
engee.close(cCodeIntegration_library_dynamic, force = true);
Dynamic Library: Modelling Results
Import the simulation results:
cCodeIntegration_library_dynamic_noisy_t = simulationResults_library_dynamic["noisy"].time;
cCodeIntegration_library_dynamic_noisy_y = simulationResults_library_dynamic["noisy"].value;
cCodeIntegration_library_dynamic_filtered_t = simulationResults_library_dynamic["filtered"].time;
cCodeIntegration_library_dynamic_filtered_y = simulationResults_library_dynamic["filtered"].value;
Plot the graph:
plot(cCodeIntegration_library_dynamic_noisy_t, cCodeIntegration_library_dynamic_noisy_y, label = "Исходный сигнал")
plot!(cCodeIntegration_library_dynamic_filtered_t, cCodeIntegration_library_dynamic_filtered_y, label = "Отфильтрованный сигнал")
plot!(legend = :bottomright)
title!("Альфа-бета фильтр <br> (Интеграция динамической библиотеки)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
Integration of code generated from the Simulink model
Problem Statement
Let's generate C code from the Simulink model alphabetafilter.slx
and integrate it into our Engee model cCodeIntegration_cg_simulink.engee
.
The model alphabetafilter.slx
, which implements the alpha-beta filter, is built in Simulink from simple blocks and consists of subsystems determineAlphaBeta
and filterInputSignal
.
Top level of the model:

Subsystem determineAlphaBeta
:

Subsystem filterInputSignal
:

Let's generate the code using Simulink Embedded Coder:
cgPath = joinpath(demoroot,"cg/simulink")
cgModel = joinpath(demoroot,"cg/simulink/alphabetafilter")
mat"""
model = load_system($cgModel);
path = $cgPath;
set_param(0, 'CacheFolder', path)
set_param(0, 'CodeGenFolder', path)
slbuild(model)
set_param(0, 'CacheFolder', '')
set_param(0, 'CodeGenFolder', '')
"""
Model Analysis
By its structure, the model cCodeIntegration_cg_simulink.engee
does not differ from the previously considered cCodeIntegration_source.engee
; only the directives on the Build options
tab of the Filter
block settings differ. Let's analyse its contents:

The following construction options are defined here:
Source files
- connected source code filesalphabetafilter.c
andalphabetafilter_data.c
;Include directories
- the path to the folder containing header files is set;Headers
- header filesalphabetafilter.h
,alphabetafilter_types.h
andrtwtypes.h
are connected.
Cyclically executable code is given on the Output code
tab of the source code editor of the Filter
block:

At each step of the model calculation is performed:
-
Initialisation of the structure
alphabetafilter_U
with variablesin
,processNoiseVariance
andmeasurementNoiseVariance
(input signals); -
Calling the function
alphabetafilter_step()
; -
Assignment to the variable
out
(output signal) of the resultx
, stored in the structurealphabetafilter_Y
.
In the Start code tab of the source code editor of the Filter
block, the function alphabetafilter_initialize()
is called:

In the Terminate code tab of the source code editor of the Filter
block, the function alphabetafilter_terminate()
is called:

Simulation start
Let's simulate the system:
cCodeIntegration_cg_simulink = engee.load("$demoroot/models/cCodeIntegration_cg_simulink.engee", force = true)
simulationResults_cg_simulink = engee.run(cCodeIntegration_cg_simulink)
Let's close the model:
engee.close(cCodeIntegration_cg_simulink, force = true);
Simulation results
Import simulation results:
cCodeIntegration_cg_simulink_noisy_t = simulationResults_cg_simulink["noisy"].time;
cCodeIntegration_cg_simulink_noisy_y = simulationResults_cg_simulink["noisy"].value;
cCodeIntegration_cg_simulink_filtered_t = simulationResults_cg_simulink["filtered"].time;
cCodeIntegration_cg_simulink_filtered_y = simulationResults_cg_simulink["filtered"].value;
Plot the graph:
plot(cCodeIntegration_cg_simulink_noisy_t, cCodeIntegration_cg_simulink_noisy_y, label = "Исходный сигнал")
plot!(cCodeIntegration_cg_simulink_filtered_t, cCodeIntegration_cg_simulink_filtered_y, label = "Отфильтрованный сигнал")
plot!(legend = :bottomright)
title!("Альфа-бета фильтр <br> (Интеграция кода, сгенерированного из модели Simulink)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
Integration of code generated from the Engee model
Task Statement
Let's generate C code from the model Engee alphabetafilter.engee
, built from simple blocks and implementing the alpha-beta filter, and integrate it into our model Engee cCodeIntegration_cg_engee.engee
.
Top level of the model:
Subsystem determineAlphaBeta
:
Subsystem filterInputSignal
:
Let's generate the code using the Engee code generator:
engee.generate_code("$demoroot/cg/engee/alphabetafilter.engee", "$demoroot/cg/engee/alphabetafilter_cg")
Model Analysis
By its structure, the model cCodeIntegration_cg_engee.engee
does not differ from the previously considered cCodeIntegration_source.engee
; only the directives on the Build options
tab of the Filter
block settings differ. Let's analyse its contents:

The following construction options are defined here:
Source files
- connected source code filealphabetafilter.c
;Include directories
- the path to the folder containing header files is set;Headers
- connected header filealphabetafilter.h
.
Cyclically executable code is given on the Output code
tab of the source code editor of the Filter
block:

At each step of the model calculation is performed:
-
Initialisation of the structure
alphabetafilter_U
with variablesin
,processNoiseVariance
andmeasurementNoiseVariance
(input signals); -
Calling the function
alphabetafilter_step()
; -
Assignment to the variable
out
(output signal) of the resultx
, stored in the structurealphabetafilter_Y
.
In the Start code tab of the source code editor of the Filter
block, the function alphabetafilter_init()
is called:
In the Terminate code tab of the source code editor of the Filter
block, the function alphabetafilter_term()
is called:
Simulation start
Let's simulate the system:
cCodeIntegration_cg_engee = engee.load("$demoroot/models/cCodeIntegration_cg_engee.engee", force = true)
simulationResults_cg_engee = engee.run(cCodeIntegration_cg_engee)
Let's close the model:
engee.close(cCodeIntegration_cg_engee, force = true);
Simulation results
Import simulation results:
cCodeIntegration_cg_engee_noisy_t = simulationResults_cg_engee["noisy"].time;
cCodeIntegration_cg_engee_noisy_y = simulationResults_cg_engee["noisy"].value;
cCodeIntegration_cg_engee_filtered_t = simulationResults_cg_engee["filtered"].time;
cCodeIntegration_cg_engee_filtered_y = simulationResults_cg_engee["filtered"].value;
Plot the graph:
plot(cCodeIntegration_cg_engee_noisy_t, cCodeIntegration_cg_engee_noisy_y, label = "Исходный сигнал")
plot!(cCodeIntegration_cg_engee_filtered_t, cCodeIntegration_cg_engee_filtered_y, label = "Отфильтрованный сигнал")
plot!(legend = :bottomright)
title!("Альфа-бета фильтр <br> (Интеграция кода, сгенерированного из модели Engee)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
Conclusions
This example demonstrates the features of using the C Function block and the different ways to integrate C code into the Engee model:
- Integration of external source code;
- Integration of static and dynamic libraries;
- Integration of code generated from the Simulink model;
- integration of code generated from the Engee model.