/**
  ******************************************************************************
  * @file     ADC_user.c
  * @author   SINOMCU-AE
  * @brief    ADC user
  *    
  *          This file provides functions to ADC using:
  *    Activate_ADC();
  *    Gat_ADC_Conversion_Result();
  *    ADC_ERROR();     
  ******************************************************************************
  * @attention 
  *                           DISCLAIMER
  * The present firmware which is for guidance only aims at providing customers
  * with coding information regarding their products in order for them to save
  * time. As a result, SinoMCU shall not be held liable for any direct,indirect
  * or consequential damages with respect to any claims arising from the content 
  * of such firmware and/or the use made by customers of the coding information 
  * contained herein in connection with their products.
  *
  * <h2><center>&copy; Copyright (c) 2021 sinomcu All rights reserved.</center></h2>
  *
  ******************************************************************************
  */

#include "stdio.h"   
#include "ADC_user.h"
#include "GPIO_CFG.h"

// this example time,you need calculate actual time + margin,unit: ms 
#define ADC_CALIBRATION_TIMEOUT_MS         ((uint32_t) 2)
#define ADC_ENABLE_TIMEOUT_MS              ((uint32_t) 2)
#define ADC_CONVERSION_TIMEOUT_MS          ((uint32_t) 500)
#define ADC_UNITARY_CONVERSION_TIMEOUT_MS  ((uint32_t) 2)
#define ADC_DELAY_CALIB_ENABLE_CPU_CYCLES  (MS32_ADC_DELAY_CALIB_ENABLE_ADC_CYCLES * 16)

/**
  * @brief  ADC1 Calibration, enable, ready to convert
  * @param  None
  * @retval ADCErrorStatus
  */
ADCErrorStatus Activate_ADC(void) {
  __IO uint32_t wait_loop_index = 0;
  __IO uint32_t backup_setting_adc_dma_transfer = 0U;
  ADCErrorStatus Activate_ADC_status =  ADC_NO_ERROR;

  #if (USE_TIMEOUT == 1)
  uint32_t Timeout = 0; 
  #endif
 
  if (MS32_ADC_IsEnabled(ADC1) == 0) {
    // Disable ADC DMA ,during calibration
    backup_setting_adc_dma_transfer = MS32_ADC_REG_GetDMATransfer(ADC1);
    MS32_ADC_REG_SetDMATransfer(ADC1, MS32_ADC_REG_DMA_TRANSFER_NONE);
    
    MS32_ADC_StartCalibration(ADC1); 
    
    // Poll for ADC effectively calibrated
#if (USE_TIMEOUT == 1)
    Timeout = ADC_CALIBRATION_TIMEOUT_MS;
#endif 
    
    while (MS32_ADC_IsCalibrationOnGoing(ADC1) != 0) {
#if (USE_TIMEOUT == 1)
      if (MS32_SYSTICK_IsActiveCounterFlag()) {
        if(Timeout-- == 0) {
          Activate_ADC_status = ADC_CALI_ERROR;       // ADC Calibration Time out
          break;
        }
      }
#endif
    }
    
    // Restore ADC DMA transfer 
    MS32_ADC_REG_SetDMATransfer(ADC1, backup_setting_adc_dma_transfer); 
    
    // Delay between calibration and ADC enable
    wait_loop_index = (ADC_DELAY_CALIB_ENABLE_CPU_CYCLES >> 1);
    while(wait_loop_index != 0) {
      wait_loop_index--;
    }
    
    MS32_ADC_Enable(ADC1); 
    
    // Poll ADC ready to convert
#if (USE_TIMEOUT == 1)
    Timeout = ADC_ENABLE_TIMEOUT_MS;
#endif
    
    while (MS32_ADC_IsActiveFlag_ADRDY(ADC1) == 0) {
#if (USE_TIMEOUT == 1)
      if (MS32_SYSTICK_IsActiveCounterFlag()) {
        if(Timeout-- == 0) {
          Activate_ADC_status = ADC_ADRDY_ERROR;
          break;
        }
      }
#endif
    }
  }
  return Activate_ADC_status;
}

/**
  * @brief  ADC start Conversion,get signal channel result
  * @param  Channel This parameter can be a combination of the following values:
  *    @arg @ref MS32_ADC_CHANNEL_0
  *    @arg @ref MS32_ADC_CHANNEL_1
  *    @arg @ref MS32_ADC_CHANNEL_2
  *    @arg @ref MS32_ADC_CHANNEL_3
  *    @arg @ref MS32_ADC_CHANNEL_4
  *    @arg @ref MS32_ADC_CHANNEL_5
  *    @arg @ref MS32_ADC_CHANNEL_6
  *    @arg @ref MS32_ADC_CHANNEL_7
  *    @arg @ref MS32_ADC_CHANNEL_8
  *    @arg @ref MS32_ADC_CHANNEL_9
  *    @arg @ref MS32_ADC_CHANNEL_10
  *    @arg @ref MS32_ADC_CHANNEL_11
  *    @arg @ref MS32_ADC_CHANNEL_12
  *    @arg @ref MS32_ADC_CHANNEL_13
  *    @arg @ref MS32_ADC_CHANNEL_14
  *    @arg @ref MS32_ADC_CHANNEL_15
  *    @arg @ref MS32_ADC_CHANNEL_16
  *    @arg @ref MS32_ADC_CHANNEL_17
  *    @arg @ref MS32_ADC_CHANNEL_18
  *    @arg @ref MS32_ADC_CHANNEL_19
  *    @arg @ref MS32_ADC_CHANNEL_20
  *    @arg @ref MS32_ADC_CHANNEL_21
  *    @arg @ref MS32_ADC_CHANNEL_VREFINT
  *    @arg @ref MS32_ADC_CHANNEL_TEMPSENSOR
  *    @arg @ref MS32_ADC_CHANNEL_VBAT
  *    @arg @ref MS32_ADC_CHANNEL_OPAMP1
  *    @arg @ref MS32_ADC_CHANNEL_OPAMP2
  *    @arg @ref MS32_ADC_CHANNEL_OPAMP3
  * @param ADC_result between Min_Data=0x000 and Max_Data=0xFFF
  * @retval ADCErrorStatus:
  *    @arg @ref ADC_NO_ERROR
  *    @arg @ref ADC_CALI_ERROR
  *    @arg @ref ADC_ADRDY_ERROR
  *    @arg @ref ADC_CONVERSION_EOC_ERROR
  *    @arg @ref ADC_CONVERSION_EOS_ERROR
  *    @arg @ref ADC_OVERRUN_ERROR
  */
ADCErrorStatus Get_Single_Channel_ADC_Conversion_Result(uint32_t channel , uint16_t * ADC_result) {
  ADCErrorStatus ADC_status =  ADC_NO_ERROR;
#if (USE_TIMEOUT == 1)
  uint32_t Timeout = 0; 
#endif
  MS32_ADC_REG_SetSequencerChRem(ADC1, ADC_CHSELR_CHSEL);  // none channel in sequencer
  MS32_ADC_REG_SetSequencerChAdd(ADC1, channel);           // only one channel which need to convert  
  MS32_ADC_REG_StartConversion(ADC1);

#if (USE_TIMEOUT == 1)
  Timeout = ADC_UNITARY_CONVERSION_TIMEOUT_MS;
#endif
  while (MS32_ADC_IsActiveFlag_EOC(ADC1) == 0) {           // ADC_REG_InitStruct.DMATransfer = MS32_ADC_REG_DMA_TRANSFER_LIMITED
#if (USE_TIMEOUT == 1)
    if (MS32_SYSTICK_IsActiveCounterFlag()) {
      if(Timeout-- == 0) {
        ADC_status =ADC_CONVERSION_EOS_ERROR;              // ADC Conversion End Time out
        break;
      }
    }
#endif
  }

  *ADC_result = MS32_ADC_REG_ReadConversionData12(ADC1);   // get result
  // MS32_ADC_ClearFlag_EOC(ADC1);                         // EOC Clear by read ADC_CR
  MS32_ADC_ClearFlag_EOS(ADC1);
  if(  MS32_ADC_IsActiveFlag_OVR(ADC1) ) {                 // if OVE set clear it
    MS32_ADC_ClearFlag_OVR(ADC1);
  }
  return ADC_status;
}

/**
  * @brief  ADC start Conversion,get result
  * @param  ADC_Result_buff,uint16_t *
  * @retval ADCErrorStatus:
  *    @arg @ref ADC_NO_ERROR
  *    @arg @ref ADC_CALI_ERROR
  *    @arg @ref ADC_ADRDY_ERROR
  *    @arg @ref ADC_CONVERSION_EOC_ERROR
  *    @arg @ref ADC_CONVERSION_EOS_ERROR
  *    @arg @ref ADC_OVERRUN_ERROR
  */
ADCErrorStatus Get_ADC_Conversion_Result(uint16_t *ADC_Result_buff) {
  uint16_t ADC_result = 0;
  ADCErrorStatus ADC_status =  ADC_NO_ERROR;
  // get result by one channel
  ADC_status = Get_Single_Channel_ADC_Conversion_Result(MS32_ADC_CHANNEL_1, &ADC_result);
  if( ADC_status ==  ADC_NO_ERROR) {
    ADC_Result_buff[0] = ADC_result; 
  } else {
  // user error function
  }
  
  ADC_status = Get_Single_Channel_ADC_Conversion_Result(MS32_ADC_CHANNEL_4, &ADC_result);
  if( ADC_status ==  ADC_NO_ERROR) {
    ADC_Result_buff[1] = ADC_result; 
  } else {
  // user error function
  }
  return ADC_status;
}

/**
  * @brief  Adc error handle.
  * @param  ADC_status
  * @retval None
  */
void ADC_ERROR(ADCErrorStatus error_status) {
  switch(error_status) {
    case ADC_CALI_ERROR:
      printf("\r\n##Error:ADC_CALI_ERROR\r\n");
      break;
    case ADC_ADRDY_ERROR:
      printf("\r\n##Error:ADC_ADRDY_ERROR\r\n");
      break;
    case ADC_CONVERSION_EOC_ERROR:
      printf("\r\n##Error:ADC_CONVERSION_EOC_ERROR\r\n");
      break;
    case ADC_CONVERSION_EOS_ERROR:
      printf("\r\n##Error:ADC_CONVERSION_EOS_ERROR\r\n");
      break;
    case ADC_OVERRUN_ERROR:
      printf("\r\n##Error:ADC_OVERRUN_ERROR\r\n");
      break;
    default:
      break;    
  }
      
  for(;;) {
    LED1_TOGGLE();
    SysTick_Ms(ADC_ERROR_LED_BLINK_HALF_PER);
  }
}
/******************************** END OF FILE *********************************/
