How Generated Code Stores Internal Signal, State, and Parameter Data
To calculate outputs from inputs, the generated code stores some internal data in global memory. A signal that does not connect to a root-level input or output (Inport or Outport block) is internal data.
Internal data can also include:
A block state such as the state of a Unit Delay block. The algorithm must retain the state value between execution cycles, so the generated code typically stores states in global memory (for example, as a global variable or a field of a global structure variable).
A block parameter, such as the Gain parameter of a Gain block, whose value the code generator cannot inline in the code. For example, the code generator cannot inline the value of a nonscalar parameter.
The status indicator of a conditionally executed subsystem such as an enabled subsystem.
For more efficient code, you can configure optimizations such as Configuration Parameters > Default parameter behavior and Configuration Parameters > Signal storage reuse that attempt to eliminate storage for internal data. However, the optimizations cannot eliminate storage for some data, which consume memory in the generated code.
When you understand the default format in which the generated code stores internal data, you can:
Make signals accessible and parameters tunable by default. You can then interact with and monitor the code during execution.
Generate efficient production code by eliminating storage for internal data and, depending on your hardware and build toolchain, controlling the placement in memory of data that the optimizations cannot eliminate.
Promote pieces of internal data to the model interface so that other components and systems can access that data.
For information about how the generated code exchanges data with a calling environment through interfaces, see How Generated Code Exchanges Data with an Environment.
Internal Data in the Generated Code
This example shows how the generated code stores internal data such as block states.
Explore Example Model
Open the example model RollAxisAutopilot
.
open_system('RollAxisAutopilot')
The model contains internal signals that do not connect to root-level Inport or Outport blocks. Some of the signals have a name, such as the phiCmd
signal.
The model also contains some blocks that maintain state data. For example, in the BasicRollMode
subsystem, a Discrete-Time Integrator block labeled Integrator
maintains a state.
In the model, set Configuration Parameters > Code Generation > System target file to grt.tlc
.
set_param('RollAxisAutopilot','SystemTargetFile','grt.tlc')
Inspect the setting for Configuration Parameters > Code Generation > Interface > Code interface packaging. The setting Nonreusable function
means that the generated code is not reusable (reentrant).
For this example, generate simpler code by clearing Configuration Parameters > Code Generation > Interface > Advanced parameters > Mat-file logging.
set_param('RollAxisAutopilot','MatFileLogging','off')
Generate Nonreusable Code
Set these configuration parameters:
Set Default parameter behavior to
Tunable
.Clear Signal storage reuse.
set_param('RollAxisAutopilot','DefaultParameterBehavior','Tunable',... 'OptimizeBlockIOStorage','off')
Generate code from the model.
slbuild('RollAxisAutopilot')
### Starting build procedure for: RollAxisAutopilot ### Successful completion of build procedure for: RollAxisAutopilot Build Summary Top model targets: Model Build Reason Status Build Duration ==================================================================================================================== RollAxisAutopilot Information cache folder or artifacts were missing. Code generated and compiled. 0h 0m 16.887s 1 of 1 models built (0 models already up to date) Build duration: 0h 0m 18.08s
The file RollAxisAutopilot.h
defines several structure types that represent internal data. For example, the block input and output structure defines one field for each internal signal in the model. Each field name derives from the name of the block that generates the signal or, if you specify a name for the signal, from the name of the signal.
file = fullfile('RollAxisAutopilot_grt_rtw','RollAxisAutopilot.h'); coder.example.extractLines(file,... '/* Block signals (default storage) */','} B_RollAxisAutopilot_T;',1,1)
/* Block signals (default storage) */ typedef struct { real32_T phiCmd; /* '<Root>/ModeSwitch' */ real32_T hdgError; /* '<S2>/Sum' */ real32_T DispGain; /* '<S2>/DispGain' */ real32_T Product; /* '<S2>/Product' */ real32_T Abs; /* '<S3>/Abs' */ real32_T FixPtUnitDelay1; /* '<S4>/FixPt Unit Delay1' */ real32_T Xnew; /* '<S4>/Enable' */ real32_T TKSwitch; /* '<S3>/TKSwitch' */ real32_T RefSwitch; /* '<S3>/RefSwitch' */ real32_T Integrator; /* '<S1>/Integrator' */ real32_T DispLimit; /* '<S1>/DispLimit' */ real32_T Sum; /* '<S1>/Sum' */ real32_T DispGain_f; /* '<S1>/DispGain' */ real32_T RateLimit; /* '<S1>/RateLimit' */ real32_T Sum1; /* '<S1>/Sum1' */ real32_T RateGain; /* '<S1>/RateGain' */ real32_T Sum2; /* '<S1>/Sum2' */ real32_T CmdLimit; /* '<S1>/CmdLimit' */ real32_T IntGain; /* '<S1>/IntGain' */ boolean_T NotEngaged; /* '<S3>/NotEngaged' */ boolean_T TKThreshold; /* '<S3>/TKThreshold' */ boolean_T RefThreshold2; /* '<S3>/RefThreshold2' */ boolean_T RefThreshold1; /* '<S3>/RefThreshold1' */ boolean_T Or; /* '<S3>/Or' */ boolean_T NotEngaged_e; /* '<S1>/NotEngaged' */ } B_RollAxisAutopilot_T;
The file defines a structure type, the DWork structure, to represent block states such as the state of the Discrete-Time Integrator block.
coder.example.extractLines(file,... '/* Block states (default storage) for system','} DW_RollAxisAutopilot_T;',1,1)
/* Block states (default storage) for system '<Root>' */ typedef struct { real32_T FixPtUnitDelay1_DSTATE; /* '<S4>/FixPt Unit Delay1' */ real32_T Integrator_DSTATE; /* '<S1>/Integrator' */ int8_T Integrator_PrevResetState; /* '<S1>/Integrator' */ } DW_RollAxisAutopilot_T;
The file defines a structure type to represent parameter data. Each tunable block parameter in the model, such as the Gain parameter of a Gain block, appears as a field of this structure. If a block parameter acquires its value from a MATLAB® variable or a Simulink.Parameter
object, the variable or object appears as a field, not the block parameter.
The file also defines a structure type, the real-time model data structure, whose single field represents a run-time indication of whether the generated code has encountered an error during execution.
coder.example.extractLines(file,'/* Real-time Model Data Structure */',... '/* Block parameters (default storage) */',1,0)
/* Real-time Model Data Structure */ struct tag_RTM_RollAxisAutopilot_T { const char_T *errorStatus; };
For the structure type that represents the real-time model data structure, the file RollAxisAutopilot_types.h
creates an alias that the generated code later uses to allocate memory for the structure.
file = fullfile('RollAxisAutopilot_grt_rtw','RollAxisAutopilot_types.h'); coder.example.extractLines(file,'/* Forward declaration for rtModel */',... 'RT_MODEL_RollAxisAutopilot_T;',1,1)
/* Forward declaration for rtModel */ typedef struct tag_RTM_RollAxisAutopilot_T RT_MODEL_RollAxisAutopilot_T;
Using these structure types, the file RollAxisAutopilot.c
defines (allocates memory for) global structure variables that store internal data for the generated algorithm. The file also defines variables that represent the real-time model data structure and a pointer to the structure.
file = fullfile('RollAxisAutopilot_grt_rtw','RollAxisAutopilot.c'); coder.example.extractLines(file,'/* Block signals (default storage) */',... '= &RollAxisAutopilot_M_;',1,1)
/* Block signals (default storage) */ B_RollAxisAutopilot_T RollAxisAutopilot_B; /* Block states (default storage) */ DW_RollAxisAutopilot_T RollAxisAutopilot_DW; /* External inputs (root inport signals with default storage) */ ExtU_RollAxisAutopilot_T RollAxisAutopilot_U; /* External outputs (root outports fed by signals with default storage) */ ExtY_RollAxisAutopilot_T RollAxisAutopilot_Y; /* Real-time model */ static RT_MODEL_RollAxisAutopilot_T RollAxisAutopilot_M_; RT_MODEL_RollAxisAutopilot_T *const RollAxisAutopilot_M = &RollAxisAutopilot_M_;
The model step
function, which represents the primary model algorithm, uses a void void
interface (with no arguments).
coder.example.extractLines(file,... '/* Model step function */','void RollAxisAutopilot_step(void)',1,1)
/* Model step function */ void RollAxisAutopilot_step(void)
In the function definition, the algorithm performs calculations and stores intermediate results in the signal and state structures by directly accessing the global variables. The algorithm also reads parameter data from the corresponding global variable. For example, in the BasicRollMode
subsystem, the code generated for the Integrator
block reads and writes signal, state, and parameter data from the structures.
coder.example.extractLines(file,'/* DiscreteIntegrator: ''<S1>/Integrator'' *',... '/* End of DiscreteIntegrator: ''<S1>/Integrator'' */',1,1)
/* DiscreteIntegrator: '<S1>/Integrator' */ if (RollAxisAutopilot_B.NotEngaged_e || (RollAxisAutopilot_DW.Integrator_PrevResetState != 0)) { RollAxisAutopilot_DW.Integrator_DSTATE = RollAxisAutopilot_P.Integrator_IC; } /* DiscreteIntegrator: '<S1>/Integrator' */ RollAxisAutopilot_B.Integrator = RollAxisAutopilot_DW.Integrator_DSTATE; /* Saturate: '<S1>/DispLimit' */ u0 = RollAxisAutopilot_B.phiCmd; u1 = RollAxisAutopilot_P.DispLimit_LowerSat; u2 = RollAxisAutopilot_P.dispLim; if (u0 > u2) { /* Saturate: '<S1>/DispLimit' */ RollAxisAutopilot_B.DispLimit = u2; } else if (u0 < u1) { /* Saturate: '<S1>/DispLimit' */ RollAxisAutopilot_B.DispLimit = u1; } else { /* Saturate: '<S1>/DispLimit' */ RollAxisAutopilot_B.DispLimit = u0; } /* End of Saturate: '<S1>/DispLimit' */ /* Sum: '<S1>/Sum' incorporates: * Inport: '<Root>/Phi' */ RollAxisAutopilot_B.Sum = RollAxisAutopilot_B.DispLimit - RollAxisAutopilot_U.Phi; /* Gain: '<S1>/DispGain' */ RollAxisAutopilot_B.DispGain_f = RollAxisAutopilot_P.dispGain * RollAxisAutopilot_B.Sum; /* Saturate: '<S1>/RateLimit' */ u0 = RollAxisAutopilot_B.DispGain_f; u1 = RollAxisAutopilot_P.RateLimit_LowerSat; u2 = RollAxisAutopilot_P.rateLim; if (u0 > u2) { /* Saturate: '<S1>/RateLimit' */ RollAxisAutopilot_B.RateLimit = u2; } else if (u0 < u1) { /* Saturate: '<S1>/RateLimit' */ RollAxisAutopilot_B.RateLimit = u1; } else { /* Saturate: '<S1>/RateLimit' */ RollAxisAutopilot_B.RateLimit = u0; } /* End of Saturate: '<S1>/RateLimit' */ /* Sum: '<S1>/Sum1' incorporates: * Inport: '<Root>/Rate_FB' */ RollAxisAutopilot_B.Sum1 = RollAxisAutopilot_B.RateLimit - RollAxisAutopilot_U.Rate_FB; /* Gain: '<S1>/RateGain' */ RollAxisAutopilot_B.RateGain = RollAxisAutopilot_P.rateGain * RollAxisAutopilot_B.Sum1; /* Sum: '<S1>/Sum2' */ RollAxisAutopilot_B.Sum2 = RollAxisAutopilot_B.Integrator + RollAxisAutopilot_B.RateGain; /* Saturate: '<S1>/CmdLimit' */ u0 = RollAxisAutopilot_B.Sum2; u1 = RollAxisAutopilot_P.CmdLimit_LowerSat; u2 = RollAxisAutopilot_P.cmdLim; if (u0 > u2) { /* Saturate: '<S1>/CmdLimit' */ RollAxisAutopilot_B.CmdLimit = u2; } else if (u0 < u1) { /* Saturate: '<S1>/CmdLimit' */ RollAxisAutopilot_B.CmdLimit = u1; } else { /* Saturate: '<S1>/CmdLimit' */ RollAxisAutopilot_B.CmdLimit = u0; } /* End of Saturate: '<S1>/CmdLimit' */ /* Gain: '<S1>/IntGain' */ RollAxisAutopilot_B.IntGain = RollAxisAutopilot_P.intGain * RollAxisAutopilot_B.Sum1; /* Update for DiscreteIntegrator: '<S1>/Integrator' */ RollAxisAutopilot_DW.Integrator_DSTATE += RollAxisAutopilot_P.Integrator_gainval * RollAxisAutopilot_B.IntGain; if (RollAxisAutopilot_DW.Integrator_DSTATE > RollAxisAutopilot_P.intLim) { RollAxisAutopilot_DW.Integrator_DSTATE = RollAxisAutopilot_P.intLim; } else if (RollAxisAutopilot_DW.Integrator_DSTATE < RollAxisAutopilot_P.Integrator_LowerSat) { RollAxisAutopilot_DW.Integrator_DSTATE = RollAxisAutopilot_P.Integrator_LowerSat; } RollAxisAutopilot_DW.Integrator_PrevResetState = (int8_T) RollAxisAutopilot_B.NotEngaged_e; /* End of Update for DiscreteIntegrator: '<S1>/Integrator' */ /* End of Outputs for SubSystem: '<Root>/BasicRollMode' */ /* Switch: '<Root>/EngSwitch' incorporates: * Inport: '<Root>/AP_Eng' */ if (RollAxisAutopilot_U.AP_Eng) { /* Outport: '<Root>/Ail_Cmd' */ RollAxisAutopilot_Y.Ail_Cmd = RollAxisAutopilot_B.CmdLimit; } else { /* Outport: '<Root>/Ail_Cmd' incorporates: * Constant: '<Root>/Zero' */ RollAxisAutopilot_Y.Ail_Cmd = RollAxisAutopilot_P.Zero_Value_c; } /* End of Switch: '<Root>/EngSwitch' */ } /* Model initialize function */ void RollAxisAutopilot_initialize(void) { /* Registration code */ /* initialize error status */ rtmSetErrorStatus(RollAxisAutopilot_M, (NULL)); /* block I/O */ (void) memset(((void *) &RollAxisAutopilot_B), 0, sizeof(B_RollAxisAutopilot_T)); /* states (dwork) */ (void) memset((void *)&RollAxisAutopilot_DW, 0, sizeof(DW_RollAxisAutopilot_T)); /* external inputs */ (void)memset(&RollAxisAutopilot_U, 0, sizeof(ExtU_RollAxisAutopilot_T)); /* external outputs */ RollAxisAutopilot_Y.Ail_Cmd = 0.0F; /* SystemInitialize for Atomic SubSystem: '<Root>/RollAngleReference' */ /* InitializeConditions for UnitDelay: '<S4>/FixPt Unit Delay1' */ RollAxisAutopilot_DW.FixPtUnitDelay1_DSTATE = RollAxisAutopilot_P.LatchPhi_vinit; /* End of SystemInitialize for SubSystem: '<Root>/RollAngleReference' */ /* SystemInitialize for Atomic SubSystem: '<Root>/BasicRollMode' */ /* InitializeConditions for DiscreteIntegrator: '<S1>/Integrator' */ RollAxisAutopilot_DW.Integrator_DSTATE = RollAxisAutopilot_P.Integrator_IC; RollAxisAutopilot_DW.Integrator_PrevResetState = 0; /* End of SystemInitialize for SubSystem: '<Root>/BasicRollMode' */ } /* Model terminate function */ void RollAxisAutopilot_terminate(void) { /* (no terminate code required) */ }
Due to the void void
interface and the direct data access, the function is not reentrant. If you call the function multiple times in an application, each call writes data to the global structure variables and the subsequent call can read that data, resulting in unintentional interference between the calls.
The model initialization function RollAxisAutopilot_initialize
initializes all of the internal data to zero. The function also initializes the error status by calling a specialized macro function. The initialization function directly accesses the global variables, which means that the function is not reentrant.
coder.example.extractLines(file,'/* Model initialize function */',... 'sizeof(DW_RollAxisAutopilot_T));',1,1)
/* Model initialize function */ void RollAxisAutopilot_initialize(void) { /* Registration code */ /* initialize error status */ rtmSetErrorStatus(RollAxisAutopilot_M, (NULL)); /* block I/O */ (void) memset(((void *) &RollAxisAutopilot_B), 0, sizeof(B_RollAxisAutopilot_T)); /* states (dwork) */ (void) memset((void *)&RollAxisAutopilot_DW, 0, sizeof(DW_RollAxisAutopilot_T));
The function then initializes the block states in the DWork structure to the initial values that the block parameters in the model specify. Two of the three states in the model have tunable initial values, so the code initializes them by reading data from the parameters structure.
coder.example.extractLines(file,... '/* SystemInitialize for Atomic SubSystem: ''<Root>/RollAngleReference'' */',... '/* Model terminate function */',1,0)
/* SystemInitialize for Atomic SubSystem: '<Root>/RollAngleReference' */ /* InitializeConditions for UnitDelay: '<S4>/FixPt Unit Delay1' */ RollAxisAutopilot_DW.FixPtUnitDelay1_DSTATE = RollAxisAutopilot_P.LatchPhi_vinit; /* End of SystemInitialize for SubSystem: '<Root>/RollAngleReference' */ /* SystemInitialize for Atomic SubSystem: '<Root>/BasicRollMode' */ /* InitializeConditions for DiscreteIntegrator: '<S1>/Integrator' */ RollAxisAutopilot_DW.Integrator_DSTATE = RollAxisAutopilot_P.Integrator_IC; RollAxisAutopilot_DW.Integrator_PrevResetState = 0; /* End of SystemInitialize for SubSystem: '<Root>/BasicRollMode' */ }
Generate Reusable Code
You can configure the generated code as reentrant, which means you can call the entry-point functions multiple times in an application. With this configuration, instead of directly accessing global variables, the entry-point functions accept internal data through formal parameters (pointer arguments). With these pointer arguments, each call can maintain internal data in a set of separate global variables, preventing unintentional interaction between the calls.
In the model, set Configuration Parameters > Code Generation > Interface > Code interface packaging to Reusable function
.
set_param('RollAxisAutopilot','CodeInterfacePackaging','Reusable function')
Generate code from the model.
slbuild('RollAxisAutopilot')
### Starting build procedure for: RollAxisAutopilot ### Successful completion of build procedure for: RollAxisAutopilot Build Summary Top model targets: Model Build Reason Status Build Duration ================================================================================================ RollAxisAutopilot Generated code was out of date. Code generated and compiled. 0h 0m 12.911s 1 of 1 models built (0 models already up to date) Build duration: 0h 0m 13.88s
Now, in RollAxisAutopilot.h
, the real-time model data structure contains pointers to the error indication, the internal data, and primary input and output data in the form of ExtU
and ExtY
substructures (the fields of which represent Inport and Outport blocks at the root level of the model).
file = fullfile('RollAxisAutopilot_grt_rtw','RollAxisAutopilot.h'); coder.example.extractLines(file,'/* Real-time Model Data Structure */',... '/* External data declarations for dependent source files */',1,0)
/* Real-time Model Data Structure */ struct tag_RTM_RollAxisAutopilot_T { const char_T *errorStatus; B_RollAxisAutopilot_T *blockIO; ExtU_RollAxisAutopilot_T *inputs; ExtY_RollAxisAutopilot_T *outputs; DW_RollAxisAutopilot_T *dwork; }; /* Block parameters (default storage) */ extern P_RollAxisAutopilot_T RollAxisAutopilot_P;
To call the generated code multiple times in an application, your code must allocate memory for a real-time model data structure per call. The file RollAxisAutopilot.c
defines a specialized function that allocates memory for a new real-time model data structure and returns a pointer to the structure. The function also allocates memory for the substructures that the fields in the model data structure point to, such as the DWork structure.
file = fullfile('RollAxisAutopilot_grt_rtw','RollAxisAutopilot.c'); coder.example.extractLines(file,'/* Model data allocation function */',... 'RT_MODEL_RollAxisAutopilot_T *RollAxisAutopilot(void)',1,1)
/* Model data allocation function */ RT_MODEL_RollAxisAutopilot_T *RollAxisAutopilot(void)
The model step
function accepts an argument that represents the real-time model data structure.
coder.example.extractLines(file,'/* Model step function */','void RollAxisAutopilot_step',1,1)
/* Model step function */ void RollAxisAutopilot_step(RT_MODEL_RollAxisAutopilot_T *const
In the function definition, the algorithm first extracts each pointer from the real-time model data structure into a local variable.
coder.example.extractLines(file,'*RollAxisAutopilot_B =','RollAxisAutopilot_M->outputs;',1,1)
B_RollAxisAutopilot_T *RollAxisAutopilot_B = RollAxisAutopilot_M->blockIO; DW_RollAxisAutopilot_T *RollAxisAutopilot_DW = RollAxisAutopilot_M->dwork; ExtU_RollAxisAutopilot_T *RollAxisAutopilot_U = (ExtU_RollAxisAutopilot_T *) RollAxisAutopilot_M->inputs; ExtY_RollAxisAutopilot_T *RollAxisAutopilot_Y = (ExtY_RollAxisAutopilot_T *) RollAxisAutopilot_M->outputs;
Then, to access the internal data stored in global memory, the algorithm interacts with these local variables.
coder.example.extractLines(file,'/* DiscreteIntegrator: ''<S1>/Integrator'' */',... '/* End of DiscreteIntegrator: ''<S1>/Integrator'' */',1,1)
/* DiscreteIntegrator: '<S1>/Integrator' */ if (RollAxisAutopilot_B->NotEngaged_e || (RollAxisAutopilot_DW->Integrator_PrevResetState != 0)) { RollAxisAutopilot_DW->Integrator_DSTATE = RollAxisAutopilot_P.Integrator_IC; } /* DiscreteIntegrator: '<S1>/Integrator' */ RollAxisAutopilot_B->Integrator = RollAxisAutopilot_DW->Integrator_DSTATE; /* Saturate: '<S1>/DispLimit' */ u0 = RollAxisAutopilot_B->phiCmd; u1 = RollAxisAutopilot_P.DispLimit_LowerSat; u2 = RollAxisAutopilot_P.dispLim; if (u0 > u2) { /* Saturate: '<S1>/DispLimit' */ RollAxisAutopilot_B->DispLimit = u2; } else if (u0 < u1) { /* Saturate: '<S1>/DispLimit' */ RollAxisAutopilot_B->DispLimit = u1; } else { /* Saturate: '<S1>/DispLimit' */ RollAxisAutopilot_B->DispLimit = u0; } /* End of Saturate: '<S1>/DispLimit' */ /* Sum: '<S1>/Sum' */ RollAxisAutopilot_B->Sum = RollAxisAutopilot_B->DispLimit - RollAxisAutopilot_U->Phi; /* Gain: '<S1>/DispGain' */ RollAxisAutopilot_B->DispGain_f = RollAxisAutopilot_P.dispGain * RollAxisAutopilot_B->Sum; /* Saturate: '<S1>/RateLimit' */ u0 = RollAxisAutopilot_B->DispGain_f; u1 = RollAxisAutopilot_P.RateLimit_LowerSat; u2 = RollAxisAutopilot_P.rateLim; if (u0 > u2) { /* Saturate: '<S1>/RateLimit' */ RollAxisAutopilot_B->RateLimit = u2; } else if (u0 < u1) { /* Saturate: '<S1>/RateLimit' */ RollAxisAutopilot_B->RateLimit = u1; } else { /* Saturate: '<S1>/RateLimit' */ RollAxisAutopilot_B->RateLimit = u0; } /* End of Saturate: '<S1>/RateLimit' */ /* Sum: '<S1>/Sum1' */ RollAxisAutopilot_B->Sum1 = RollAxisAutopilot_B->RateLimit - RollAxisAutopilot_U->Rate_FB; /* Gain: '<S1>/RateGain' */ RollAxisAutopilot_B->RateGain = RollAxisAutopilot_P.rateGain * RollAxisAutopilot_B->Sum1; /* Sum: '<S1>/Sum2' */ RollAxisAutopilot_B->Sum2 = RollAxisAutopilot_B->Integrator + RollAxisAutopilot_B->RateGain; /* Saturate: '<S1>/CmdLimit' */ u0 = RollAxisAutopilot_B->Sum2; u1 = RollAxisAutopilot_P.CmdLimit_LowerSat; u2 = RollAxisAutopilot_P.cmdLim; if (u0 > u2) { /* Saturate: '<S1>/CmdLimit' */ RollAxisAutopilot_B->CmdLimit = u2; } else if (u0 < u1) { /* Saturate: '<S1>/CmdLimit' */ RollAxisAutopilot_B->CmdLimit = u1; } else { /* Saturate: '<S1>/CmdLimit' */ RollAxisAutopilot_B->CmdLimit = u0; } /* End of Saturate: '<S1>/CmdLimit' */ /* Gain: '<S1>/IntGain' */ RollAxisAutopilot_B->IntGain = RollAxisAutopilot_P.intGain * RollAxisAutopilot_B->Sum1; /* Update for DiscreteIntegrator: '<S1>/Integrator' */ RollAxisAutopilot_DW->Integrator_DSTATE += RollAxisAutopilot_P.Integrator_gainval * RollAxisAutopilot_B->IntGain; if (RollAxisAutopilot_DW->Integrator_DSTATE > RollAxisAutopilot_P.intLim) { RollAxisAutopilot_DW->Integrator_DSTATE = RollAxisAutopilot_P.intLim; } else if (RollAxisAutopilot_DW->Integrator_DSTATE < RollAxisAutopilot_P.Integrator_LowerSat) { RollAxisAutopilot_DW->Integrator_DSTATE = RollAxisAutopilot_P.Integrator_LowerSat; } RollAxisAutopilot_DW->Integrator_PrevResetState = (int8_T) RollAxisAutopilot_B->NotEngaged_e; /* End of Update for DiscreteIntegrator: '<S1>/Integrator' */ /* End of Outputs for SubSystem: '<Root>/BasicRollMode' */ /* Switch: '<Root>/EngSwitch' */ if (RollAxisAutopilot_U->AP_Eng) { /* Outport: '<Root>/Ail_Cmd' */ RollAxisAutopilot_Y->Ail_Cmd = RollAxisAutopilot_B->CmdLimit; } else { /* Outport: '<Root>/Ail_Cmd' incorporates: * Constant: '<Root>/Zero' */ RollAxisAutopilot_Y->Ail_Cmd = RollAxisAutopilot_P.Zero_Value_c; } /* End of Switch: '<Root>/EngSwitch' */ } /* Model initialize function */ void RollAxisAutopilot_initialize(RT_MODEL_RollAxisAutopilot_T *const RollAxisAutopilot_M) { DW_RollAxisAutopilot_T *RollAxisAutopilot_DW = RollAxisAutopilot_M->dwork; /* SystemInitialize for Atomic SubSystem: '<Root>/RollAngleReference' */ /* InitializeConditions for UnitDelay: '<S4>/FixPt Unit Delay1' */ RollAxisAutopilot_DW->FixPtUnitDelay1_DSTATE = RollAxisAutopilot_P.LatchPhi_vinit; /* End of SystemInitialize for SubSystem: '<Root>/RollAngleReference' */ /* SystemInitialize for Atomic SubSystem: '<Root>/BasicRollMode' */ /* InitializeConditions for DiscreteIntegrator: '<S1>/Integrator' */ RollAxisAutopilot_DW->Integrator_DSTATE = RollAxisAutopilot_P.Integrator_IC; RollAxisAutopilot_DW->Integrator_PrevResetState = 0; /* End of SystemInitialize for SubSystem: '<Root>/BasicRollMode' */ } /* Model terminate function */ void RollAxisAutopilot_terminate(RT_MODEL_RollAxisAutopilot_T * RollAxisAutopilot_M) { /* model code */ rt_FREE(RollAxisAutopilot_M->blockIO); rt_FREE(RollAxisAutopilot_M->inputs); rt_FREE(RollAxisAutopilot_M->outputs); rt_FREE(RollAxisAutopilot_M->dwork); rt_FREE(RollAxisAutopilot_M); } /* Model data allocation function */ RT_MODEL_RollAxisAutopilot_T *RollAxisAutopilot(void) { RT_MODEL_RollAxisAutopilot_T *RollAxisAutopilot_M; RollAxisAutopilot_M = (RT_MODEL_RollAxisAutopilot_T *) malloc(sizeof (RT_MODEL_RollAxisAutopilot_T)); if (RollAxisAutopilot_M == (NULL)) { return (NULL); } (void) memset((char *)RollAxisAutopilot_M, 0, sizeof(RT_MODEL_RollAxisAutopilot_T)); /* block I/O */ { B_RollAxisAutopilot_T *b = (B_RollAxisAutopilot_T *) malloc(sizeof (B_RollAxisAutopilot_T)); rt_VALIDATE_MEMORY(RollAxisAutopilot_M,b); RollAxisAutopilot_M->blockIO = (b); } /* states (dwork) */ { DW_RollAxisAutopilot_T *dwork = (DW_RollAxisAutopilot_T *) malloc(sizeof (DW_RollAxisAutopilot_T)); rt_VALIDATE_MEMORY(RollAxisAutopilot_M,dwork); RollAxisAutopilot_M->dwork = (dwork); } /* external inputs */ { ExtU_RollAxisAutopilot_T *RollAxisAutopilot_U = (ExtU_RollAxisAutopilot_T *) malloc(sizeof(ExtU_RollAxisAutopilot_T)); rt_VALIDATE_MEMORY(RollAxisAutopilot_M,RollAxisAutopilot_U); RollAxisAutopilot_M->inputs = (((ExtU_RollAxisAutopilot_T *) RollAxisAutopilot_U)); } /* external outputs */ { ExtY_RollAxisAutopilot_T *RollAxisAutopilot_Y = (ExtY_RollAxisAutopilot_T *) malloc(sizeof(ExtY_RollAxisAutopilot_T)); rt_VALIDATE_MEMORY(RollAxisAutopilot_M,RollAxisAutopilot_Y); RollAxisAutopilot_M->outputs = (RollAxisAutopilot_Y); } { B_RollAxisAutopilot_T *RollAxisAutopilot_B = RollAxisAutopilot_M->blockIO; DW_RollAxisAutopilot_T *RollAxisAutopilot_DW = RollAxisAutopilot_M->dwork; ExtU_RollAxisAutopilot_T *RollAxisAutopilot_U = (ExtU_RollAxisAutopilot_T *) RollAxisAutopilot_M->inputs; ExtY_RollAxisAutopilot_T *RollAxisAutopilot_Y = (ExtY_RollAxisAutopilot_T *) RollAxisAutopilot_M->outputs; /* block I/O */ (void) memset(((void *) RollAxisAutopilot_B), 0, sizeof(B_RollAxisAutopilot_T)); /* states (dwork) */ (void) memset((void *)RollAxisAutopilot_DW, 0, sizeof(DW_RollAxisAutopilot_T)); /* external inputs */ (void)memset(RollAxisAutopilot_U, 0, sizeof(ExtU_RollAxisAutopilot_T)); /* external outputs */ RollAxisAutopilot_Y->Ail_Cmd = 0.0F; } return RollAxisAutopilot_M; }
Similarly, the model initialization function accepts the real-time model data structure as an argument.
coder.example.extractLines(file,... '/* Model initialize function */','void RollAxisAutopilot_initialize',1,1)
/* Model initialize function */ void RollAxisAutopilot_initialize(RT_MODEL_RollAxisAutopilot_T *const
Because each call that you make to an entry-point function interacts with a separate real-time model data structure, you avoid unintentional interaction between the calls.
Eliminate Internal Data with Code Generation Optimizations
For more efficient code that consumes less memory, select the optimizations, such as Default parameter behavior, that you cleared earlier.
set_param('RollAxisAutopilot','DefaultParameterBehavior','Inlined',... 'OptimizeBlockIOStorage','on',... 'LocalBlockOutputs','on')
In this example, for simpler code, set Code interface packaging to Nonreusable function
.
set_param('RollAxisAutopilot','CodeInterfacePackaging','Nonreusable function')
Generate code from the model.
slbuild('RollAxisAutopilot')
### Starting build procedure for: RollAxisAutopilot ### Successful completion of build procedure for: RollAxisAutopilot Build Summary Top model targets: Model Build Reason Status Build Duration ================================================================================================ RollAxisAutopilot Generated code was out of date. Code generated and compiled. 0h 0m 13.182s 1 of 1 models built (0 models already up to date) Build duration: 0h 0m 14.246s
Now, RollAxisAutopilot.h
does not define a structure for block inputs and outputs. For all of the internal signals in the model, the optimizations either eliminated storage or created local function variables instead of global structure fields.
The optimizations were not able to eliminate storage for the three block states, so the file continues to define the DWork structure type.
file = fullfile('RollAxisAutopilot_grt_rtw','RollAxisAutopilot.h'); coder.example.extractLines(file,... '/* Block states (default storage) for system','} DW_RollAxisAutopilot_T;',1,1)
/* Block states (default storage) for system '<Root>' */ typedef struct { real32_T FixPtUnitDelay1_DSTATE; /* '<S4>/FixPt Unit Delay1' */ real32_T Integrator_DSTATE; /* '<S1>/Integrator' */ int8_T Integrator_PrevResetState; /* '<S1>/Integrator' */ } DW_RollAxisAutopilot_T;
The code generated for the Discrete-Time Integrator block now stores state and output data only in the DWork structure.
file = fullfile('RollAxisAutopilot_grt_rtw','RollAxisAutopilot.c'); coder.example.extractLines(file,'/* Update for DiscreteIntegrator: ''<S1>/Integrator''',... '/* End of Update for DiscreteIntegrator: ''<S1>/Integrator'' */',1,1)
/* Update for DiscreteIntegrator: '<S1>/Integrator' incorporates: * Gain: '<S1>/IntGain' */ RollAxisAutopilot_DW.Integrator_DSTATE += 0.5F * rtb_TKSwitch * 0.025F; if (RollAxisAutopilot_DW.Integrator_DSTATE > 5.0F) { RollAxisAutopilot_DW.Integrator_DSTATE = 5.0F; } else if (RollAxisAutopilot_DW.Integrator_DSTATE < -5.0F) { RollAxisAutopilot_DW.Integrator_DSTATE = -5.0F; } RollAxisAutopilot_DW.Integrator_PrevResetState = (int8_T)tmp;
The optimizations also eliminated storage for the block parameters in the model. For example, in the Discrete-Time Integrator block, the Upper saturation limit and Lower saturation limit parameters are set to intLim
and -intLim
. intLim
is a Simulink.Parameter
object that stores the value 5
. In the code generated for the Discrete-Time Integrator, these block parameters and intLim
appear as inlined literal numbers 5.0F
and -5.0F
.
If a model contains a parameter that the code generator cannot inline directly (for example, an array parameter), the code defines a structure type that represents the data. This constant parameters structure uses the const
storage type qualifier, so some build toolchains can optimize the assembly code further.
The declaration of the structure, ConstParam__model_
, is in model.h
.
/* Constant parameters (auto storage) */ typedef struct { /* Expression: [1 2 3 4 5 6 7] * Referenced by: '<Root>/Constant' */ real_T Constant_Value[7]; /* Expression: [7 6 5 4 3 2 1] * Referenced by: '<Root>/Gain' */ real_T Gain_Gain[7]; } ConstParam_model;
The declaration of the constant parameters, model_ConstP
, is in model_data.c
by default.
/* Constant parameters (auto storage) */ const ConstParam_model model_ConstP = { /* Expression: [1 2 3 4 5 6 7] * Referenced by: '<Root>/Constant' */ { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 }, /* Expression: [7 6 5 4 3 2 1] * Referenced by: '<Root>/Gain' */ { 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0 } };
model_ConstP
is passed as an argument to referenced models.
Local Variables in the Generated Code
When you select the optimization Configuration Parameters > Enable local block outputs, the code generator attempts to yield more efficient code by representing internal signals as local variables instead of fields of a global structure. If the memory consumed by local variables risks exceeding the stack space available on your target hardware, consider indicating the maximum stack size by setting Configuration Parameters > Maximum stack size (bytes). For more information, see Maximum stack size (bytes).
Appearance of Test Points in the Generated Code
A test point is a signal that is stored in a unique memory location. For information about including test points in your model, see Configure Signals as Test Points.
When you generate code for models that include test points,
the build process allocates a separate memory
buffer for each test point. By default, test points are stored as members of a standard data
structure such as
.model
_B
If you have Embedded Coder®:
You can control the default representation of test points by specifying code generation settings for the Internal data category of data in the Code Mapping Editor (see Configure Default Code Generation for Data (Embedded Coder)).
You can specify that the build process ignore test points in the model, allowing optimal buffer allocation, by using the Ignore test point signals (Embedded Coder) parameter. Ignoring test points facilitates transitioning from prototyping to deployment and avoids accidental degradation of generated code due to workflow artifacts. See Ignore test point signals (Embedded Coder).
Virtual buses do not appear in generated code, even when associated with a test point. To display a bus in generated code, use a nonvirtual bus or a virtual bus converted to a nonvirtual bus by a Signal Conversion block.
Appearance of Workspace Variables in the Generated Code
Workspace variables are variables that you use to specify block
parameter values in a model. Workspace variables include numeric MATLAB® variables and Simulink.Parameter
objects that you store in a
workspace, such as the base workspace, or in a data dictionary.
When you set Default parameter behavior to
Tunable
, by default, workspace variables appear in the
generated code as tunable fields of the global parameters structure. If you use such a
variable to specify multiple block parameter values, the variable appears as a single field
of the global parameters structure. The code does not create multiple fields to represent
the block parameters. Therefore, tuning the field value during code execution changes the
mathematical behavior of the model in the same way as tuning the value of the MATLAB variable or parameter object during simulation.
If you have Embedded Coder, you can control the default representation of workspace variables by specifying code generation settings for categories of parameter data in the Code Mapping Editor (see Configure Default Code Generation for Data (Embedded Coder)).
The Model parameters category applies to variables that you store in a model workspace.
The External parameters category applies to variables that you store in the base workspace or a data dictionary.
Promote Internal Data to the Interface
By default, the code generator assumes that other systems and components in your application do not need to access internal data. For example, internal data are subject to optimizations that can eliminate them from the generated code. For prototyping and testing purposes, you can access internal data by clearing the optimizations or by configuring test points and applying storage classes (see Preserve Variables in Generated Code). For optimized production code, configure individual data items to appear in the generated code as part of the model interface.
Data That You Can Promote
Depending on the reentrancy of the generated code, that is, the setting that you choose for Code interface packaging, you can configure each data item in a model to participate in the interface by appearing in the code as one of these entities:
A global symbol, such as a global variable or a call to a specialized function
A formal parameter (argument) of the generated entry-point functions
The table shows the mechanisms that each category of data can use to participate in the interface.
Category of Data | Appear as Global Symbol | Appear as Argument of Entry-Point Function |
---|---|---|
Root-level Inport or Outport block | Only for a nonreentrant model. | Yes. |
Signal connecting two blocks | Only for a nonreentrant model. | Only for a reentrant model and only as a field of a structure. Alternatively, connect the signal to a root-level Outport block. |
Block state | Only for a nonreentrant model. | Only for a reentrant model and only as a field of a structure. |
Data store such as a Data Store Memory block | Yes. | Only for a reentrant model and only as a field of a structure. |
Block parameter or parameter object such as
Simulink.Parameter | Yes. | Only as a field of a structure. |
Single-Instance Algorithm
For a single-instance algorithm (you set Code interface packaging
to Nonreusable function
), apply storage classes directly to
individual data items by using the Model Data Editor or the Property Inspector. With a
directly applied storage class, a data item appears in the code as a global symbol such as
a global variable. The storage class also prevents optimizations from eliminating storage
for the data item.
You can apply storage classes to signals, block states, and block parameters. (For
block parameters, you apply storage classes indirectly through parameter objects such as
Simulink.Parameter
). However, for a signal, consider connecting the
signal to an Outport block at the root level of the model. Then,
optionally, you can apply a storage class to the block. In the block diagram, the
Outport block shows that the signal represents a system output.
For more information about storage classes, see C Data Code Interface Configuration for Model Interface Elements.
Reentrant Algorithm
For a reentrant algorithm (you set Code interface packaging to
Reusable function
), use different techniques to configure
data items to appear in the code as formal parameters (arguments) of the generated
entry-point functions.
For an internal signal, directly apply the storage class
Model default
(see C Data Code Interface Configuration for Model Interface Elements). If you have Embedded Coder, in the Code Mapping Editor, for the Internal data category, set the default storage class toDefault
or to a structured storage class that you define in an Embedded Coder Dictionary (see Create Code Definitions for Use in the Code Mappings Editor (Embedded Coder)). Alternatively, configure the signal as a test point (see Appearance of Test Points in the Generated Code). By default, the signal appears as a field of one of the standard data structures (see How Generated Code Stores Internal Signal, State, and Parameter Data). If you do not want the signal to appear in production code, use a test point so that you can later select the model configuration parameter Ignore test point signals.Alternatively, connect the signal to an Outport block at the root level of the model. Connecting the signal to a root-level Outport block prevents optimizations from eliminating the signal from the code. To help with signal routing in a large model, use Goto and From blocks.
For a block parameter, create a parameter object such as
Simulink.Parameter
and directly apply a storage class other thanAuto
to the object. The storage class prevents optimizations from inlining the parameter value in the code.With the storage class, the parameter is shared between instances of the model, which are calls to the entry-point functions. The functions access the parameter data directly, as a global symbol, not an argument. To enable each instance of the model to use a different value for the parameter, configure the parameter as a model argument. For more information, see Specify Instance-Specific Parameter Values for Reusable Referenced Model.
For information about applying storage classes, see C Data Code Interface Configuration for Model Interface Elements.
Control Default Representation of Internal Data (Embedded Coder)
By default, the code generator aggregates internal data that optimizations cannot eliminate, such as most state data, into standard structures such as the DWork structure. With Embedded Coder, you can control how the generated code stores this data.
Control Placement of Data in Memory by Inserting Pragmas
Use the Code Mapping Editor to specify a default memory section for each category of data such as states and signals (Internal data). In the generated code, your custom pragmas or other decorations surround the data definitions and declarations.
You can also partition the structures according to atomic subsystems in your model so that you can specify different default memory sections for the data of subroutines and other algorithmic subcomponents.
For more information, see Control Data and Function Placement in Memory by Inserting Pragmas (Embedded Coder).
Control Names of Types, Fields, and Global Variables for Standard Data Structures
You can control some characteristics of the standard data structures. For more information, see Manage Replacement of Simulink Data Types in Generated Code (Embedded Coder).
For additional control over structure characteristics, such as placement in generated code files, create your own structured storage class by using the Embedded Coder Dictionary. Then, apply the storage class to categories of data by using the Code Mapping Editor. The storage class removes the data from the standard structures, creating other structures that you can more finely control. For more information about applying default storage classes to categories of data, see Configure Default Code Generation for Data (Embedded Coder). For more information about creating a storage class, see Define Service Interfaces, Storage Classes, Memory Sections, and Function Templates for Software Architecture (Embedded Coder).
Organize Data into Structures According to Subcomponents
In the standard data structures, to create substructures that contain the data for a single-instance (nonreentrant) subroutine or subcomponent, use an atomic subsystem to encapsulate the corresponding blocks. In the subsystem parameters, set Function packaging to
Reusable function
. For more information, see Generate Modular Function Code for Nonvirtual Subsystems (Embedded Coder).Alternatively, encapsulate the blocks in a model and use a Model block. In the referenced model, set Configuration Parameters > Model Referencing > Total number of instances allowed per top model to
Multiple
. For more information, see Generate Code for Model Reference Hierarchy.To create separate, standalone structures that contain the data for a multi-instance (reentrant) subroutine or subcomponent, in the model, use an atomic subsystem to encapsulate the corresponding blocks. In the subsystem parameters, set Function packaging to
Nonreusable function
and select Function with separate data. For more information, see Generate Modular Function Code for Nonvirtual Subsystems (Embedded Coder).Alternatively, encapsulate the blocks in a model and use a Model block. In the referenced model, choose one of these techniques:
Set Configuration Parameters > Model Referencing > Total number of instances allowed per top model to
One
. For more information, see Generate Code for Model Reference Hierarchy.Set Total number of instances allowed per top model to
Multiple
and create a structured storage class by using an Embedded Coder Dictionary. Then, apply the storage class to categories of data by using the Code Mapping Editor. To create a storage class, see Define Service Interfaces, Storage Classes, Memory Sections, and Function Templates for Software Architecture (Embedded Coder). To apply the storage class to categories of data, see Configure Default Code Generation for Data (Embedded Coder).
Organize Signal and Parameter Data into Meaningful, Custom Structures and Substructures
To organize arbitrary signals and parameters into custom structures and substructures,
create nonvirtual buses and parameter structures. Optionally, to prevent optimizations
from eliminating the data from the code, set the storage class of a bus or parameter
structure to a value other than Auto
(the default setting).
As you add blocks to the model, you must explicitly place each new signal and parameter into a bus or a structure.
For more information, see Organize Data into Structures in Generated Code.
Create Separate Global Variables Instead of Structure Fields
To make a category of internal data appear in the generated code as separate,
unstructured global variables instead of fields of a standard data structure, apply an
unstructured storage class to the data category by using the Code Mapping Editor. For
example, apply the storage class ExportedGlobal
. However, if you
generate multi-instance, reentrant code by setting the configuration parameter
Code interface packaging to a value other than
Nonreusable function
, you cannot use this technique for some
categories of data (see Storage Classes and Reentrant, Multi-Instance Models and Components).
To apply default storage classes to categories of data by using the Code Mapping Editor, see Configure Default Code Generation for Data (Embedded Coder). To choose a storage class, see Choose Storage Class for Controlling Data Representation in Generated Code.