S-functions model zero crossings using the mode work vector (or a DWork vector configured as a mode vector) and the continuous zero-crossing vector. Whether the S-function uses mode or DWork vectors, the concept and implementation are the same. For an example using DWork vectors to model zero crossings, see DWork Mode Vector in the “Using Work Vectors” section. The remainder of this section uses mode vectors to model zero crossings.

Elements of the mode vector are integer values. You specify the number of mode vector
elements in `mdlInitializeSizes`

, using
`ssSetNumModes(S,num)`

. You can then access the mode vector using
`ssGetModeVector`

. The mode vector values determine how the
`mdlOutputs`

routine operates when the solvers are homing in on
zero crossings. The Simulink^{®} solvers track the zero crossings or state events (i.e., discontinuities in
the first derivatives) of some signal, usually a function of an input to your
S-function, by looking at the continuous zero crossings. Register the number of
continuous zero crossings in `mdlInitializeSizes`

, using
`ssSetNumNonsampledZCs(S, num)`

, then include an
`mdlZeroCrossings`

routine to calculate the continuous zero
crossings. The S-function `sfun_zc_sat.c`

contains a zero-crossing example. The remainder of
this section describes the portions of this S-function that pertain to zero-crossing
detection. For a full description of this example, see Zero-Crossing Detection.

First, `mdlInitializeSizes`

specifies the sizes for the mode and
continuous zero-crossing vectors using the following lines of code.

ssSetNumModes(S, DYNAMICALLY_SIZED); ssSetNumNonsampledZCs(S, DYNAMICALLY_SIZED);

Since the number of modes and continuous zero crossings is dynamically sized,
`mdlSetWorkWidths`

must initialize the actual size of these
vectors. In this example, shown below, there is one mode vector for each output element
and two continuous zero crossings for each mode. In general, the number of continuous
zero crossings needed for each mode depends on the number of events that need to be
detected. In this case, each output (mode) needs to detect when it hits the upper or the
lower bound, hence two continuous zero crossings per mode.

static void mdlSetWorkWidths(SimStruct *S) { int nModes; int nNonsampledZCs; nModes = numOutput; nNonsampledZCs = 2 * numOutput; ssSetNumModes(S,nModes); ssSetNumNonsampledZCs(S,nNonsampledZCs); }

Next, `mdlOutputs`

determines which mode the simulation is running in
at the beginning of each major time step. The method stores this information in the mode
vector so it is available when calculating outputs at both major and minor time
steps.

/* Get the mode vector */ int_T *mode = ssGetModeVector(S); /* Specify three possible mode values.*/ enum { UpperLimitEquation, NonLimitEquation, LowerLimitEquation }; /* Update the mode vector at the beginning of a major time step */ if ( ssIsMajorTimeStep(S) ) { for ( iOutput = 0; iOutput < numOutput; iOutput++ ) { if ( *uPtrs[uIdx] > *upperLimit ) { /* Upper limit is reached. */ mode[iOutput] = UpperLimitEquation; } else if ( *uPtrs[uIdx] < *lowerLimit ) { /* Lower limit is reached. */ mode[iOutput] = LowerLimitEquation; } else { /* Output is not limited. */ mode[iOutput] = NonLimitEquation; } /* Adjust indices to give scalar expansion. */ uIdx += uInc; upperLimit += upperLimitInc; lowerLimit += lowerLimitInc; } /* Reset index to input and limits. */ uIdx = 0; upperLimit = mxGetPr( P_PAR_UPPER_LIMIT ); lowerLimit = mxGetPr( P_PAR_LOWER_LIMIT ); } /* end IsMajorTimeStep */

Output calculations in `mdlOutputs`

are done based on the values
stored in the mode vector.

for ( iOutput = 0; iOutput < numOutput; iOutput++ ) { if ( mode[iOutput] == UpperLimitEquation ) { /* Output upper limit. */ *y++ = *upperLimit; } else if ( mode[iOutput] == LowerLimitEquation ) { /* Output lower limit. */ *y++ = *lowerLimit; } else { /* Output is equal to input */ *y++ = *uPtrs[uIdx]; }

After outputs are calculated, the Simulink engine calls `mdlZeroCrossings`

to determine if a zero
crossing has occurred. A zero crossing is detected if any element of the continuous
zero-crossing vector switches from negative to positive, or positive to negative. If
this occurs, the simulation modifies the step size and recalculates the outputs to try
to locate the exact zero crossing. For this example, the values for the continuous
zero-crossing vectors are calculated as shown below.

static void mdlZeroCrossings(SimStruct *S) { int_T iOutput; int_T numOutput = ssGetOutputPortWidth(S,0); real_T *zcSignals = ssGetNonsampledZCs(S); InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0); /* Set index and increment for the input signal, upper limit, and lower * limit parameters so that each gives scalar expansion if needed. */ int_T uIdx = 0; int_T uInc = ( ssGetInputPortWidth(S,0) > 1 ); const real_T *upperLimit = mxGetPr( P_PAR_UPPER_LIMIT ); int_T upperLimitInc = ( mxGetNumberOfElements( P_PAR_UPPER_LIMIT ) > 1 ); const real_T *lowerLimit = mxGetPr( P_PAR_LOWER_LIMIT ); int_T lowerLimitInc = ( mxGetNumberOfElements( P_PAR_LOWER_LIMIT ) > 1 ); /*Check if the input has crossed an upper or lower limit */ for ( iOutput = 0; iOutput < numOutput; iOutput++ ) { zcSignals[2*iOutput] = *uPtrs[uIdx] - *upperLimit; zcSignals[2*iOutput+1] = *uPtrs[uIdx] - *lowerLimit; /* Adjust indices to give scalar expansion if needed */ uIdx += uInc; upperLimit += upperLimitInc; lowerLimit += lowerLimitInc; } }