Create Service Interface Configuration Programmatically
This example shows how to programmatically create a service interface configuration in an Embedded Coder Dictionary. When you deploy your generated component code within a target environment, your generated code interacts with the target environment and other components by calling the environment services. The target environment executes your code by calling the generated functions. To configure your generated code to match the interfaces of the environment services, you define the service interfaces in an Embedded Coder Dictionary and then apply them to the corresponding model elements. To automate the creation of service interface definitions in an Embedded Coder Dictionary, use the programming interface described in this example.
In this example you:
Create a shared Embedded Coder Dictionary in a Simulink data dictionary (SLDD) file to contain the service interface configuration.
Create definitions for service interfaces including sender, receiver, timer, measurement, and other services.
Define the interfaces for the generated functions.
Verify the interfaces that you created by applying the definitions to a model and generate the code.
Create Embedded Coder Dictionary for Service Interface Configuration
Store the name of the dictionary file in the variable dictFileName
.
dictFileName = "serviceInterfaces.sldd";
Create an SLDD file to store the service interface definitions. Storing the definitions in an SLDD file enables you to share the definitions with multiple models and is required for service interface definitions.
dictObj = Simulink.data.dictionary.create(dictFileName);
Create an Embedded Coder Dictionary in the SLDD file. When you create the dictionary, represent it by using a coder.Dictionary
object. You use the object to programmatically perform operations on the entire Embedded Coder Dictionary and to access sections of the dictionary.
coderDictObj = coder.dictionary.create(dictFileName,"ServiceInterface")
coderDictObj = Dictionary with properties and Sections: ServicesHeaderFileName: 'services.h' InitTermFunctions: [1x1 coder.dictionary.Section] PeriodicAperiodicFunctions: [1x1 coder.dictionary.Section] DataReceiverInterfaces: [1x1 coder.dictionary.Section] DataSenderInterfaces: [1x1 coder.dictionary.Section] DataTransferInterfaces: [1x1 coder.dictionary.Section] TimerInterfaces: [1x1 coder.dictionary.Section] ParameterTuningInterfaces: [1x1 coder.dictionary.Section] ParameterArgumentTuningInterfaces: [1x1 coder.dictionary.Section] MeasurementInterfaces: [1x1 coder.dictionary.Section] SubcomponentInitTermFunctions: [1x1 coder.dictionary.Section] SubcomponentPeriodicAperiodicFunctions: [1x1 coder.dictionary.Section] SharedUtilityFunctions: [1x1 coder.dictionary.Section] InternalData: [1x1 coder.dictionary.Section] Constants: [1x1 coder.dictionary.Section] StorageClasses: [1x1 coder.dictionary.Section] DataMemorySections: [1x1 coder.dictionary.Section] FunctionMemorySections: [1x1 coder.dictionary.Section]
The coder.Dictionary
object contains coder.dictionary.Section
objects that represent the sections of an Embedded Coder Dictionary. There is one section for each category of code interface definitions, such as sender and receiver interfaces, timer interfaces, storage classes, data memory sections, and other categories. A coder.dictionary.Section
object contains coder.dictionary.Entry
objects that represent the definitions in that section. To edit a definition and access its properties, use the coder.dictionary.Entry
object that represents it.
Define Service Interfaces
To define the service interfaces that your model uses for deployment, add service interface definitions to the sections of the Embedded Coder Dictionary. To define a service interface programmatically:
Access the
coder.dictionary.Section
object for the interface category by using thecoder.Dictionary
object and thegetSection
method.Using the
coder.dictionary.Section
object, add acoder.dictionary.Entry
object that represents the service interface.Use the
coder.dictionary.Entry
object to edit the interface definition to match the interface of the service that the environment provides.
Sender and Receiver Interfaces
The generated component code receives data from and sends data to other components by calling the target environment receiver and sender services. Define how the generated code calls the sender and receiver services by adding service interface definitions to the sender and receiver interface sections of the dictionary.
For this example, add sender and receiver interfaces that access data during function execution and use the name format modelnam
e_
elementname
_sender
and modelnam
e_
elementname
_receiver
.
senderInterfaces = getSection(coderDictObj,"DataSenderInterfaces"); dataSender = addEntry(senderInterfaces,"SenderDuringExecution"); set(dataSender,DataCommunicationMethod="DuringExecution"); set(dataSender,FunctionNamingRuleForValue="$R_$N_sender"); receiverInterfaces = getSection(coderDictObj,"DataReceiverInterfaces"); dataReceiver = addEntry(receiverInterfaces,"ReceiverDuringExecution"); set(dataReceiver,DataCommunicationMethod="DuringExecution"); set(dataReceiver,FunctionName="$R_$N_receiver");
Data Transfer Interfaces
Execution entry-point functions communicate with other execution entry-point functions by calling the target environment data transfer services. Define how the generated code calls the data transfer services by adding interface definitions to the data transfer interfaces section of the dictionary.
For this example, add a definition for a data transfer service interface that communicates data with other functions immediately during execution. Name the generated functions modelelementname
_data_transfer_receiver
and modelelementname_
data_transfer_sender
. Include name-mangling text at the end of the names to avoid collisions.
dataTransferInterfaces = getSection(coderDictObj,"DataTransferInterfaces"); dataTransfer = addEntry(dataTransferInterfaces,"DataTransferDuringExecution"); set(dataTransfer,DataCommunicationMethod="DuringExecution"); set(dataTransfer,DataReceiverFunctionName="$N_data_transfer_receiver_$M"); set(dataTransfer,DataSenderFunctionName="$N_data_transfer_sender_$M");
Parameter Tuning Interfaces
Some components contain parameters or parameter arguments whose values must be tunable during program execution. To configure parameters and parameter arguments for tuning, define how to store the values in memory by selecting a storage class in the dictionary. To see the available storage classes, use the find
method for the storage classes section.
storageClasses = getSection(coderDictObj,"StorageClasses");
storageClassEntries = find(storageClasses);
storageClassEntries(:).Name
ans = 'ExportedGlobal'
ans = 'ImportedExtern'
ans = 'ImportedExternPointer'
ans = 'MeasurementStruct'
ans = 'ParamStruct'
Some storage classes do not support parameter interfaces. To see only storage classes that do support parameters, use tab completion when calling set
for the parameter interface entry.
For this example, use the built-in storage class ParamStruct
to store the tunable parameters and parameter arguments in structures in global memory. For more configuration options, you can add your own storage classes to the dictionary.
paramTuningInterfaces = getSection(coderDictObj,"ParameterTuningInterfaces"); paramInterface = addEntry(paramTuningInterfaces,"ParamExportedGlobal"); set(paramInterface,StorageClass="ParamStruct"); paramArgTuningInterfaces = getSection(coderDictObj,"ParameterArgumentTuningInterfaces"); paramArgInterface = addEntry(paramArgTuningInterfaces,"ParamArgExportedGlobal"); set(paramArgInterface,StorageClass="ParamStruct");
Measurement Interfaces
Some components contain signals, states, or datastores whose values must be measurable during program execution. To configure these elements for measurement, define how to store the values in memory by selecting a storage class in the dictionary. To see the available storage classes, use the find
method for the storage classes section.
storageClasses = getSection(coderDictObj,"StorageClasses");
storageClassEntries = find(storageClasses);
storageClassEntries(:).Name
ans = 'ExportedGlobal'
ans = 'ImportedExtern'
ans = 'ImportedExternPointer'
ans = 'MeasurementStruct'
ans = 'ParamStruct'
For this example, use the built-in storage class MeasurementStruct
to store the measurable data in structures in global memory. For more configuration options, you can add your own storage classes to the dictionary.
measInterfaces = getSection(coderDictObj,"MeasurementInterfaces"); measInterface = addEntry(measInterfaces,"MeasExportedStruct"); set(measInterface,StorageClass="MeasurementStruct");
Timer Interfaces
Generated component code accesses the function clock tick provided by the target environment by calling the environment timer service. Define how the generated code calls the timer service by adding the timer service interface definition to the timer services section of the dictionary.
For this example, add a timer service interface that accesses the clock tick immediately during function execution. Use the name format TimerDuringExecution_
currentfunctionname
_get_tick
. Because the Embedded Coder Dictionary includes multiple timer interface definitions, you must include $G
in the function naming rule. $G
represents the name of the service interface definition name, which is TimerDuringExecution
for this example.
timerInterfaces = getSection(coderDictObj,"TimerInterfaces"); timer = addEntry(timerInterfaces,"TimerDuringExecution"); set(timer,DataCommunicationMethod="DuringExecution"); set(timer,FunctionClockTickFunctionName="$G_$X_get_tick");
Define Generated Function Interfaces
To define the interfaces of the generated functions, add function definitions to the sections of the Embedded Coder Dictionary. To define a function interface programmatically:
Access the
coder.dictionary.Section
object for the function category by using thecoder.Dictionary
object and thegetSection
method.Using the
coder.dictionary.Section
object, add acoder.dictionary.Entry
object that represents the function interface.Use the
coder.dictionary.Entry
object to edit the interface definition to specify the function interface.
Define Initialize and Terminate Function Interfaces
When your model includes Initialize Function and Terminate Function blocks, you can control the interfaces of the generated initialize and terminate functions. Define the function interfaces by adding function customization templates to the initialize and terminate functions section of the dictionary.
For this example, add a function customization template that generates functions according to the naming rule model
_initialize
and model
_terminate
.
initTermFunctions = getSection(coderDictObj,"InitTermFunctions"); ITFunc = addEntry(initTermFunctions,"ModelInitTerm"); set(ITFunc,FunctionName="$R_$N");
Define Periodic and Aperiodic Function Interfaces
Your target environment executes the generated code by calling the generated execution entry-point functions. Execution functions are periodic for rate-based models and aperiodic for entry-point function models. Define the interfaces of execution entry-point functions by adding function customization templates to the periodic and aperiodic functions section of the dictionary.
For this example, add a function customization template that generates execution functions according to the naming rule model
_
function
_
manglingtext
.
execFunctions = getSection(coderDictObj,"PeriodicAperiodicFunctions"); execFunc = addEntry(execFunctions,"ModelExecution"); set(execFunc,FunctionName="$R_$N_$M");
Define Interfaces for Functions of Subcomponent Models
When your model is deployed as a subcomponent, you control the interfaces of the initialize, terminate, and execution entry-point functions by using dictionary sections for subcomponents.
For this example, add templates with these naming rules:
subcomp
_initialize
andsubcomp
_terminate
for the initialize and terminate functions.subcomp
_
function
_
manglingtext
for the periodic and aperiodic functions.
subcompInitTermFunctions = getSection(coderDictObj,"SubcomponentInitTermFunctions"); Subcomp_ITFuncs = addEntry(subcompInitTermFunctions,"SubcompInitTerm"); set(Subcomp_ITFuncs,FunctionName="Subcomp_$R_$N"); subcompExecFunctions = getSection(coderDictObj,"SubcomponentPeriodicAperiodicFunctions"); subcompExecFunc = addEntry(subcompExecFunctions,"SubcompExecution"); set(subcompExecFunc,FunctionName="Subcomp_$R_$N_$M");
Define Shared Utility Function Interfaces
Define the interfaces of shared utility functions by adding function customization templates to the shared utility functions section of the dictionary.
For this example, add a function customization template that generates shared utility functions according to the naming rule model
_
function
_
checksum
. The checksum avoids naming collisions and is required for shared utility functions.
sharedUtilFunctions = getSection(coderDictObj,"SharedUtilityFunctions"); sharedUtilFunc = addEntry(sharedUtilFunctions,"SharedUtilFunc"); set(sharedUtilFunc,FunctionName="$R_$N_$C");
Apply Service and Function Interface Definitions
To apply the service and function interface definitions to a model:
Select the interface definitions as the default definitions for the categories in the dictionary.
Configure the model to use the Embedded Coder Dictionary.
The code generator uses the selected interfaces by default for each model that uses the Embedded Coder Dictionary. You can configure individual elements in a model to use other service interface definitions from the dictionary by using the code mappings programming interface or the Code Mappings editor.
Select Default Interfaces
Select the service and function interfaces that you created as the default interfaces for the corresponding sections in the Embedded Coder Dictionary. Then use the object dictObj
to save the dictionary.
setDictionaryDefault(coderDictObj,DataSenderInterfaces="SenderDuringExecution"); setDictionaryDefault(coderDictObj,DataReceiverInterfaces="ReceiverDuringExecution"); setDictionaryDefault(coderDictObj,DataTransferInterfaces="DataTransferDuringExecution"); setDictionaryDefault(coderDictObj,ParameterTuningInterfaces="ParamExportedGlobal"); setDictionaryDefault(coderDictObj,ParameterArgumentTuningInterfaces="ParamArgExportedGlobal"); setDictionaryDefault(coderDictObj,MeasurementInterfaces="MeasExportedStruct"); setDictionaryDefault(coderDictObj,TimerInterfaces="TimerDuringExecution"); setDictionaryDefault(coderDictObj,InitTermFunctions="ModelInitTerm"); setDictionaryDefault(coderDictObj,PeriodicAperiodicFunctions="ModelExecution"); setDictionaryDefault(coderDictObj,SharedUtilityFunctions="SharedUtilFunc"); setDictionaryDefault(coderDictObj,SubcomponentInitTermFunctions="SubcompInitTerm"); setDictionaryDefault(coderDictObj,SubcomponentPeriodicAperiodicFunctions="SubcompExecution"); saveChanges(dictObj);
Configure Model to Use Embedded Coder Dictionary
For this example, configure the model ComponentDeploymentFcn
to use the service interface configuration.
model = "ComponentDeploymentFcn";
load_system(model);
Configure the model to use the Embedded Coder Dictionary that you created.
set_param(model,EmbeddedCoderDictionary=dictFileName);
Generate code from the model.
slbuild(model,GenerateCodeOnly=true);
### Starting build procedure for: ComponentDeploymentFcn ### Successful completion of code generation for: ComponentDeploymentFcn Build Summary Top model targets: Model Build Reason Status Build Duration ============================================================================================================ ComponentDeploymentFcn Information cache folder or artifacts were missing. Code generated. 0h 0m 29.878s 1 of 1 models built (0 models already up to date) Build duration: 0h 0m 31.891s
The generated code calls the services by using the interfaces that you defined. For example, the CD_accumulator
function receives a data transfer by calling DataTransfer_data_transfer_receiver
. The function sends data by calling ComponentDeploymentFcn_OutBus_y_sender
.
file = fullfile("ComponentDeploymentFcn_ert_rtw","ComponentDeploymentFcn.c"); coder.example.extractLines(file,"void CD_accumulator","void CD_integrator");
void CD_accumulator(void) /* Explicit Task: Periodic */ { double rtb_Sum[10]; double rtb_Sum_0; int32_t i; /* DataTransferBlock generated from: '<Root>/Integrator' */ DataTransfer_data_transfer_receiver_(rtb_Sum); /* RootInportFunctionCallGenerator generated from: '<Root>/Periodic' incorporates: * SubSystem: '<Root>/Accumulator' */ for (i = 0; i < 10; i++) { /* Sum: '<S1>/Sum' incorporates: * DataTransferBlock generated from: '<Root>/Integrator' * UnitDelay: '<S1>/Unit Delay' */ rtb_Sum_0 = rtb_Sum[i] + ComponentDeploymen_Measurements.delay[i]; rtb_Sum[i] = rtb_Sum_0; /* Outport generated from: '<Root>/Out' incorporates: * Gain: '<S1>/Gain' */ rtDWork.OutBus_y[i] = ComponentDeploymentFcn_Params.k * rtb_Sum_0; /* Update for UnitDelay: '<S1>/Unit Delay' */ ComponentDeploymen_Measurements.delay[i] = rtb_Sum_0; } /* End of Outputs for RootInportFunctionCallGenerator generated from: '<Root>/Periodic' */ /* Outport generated from: '<Root>/Out' */ ComponentDeploymentFcn_OutBus_y_sender(&rtDWork.OutBus_y[0]); } /* Output function */
Close the model without saving it. The model is configured to delete the dictionary upon closing. This way you can run the example multiple times without getting an error when trying to create the dictionary.
close_system(model,false)
See Also
Embedded Coder Dictionary | Code Mappings
Editor – C | coder.Dictionary
| coder.mapping.api.CodeMapping
| coder.dictionary.Section
| coder.dictionary.Entry