/** 
 * @file  hal_adc.c
 * 
 *  Copyright 2008 Texas Instruments, Inc.
***************************************************************************/

#include "hal_adc.h"

#include <msp430x54x.h>
#include "hal_compat.h"

static int SavedADC12MEM0 = 0, SavedADC12MEM1 = 0, SavedADC12MEM2 = 0;
static int Acc_x = 0, Acc_y = 0, Acc_z = 0;
static int Acc_x_offset = 0, Acc_y_offset = 0, Acc_z_offset = 0;
static long int Vcc = 0, Temperature = 0;
static long int temperatureOffset = CELSIUS_OFFSET;
static unsigned char conversionType = CELSIUS, adcMode = ADC_OFF_MODE;
static unsigned char exit_active_from_ADC12 = 0;

/************************************************************************
 * @brief  Turns on and initializes ADC12, accelerometer in order to 
 *         sample x, y, z-axis inputs.
 * 
 * @param  none
 * 
 * @return none
 *************************************************************************/
void halAccelerometerInit(void)
{
  adcMode = ADC_ACC_MODE;
  ACC_PORT_SEL |= ACC_X_PIN + ACC_Y_PIN;    //Enable A/D channel inputs
  ACC_PORT_DIR &= ~(ACC_X_PIN + ACC_Y_PIN + ACC_Z_PIN);
  ACC_PORT_DIR |= ACC_PWR_PIN;              //Enable ACC_POWER
  ACC_PORT_OUT |= ACC_PWR_PIN;

  //Sequence of channels, once, ACLK  
  ADC12CTL0 = ADC12ON + ADC12SHT02 + ADC12MSC;  
  ADC12CTL1 = ADC12SHP + ADC12CONSEQ_1 + ADC12SSEL_0;  
  ADC12CTL2 = ADC12RES_2;    
  ADC12MCTL0 = ACC_X_CHANNEL;
  ADC12MCTL1 = ACC_Y_CHANNEL;
  ADC12MCTL2 = ACC_Z_CHANNEL + ADC12EOS;
  
  // Allow the accelerometer to settle before sampling any data

  // 4.5.3-20110706-2 doesn't allow for 32-bit delay cycles
  int i;
  for (i=0;i<10;i++){
      __delay_cycles(20000);                     
  }
  UCSCTL8 |= MODOSCREQEN;                   
}

/************************************************************************
 * @brief  Calibrates the offset values for x, y, and z axes.
 * 
 * @param  none
 * 
 * @return none
 *************************************************************************/
void halAccelerometerCalibrate(void)
{
  unsigned char tempQuit;
  
  tempQuit = exit_active_from_ADC12;
  halAdcSetQuitFromISR( 1 );
  halAdcStartRead();
  
  __bis_SR_register(LPM3_bits + GIE);   
  __no_operation(); 
  
  halAccelerometerReadWithOffset(&Acc_x_offset, &Acc_y_offset, &Acc_z_offset);
  halAdcSetQuitFromISR( tempQuit );    
}

/************************************************************************
 * @brief  Set function for the calibrated offsets for the x, y, and z axes.
 * 
 * @param  x Calibrated offset for the x-axis
 * 
 * @param  y Calibrated offset for the y-axis
 * 
 * @param  z Calibrated offset for the z-axis
 * 
 * @return none
 *************************************************************************/
void halAccelerometerSetCalibratedOffset( int x, int y, int z )
{
  Acc_x_offset = x;
  Acc_y_offset = y;
  Acc_z_offset = z;
}

/************************************************************************
 * @brief  Get function for the x, y, and z axes calibrated offsets
 * 
 * @param  x Pointer to the calibrated offset for the x-axis
 * 
 * @param  y Pointer to the calibrated offset for the y-axis
 * 
 * @param  z Pointer to the calibrated offset for the z-axis
 * 
 * @return none 
 *************************************************************************/
void halAccelerometerGetCalibratedOffset(int *x, int *y, int *z)
{
  *x = Acc_x_offset;
  *y = Acc_y_offset;
  *z = Acc_y_offset;
}

/************************************************************************
 * @brief  Get function for the x, y, and z accelerometer samples, 
 *         including the calibrated offsets.
 * 
 * @param  x Pointer to the accelerometer reading (x-axis)
 * 
 * @param  y Pointer to the accelerometer reading (y-axis)
 * 
 * @param  z Pointer to the accelerometer reading (z-axis)
 * 
 * @return none
 *************************************************************************/
void halAccelerometerRead(int *x, int *y, int *z)
{
  Acc_x = SavedADC12MEM0;
  Acc_y = SavedADC12MEM1;
  Acc_z = SavedADC12MEM2;
  
  *x = Acc_x - Acc_x_offset;
  *y = Acc_y - Acc_y_offset;
  *z = Acc_z - Acc_z_offset;  
}

/************************************************************************
 * @brief  Get function for the x, y, and z accelerometer samples, 
 *         excluding the calibrated offsets.
 * 
 * @param  x Pointer to the accelerometer reading (x-axis)
 * 
 * @param  y Pointer to the accelerometer reading (y-axis)
 * 
 * @param  z Pointer to the accelerometer reading (z-axis)
 * 
 * @return none
 *************************************************************************/
void halAccelerometerReadWithOffset(int *x, int *y, int *z)
{
  *x = SavedADC12MEM0;
  *y = SavedADC12MEM1;    
  *z = SavedADC12MEM2;
}

/************************************************************************
 * @brief  Disables the ADC12, accelerometer that sampled x, y, z-axis inputs.
 * 
 * @param  none
 * 
 * @return none
 *************************************************************************/
void halAccelerometerShutDown(void)
{
  //Turn off ADC Module
  ADC12CTL0 &= ~( ADC12ON + ADC12ENC );  
  ACC_PORT_OUT &= ~ACC_PWR_PIN;             //Disable ACC_POWER

  //Disable A/D channel inputs
  ACC_PORT_SEL &= ~(ACC_X_PIN + ACC_Y_PIN + ACC_Z_PIN); 
  ACC_PORT_DIR |= (ACC_X_PIN + ACC_Y_PIN + ACC_Z_PIN + ACC_PWR_PIN);
  ACC_PORT_OUT &= ~(ACC_X_PIN + ACC_Y_PIN + ACC_Z_PIN + ACC_PWR_PIN);
  
  adcMode = ADC_OFF_MODE;
}

/*----------------------------------------------------------------------------*/
/************************************************************************
 * @brief  Intializes the ADC12 to sample Temperature and Vcc. 
 * 
 * @param  none
 * 
 * @return none
 *************************************************************************/
void halAdcInitTempVcc(void)
{
  //Sequence of channels, once,   
  adcMode = ADC_TEMP_MODE;
  UCSCTL8 |= MODOSCREQEN;
  ADC12CTL0 = ADC12ON + ADC12SHT0_15 + ADC12MSC + + ADC12REFON + ADC12REF2_5V;  
  ADC12CTL1 = ADC12SHP + ADC12CONSEQ_1 + ADC12SSEL_0;  
  ADC12CTL2 = ADC12RES_2;    
  
  ADC12MCTL0 = ADC12SREF_1 + TEMP_CHANNEL;
  ADC12MCTL1 = ADC12SREF_1 + VCC_CHANNEL + ADC12EOS;                            
}

/************************************************************************
 * @brief  Turns off / disable the ADC12. 
 * 
 * @param  none
 * 
 * @return none
 *************************************************************************/
void halAdcShutDownTempVcc(void)
{
  ADC12CTL0 &= ~ ( ADC12ON + ADC12ENC + ADC12REFON );
  adcMode = ADC_OFF_MODE;      
}

/************************************************************************
 * @brief  Sets the conversion type to either Farenheit (F) or Celsius (C).
 * 
 * @param  conversion The #define constant CELSIUS or FAHRENHEIT. 
 * 
 * @return none
 *************************************************************************/
void halAdcSetTempConversionType(unsigned char conversion)
{
  conversionType = conversion;
}

/************************************************************************
 * @brief  Set function for the calibrated temperature offset.
 * 
 * @param  offset The temperature offset.
 * 
 * @return none
 *************************************************************************/
void halAdcSetTempOffset(long offset)
{
  temperatureOffset = offset;
}

/************************************************************************
 * @brief  Get function for the current temperature value. 
 * 
 * @param  none
 * 
 * @return The current temperature value. 
 *************************************************************************/
int halAdcGetTemp(void)
{
  return Temperature; 
}

/************************************************************************
 * @brief  Get function for the current Vcc value. 
 * 
 * @param  none
 * 
 * @return The current Vcc value.  
 *************************************************************************/
int halAdcGetVcc(void)
{
  return Vcc;
}

/************************************************************************
 * @brief  Converts the Vcc and Temp readings from the ADC to BCD format. 
 * 
 * @param  none
 * 
 * @return none
 *************************************************************************/
void halAdcConvertTempVccFromADC(void)
{ 	
  long multiplier, offset;

  // Convert Vcc
  Vcc = SavedADC12MEM1;
  Vcc = Vcc * 50;
  Vcc = Vcc / 4096;
    
  // Convert Temperature  
  if (conversionType == CELSIUS)
  {
    multiplier = CELSIUS_MUL;
    offset = temperatureOffset;
  }  
  else
  {
    multiplier = (long) CELSIUS_MUL * 9 /5 ;
    offset = (long) temperatureOffset * 9 / 5 - 320;    
  }   
  Temperature = (long) SavedADC12MEM0 * multiplier/4096 - offset;   	
}

/************************************************************************
 * @brief  Get function for the temperature and Vcc samples in "xxx^C/F" and 
 *         "x.xV" format.
 * 
 * @param  TemperatureStr The string that holds the temperature reading
 * 
 * @param  Vcc            The string that holds the Vcc reading
 * 
 * @return none
 *************************************************************************/
void halAdcReadTempVcc(char *TemperatureStr, char *VccStr)
{    
  unsigned char i, leadingZero = 0;
  long int dummyTemperature, dummyVcc;
	
  halAdcConvertTempVccFromADC(); 
  dummyTemperature = Temperature;
  dummyVcc = Vcc;	
  for (i = 0; i < 6; i++)   	
	  TemperatureStr[i] = '\0';	 
	i=0;
  //Check for negative  	   	
  if (Temperature < 0)
  {
  	TemperatureStr[i++]='-';
  	Temperature = -Temperature;  	
  }
  TemperatureStr[i] ='0';
  if (Temperature >= 1000)
  {
  	TemperatureStr[i]='1';
  	Temperature -=1000;
  	leadingZero = 1;
  }
  if (leadingZero == 1)
    i++;
  //100s digit
  TemperatureStr[i] = '0';
  if  (Temperature >= 100)  
  {
    do
	{
	  TemperatureStr[i]++;
	  Temperature -=100;
	}
	while (Temperature >=100);	  
	leadingZero =  1;
  }
  if (leadingZero == 1)
    i++;
  //10s digit
  TemperatureStr[i] = '0';
  if (Temperature >=10)
  {
	  do
	  {
	    TemperatureStr[i]++;
	    Temperature -=10;
	  }
	  while (Temperature >=10);	  
  }
  
  TemperatureStr[++i] = '^';
  if (conversionType == CELSIUS)
  	TemperatureStr[++i]='C';
  else
  	TemperatureStr[++i]='F';
  
  VccStr[0] = '0';
  VccStr[2] = '0';
  while (Vcc >= 10)
  {
  	VccStr[0]++;
  	Vcc -= 10;  	
  }
  VccStr[2] += Vcc;  
  Temperature = dummyTemperature;
  Vcc = dummyVcc;
}

/*----------------------------------------------------------------------------*/
/************************************************************************
 * @brief  Starts the ADC conversion.
 * 
 * @param  none
 * 
 * @return none
 *************************************************************************/
void halAdcStartRead(void)
{
  ADC12IFG &= ~(BIT1+BIT0);                 // Clear any pending flags 
  
  if (adcMode == ADC_ACC_MODE)
  {
  	ADC12CTL0 |=  ADC12ENC | ADC12SC ;   
  	ADC12IE |= BIT2;
  }
  else
  {
    ADC12CTL0 |= ADC12REFON;                // Turn on ADC12 reference
    
    // Delay to stabilize ADC12 reference assuming the fastest MCLK of 18 MHz. 
    // 35 us = 1 / 18 MHz * 630 
    __delay_cycles(630); 
  	
	ADC12IE |= BIT1;                        // Enable interrupt
  	ADC12CTL0 |=  ADC12ENC | ADC12SC;  
  }
}

/************************************************************************
 * @brief  Sets the flag that causes an exit into active CPU mode from 
 *         the ADC12 ISR.
 * 
 * @param  quit 
 * 
 * - 1 - Exit active from ADC12 ISR
 * - 0 - Remain in LPMx on exit from ADC12ISR
 * 
 * @return none
 *************************************************************************/
void halAdcSetQuitFromISR(unsigned char quit)
{
  exit_active_from_ADC12 = quit;  
}

/*----------------------------------------------------------------------------*/

#ifdef __GNUC__
__attribute__((interrupt(ADC12_VECTOR)))
#endif
#ifdef __IAR_SYSTEMS_ICC__
#pragma vector=ADC12_VECTOR
__interrupt
#endif
void ADC12_ISR(void)
{
    SavedADC12MEM0 = ADC12MEM0;             // Store the sampled data 
	SavedADC12MEM1 = ADC12MEM1;
	SavedADC12MEM2 = ADC12MEM2;
    ADC12IFG = 0;                           // Clear the interrupt flags
	ADC12CTL0 &= ~( ADC12ENC | ADC12SC | ADC12REFON);    
    if (exit_active_from_ADC12) __bic_SR_register_on_exit(LPM3_bits);    
}
