/* *****************************************************************************
 * File:      adc.c
 * Date:      4 Feb 2025
 * Author:    Andrew Levido
 *
 * Copyright 2025 Imbrius Pty Ltd - All Rights Reserved
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "adc.h"

/* Defines -------------------------------------------------------------------*/

/* Typedefs & Enumerations ---------------------------------------------------*/
typedef enum { ADC_READY, ADC_CONV_INHI, ADC_CONV_INLO, ADC_CONV_MID, 
               ADC_CONV_REF } adc_state_t;
/* Global Variables ----------------------------------------------------------*/
static struct {
  void (*callback)(int32_t result_hi, int32_t result_lo);
  volatile adc_state_t state;
  volatile int32_t in_hi;
  volatile int32_t in_lo;
  volatile int32_t in_mid;
  volatile int32_t in_ref;
} adc;

/* Private Functions ---------------------------------------------------------*/

/* Public Functions ----------------------------------------------------------*/
void adc_start(void(*adc_callback)(int32_t result_hi, int32_t result_lo))
{
  /* Register callback */
  adc.callback = adc_callback;

  /* Calibrate ADC */
  LL_ADC_StartCalibration(ADC1);
  while(LL_ADC_IsCalibrationOnGoing(ADC1));

  /* Set state */
  adc.state = ADC_CONV_INHI;

  /* Enable ADC */
  LL_ADC_Enable(ADC1);
  while ( LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0 );
  //if( LL_ADC_IsActiveFlag_EOC(ADC1) ) { LL_ADC_ClearFlag_EOC(ADC1); }

  /* Enable End of Conversion Interrupt and clear endo of sequence flag */
  LL_ADC_ClearFlag_EOC(ADC1);
  LL_ADC_ClearFlag_EOS(ADC1);
  LL_ADC_EnableIT_EOC(ADC1);

  /* Start ADC trigger timer */
  LL_TIM_EnableCounter(TIM21);
  LL_TIM_CC_EnableChannel(TIM21, LL_TIM_CHANNEL_CH2);
  
  /* Trigger fisrt conversion sequence */
  LL_ADC_REG_StartConversion(ADC1);
}

void adc_stop(void)
{
  /* Disable Interrupts */
  LL_ADC_DisableIT_EOC(ADC1);

  /* Disable counter */
  LL_TIM_DisableCounter(TIM21);
  LL_TIM_CC_DisableChannel(TIM21, LL_TIM_CHANNEL_CH2);

  /* Stop ADC */
  LL_ADC_REG_StopConversion(ADC1);
  LL_ADC_Disable(ADC1);
}

/* Interrupt Service Routines ------------------------------------------------*/
void adc_ISR(void) {

  int32_t vdda;

  /* Synchronise to End of sequence in case of sync loss */
  if(LL_ADC_IsActiveFlag_EOS(ADC1)) {
    adc.state = ADC_CONV_REF;
  }

  switch(adc.state) {
    case ADC_CONV_INHI:
      adc.in_hi = LL_ADC_REG_ReadConversionData12(ADC1);
      LL_ADC_ClearFlag_EOC(ADC1);
      adc.state = ADC_CONV_INLO;
      break;
    case ADC_CONV_INLO:
      adc.in_lo = LL_ADC_REG_ReadConversionData12(ADC1);
      LL_ADC_ClearFlag_EOC(ADC1);
      adc.state = ADC_CONV_MID;
      break;
    case ADC_CONV_MID:
      adc.in_mid = LL_ADC_REG_ReadConversionData12(ADC1);
      LL_ADC_ClearFlag_EOC(ADC1);
      adc.state = ADC_CONV_REF;
      break;
     case ADC_CONV_REF:
      adc.in_ref = LL_ADC_REG_ReadConversionData12(ADC1);
      LL_ADC_ClearFlag_EOC(ADC1);
      LL_ADC_ClearFlag_EOS(ADC1);
      
      /* Calculate VDDA (ADC VREF) from internal bandgap reference */
      vdda = __LL_ADC_CALC_VREFANALOG_VOLTAGE(adc.in_ref, LL_ADC_RESOLUTION_12B);

      /* Calculate input in absolute millivolts using VDDA */
      adc.in_hi = __LL_ADC_CALC_DATA_TO_VOLTAGE(vdda, adc.in_hi, LL_ADC_RESOLUTION_12B);
      adc.in_lo = __LL_ADC_CALC_DATA_TO_VOLTAGE(vdda, adc.in_lo, LL_ADC_RESOLUTION_12B);
      adc.in_mid = __LL_ADC_CALC_DATA_TO_VOLTAGE(vdda, adc.in_mid, LL_ADC_RESOLUTION_12B);
    
      if(adc.callback != 0) { adc.callback(adc.in_hi - adc.in_mid, adc.in_lo - adc.in_mid); }
      adc.state = ADC_CONV_INHI;
      break;
    default:
      break;
  }
}
   


/* End adc */