/* ---------------------------------------------------------------------------- | |
* ATMEL Microcontroller Software Support | |
* ---------------------------------------------------------------------------- | |
* Copyright (c) 2010, Atmel Corporation | |
* | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* - Redistributions of source code must retain the above copyright notice, | |
* this list of conditions and the disclaimer below. | |
* | |
* Atmel's name may not be used to endorse or promote products derived from | |
* this software without specific prior written permission. | |
* | |
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR | |
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE | |
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, | |
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, | |
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | |
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
* ---------------------------------------------------------------------------- | |
*/ | |
/** \addtogroup adc_module Working with ADC | |
* \ingroup peripherals_module | |
* The ADC driver provides the interface to configure and use the ADC peripheral. | |
* \n | |
* | |
* It converts the analog input to digital format. The converted result could be | |
* 12bit or 10bit. The ADC supports up to 16 analog lines. | |
* | |
* To Enable a ADC conversion,the user has to follow these few steps: | |
* <ul> | |
* <li> Select an appropriate reference voltage on ADVREF </li> | |
* <li> Configure the ADC according to its requirements and special needs,which | |
* could be broken down into several parts: | |
* -# Select the resolution by setting or clearing ADC_MR_LOWRES bit in | |
* ADC_MR (Mode Register) | |
* -# Set ADC clock by setting ADC_MR_PRESCAL bits in ADC_MR, the clock is | |
* calculated with ADCClock = MCK / ( (PRESCAL+1) * 2 ) | |
* -# Set Startup Time,Tracking Clock cycles and Transfer Clock respectively | |
* in ADC_MR. | |
</li> | |
* <li> Start conversion by setting ADC_CR_START in ADC_CR. </li> | |
* </ul> | |
* | |
* For more accurate information, please look at the ADC section of the | |
* Datasheet. | |
* | |
* Related files :\n | |
* \ref adc.c\n | |
* \ref adc.h\n | |
*/ | |
/*@{*/ | |
/*@}*/ | |
/** | |
* \file | |
* | |
* Implementation of Analog-to-Digital Converter (ADC). | |
* | |
*/ | |
/*---------------------------------------------------------------------------- | |
* Headers | |
*----------------------------------------------------------------------------*/ | |
#include "chip.h" | |
/*---------------------------------------------------------------------------- | |
* Exported functions | |
*----------------------------------------------------------------------------*/ | |
/** | |
* \brief Initialize the ADC controller | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param dwID ADC Index | |
*/ | |
extern void ADC_Initialize( Adc* pAdc, uint32_t dwID ) | |
{ | |
/* Enable peripheral clock*/ | |
PMC_EnablePeripheral(dwID); | |
/* Reset the controller */ | |
pAdc->ADC_CR = ADC_CR_SWRST; | |
/* Reset Mode Register */ | |
pAdc->ADC_MR = 0; | |
/* Reset PDC transfer */ | |
pAdc->ADC_PTCR = (ADC_PTCR_RXTDIS | ADC_PTCR_TXTDIS); | |
pAdc->ADC_RCR = 0; | |
pAdc->ADC_RNCR = 0; | |
pAdc->ADC_TCR = 0; | |
pAdc->ADC_TNCR = 0; | |
} | |
/** | |
* \brief Set ADC clock. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param dwPres prescal value | |
* \param dwMck Board MCK (Hz) | |
* | |
* \return ADC clock | |
*/ | |
extern uint32_t ADC_SetClock( Adc* pAdc, uint32_t dwPres, uint32_t dwMck ) | |
{ | |
uint32_t dwMr; | |
uint32_t dwAdcClock; | |
dwMr = pAdc->ADC_MR; | |
dwMr = (dwMr & ~ADC_MR_PRESCAL_Msk) | ADC_MR_PRESCAL( dwPres ); | |
pAdc->ADC_MR |= dwMr; | |
/* Formula: ADCClock = MCK / ( (PRESCAL+1) * 2 ) */ | |
dwAdcClock = dwMck / ( (dwPres + 1) * 2 ); | |
return dwAdcClock; | |
} | |
/** | |
* \brief Set ADC timing. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param dwStartup startup value | |
* \param dwTransfer transfer value | |
* \param dwTracking tracking value | |
* \param dwSettling settling value | |
*/ | |
extern void ADC_SetTiming( Adc* pAdc, uint32_t dwStartup, uint32_t dwTransfer, | |
uint32_t dwTracking, uint32_t dwSettling ) | |
{ | |
uint32_t dwMr; | |
dwMr = pAdc->ADC_MR; | |
dwMr &= (~ADC_MR_STARTUP_Msk) & (~ADC_MR_TRACKTIM_Msk) & | |
(~ADC_MR_TRANSFER_Msk) & (~ADC_MR_SETTLING_Msk); | |
/* Formula: | |
* Startup Time = startup value / ADCClock | |
* Transfer Time = (TRANSFER * 2 + 3) / ADCClock | |
* Tracking Time = (TRACKTIM + 1) / ADCClock | |
* Settling Time = settling value / ADCClock | |
*/ | |
dwMr |= dwStartup | dwTransfer | dwTracking | dwSettling; | |
pAdc->ADC_MR |= dwMr; | |
} | |
/** | |
* \brief Set ADC trigger. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param bEnDis Enable/Disable hardware trigger | |
* \param dwTrgSel Trigger selection | |
*/ | |
extern void ADC_SetTrigger( Adc* pAdc, uint32_t bEnDis, uint32_t dwTrgSel ) | |
{ | |
uint32_t dwMr; | |
dwMr = pAdc->ADC_MR; | |
if ( bEnDis ) | |
{ | |
dwMr |= ADC_MR_TRGEN; | |
} | |
else | |
{ | |
dwMr &= ~ADC_MR_TRGEN; | |
} | |
dwMr &= ~ADC_MR_TRGSEL_Msk; | |
dwMr |= dwTrgSel; | |
pAdc->ADC_MR |= dwMr; | |
} | |
/** | |
* \brief Set free run mode. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param bEnDis Enable/Disable free run mode. | |
*/ | |
extern void ADC_SetFreeRunMode( Adc* pAdc, uint32_t bEnDis ) | |
{ | |
if ( bEnDis ) | |
{ | |
pAdc->ADC_MR |= ADC_MR_FREERUN; | |
} | |
else | |
{ | |
pAdc->ADC_MR &= ~ADC_MR_FREERUN; | |
} | |
} | |
/** | |
* \brief Enable/Disable low resolution. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param bEnDis Enable/Disable low resolution. | |
*/ | |
extern void ADC_SetLowResolution( Adc* pAdc, uint32_t bEnDis ) | |
{ | |
if ( bEnDis ) | |
{ | |
pAdc->ADC_MR |= ADC_MR_LOWRES; | |
} | |
else | |
{ | |
pAdc->ADC_MR &= ~ADC_MR_LOWRES; | |
} | |
} | |
/** | |
* \brief Enable/Disable sleep mode. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param bEnDis Enable/Disable sleep mode. | |
*/ | |
extern void ADC_SetSleepMode( Adc *pAdc, uint8_t bEnDis ) | |
{ | |
if ( bEnDis ) | |
{ | |
pAdc->ADC_MR |= ADC_MR_SLEEP; | |
} | |
else | |
{ | |
pAdc->ADC_MR &= ~ADC_MR_SLEEP; | |
} | |
} | |
/** | |
* \brief Enable/Disable fast wake up. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param bEnDis Enable/Disable fast wake up in sleep mode. | |
*/ | |
extern void ADC_SetFastWakeup( Adc *pAdc, uint8_t bEnDis ) | |
{ | |
if ( bEnDis ) | |
{ | |
pAdc->ADC_MR |= ADC_MR_FWUP; | |
} | |
else | |
{ | |
pAdc->ADC_MR &= ~ADC_MR_FWUP; | |
} | |
} | |
/** | |
* \brief Enable/Disable seqnence mode. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param bEnDis Enable/Disable seqnence mode. | |
*/ | |
extern void ADC_SetSequenceMode( Adc *pAdc, uint8_t bEnDis ) | |
{ | |
if ( bEnDis ) | |
{ | |
/* User Sequence Mode: The sequence respects what is defined in | |
ADC_SEQR1 and ADC_SEQR2 */ | |
pAdc->ADC_MR |= ADC_MR_USEQ; | |
} | |
else | |
{ | |
/* Normal Mode: The controller converts channels in a simple numeric order. */ | |
pAdc->ADC_MR &= ~ADC_MR_USEQ; | |
} | |
} | |
/** | |
* \brief Set channel sequence. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param dwSEQ1 Sequence 1 ~ 8 channel number. | |
* \param dwSEQ2 Sequence 9 ~ 16 channel number. | |
*/ | |
extern void ADC_SetSequence( Adc *pAdc, uint32_t dwSEQ1, uint32_t dwSEQ2 ) | |
{ | |
pAdc->ADC_SEQR1 = dwSEQ1; | |
pAdc->ADC_SEQR2 = dwSEQ2; | |
} | |
/** | |
* \brief Set channel sequence by given channel list. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param ucChList Channel list. | |
* \param ucNumCh Number of channels in list. | |
*/ | |
extern void ADC_SetSequenceByList( Adc *pAdc, uint8_t ucChList[], uint8_t ucNumCh ) | |
{ | |
uint8_t i; | |
uint8_t ucShift; | |
pAdc->ADC_SEQR1 = 0; | |
for (i = 0, ucShift = 0; i < 8; i ++, ucShift += 4) | |
{ | |
if (i >= ucNumCh) return; | |
pAdc->ADC_SEQR1 |= ucChList[i] << ucShift; | |
} | |
pAdc->ADC_SEQR2 = 0; | |
for (ucShift = 0; i < 16; i ++, ucShift += 4) | |
{ | |
if (i >= ucNumCh) return; | |
pAdc->ADC_SEQR2 |= ucChList[i] << ucShift; | |
} | |
} | |
/** | |
* \brief Set analog change. | |
* IF enabled, it allows different analog settings for each channel, | |
* otherwise, DIFF0, GAIN0 and OFF0 are used for all channels. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param bEnDis Enable/Disable. | |
*/ | |
extern void ADC_SetAnalogChange( Adc *pAdc, uint8_t bEnDis ) | |
{ | |
if ( bEnDis ) | |
{ | |
pAdc->ADC_MR |= ADC_MR_ANACH; | |
} | |
else | |
{ | |
pAdc->ADC_MR &= ~ADC_MR_ANACH; | |
} | |
} | |
/** | |
* \brief Set "TAG" mode, show channel number in last data or not. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param bEnDis Enable/Disable TAG value. | |
*/ | |
extern void ADC_SetTagEnable( Adc *pAdc, uint8_t bEnDis ) | |
{ | |
if ( bEnDis ) | |
{ | |
pAdc->ADC_EMR |= ADC_EMR_TAG; | |
} | |
else | |
{ | |
pAdc->ADC_EMR &= ~ADC_EMR_TAG; | |
} | |
} | |
/** | |
* \brief Set compare channel. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param dwChannel channel number to be set,16 for all channels | |
*/ | |
extern void ADC_SetCompareChannel( Adc* pAdc, uint32_t dwChannel ) | |
{ | |
assert( dwChannel <= 16 ) ; | |
if ( dwChannel < 16 ) | |
{ | |
pAdc->ADC_EMR &= ~(ADC_EMR_CMPALL); | |
pAdc->ADC_EMR &= ~(ADC_EMR_CMPSEL_Msk); | |
pAdc->ADC_EMR |= (dwChannel << ADC_EMR_CMPSEL_Pos); | |
} | |
else | |
{ | |
pAdc->ADC_EMR |= ADC_EMR_CMPALL; | |
} | |
} | |
/** | |
* \brief Set compare mode. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param dwMode compare mode | |
*/ | |
extern void ADC_SetCompareMode( Adc* pAdc, uint32_t dwMode ) | |
{ | |
pAdc->ADC_EMR &= ~(ADC_EMR_CMPMODE_Msk); | |
pAdc->ADC_EMR |= (dwMode & ADC_EMR_CMPMODE_Msk); | |
} | |
/** | |
* \brief Set comparsion window. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param dwHi_Lo Comparison Window | |
*/ | |
extern void ADC_SetComparisonWindow( Adc* pAdc, uint32_t dwHi_Lo ) | |
{ | |
pAdc->ADC_CWR = dwHi_Lo ; | |
} | |
/** | |
* \brief Get startup value. | |
*/ | |
static uint32_t GetStartupValue( uint32_t dwStartup ) | |
{ | |
uint32_t dwStartupValue = 0; | |
if( dwStartup == 0 ) | |
dwStartupValue = 0; | |
else if( dwStartup == 1 ) | |
dwStartupValue = 8; | |
else if( dwStartup == 2 ) | |
dwStartupValue = 16; | |
else if( dwStartup == 3 ) | |
dwStartupValue = 24; | |
else if( dwStartup == 4 ) | |
dwStartupValue = 64; | |
else if( dwStartup == 5 ) | |
dwStartupValue = 80; | |
else if( dwStartup == 6 ) | |
dwStartupValue = 96; | |
else if( dwStartup == 7 ) | |
dwStartupValue = 112; | |
else if( dwStartup == 8 ) | |
dwStartupValue = 512; | |
else if( dwStartup == 9 ) | |
dwStartupValue = 576; | |
else if( dwStartup == 10 ) | |
dwStartupValue = 640; | |
else if( dwStartup == 11 ) | |
dwStartupValue = 704; | |
else if( dwStartup == 12 ) | |
dwStartupValue = 768; | |
else if( dwStartup == 13 ) | |
dwStartupValue = 832; | |
else if( dwStartup == 14 ) | |
dwStartupValue = 896; | |
else if( dwStartup == 15 ) | |
dwStartupValue = 960; | |
return dwStartupValue; | |
} | |
/** | |
* \brief Check if ADC configuration is right. | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param dwMck Board MCK (Hz) | |
* | |
* \return 0 if check ok, others if not ok. | |
*/ | |
extern uint8_t ADC_CheckConfiguration( Adc* pAdc, uint32_t dwMck ) | |
{ | |
uint8_t bOk = 0; | |
uint32_t dwMr; | |
uint32_t dwPres; | |
uint32_t dwStartup; | |
uint32_t dwAdcClock; | |
uint32_t dwTemp; | |
dwMr = pAdc->ADC_MR; | |
dwPres = (dwMr & ADC_MR_PRESCAL_Msk) >> ADC_MR_PRESCAL_Pos; | |
/* Formula: ADCClock = MCK / ( (PRESCAL+1) * 2 ) */ | |
dwAdcClock = dwMck / ( (dwPres + 1) * 2 ); | |
if (dwAdcClock > ADC_CLOCK_MAX) | |
{ | |
printf("ADC clock is too high (out of specification: %d Hz)\r\n", (int)ADC_CLOCK_MAX); | |
bOk = 1; | |
} | |
dwStartup = (dwMr & ADC_MR_STARTUP_Msk) >> ADC_MR_STARTUP_Pos; | |
if (dwMr & ADC_MR_SLEEP_SLEEP) | |
{ | |
if (dwMr & ADC_MR_FREERUN_ON) | |
{ | |
printf("FreeRun mode is forbidden in sleep mode\n\r"); | |
bOk = 1; | |
} | |
if( !(dwMr & ADC_MR_FWUP_ON) ) | |
{ | |
/* Sleep 40µs */ | |
dwTemp = ADC_STARTUP_NORMAL_MAX * dwAdcClock / 1000000; | |
if( dwTemp > GetStartupValue(dwStartup) ) | |
{ | |
printf("Startup time too small: %d, programmed: %d\r\n", (int)dwTemp, (int)(GetStartupValue(dwStartup))); | |
bOk = 1; | |
} | |
} | |
else | |
{ | |
if( pAdc->ADC_MR & ADC_MR_FWUP_ON ) | |
{ | |
/* Fast Wake Up Sleep Mode: 12µs */ | |
dwTemp = ADC_STARTUP_FAST_MAX * dwAdcClock / 1000000; | |
if( dwTemp > GetStartupValue(dwStartup) ) | |
{ | |
printf("Startup time too small: %d, programmed: %d\r\n", (int)dwTemp, (int)(GetStartupValue(dwStartup))); | |
bOk = 1; | |
} | |
} | |
} | |
} | |
return bOk; | |
} | |
/** | |
* \brief Return the Channel Converted Data | |
* | |
* \param pAdc Pointer to an Adc instance. | |
* \param dwChannel channel to get converted value | |
*/ | |
extern uint32_t ADC_GetConvertedData( Adc* pAdc, uint32_t dwChannel ) | |
{ | |
uint32_t dwData = 0; | |
assert( dwChannel < 16 ) ; | |
dwData = pAdc->ADC_CDR[dwChannel]; | |
return dwData ; | |
} | |
/** | |
* \brief Read converted data through PDC channel | |
* | |
* \param pADC the pointer of adc peripheral | |
* \param pBuffer the destination buffer | |
* \param dwSize the size of the buffer | |
*/ | |
extern uint32_t ADC_ReadBuffer( Adc* pADC, uint16_t *pwBuffer, uint32_t dwSize ) | |
{ | |
/* Check if the first PDC bank is free*/ | |
if ( (pADC->ADC_RCR == 0) && (pADC->ADC_RNCR == 0) ) | |
{ | |
pADC->ADC_RPR = (uint32_t)pwBuffer ; | |
pADC->ADC_RCR = dwSize ; | |
pADC->ADC_PTCR = ADC_PTCR_RXTEN; | |
return 1; | |
} | |
/* Check if the second PDC bank is free*/ | |
else | |
{ | |
if ( pADC->ADC_RNCR == 0 ) | |
{ | |
pADC->ADC_RNPR = (uint32_t)pwBuffer ; | |
pADC->ADC_RNCR = dwSize ; | |
return 1 ; | |
} | |
else | |
{ | |
return 0 ; | |
} | |
} | |
} | |