/***************************************************************************//** * @file * @brief Analog to Digital Converter (ADC) Peripheral API for EFM32 * @author Energy Micro AS * @version 2.3.2 ******************************************************************************* * @section License * (C) Copyright 2010 Energy Micro AS, http://www.energymicro.com ******************************************************************************* * * This source code is the property of Energy Micro AS. The source and compiled * code may only be used on Energy Micro "EFM32" microcontrollers. * * This copyright notice may not be removed from the source code nor changed. * * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Energy Micro AS has no * obligation to support this Software. Energy Micro AS is providing the * Software "AS IS", with no express or implied warranties of any kind, * including, but not limited to, any implied warranties of merchantability * or fitness for any particular purpose or warranties against infringement * of any proprietary rights of a third party. * * Energy Micro AS will not be liable for any consequential, incidental, or * special damages, or any other relief, or for any claim by any third party, * arising from your use of this Software. * ******************************************************************************/ #include "efm32_adc.h" #include "efm32_cmu.h" #include "efm32_assert.h" /***************************************************************************//** * @addtogroup EFM32_Library * @{ ******************************************************************************/ /***************************************************************************//** * @addtogroup ADC * @brief Analog to Digital Converter (ADC) Peripheral API for EFM32 * @{ ******************************************************************************/ /******************************************************************************* ******************************* DEFINES *********************************** ******************************************************************************/ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ /** Validation of ADC register block pointer reference for assert statements. */ #define ADC_REF_VALID(ref) ((ref) == ADC0) /** Max ADC clock */ #define ADC_MAX_CLOCK 13000000 /** Min ADC clock */ #define ADC_MIN_CLOCK 32000 /** @endcond */ /******************************************************************************* *************************** LOCAL FUNCTIONS ******************************* ******************************************************************************/ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ /***************************************************************************//** * @brief * Load SCAN calibrate register with predefined values for a certain * reference. * * @details * During production, calibration values are made and stored in the device * information page for known references. Notice that for external references, * calibration values must be determined explicitly, and this function * will not modify the calibration register. * * @param[in] adc * Pointer to ADC peripheral register block. * * @param[in] ref * Reference to load calibrated values for. No values are loaded for * external references. ******************************************************************************/ static void ADC_CalibrateLoadScan(ADC_TypeDef *adc, ADC_Ref_TypeDef ref) { uint32_t cal; /* Load proper calibration data depending on selected reference */ /* NOTE: We use ...SCAN... defines below, they are the same as */ /* similar ...SINGLE... defines. */ switch (ref) { case adcRef1V25: cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK); cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_GAIN_MASK) >> _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK) >> _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT; adc->CAL = cal; break; case adcRef2V5: cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK); cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_GAIN_MASK) >> _DEVINFO_ADC0CAL0_2V5_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_OFFSET_MASK) >> _DEVINFO_ADC0CAL0_2V5_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT; adc->CAL = cal; break; case adcRefVDD: cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK); cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_GAIN_MASK) >> _DEVINFO_ADC0CAL1_VDD_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_OFFSET_MASK) >> _DEVINFO_ADC0CAL1_VDD_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT; adc->CAL = cal; break; case adcRef5VDIFF: cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK); cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_GAIN_MASK) >> _DEVINFO_ADC0CAL1_5VDIFF_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_MASK) >> _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT; adc->CAL = cal; break; case adcRef2xVDD: /* Gain value not of relevance for this reference, leave as is */ cal = adc->CAL & ~_ADC_CAL_SCANOFFSET_MASK; cal |= ((DEVINFO->ADC0CAL2 & _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_MASK) >> _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT; adc->CAL = cal; break; /* For external references, the calibration must be determined for the */ /* specific application and set explicitly. */ default: break; } } /***************************************************************************//** * @brief * Load SINGLE calibrate register with predefined values for a certain * reference. * * @details * During production, calibration values are made and stored in the device * information page for known references. Notice that for external references, * calibration values must be determined explicitly, and this function * will not modify the calibration register. * * @param[in] adc * Pointer to ADC peripheral register block. * * @param[in] ref * Reference to load calibrated values for. No values are loaded for * external references. ******************************************************************************/ static void ADC_CalibrateLoadSingle(ADC_TypeDef *adc, ADC_Ref_TypeDef ref) { uint32_t cal; /* Load proper calibration data depending on selected reference */ /* NOTE: We use ...SCAN... defines below, they are the same as */ /* similar ...SINGLE... defines. */ switch (ref) { case adcRef1V25: cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK); cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_GAIN_MASK) >> _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK) >> _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; break; case adcRef2V5: cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK); cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_GAIN_MASK) >> _DEVINFO_ADC0CAL0_2V5_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_OFFSET_MASK) >> _DEVINFO_ADC0CAL0_2V5_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; break; case adcRefVDD: cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK); cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_GAIN_MASK) >> _DEVINFO_ADC0CAL1_VDD_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_OFFSET_MASK) >> _DEVINFO_ADC0CAL1_VDD_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; break; case adcRef5VDIFF: cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK); cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_GAIN_MASK) >> _DEVINFO_ADC0CAL1_5VDIFF_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_MASK) >> _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; break; case adcRef2xVDD: /* Gain value not of relevance for this reference, leave as is */ cal = adc->CAL & ~_ADC_CAL_SINGLEOFFSET_MASK; cal |= ((DEVINFO->ADC0CAL2 & _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_MASK) >> _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; break; /* For external references, the calibration must be determined for the */ /* specific application and set explicitly. */ default: break; } } /** @endcond */ /******************************************************************************* ************************** GLOBAL FUNCTIONS ******************************* ******************************************************************************/ /***************************************************************************//** * @brief * Initialize ADC. * * @details * Initializes common parts for both single conversion and scan sequence. * In addition, single and/or scan control configuration must be done, please * refer to ADC_InitSingle() and ADC_InitScan() respectively. * * @note * This function will stop any ongoing conversion. * * @param[in] adc * Pointer to ADC peripheral register block. * * @param[in] init * Pointer to ADC initialization structure. ******************************************************************************/ void ADC_Init(ADC_TypeDef *adc, const ADC_Init_TypeDef *init) { uint32_t tmp; EFM_ASSERT(ADC_REF_VALID(adc)); /* Make sure conversion is not in progress */ adc->CMD = ADC_CMD_SINGLESTOP | ADC_CMD_SCANSTOP; tmp = ((uint32_t)(init->ovsRateSel) << _ADC_CTRL_OVSRSEL_SHIFT) | (((uint32_t)(init->timebase) << _ADC_CTRL_TIMEBASE_SHIFT) & _ADC_CTRL_TIMEBASE_MASK) | (((uint32_t)(init->prescale) << _ADC_CTRL_PRESC_SHIFT) & _ADC_CTRL_PRESC_MASK) | ((uint32_t)(init->lpfMode) << _ADC_CTRL_LPFMODE_SHIFT) | ((uint32_t)(init->warmUpMode) << _ADC_CTRL_WARMUPMODE_SHIFT); if (init->tailgate) { tmp |= ADC_CTRL_TAILGATE; } adc->CTRL = tmp; } /***************************************************************************//** * @brief * Initialize ADC scan sequence. * * @details * Please refer to ADC_StartScan() for starting scan sequence. * * When selecting an external reference, the gain and offset calibration * must be set explicitly (CAL register). For other references, the * calibration is updated with values defined during manufacturing. * * @note * This function will stop any ongoing scan sequence. * * @param[in] adc * Pointer to ADC peripheral register block. * * @param[in] init * Pointer to ADC initialization structure. ******************************************************************************/ void ADC_InitScan(ADC_TypeDef *adc, const ADC_InitScan_TypeDef *init) { uint32_t tmp; EFM_ASSERT(ADC_REF_VALID(adc)); /* Make sure scan sequence is not in progress */ adc->CMD = ADC_CMD_SCANSTOP; /* Load proper calibration data depending on selected reference */ ADC_CalibrateLoadScan(adc, init->reference); tmp = ((uint32_t)(init->prsSel) << _ADC_SCANCTRL_PRSSEL_SHIFT) | ((uint32_t)(init->acqTime) << _ADC_SCANCTRL_AT_SHIFT) | ((uint32_t)(init->reference) << _ADC_SCANCTRL_REF_SHIFT) | init->input | ((uint32_t)(init->resolution) << _ADC_SCANCTRL_RES_SHIFT); if (init->prsEnable) { tmp |= ADC_SCANCTRL_PRSEN; } if (init->leftAdjust) { tmp |= ADC_SCANCTRL_ADJ_LEFT; } if (init->diff) { tmp |= ADC_SCANCTRL_DIFF; } if (init->rep) { tmp |= ADC_SCANCTRL_REP; } adc->SCANCTRL = tmp; } /***************************************************************************//** * @brief * Initialize single ADC sample conversion. * * @details * Please refer to ADC_StartSingle() for starting single conversion. * * When selecting an external reference, the gain and offset calibration * must be set explicitly (CAL register). For other references, the * calibration is updated with values defined during manufacturing. * * @note * This function will stop any ongoing single conversion. * * @param[in] adc * Pointer to ADC peripheral register block. * * @param[in] init * Pointer to ADC initialization structure. ******************************************************************************/ void ADC_InitSingle(ADC_TypeDef *adc, const ADC_InitSingle_TypeDef *init) { uint32_t tmp; EFM_ASSERT(ADC_REF_VALID(adc)); /* Make sure single conversion is not in progress */ adc->CMD = ADC_CMD_SINGLESTOP; /* Load proper calibration data depending on selected reference */ ADC_CalibrateLoadSingle(adc, init->reference); tmp = ((uint32_t)(init->prsSel) << _ADC_SINGLECTRL_PRSSEL_SHIFT) | ((uint32_t)(init->acqTime) << _ADC_SINGLECTRL_AT_SHIFT) | ((uint32_t)(init->reference) << _ADC_SINGLECTRL_REF_SHIFT) | ((uint32_t)(init->input) << _ADC_SINGLECTRL_INPUTSEL_SHIFT) | ((uint32_t)(init->resolution) << _ADC_SINGLECTRL_RES_SHIFT); if (init->prsEnable) { tmp |= ADC_SINGLECTRL_PRSEN; } if (init->leftAdjust) { tmp |= ADC_SINGLECTRL_ADJ_LEFT; } if (init->diff) { tmp |= ADC_SINGLECTRL_DIFF; } if (init->rep) { tmp |= ADC_SINGLECTRL_REP; } adc->SINGLECTRL = tmp; } /***************************************************************************//** * @brief * Calculate prescaler value used to determine ADC clock. * * @details * The ADC clock is given by: HFPERCLK / (prescale + 1). * * @param[in] adcFreq ADC frequency wanted. The frequency will automatically * be adjusted to be within valid range according to reference manual. * * @param[in] hfperFreq Frequency in Hz of reference HFPER clock. Set to 0 to * use currently defined HFPER clock setting. * * @return * Prescaler value to use for ADC in order to achieve a clock value * <= @p adcFreq. ******************************************************************************/ uint8_t ADC_PrescaleCalc(uint32_t adcFreq, uint32_t hfperFreq) { uint32_t ret; /* Make sure selected ADC clock is within valid range */ if (adcFreq > ADC_MAX_CLOCK) { adcFreq = ADC_MAX_CLOCK; } else if (adcFreq < ADC_MIN_CLOCK) { adcFreq = ADC_MIN_CLOCK; } /* Use current HFPER frequency? */ if (!hfperFreq) { hfperFreq = CMU_ClockFreqGet(cmuClock_HFPER); } ret = (hfperFreq + adcFreq - 1) / adcFreq; if (ret) { ret--; } return (uint8_t)ret; } /***************************************************************************//** * @brief * Reset ADC to same state as after a HW reset. * * @note * The ROUTE register is NOT reset by this function, in order to allow for * centralized setup of this feature. * * @param[in] adc * Pointer to ADC peripheral register block. ******************************************************************************/ void ADC_Reset(ADC_TypeDef *adc) { uint32_t cal; /* Stop conversions, before resetting other registers. */ adc->CMD = ADC_CMD_SINGLESTOP | ADC_CMD_SCANSTOP; adc->SINGLECTRL = _ADC_SINGLECTRL_RESETVALUE; adc->SCANCTRL = _ADC_SCANCTRL_RESETVALUE; adc->CTRL = _ADC_CTRL_RESETVALUE; adc->IEN = _ADC_IEN_RESETVALUE; adc->IFC = _ADC_IFC_MASK; adc->BIASPROG = _ADC_BIASPROG_RESETVALUE; cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK); cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_GAIN_MASK) >> _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT; cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK) >> _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT; adc->CAL = cal; /* Do not reset route register, setting should be done independently */ } /***************************************************************************//** * @brief * Calculate timebase value in order to get a timebase providing at least 1us. * * @param[in] hfperFreq Frequency in Hz of reference HFPER clock. Set to 0 to * use currently defined HFPER clock setting. * * @return * Timebase value to use for ADC in order to achieve at least 1 us. ******************************************************************************/ uint8_t ADC_TimebaseCalc(uint32_t hfperFreq) { if (!hfperFreq) { hfperFreq = CMU_ClockFreqGet(cmuClock_HFPER); /* Just in case, make sure we get non-zero freq for below calculation */ if (!hfperFreq) { hfperFreq = 1; } } /* Determine number of HFPERCLK cycle >= 1us */ hfperFreq += 999999; hfperFreq /= 1000000; /* Return timebase value (N+1 format) */ return (uint8_t)(hfperFreq - 1); } /** @} (end addtogroup ADC) */ /** @} (end addtogroup EFM32_Library) */