Main Content

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:

  1. Create a shared Embedded Coder Dictionary in a Simulink data dictionary (SLDD) file to contain the service interface configuration.

  2. Create definitions for service interfaces including sender, receiver, timer, measurement, and other services.

  3. Define the interfaces for the generated functions.

  4. 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

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.

dictionaryFile = Simulink.data.dictionary.create("serviceInterfaces.sldd");

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.

coderDictionary = coder.dictionary.create("serviceInterfaces.sldd","ServiceInterface")
coderDictionary = 
  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]
           SubcomponentEntryFunctions: [1x1 coder.dictionary.Section]
               SharedUtilityFunctions: [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:

  1. Access the coder.dictionary.Section object for the interface category by using the coder.Dictionary object and the getSection method.

  2. Using the coder.dictionary.Section object, add a coder.dictionary.Entry object that represents the service interface.

  3. 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 modelname_elementname_sender and modelname_elementname_receiver.

senderInterfaces = getSection(coderDictionary,"DataSenderInterfaces");
dataSender = addEntry(senderInterfaces,"SenderDuringExecution");
set(dataSender,"DataCommunicationMethod","DuringExecution");
set(dataSender,"FunctionNamingRuleForValue","$R_$N_sender");

receiverInterfaces = getSection(coderDictionary,"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(coderDictionary,"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(coderDictionary,"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(coderDictionary,"ParameterTuningInterfaces");
paramInterface = addEntry(paramTuningInterfaces,"ParamExportedGlobal");
set(paramInterface,"StorageClass","ParamStruct");

paramArgTuningInterfaces = getSection(coderDictionary,"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(coderDictionary,"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(coderDictionary,"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(coderDictionary,"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:

  1. Access the coder.dictionary.Section object for the function category by using the coder.Dictionary object and the getSection method.

  2. Using the coder.dictionary.Section object, add a coder.dictionary.Entry object that represents the function interface.

  3. 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 initalize 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(coderDictionary,"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(coderDictionary,"PeriodicAperiodicFunctions");
execFunc = addEntry(execFunctions,"ModelExecution");
set(execFunc,"FunctionName","$R_$N_$M");

Define Subcomponent Entry Function Interfaces

Define the interfaces of subcomponent entry-point functions by adding function customization templates to the sumcomponent functions section of the dictionary.

For this example, add a function customization template that generates subcomponent functions according to the naming rule model_function.

subcompFunctions = getSection(coderDictionary,"SubcomponentEntryFunctions");
subcompFunc = addEntry(subcompFunctions,"SubcompFunc");
set(subcompFunc,"FunctionName","$R_$N");

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(coderDictionary,"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:

  1. Select the interface definitions as the default definitions for the categories in the dictionary.

  2. 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.

setDictionaryDefault(coderDictionary,"DataSenderInterfaces","SenderDuringExecution");
setDictionaryDefault(coderDictionary,"DataReceiverInterfaces","ReceiverDuringExecution");
setDictionaryDefault(coderDictionary,"DataTransferInterfaces","DataTransferDuringExecution");
setDictionaryDefault(coderDictionary,"ParameterTuningInterfaces","ParamExportedGlobal");
setDictionaryDefault(coderDictionary,"ParameterArgumentTuningInterfaces","ParamArgExportedGlobal");
setDictionaryDefault(coderDictionary,"MeasurementInterfaces","MeasExportedStruct");
setDictionaryDefault(coderDictionary,"TimerInterfaces","TimerDuringExecution");
setDictionaryDefault(coderDictionary,"InitTermFunctions","ModelInitTerm");
setDictionaryDefault(coderDictionary,"PeriodicAperiodicFunctions","ModelExecution");
setDictionaryDefault(coderDictionary,"SubcomponentEntryFunctions","SubcompFunc");
setDictionaryDefault(coderDictionary,"SharedUtilityFunctions","SharedUtilFunc");

Configure Model to Use Embedded Coder Dictionary

For this example, configure the model ComponentDeploymentFcn to use the service interface configuration.

model = "ComponentDeploymentFcn";
open_system(model);

Configure the model to use the Embedded Coder Dictionary that you created.

set_param(model,"EmbeddedCoderDictionary","serviceInterfaces.sldd");

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 built:

Model                   Action           Rebuild Reason                                    
===========================================================================================
ComponentDeploymentFcn  Code generated.  Code generation information file does not exist.  

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 15.099s

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)
{
  double OutBus_y[10];
  double delay;
  int32_t i;
  DataTransfer_data_transfer_receiver_(OutBus_y);
  for (i = 0; i < 10; i++) {
    delay = OutBus_y[i] + ComponentDeploymen_Measurements.delay[i];
    ComponentDeploymen_Measurements.delay[i] = delay;
    OutBus_y[i] = ComponentDeploymentFcn_Params.k * delay;
  }

  ComponentDeploymentFcn_OutBus_y_sender(&OutBus_y[0]);
}

See Also

| | | | |

Related Topics