/****************************************************************************
 Module
   PWM_Module.c

 Revision
   1.0.1

 Description
   This is a PWM file for ME218B Lab 8 for Team 3.

 Notes

 History
 When           Who     What/Why
 -------------- ---     --------
 02/04/2019     bibit   converted from lab 7 to lab 8 module
 01/../2019     robbie  lab 7 module
 01/15/12 11:12 jec     revisions for Gen2 framework
 11/07/11 11:26 jec     made the queue static
 10/30/11 17:59 jec     fixed references to CurrentEvent in RunTemplateSM()
 10/23/11 18:20 jec     began conversion from SMTemplate.c (02/20/07 rev)
****************************************************************************/

/*----------------------------- Include Files -----------------------------*/
/* include header files for this state machine as well as any machines at the
   next lower level in the hierarchy that are sub-machines to this machine
*/
#include "ES_Configure.h"
#include "ES_Framework.h"
#include "PWM_Module.h"

#include "ES_Types.h"
#include "termio.h"
#include "BITDEFS.H"
#include <stdint.h>
#include <stdbool.h>

#include "inc/hw_pwm.h"
#include "inc/hw_gpio.h"
#include "inc/hw_sysctl.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_nvic.h"
#include "inc/hw_Timer.h"

#include "driverlib/gpio.h"
#include "driverlib/sysctl.h"
#include "driverlib/pin_map.h"
#include "driverlib/pwm.h"

#include "GameplayHSM.h"

/*----------------------------- Module Defines ----------------------------*/
// Clock is 40MHz..with 32x prescaling that is 125 KHz
// 125 KHz is 1250000 ticks per second or 1250 ticks per milisecond
#define PWMTicksPerMS 1250
#define BitsPerNibble 4
#define MsPerSecond 1000
#define PercentToDec 100

#define PWM0_GenA_Normal (PWM_0_GENA_ACTCMPAU_ONE | PWM_0_GENA_ACTCMPAD_ZERO)
#define PWM0_GenB_Normal (PWM_0_GENB_ACTCMPBU_ONE | PWM_0_GENB_ACTCMPBD_ZERO)
#define PWM1_GenA_Normal (PWM_1_GENA_ACTCMPAU_ONE | PWM_1_GENA_ACTCMPAD_ZERO)
#define PWM1_GenB_Normal (PWM_1_GENB_ACTCMPBU_ONE | PWM_1_GENB_ACTCMPBD_ZERO)

/*---------------------------- Module Functions ---------------------------*/
/* prototypes for private functions for this machine.They should be functions
   relevant to the behavior of this state machine
*/
static void Set0_DC(uint8_t PinNum);
static void Set100_DC(uint8_t PinNum);
static void RestoreDC(uint8_t PinNum);

/*---------------------------- Module Variables ---------------------------*/
// everybody needs a state variable, you may need others as well.
// type of state variable should match htat of enum in header file

// Startup freq. of 200 Hz...(1/StartupFreq) * MsPerSecond;
static uint32_t Startup_Period_ms = 5;
static uint32_t Period_0; // ticks
static uint32_t Duty_0;   // percent

static uint32_t CompareA;
static uint32_t CompareB;

// with the introduction of Gen2, we need a module level Priority var as well

/*------------------------------ Module Code ------------------------------*/
//**************************************************************************/

/****************************************************************************
 Function
     PWM_Init

 Parameters
     uint8_t (HowMany)

 Returns
     None

 Description
     Initializes PWM ports.  Capable of doing 1 or 2 pins at PB6 and PB7.

 Notes
    PWM PORT SEQUENCE:

    NUM. 0 ----> PB6 M0PWM0
    NUM. 1 ----> PB7 M0PWM1

 Author
     -
****************************************************************************/
void PWM_Init(uint8_t NumPWM)
{
  switch (NumPWM)
  {
    // We only want one PWM port. This corresponds to PB6
    case 1:
    {
      // start by enabling the clock to the PWM Module (PWM0)
      HWREG(SYSCTL_RCGCPWM) |= SYSCTL_RCGCPWM_R0;

      // enable the clock to Port B
      HWREG(SYSCTL_RCGCGPIO) |= SYSCTL_RCGCGPIO_R1;

      // make sure that Port B is initialized
      while ((HWREG(SYSCTL_PRGPIO) & SYSCTL_PRGPIO_R1) != BIT1HI)
      {}

      // Select the PWM clock as System Clock/32
      HWREG(SYSCTL_RCC) = (HWREG(SYSCTL_RCC) & ~SYSCTL_RCC_PWMDIV_M) |
          (SYSCTL_RCC_USEPWMDIV | SYSCTL_RCC_PWMDIV_32);

      // make sure that the PWM module clock has gotten going
      while ((HWREG(SYSCTL_PRPWM) & SYSCTL_PRPWM_R0) != SYSCTL_PRPWM_R0)
      {}

      // disable the PWM while initializing
      HWREG(PWM0_BASE + PWM_O_0_CTL) = 0;

      // program generators to go to 1 at rising compare A/B, 0 on falling
      // compare A/B
      HWREG(PWM0_BASE + PWM_O_0_GENA) = PWM0_GenA_Normal;

      // Set the PWM period. Since we are counting both up & down, we
      // initialize the load register to 1/2 the desired total period. We
      // will also program the match compare registers to 1/2 the desired
      // high time
      Period_0 = Startup_Period_ms * PWMTicksPerMS;
      HWREG(PWM0_BASE + PWM_O_0_LOAD) = ((Period_0)) >> 1;

      // Set the initial Duty cycle on A to 50% by programming the compare
      // value to 1/2 the period to count up (or down). Technically, the
      // value to program should be Period/2 - DesiredHighTime/2, but since
      // the desired high time is 1/2 the period, we can skip the subtract
      Duty_0 = 50;
      HWREG(PWM0_BASE + PWM_O_0_CMPA) = HWREG(PWM0_BASE + PWM_O_0_LOAD) >> 1;
      HWREG(PWM0_BASE + PWM_O_ENABLE) |= (PWM_ENABLE_PWM0EN);

      // now configure the Port B pin to be PWM outputs
      // start by selecting the alternate function for PB6
      HWREG(GPIO_PORTB_BASE + GPIO_O_AFSEL) |= (BIT6HI);

      // now choose to map PWM to those pins, this is a mux value of 4 that we
      // want to use for specifying the function on bits 6
      HWREG(GPIO_PORTB_BASE + GPIO_O_PCTL) =
          (HWREG(GPIO_PORTB_BASE + GPIO_O_PCTL) & 0xf0ffffff)
          + (4 << (6 * BitsPerNibble));

      // Enable pin 6 on Port B for digital I/O
      HWREG(GPIO_PORTB_BASE + GPIO_O_DEN) |= (BIT6HI);
      // make pin 6 on Port B an output
      HWREG(GPIO_PORTB_BASE + GPIO_O_DIR) |= (BIT6HI);

      // set the up/down count mode, enable the PWM generator and make
      // both generator updates locally synchronized to zero count
      HWREG(PWM0_BASE + PWM_O_0_CTL) = (PWM_0_CTL_MODE | PWM_0_CTL_ENABLE |
          PWM_0_CTL_GENAUPD_LS | PWM_0_CTL_GENBUPD_LS);
    }
    break;

    // We want 2 PWM ports. This corresponds to PB6 and PB7.
    case 2:
    {
      // start by enabling the clock to the PWM Module (PWM0)
      HWREG(SYSCTL_RCGCPWM) |= SYSCTL_RCGCPWM_R0;

      // enable the clock to Port B
      HWREG(SYSCTL_RCGCGPIO) |= SYSCTL_RCGCGPIO_R1;

      // make sure that Port B is initialized
      while ((HWREG(SYSCTL_PRGPIO) & SYSCTL_PRGPIO_R1) != BIT1HI)
      {}

      // Select the PWM clock as System Clock/32
      HWREG(SYSCTL_RCC) = (HWREG(SYSCTL_RCC) & ~SYSCTL_RCC_PWMDIV_M) |
          (SYSCTL_RCC_USEPWMDIV | SYSCTL_RCC_PWMDIV_32);

      // make sure that the PWM module clock has gotten going
      while ((HWREG(SYSCTL_PRPWM) & SYSCTL_PRPWM_R0) != SYSCTL_PRPWM_R0)
      {}

      // disable the PWM while initializing
      HWREG(PWM0_BASE + PWM_O_0_CTL) = 0;

      // program generators to go to 1 at rising compare A/B, 0 on falling
      // compare A/B
      HWREG(PWM0_BASE + PWM_O_0_GENA) = PWM0_GenA_Normal;
      HWREG(PWM0_BASE + PWM_O_0_GENB) = PWM0_GenB_Normal;

      // Set the PWM period. Since we are counting both up & down, we
      // initialize the load register to 1/2 the desired total period. We
      // will also program the match compare registers to 1/2 the desired
      // high time
      Period_0 = Startup_Period_ms * PWMTicksPerMS;
      HWREG(PWM0_BASE + PWM_O_0_LOAD) = ((Period_0)) >> 1;

      // Set the initial Duty cycle on A to 50% by programming the compare
      // value to 1/2 the period to count up (or down). Technically, the
      // value to program should be Period/2 - DesiredHighTime/2, but since
      // the desired high time is 1/2 the period, we can skip the subtract
      Duty_0 = 50;
      HWREG(PWM0_BASE + PWM_O_0_CMPA) = HWREG(PWM0_BASE + PWM_O_0_LOAD) >> 1;
      // Set the initial Duty cycle on B to 50% like A
      HWREG(PWM0_BASE + PWM_O_0_CMPB) = HWREG(PWM0_BASE + PWM_O_0_LOAD) >> 1;

      // enable the PWM outputs
      HWREG(PWM0_BASE + PWM_O_ENABLE) |= (PWM_ENABLE_PWM0EN | PWM_ENABLE_PWM1EN);

      // now configure the Port B pins to be PWM outputs
      // start by selecting the alternate function for PB6 and PB7
      HWREG(GPIO_PORTB_BASE + GPIO_O_AFSEL) |= (BIT6HI | BIT7HI);

      // now choose to map PWM to those pins, this is a mux value of 4 that
      // we want to use for specifying the function on bits 6 and 7
      HWREG(GPIO_PORTB_BASE + GPIO_O_PCTL) =
          (HWREG(GPIO_PORTB_BASE + GPIO_O_PCTL) & 0x00ffffff)
          + (4 << (6 * BitsPerNibble)) + (4 << (7 * BitsPerNibble));

      // Enable pins 6 and 7 on Port B for digital I/O
      HWREG(GPIO_PORTB_BASE + GPIO_O_DEN) |= (BIT6HI | BIT7HI);
      // make pins 6 and 7 on Port B an output
      HWREG(GPIO_PORTB_BASE + GPIO_O_DIR) |= (BIT6HI | BIT7HI);

      // set the up/down count mode, enable the PWM generator and make
      // both generator updates locally synchronized to zero count
      HWREG(PWM0_BASE + PWM_O_0_CTL) = (PWM_0_CTL_MODE | PWM_0_CTL_ENABLE |
          PWM_0_CTL_GENAUPD_LS | PWM_0_CTL_GENBUPD_LS);
    }
    break;
  }
}

/****************************************************************************
 Function
     PWM_SetFrequency

 Parameters
     uint16_t (PWMFreq)
     uint8_t (Group)

 Returns
     None

 Description
     Sets the PWM frequency to desired value for designated group.

 Notes

 Author
     -
****************************************************************************/
void PWM_SetFrequency(uint16_t PWMFreq, uint8_t Group)
{
  uint32_t  CompVal = 0;
  uint32_t  DesiredHighTime = 0;

  Period_0 = ((MsPerSecond * PWMTicksPerMS) / PWMFreq); // in ticks
  DesiredHighTime = (Period_0 * Duty_0) / 100;          // ticks
  CompVal = Period_0 / 2 - DesiredHighTime / 2;

  switch (Group)
  {
    case DriveMotors:
    {
      // Set the PWM period. Since we are counting both up & down, we initialize
      // the load register to 1/2 the desired total period. We will also program
      // the match compare registers to 1/2 the desired high time
      HWREG(PWM0_BASE + PWM_O_0_LOAD) = ((Period_0) >> 1);

      // modify compare values
      HWREG(PWM0_BASE + PWM_O_0_CMPA) = CompVal;
    }
    break;

    case AllServos:
    {
      // Set the PWM period. Since we are counting both up & down, we initialize
      // the load register to 1/2 the desired total period. We will also program
      // the match compare registers to 1/2 the desired high time
      HWREG(PWM0_BASE + PWM_O_1_LOAD) = ((Period_0) >> 1);
      HWREG(PWM1_BASE + PWM_O_1_LOAD) = ((Period_0) >> 1);

      // modify compare values
      HWREG(PWM0_BASE + PWM_O_1_CMPA) = CompVal;
      HWREG(PWM0_BASE + PWM_O_1_CMPB) = CompVal;
      HWREG(PWM1_BASE + PWM_O_1_CMPA) = CompVal;
      HWREG(PWM1_BASE + PWM_O_1_CMPB) = CompVal;
    }
    break;

    case RecycleEmitter:
    {
      // Set the PWM period. Since we are counting both up & down, we
      // initialize the load register to 1/2 the desired total period. We
      // will also program the match compare registers to 1/2 the desired
      // high time
      HWREG(PWM1_BASE + PWM_O_0_LOAD) = (Period_0 >> 1);

      // modify compare values
      HWREG(PWM1_BASE + PWM_O_0_CMPA) = CompVal;
    }
    break;
    case RobotDetEmitter:
    {
      // Set the PWM period. Since we are counting both up & down, we
      // initialize the load register to 1/2 the desired total period. We
      // will also program the match compare registers to 1/2 the desired
      // high time
      HWREG(PWM0_BASE + PWM_O_2_LOAD) = (Period_0 >> 1);

      // modify compare values
      HWREG(PWM0_BASE + PWM_O_0_CMPA) = CompVal;
    }
    break;
  }
}

/****************************************************************************
 Function
     PWM_SetDuty

 Parameters
     uint8_t:  DutyCycle (0-100)
     uint8_t:  PinNum (0 or 1 to correspond to PB6 or PB7)

 Returns
     none

 Description
     Sets cmpA or cmpB (specified by PinNum) to get the duty cycle of the PWM
     signal to the desired value.

 Notes
     PB6 - PWM M0PWM0 (Alt. No. 4) left drive motor
     PB7 - PWM M0PWM1 (Alt. No. 4) right drive motor
     PB4 - PWM M0PWM2 (Alt. No. 4) recycling bin servo
     PB5 - PWM M0PWM3 (Alt. No. 4) trash bin servo
     PA6 - PWM M1PWM2 (Alt. No. 5) sorter servo
     PA7 - PWM M1PWM3 (Alt. No. 5) team selection servo
     PD0 - PWM M1PWM0 (Alt. No. 5) IR recycling center

 Author
     Bibit Bianchini, 2/04/2019
****************************************************************************/
void PWM_SetDuty(uint8_t DutyCycle, uint8_t PinNum)
{
  // first take care of the duty cycles of 0 or 100 cases:
  if (DutyCycle == 0)
  {
    Set0_DC(PinNum);
  }
  else if (DutyCycle == 100)
  {
    Set100_DC(PinNum);
  }
  else
  {
    // make sure to reset the DC
    RestoreDC(PinNum);

    // now change the correct PWM pin based on PinNum
    switch (PinNum)
    {
      case MotorA:
      {
        // calculate what compare value should be for desired duty cycle
        CompareA = (HWREG(PWM0_BASE + PWM_O_0_LOAD) * (100 - DutyCycle)) / 100;

        // store that compare value into PWM port
        HWREG(PWM0_BASE + PWM_O_0_CMPA) = CompareA;
      }
      break;

      case MotorB:
      {
        // calculate what compare value should be for desired duty cycle
        CompareB = (HWREG(PWM0_BASE + PWM_O_0_LOAD) * (100 - DutyCycle)) / 100;

        // store that compare value into PWM port
        HWREG(PWM0_BASE + PWM_O_0_CMPB) = CompareB;
      }
      break;

      case RecycleServo:
      {
        // calculate what compare value should be for desired duty cycle
        CompareA = (HWREG(PWM0_BASE + PWM_O_1_LOAD) * (100 - DutyCycle)) / 100;        // store that compare value into PWM port
        HWREG(PWM0_BASE + PWM_O_1_CMPA) = CompareA;
      }
      break;

      case TrashServo:
      {
        // calculate what compare value should be for desired duty cycle
        CompareB = (HWREG(PWM0_BASE + PWM_O_1_LOAD) * (100 - DutyCycle)) / 100;        // store that compare value into PWM port
        HWREG(PWM0_BASE + PWM_O_1_CMPB) = CompareB;
      }
      break;

      case SortServo:
      {
        // calculate what compare value should be for desired duty cycle
        CompareA = (HWREG(PWM1_BASE + PWM_O_1_LOAD) * (100 - DutyCycle)) / 100;        // store that compare value into PWM port
        HWREG(PWM1_BASE + PWM_O_1_CMPA) = CompareA;
      }
      break;

      case FlagServo:
      {
        // calculate what compare value should be for desired duty cycle
        CompareB = (HWREG(PWM1_BASE + PWM_O_1_LOAD) * (100 - DutyCycle)) / 100;        // store that compare value into PWM port
        HWREG(PWM1_BASE + PWM_O_1_CMPB) = CompareB;
      }
      break;

      case RecycleEmitter:
      {
        // calculate what compare value should be for desired duty cycle
        CompareA = (HWREG(PWM1_BASE + PWM_O_0_LOAD) * (100 - DutyCycle)) / 100;        // store that compare value into PWM port
        HWREG(PWM1_BASE + PWM_O_0_CMPA) = CompareA;
      }
      break;
    }
  }
}

/***************************************************************************
 private functions
 ***************************************************************************/
/****************************************************************************
 Function
     Set0_DC

 Parameters
     uint8_t: PinNum (0 or 1 to correspond to PB6 or PB7 respectively)

 Returns
     none

 Description
     Deals with the case of 0% duty cycle

 Notes

 Author
     Bibit Bianchini, 2/04/2019
****************************************************************************/
static void Set0_DC(uint8_t PinNum)
{
  // check for which pin we should be changing
  switch (PinNum)
  {
    // if we want to change PB6, then make changes to generator A
    case MotorA:
    {
      // to program 0% DC, set the action on Zero to set the output to 0
      HWREG(PWM0_BASE + PWM_O_0_GENA) = PWM_0_GENA_ACTZERO_ZERO;
    }
    break;

    // if we want to change PB7, then make changes to generator B
    case MotorB:
    {
      // to program 0% DC, set the action on Zero to set the output to 0
      HWREG(PWM0_BASE + PWM_O_0_GENB) = PWM_0_GENB_ACTZERO_ZERO;
    }
    break;

    case RecycleServo:
    {
      // to program 0% DC, set the action on Zero to set the output to 0
      HWREG(PWM0_BASE + PWM_O_1_GENA) = PWM_0_GENA_ACTZERO_ZERO;
    }
    break;

    case TrashServo:
    {
      // to program 0% DC, set the action on Zero to set the output to 0
      HWREG(PWM0_BASE + PWM_O_1_GENB) = PWM_0_GENB_ACTZERO_ZERO;
    }
    break;

    case SortServo:
    {
      // to program 0% DC, set the action on Zero to set the output to 0
      HWREG(PWM1_BASE + PWM_O_1_GENA) = PWM_1_GENA_ACTZERO_ZERO;
    }
    break;

    case FlagServo:
    {
      // to program 0% DC, set the action on Zero to set the output to 0
      HWREG(PWM1_BASE + PWM_O_1_GENB) = PWM_1_GENB_ACTZERO_ZERO;
    }
    break;

    case RecycleEmitter:
    {
      // to program 0% DC, set the action on Zero to set the output to 0
      HWREG(PWM1_BASE + PWM_O_0_GENA) = PWM_0_GENA_ACTZERO_ZERO;
    }
    break;
  }
}

/****************************************************************************
 Function
     Set100_DC

 Parameters
     uint8_t: PinNum (0 or 1 to correspond to PB6 or PB7 respectively)

 Returns
     none

 Description
     Deals with the case of 100% duty cycle

 Notes

 Author
     Bibit Bianchini, 2/04/2019
****************************************************************************/
static void Set100_DC(uint8_t PinNum)
{
  // check for which pin we should be changing
  switch (PinNum)
  {
    // if we want to change PB6, then make changes to generator A
    case MotorA:
    {
      // to program 100% DC, set the action on Zero to set the output to 1
      HWREG(PWM0_BASE + PWM_O_0_GENA) = PWM_0_GENA_ACTZERO_ONE;
    }
    break;

    // if we want to change PB7, then make changes to generator B
    case MotorB:
    {
      // to program 100% DC, set the action on Zero to set the output to 1
      HWREG(PWM0_BASE + PWM_O_0_GENB) = PWM_0_GENB_ACTZERO_ONE;
    }
    break;

    case RecycleServo:
    {
      // to program 100% DC, set the action on Zero to set the output to 1
      HWREG(PWM0_BASE + PWM_O_1_GENA) = PWM_0_GENA_ACTZERO_ONE;
    }
    break;

    case TrashServo:
    {
      // to program 100% DC, set the action on Zero to set the output to 1
      HWREG(PWM0_BASE + PWM_O_1_GENB) = PWM_0_GENB_ACTZERO_ONE;
    }
    break;

    case SortServo:
    {
      // to program 100% DC, set the action on Zero to set the output to 1
      HWREG(PWM1_BASE + PWM_O_1_GENA) = PWM_1_GENA_ACTZERO_ONE;
    }
    break;

    case FlagServo:
    {
      // to program 100% DC, set the action on Zero to set the output to 1
      HWREG(PWM1_BASE + PWM_O_1_GENB) = PWM_1_GENB_ACTZERO_ONE;
    }
    break;

    case RecycleEmitter:
    {
      // to program 100% DC, set the action on Zero to set the output to 1
      HWREG(PWM1_BASE + PWM_O_0_GENA) = PWM_0_GENA_ACTZERO_ONE;
    }
    break;
  }
}

/****************************************************************************
 Function
     RestoreDC

 Parameters
     uint8_t: PinNum

 Returns
     none

 Description
     Restores the original DC settings so PWM of duty cycles in between
     0 and 100 work again.

 Notes

 Author
     Bibit Bianchini, 2/04/2019
****************************************************************************/
static void RestoreDC(uint8_t PinNum)
{
  // check for which pin we should be changing
  switch (PinNum)
  {
    // if we want to change PB6, then make changes to generator A
    case MotorA:
    {
      // to restore the previous DC, set the action back to the normal actions
      HWREG(PWM0_BASE + PWM_O_0_GENA) = PWM0_GenA_Normal;
    }
    break;

    // if we want to change PB7, then make changes to generator B
    case MotorB:
    {
      // to restore the previous DC, set the action back to the normal actions
      HWREG(PWM0_BASE + PWM_O_0_GENB) = PWM0_GenB_Normal;
    }
    break;

    case RecycleServo:
    {
      // to restore the previous DC, set the action back to the normal actions
      HWREG(PWM0_BASE + PWM_O_1_GENA) = PWM1_GenA_Normal;
    }
    break;

    case TrashServo:
    {
      // to restore the previous DC, set the action back to the normal actions
      HWREG(PWM0_BASE + PWM_O_1_GENB) = PWM1_GenB_Normal;
    }
    break;

    case SortServo:
    {
      // to restore the previous DC, set the action back to the normal actions
      HWREG(PWM1_BASE + PWM_O_1_GENA) = PWM1_GenA_Normal;
    }
    break;

    case FlagServo:
    {
      // to restore the previous DC, set the action back to the normal actions
      HWREG(PWM1_BASE + PWM_O_1_GENB) = PWM1_GenB_Normal;
    }
    break;

    case RecycleEmitter:
    {
      // to restore the previous DC, set the action back to the normal actions
      HWREG(PWM1_BASE + PWM_O_0_GENA) = PWM0_GenA_Normal;
    }
    break;
  }
}

/*------------------------------- Footnotes -------------------------------*/
/*------------------------------ End of file ------------------------------*/