/*  File    : sfunc_pmf.c
 *  Abstract: 16bit S12Z PMF
 *
 *
 *  Copyright 1990-2013 The MathWorks, Inc.
 */

#define S_FUNCTION_NAME     sfunc_pmf
#define S_FUNCTION_LEVEL    2

#include "simstruc.h"

// Units
#define NANO                            (1e-9)
#define MICRO                           (1e-6)
#define KILO                            (1e3)
#define MEGA                            (1e6)

// no. of outputs
#define OUTPUT_NUM                      (2)

// Inputs
#define Inp_Modulus                     (*uPtrs[0])
#define Inp_PwmVal                      (*uPtrs[1])

// Discrete states
#define DiskState_Modulus               (x[0])
#define DiskState_PwmVal                (x[1])

// Parameters
#define Par_GetClockFreq()              ((*mxGetPr(ssGetSFcnParam(S, 0))) * MEGA)
#define PMF_CLK_RES                     (1.0/Par_GetClockFreq())

#define MAX_HIT_TIMES                   (3)
#define HS_INDEX                        (0)
#define LS_INDEX                        (1)
#define SEQ_HS_OFF_LS_OFF               {0, 0} // {HS_INDEX, LS_INDEX}
#define SEQ_HS_ON_LS_OFF                {1, 0}
#define SEQ_HS_OFF_LS_ON                {0, 1}
#define SET_SEQ(seq_index, HSLS_Seq)    (memcpy(sw_seqs[seq_index], (boolean_T[])HSLS_Seq, sizeof((boolean_T[])HSLS_Seq)))

real_T    TimeHit_arr[MAX_HIT_TIMES];
int_T     TimeHit_index = 0;
int_T     TimeHitIndexMax = MAX_HIT_TIMES;
boolean_T sw_seqs[MAX_HIT_TIMES][2];
real_T    NextHitTime;

#define MDL_CHECK_PARAMETERS
/* Function: mdlCheckParameters =============================================
* Abstract:
*    Validate our parameters to verify they are okay.
*/
static void mdlCheckParameters(SimStruct *S)
{
}
  
/* Function: mdlInitializeSizes ===============================================
 * Abstract:
 *    The sizes information is used by Simulink to determine the S-function
 *    block's characteristics (number of inputs, outputs, states, etc.).
 */
static void mdlInitializeSizes(SimStruct *S)
{
    ssSetNumSFcnParams(S, 1);  /* Number of expected parameters */
    if (ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S)) {
      mdlCheckParameters(S);
      if(ssGetErrorStatus(S) != NULL) return;
    } else {    
      return; // Parameter mismatch will be reported by Simulink 
    }

    // set all 1 parameter(s) as not tunable
    ssSetSFcnParamTunable(S, 0 ,0 ); //  
    
    ssSetNumContStates(S, 0);
    
    // 2 Disc States
    ssSetNumDiscStates(S, 2);

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

    if (!ssSetNumOutputPorts(S, OUTPUT_NUM)) return;
    {
        int_T i = 0;              
        for(i=0; i < OUTPUT_NUM; i++){
            ssSetOutputPortWidth(S, i, 1);
        }
    }

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

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

    ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);
}

/* Function: mdlInitializeSampleTimes =========================================
 * Abstract:
 *    Variable-Step S-function
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, VARIABLE_SAMPLE_TIME);
    ssSetOffsetTime(S, 0, 0.0);
    ssSetModelReferenceSampleTimeDefaultInheritance(S); 
}

#define MDL_INITIALIZE_CONDITIONS
/* Function: mdlInitializeConditions ========================================
 * Abstract:
 *    Initialize discrete state.
 */
static void mdlInitializeConditions(SimStruct *S)
{
    real_T *x = ssGetRealDiscStates(S);
            
    DiskState_Modulus = 1000;
    DiskState_PwmVal = (uint16_T)0;
    
    TimeHit_index = 0;             
    NextHitTime = 0.0;
}



#define MDL_GET_TIME_OF_NEXT_VAR_HIT
static void mdlGetTimeOfNextVarHit(SimStruct *S)
{
    real_T *x = ssGetRealDiscStates(S);
    
    if (TimeHit_index == 0) {        // new PWM-periode begins                
        if (DiskState_PwmVal <= 0) { // Duty cycle <= 0%
            TimeHitIndexMax = 1;
            TimeHit_arr[0] = ssGetT(S) + (real_T)(DiskState_Modulus * 2.0) * PMF_CLK_RES;
            SET_SEQ(0, SEQ_HS_OFF_LS_ON);
        } else {                    // Duty cycle >= 100%            
            if (DiskState_PwmVal >= DiskState_Modulus) {
                TimeHitIndexMax = 1;
                TimeHit_arr[0] = ssGetT(S) + (real_T)(DiskState_Modulus * 2.0) * PMF_CLK_RES;
                SET_SEQ(0, SEQ_HS_ON_LS_OFF);
            } else {                // 0% < Duty cycle < 100%                                
                TimeHitIndexMax = 3;
                TimeHit_arr[0] = ssGetT(S) + (real_T)DiskState_PwmVal * PMF_CLK_RES; 
                TimeHit_arr[1] = ssGetT(S) + (real_T)(DiskState_Modulus * 2.0 - DiskState_PwmVal) * PMF_CLK_RES;
                TimeHit_arr[2] = ssGetT(S) + (real_T)(DiskState_Modulus * 2.0) * PMF_CLK_RES;

                SET_SEQ(0, SEQ_HS_ON_LS_OFF);
                SET_SEQ(1, SEQ_HS_OFF_LS_ON);
                SET_SEQ(2, SEQ_HS_ON_LS_OFF);
            }
        }
    }
    
    NextHitTime = TimeHit_arr[TimeHit_index];

    /* Make sure input will increase time */
    if (NextHitTime <= ssGetT(S)) {
        /* If not, abort simulation */
        ssSetErrorStatus(S,"sfunc_pmf.c/mdlGetTimeOfNextVarHit(): Variable step control\n" 
                           "Check NextHitTime!");
        ssPrintf("Error in sfunc_pmf.c/mdlGetTimeOfNextVarHit():\nTimeHit_index = %d\nNextHitTime[s] = %.f\nssGetT(S) = %.f\n",
                  TimeHit_index, NextHitTime, ssGetT(S));
        return;
    }
    
    ssSetTNext(S, NextHitTime);
    
}


/* Function: mdlOutputs =======================================================
 * Abstract:
 *      
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    real_T *y = ssGetOutputPortRealSignal(S,0);
            
    y[0] = sw_seqs[TimeHit_index][HS_INDEX];
    y[1] = sw_seqs[TimeHit_index][LS_INDEX];
            
}



#define MDL_UPDATE
/* Function: mdlUpdate ========================================================
 * Abstract:
 *    This function is called once for every major integration time step.
 *    Discrete states are typically updated here, but this function is useful
 *    for performing any tasks that should only take place once per integration
 *    step.
 */
static void mdlUpdate(SimStruct *S, int_T tid)
{
    real_T            *x    = ssGetRealDiscStates(S);
    InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
       

    // time to latch new duty and modulus
    if(TimeHit_index == 0){
        DiskState_Modulus = (real_T)((uint16_T)Inp_Modulus);
        DiskState_PwmVal  = (real_T)((uint16_T)Inp_PwmVal);
        if((Inp_PwmVal < 0) || (Inp_Modulus < 0)){
            ssSetErrorStatus(S,"sfunc_pmf.c/mdlUpdate(): Input(s) Reload and CmpVal must be > 0!\n");
        }
    }
        
    TimeHit_index++;
    if(TimeHit_index >= TimeHitIndexMax){
        TimeHit_index = 0;            
    }

}

/* Function: mdlTerminate =====================================================
 * Abstract:
 *    No termination needed, but we are required to have this routine.
 */
static void mdlTerminate(SimStruct *S)
{
}

#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
