Deploy Multirate Rate-Based Component Configured for C Service Interface Code Generation
This example shows how to develop a multirate model as a component that includes service interface support for interacting with target platform software. In this example you:
Assess project requirements.
Define the service code interface configuration in the coder dictionary.
Design the component model.
Configure the component model interfaces for code generation.
Assess component model readiness for code generation.
Generate and inspect the generated component code.
Test the generated service interface code by using a software-in-the-loop (SIL) or processor-in-the-loop (PIL).
Get meta-information about the generated code interface for integration with target platform software.
Integrate generated component code with target environment code.
Assess Project Requirements
This example assumes these component and target platform requirements.
Component Description and Requirements
The component model must:
Favor memory optimization over data freshness when communicating data.
Include an initialize function that reads a value from nonvolatile memory and uses that value to initialize a state variable.
Include a function that receives the value of the state variable from the initialize function every 2 seconds. When the state variable rises from a negative or zero value to a positive value the function applies a forward integration method and sends its output for further processing. Every 1 second, the component model increments the integration function output value by one, applies a forward integration method, and applies a gain value. The gain value must be initialized to 3 and tunable during execution.
Include a terminate function that writes the output of the component model to nonvolatile memory after the component stops executing.
Make the state variable accessible for monitoring during execution.
Target Platform Requirements
The target platform for this example project has these requirements:
To initiate component initialization, the target platform function scheduler expects to call entry-point function
CD_initialize
with no arguments.To initiate execution of the integrator function, the target platform function scheduler expects to call entry-point function
CD_integrator
with no arguments every 2 seconds.The target platform function scheduler expects to call the entry-point function CD_accumulator every 1 second to process the output of the integrator function.
To complete component termination, the target platform function scheduler expects to call entry-point function
CD_terminate
with no arguments.The component reads from and writes to target platform nonvolatile memory.
The platform receiver service function is named
get_CD_accumulator_InBus_u
and uses outside-execution data communication. For more information, see Data Communication Methods.The platform sender service function is named
getref_CD_accumulator_OutBus_y
and uses outside-execution data communication.The variable for the tunable gain value applied by the accumulator function is named
CD_tunable.k
.The variable that represents the delay state value is named
CD_measured.delay
.
Define Service Code Interface Configuration
You address the target platform requirements by defining a service code interface configuration in a shared Embedded Coder Dictionary. For this example, the requirements are defined in the dictionary file ComponentDeploymentCoderDictionary.sldd
, which is configured for the example model.
Open example component model ComponentDeploymentMultirate
.
open_system('ComponentDeploymentMultirate');
Open the Embedded Coder Dictionary (ComponentDeploymentCoderDictionary.sldd
), which is configured for the model.
Open the Embedded Coder app.
In the toolstrip, under Code interface, select
Embedded Coder Dictionary (Shared)
. The Embedded Coder Dictionary configured for the example model opens. You can create a coder dictionary in the model Configuration Parameters dialog box (click Set up to the right of the Shared coder dictionary parameter) or in the Model Explorer (click File > New > Embedded Coder Dictionary).Under Service Interfaces, select Data Receiver. The Embedded Coder Dictionary shows content in three panes. Use the left pane to navigate through the dictionary configurations. The center pane lists the interfaces that are defined in a code interface configuration for the item you select in the navigation pane. From the center pane you can create and delete interfaces and specify a default interface. The right pane shows the property settings for the interface selected in the center pane. The Pseudocode Preview updates as you change property settings.
In the dictionary, browse through the interface settings. Check that the settings align with the requirements noted in Target Platform Requirements.
Based on requirements listed in Assess Project Requirements, the dictionary defines these relevant interfaces:
Initialize and terminate callable entry-point function interface
InitTermInterface
, which specifies naming ruleCD_$N
, where$N
isinitialize
orterminate
, depending on the type of function generated.Periodic and aperiodic callable entry-point function interface
PeriodicAperiodicInterface
, which specifies naming ruleCD_$N
, where$N
is the name of the function for a sample period used in the model. Later, you override the default naming in the code mappings.Receiver service interface
ReceiverOutsideExe
specifies outside-execution data communication and naming ruleget_$X$N
, where$X
is the name of the callable function and$N
is the name of the receiver port for the receive operation.Sender service interface
SenderOutsideExe
specifies outside-execution data communication and naming rulesset_$X$N
andgetref_$X$N
for the by value and by reference functions, where$X
is the name of the callable function and$N
is the name of the sender port for the send operation.Parameter tuning service interface
ParameterTuningService
applies storage classTunableStruct
to data elements. To align with the platform requirements for naming tunable parameters, property Type Name is set toCD_tunable_T$M
and property Instance Name is set toCD_tunable
.Measurement service interface,
MeasurementService
, applies storage classMeasurementStruct
. To align with the platform requirements for naming state data, property Type Name is set toCD_measured_T$M
and property Instance Name is set toCD_measured
.
The during-execution and direct-access receiver and sender service interfaces are defined so that you can explore differences in the generated code for the different data communication methods.
Later, in Configure Component Model Interface for Code Generation, you map elements of the example model to interfaces defined by the service interface configuration in the dictionary.
For more information about defining code interface configurations, see Code Interface Definitions and Embedded Coder Dictionary.
Design Component Model
The design of example component model ComponentDeploymentMultirate
reflects the requirements listed in Assess Project Requirements.
The timing legend for the model identifies four callable functions: functions Initialize
and Terminate
and two discrete periodic functions. To open the timing legend, on the Debug tab, click Information Overlays > Timing Legend.
The integrator function receives the value of the state variable from the initialize function. Every 2 seconds the integrator function applies a forward integration method and transfers the result to the accumulator portion of the model, which executes every 1 second. The accumulator increments the value received by one, applies a forward integration method, and applies a gain value, which is tunable during execution. The accumulator maintains the most recent value of the state variable. See cgsl_0401: Modeling styles for component deployment.
The Initialize Function block represents a function that initializes a state variable
x
with a value read from nonvolatile memory before the algorithm executes. See cgsl_0404: Model startup and shutdown events by using Initialize Function and Terminate Function blocks for component deployment and cgsl_0411: Access nonvolatile memory by using Initialize Function and Terminate Function blocks.
The In Bus Element block
InBus.NVM
, which feeds the initialize function, represents a call to the platform service that reads values from nonvolatile memory. See cgsl_0402: Signal interfaces for component deployment and cgsl_0405: Data receive for component deployment.The inport for the model algorithm,
InBus.u
, represents a call to the platform receiver service for reading the input value from the target environment. See cgsl_0402: Signal interfaces for component deployment and cgsl_0405: Data receive for component deployment.The outport of the algorithm represents a call to the platform sender service for writing the output value of the Gain block. See cgsl_0402: Signal interfaces for component deployment and cgsl_0406: Data send for component deployment.
The Terminate Function block represents the function that writes the output of the accumulation to nonvolatile memory after the component stops executing. See cgsl_0404: Model startup and shutdown events by using Initialize Function and Terminate Function blocks for component deployment and cgsl_0411: Access nonvolatile memory by using Initialize Function and Terminate Function blocks.
The outport of the terminate function represents a call to the platform service that writes values to nonvolatile memory. See cgsl_0402: Signal interfaces for component deployment and cgsl_0406: Data send for component deployment.
For more information about modeling guidelines that can help you develop and deploy component models that use a service code interface, see Modeling Guidelines for Generated Code.
Configure Component Model Interface for Code Generation
For the code generator to produce interface code that aligns with the target environment requirements, there needs to be a mapping between the interface elements in the model and code interfaces. A model can be associated with one of two types of code interfaces: data interface or service interface. The code generator determines the type of code interface to apply automatically based on this information:
Modeling styles and patterns used in the model
Embedded Coder app Output selection
Type and contents of the Embedded Coder Dictionary associated with the model
For this example, the code generator applies a service interface because the model is configured to use a shared Embedded Coder Dictionary that defines a service interface configuration. The code generator uses the configured dictionary to establish default mappings that you can view and, in some cases, change by using the Code Mappings Editor or code mappings programming interface.
To open the Code Mappings editor and explore the interface settings, in the Embedded Coder app, under Code Interface, select Component Interface.
For elements for which the default interface aligns with the requirements, you do not need to make changes. Depending on your model, your code generation goals, and available interfaces, you might need to make some adjustments.
For this example, to meet the code interface requirements listed in Assess Project Requirements, these adjustments were made:
On the Functions tab, the default function customization template for the periodic algorithm functions produces function names
CD_step0
andCD_step1
. To align with target platform requirements for this function, the code mapping overrides the default function name, by specifying the function namesCD_accumulator
andCD_integrator
.On the Signals/States tab, a code identifier was specified for the state associated with the unit delay so the delay can be measured. The measurement service for the state is set to the default service. To see the identifier setting for the state, select the row for the state and click the pencil icon. In the dialog box that appears, see the value specified for the Identifier property,
delay
. The identifier informs the code generator how to represent the corresponding variable in the generated code.
In the Code Mappings editor, you can change the interfaces for the sender and receiver services by clicking the corresponding tab and, in the service column, selecting an alternative service. The default interfaces use the outside-execution data communication method. Alternative interfaces use during-execution and direct-access data communication. Consider changing the interfaces to view differences in the generated code. If you choose to change the interfaces, for each service called along the data path of a signal, set the interfaces such that they apply the same data communication method.
For guidance on setting up a model to use a service interface configuration, see cgsl_0414: Configure service interface for component model. For more information about model code mappings for service interfaces, see C Service Interfaces and Code Mappings Editor – C.
Assess Component Model Readiness for Code Generation
Run the Simulink Model Advisor to confirm that the component model passes component modeling guideline checks.
In the Embedded Coder app, select C/C++ Code Advisor.
In the System Selector dialog box, select
ComponentDeploymentMultirate
. Click Ok.In the Code Generation Advisor dialog box, in the left pane, select Model Advisor.
In the Model Advisor dialog box, in the left pane, expand
By Product
andEmbedded Coder
.Select the checks Check modeling style for component deployment, Check signal interfaces, and Check configuration for component deployment.
Run the selected checks.
If the checks do not pass, use the information provided to correct the conditions reported.
For more information, see Modeling Guidelines for Generated Code and Model Readiness for Code Generation.
Generate and Inspect Generated Component Code
In the toolstrip, click Generate Code. From the component model, the code generator creates the folder ComponentDeploymentMultirate_ert_rtw
in the current working folder and places source code files in that folder. The generated code is in two primary files: header file ComponentDeploymentMultirate.h
and source code file ComponentDeploymentMultirate.c
. Model data and entry-point functions are accessible to a caller by including the header file.
The code generator also produces an interface header file, which by default the code generator names services.h
. This file includes declarations for platform service interface functions called by the component code.
To inspect the generated code, use the generated Code Interface Report or, in the Embedded Coder app, use the Code view. The report is helpful for verifying that the generated interface code aligns with the target platform requirements.
For each callable entry-point function, the Code Interface Report provides:
Function prototype
Description
Header file name
Information about platform services called, such as prototypes and data communication method used
For each platform service called, the report provides:
Function prototypes
Data communication method used
Callable functions that call the service
For more information, see Analyze Generated Service Code Interface Report.
Header File
Header file ComponentDeploymentMultirate.h
includes header files and declares:
Structure for representing system signals and states
Structure for representing the zero-crossing state
Structures for representing measurable state data and tunable parameters
Default storage for block signals and states
Zero-crossing state
Callable entry-point functions
Storage class declarations for measurable state data and tunable parameters
#include "ComponentDeploymentMultirate_types.h" #include "rt_zcfcn.h" #include "services.h" #include "zero_crossing_types.h" typedef struct { double OutportBufferForDataTransfer[10]; double DiscreteTimeIntegrator1_DSTATE[10]; double DiscreteTimeIntegrator_DSTATE[10]; double DiscreteTimeIntegrator_PREV_U[10]; uint64_t Integrator_PREV_T; int32_t clockTickCounter; uint8_t DiscreteTimeIntegrator_SYSTEM_E; bool Integrator_RESET_ELAPS_T; } DW_ComponentDeploymentMultira_T; typedef struct { ZCSigState Integrator_Trig_ZCE; } PrevZCX_ComponentDeploymentMu_T; typedef struct { double delay[10]; } CD_measured_T; typedef struct { double k; } CD_tunable_T; extern DW_ComponentDeploymentMultira_T ComponentDeploymentMultirate_DW; extern PrevZCX_ComponentDeploymentMu_T ComponentDeploymentMult_PrevZCX; extern void CD_initialize(void); extern void CD_accumulator(void); extern void CD_integrator(void); extern void CD_terminate(void); extern CD_measured_T CD_measured; extern CD_tunable_T CD_tunable;
Source Code File
Source code file ComponentDeploymentMultirate.c
shows the code for the functions represented in the component model:
Accumulator function calls the data transfer service function
get_CD_accumulator_DataTransfer()
to read an input value. For each element of the bus signal, the function handles the rate transition as it applies a delay, discrete integration, and gain. The function also writes the output by calling sender service functiongetref_CD_accumulator_OutBus_y
and updates the unit delay and discrete integrator.Integrator function handles the rate transition between functions by calling the data transfer service function
set_CD_integrator_DataTransfer
. The integrator function then sets the elapsed time value to 0, if initialized, or to the current elapsed time. The integrator function calculates the current elapsed time by subtracting the cached elapsed time value (rtDWork.Integrator_PREV_T)
from the current tick value that the function gets by calling the timer service function get_tick_outside_tick_CD_integrator
. The code generator assumes that the resolution of the clock is the fundamental step size of the model. The function uses the calculated elapsed time to calculate the integration and calls the receiver service functionget_CD_integrator_InBus_u
to receive the next input value.Initialize function calls the receiver service function
get_CD_initialize_InBus_NVM
to initialize state data that is read from nonvolatile memory.Terminate function calls the sender service function
getref_CD_terminate_OutBus_NVM
to write the output of the accumulator function to memory.
#include <emmintrin.h> #include <stdint.h> #include <string.h> #include "zero_crossing_types.h" #include "ComponentDeploymentMultirate.h" #include "services.h" CD_measured_T CD_measured; CD_tunable_T CD_tunable = { 3.0 }; DW_ComponentDeploymentMultira_T ComponentDeploymentMultirate_DW; PrevZCX_ComponentDeploymentMu_T ComponentDeploymentMult_PrevZCX; void CD_accumulator(void) /* Sample time: [1.0s, 0.0s] */ { __m128d tmp; __m128d tmp_0; __m128d tmp_1; const double *tmpIrvIRead; int32_t i; tmpIrvIRead = get_CD_accumulator_DataTransfer(); for (i = 0; i <= 8; i += 2) { tmp = _mm_loadu_pd(&tmpIrvIRead[i]); tmp_0 = _mm_loadu_pd(&CD_measured.delay[i]); tmp_1 = _mm_loadu_pd (&ComponentDeploymentMultirate_DW.DiscreteTimeIntegrator1_DSTATE[i]); _mm_storeu_pd(&(getref_CD_accumulator_OutBus_y())[i], _mm_mul_pd(_mm_set1_pd (CD_tunable.k), tmp_1)); _mm_storeu_pd(&CD_measured.delay[i], tmp_1); _mm_storeu_pd (&ComponentDeploymentMultirate_DW.DiscreteTimeIntegrator1_DSTATE[i], _mm_add_pd(_mm_mul_pd(_mm_set1_pd(1.25), _mm_add_pd(tmp, tmp_0)), tmp_1)); } } void CD_integrator(void) /* Sample time: [2.0s, 0.0s] */ { double OutportBufferForDataTransfer; double *tmp; uint64_t Integrator_ELAPS_T; int32_t rtb_PulseGenerator; ZCEventType zcEvent; tmp = set_CD_integrator_DataTransfer(); rtb_PulseGenerator = ((ComponentDeploymentMultirate_DW.clockTickCounter < 5) && (ComponentDeploymentMultirate_DW.clockTickCounter >= 0)); if (ComponentDeploymentMultirate_DW.clockTickCounter >= 9) { ComponentDeploymentMultirate_DW.clockTickCounter = 0; } else { ComponentDeploymentMultirate_DW.clockTickCounter++; } zcEvent = rt_ZCFcn(RISING_ZERO_CROSSING, &ComponentDeploymentMult_PrevZCX.Integrator_Trig_ZCE, ((double)rtb_PulseGenerator)); if (zcEvent != NO_ZCEVENT) { if (ComponentDeploymentMultirate_DW.Integrator_RESET_ELAPS_T) { Integrator_ELAPS_T = 0ULL; } else { Integrator_ELAPS_T = (uint64_t)get_tick_outside_tick_CD_integrator() - ComponentDeploymentMultirate_DW.Integrator_PREV_T; } ComponentDeploymentMultirate_DW.Integrator_PREV_T = get_tick_outside_tick_CD_integrator(); ComponentDeploymentMultirate_DW.Integrator_RESET_ELAPS_T = false; for (rtb_PulseGenerator = 0; rtb_PulseGenerator < 10; rtb_PulseGenerator++) { if (ComponentDeploymentMultirate_DW.DiscreteTimeIntegrator_SYSTEM_E != 0) { OutportBufferForDataTransfer = ComponentDeploymentMultirate_DW.DiscreteTimeIntegrator_DSTATE[rtb_PulseGenerator]; } else { OutportBufferForDataTransfer = 2.5 * (double)Integrator_ELAPS_T * ComponentDeploymentMultirate_DW.DiscreteTimeIntegrator_PREV_U[rtb_PulseGenerator] + ComponentDeploymentMultirate_DW.DiscreteTimeIntegrator_DSTATE[rtb_PulseGenerator]; } ComponentDeploymentMultirate_DW.OutportBufferForDataTransfer[rtb_PulseGenerator] = OutportBufferForDataTransfer; ComponentDeploymentMultirate_DW.DiscreteTimeIntegrator_DSTATE[rtb_PulseGenerator] = OutportBufferForDataTransfer; ComponentDeploymentMultirate_DW.DiscreteTimeIntegrator_PREV_U[rtb_PulseGenerator] = (get_CD_integrator_InBus_u())[rtb_PulseGenerator]; } ComponentDeploymentMultirate_DW.DiscreteTimeIntegrator_SYSTEM_E = 0U; } memcpy(&tmp[0], &ComponentDeploymentMultirate_DW.OutportBufferForDataTransfer [0], 10U * sizeof(double)); } void CD_initialize(void) { ComponentDeploymentMult_PrevZCX.Integrator_Trig_ZCE = UNINITIALIZED_ZCSIG; memcpy(&CD_measured.delay[0], &(get_CD_initialize_InBus_NVM())[0], 10U * sizeof(double)); ComponentDeploymentMultirate_DW.Integrator_RESET_ELAPS_T = true; ComponentDeploymentMultirate_DW.DiscreteTimeIntegrator_SYSTEM_E = 1U; } void CD_terminate(void) { memcpy(&(getref_CD_terminate_OutBus_NVM())[0], &CD_measured.delay[0], 10U * sizeof(double)); }
Services Header File
The services header file services.h
declares prototypes for calling timer, data transfer, receiver, and sender services that run as platform software in a target environment.
/* timer service interfaces */ extern uint64_t get_tick_outside_tick_CD_integrator(void); /* data transfer service interfaces */ extern const double * get_CD_accumulator_DataTransfer(void); extern double * set_CD_integrator_DataTransfer(void); /* receiver service interfaces */ extern const double * get_CD_integrator_InBus_u(void); extern const double * get_CD_initialize_InBus_NVM(void); /* sender service interfaces */ extern double * getref_CD_accumulator_OutBus_y(void); extern double * getref_CD_terminate_OutBus_NVM(void);
Test Generated Service Interface Code by Using SIL and PIL Simulations
Consider using a software-in-the-loop (SIL) and processor-in-the-loop (PIL) simulations to verify the generated code. A SIL simulation compiles and runs the generated code on your development computer. A PIL simulation compiles and runs the generated code on a target computer. During a SIL and PIL simulations, you can collect code coverage and execution-time metrics for the generated code. For example, you can use the SIL simulation results to check for numerical equivalence between a normal mode simulation and the SIL simulation.
To test numeric equivalence between the model and generated code:
Run a normal mode simulation and capture the results.
Run a SIL or PIL simulation and capture the results.
Compare the results from steps 1 and 2.
To run SIL and PIL simulations on the example model, you can use the example test harness model ComponentDeploymentMultirateHarness.slx
and data file ComponentDeploymentBus.mat
.
open_system('ComponentDeploymentMultirateHarness');
For more information, see Software-in-the-Loop Simulation and Processor-in-the-Loop Simulation.
Get Meta-Information About Generated Code Interface for Integration
By default, the code generator creates a code descriptor file (codedescriptor.dmr
) in the build folder. That file contains meta-information about the generated code, including:
Data interfaces (inports, outports, parameters, datastores, and internal data)
Function interfaces (initialize, output, update, and terminate)
Execution information for the data and function interfaces, such as timing requirements
Model hierarchy information and information for referenced models
You can use the code descriptor programming interface to get access to the contents of the code descriptor file and use the results to confirm that generated interfaces meet integration requirements. You can also use the programming interface to provide input to tools that generate interfaces for target platform services.
For more information, see Get Code Description of Generated Code and coder.codedescriptor.CodeDescriptor
class.
Integrate Generated Component Code with Target Environment Code
To integrate the generated component code with a main
function and other target environment code, you must:
Match the data and function interfaces of the generated code with other interfaces of existing system code.
Connect input data.
Connect output data.
Access other data, such as block state values, local parameters, and time.
The model used in this example is designed and configured such that the generated code aligns with the code interfaces of the target environment.
The target environment software calls the generated entry-point functions, which provide input signal data and scheduling information.
The generated algorithm computes output data that the calling environment uses.
The set of input and output data and the data access mechanisms constitute the interfaces of the entry-point functions.
In a model, root-level inports and outports represent the primary inputs and outputs of the component algorithm. By default, the code generator aggregates this input and output data into standard structures.
You can generate code for a component model and compile a component model library by clearing model configuration parameter Generate code only and initiating a model build. If code for the component model is already generated, you can build a component model library by using the codebuild
command with the path codeGenerationFolder
/
modelBuildFolder
/services/lib
. You can link the generated component model library with a main
function and other target environment code to create an executable program.
For the component model library, you can also create a:
CMake configuration (
CMakeLists.txt
) file by using thecodebuild
functionZIP file by selecting model configuration parameter Package code and artifacts and using the
packNGo
function
For more information about model build options, see Manage Build Process Folders, Approaches for Building Code Generated from Simulink Models, Configure CMake Build Process, Compile Code in Another Development Environment, and Deploy Component Algorithm as Component Model Library by Using CMake.
For examples, see Configure Generated Code According to Interface Control Document Specifications and Integrate External Application Code with Code Generated from PID Controller.
For more information, see Deployment, Integration, and Supported Hardware.