/*
 * stm32f10x_sadc.c - Part of Library
 *
 * Copyright (C) 2013 Christos Choutouridis <houtouridis.ch@gmail.com>
 * All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Christos Choutouridis. The intellectual
 * and technical concepts contained herein are proprietary to
 * Christos Choutouridis and are protected by copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Christos Choutouridis.
 *
 * Author:     Christos Choutouridis <houtouridis.ch@gmail.com>
 * Date:       04/2013
 */

#include <stm32f10x_sadc.h>

void _smpr_apply_n(__IO uint32_t* reg, uint32_t v, int n) {
   for (int i=0 ; i<3*n ; i+=3) {
      *reg |= (v << i);
   }
}

/*!
 * \brief  This function Disable the ADC.
 * \param  none
 * \retval none
 */
void  sADC_DeInit  (void)
{
   ADC1->CR2 &= ~ADC_CR2_ADON;
   RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN;
}

/*!
 * \brief  This function Enable, initialize and calibrates the ADC.
 * The ADC configuration is Single Channel, single conversion.
 * \param  none
 * \retval none
 */
void  sADC_Init (uint8_t calib)
{
   RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;

   //Enable ADC
   ADC1->CR2 |= ADC_CR2_ADON;
   //ADC Calibration
   __NOP();
   __NOP();
   ADC1->CR2 |= ADC_CR2_RSTCAL;
   while (ADC1->CR2 & ADC_CR2_RSTCAL) ;   //Wait until reset finished
   if (calib)
   {
      ADC1->CR2 |= ADC_CR2_CAL;
      while (ADC1->CR2 & ADC_CR2_CAL) ;   //Wait until calibration finished
   }

   /*
    *    ADC1 Settings
    *  -----------------------
    *  1 Regular channel in single Conversion mode
    *  External Trigger from SWSTART
    */
   ADC1->SQR1 |= ADC_SQR1_L_0 * 0;
   ADC1->CR2 |= ADC_CR2_EXTTRIG + ADC_CR2_EXTSEL_0 * 7;

   _smpr_apply_n (&ADC1->SMPR1, 0x02, 8);
   _smpr_apply_n (&ADC1->SMPR2, 0x02, 9);
}

/*!
 * \brief ADC Calibration
 */
void sADC_Calibration (void)
{
   ADC1->CR2 |= ADC_CR2_CAL;
   while (ADC1->CR2 & ADC_CR2_CAL)  //Wait until calibration finished
      ;
}

/*!
 * \brief  This Read the analog input on channel \a ch.
 * \param  ch The selected channel
 * \retval The integer value
 */
adc_t  sADC_GetChannel ( ADC_Channel_t ch )
{
   ADC1->SQR3 = ADC_SQR3_SQ1_0 * ch;  //Clear and Select new channel

   //Start and wait the EOC
   ADC1->CR2 |= ADC_CR2_SWSTART;
   while (!(ADC1->SR & ADC_SR_EOC))
      ;
   return (adc_t)ADC1->DR;
}

/*================     Temp and Vref     ======================*/
void sADC_TempVref_Enable (void)
{
   uint32_t    SMPR1_REG = ADC1->SMPR1 & ~ADC_SMPR1_SMP16 & ~ADC_SMPR1_SMP17;

   ADC1->CR2 |= ADC_CR2_TSVREFE;
   SMPR1_REG |= ADC_SMPR1_SMP16 + ADC_SMPR1_SMP17;  //239.5 cycles
   ADC1->SMPR1 = SMPR1_REG;
}


void sADC_TempVref_Disable (void) {
   ADC1->CR2 &= ~ADC_CR2_TSVREFE;
}

adc_t sADC_GetVref (void) {
   return sADC_GetChannel (ADC_VREFINT);
}

adc_t sADC_GetTemp (void)
{
   float Vsens;
   adc_t vrefadc = sADC_GetChannel (ADC_VREFINT);
   adc_t vsensadc = sADC_GetChannel (ADC_TEMP);

   Vsens = vsensadc * ADC_VREF_INT_VOLT / vrefadc;
   return (adc_t)((ADC_TEMP_V25 - Vsens) / ADC_TEMP_AVG_SLOPE + 25);
}
