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.
Note Level-2 MATLAB^{®} S-functions do not support zero-crossing detection. The remainder of this section pertains only to C MEX S-functions. |
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; } }