/***********************************************************************************************************************
* Copyright (C) All rights reserved.
***********************************************************************************************************************/

/***********************************************************************************************************************
* @file    adc.c
* @brief   This file implements device driver for ADC module.
* @version 1.0.0  
* @date    2019/12/24
***********************************************************************************************************************/

/***********************************************************************************************************************
Macro definitions 
***********************************************************************************************************************/
#define ADC_OPERMODE_SELECT     /* ADC Operation mode: select mode */
#define ADC_TRIGGER_SOFTWARE    /* ADC Trigger: software trigger */
#define ADC_CONVMODE_ONESHOT    /* ADC Conversion mode: one-shot */
#define ADC_VREF_VDD_VSS        /* ADC reference voltage: VDD/VSS */

/***********************************************************************************************************************
Includes
***********************************************************************************************************************/
#include "BAT32A239.h"
#include "userdefine.h"
#include "adc.h"
#include "Analog_Signals.h"
/* Start user code for include. Do not edit comment generated here */
/* End user code. Do not edit comment generated here */

/***********************************************************************************************************************
Pragma directive
***********************************************************************************************************************/
/* Start user code for pragma. Do not edit comment generated here */
/* End user code. Do not edit comment generated here */

/***********************************************************************************************************************
Global variables and functions
***********************************************************************************************************************/
/* Start user code for global. Do not edit comment generated here */
/**************************************************************************/ /**
  * \brief      Get ADC conversion status
  * \retval     0 - ADC is idle
  *             1 - ADC is busy
******************************************************************************/
uint8_t u8ADCSequenceRunningFlag = 0U; /*序列转化完成标志*/
uint8_t u8ADCChannelCount = 0U;		/*通道计数*/
uint16_t u16ADCChannelResult[ADC_SIGNAL_CH_NUMBER];	/*存放结果数组*/


uint8_t ADC_Get_Conversion_Status(void)
{
	return u8ADCSequenceRunningFlag;
}

/*实时运行*/
void Analog_Signal_Conv_Real_Time_Run(void)
{
    if(R_INT->IF[ADC_IRQn].IFL)
    {
		if ((ADC->ADM0 & ADCS) == 0)    //待机状态
        {
		    R_INT->IF[ADC_IRQn].IFL = 0;
			u16ADCChannelResult[u8ADCChannelCount] = ADC->ADCR; /*获取上次开启的AD通道转化值*/

			u8ADCChannelCount++;
			if (u8ADCChannelCount >= ADC_SIGNAL_CH_NUMBER)
			{
				u8ADCChannelCount = 0U;
				u8ADCSequenceRunningFlag = 0U; //整个序列转化完成
			}
			else
			{
			    ADC_Start((adc_channel_t)u8ADCChList[u8ADCChannelCount]);/*开始下一个通道的转换*/
			}
		}

    }
}
void ADC_Start_Conversion(void)
{
    u8ADCChannelCount = 0;
    u8ADCSequenceRunningFlag = 1;
    ADC_Start((adc_channel_t)u8ADCChList[u8ADCChannelCount]);/*从第0个通道开始转换*/
}

void ADC_Stop_Conversion(void)
{
    ADC_Stop();
}


void ADC_Get_Conversion_Result(uint16_t *pu16Data, uint8_t u8ChNum)
{
	uint8_t i;

	if (u8ADCSequenceRunningFlag == 0U) /* 空闲 */
	{
		for (i = 0U; i < u8ChNum; i++)
		{
			pu16Data[i] = u16ADCChannelResult[i];
		}
	}
}
/* End user code. Do not edit comment generated here */

/***********************************************************************************************************************
* Function Name: ADC_Init
* @brief  This function initializes the AD converter.
* @param  None
* @return None
***********************************************************************************************************************/
void ADC_Init(void)
{
    INTC_DisableIRQ(ADC_IRQn);          /* disable INTAD interrupt */
    CGC->PER0 |= CGC_PER0_ADCEN_Msk;    /* enables input clock supply */
    ADC->ADM0  = 0x00U;                 /* disable AD conversion and clear ADM0 register */
    INTC_ClearPendingIRQ(ADC_IRQn);     /* clear INTAD interrupt flag */
    NVIC_ClearPendingIRQ(ADC_IRQn);     /* clear INTAD interrupt flag */

    ADC_PORT_SETTING();

    /* AD operation mode: select or scan mode  */
    //ADC->ADM0 = _10_AD_CONVERSION_CLOCK_8 | _00_AD_COMPARATOR_DISABLE;
    ADC->ADM0 = _28_AD_CONVERSION_CLOCK_1 | _00_AD_COMPARATOR_DISABLE;

    /* AD conversion mode setting */
    ADC->ADM1 = _00_AD_HISPEED;
#ifdef ADC_OPERMODE_SELECT     
    ADC->ADM1 |= _00_AD_OPERMODE_SELECT;
#endif
#ifdef ADC_OPERMODE_SCAN     
    ADC->ADM1 |= _80_AD_OPERMODE_SCAN;
#endif

#ifdef ADC_CONVMODE_SEQUENTIAL
    ADC->ADM1 |= _00_AD_CONVMODE_SEQUENTIAL;
#endif
#ifdef ADC_CONVMODE_ONESHOT
    ADC->ADM1 |= _08_AD_CONVMODE_ONESHOT;
#endif

    /* AD reference voltage setting */
#ifdef ADC_VREF_VDD_VSS
    ADC->ADM2 = _00_AD_POSITIVE_VDD | _00_AD_NEGATIVE_VSS | _00_AD_AREA_MODE_1 ;
#endif
#ifdef ADC_VREF_AVREFP_AVREFM
    ADC->ADM2 = _40_AD_POSITIVE_AVREFP | _20_AD_NEGATIVE_AVREFM | _00_AD_AREA_MODE_1 ;
#endif

    /* AD trigger selection */
#ifdef ADC_TRIGGER_SOFTWARE
    ADC->ADTRG = _00_AD_TRIGGER_SOFTWARE;
#endif
#ifdef ADC_TRIGGER_HARDWARE_NOWAIT
    ADC->ADTRG = _80_AD_TRIGGER_HARDWARE_NOWAIT;
#endif
#ifdef ADC_TRIGGER_HARDWARE_WAIT
    ADC->ADTRG = _C0_AD_TRIGGER_HARDWARE_WAIT;
#endif

    /* AD comversion result comprision upper limit setting */
    ADC->ADUL = _FF_AD_ADUL_VALUE;
    /* AD comversion result comprision lower limit setting */
    ADC->ADLL = _00_AD_ADLL_VALUE;

    /* adhard power up */
    ADC->ADM0 |= ADCE;  
}
/***********************************************************************************************************************
* Function Name: ADC_Set_OperationOn
* @brief  This function enables comparator operation.
* @param  None
* @return None
***********************************************************************************************************************/
void ADC_Set_OperationOn(void)
{
    ADC->ADM0 |= ADCE;      /* enables A/D voltage comparator operation */
}
/***********************************************************************************************************************
* Function Name: ADC_Set_OperationOff
* @brief  This function stops comparator operation.
* @param  None
* @return None
***********************************************************************************************************************/
void ADC_Set_OperationOff(void)
{
    ADC->ADM0 &= ~ADCE;      /* stops A/D voltage comparator operation */
}
/***********************************************************************************************************************
* Function Name: ADC_Start
* @brief  This function starts the AD converter.
* @param  ch - specify the conversion channel
* @return None
***********************************************************************************************************************/
void ADC_Start(adc_channel_t ch)
{
    ADC->ADS = ch;
    INTC_ClearPendingIRQ(ADC_IRQn); /* clear INTAD interrupt flag */
    NVIC_ClearPendingIRQ(ADC_IRQn); /* clear INTAD interrupt flag */
   /* INTC_EnableIRQ(ADC_IRQn);*/       /* enable INTAD interrupt */

	if((ADC->ADTRG >> 6) != 3) 			/* write ADCS except for hardwait mode */
	{
		ADC->ADM0 |= ADCS;            /* enables conversion operation */
	}
}
/***********************************************************************************************************************
* Function Name: ADC_Stop
* @brief  This function stops the AD converter.
* @param  None
* @return None
***********************************************************************************************************************/
void ADC_Stop(void)
{
    ADC->ADM0 &= ~(ADCS | ADCE);             /* stops conversion operation */
    INTC_DisableIRQ(ADC_IRQn);      /* disable INTAD interrupt */
    INTC_ClearPendingIRQ(ADC_IRQn); /* clear INTAD interrupt flag */
    NVIC_ClearPendingIRQ(ADC_IRQn); /* clear INTAD interrupt flag */
}
/***********************************************************************************************************************
* Function Name: ADC_Set_RefVoltage
* @brief  This function sets the Reference Voltage of AD converter.
* @param  vref - 0: VDD/VSS; 1: AVREFP/AVREFM; 2: Internal 1.45V/VSS
* @return None
***********************************************************************************************************************/
void ADC_Set_RefVoltage(adc_vref_t vref)
{
    ADC->ADM2 &= ~(7<<5);
    if(vref == 0)
    {
        ADC->ADM2 |= _00_AD_POSITIVE_VDD | _00_AD_NEGATIVE_VSS;
    }
    if(vref == 1)
    {
        ADC->ADM2 |= _40_AD_POSITIVE_AVREFP | _20_AD_NEGATIVE_AVREFM;
    }
    if(vref == 2)
    {
        ADC->ADM2 |= _80_AD_POSITIVE_INVREFP | _00_AD_NEGATIVE_VSS;
    }
}
/***********************************************************************************************************************
* Function Name: ADC_Set_HardTrigger
* @brief  This function sets the trigger of AD converter.
* @param  wait - 0: NoWait; 1: Wait
* @param  trg - select the hard trigger signal
* @return None
***********************************************************************************************************************/
void ADC_Set_HardTrigger(uint8_t wait, adc_trigger_t trg)
{
    ADC->ADM0 &= ~ADCE;
    if(wait == 0)
    {
        ADC->ADTRG = _80_AD_TRIGGER_HARDWARE_NOWAIT | trg;
    }
    else
    {
        ADC->ADTRG = _C0_AD_TRIGGER_HARDWARE_WAIT   | trg;
    }
    ADC->ADM0 |= ADCE;
}
/***********************************************************************************************************************
* Function Name: ADC_Set_Clock
* @brief  This function sets the operation clock of AD converter.
* @param  fr - specify the value of FR0~2 in ADM0 register
* @param  smp - specify the sample clock number of AD converter
* @return None
***********************************************************************************************************************/
void ADC_Set_Clock(adc_clock_t fr, uint8_t smp)
{
	ADC->ADM0 &= ~(7 << FR_Pos);
	ADC->ADM0 |= fr << FR_Pos;
	ADC->ADNSMP = smp;
}
/***********************************************************************************************************************
* Function Name: ADC_Converse
* @brief  This function starts the AD converter and returns the conversion result in the buf
*         It is suitable for software trigger mode with polling mode.
* @param  ch - specify the ad channel
* @param  sz - set the times of ad conversion
* @param  buf - the address where to write the conversion result
* @return average value
***********************************************************************************************************************/
uint16_t ADC_Converse(adc_channel_t ch, uint32_t sz, uint16_t *buf)
{
    uint32_t i;
    volatile uint8_t  flag;
    uint32_t total = 0;
   
    INTC_DisableIRQ(ADC_IRQn);     /* disable INTAD interrupt */
//-----------------------------------------------------------------------
// normal mode
//-----------------------------------------------------------------------
    /* Soft Trigger one-shot select mode */
	ADC->ADM0 &= ~ADCE;
    ADC->ADM1 = _08_AD_CONVMODE_ONESELECT ;
    ADC->ADTRG = _00_AD_TRIGGER_SOFTWARE;
    ADC->ADM0 |= ADCE;
	
    /* specify ad channel */
    ADC->ADS  = ch; 
    
    /* Execute ADC converse and get the conversion result */
    for (i=0; i<sz; i++)
    {
        /* ADC start */
        ADC->ADM0 |= ADCS; 

        /* waiting interrupt flag */
        while(INTC_GetPendingIRQ(ADC_IRQn) == 0);
        INTC_ClearPendingIRQ(ADC_IRQn);     /* clear INTAD interrupt flag */

        /* Get the conversion result */
        *buf++ = ADC->ADCR;
        /* Calculate the total value of the conversion result */
        total += ADC->ADCR;
    } 
    
    return (total / sz); // return average value
}

/***********************************************************************************************************************
* Function Name: ADC_SelfTest
* @brief  This function starts the AD converter and returns the conversion result in the buf
*         It is suitable for software trigger mode with polling mode.
* @param  tes - select the self test mode
* @param  sz - set the times of ad conversion
* @param  buf - the address where to write the conversion result
* @return average value
***********************************************************************************************************************/
uint16_t ADC_SelfTest(adc_tes_t tes, uint32_t sz, uint16_t *buf)
{
    uint32_t i;
    volatile uint8_t  flag;
    uint32_t total = 0;
   
    INTC_DisableIRQ(ADC_IRQn);     /* disable INTAD interrupt */
//-----------------------------------------------------------------------
// normal mode
//-----------------------------------------------------------------------
    if(tes == ADTES_FREE_CODE)
    {
        /* one-shot select mode, low current mode only for free code test */
        ADC->ADM1 = _08_AD_CONVMODE_ONESELECT | _03_AD_LOW_CURRENT;
    }
    else
    {
        /* one-shot select mode, high speed mode for others code test */
        ADC->ADM1 = _08_AD_CONVMODE_ONESELECT;
    }

    ADC->ADTRG = _00_AD_TRIGGER_SOFTWARE;
    
    /* test mode */
    ADC->ADTES = tes;  
	
    /* specify ad channel */
    ADC->ADS  = ADC_CHANNEL_OFF; 
    
    /* Execute ADC converse and get the conversion result */
    for (i=0; i<sz; i++)
    {
        if(tes == ADTES_FREE_CODE)
        {
	        /* ADVALL */	
	        *((uint16_t *)0x40045012) = (((i + 1) * (4096 / sz) - 1) << 2);
        }
			
        /* ADC start */
        ADC->ADM0 |= ADCS; 

        /* waiting interrupt flag */
        while(INTC_GetPendingIRQ(ADC_IRQn) == 0);
        INTC_ClearPendingIRQ(ADC_IRQn);     /* clear INTAD interrupt flag */

        /* Get the conversion result */
        *buf++ = ADC->ADCR;
        /* Calculate the total value of the conversion result */
        total += ADC->ADCR;
    } 
    
    return (total / sz); // return average value
}
/***********************************************************************************************************************
* Function Name: ADC_MidAvg_Filter
* @brief  This function gets the middle average of middle values of the AD results.  
* @param  buf - the pointer of the conversion result
* @param  num - set the numbers of ad conversion result
* @return average
***********************************************************************************************************************/
uint16_t ADC_MidAvg_Filter(uint16_t *buf, uint8_t num)
{
    uint8_t i, j;
    uint16_t tmp;
    uint32_t sum;

    /* sort the value from small to large */
    for(i = 0; i < num; i++)
    {
        for(j = 0; j < ((num - 1) - i); j++)
        {
            if(buf[j] > buf[j + 1])
            {
                tmp = buf[j];
                buf[j] = buf[j + 1];
                buf[j + 1] = tmp;
            }
        }
    }

    /* Remove the smallest and largest values, then take the average */
    sum = 0;
    for(i = 2; i < (num - 2); i++)
    {
        sum += buf[i];
    }
    tmp = (uint16_t) (sum / (num - 4));

    return (tmp);
}
/* Start user code for adding. Do not edit comment generated here */
/* End user code. Do not edit comment generated here */

