Zero Crossings
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;
    }
}