C MEX S-Function Examples - MATLAB & Simulink (original) (raw)

About S-Function Examples

All examples are based on the C MEX S-function templatessfuntmpl_basic.c and sfuntmpl_doc.c. Open[sfuntmpl_doc.c](https://mdsite.deno.dev/matlab:edit%28[matlabroot,'/toolbox/simulink/blocks/src/sfuntmpl%5Fdoc.c']%29;). for a detailed discussion of the S-function template.

Continuous States

The [csfunc.c](https://mdsite.deno.dev/matlab:edit%28[matlabroot,'/toolbox/simulink/sfuntemplates/src/csfunc.c']%29;) example shows how to model a continuous system with states using a C MEX S-function. The following Simulink® model uses this S-function.

[sfcndemo_csfunc](https://mdsite.deno.dev/matlab:open%5Fsystem%28[matlabroot,'/toolbox/simulink/sfuntemplates/sfcndemo%5Fcsfunc']%29;)

In continuous state integration, the Simulink solvers integrate a set of continuous states using the following equations.

S-functions that contain continuous states implement a state-space equation. ThemdlOutputs method contains the output portion andmdlDerivatives method contains the derivative portion of the state-space equation. To visualize how the integration works, see the flow chart inSimulink Engine Interaction with C S-Functions. The output equation corresponds to the mdlOutputs in the major time step. Next, the example enters the integration section of the flow chart. Here the Simulink engine performs a number of minor time steps during which it callsmdlOutputs and mdlDerivatives. Each of these pairs of calls is referred to as an integration stage. The integration returns with the continuous states updated and the simulation time moved forward. Time is moved forward as far as possible, providing that error tolerances in the state are met. The maximum time step is subject to constraints of discrete events such as the actual simulation stop time and the user-imposed limit.

The csfunc.c example specifies that the input port has direct feedthrough. This is because matrix D is initialized to a nonzero matrix. If D is set equal to a zero matrix in the state-space representation, the input signal is not used in mdlOutputs. In this case, the direct feedthrough can be set to 0, which indicates that csfunc.c does not require the input signal when executing mdlOutputs.

matlabroot/toolbox/simulink/sfuntemplates/src/csfunc.c

The S-function [csfunc.c](https://mdsite.deno.dev/matlab:edit%28fullfile%28matlabroot,'/toolbox/simulink/sfuntemplates/src/csfunc.c'%29%29) begins with #define statements for the S-function name and level, and a #include statement for the simstruc.h header. After these statements, the S-function can include or define any other necessary headers, data, etc. Thecsfunc.c example defines the variableU as a pointer to the first input port's signal and initializes static variables for the state-space matrices.

/* File : csfunc.c

#define S_FUNCTION_NAME csfunc #define S_FUNCTION_LEVEL 2

#include "simstruc.h"

#define U(element) (uPtrs[element]) / Pointer to Input Port0 */

static real_T A[2][2]={ { -0.09, -0.01 } , { 1 , 0 } };

static real_T B[2][2]={ { 1 , -7 } , { 0 , -2 } };

static real_T C[2][2]={ { 0 , 2 } , { 1 , -5 } };

static real_T D[2][2]={ { -3 , 0 } , { 1 , 0 } };

The required S-function method mdlInitializeSizes then sets up the following S-function characteristics.

The mdlInitializeSizes function for this example is shown below.

/====================

/* Function: mdlInitializeSizes ===============================================

/ static void mdlInitializeSizes(SimStruct S) { ssSetNumSFcnParams(S, 0); / Number of expected parameters / if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) { return; / Parameter mismatch reported by the Simulink engine/ }

ssSetNumContStates(S, 2);
ssSetNumDiscStates(S, 0);

if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, 2);
ssSetInputPortDirectFeedThrough(S, 0, 1);

if (!ssSetNumOutputPorts(S, 1)) return;
ssSetOutputPortWidth(S, 0, 2);

ssSetNumSampleTimes(S, 1);
ssSetNumRWork(S, 0);
ssSetNumIWork(S, 0);
ssSetNumPWork(S, 0);
ssSetNumModes(S, 0);
ssSetNumNonsampledZCs(S, 0);

/* Take care when specifying exception free code - see sfuntmpl_doc.c */
ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);

}

The required S-function method mdlInitializeSampleTimes specifies the S-function sample rates. The valueCONTINUOUS_SAMPLE_TIME passed to the [ssSetSampleTime](sssetsampletime.html) macro specifies that the first S-function sample rate be continuous.[ssSetOffsetTime](sssetoffsettime.html) then specifies an offset time of zero for this sample rate. The call tossSetModelReferenceSampleTimeDefaultInheritance tells the solver to use the default rule to determine if referenced models containing this S-function can inherit their sample times from the parent model.

/* Function: mdlInitializeSampleTimes =========================================

*/ static void mdlInitializeSampleTimes(SimStruct *S) { ssSetSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME); ssSetOffsetTime(S, 0, 0.0); ssSetModelReferenceSampleTimeDefaultInheritance(S); }

The optional S-function method mdlInitializeConditions initializes the continuous state vector. The #define statement before this method is required for the Simulink engine to call this function. In the example below,[ssGetContStates](ssgetcontstates.html) obtains a pointer to the continuous state vector. The for loop then initializes each state to zero.

#define MDL_INITIALIZE_CONDITIONS /* Function: mdlInitializeConditions ========================================

*/ static void mdlInitializeConditions(SimStruct *S) { real_T *x0 = ssGetContStates(S); int_T lp;

for (lp=0;lp<2;lp++) { 
    *x0++=0.0; 
}

}

The required mdlOutputs function computes the output signal of this S-function. The beginning of the function obtains pointers to the first output port, continuous states, and first input port. The S-function uses the data in these arrays to solve the output equationy=Cx+Du.

/* Function: mdlOutputs =======================================================

*/ static void mdlOutputs(SimStruct *S, int_T tid) { real_T *y = ssGetOutputPortRealSignal(S,0); real_T *x = ssGetContStates(S); InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

UNUSED_ARG(tid); /* not used in single tasking mode */

/* y=Cx+Du */
y[0]=C[0][0]*x[0]+C[0][1]*x[1]+D[0][0]*U(0)+D[0][1]*U(1);
y[1]=C[1][0]*x[0]+C[1][1]*x[1]+D[1][0]*U(0)+D[1][1]*U(1);

}

The mdlDerivatives function calculates the continuous state derivatives. Because this function is an optional method, a#define statement must precede the function. The beginning of the function obtains pointers to the S-function continuous states, state derivatives, and first input port. The S-function uses this data to solve the equation dx=Ax+Bu.

#define MDL_DERIVATIVES /* Function: mdlDerivatives =================================================

*/ static void mdlDerivatives(SimStruct *S) { real_T *dx = ssGetdX(S); real_T *x = ssGetContStates(S); InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

/* xdot=Ax+Bu */
dx[0]=A[0][0]*x[0]+A[0][1]*x[1]+B[0][0]*U(0)+B[0][1]*U(1);
dx[1]=A[1][0]*x[0]+A[1][1]*x[1]+B[1][0]*U(0)+B[1][1]*U(1);

}

The required mdlTerminate function performs any actions, such as freeing memory, necessary at the end of the simulation. In this example, the function is empty.

/* Function: mdlTerminate =====================================================

*/ static void mdlTerminate(SimStruct S) { UNUSED_ARG(S); / unused input argument */ }

The required S-function trailer includes the files necessary for simulation or code generation, as follows.

#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX file? / #include "simulink.c" / MEX file interface mechanism / #else #include "cg_sfun.h" / Code generation registration function */ #endif

Note

The mdlOutputs and mdlTerminate functions use the UNUSED_ARG macro to indicate that an input argument the callback requires is not used. This optional macro is defined in [simstruc_types.h](https://mdsite.deno.dev/matlab:edit%28[matlabroot,'/simulink/include/simstruc%5Ftypes.h']%29;). If used, you must call this macro once for each input argument that a callback does not use.

Discrete States

The [dsfunc.c](https://mdsite.deno.dev/matlab:sfunddg%5Fcb%5Fedit%28'dsfunc'%29;) example shows how to model a discrete system in a C MEX S-function. The following Simulink model uses this S-function.

[sfcndemo_dsfunc](https://mdsite.deno.dev/matlab:open%5Fsystem%28[matlabroot,'/toolbox/simulink/sfuntemplates/sfcndemo%5Fdsfunc']%29;)

Discrete systems can be modeled by the following set of equations.

The dsfunc.c example implements a discrete state-space equation. The mdlOutputs method contains the output portion and the mdlUpdate method contains the update portion of the discrete state-space equation. To visualize how the simulation works, see the flow chart inSimulink Engine Interaction with C S-Functions. The output equation above corresponds to the mdlOutputs in the major time step. The preceding update equation corresponds to themdlUpdate in the major time step. If your model does not contain continuous elements, the Simulink engine skips the integration phase and time is moved forward to the next discrete sample hit.

matlabroot/toolbox/simulink/sfuntemplates/src/dsfunc.c

The S-function [dsfunc.c](https://mdsite.deno.dev/matlab:edit%28fullfile%28matlabroot,'/toolbox/simulink/sfuntemplates/src/dsfunc.c'%29%29) begins with #define statements for the S-function name and level, along with a#include statement for the simstruc.h header. After these statements, the S-function can include or define any other necessary headers, data, etc. The dsfunc.c example definesU as a pointer to the first input port's signal and initializes static variables for the state-space matrices.

/* File : dsfunc.c

#define S_FUNCTION_NAME dsfunc #define S_FUNCTION_LEVEL 2

#include "simstruc.h"

#define U(element) (uPtrs[element]) / Pointer to Input Port0 */

static real_T A[2][2]={ { -1.3839, -0.5097 } , { 1 , 0 } };

static real_T B[2][2]={ { -2.5559, 0 } , { 0 , 4.2382 } };

static real_T C[2][2]={ { 0 , 2.0761 } , { 0 , 7.7891 } };

static real_T D[2][2]={ { -0.8141, -2.9334 } , { 1.2426, 0 } };

The required S-function method mdlInitializeSizes then sets up the following S-function characteristics.

The mdlInitializeSizes function for this example is shown below.

/====================

/* Function: mdlInitializeSizes ===============================================

/ static void mdlInitializeSizes(SimStruct S) { ssSetNumSFcnParams(S, 0); / Number of expected parameters / if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) { return; / Parameter mismatch reported by the Simulink engine/ }

ssSetNumContStates(S, 0);
ssSetNumDiscStates(S, 2);

if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, 2);
ssSetInputPortDirectFeedThrough(S, 0, 1);

if (!ssSetNumOutputPorts(S, 1)) return;
ssSetOutputPortWidth(S, 0, 2);

ssSetNumSampleTimes(S, 1);
ssSetNumRWork(S, 0);
ssSetNumIWork(S, 0);
ssSetNumPWork(S, 0);
ssSetNumModes(S, 0);
ssSetNumNonsampledZCs(S, 0);

/* Take care when specifying exception free code - see sfuntmpl_doc.c */
ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);

}

The required S-function method mdlInitializeSampleTimes specifies the S-function sample rates. A call to [ssSetSampleTime](sssetsampletime.html) sets this first S-function sample period to 1.0. [ssSetOffsetTime](sssetoffsettime.html) then specifies an offset time of zero for the first sample rate. The call tossSetModelReferenceSampleTimeDefaultInheritance tells the solver to use the default rule to determine if referenced models containing this S-function can inherit their sample times from the parent model.

/* Function: mdlInitializeSampleTimes =========================================

*/ static void mdlInitializeSampleTimes(SimStruct *S) { ssSetSampleTime(S, 0, 1.0); ssSetOffsetTime(S, 0, 0.0); ssSetModelReferenceSampleTimeDefaultInheritance(S); }

The optional S-function method mdlInitializeConditions initializes the discrete state vector. The #define statement before this method is required for the Simulink engine to call this function. In the example below,[ssGetRealDiscStates](ssgetrealdiscstates.html) obtains a pointer to the discrete state vector. The for loop then initializes each discrete state to one.

#define MDL_INITIALIZE_CONDITIONS /* Function: mdlInitializeConditions ========================================

*/ static void mdlInitializeConditions(SimStruct *S) { real_T *x0 = ssGetRealDiscStates(S); int_T lp;

for (lp=0;lp<2;lp++) { 
    *x0++=1.0; 
}

}

The required mdlOutputs function computes the output signal of this S-function. The beginning of the function obtains pointers to the first output port, discrete states, and first input port. The S-function uses the data in these arrays to solve the output equation y=Cx+Du.

/* Function: mdlOutputs =======================================================

*/ static void mdlOutputs(SimStruct *S, int_T tid) { real_T *y = ssGetOutputPortRealSignal(S,0); real_T *x = ssGetRealDiscStates(S); InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

UNUSED_ARG(tid); /* not used in single tasking mode */

/* y=Cx+Du */
y[0]=C[0][0]*x[0]+C[0][1]*x[1]+D[0][0]*U(0)+D[0][1]*U(1);
y[1]=C[1][0]*x[0]+C[1][1]*x[1]+D[1][0]*U(0)+D[1][1]*U(1);

}

The Simulink engine calls the mdlUpdate function once every major integration time step to update the discrete states' values. Because this function is an optional method, a #define statement must precede the function. The beginning of the function obtains pointers to the S-function discrete states and first input port. The S-function uses the data in these arrays to solve the equation dx=Ax+Bu, which is stored in the temporary variable tempX before being assigned into the discrete state vector x.

#define MDL_UPDATE /* Function: mdlUpdate ======================================================

*/ static void mdlUpdate(SimStruct *S, int_T tid) { real_T tempX[2] = {0.0, 0.0}; real_T *x = ssGetRealDiscStates(S); InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

UNUSED_ARG(tid); /* not used in single tasking mode */

/* xdot=Ax+Bu */
tempX[0]=A[0][0]*x[0]+A[0][1]*x[1]+B[0][0]*U(0)+B[0][1]*U(1);
tempX[1]=A[1][0]*x[0]+A[1][1]*x[1]+B[1][0]*U(0)+B[1][1]*U(1);

x[0]=tempX[0];
x[1]=tempX[1];

}

The required mdlTerminate function performs any actions, such as freeing memory, necessary at the end of the simulation. In this example, the function is empty.

/* Function: mdlTerminate =====================================================

*/ static void mdlTerminate(SimStruct S) { UNUSED_ARG(S); / unused input argument */ }

The required S-function trailer includes the files necessary for simulation or code generation, as follows.

#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX file? / #include "simulink.c" / MEX file interface mechanism / #else #include "cg_sfun.h" / Code generation registration function */ #endif

Note

The mdlOutputs and mdlTerminate functions use the UNUSED_ARG macro to indicate that an input argument the callback requires is not used. This optional macro is defined in [simstruc_types.h](https://mdsite.deno.dev/matlab:edit%28[matlabroot,'/simulink/include/simstruc%5Ftypes.h']%29;). If used, you must call this macro once for each input argument that a callback does not use.

Continuous and Discrete States

The [mixedm.c](https://mdsite.deno.dev/matlab:sfunddg%5Fcb%5Fedit%28'mixedm'%29;) example shows a hybrid (a combination of continuous and discrete states) system. The mixedm.c example combines elements of csfunc.c and dsfunc.c. The following Simulink model uses this S-function.

[sfcndemo_mixedm](https://mdsite.deno.dev/matlab:open%5Fsystem%28[matlabroot,'/toolbox/simulink/sfuntemplates/sfcndemo%5Fmixedm']%29;)

If you have a hybrid system, the mdlDerivatives method calculates the derivatives of the continuous states of the state vector,x, and the mdlUpdate method contains the equations used to update the discrete state vector, xD. ThemdlOutputs method computes the S-function outputs after checking for sample hits to determine at what point the S-function is being called.

In Simulink block diagram form, the S-function mixedm.c looks like

which implements a continuous integrator followed by a discrete unit delay.

matlabroot/toolbox/simulink/sfuntemplates/src/mixedm.c

The S-function [mixedm.c](https://mdsite.deno.dev/matlab:edit%28fullfile%28matlabroot,'/toolbox/simulink/sfuntemplates/src/mixedm.c'%29%29) begins with #define statements for the S-function name and level, along with a#include statement for the simstruc.h header. After these statements, the S-function can include or define any other necessary headers, data, etc. The mixedm.c example definesU as a pointer to the first input port's signal.

/* File : mixedm.c

#define S_FUNCTION_NAME mixedm #define S_FUNCTION_LEVEL 2

#include "simstruc.h"

#define U(element) (uPtrs[element]) / Pointer to Input Port0 */

The required S-function method mdlInitializeSizes then sets up the following S-function characteristics.

The mdlInitializeSizes function for this example is shown below.

====================

/* Function: mdlInitializeSizes ===============================================

/ static void mdlInitializeSizes(SimStruct S) { ssSetNumSFcnParams(S, 0); / Number of expected parameters / if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) { return; / Parameter mismatch reported by the Simulink engine/ }

ssSetNumContStates(S, 1);
ssSetNumDiscStates(S, 1);
ssSetNumRWork(S, 1);  /* for zoh output feeding the delay operator */

if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, 1);
ssSetInputPortDirectFeedThrough(S, 0, 1);
ssSetInputPortSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME);
ssSetInputPortOffsetTime(S, 0, 0.0);

if (!ssSetNumOutputPorts(S, 1)) return;
ssSetOutputPortWidth(S, 0, 1);
ssSetOutputPortSampleTime(S, 0, 1.0);
ssSetOutputPortOffsetTime(S, 0, 0.0);

ssSetNumSampleTimes(S, 2);

/* Take care when specifying exception free code - see sfuntmpl_doc.c. */
ssSetOptions(S, (SS_OPTION_EXCEPTION_FREE_CODE |
                 SS_OPTION_PORT_SAMPLE_TIMES_ASSIGNED));

} /* end mdlInitializeSizes */

The required S-function method mdlInitializeSampleTimes specifies the S-function block-based sample rates. The first call to[ssSetSampleTime](sssetsampletime.html) specifies that the first sample rate is continuous, with the subsequent call to[ssSetOffsetTime](sssetoffsettime.html) setting the offset to zero. The second call to this pair of macros sets the second sample time to 1 with an offset of zero. The S-function port-based sample times set in mdlInitializeSizes must all be registered as a block-based sample time. The call tossSetModelReferenceSampleTimeDefaultInheritance tells the solver to use the default rule to determine if referenced models containing this S-function can inherit their sample times from the parent model.

/* Function: mdlInitializeSampleTimes =========================================

*/ static void mdlInitializeSampleTimes(SimStruct *S) { ssSetSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME); ssSetOffsetTime(S, 0, 0.0);

ssSetSampleTime(S, 1, 1.0);
ssSetOffsetTime(S, 1, 0.0);
ssSetModelReferenceSampleTimeDefaultInheritance(S);

} /* end mdlInitializeSampleTimes */

The optional S-function method mdlInitializeConditions initializes the continuous and discrete state vectors. The#define statement before this method is required for the Simulink engine to call this function. In this example, [ssGetContStates](ssgetcontstates.html) obtains a pointer to the continuous state vector and [ssGetRealDiscStates](ssgetrealdiscstates.html) obtains a pointer to the discrete state vector. The method then sets all states' initial conditions to one.

#define MDL_INITIALIZE_CONDITIONS /* Function: mdlInitializeConditions ==========================================

*/ static void mdlInitializeConditions(SimStruct *S) { real_T *xC0 = ssGetContStates(S); real_T *xD0 = ssGetRealDiscStates(S);

xC0[0] = 1.0;
xD0[0] = 1.0;

} /* end mdlInitializeConditions */

The required mdlOutputs function performs computations based on the current task. The macro [ssIsContinuousTask](ssiscontinuoustask.html) checks if the continuous task is executing. If this macro returns true, [ssIsSpecialSampleHit](ssisspecialsamplehit.html) then checks if the discrete sample rate is also executing. If this macro also returnstrue, the method sets the value of the floating-point work vector to the current value of the continuous state, via pointers obtained using [ssGetRWork](ssgetrwork.html) and[ssGetContStates](ssgetcontstates.html), respectively. ThemdlUpdate method later uses the floating-point work vector as the input to the zero-order hold. Updating the work vector inmdlOutputs ensures that the correct values are available during subsequent calls to mdlUpdate. Finally, if the S-function is running at its discrete rate, i.e., the call to [ssIsSampleHit](ssissamplehit.html) returns true, the method sets the output to the value of the discrete state.

/* Function: mdlOutputs =======================================================

*/ static void mdlOutputs(SimStruct S, int_T tid) { / update the internal "zoh" output */ if (ssIsContinuousTask(S, tid)) { if (ssIsSpecialSampleHit(S, 1, 0, tid)) { real_T *zoh = ssGetRWork(S); real_T *xC = ssGetContStates(S); *zoh = *xC; } }

/* y=xD */
if (ssIsSampleHit(S, 1, tid)) {
    real_T *y   = ssGetOutputPortRealSignal(S,0);
    real_T *xD  = ssGetRealDiscStates(S);
    y[0]=xD[0];
}

} /* end mdlOutputs */

The Simulink engine calls the mdlUpdate function once every major integration time step to update the discrete states' values. Because this function is an optional method, a #define statement must precede the function. The call to ssIsSampleHit ensures the body of the method is executed only when the S-function is operating at its discrete rate. If ssIsSampleHit returnstrue, the method obtains pointers to the S-function discrete state and floating-point work vector and updates the discrete state's value using the value stored in the work vector.

#define MDL_UPDATE /* Function: mdlUpdate ======================================================

*/ static void mdlUpdate(SimStruct S, int_T tid) { UNUSED_ARG(tid); / not used in single tasking mode */

/* xD=xC */
if (ssIsSampleHit(S, 1, tid)) {
    real_T *xD = ssGetRealDiscStates(S);
    real_T *zoh = ssGetRWork(S);
    xD[0]=*zoh;
}

} /* end mdlUpdate */

The mdlDerivatives function calculates the continuous state derivatives. Because this function is an optional method, a#define statement must precede the function. The function obtains pointers to the S-function continuous state derivative and first input port then sets the continuous state derivative equal to the value of the first input.

#define MDL_DERIVATIVES /* Function: mdlDerivatives =================================================

*/ static void mdlDerivatives(SimStruct *S) { real_T *dx = ssGetdX(S); InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

/* xdot=U */
dx[0]=U(0);

} /* end mdlDerivatives */

The required mdlTerminate function performs any actions, such as freeing memory, necessary at the end of the simulation. In this example, the function is empty.

/* Function: mdlTerminate =====================================================

*/ static void mdlTerminate(SimStruct S) { UNUSED_ARG(S); / unused input argument */ }

The S-function trailer includes the files necessary for simulation or code generation, as follows.

#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX file? / #include "simulink.c" / MEX file interface mechanism / #else #include "cg_sfun.h" / Code generation registration function */ #endif

Note

The mdlUpdate and mdlTerminate functions use the UNUSED_ARG macro to indicate that an input argument the callback requires is not used. This optional macro is defined in [simstruc_types.h](https://mdsite.deno.dev/matlab:edit%28[matlabroot,'/simulink/include/simstruc%5Ftypes.h']%29;). If used, you must call this macro once for each input argument that a callback does not use.

Variable Sample Time

The example S-function [vsfunc.c](https://mdsite.deno.dev/matlab:sfunddg%5Fcb%5Fedit%28'vsfunc'%29;) uses a variable-step sample time. The following Simulink model uses this S-function.

[sfcndemo_vsfunc](https://mdsite.deno.dev/matlab:open%5Fsystem%28[matlabroot,'/toolbox/simulink/sfuntemplates/sfcndemo%5Fvsfunc']%29;)

Variable step-size functions require a call tomdlGetTimeOfNextVarHit, which is an S-function routine that calculates the time of the next sample hit. S-functions that use the variable-step sample time can be used only with variable-step solvers. Thevsfunc.c example is a discrete S-function that delays its first input by an amount of time determined by the second input.

The vsfunc.c example outputs the input u delayed by a variable amount of time. mdlOutputs sets the outputy equal to state x.mdlUpdate sets the state vector x equal tou, the input vector. This example callsmdlGetTimeOfNextVarHit to calculate and set the time of the next sample hit, that is, the time when vsfunc.c is next called. In mdlGetTimeOfNextVarHit, the macro [ssGetInputPortRealSignalPtrs](ssgetinputportrealsignalptrs.html) gets a pointer to the inputu. Then this call is made:

ssSetTNext(S, ssGetT(S) + U(1));

The macro [ssGetT](ssgett.html) gets the simulation time t. The second input to the block,U(1), is added to t, and the macro[ssSetTNext](sssettnext.html) sets the time of the next hit equal to t+U(1), delaying the output by the amount of time set in (U(1)).

matlabroot/toolbox/simulink/sfuntemplates/src/vsfunc.c

The S-function [vsfunc.c](https://mdsite.deno.dev/matlab:edit%28fullfile%28matlabroot,'/toolbox/simulink/sfuntemplates/src/vsfunc.c'%29%29) begins with #define statements for the S-function name and level, along with a#include statement for the simstruc.h header. After these statements, the S-function can include or define any other necessary headers, data, etc. The vsfunc.c example definesU as a pointer to the first input port's signal.

/* File : vsfunc.c

#define S_FUNCTION_NAME vsfunc #define S_FUNCTION_LEVEL 2

#include "simstruc.h"

#define U(element) (uPtrs[element]) / Pointer to Input Port0 */

The required S-function method mdlInitializeSizes then sets up the following S-function characteristics.

The mdlInitializeSizes function for this example is shown below.

/* Function: mdlInitializeSizes ===============================================

/ static void mdlInitializeSizes(SimStruct S) { ssSetNumSFcnParams(S, 0); / Number of expected parameters / if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) { return; / Parameter mismatch reported by the Simulink engine/ }

ssSetNumContStates(S, 0);
ssSetNumDiscStates(S, 1);

if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, 2);
ssSetInputPortDirectFeedThrough(S, 0, 1);

if (!ssSetNumOutputPorts(S, 1)) return;
ssSetOutputPortWidth(S, 0, 1);

ssSetNumSampleTimes(S, 1);
ssSetNumRWork(S, 0);
ssSetNumIWork(S, 0);
ssSetNumPWork(S, 0);
ssSetNumModes(S, 0);
ssSetNumNonsampledZCs(S, 0);

if (ssGetSimMode(S) == SS_SIMMODE_RTWGEN && !ssIsVariableStepSolver(S)) {
    ssSetErrorStatus(S, "S-function vsfunc.c cannot be used with RTW "
                     "and Fixed-Step Solvers because it contains variable"
                     " sample time");
}

/* Take care when specifying exception free code - see sfuntmpl_doc.c */
ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);

}

The required S-function method mdlInitializeSampleTimes specifies the S-function sample rates. The input argumentVARIABLE_SAMPLE_TIME passed to [ssSetSampleTime](sssetsampletime.html) specifies that this S-function has a variable-step sample time and[ssSetOffsetTime](sssetoffsettime.html) specifies an offset time of zero. The call tossSetModelReferenceSampleTimeDefaultInheritance tells the solver to use the default rule to determine if referenced models containing this S-function can inherit their sample times from the parent model. Because the S-function has a variable-step sample time, vsfunc.c must calculate the time of the next sample hit in themdlGetTimeOfNextVarHit method, shown later.

/* Function: mdlInitializeSampleTimes =========================================

*/ static void mdlInitializeSampleTimes(SimStruct *S) { ssSetSampleTime(S, 0, VARIABLE_SAMPLE_TIME); ssSetOffsetTime(S, 0, 0.0); ssSetModelReferenceSampleTimeDefaultInheritance(S); }

The optional S-function method mdlInitializeConditions initializes the discrete state vector. The #define statement before this method is required for the Simulink engine to call this function. In the example, the method uses[ssGetRealDiscStates](ssgetrealdiscstates.html) to obtain a pointer to the discrete state vector and sets the state's initial value to zero.

#define MDL_INITIALIZE_CONDITIONS /* Function: mdlInitializeConditions ========================================

*/ static void mdlInitializeConditions(SimStruct *S) { real_T *x0 = ssGetRealDiscStates(S);

x0[0] = 0.0;

}

The optional mdlGetTimeOfNextVarHit method calculates the time of the next sample hit. Because this method is optional, a#define statement precedes it. First, this method obtains a pointer to the first input port's signal using [ssGetInputPortRealSignalPtrs](ssgetinputportrealsignalptrs.html). If the input signal's second element is positive, the macro [ssGetT](ssgett.html) gets the simulation time t. The macro [ssSetTNext](sssettnext.html) sets the time of the next hit equal to t+(*U[1]), delaying the output by the amount of time specified by the input's second element (*U[1]).

#define MDL_GET_TIME_OF_NEXT_VAR_HIT static void mdlGetTimeOfNextVarHit(SimStruct *S) { InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

/* Make sure input will increase time */
if (U(1) <= 0.0) {
    /* If not, abort simulation */
    ssSetErrorStatus(S,"Variable step control input must be "
                     "greater than zero");
    return;
}
ssSetTNext(S, ssGetT(S)+U(1));

}

The required mdlOutputs function computes the S-function output signal. The function obtains pointers to the first output port and discrete state and then assigns the state's current value to the output.

/* Function: mdlOutputs =======================================================

*/ static void mdlOutputs(SimStruct *S, int_T tid) { real_T *y = ssGetOutputPortRealSignal(S,0); real_T *x = ssGetRealDiscStates(S);

/* Return the current state as the output */
y[0] = x[0];

}

The mdlUpdate function updates the discrete state's value. Because this method is optional, a #define statement precedes it. The function first obtains pointers to the S-function discrete state and first input port then assigns the value of the first element of the first input port signal to the state.

#define MDL_UPDATE /* Function: mdlUpdate ========================================================

*/ static void mdlUpdate(SimStruct *S, int_T tid) { real_T *x = ssGetRealDiscStates(S); InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

x[0]=U(0);

}

The required mdlTerminate function performs any actions, such as freeing memory, necessary at the end of the simulation. In this example, the function is empty.

/* Function: mdlTerminate =====================================================

*/ static void mdlTerminate(SimStruct *S) { }

The required S-function trailer includes the files necessary for simulation or code generation, as follows.

#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX file? / #include "simulink.c" / MEX file interface mechanism / #else #include "cg_sfun.h" / Code generation registration function */ #endif

Array Inputs and Outputs

The example S-function [sfun_matadd.c](https://mdsite.deno.dev/matlab:sfunddg%5Fcb%5Fedit%28'sfun%5Fmatadd.c'%29;) demonstrates how to implement a matrix addition block. The following Simulink model uses this S-function.

[sfcndemo_matadd](https://mdsite.deno.dev/matlab:open%5Fsystem%28[matlabroot,'/toolbox/simulink/sfuntemplates/sfcndemo%5Fmatadd']%29;)

The S-function adds signals of various dimensions to a parameter value entered in the S-function. The S-function accepts and outputs 2-D or n-D signals.

matlabroot/toolbox/simulink/sfuntemplates/src/sfun_matadd.c

The S-function [sfun_matadd.c](https://mdsite.deno.dev/matlab:edit%28fullfile%28matlabroot,'/toolbox/simulink/sfuntemplates/src/sfun%5Fmatadd.c'%29%29) begins with #define statements for the S-function name and level, along with a#include statement for the simstruc.h header. After these statements, the S-function includes or defines any other necessary headers, data, etc. This example defines additional variables for the number of S-function parameters, the S-function parameter value, and the flagEDIT_OK that indicates if the parameter value can be edited during simulation.

/* SFUN_MATADD matrix support example.

#define S_FUNCTION_NAME sfun_matadd #define S_FUNCTION_LEVEL 2

#include "simstruc.h"

enum {PARAM = 0, NUM_PARAMS};

#define PARAM_ARG ssGetSFcnParam(S, PARAM)

#define EDIT_OK(S, ARG)
(!((ssGetSimMode(S) == SS_SIMMODE_SIZES_CALL_ONLY)
&& mxIsEmpty(ARG)))

The S-function next implements the mdlCheckParameters method to validate the S-function dialog parameters. The#ifdef statement checks that the S-function is compiled as a MEX file, instead of for use with the Simulink Coder product. Because mdlCheckParameters is optional, the S-function code contains a #define statement to register the method. The body of the function checks that the S-function parameter value is not empty. If the parameter check fails, the S-function errors out with a call to ssSetErrorStatus.

#ifdef MATLAB_MEX_FILE #define MDL_CHECK_PARAMETERS /* Function: mdlCheckParameters ================================

/ static void mdlCheckParameters(SimStruct S) { if(EDIT_OK(S, PARAM_ARG)){ / Check that parameter value is not empty/ if( mxIsEmpty(PARAM_ARG) ) { ssSetErrorStatus(S, "Invalid parameter specified. The" "parameter must be non-empty"); return; }
} } /* end mdlCheckParameters */ #endif

The required S-function method mdlInitializeSizes then sets up the following S-function characteristics.

/* Function: mdlInitializeSizes ================================

#if defined(MATLAB_MEX_FILE) if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) { return; } mdlCheckParameters(S); if (ssGetErrorStatus(S) != NULL) return; #endif

{ int iParam = 0; int nParam = ssGetNumSFcnParams(S);

  for ( iParam = 0; iParam < nParam; iParam++ )
    {
      ssSetSFcnParamTunable( S, iParam, SS_PRM_TUNABLE );
    }
}

/* Allow signal dimensions greater than 2 */ ssAllowSignalsWithMoreThan2D(S);

/* Set number of input and output ports */ if (!ssSetNumInputPorts( S,1)) return; if (!ssSetNumOutputPorts(S,1)) return;

/* Set dimensions of input and output ports / { int_T pWidth = mxGetNumberOfElements(PARAM_ARG); / Input can be a scalar or a matrix signal. */ if(!ssSetInputPortDimensionInfo(S,0,DYNAMIC_DIMENSION)) { return; }

  if( pWidth == 1) { 
   /* Scalar parameter: output dimensions are unknown. */
   if(!ssSetOutputPortDimensionInfo(S,0,DYNAMIC_DIMENSION)){
       return; }
   }
  else{
     /* 
      * Non-scalar parameter: output dimensions are the same
      * as the parameter dimensions. To support n-D signals,
      * must use a dimsInfo structure to specify dimensions.
      */
      DECL_AND_INIT_DIMSINFO(di); /*Initializes structure*/
      int_T      pSize = mxGetNumberOfDimensions(PARAM_ARG);
      const int_T *pDims = mxGetDimensions(PARAM_ARG);
      di.width   = pWidth;
      di.numDims = pSize;
      di.dims    = pDims;
      if(!ssSetOutputPortDimensionInfo(S, 0, &di)) return;
      }
}
ssSetInputPortDirectFeedThrough(S, 0, 1);

ssSetNumSampleTimes(S, 1);
ssSetOptions(S,
             SS_OPTION_WORKS_WITH_CODE_REUSE |
             SS_OPTION_EXCEPTION_FREE_CODE);

} /* end mdlInitializeSizes */

The required S-function method mdlInitializeSampleTimes specifies the S-function sample rates. To specify that this S-function inherits its sample time from its driving block, the S-function calls [ssSetSampleTime](sssetsampletime.html) with the input argument INHERITED_SAMPLE_TIME. The call to[ssSetModelReferenceSampleTimeDefaultInheritance](sssetmodelreferencesampletimedefaultinheritance.html) tells the solver to use the default rule to determine if referenced models containing this S-function can inherit their sample times from the parent model.

/* Function: mdlInitializeSampleTimes ==========================

*/ static void mdlInitializeSampleTimes(SimStruct S) { ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME); ssSetOffsetTime(S, 0, 0.0); ssSetModelReferenceSampleTimeDefaultInheritance(S); } / end mdlInitializeSampleTimes */

The S-function calls the mdlSetWorkWidths method to register its run-time parameters. Because mdlSetWorkWidths is an optional method, a #define statement precedes it. The method first initializes a name for the run-time parameter and then usesssRegAllTunableParamsAsRunTimeParams to register the run-time parameter.

/* Function: mdlSetWorkWidths ==================================

*/ #define MDL_SET_WORK_WIDTHS static void mdlSetWorkWidths(SimStruct *S) { const char_T rtParamNames[] = {"Operand"}; ssRegAllTunableParamsAsRunTimeParams(S, rtParamNames); } / end mdlSetWorkWidths */

The S-function mdlOutputs method uses afor loop to calculate the output as the sum of the input and S-function parameter. The S-function handles n-D arrays of data using a single index into the array.

/* Function: mdlOutputs ========================================

int_T             uWidth = ssGetInputPortWidth(S,0);
int_T             pWidth = mxGetNumberOfElements(PARAM_ARG);
int_T             yWidth = ssGetOutputPortWidth(S,0);
int               i;

UNUSED_ARG(tid); /* not used in single tasking mode */

/*
 * Note1: Matrix signals are stored in column major order.
 * Note2: Access each matrix element by one index not two
 *        indices. For example, if the output signal is a 
 *        [2x2] matrix signal,
 *        -          -
 *       | y[0]  y[2] |
 *       | y[1]  y[3] |
 *       -           -
 *       Output elements are stored as follows:
 *           y[0] --> row = 0, col = 0
 *           y[1] --> row = 1, col = 0
 *           y[2] --> row = 0, col = 1
 *           y[3] --> row = 1, col = 1
 */

for (i = 0; i < yWidth; i++) {
    int_T uIdx = (uWidth == 1) ? 0 : i;
    int_T pIdx = (pWidth == 1) ? 0 : i;
    
    y[i] = *uPtr[uIdx] + p[pIdx];
}

} /* end mdlOutputs */

During signal propagation, the S-function calls the optionalmdlSetInputPortDimensionInfo method with the candidate input port dimensions stored in dimsInfo. The #if defined statement checks that the S-function is compiled as a MEX file. Because mdlSetInputPortDimensionInfo is an optional method, a #define statement precedes it. InmdlSetInputPortDimensionInfo, the S-function uses[ssSetInputPortDimensionInfo](sssetinputportdimensioninfo.html) to set the dimensions of the input port to the candidate dimensions. If the call to this macro succeeds, the S-function further checks the candidate dimensions to ensure that the input signal is either a 2-D scalar or a matrix. If this condition is met and the output port dimensions are still dynamically sized, the S-function calls[ssSetOutputPortDimensionInfo](sssetoutputportdimensioninfo.html) to set the dimension of the output port to the same candidate dimensions. ThessSetOutputPortDimensionInfo macro cannot modify the output port dimensions if they are already specified.

#if defined(MATLAB_MEX_FILE) #define MDL_SET_INPUT_PORT_DIMENSION_INFO /* Function: mdlSetInputPortDimensionInfo ======================

*/ static void mdlSetInputPortDimensionInfo(SimStruct *S, int_T port, const DimsInfo_T *dimsInfo) { int_T pWidth = mxGetNumberOfElements(PARAM_ARG); int_T pSize = mxGetNumberOfDimensions(PARAM_ARG); const int_T *pDims = mxGetDimensions(PARAM_ARG);

int_T  uNumDims = dimsInfo->numDims;
int_T  uWidth   = dimsInfo->width;
int_T  *uDims   = dimsInfo->dims;

int_T numDims;
boolean_T  isOk = true;
int iParam = 0;
int_T outWidth = ssGetOutputPortWidth(S, 0);

/* Set input port dimension */
if(!ssSetInputPortDimensionInfo(S, port, dimsInfo)) return;

/*
 * The block only accepts 2-D or higher signals. Check 
 * number of dimensions. If the parameter and the input
 * signal are non-scalar, their dimensions must be the same.
 */
isOk = (uNumDims >= 2) && (pWidth == 1 || uWidth == 1 || 
   pWidth == uWidth);
numDims = (pSize != uNumDims) ? numDims : uNumDims;

if(isOk && pWidth > 1 && uWidth > 1){
    for ( iParam = 0; iParam < numDims; iParam++ ) {
        isOk = (pDims[iParam] == uDims[iParam]);
        if(!isOk) break;
    }
}

if(!isOk){
    ssSetErrorStatus(S,"Invalid input port dimensions. The "
    "input signal must be a 2-D scalar signal, or it must "
    "be a matrix with the same dimensions as the parameter "
    "dimensions.");
    return;
}

/* Set the output port dimensions */
if (outWidth == DYNAMICALLY_SIZED){
  if(!ssSetOutputPortDimensionInfo(S,port,dimsInfo)) return;
}

} /* end mdlSetInputPortDimensionInfo */

During signal propagation, if any output ports have unknown dimensions, the S-function calls the optional mdlSetOutputPortDimensionInfo method. Because this method is optional, a #define statement precedes it. In mdlSetOutputPortDimensionInfo, the S-function uses [ssSetOutputPortDimensionInfo](sssetoutputportdimensioninfo.html) to set the dimensions of the output port to the candidate dimensions dimsInfo. If the call to this macro succeeds, the S-function further checks the candidate dimensions to ensure that the input signal is either a 2-D or n-D matrix. If this condition is not met, the S-function errors out with a call tossSetErrorStatus. Otherwise, the S-function calls[ssSetInputPortDimensionInfo](sssetinputportdimensioninfo.html) to set the dimension of the input port to the same candidate dimensions.

define MDL_SET_OUTPUT_PORT_DIMENSION_INFO

/* Function: mdlSetOutputPortDimensionInfo =====================

*/ static void mdlSetOutputPortDimensionInfo(SimStruct *S, int_T port, const DimsInfo_T dimsInfo) { / * If the block has scalar parameter, the output dimensions * are unknown. Set the input and output port to have the * same dimensions. */ if(!ssSetOutputPortDimensionInfo(S, port, dimsInfo)) return;

/* The block only accepts 2-D or n-D signals.
 * Check number of dimensions.
 */
if (!(dimsInfo->numDims >= 2)){
    ssSetErrorStatus(S, "Invalid output port dimensions. "
    "The output signal must be a 2-D or n-D array (matrix) "
    "signal.");
    return;
}else{
   /* Set the input port dimensions */
   if(!ssSetInputPortDimensionInfo(S,port,dimsInfo)) return;
}

} /* end mdlSetOutputPortDimensionInfo */

Because the S-function has ports that are dynamically sized, it must provide an mdlSetDefaultPortDimensionInfo method. The Simulink engine invokes this method during signal propagation when it cannot determine the dimensionality of the signal connected to the block's input port. This situation can happen, for example, if the input port is unconnected. In this example, the mdlSetDefaultPortDimensionInfo method sets the input and output ports dimensions to a scalar.

define MDL_SET_DEFAULT_PORT_DIMENSION_INFO

/* Function: mdlSetDefaultPortDimensionInfo ====================

/ static void mdlSetDefaultPortDimensionInfo(SimStruct S) { int_T outWidth = ssGetOutputPortWidth(S, 0); / Input port dimension must be unknown. Set it to scalar./ if(!ssSetInputPortMatrixDimensions(S, 0, 1, 1)) return; if(outWidth == DYNAMICALLY_SIZED){ /* Output dimensions are unknown. Set it to scalar. / if(!ssSetOutputPortMatrixDimensions(S, 0, 1, 1)) return; } } / end mdlSetDefaultPortDimensionInfo */ #endif

The required mdlTerminate function performs any actions, such as freeing memory, necessary at the end of the simulation. In this example, the function is empty.

/* Function: mdlTerminate ======================================

*/ static void mdlTerminate(SimStruct S) { UNUSED_ARG(S); / unused input argument */

} /* end mdlTerminate */

The required S-function trailer includes the files necessary for simulation or code generation.

#ifdef MATLAB_MEX_FILE #include "simulink.c" #else #include "cg_sfun.h" #endif

/* [EOF] sfun_matadd.c */

Note

The mdlOutputs and mdlTerminate functions use the UNUSED_ARG macro to indicate that an input argument the callback requires is not used. This optional macro is defined in [simstruc_types.h](https://mdsite.deno.dev/matlab:edit%28[matlabroot,'/simulink/include/simstruc%5Ftypes.h']%29;). You must call this macro once for each input argument that a callback does not use.

Zero-Crossing Detection

The example S-function [sfun_zc_sat.c](https://mdsite.deno.dev/matlab:sfunddg%5Fcb%5Fedit%28'sfun%5Fzc%5Fsat'%29;) demonstrates how to implement a Saturation block. The following Simulink model uses this S-function.

[sfcndemo_sfun_zc_sat](https://mdsite.deno.dev/matlab:open%5Fsystem%28[matlabroot,'/toolbox/simulink/sfuntemplates/sfcndemo%5Fsfun%5Fzc%5Fsat']%29;)

The S-function works with either fixed-step or variable-step solvers. When this S-function inherits a continuous sample time and uses a variable-step solver, it uses a zero-crossings algorithm to locate the exact points at which the saturation occurs.

matlabroot/toolbox/simulink/sfuntemplates/src/sfun_zc_sat.c

The S-function [sfun_zc_sat.c](https://mdsite.deno.dev/matlab:edit%28fullfile%28matlabroot,'/toolbox/simulink/sfuntemplates/src/sfun%5Fzc%5Fsat.c'%29%29) begins with #define statements for the S-function name and level, along with a#include statement for the simstruc.h header. After these statements, the S-function includes or defines any other necessary headers, data, etc. This example defines various parameters associated with the upper and lower saturation bounds.

/* File : sfun_zc_sat.c

#define S_FUNCTION_NAME sfun_zc_sat #define S_FUNCTION_LEVEL 2

#include "simstruc.h"

/========================

/* index to Upper Limit */ #define I_PAR_UPPER_LIMIT 0

/* index to Lower Limit */ #define I_PAR_LOWER_LIMIT 1

/* total number of block parameters */ #define N_PAR 2

/*

#define P_PAR_UPPER_LIMIT ( ssGetSFcnParam(S,I_PAR_UPPER_LIMIT) ) #define P_PAR_LOWER_LIMIT ( ssGetSFcnParam(S,I_PAR_LOWER_LIMIT) )

This S-function next implements the mdlCheckParameters method to check the validity of the S-function dialog parameters. Because this method is optional, a #define statement precedes it. The#if defined statement checks that this function is compiled as a MEX file, instead of for use with the Simulink Coder product. The body of the function performs basic checks to ensure that the user entered real vectors of equal length for the upper and lower saturation limits. If the parameter checks fail, the S-function errors out.

#define MDL_CHECK_PARAMETERS #if defined(MDL_CHECK_PARAMETERS) && defined(MATLAB_MEX_FILE)

/* Function: mdlCheckParameters =============================================

  /*
   * check parameter basics
   */
  for ( i = 0; i < N_PAR; i++ ) {
      if ( mxIsEmpty(    ssGetSFcnParam(S,i) ) ||
           mxIsSparse(   ssGetSFcnParam(S,i) ) ||
           mxIsComplex(  ssGetSFcnParam(S,i) ) ||
           !mxIsNumeric( ssGetSFcnParam(S,i) ) ) {
          msg = "Parameters must be real vectors.";
          goto EXIT_POINT;
      }
  }

  /*
   * Check sizes of parameters.
   */
  numUpperLimit = mxGetNumberOfElements( P_PAR_UPPER_LIMIT );
  numLowerLimit = mxGetNumberOfElements( P_PAR_LOWER_LIMIT );

  if ( ( numUpperLimit != 1             ) &&
       ( numLowerLimit != 1             ) &&
       ( numUpperLimit != numLowerLimit ) ) {
      msg = "Number of input and output values must be equal.";
      goto EXIT_POINT;
  }

  /*
   * Error exit point
   */

EXIT_POINT: if (msg != NULL) { ssSetErrorStatus(S, msg); } } #endif /* MDL_CHECK_PARAMETERS */

The required S-function method mdlInitializeSizes sets up the following S-function characteristics.

The mdlInitializeSizes function for this example is shown below.

/* Function: mdlInitializeSizes ===============================================

/*
 * Set and Check parameter count
 */
ssSetNumSFcnParams(S, N_PAR);

#if defined(MATLAB_MEX_FILE) if (ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S)) { mdlCheckParameters(S); if (ssGetErrorStatus(S) != NULL) { return; } } else { return; /* Parameter mismatch reported by the Simulink engine*/ } #endif

/*
 * Get parameter size info.
 */
numUpperLimit = mxGetNumberOfElements( P_PAR_UPPER_LIMIT );
numLowerLimit = mxGetNumberOfElements( P_PAR_LOWER_LIMIT );

if (numUpperLimit > numLowerLimit) {
    maxNumLimit = numUpperLimit;
} else {
    maxNumLimit = numLowerLimit;
}

/*
 * states
 */
ssSetNumContStates(S, 0);
ssSetNumDiscStates(S, 0);

/*
 * outputs
 *   The upper and lower limits are scalar expanded
 *   so their size determines the size of the output
 *   only if at least one of them is not scalar.
 */
if (!ssSetNumOutputPorts(S, 1)) return;

if ( maxNumLimit > 1 ) {
    ssSetOutputPortWidth(S, 0, maxNumLimit);
} else {
    ssSetOutputPortWidth(S, 0, DYNAMICALLY_SIZED);
}

/*
 * inputs
 *   If the upper or lower limits are not scalar then
 *   the input is set to the same size.  However, the
 *   ssSetOptions below allows the actual width to
 *   be reduced to 1 if needed for scalar expansion.
 */
if (!ssSetNumInputPorts(S, 1)) return;

ssSetInputPortDirectFeedThrough(S, 0, 1 );

if ( maxNumLimit > 1 ) {
    ssSetInputPortWidth(S, 0, maxNumLimit);
} else {
    ssSetInputPortWidth(S, 0, DYNAMICALLY_SIZED);
}

/*
 * sample times
 */
ssSetNumSampleTimes(S, 1);

/*
 * work
 */
ssSetNumRWork(S, 0);
ssSetNumIWork(S, 0);
ssSetNumPWork(S, 0);


/*
 * Modes and zero crossings:
 * If we have a variable-step solver and this block has a continuous
 * sample time, then
 *   o One mode element will be needed for each scalar output
 *     in order to specify which equation is valid (1), (2), or (3).
 *   o Two ZC elements will be needed for each scalar output
 *     in order to help the solver find the exact instants
 *     at which either of the two possible "equation switches"
 *     One will be for the switch from eq. (1) to (2);
 *     the other will be for eq. (2) to (3) and vice versa.
 * otherwise
 *   o No modes and nonsampled zero crossings will be used.
 *
 */
ssSetNumModes(S, DYNAMICALLY_SIZED);
ssSetNumNonsampledZCs(S, DYNAMICALLY_SIZED);

/*
 * options
 *   o No mexFunctions and no problematic mxFunctions are called
 *     so the exception free code option safely gives faster simulations.
 *   o Scalar expansion of the inputs is desired.  The option provides
 *     this without the need to  write mdlSetOutputPortWidth and
 *     mdlSetInputPortWidth functions.
 */
ssSetOptions(S, ( SS_OPTION_EXCEPTION_FREE_CODE |
                  SS_OPTION_ALLOW_INPUT_SCALAR_EXPANSION));

} /* end mdlInitializeSizes */

The required S-function method mdlInitializeSampleTimes specifies the S-function sample rates. The input argumentINHERITED_SAMPLE_TIME passed to [ssSetSampleTime](sssetsampletime.html) specifies that this S-function inherits its sample time from its driving block. The call to ssSetModelReferenceSampleTimeDefaultInheritance tells the solver to use the default rule to determine if referenced models containing this S-function can inherit their sample times from the parent model.

/* Function: mdlInitializeSampleTimes =========================================

*/ static void mdlInitializeSampleTimes(SimStruct *S) { ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME); ssSetOffsetTime(S, 0, 0); ssSetModelReferenceSampleTimeDefaultInheritance(S); }

The optional method mdlSetWorkWidths initializes the size of the zero-crossing detection work vectors. Because this method is optional, a#define statement precedes it. The #if defined statement checks that the S-function is being compiled as a MEX file. Zero-crossing detection can be done only when the S-function is running at a continuous sample rate using a variable-step solver. Theif statement uses [ssIsVariableStepSolver](ssisvariablestepsolver.html), [ssGetSampleTime](ssgetsampletime.html), and [ssGetOffsetTime](ssgetoffsettime.html) to determine if this condition is met. If so, the method sets the number of modes equal to the width of the first output port and the number of nonsampled zero crossings to twice this amount. Otherwise, the method sets both values to zero.

#define MDL_SET_WORK_WIDTHS #if defined(MDL_SET_WORK_WIDTHS) && defined(MATLAB_MEX_FILE) /* Function: mdlSetWorkWidths ===============================================

if (ssIsVariableStepSolver(S) &&
    ssGetSampleTime(S,0) == CONTINUOUS_SAMPLE_TIME &&
    ssGetOffsetTime(S,0) == 0.0) {

    int numOutput = ssGetOutputPortWidth(S, 0);

    /*
     * modes and zero crossings 
     *    o One mode element will be needed for each scalar output
     *      in order to specify which equation is valid (1), (2), or (3).
     *    o Two ZC elements will be needed for each scalar output
     *      in order to help the solver find the exact instants
     *      at which either of the two possible "equation switches"
     *      One will be for the switch from eq. (1) to (2);
     *      the other will be for eq. (2) to (3) and vice versa.
     */
    nModes         = numOutput;
    nNonsampledZCs = 2 * numOutput;
} else {
    nModes         = 0;
    nNonsampledZCs = 0;
}
ssSetNumModes(S,nModes);
ssSetNumNonsampledZCs(S,nNonsampledZCs);

} #endif /* MDL_SET_WORK_WIDTHS */

After declaring variables for the input and output signals, themdlOutputs functions uses an if-else statement to create blocks of code used to calculate the output signal based on whether the S-function uses a fixed-step or variable-step solver. Theif statement queries the length of the nonsampled zero-crossing vector. If the length, set in mdlWorkWidths, is zero, then no zero-crossing detection is done and the output signals are calculated directly from the input signals. Otherwise, the function uses the mode work vector to determine how to calculate the output signal. If the simulation is at a major time step, i.e., [ssIsMajorTimeStep](ssismajortimestep.html) returns true,mdlOutputs determines which mode the simulation is running in, either saturated at the upper limit, saturated at the lower limit, or not saturated. Then, for both major and minor time steps, the function calculates an output based on this mode. If the mode changed between the previous and current time step, then a zero crossing occurred. ThemdlZeroCrossings function, notmdlOutputs, indicates this crossing to the solver.

/* Function: mdlOutputs =======================================================

} /* end mdlOutputs */

The mdlZeroCrossings method determines if a zero crossing occurred between the previous and current time step. The method obtains a pointer to the input signal using [ssGetInputPortRealSignalPtrs](ssgetinputportrealsignalptrs.html). A comparison of this signal's value to the value of the upper and lower saturation limits determines values for the elements of the nonsampled zero-crossing vector. If any element of the nonsampled zero-crossing vector switches from negative to positive, or positive to negative, a zero crossing occurred. In the event of a zero crossing, the Simulink engine modifies the step size and recalculates the outputs to try to locate the exact zero crossing.

#define MDL_ZERO_CROSSINGS #if defined(MDL_ZERO_CROSSINGS) && (defined(MATLAB_MEX_FILE) || defined(NRT))

/* Function: mdlZeroCrossings =================================================

}

#endif /* end mdlZeroCrossings */

The S-function concludes with the required mdlTerminate function. In this example, the function is empty.

/* Function: mdlTerminate =====================================================

*/ static void mdlTerminate(SimStruct S) { UNUSED_ARG(S); / unused input argument */ }

The required S-function trailer includes the files necessary for simulation or code generation, as follows.

#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX file? / #include "simulink.c" / MEX file interface mechanism / #else #include "cg_sfun.h" / Code generation registration function */ #endif

Note

The mdlOutputs and mdlTerminate functions use the UNUSED_ARG macro to indicate that an input argument the callback requires is not used. This optional macro is defined in [simstruc_types.h](https://mdsite.deno.dev/matlab:edit%28[matlabroot,'/simulink/include/simstruc%5Ftypes.h']%29;). If used, you must call this macro once for each input argument that a callback does not use.

Discontinuities in Continuous States

The example S-function [stvctf.c](https://mdsite.deno.dev/matlab:sfunddg%5Fcb%5Fedit%28'stvctf'%29;) demonstrates a time-varying continuous transfer function. The following Simulink model uses this S-function.

[sfcndemo_stvctf](https://mdsite.deno.dev/matlab:open%5Fsystem%28'sfcndemo%5Fstvctf'%29;)

The S-function demonstrates how to work with the solvers so that the simulation maintains consistency, which means that the block maintains smooth and consistent signals for the integrators although the equations that are being integrated are changing.

matlabroot/toolbox/simulink/sfuntemplates/src/stvctf.c

The S-function [stvctf.c](https://mdsite.deno.dev/matlab:edit%28fullfile%28matlabroot,'/toolbox/simulink/sfuntemplates/src/stvctf.c'%29%29) begins with #define statements for the S-function name and level, along with a#include statement for the simstruc.h header. After these statements, the S-function includes or defines any other necessary headers, data, etc. This example defines parameters for the transfer function's numerator and denominator, which are entered into the S-function dialog. The comments at the beginning of this S-function provide additional information on the purpose of the work vectors in this example.

/*

#define S_FUNCTION_NAME stvctf #define S_FUNCTION_LEVEL 2

#include "simstruc.h"

/*

#define NUM(S) ssGetSFcnParam(S, 0) #define DEN(S) ssGetSFcnParam(S, 1) #define TS(S) ssGetSFcnParam(S, 2) #define NPARAMS 3

This S-function implements the mdlCheckParameters method to check the validity of the S-function dialog parameters. Because this method is optional, a #define statement precedes it. The #if defined statement checks that this function is compiled as a MEX file, instead of for use with the Simulink Coder product. The body of the function performs basic checks to ensure that the user entered real vectors for the numerator and denominator, and that the denominator has a higher order than the numerator. If the parameter check fails, the S-function errors out.

#define MDL_CHECK_PARAMETERS #if defined(MDL_CHECK_PARAMETERS) && defined(MATLAB_MEX_FILE) /* Function: mdlCheckParameters =============================================

*/ static void mdlCheckParameters(SimStruct *S) { int_T i;

  for (i = 0; i < NPARAMS; i++) {
      real_T *pr;
      int_T   el;
      int_T   nEls;
      if (mxIsEmpty(    ssGetSFcnParam(S,i)) ||
          mxIsSparse(   ssGetSFcnParam(S,i)) ||
          mxIsComplex(  ssGetSFcnParam(S,i)) ||
          !mxIsNumeric( ssGetSFcnParam(S,i)) ) {
          ssSetErrorStatus(S,"Parameters must be real finite vectors");
          return;
      } 
      pr   = mxGetPr(ssGetSFcnParam(S,i));
      nEls = mxGetNumberOfElements(ssGetSFcnParam(S,i));
      for (el = 0; el < nEls; el++) {
          if (!mxIsFinite(pr[el])) {
              ssSetErrorStatus(S,"Parameters must be real finite vectors");
              return;
          }
      }
  }

  if (mxGetNumberOfElements(NUM(S)) > mxGetNumberOfElements(DEN(S)) &&
      mxGetNumberOfElements(DEN(S)) > 0  && *mxGetPr(DEN(S)) != 0.0) {
      ssSetErrorStatus(S,"The denominator must be of higher order than "
                       "the numerator, nonempty and with first "
                       "element nonzero");
      return;
  }

  /* xxx verify finite */
  if (mxGetNumberOfElements(TS(S)) != 1 || mxGetPr(TS(S))[0] <= 0.0) {
      ssSetErrorStatus(S,"Invalid sample time specified");
      return;
  }

} #endif /* MDL_CHECK_PARAMETERS */

The required S-function method mdlInitializeSizes then sets up the following S-function characteristics.

The mdlInitializeSizes function for this example is shown below.

/* Function: mdlInitializeSizes ===============================================

*/ static void mdlInitializeSizes(SimStruct *S) { int_T nContStates; int_T nCoeffs;

/* See sfuntmpl_doc.c for more details on the macros below. */

ssSetNumSFcnParams(S, NPARAMS);  /* Number of expected parameters. */

#if defined(MATLAB_MEX_FILE) if (ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S)) { mdlCheckParameters(S); if (ssGetErrorStatus(S) != NULL) { return; } } else { return; /* Parameter mismatch reported by the Simulink engine*/ } #endif

/*
 * Define the characteristics of the block:
 *
 *   Number of continuous states:     length of denominator - 1
 *   Inputs port width                2 * (NumContStates+1) + 1
 *   Output port width                1
 *   DirectFeedThrough:               0 (Although this should be computed.
 *                                       We'll assume coefficients entered
 *                                       are strictly proper).
 *   Number of sample times:          2 (continuous and discrete)
 *   Number of Real work elements:    4*NumCoeffs
 *                                    (Two banks for num and den coeff's:
 *                                     NumBank0Coeffs
 *                                     DenBank0Coeffs
 *                                     NumBank1Coeffs
 *                                     DenBank1Coeffs)
 *   Number of Integer work elements: 2 (indicator of active bank 0 or 1
 *                                       and flag to indicate when banks
 *                                       have been updated).
 *
 * The number of inputs arises from the following:
 *   o 1 input (u)
 *   o the numerator and denominator polynomials each have NumContStates+1
 *     coefficients
 */
nCoeffs     = mxGetNumberOfElements(DEN(S));
nContStates = nCoeffs - 1;

ssSetNumContStates(S, nContStates);
ssSetNumDiscStates(S, 0);

if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, 1 + (2*nCoeffs));
ssSetInputPortDirectFeedThrough(S, 0, 0);
ssSetInputPortSampleTime(S, 0, mxGetPr(TS(S))[0]);
ssSetInputPortOffsetTime(S, 0, 0);

if (!ssSetNumOutputPorts(S,1)) return;
ssSetOutputPortWidth(S, 0, 1);
ssSetOutputPortSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME);
ssSetOutputPortOffsetTime(S, 0, 0);

ssSetNumSampleTimes(S, 2);

ssSetNumRWork(S, 4 * nCoeffs);
ssSetNumIWork(S, 2);
ssSetNumPWork(S, 0);

ssSetNumModes(S, 0);
ssSetNumNonsampledZCs(S, 0);

/* Take care when specifying exception free code - see sfuntmpl_doc.c */
ssSetOptions(S, (SS_OPTION_EXCEPTION_FREE_CODE));

} /* end mdlInitializeSizes */

The required S-function method mdlInitializeSampleTimes specifies the S-function sample rates. The first call to [ssSetSampleTime](sssetsampletime.html) specifies that the first sample rate is continuous and the subsequent call to[ssSetOffsetTime](sssetoffsettime.html) sets the offset to zero. The second call to this pair of macros sets the second sample time to the value of the third S-function parameter with an offset of zero. The call tossSetModelReferenceSampleTimeDefaultInheritance tells the solver to use the default rule to determine if referenced models containing this S-function can inherit their sample times from the parent model.

/* Function: mdlInitializeSampleTimes =========================================

*/ static void mdlInitializeSampleTimes(SimStruct S) { / * the first sample time, continuous */ ssSetSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME); ssSetOffsetTime(S, 0, 0.0);

/*
 * the second, discrete sample time, is user provided
 */
ssSetSampleTime(S, 1, mxGetPr(TS(S))[0]);
ssSetOffsetTime(S, 1, 0.0);
ssSetModelReferenceSampleTimeDefaultInheritance(S);

} /* end mdlInitializeSampleTimes */

The optional S-function method mdlInitializeConditions initializes the continuous state vector and the initial numerator and denominator vectors. The #define statement before this method is required for the Simulink engine to call this function. The function initializes the continuous states to zero. The numerator and denominator coefficients are initialized from the first two S-function parameters, normalized by the first denominator coefficient. The function sets the value stored in the IWork vector to zero, to indicate that the first bank of numerator and denominator coefficients stored in the RWork vector is currently in use.

#define MDL_INITIALIZE_CONDITIONS /* Function: mdlInitializeConditions ==========================================

*/ static void mdlInitializeConditions(SimStruct *S) { int_T i; int_T nContStates = ssGetNumContStates(S); real_T *x0 = ssGetContStates(S); int_T nCoeffs = nContStates + 1; real_T *numBank0 = ssGetRWork(S); real_T *denBank0 = numBank0 + nCoeffs; int_T *activeBank = ssGetIWork(S);

/*
 * The continuous states are all initialized to zero.
 */
for (i = 0; i < nContStates; i++) {
    x0[i]       = 0.0;
    numBank0[i] = 0.0;
    denBank0[i] = 0.0;
}
numBank0[nContStates] = 0.0;
denBank0[nContStates] = 0.0;

/*
 * Set up the initial numerator and denominator.
 */
{
    const real_T *numParam   = mxGetPr(NUM(S));
    int          numParamLen = mxGetNumberOfElements(NUM(S));

    const real_T *denParam   = mxGetPr(DEN(S));
    int          denParamLen = mxGetNumberOfElements(DEN(S));
    real_T       den0        = denParam[0];

    for (i = 0; i < denParamLen; i++) {
        denBank0[i] = denParam[i] / den0;
    }

    for (i = 0; i < numParamLen; i++) {
        numBank0[i] = numParam[i] / den0;
    }
}

/*
 * Normalize if this transfer function has direct feedthrough.
 */
for (i = 1; i < nCoeffs; i++) {
    numBank0[i] -= denBank0[i]*numBank0[0];
}

/*
 * Indicate bank0 is active (i.e. bank1 is oldest).
 */
*activeBank = 0;

} /* end mdlInitializeConditions */

The mdlOutputs function calculates the S-function output signals when the S-function is simulating in a continuous task, i.e.,[ssIsContinuousTask](ssiscontinuoustask.html) is true. If the simulation is also at a major time step, mdlOutputs checks if the numerator and denominator coefficients need to be updated, as indicated by a switch in the active bank stored in the IWork vector. At both major and minor time steps, the S-function calculates the output using the numerator coefficients stored in the active bank.

/* Function: mdlOutputs =======================================================

*/ static void mdlOutputs(SimStruct *S, int_T tid) { if (ssIsContinuousTask(S,tid)) { int i; real_T *num; int nContStates = ssGetNumContStates(S); real_T *x = ssGetContStates(S); int_T nCoeffs = nContStates + 1; InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0); real_T *y = ssGetOutputPortRealSignal(S,0); int_T *activeBank = ssGetIWork(S);

    /*
     * Switch banks because we've updated them in mdlUpdate and we're no 
     * longer in a minor time step.
     */
    if (ssIsMajorTimeStep(S)) {
        int_T *banksUpdated = ssGetIWork(S) + 1;
        if (*banksUpdated) {
            *activeBank = !(*activeBank);
            *banksUpdated = 0;
            /*
             * Need to tell the solvers that the derivatives are no
             * longer valid.
             */
            ssSetSolverNeedsReset(S);
        }
    }
    num = ssGetRWork(S) + (*activeBank) * (2*nCoeffs);

    /*
     * The continuous system is evaluated using a controllable state space
     * representation of the transfer function.  This implies that the
     * output of the system is equal to:
     *
     *     y(t) = Cx(t) + Du(t)
     *          = [ b1 b2 ... bn]x(t) + b0u(t)
     *
     * where b0, b1, b2, ... are the coefficients of the numerator 
     * polynomial:
     *
     *    B(s) = b0 s^n + b1 s^n-1 + b2 s^n-2 + ... + bn-1 s + bn
     */
    *y = *num++ * (*uPtrs[0]);
    for (i = 0; i < nContStates; i++) {
        *y += *num++ * *x++;
    }
}

} /* end mdlOutputs */

Although this example has no discrete states, the method still implements themdlUpdate function to update the transfer function coefficients at every major time step. Because this method is optional, a#define statement precedes it. The method uses[ssGetInputPortRealSignalPtrs](ssgetinputportrealsignalptrs.html) to obtain a pointer to the input signal. The input signal's values become the new transfer function coefficients, which the S-function stores in the bank of the inactive RWork vector. When the mdlOutputs function is later called at this major time step, it updates the active bank to be this updated bank of coefficients.

#define MDL_UPDATE /* Function: mdlUpdate ========================================================

*/ static void mdlUpdate(SimStruct S, int_T tid) { if (ssIsSampleHit(S, 1, tid)) { int_T i; InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0); int_T uIdx = 1;/1st coeff is after signal input/ int_T nContStates = ssGetNumContStates(S); int_T nCoeffs = nContStates + 1; int_T bankToUpdate = !ssGetIWork(S)[0]; real_T num = ssGetRWork(S)+bankToUpdate2nCoeffs; real_T *den = num + nCoeffs;

    real_T            den0;
    int_T             allZero;


    /*
     * Get the first denominator coefficient.  It will be used
     * for normalizing the numerator and denominator coefficients.
     *
     * If all inputs are zero, we probably could have unconnected
     * inputs, so use the parameter as the first denominator coefficient.
     */
    den0 = *uPtrs[uIdx+nCoeffs];
    if (den0 == 0.0) {
        den0 = mxGetPr(DEN(S))[0];
    }

    /*
     * Grab the numerator.
     */
    allZero = 1;
    for (i = 0; (i < nCoeffs) && allZero; i++) {
        allZero &= *uPtrs[uIdx+i] == 0.0;
    }

    if (allZero) { /* if numerator is all zero */
        const real_T *numParam   = mxGetPr(NUM(S));
        int_T        numParamLen = mxGetNumberOfElements(NUM(S));
        /*
         * Move the input to the denominator input and
         * get the denominator from the input parameter.
         */
        uIdx += nCoeffs;
        num += nCoeffs - numParamLen;
        for (i = 0; i < numParamLen; i++) {
            *num++ = *numParam++ / den0;
        }
    } else {
        for (i = 0; i < nCoeffs; i++) {
            *num++ = *uPtrs[uIdx++] / den0;
        }
    }

    /*
     * Grab the denominator.
     */
    allZero = 1;
    for (i = 0; (i < nCoeffs) && allZero; i++) {
        allZero &= *uPtrs[uIdx+i] == 0.0;
    }

    if (allZero) {  /* If denominator is all zero. */
        const real_T *denParam   = mxGetPr(DEN(S));
        int_T        denParamLen = mxGetNumberOfElements(DEN(S));

        den0 = denParam[0];
        for (i = 0; i < denParamLen; i++) {
            *den++ = *denParam++ / den0;
        }
    } else {
        for (i = 0; i < nCoeffs; i++) {
            *den++ = *uPtrs[uIdx++] / den0;
        }
    }

    /*
     * Normalize if this transfer function has direct feedthrough.
     */
    num = ssGetRWork(S) + bankToUpdate*2*nCoeffs;
    den = num + nCoeffs;
    for (i = 1; i < nCoeffs; i++) {
        num[i] -= den[i]*num[0];
    }

    /*
     * Indicate oldest bank has been updated.
     */
    ssGetIWork(S)[1] = 1;
}

} /* end mdlUpdate */

The mdlDerivatives function calculates the continuous state derivatives. The function uses the coefficients from the active bank to solve a controllable state-space representation of the transfer function.

#define MDL_DERIVATIVES /* Function: mdlDerivatives ===================================================

*/ static void mdlDerivatives(SimStruct *S) { int_T i; int_T nContStates = ssGetNumContStates(S); real_T *x = ssGetContStates(S); real_T dx = ssGetdX(S); int_T nCoeffs = nContStates + 1; int_T activeBank = ssGetIWork(S)[0]; const real_T num = ssGetRWork(S) + activeBank(2nCoeffs); const real_T *den = num + nCoeffs; InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

/*
 * The continuous system is evaluated using a controllable state-space
 * representation of the transfer function.  This implies that the
 * next continuous states are computed using:
 *
 *     dx = Ax(t) + Bu(t)
 *        = [-a1 -a2 ... -an] [x1(t)] + [u(t)]
 *          [  1  0  ...   0] [x2(t)] + [0]
 *          [  0  1  ...   0] [x3(t)] + [0]
 *          [  .  .  ...   .]   .     +  .
 *          [  .  .  ...   .]   .     +  .
 *          [  .  .  ...   .]   .     +  .
 *          [  0  0  ... 1 0] [xn(t)] + [0]
 *
 * where a1, a2, ... are the coefficients of the numerator polynomial:
 *
 *    A(s) = s^n + a1 s^n-1 + a2 s^n-2 + ... + an-1 s + an
 */
dx[0] = -den[1] * x[0] + *uPtrs[0];
for (i = 1; i < nContStates; i++) {
    dx[i] = x[i-1];
    dx[0] -= den[i+1] * x[i];
}

} /* end mdlDerivatives */

The required mdlTerminate function performs any actions, such as freeing memory, necessary at the end of the simulation. In this example, the function is empty.

/* Function: mdlTerminate =====================================================

*/ static void mdlTerminate(SimStruct S) { UNUSED_ARG(S); / unused input argument / } / end mdlTerminate */

The required S-function trailer includes the files necessary for simulation or code generation, as follows.

#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX file? / #include "simulink.c" / MEX file interface mechanism / #else #include "cg_sfun.h" / Code generation registration function */ #endif

Note

The mdlTerminate function uses theUNUSED_ARG macro to indicate that an input argument the callback requires is not used. This optional macro is defined in[simstruc_types.h](https://mdsite.deno.dev/matlab:edit%28[matlabroot,'/simulink/include/simstruc%5Ftypes.h']%29;). If used, you must call this macro once for each input argument that a callback does not use.

See Also

mdlInitializeSizes | mdlUpdate | mdlTerminate

Topics