How to create framework functions from Embedded Coder#
The functions of the UltraZohm Software follow the principles outlined in Software Development Guidelines. Encapsulation and data hiding are important characteristics for these software modules. C-Code that is generated by using the Embedded-Coder (C-Code) is not directly compatible with the software guidelines and require a wrapper module to be included in the software framework. The guiding principle is that the interface has to be consistent across modules, since it is not relevant whether the code is handwritten or auto-generated.
The following guide builds on the example model of Embedded-Coder (C-Code) and highlight how to create an interface that is consistent with the Software Development Guidelines (multiple-instance module).
Functional description of the module#
![../../../_images/codegen_simulink.png](../../../_images/codegen_simulink.png)
Fig. 39 Screenshot of the subsystem that is used for code generation in the example of Embedded-Coder (C-Code)#
Fig. 39 shows the subsystem that is code generated in Embedded-Coder (C-Code). The following guide only implements an interface for the sum functionality of the module. Thus, the code ought to calculate:
The Simulink model uses forward Euler integration (see Matlab documentation discrete integrator). Note that the integration time \(T_s=\frac{1}{10000}=0.0001\) is hard-coded in the module, due to the model settings in Embedded-Coder (C-Code). The chirp function is not used in this guide, but can be implemented with the same principles.
Create framework function#
Note
This guide uses the already generated files of the example in Embedded-Coder (C-Code). For custom modules, this step has to be adjusted to match the path of the specific embedded coder settings!
Open
ultrazohm_sw
with VSCode and remote container (see VS Code Remote Container)Create a new module
uz_sum
using Ceedling
cd vitis/software/Baremetal
ceedling module:create[uz/uz_sum/uz_sum]
Ceedling creates the header, source, and test files
Copy the generated files to
uz_sum
, e.g., by using the following command:
cp -R src/Codegen/uz_codegen0_ert_rtw/ src/uz/uz_sum/
Warning
Re-using this module with the same name breaks the software. In the finished code of this how to guide, everything in uz_sum
folder is protected by #ifdef TEST
such that it can only be used in the ceedling unit tests.
Create the allocation scheme using the allocation snippet (see Static memory allocation)
#include "../uz_global_configuration.h"
#if UZ_SUM_MAX_INSTANCES > 0U
#include <stdbool.h>
#include "../../uz_HAL.h"
#include "uz_sum.h"
struct uz_sum_t {
bool is_ready;
};
static uint32_t instance_counter = 0U;
static uz_sum_t instances[UZ_SUM_MAX_INSTANCES] = { 0 };
static uz_sum_t* uz_sum_allocation(void);
static uz_sum_t* uz_sum_allocation(void){
uz_assert(instance_counter < UZ_SUM_MAX_INSTANCES);
uz_sum_t* self = &instances[instance_counter];
uz_assert_false(self->is_ready);
instance_counter++;
self->is_ready = true;
return (self);
}
uz_sum_t* uz_sum_init(void) {
uz_sum_t* self = uz_sum_allocation();
return (self);
}
#endif
Call the module
uz_sum
Add
UZ_SUM_MAX_INSTANCES
touz_global_configuration.h
and set it to 5Add the typedef for the
uz_sum_t
touz_sum.h
as well as the function declaration for the init function:
typedef struct uz_sum_t uz_sum_t;
uz_sum_t* uz_sum_init(void);
In
test_uz_sum.c
, change the existing test to:
void test_uz_sum_NeedToImplement(void)
{
uz_sum_init();
}
Run the tests, they compile but
test_uz_sum.c
does not perform any real testsCreate the interface for stepping the model once (one integration / time step) with the given summand in
uz_sum.h
:
void uz_sum_step(uz_sum_t* self, float a, float b, float c);
Add an interface for reading the results from the module in
uz_sum.h
float uz_sum_get_sum(uz_sum_t* self);
float uz_sum_get_integral_over_sum(uz_sum_t* self);
Write empty functions for the defined interface in
uz_sum.c
void uz_sum_step(uz_sum_t* self, float a, float b, float c){
}
float uz_sum_get_sum(uz_sum_t* self){
}
float uz_sum_get_integral_over_sum(uz_sum_t* self){
}
Write a test that checks for the summation of three values:
void test_uz_sum_add_numbers(void)
{
uz_sum_t* test_instance=uz_sum_init();
float a=1.1f;
float b=2.2f;
float c=3.3f;
float expected_result=6.6f;
uz_sum_step(test_instance,a,b,c);
float result=uz_sum_get_sum(test_instance);
TEST_ASSERT_EQUAL_FLOAT(expected_result, result);
}
Run the tests. They will compile, but fail.
Add the include for the generated code as well as private data to
uz_sum.c
(note: this has to be in the.c
file!)
#include "uz_codegen0_ert_rtw/uz_codegen0.h"
struct uz_sum_t {
bool is_ready;
ExtY output;
ExtU input;
DW rtDW; /* Observable states */
RT_MODEL modelData;
RT_MODEL *PtrToModelData;
};
Implement the initialization of the code-generated software in
uz_sum_init.c
uz_sum_t* uz_sum_init(void) {
uz_sum_t* self = uz_sum_allocation();
self->PtrToModelData=&self->modelData;
self->PtrToModelData->dwork=&self->rtDW;
self->PtrToModelData->inputs=&self->input;
self->PtrToModelData->outputs=&self->output;
return (self);
}
Note that
uz_sum_init
is just wiring of private variables of the module to meet the interface of the generated code and to be able to pass all data of the model to the step function by a single pointer.Add
#include "uz_codegen0_ert_rtw/uz_codegen0.h"
totest_uz_sum.c
to enable calling the generated code in the testsImplement the function
uz_sum_step
inuz_sum.c
:
void uz_sum_step(uz_sum_t* self, float a, float b, float c){
self->input.summand1=a;
self->input.summand2=b;
self->input.summand3=c;
uz_codegen0_step(self->PtrToModelData);
}
Run the tests, they still fail.
Implement
uz_sum_get_sum
inuz_sum.c
float uz_sum_get_sum(uz_sum_t* self){
return self->output.sum;
}
Run tests, they pass.
Write a test for the integration
void test_uz_sum_integrate(void)
{
uz_sum_t* test_instance=uz_sum_init();
float a=1.1f;
float b=2.2f;
float c=3.3f;
float expected_result=0.00198f;
// Call step four times with sum=6.6, integration time Ts is 1/10000
// First call: y(0)=0
// Second call: y(1)= 1/10000*6.6=0.00066
// Third call: y(2)=0.00066+0.00066=0.00132
// Last call: y(3)=0.00132+0.00066=0.00198
// Step four times - no loop to make it explicit
uz_sum_step(test_instance,a,b,c);
uz_sum_step(test_instance,a,b,c);
uz_sum_step(test_instance,a,b,c);
uz_sum_step(test_instance,a,b,c);
float result=uz_sum_get_integral_over_sum(test_instance);
TEST_ASSERT_EQUAL_FLOAT(expected_result,result);
}
Run tests, they fail.
Implement
uz_sum_get_integral_over_sum
float uz_sum_get_integral_over_sum(uz_sum_t* self){
return self->output.IntegrationOfSum;
}
Run tests, they pass.
Implement tests and interface for the chirp functionality
Write documentation for the software module
Note
The source Simulink model that is used for generating the code should be supplied in the same folder as the generated code. Do not commit anything expect .slx
, .c
, and .h
files after the code generation, i.e., no Simulink cache or other build artifacts!