/**
  ******************************************************************************
  * @file     ADC_user.c
  * @author   SINOMCU-AE
  * @brief    ADC user
  *    
  *          This file provides functions to ADC using:
  *    Activate_ADC();
  *    ADC_ERROR();
  *    DMA_CH1ADC_Init();
  *    DMA_CH1_Callback(); 
  * 
  ******************************************************************************
  * @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_CFG.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)

extern FlagStatus DMA_CH1_TC_Trigger;
extern uint16_t ADC_result_buff[];

/**
  * @brief  ADC1 activation procedure to make it 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 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);
  }
}

/**
  * @brief  DMA initialization
  *         DMA Transfer ADC sequence result.
  *         ADC CH1,CH2 result to ADC_result_buff[0],ADC_result_buff[1]
  * @param  None
  * @retval None
  */
void DMA_CH1ADC_Init(void)
{
  MS32_DMA_InitTypeDef  DMA_InitStruct;

  MS32_AHB1_GRP1_EnableClock(MS32_AHB1_GRP1_PERIPH_DMA1);

  // DMA1 Channel1 Config
  MS32_DMA_StructInit(&DMA_InitStruct);
  DMA_InitStruct.PeriphOrM2MSrcAddress = MS32_ADC_DMA_GetRegAddr(ADC1, MS32_ADC_DMA_REG_REGULAR_DATA);
  DMA_InitStruct.MemoryOrM2MDstAddress = (uint32_t)ADC_result_buff;
  DMA_InitStruct.Direction = MS32_DMA_DIRECTION_PERIPH_TO_MEMORY;
  DMA_InitStruct.NbData = ADC_BUFF_LEN;
  DMA_InitStruct.PeriphOrM2MSrcIncMode = MS32_DMA_PERIPH_NOINCREMENT;
  DMA_InitStruct.MemoryOrM2MDstIncMode = MS32_DMA_MEMORY_INCREMENT;
  DMA_InitStruct.PeriphOrM2MSrcDataSize = MS32_DMA_PDATAALIGN_HALFWORD;
  DMA_InitStruct.MemoryOrM2MDstDataSize = MS32_DMA_MDATAALIGN_HALFWORD;
  DMA_InitStruct.Mode = MS32_DMA_MODE_CIRCULAR;
  DMA_InitStruct.Priority = MS32_DMA_PRIORITY_HIGH;
  MS32_DMA_Init(DMA1,MS32_DMA_CHANNEL_1,&DMA_InitStruct);  

  // DMA1 Channel1 enable 
  MS32_DMA_EnableChannel(DMA1,MS32_DMA_CHANNEL_1);
  // DMA1 interrupt enable 
  MS32_DMA_ITConfig(DMA1,MS32_DMA_CHANNEL_1,MS32_DMA_CCR_TCIE,1);
  MS32_DMA_ClearFlag_TC1(DMA1);
}

/**
  * @brief  DMA1 Channel1 IRQ callback function
  * @param  None
  * @retval None
  */
void DMA_CH1_Callback(void)
{
  DMA_CH1_TC_Trigger = SET;
}

/******************************** END OF FILE *********************************/
