blob: f15cd44e5ffd9151bb0b36f3bcab06c353abebec [file] [log] [blame]
/***************************************************************************//**
* @file em_adc.c
* @brief Analog to Digital Converter (ADC) Peripheral API
* @version 5.1.2
*******************************************************************************
* @section License
* <b>Copyright 2016 Silicon Laboratories, Inc. http://www.silabs.com</b>
*******************************************************************************
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
* DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
* obligation to support this Software. Silicon Labs 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.
*
* Silicon Labs 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 "em_adc.h"
#if defined( ADC_COUNT ) && ( ADC_COUNT > 0 )
#include "em_assert.h"
#include "em_cmu.h"
#include <stddef.h>
/***************************************************************************//**
* @addtogroup emlib
* @{
******************************************************************************/
/***************************************************************************//**
* @addtogroup ADC
* @brief Analog to Digital Converter (ADC) Peripheral API
* @details
* This module contains functions to control the ADC peripheral of Silicon
* Labs 32-bit MCUs and SoCs. The ADC is used to convert analog signals into a
* digital representation.
* @{
******************************************************************************/
/*******************************************************************************
******************************* 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 */
#if defined( _SILICON_LABS_32B_SERIES_0 )
#define ADC_MAX_CLOCK 13000000
#else
#define ADC_MAX_CLOCK 16000000
#endif
/** Min ADC clock */
#define ADC_MIN_CLOCK 32000
/** Helper defines for selecting ADC calibration and DEVINFO register fields. */
#if defined( _DEVINFO_ADC0CAL0_1V25_GAIN_MASK )
#define DEVINFO_ADC0_GAIN1V25_MASK _DEVINFO_ADC0CAL0_1V25_GAIN_MASK
#elif defined( _DEVINFO_ADC0CAL0_GAIN1V25_MASK )
#define DEVINFO_ADC0_GAIN1V25_MASK _DEVINFO_ADC0CAL0_GAIN1V25_MASK
#endif
#if defined( _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT )
#define DEVINFO_ADC0_GAIN1V25_SHIFT _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT
#elif defined( _DEVINFO_ADC0CAL0_GAIN1V25_SHIFT )
#define DEVINFO_ADC0_GAIN1V25_SHIFT _DEVINFO_ADC0CAL0_GAIN1V25_SHIFT
#endif
#if defined( _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK )
#define DEVINFO_ADC0_OFFSET1V25_MASK _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK
#elif defined( _DEVINFO_ADC0CAL0_OFFSET1V25_MASK )
#define DEVINFO_ADC0_OFFSET1V25_MASK _DEVINFO_ADC0CAL0_OFFSET1V25_MASK
#endif
#if defined( _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT )
#define DEVINFO_ADC0_OFFSET1V25_SHIFT _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT
#elif defined( _DEVINFO_ADC0CAL0_OFFSET1V25_SHIFT )
#define DEVINFO_ADC0_OFFSET1V25_SHIFT _DEVINFO_ADC0CAL0_OFFSET1V25_SHIFT
#endif
#if defined( _DEVINFO_ADC0CAL0_2V5_GAIN_MASK )
#define DEVINFO_ADC0_GAIN2V5_MASK _DEVINFO_ADC0CAL0_2V5_GAIN_MASK
#elif defined( _DEVINFO_ADC0CAL0_GAIN2V5_MASK )
#define DEVINFO_ADC0_GAIN2V5_MASK _DEVINFO_ADC0CAL0_GAIN2V5_MASK
#endif
#if defined( _DEVINFO_ADC0CAL0_2V5_GAIN_SHIFT )
#define DEVINFO_ADC0_GAIN2V5_SHIFT _DEVINFO_ADC0CAL0_2V5_GAIN_SHIFT
#elif defined( _DEVINFO_ADC0CAL0_GAIN2V5_SHIFT )
#define DEVINFO_ADC0_GAIN2V5_SHIFT _DEVINFO_ADC0CAL0_GAIN2V5_SHIFT
#endif
#if defined( _DEVINFO_ADC0CAL0_2V5_OFFSET_MASK )
#define DEVINFO_ADC0_OFFSET2V5_MASK _DEVINFO_ADC0CAL0_2V5_OFFSET_MASK
#elif defined( _DEVINFO_ADC0CAL0_OFFSET2V5_MASK )
#define DEVINFO_ADC0_OFFSET2V5_MASK _DEVINFO_ADC0CAL0_OFFSET2V5_MASK
#endif
#if defined( _DEVINFO_ADC0CAL0_2V5_OFFSET_SHIFT )
#define DEVINFO_ADC0_OFFSET2V5_SHIFT _DEVINFO_ADC0CAL0_2V5_OFFSET_SHIFT
#elif defined( _DEVINFO_ADC0CAL0_OFFSET2V5_SHIFT )
#define DEVINFO_ADC0_OFFSET2V5_SHIFT _DEVINFO_ADC0CAL0_OFFSET2V5_SHIFT
#endif
#if defined( _DEVINFO_ADC0CAL1_VDD_GAIN_MASK )
#define DEVINFO_ADC0_GAINVDD_MASK _DEVINFO_ADC0CAL1_VDD_GAIN_MASK
#elif defined( _DEVINFO_ADC0CAL1_GAINVDD_MASK )
#define DEVINFO_ADC0_GAINVDD_MASK _DEVINFO_ADC0CAL1_GAINVDD_MASK
#endif
#if defined( _DEVINFO_ADC0CAL1_VDD_GAIN_SHIFT )
#define DEVINFO_ADC0_GAINVDD_SHIFT _DEVINFO_ADC0CAL1_VDD_GAIN_SHIFT
#elif defined( _DEVINFO_ADC0CAL1_GAINVDD_SHIFT )
#define DEVINFO_ADC0_GAINVDD_SHIFT _DEVINFO_ADC0CAL1_GAINVDD_SHIFT
#endif
#if defined( _DEVINFO_ADC0CAL1_VDD_OFFSET_MASK )
#define DEVINFO_ADC0_OFFSETVDD_MASK _DEVINFO_ADC0CAL1_VDD_OFFSET_MASK
#elif defined( _DEVINFO_ADC0CAL1_OFFSETVDD_MASK )
#define DEVINFO_ADC0_OFFSETVDD_MASK _DEVINFO_ADC0CAL1_OFFSETVDD_MASK
#endif
#if defined( _DEVINFO_ADC0CAL1_VDD_OFFSET_SHIFT )
#define DEVINFO_ADC0_OFFSETVDD_SHIFT _DEVINFO_ADC0CAL1_VDD_OFFSET_SHIFT
#elif defined( _DEVINFO_ADC0CAL1_OFFSETVDD_SHIFT )
#define DEVINFO_ADC0_OFFSETVDD_SHIFT _DEVINFO_ADC0CAL1_OFFSETVDD_SHIFT
#endif
#if defined( _DEVINFO_ADC0CAL1_5VDIFF_GAIN_MASK )
#define DEVINFO_ADC0_GAIN5VDIFF_MASK _DEVINFO_ADC0CAL1_5VDIFF_GAIN_MASK
#elif defined( _DEVINFO_ADC0CAL1_GAIN5VDIFF_MASK )
#define DEVINFO_ADC0_GAIN5VDIFF_MASK _DEVINFO_ADC0CAL1_GAIN5VDIFF_MASK
#endif
#if defined( _DEVINFO_ADC0CAL1_5VDIFF_GAIN_SHIFT )
#define DEVINFO_ADC0_GAIN5VDIFF_SHIFT _DEVINFO_ADC0CAL1_5VDIFF_GAIN_SHIFT
#elif defined( _DEVINFO_ADC0CAL1_GAIN5VDIFF_SHIFT )
#define DEVINFO_ADC0_GAIN5VDIFF_SHIFT _DEVINFO_ADC0CAL1_GAIN5VDIFF_SHIFT
#endif
#if defined( _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_MASK )
#define DEVINFO_ADC0_OFFSET5VDIFF_MASK _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_MASK
#elif defined( _DEVINFO_ADC0CAL1_OFFSET5VDIFF_MASK )
#define DEVINFO_ADC0_OFFSET5VDIFF_MASK _DEVINFO_ADC0CAL1_OFFSET5VDIFF_MASK
#endif
#if defined( _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_SHIFT )
#define DEVINFO_ADC0_OFFSET5VDIFF_SHIFT _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_SHIFT
#elif defined( _DEVINFO_ADC0CAL1_OFFSET5VDIFF_SHIFT )
#define DEVINFO_ADC0_OFFSET5VDIFF_SHIFT _DEVINFO_ADC0CAL1_OFFSET5VDIFF_SHIFT
#endif
#if defined( _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_MASK )
#define DEVINFO_ADC0_OFFSET2XVDD_MASK _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_MASK
#elif defined( _DEVINFO_ADC0CAL2_OFFSET2XVDD_MASK )
#define DEVINFO_ADC0_OFFSET2XVDD_MASK _DEVINFO_ADC0CAL2_OFFSET2XVDD_MASK
#endif
#if defined( _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_SHIFT )
#define DEVINFO_ADC0_OFFSET2XVDD_SHIFT _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_SHIFT
#elif defined( _DEVINFO_ADC0CAL2_OFFSET2XVDD_SHIFT )
#define DEVINFO_ADC0_OFFSET2XVDD_SHIFT _DEVINFO_ADC0CAL2_OFFSET2XVDD_SHIFT
#endif
#if defined( _SILICON_LABS_32B_SERIES_1 )
#define FIX_ADC_TEMP_BIAS_EN
#endif
/** @endcond */
/*******************************************************************************
*************************** LOCAL FUNCTIONS *******************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/***************************************************************************//**
* @brief
* Load ADC calibration register for a selected reference and conversion mode.
*
* @details
* During production, calibration values are stored in the device
* information page for internal references. Notice that for external references,
* calibration values must be determined explicitly, and this function
* will not modify the calibration register for external references.
*
* @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.
*
* @param[in] setScanCal
* Select scan mode (true) or single mode (false) calibration load.
******************************************************************************/
static void ADC_LoadDevinfoCal(ADC_TypeDef *adc,
ADC_Ref_TypeDef ref,
bool setScanCal)
{
uint32_t calReg;
uint32_t newCal;
uint32_t mask;
uint32_t shift;
if (setScanCal)
{
shift = _ADC_CAL_SCANOFFSET_SHIFT;
mask = ~(_ADC_CAL_SCANOFFSET_MASK
#if defined( _ADC_CAL_SCANOFFSETINV_MASK )
| _ADC_CAL_SCANOFFSETINV_MASK
#endif
| _ADC_CAL_SCANGAIN_MASK);
}
else
{
shift = _ADC_CAL_SINGLEOFFSET_SHIFT;
mask = ~(_ADC_CAL_SINGLEOFFSET_MASK
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
| _ADC_CAL_SINGLEOFFSETINV_MASK
#endif
| _ADC_CAL_SINGLEGAIN_MASK);
}
calReg = adc->CAL & mask;
newCal = 0;
switch (ref)
{
case adcRef1V25:
newCal |= ((DEVINFO->ADC0CAL0 & DEVINFO_ADC0_GAIN1V25_MASK)
>> DEVINFO_ADC0_GAIN1V25_SHIFT)
<< _ADC_CAL_SINGLEGAIN_SHIFT;
newCal |= ((DEVINFO->ADC0CAL0 & DEVINFO_ADC0_OFFSET1V25_MASK)
>> DEVINFO_ADC0_OFFSET1V25_SHIFT)
<< _ADC_CAL_SINGLEOFFSET_SHIFT;
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
newCal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_NEGSEOFFSET1V25_MASK)
>> _DEVINFO_ADC0CAL0_NEGSEOFFSET1V25_SHIFT)
<< _ADC_CAL_SINGLEOFFSETINV_SHIFT;
#endif
break;
case adcRef2V5:
newCal |= ((DEVINFO->ADC0CAL0 & DEVINFO_ADC0_GAIN2V5_MASK)
>> DEVINFO_ADC0_GAIN2V5_SHIFT)
<< _ADC_CAL_SINGLEGAIN_SHIFT;
newCal |= ((DEVINFO->ADC0CAL0 & DEVINFO_ADC0_OFFSET2V5_MASK)
>> DEVINFO_ADC0_OFFSET2V5_SHIFT)
<< _ADC_CAL_SINGLEOFFSET_SHIFT;
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
newCal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_NEGSEOFFSET2V5_MASK)
>> _DEVINFO_ADC0CAL0_NEGSEOFFSET2V5_SHIFT)
<< _ADC_CAL_SINGLEOFFSETINV_SHIFT;
#endif
break;
case adcRefVDD:
newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_GAINVDD_MASK)
>> DEVINFO_ADC0_GAINVDD_SHIFT)
<< _ADC_CAL_SINGLEGAIN_SHIFT;
newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_OFFSETVDD_MASK)
>> DEVINFO_ADC0_OFFSETVDD_SHIFT)
<< _ADC_CAL_SINGLEOFFSET_SHIFT;
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
newCal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_NEGSEOFFSETVDD_MASK)
>> _DEVINFO_ADC0CAL1_NEGSEOFFSETVDD_SHIFT)
<< _ADC_CAL_SINGLEOFFSETINV_SHIFT;
#endif
break;
case adcRef5VDIFF:
newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_GAIN5VDIFF_MASK)
>> DEVINFO_ADC0_GAIN5VDIFF_SHIFT)
<< _ADC_CAL_SINGLEGAIN_SHIFT;
newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_OFFSET5VDIFF_MASK)
>> DEVINFO_ADC0_OFFSET5VDIFF_SHIFT)
<< _ADC_CAL_SINGLEOFFSET_SHIFT;
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
newCal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_NEGSEOFFSET5VDIFF_MASK)
>> _DEVINFO_ADC0CAL1_NEGSEOFFSET5VDIFF_SHIFT)
<< _ADC_CAL_SINGLEOFFSETINV_SHIFT;
#endif
break;
case adcRef2xVDD:
/* There is no gain calibration for this reference */
newCal |= ((DEVINFO->ADC0CAL2 & DEVINFO_ADC0_OFFSET2XVDD_MASK)
>> DEVINFO_ADC0_OFFSET2XVDD_SHIFT)
<< _ADC_CAL_SINGLEOFFSET_SHIFT;
#if defined( _ADC_CAL_SINGLEOFFSETINV_MASK )
newCal |= ((DEVINFO->ADC0CAL2 & _DEVINFO_ADC0CAL2_NEGSEOFFSET2XVDD_MASK)
>> _DEVINFO_ADC0CAL2_NEGSEOFFSET2XVDD_SHIFT)
<< _ADC_CAL_SINGLEOFFSETINV_SHIFT;
#endif
break;
#if defined( _ADC_SINGLECTRLX_VREFSEL_VDDXWATT )
case adcRefVddxAtt:
newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_GAINVDD_MASK)
>> DEVINFO_ADC0_GAINVDD_SHIFT)
<< _ADC_CAL_SINGLEGAIN_SHIFT;
newCal |= ((DEVINFO->ADC0CAL1 & DEVINFO_ADC0_OFFSETVDD_MASK)
>> DEVINFO_ADC0_OFFSETVDD_SHIFT)
<< _ADC_CAL_SINGLEOFFSET_SHIFT;
newCal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_NEGSEOFFSETVDD_MASK)
>> _DEVINFO_ADC0CAL1_NEGSEOFFSETVDD_SHIFT)
<< _ADC_CAL_SINGLEOFFSETINV_SHIFT;
break;
#endif
/* For external references, the calibration must be determined for the
specific application and set by the user. Calibration data is also not
available for the internal references adcRefVBGR, adcRefVEntropy and
adcRefVBGRlow. */
default:
newCal = 0;
break;
}
adc->CAL = calReg | (newCal << shift);
}
/** @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 @ref ADC_InitSingle() and @ref ADC_InitScan() respectively.
* For ADC architectures with the ADCn->SCANINPUTSEL register, use
* @ref ADC_ScanSingleEndedInputAdd() to configure single-ended scan inputs or
* @ref ADC_ScanDifferentialInputAdd() to configure differential scan inputs.
* @ref ADC_ScanInputClear() is also provided for applications that need to update
* the input configuration.
*
* @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;
uint8_t presc = init->prescale;
EFM_ASSERT(ADC_REF_VALID(adc));
if (presc == 0)
{
/* Assume maximum ADC clock for prescaler 0 */
presc = ADC_PrescaleCalc(ADC_MAX_CLOCK, 0);
}
else
{
/* Check prescaler bounds against ADC_MAX_CLOCK and ADC_MIN_CLOCK */
#if defined(_ADC_CTRL_ADCCLKMODE_MASK)
if (ADC0->CTRL & ADC_CTRL_ADCCLKMODE_SYNC)
#endif
{
EFM_ASSERT(presc >= ADC_PrescaleCalc(ADC_MAX_CLOCK, 0));
EFM_ASSERT(presc <= ADC_PrescaleCalc(ADC_MIN_CLOCK, 0));
}
}
/* 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)(presc) << _ADC_CTRL_PRESC_SHIFT)
& _ADC_CTRL_PRESC_MASK)
#if defined ( _ADC_CTRL_LPFMODE_MASK )
| ((uint32_t)(init->lpfMode) << _ADC_CTRL_LPFMODE_SHIFT)
#endif
| ((uint32_t)(init->warmUpMode) << _ADC_CTRL_WARMUPMODE_SHIFT);
if (init->tailgate)
{
tmp |= ADC_CTRL_TAILGATE;
}
adc->CTRL = tmp;
/* Set ADC EM2 clock configuration */
#if defined( _ADC_CTRL_ADCCLKMODE_MASK )
BUS_RegMaskedWrite(&ADC0->CTRL,
_ADC_CTRL_ADCCLKMODE_MASK | _ADC_CTRL_ASYNCCLKEN_MASK,
init->em2ClockConfig);
#endif
#if defined( _SILICON_LABS_GECKO_INTERNAL_SDID_80 )
/* A debugger can trigger the SCANUF interrupt on EFM32xG1 or EFR32xG1 */
ADC_IntClear(adc, ADC_IFC_SCANUF);
#endif
}
#if defined( _ADC_SCANINPUTSEL_MASK )
/***************************************************************************//**
* @brief
* Clear ADC scan input configuration.
*
* @param[in] scanInit
* Struct to hold the scan configuration, input configuration.
******************************************************************************/
void ADC_ScanInputClear(ADC_InitScan_TypeDef *scanInit)
{
/* Clear input configuration */
/* Select none */
scanInit->scanInputConfig.scanInputSel = ADC_SCANINPUTSEL_NONE;
scanInit->scanInputConfig.scanInputEn = 0;
/* Default alternative negative inputs */
scanInit->scanInputConfig.scanNegSel = _ADC_SCANNEGSEL_RESETVALUE;
}
/***************************************************************************//**
* @brief
* Initialize ADC scan single-ended input configuration.
*
* @details
* Set configuration for ADC scan conversion with single-ended inputs. The
* ADC_InitScan_TypeDef struct updated from this function should be passed to
* ADC_InitScan().
*
* @param[in] inputGroup
* ADC scan input group. See section 25.3.4 in the reference manual for
* more information.
*
* @param[in] singleEndedSel
* APORT select.
*
* @return
* Scan ID of selected ADC input. ee section 25.3.4 in the reference manual for
* more information. Note that the returned integer represents the bit position
* in ADCn_SCANMASK set by this function. The accumulated mask is stored in
* scanInit->scanInputConfig->scanInputEn.
******************************************************************************/
uint32_t ADC_ScanSingleEndedInputAdd(ADC_InitScan_TypeDef *scanInit,
ADC_ScanInputGroup_TypeDef inputGroup,
ADC_PosSel_TypeDef singleEndedSel)
{
uint32_t currentSel;
uint32_t newSel;
uint32_t scanId;
scanInit->diff = false;
/* Check for unsupported APORTs */
EFM_ASSERT((singleEndedSel <= adcPosSelAPORT0YCH0) || (singleEndedSel >= adcPosSelAPORT0YCH15));
/* Decode the input group select by shifting right by 3 */
newSel = singleEndedSel >> 3;
currentSel = (scanInit->scanInputConfig.scanInputSel >> (inputGroup * 8)) & 0xFF;
/* If none selected */
if (currentSel == ADC_SCANINPUTSEL_GROUP_NONE)
{
scanInit->scanInputConfig.scanInputSel &= ~(0xFF << (inputGroup * 8));
scanInit->scanInputConfig.scanInputSel |= (newSel << (inputGroup * 8));
}
else if (currentSel == newSel)
{
/* Ok, but do nothing. */
}
else
{
/* Invalid channel range. A range is already selected for this group. */
EFM_ASSERT(false);
}
/* Update and return scan input enable mask (SCANMASK) */
scanId = (inputGroup * 8) + (singleEndedSel & 0x7);
EFM_ASSERT(scanId < 32);
scanInit->scanInputConfig.scanInputEn |= 0x1 << scanId;
return scanId;
}
/***************************************************************************//**
* @brief
* Initialize ADC scan differential input configuration.
*
* @details
* Set configuration for ADC scan conversion with differential inputs. The
* ADC_InitScan_TypeDef struct updated by this function should be passed to
* ADC_InitScan().
*
* @param[in] scanInit
* Struct to hold the scan and input configuration.
*
* @param[in] inputGroup
* ADC scan input group. See section 25.3.4 in the reference manual for
* more information.
*
* @param[in] posSel
* APORT bus pair select. The negative terminal is implicitly selected by
* the positive terminal.
*
* @param[in] negInput
* ADC scan alternative negative input. Set to adcScanNegInputDefault to select
* default negative input (implicit from posSel).
*
* @return
* Scan ID of selected ADC input. ee section 25.3.4 in the reference manual for
* more information. Note that the returned integer represents the bit position
* in ADCn_SCANMASK set by this function. The accumulated mask is stored in
* scanInit->scanInputConfig->scanInputEn.
******************************************************************************/
uint32_t ADC_ScanDifferentialInputAdd(ADC_InitScan_TypeDef *scanInit,
ADC_ScanInputGroup_TypeDef inputGroup,
ADC_PosSel_TypeDef posSel,
ADC_ScanNegInput_TypeDef negInput)
{
uint32_t negInputRegMask = 0;
uint32_t negInputRegShift = 0;
uint32_t negInputRegVal = 0;
uint32_t scanId = 0;
/* Do a single ended init, then update for differential scan. */
scanId = ADC_ScanSingleEndedInputAdd(scanInit, inputGroup, posSel);
/* Reset to differential mode */
scanInit->diff = true;
/* Set negative ADC input, unless the default is selected. */
if (negInput != adcScanNegInputDefault)
{
if (scanId == 0)
{
negInputRegMask = _ADC_SCANNEGSEL_INPUT0NEGSEL_MASK;
negInputRegShift = _ADC_SCANNEGSEL_INPUT0NEGSEL_SHIFT;
EFM_ASSERT(inputGroup == 0);
}
else if (scanId == 2)
{
negInputRegMask = _ADC_SCANNEGSEL_INPUT2NEGSEL_MASK;
negInputRegShift = _ADC_SCANNEGSEL_INPUT2NEGSEL_SHIFT;
EFM_ASSERT(inputGroup == 0);
}
else if (scanId == 4)
{
negInputRegMask = _ADC_SCANNEGSEL_INPUT4NEGSEL_MASK;
negInputRegShift = _ADC_SCANNEGSEL_INPUT4NEGSEL_SHIFT;
EFM_ASSERT(inputGroup == 0);
}
else if (scanId == 6)
{
negInputRegMask = _ADC_SCANNEGSEL_INPUT6NEGSEL_MASK;
negInputRegShift = _ADC_SCANNEGSEL_INPUT6NEGSEL_SHIFT;
EFM_ASSERT(inputGroup == 0);
}
else if (scanId == 9)
{
negInputRegMask = _ADC_SCANNEGSEL_INPUT9NEGSEL_MASK;
negInputRegShift = _ADC_SCANNEGSEL_INPUT9NEGSEL_SHIFT;
EFM_ASSERT(inputGroup == 1);
}
else if (scanId == 11)
{
negInputRegMask = _ADC_SCANNEGSEL_INPUT11NEGSEL_MASK;
negInputRegShift = _ADC_SCANNEGSEL_INPUT11NEGSEL_SHIFT;
EFM_ASSERT(inputGroup == 1);
}
else if (scanId == 13)
{
negInputRegMask = _ADC_SCANNEGSEL_INPUT13NEGSEL_MASK;
negInputRegShift = _ADC_SCANNEGSEL_INPUT13NEGSEL_SHIFT;
EFM_ASSERT(inputGroup == 1);
}
else if (scanId == 15)
{
negInputRegMask = _ADC_SCANNEGSEL_INPUT15NEGSEL_MASK;
negInputRegShift = _ADC_SCANNEGSEL_INPUT15NEGSEL_SHIFT;
EFM_ASSERT(inputGroup == 1);
}
else
{
/* There is not negative input option for this positive input (negInput is posInput + 1). */
EFM_ASSERT(false);
}
/* Find ADC_SCANNEGSEL_CHxNSEL value for positive input 0, 2, 4 and 6 */
if (inputGroup == 0)
{
switch (negInput)
{
case adcScanNegInput1:
negInputRegVal = _ADC_SCANNEGSEL_INPUT0NEGSEL_INPUT1;
break;
case adcScanNegInput3:
negInputRegVal = _ADC_SCANNEGSEL_INPUT0NEGSEL_INPUT3;
break;
case adcScanNegInput5:
negInputRegVal = _ADC_SCANNEGSEL_INPUT0NEGSEL_INPUT5;
break;
case adcScanNegInput7:
negInputRegVal = _ADC_SCANNEGSEL_INPUT0NEGSEL_INPUT7;
break;
default:
/* Invalid selection. Options are input 1, 3, 5 and 7. */
EFM_ASSERT(false);
break;
}
}
else if (inputGroup == 1)
{
/* Find ADC_SCANNEGSEL_CHxNSEL value for positive input 9, 11, 13 and 15 */
switch (negInput)
{
case adcScanNegInput8:
negInputRegVal = _ADC_SCANNEGSEL_INPUT9NEGSEL_INPUT8;
break;
case adcScanNegInput10:
negInputRegVal = _ADC_SCANNEGSEL_INPUT9NEGSEL_INPUT10;
break;
case adcScanNegInput12:
negInputRegVal = _ADC_SCANNEGSEL_INPUT9NEGSEL_INPUT12;
break;
case adcScanNegInput14:
negInputRegVal = _ADC_SCANNEGSEL_INPUT9NEGSEL_INPUT14;
break;
default:
/* Invalid selection. Options are input 8, 10, 12 and 14. */
EFM_ASSERT(false);
break;
}
}
else
{
/* No alternative negative input for input group > 1 */
EFM_ASSERT(false);
}
/* Update config */
scanInit->scanInputConfig.scanNegSel &= ~negInputRegMask;
scanInit->scanInputConfig.scanNegSel |= negInputRegVal << negInputRegShift;
}
return scanId;
}
#endif
/***************************************************************************//**
* @brief
* Initialize ADC scan sequence.
*
* @details
* Please refer to ADC_Start() 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.
* For ADC architectures with the ADCn->SCANINPUTSEL register, use
* @ref ADC_ScanSingleEndedInputAdd() to configure single-ended scan inputs or
* @ref ADC_ScanDifferentialInputAdd() to configure differential scan inputs.
* @ref ADC_ScanInputClear() is also provided for applications that need to update
* the input configuration.
*
* @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 calibration data for selected reference */
ADC_LoadDevinfoCal(adc, init->reference, true);
tmp = 0
#if defined ( _ADC_SCANCTRL_PRSSEL_MASK )
| (init->prsSel << _ADC_SCANCTRL_PRSSEL_SHIFT)
#endif
| (init->acqTime << _ADC_SCANCTRL_AT_SHIFT)
#if defined ( _ADC_SCANCTRL_INPUTMASK_MASK )
| init->input
#endif
| (init->resolution << _ADC_SCANCTRL_RES_SHIFT);
if (init->prsEnable)
{
tmp |= ADC_SCANCTRL_PRSEN;
}
if (init->leftAdjust)
{
tmp |= ADC_SCANCTRL_ADJ_LEFT;
}
#if defined( _ADC_SCANCTRL_INPUTMASK_MASK )
if (init->diff)
#elif defined( _ADC_SCANINPUTSEL_MASK )
if (init->diff)
#endif
{
tmp |= ADC_SCANCTRL_DIFF;
}
if (init->rep)
{
#if defined( _SILICON_LABS_GECKO_INTERNAL_SDID_80 )
/* Scan repeat mode does not work on EFM32JG1, EFM32PG1 or EFR32xG1x devices.
* The errata is called ADC_E211 in the errata document. */
EFM_ASSERT(false);
#endif
tmp |= ADC_SCANCTRL_REP;
}
/* Set scan reference. Check if reference configuraion is extended to SCANCTRLX. */
#if defined ( _ADC_SCANCTRLX_VREFSEL_MASK )
if (init->reference & ADC_CTRLX_VREFSEL_REG)
{
/* Select extension register */
tmp |= ADC_SCANCTRL_REF_CONF;
}
else
{
tmp |= init->reference << _ADC_SCANCTRL_REF_SHIFT;
}
#else
tmp |= init->reference << _ADC_SCANCTRL_REF_SHIFT;
#endif
#if defined( _ADC_SCANCTRL_INPUTMASK_MASK )
tmp |= init->input;
#endif
adc->SCANCTRL = tmp;
/* Update SINGLECTRLX for reference select and PRS select */
#if defined ( _ADC_SCANCTRLX_MASK )
tmp = adc->SCANCTRLX & ~(_ADC_SCANCTRLX_VREFSEL_MASK
| _ADC_SCANCTRLX_PRSSEL_MASK
| _ADC_SCANCTRLX_FIFOOFACT_MASK);
if (init->reference & ADC_CTRLX_VREFSEL_REG)
{
tmp |= (init->reference & ~ADC_CTRLX_VREFSEL_REG) << _ADC_SCANCTRLX_VREFSEL_SHIFT;
}
tmp |= init->prsSel << _ADC_SCANCTRLX_PRSSEL_SHIFT;
if (init->fifoOverwrite)
{
tmp |= ADC_SCANCTRLX_FIFOOFACT_OVERWRITE;
}
adc->SCANCTRLX = tmp;
#endif
#if defined( _ADC_CTRL_SCANDMAWU_MASK )
BUS_RegBitWrite(&adc->CTRL, _ADC_CTRL_SCANDMAWU_SHIFT, init->scanDmaEm2Wu);
#endif
/* Write scan input configuration */
#if defined( _ADC_SCANINPUTSEL_MASK )
/* Check for valid scan input configuration. Use @ref ADC_ScanInputClear()
@ref ADC_ScanSingleEndedInputAdd() and @ref ADC_ScanDifferentialInputAdd() to set
scan input configuration. */
EFM_ASSERT(init->scanInputConfig.scanInputSel != ADC_SCANINPUTSEL_NONE);
adc->SCANINPUTSEL = init->scanInputConfig.scanInputSel;
adc->SCANMASK = init->scanInputConfig.scanInputEn;
adc->SCANNEGSEL = init->scanInputConfig.scanNegSel;
#endif
/* Assert for any APORT bus conflicts programming errors */
#if defined( _ADC_BUSCONFLICT_MASK )
tmp = adc->BUSREQ;
EFM_ASSERT(!(tmp & adc->BUSCONFLICT));
EFM_ASSERT(!(adc->STATUS & _ADC_STATUS_PROGERR_MASK));
#endif
}
/***************************************************************************//**
* @brief
* Initialize single ADC sample conversion.
*
* @details
* Please refer to ADC_Start() 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.
*
* @cond DOXYDOC_P2_DEVICE
* @note
* This function will set the BIASPROG_GPBIASACC bit when selecting the
* internal temperature sensor and clear the bit otherwise. Any
* application that depends on the state of the BIASPROG_GPBIASACC bit should
* modify it after a call to this function.
* @endcond
*
* @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 calibration data for selected reference */
ADC_LoadDevinfoCal(adc, init->reference, false);
tmp = 0
#if defined( _ADC_SINGLECTRL_PRSSEL_MASK )
| (init->prsSel << _ADC_SINGLECTRL_PRSSEL_SHIFT)
#endif
| (init->acqTime << _ADC_SINGLECTRL_AT_SHIFT)
#if defined( _ADC_SINGLECTRL_INPUTSEL_MASK )
| (init->input << _ADC_SINGLECTRL_INPUTSEL_SHIFT)
#endif
#if defined( _ADC_SINGLECTRL_POSSEL_MASK )
| (init->posSel << _ADC_SINGLECTRL_POSSEL_SHIFT)
#endif
#if defined( _ADC_SINGLECTRL_NEGSEL_MASK )
| (init->negSel << _ADC_SINGLECTRL_NEGSEL_SHIFT)
#endif
| ((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;
}
#if defined( _ADC_SINGLECTRL_POSSEL_TEMP )
/* Force at least 8 cycle acquisition time when reading internal temperature
* sensor with 1.25V reference */
if ((init->posSel == adcPosSelTEMP)
&& (init->reference == adcRef1V25)
&& (init->acqTime < adcAcqTime8))
{
tmp = (tmp & ~_ADC_SINGLECTRL_AT_MASK)
| (adcAcqTime8 << _ADC_SINGLECTRL_AT_SHIFT);
}
#endif
/* Set single reference. Check if reference configuraion is extended to SINGLECTRLX. */
#if defined ( _ADC_SINGLECTRLX_MASK )
if (init->reference & ADC_CTRLX_VREFSEL_REG)
{
/* Select extension register */
tmp |= ADC_SINGLECTRL_REF_CONF;
}
else
{
tmp |= (init->reference << _ADC_SINGLECTRL_REF_SHIFT);
}
#else
tmp |= (init->reference << _ADC_SINGLECTRL_REF_SHIFT);
#endif
adc->SINGLECTRL = tmp;
/* Update SINGLECTRLX for reference select and PRS select */
#if defined ( _ADC_SINGLECTRLX_VREFSEL_MASK )
tmp = adc->SINGLECTRLX & (_ADC_SINGLECTRLX_VREFSEL_MASK
| _ADC_SINGLECTRLX_PRSSEL_MASK
| _ADC_SINGLECTRLX_FIFOOFACT_MASK);
if (init->reference & ADC_CTRLX_VREFSEL_REG)
{
tmp |= ((init->reference & ~ADC_CTRLX_VREFSEL_REG) << _ADC_SINGLECTRLX_VREFSEL_SHIFT);
}
tmp |= ((init->prsSel << _ADC_SINGLECTRLX_PRSSEL_SHIFT));
if (init->fifoOverwrite)
{
tmp |= ADC_SINGLECTRLX_FIFOOFACT_OVERWRITE;
}
adc->SINGLECTRLX = tmp;
#endif
/* Set DMA availability in EM2 */
#if defined( _ADC_CTRL_SINGLEDMAWU_MASK )
BUS_RegBitWrite(&adc->CTRL, _ADC_CTRL_SINGLEDMAWU_SHIFT, init->singleDmaEm2Wu);
#endif
#if defined( _ADC_BIASPROG_GPBIASACC_MASK ) && defined( FIX_ADC_TEMP_BIAS_EN )
if (init->posSel == adcPosSelTEMP)
{
/* ADC should always use low accuracy setting when reading the internal
* temperature sensor on platform 2 generation 1 devices. Using high
* accuracy setting can introduce a glitch. */
BUS_RegBitWrite(&adc->BIASPROG, _ADC_BIASPROG_GPBIASACC_SHIFT, 1);
}
else
{
BUS_RegBitWrite(&adc->BIASPROG, _ADC_BIASPROG_GPBIASACC_SHIFT, 0);
}
#endif
/* Assert for any APORT bus conflicts programming errors */
#if defined( _ADC_BUSCONFLICT_MASK )
tmp = adc->BUSREQ;
EFM_ASSERT(!(tmp & adc->BUSCONFLICT));
EFM_ASSERT(!(adc->STATUS & _ADC_STATUS_PROGERR_MASK));
#endif
}
#if defined( _ADC_SCANDATAX_MASK )
/***************************************************************************//**
* @brief
* Get scan result and scan select ID.
*
* @note
* Only use if scan data valid. This function does not check the DV flag.
* The return value is intended to be used as a index for the scan select ID.
*
* @param[in] adc
* Pointer to ADC peripheral register block.
*
* @param[out] scanId
* Scan select ID of first data in scan FIFO.
*
* @return
* First scan data in scan FIFO.
******************************************************************************/
uint32_t ADC_DataIdScanGet(ADC_TypeDef *adc, uint32_t *scanId)
{
uint32_t scanData;
/* Pop data FIFO with scan ID */
scanData = adc->SCANDATAX;
*scanId = (scanData & _ADC_SCANDATAX_SCANINPUTID_MASK) >> _ADC_SCANDATAX_SCANINPUTID_SHIFT;
return (scanData & _ADC_SCANDATAX_DATA_MASK) >> _ADC_SCANDATAX_DATA_SHIFT;
}
#endif
/***************************************************************************//**
* @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)
{
/* Stop conversions, before resetting other registers. */
adc->CMD = ADC_CMD_SINGLESTOP | ADC_CMD_SCANSTOP;
adc->SINGLECTRL = _ADC_SINGLECTRL_RESETVALUE;
#if defined( _ADC_SINGLECTRLX_MASK )
adc->SINGLECTRLX = _ADC_SINGLECTRLX_RESETVALUE;
#endif
adc->SCANCTRL = _ADC_SCANCTRL_RESETVALUE;
#if defined( _ADC_SCANCTRLX_MASK )
adc->SCANCTRLX = _ADC_SCANCTRLX_RESETVALUE;
#endif
adc->CTRL = _ADC_CTRL_RESETVALUE;
adc->IEN = _ADC_IEN_RESETVALUE;
adc->IFC = _ADC_IFC_MASK;
adc->BIASPROG = _ADC_BIASPROG_RESETVALUE;
#if defined( _ADC_SCANMASK_MASK )
adc->SCANMASK = _ADC_SCANMASK_RESETVALUE;
#endif
#if defined( _ADC_SCANINPUTSEL_MASK )
adc->SCANINPUTSEL = _ADC_SCANINPUTSEL_RESETVALUE;
#endif
#if defined( _ADC_SCANNEGSEL_MASK )
adc->SCANNEGSEL = _ADC_SCANNEGSEL_RESETVALUE;
#endif
/* Clear data FIFOs */
#if defined( _ADC_SINGLEFIFOCLEAR_MASK )
adc->SINGLEFIFOCLEAR |= ADC_SINGLEFIFOCLEAR_SINGLEFIFOCLEAR;
adc->SCANFIFOCLEAR |= ADC_SCANFIFOCLEAR_SCANFIFOCLEAR;
#endif
/* Load calibration values for the 1V25 internal reference. */
ADC_LoadDevinfoCal(adc, adcRef1V25, false);
ADC_LoadDevinfoCal(adc, adcRef1V25, true);
#if defined( _ADC_SCANINPUTSEL_MASK )
/* Do not reset route register, setting should be done independently */
#endif
}
/***************************************************************************//**
* @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;
}
}
#if defined( _EFM32_GIANT_FAMILY ) || defined( _EFM32_WONDER_FAMILY )
/* Handle errata on Giant Gecko, max TIMEBASE is 5 bits wide or max 0x1F */
/* cycles. This will give a warmp up time of e.g. 0.645us, not the */
/* required 1us when operating at 48MHz. One must also increase acqTime */
/* to compensate for the missing clock cycles, adding up to 1us in total.*/
/* See reference manual for details. */
if ( hfperFreq > 32000000 )
{
hfperFreq = 32000000;
}
#endif
/* 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 emlib) */
#endif /* defined(ADC_COUNT) && (ADC_COUNT > 0) */