/***************************************************************************//** * @file * @brief Incremental Analog to Digital Converter (IADC) Peripheral API ******************************************************************************* * # License * Copyright 2018 Silicon Laboratories Inc. www.silabs.com ******************************************************************************* * * SPDX-License-Identifier: Zlib * * The licensor of this software is Silicon Laboratories Inc. * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * 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. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 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. * ******************************************************************************/ #include "em_iadc.h" #if defined(IADC_COUNT) && (IADC_COUNT > 0) #include "sl_assert.h" #include "em_cmu.h" #include "sl_common.h" #include /***************************************************************************//** * @addtogroup emlib * @{ ******************************************************************************/ /***************************************************************************//** * @addtogroup iadc IADC - Incremental ADC * @brief Incremental Analog to Digital Converter (IADC) Peripheral API * @details * This module contains functions to control the IADC peripheral of Silicon * Labs 32-bit MCUs and SoCs. The IADC is used to convert analog signals into a * digital representation. * @{ ******************************************************************************/ /******************************************************************************* ******************************* DEFINES *********************************** ******************************************************************************/ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ // Validation of IADC register block pointer reference for assert statements. #if defined(IADC_NUM) #define IADC_REF_VALID(ref) (IADC_NUM(ref) != -1) #else #if (IADC_COUNT == 1) #define IADC_REF_VALID(ref) ((ref) == IADC0) #define IADC_NUM(ref) (((ref) == IADC0) ? 0 : -1) #elif (IADC_COUNT == 2) #define IADC_REF_VALID(ref) (((ref) == IADC0) || ((ref) == IADC1)) #define IADC_NUM(ref) (((ref) == IADC0) ? 0 : ((ref) == IADC1) ? 1 : -1) #endif #endif // Max IADC clock rates #define IADC_CLK_MAX_FREQ 40000000UL #define IADC_ANA_CLK_HIGH_SPEED_MAX_FREQ 20000000UL #define IADC_ANA_CLK_NORMAL_MAX_FREQ 10000000UL #define IADC_ANA_CLK_HIGH_ACCURACY_MAX_FREQ 5000000UL #if defined (_IADC_CFG_ADCMODE_HIGHSPEED) #define IADC_ANA_CLK_MAX_FREQ(adcMode) ( \ (adcMode) == iadcCfgModeNormal ? IADC_ANA_CLK_NORMAL_MAX_FREQ \ : ((adcMode) == iadcCfgModeHighSpeed \ ? IADC_ANA_CLK_HIGH_SPEED_MAX_FREQ \ : IADC_ANA_CLK_HIGH_ACCURACY_MAX_FREQ) \ ) #else #define IADC_ANA_CLK_MAX_FREQ(adcMode) ( \ (adcMode) == iadcCfgModeNormal ? IADC_ANA_CLK_NORMAL_MAX_FREQ \ : IADC_ANA_CLK_HIGH_ACCURACY_MAX_FREQ \ ) #endif #define IADC_ROUND_D2I(n) (int)((n) < 0.0f ? ((n) - 0.5f) : ((n) + 0.5f)) #define IADC0_SCANENTRIES IADC0_ENTRIES #define IADC0_FIFOENTRIES 0x4UL #define IADC1_SCANENTRIES IADC1_ENTRIES #define IADC1_FIFOENTRIES 0x4UL #if defined(IADC_ENTRIES) #define IADC_SCANENTRIES(iadc) IADC_ENTRIES(IADC_NUM(iadc)) #else #define IADC_SCANENTRIES(iadc) ( \ (iadc) == IADC0 ? IADC0_SCANENTRIES \ : 0UL) #endif #if !defined(IADC_CONFIGNUM) #define IADC_CONFIGNUM(iadc) ( \ (iadc) == 0 ? IADC0_CONFIGNUM \ : 0UL) #endif #define IADC_FIFOENTRIES(iadc) ( \ (iadc) == IADC0 ? IADC0_FIFOENTRIES \ : 0UL) #define IADC_CMU_CLOCK(iadc) ( \ (iadc) == IADC0 ? cmuClock_IADC0 \ : cmuClock_IADC0) /** @endcond */ /******************************************************************************* *************************** LOCAL FUNCTIONS ******************************* ******************************************************************************/ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ static void IADC_disable(IADC_TypeDef *iadc) { #if defined(IADC_STATUS_SYNCBUSY) while ((iadc->STATUS & IADC_STATUS_SYNCBUSY) != 0U) { // Wait for synchronization to finish before disable } #endif iadc->EN_CLR = IADC_EN_EN; #if defined(_IADC_EN_DISABLING_MASK) while (IADC0->EN & _IADC_EN_DISABLING_MASK) { } #endif } static void IADC_enable(IADC_TypeDef *iadc) { iadc->EN_SET = IADC_EN_EN; } static IADC_Result_t IADC_ConvertRawDataToResult(uint32_t rawData, IADC_Alignment_t alignment) { IADC_Result_t result; switch (alignment) { case iadcAlignRight12: #if defined(IADC_SINGLEFIFOCFG_ALIGNMENT_RIGHT16) case iadcAlignRight16: #endif #if defined(IADC_SINGLEFIFOCFG_ALIGNMENT_RIGHT20) case iadcAlignRight20: #endif // Mask out ID and replace with sign extension result.data = (rawData & 0x00FFFFFFUL) | ((rawData & 0x00800000UL) != 0x0UL ? 0xFF000000UL : 0x0UL); // Mask out data and shift down result.id = (uint8_t)((rawData & 0xFF000000UL) >> 24); break; case iadcAlignLeft12: #if defined(IADC_SINGLEFIFOCFG_ALIGNMENT_RIGHT16) case iadcAlignLeft16: #endif #if defined(IADC_SINGLEFIFOCFG_ALIGNMENT_RIGHT20) case iadcAlignLeft20: #endif result.data = rawData & 0xFFFFFF00UL; result.id = (uint8_t)(rawData & 0x000000FFUL); break; default: break; } return result; } /** @endcond */ /******************************************************************************* ************************** GLOBAL FUNCTIONS ******************************* ******************************************************************************/ /***************************************************************************//** * @brief * Initialize IADC. * * @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 IADC_initSingle() and @ref IADC_initScan() respectively. * * @note * This function will stop any ongoing conversions. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @param[in] init * Pointer to IADC initialization structure. * * @param[in] allConfigs * Pointer to structure holding all configs. ******************************************************************************/ void IADC_init(IADC_TypeDef *iadc, const IADC_Init_t *init, const IADC_AllConfigs_t *allConfigs) { uint32_t tmp; uint32_t config; uint16_t wantedPrescale; uint8_t srcClkPrescale; uint32_t adcClkPrescale; uint8_t timebase; unsigned uiAnaGain; uint16_t uiGainCAna; IADC_CfgAdcMode_t adcMode; #if defined(_IADC_CFG_ADCMODE_HIGHACCURACY) float anaGain; int anaGainRound; float offsetAna; float offset2; int offsetLong; int offsetAna1HiAccInt; uint8_t osrValue; float offsetAnaBase; float gainSysHiAcc; float refVoltage = 0; // Over sampling ratio for high accuracy conversions const float osrHiAcc[6] = { 16.0, 32.0, 64.0, 92.0, 128.0, 256.0 }; #endif EFM_ASSERT(IADC_REF_VALID(iadc)); // Calculate min allowed SRC_CLK prescaler setting srcClkPrescale = IADC_calcSrcClkPrescale(iadc, IADC_CLK_MAX_FREQ, 0); wantedPrescale = init->srcClkPrescale; // Use wanted SRC_CLK prescaler setting instead if it is high enough if (wantedPrescale >= srcClkPrescale) { srcClkPrescale = wantedPrescale; } IADC_disable(iadc); timebase = init->timebase; if (timebase == 0) { // CLK_SRC_ADC is derived from CLK_CMU_ADC, and must be no faster than 40 MHz. Therefore we set // srcClkFreq's original value to CLK_CMU_ADC before evaluating the prescaling conditions. uint32_t srcClkFreq = CMU_ClockFreqGet(cmuClock_IADC0); // If srcClkFreq is greater than 40MHz, then divide by the prescaler HSCLKRATE to obtain valid frequency if (srcClkFreq >= IADC_CLK_MAX_FREQ) { srcClkFreq = srcClkFreq / srcClkPrescale; } // Calculate timebase based on CMU_IADCCLKCTRL timebase = IADC_calcTimebase(iadc, srcClkFreq); } tmp = (((uint32_t)(init->warmup) << _IADC_CTRL_WARMUPMODE_SHIFT) & _IADC_CTRL_WARMUPMODE_MASK) | (((uint32_t)(timebase) << _IADC_CTRL_TIMEBASE_SHIFT) & _IADC_CTRL_TIMEBASE_MASK) | (((uint32_t)(srcClkPrescale) << _IADC_CTRL_HSCLKRATE_SHIFT) & _IADC_CTRL_HSCLKRATE_MASK); if (init->iadcClkSuspend0) { tmp |= IADC_CTRL_ADCCLKSUSPEND0; } if (init->iadcClkSuspend1) { tmp |= IADC_CTRL_ADCCLKSUSPEND1; } if (init->debugHalt) { tmp |= IADC_CTRL_DBGHALT; } iadc->CTRL = tmp; iadc->TIMER = ((uint32_t) (init->timerCycles) << _IADC_TIMER_TIMER_SHIFT) & _IADC_TIMER_TIMER_MASK; iadc->CMPTHR = (((uint32_t) (init->greaterThanEqualThres) << _IADC_CMPTHR_ADGT_SHIFT) & _IADC_CMPTHR_ADGT_MASK) | (((uint32_t) (init->lessThanEqualThres) << _IADC_CMPTHR_ADLT_SHIFT) & _IADC_CMPTHR_ADLT_MASK); // Write configurations for (config = 0; config < IADC_CONFIGNUM(IADC_NUM(iadc)); config++) { // Find min allowed ADC_CLK prescaler setting for given mode adcMode = allConfigs->configs[config].adcMode; wantedPrescale = allConfigs->configs[config].adcClkPrescale; adcClkPrescale = IADC_calcAdcClkPrescale(iadc, IADC_ANA_CLK_MAX_FREQ(adcMode), 0, adcMode, srcClkPrescale); // Use wanted ADC_CLK prescaler setting instead if it is high enough adcClkPrescale = SL_MAX(adcClkPrescale, wantedPrescale); tmp = iadc->CFG[config].CFG & ~(_IADC_CFG_ADCMODE_MASK | _IADC_CFG_OSRHS_MASK | _IADC_CFG_ANALOGGAIN_MASK | _IADC_CFG_REFSEL_MASK #if defined(_IADC_CFG_DIGAVG_MASK) | _IADC_CFG_DIGAVG_MASK #endif | _IADC_CFG_TWOSCOMPL_MASK #if defined(_IADC_CFG_ADCMODE_HIGHACCURACY) | _IADC_CFG_OSRHA_MASK #endif ); iadc->CFG[config].CFG = tmp | (((uint32_t)(adcMode) << _IADC_CFG_ADCMODE_SHIFT) & _IADC_CFG_ADCMODE_MASK) | (((uint32_t)(allConfigs->configs[config].osrHighSpeed) << _IADC_CFG_OSRHS_SHIFT) & _IADC_CFG_OSRHS_MASK) #if defined(_IADC_CFG_ADCMODE_HIGHACCURACY) | (((uint32_t)(allConfigs->configs[config].osrHighAccuracy) << _IADC_CFG_OSRHA_SHIFT) & _IADC_CFG_OSRHA_MASK) #endif | (((uint32_t)(allConfigs->configs[config].analogGain) << _IADC_CFG_ANALOGGAIN_SHIFT) & _IADC_CFG_ANALOGGAIN_MASK) | (((uint32_t)(allConfigs->configs[config].reference) << _IADC_CFG_REFSEL_SHIFT) & _IADC_CFG_REFSEL_MASK) #if defined(_IADC_CFG_DIGAVG_MASK) | (((uint32_t)(allConfigs->configs[config].digAvg) << _IADC_CFG_DIGAVG_SHIFT) & _IADC_CFG_DIGAVG_MASK) #endif | (((uint32_t)(allConfigs->configs[config].twosComplement) << _IADC_CFG_TWOSCOMPL_SHIFT) & _IADC_CFG_TWOSCOMPL_MASK); uiAnaGain = (iadc->CFG[config].CFG & _IADC_CFG_ANALOGGAIN_MASK) >> _IADC_CFG_ANALOGGAIN_SHIFT; switch (uiAnaGain) { #if defined(_IADC_CFG_ANALOGGAIN_ANAGAIN0P25) case iadcCfgAnalogGain0P25x: // 0.25x #endif case iadcCfgAnalogGain0P5x: // 0.5x case iadcCfgAnalogGain1x: // 1x uiGainCAna = (uint16_t)((DEVINFO->IADC0GAIN0 & _DEVINFO_IADC0GAIN0_GAINCANA1_MASK) >> _DEVINFO_IADC0GAIN0_GAINCANA1_SHIFT); break; case iadcCfgAnalogGain2x: // 2x uiGainCAna = (uint16_t)((DEVINFO->IADC0GAIN0 & _DEVINFO_IADC0GAIN0_GAINCANA2_MASK) >> _DEVINFO_IADC0GAIN0_GAINCANA2_SHIFT); break; case iadcCfgAnalogGain3x: // 3x uiGainCAna = (uint16_t)((DEVINFO->IADC0GAIN1 & _DEVINFO_IADC0GAIN1_GAINCANA3_MASK) >> _DEVINFO_IADC0GAIN1_GAINCANA3_SHIFT); break; case iadcCfgAnalogGain4x: // 4x uiGainCAna = (uint16_t)((DEVINFO->IADC0GAIN1 & _DEVINFO_IADC0GAIN1_GAINCANA4_MASK) >> _DEVINFO_IADC0GAIN1_GAINCANA4_SHIFT); break; default: // 1x uiGainCAna = (uint16_t)((DEVINFO->IADC0GAIN0 & _DEVINFO_IADC0GAIN0_GAINCANA1_MASK) >> _DEVINFO_IADC0GAIN0_GAINCANA1_SHIFT); break; } // Gain and offset correction is applied according to adcMode and oversampling rate. switch (adcMode) { float offset; uint32_t scale; int iOffset, iOsr; case iadcCfgModeNormal: #if defined(_IADC_CFG_ADCMODE_HIGHSPEED) case iadcCfgModeHighSpeed: #endif offset = 0.0f; if (uiAnaGain == iadcCfgAnalogGain2x) { if (adcMode == iadcCfgModeNormal) { offset = (int16_t)(DEVINFO->IADC0NORMALOFFSETCAL0 >> _DEVINFO_IADC0NORMALOFFSETCAL0_OFFSETANA2NORM_SHIFT); } else { offset = (int16_t)(DEVINFO->IADC0HISPDOFFSETCAL0 >> _DEVINFO_IADC0HISPDOFFSETCAL0_OFFSETANA2HISPD_SHIFT); } } else if (uiAnaGain == iadcCfgAnalogGain3x) { if (adcMode == iadcCfgModeNormal) { offset = (int16_t)(DEVINFO->IADC0NORMALOFFSETCAL0 >> _DEVINFO_IADC0NORMALOFFSETCAL0_OFFSETANA2NORM_SHIFT) * 2; } else { offset = (int16_t)(DEVINFO->IADC0HISPDOFFSETCAL0 >> _DEVINFO_IADC0HISPDOFFSETCAL0_OFFSETANA2HISPD_SHIFT) * 2; } } else if (uiAnaGain == iadcCfgAnalogGain4x) { if (adcMode == iadcCfgModeNormal) { offset = (int16_t)(DEVINFO->IADC0NORMALOFFSETCAL0 >> _DEVINFO_IADC0NORMALOFFSETCAL0_OFFSETANA2NORM_SHIFT) * 3; } else { offset = (int16_t)(DEVINFO->IADC0HISPDOFFSETCAL0 >> _DEVINFO_IADC0HISPDOFFSETCAL0_OFFSETANA2HISPD_SHIFT) * 3; } } // Set correct gain correction bitfields in scale variable. tmp = (uint32_t)uiGainCAna & 0x9FFFU; scale = tmp << _IADC_SCALE_GAIN13LSB_SHIFT; if ((tmp & 0x8000U) != 0U) { scale |= IADC_SCALE_GAIN3MSB; } // Adjust offset according to selected OSR. iOsr = 1U << (((iadc->CFG[config].CFG & _IADC_CFG_OSRHS_MASK) >> _IADC_CFG_OSRHS_SHIFT) + 1U); if (iOsr == 2) { if (adcMode == iadcCfgModeNormal) { offset += (int16_t)(DEVINFO->IADC0NORMALOFFSETCAL0 & _DEVINFO_IADC0NORMALOFFSETCAL0_OFFSETANA1NORM_MASK); } else { offset += (int16_t)(DEVINFO->IADC0HISPDOFFSETCAL0 & _DEVINFO_IADC0HISPDOFFSETCAL0_OFFSETANA1HISPD_MASK); } } else { if (adcMode == iadcCfgModeNormal) { offset = (int16_t)(DEVINFO->IADC0NORMALOFFSETCAL1 & _DEVINFO_IADC0NORMALOFFSETCAL1_OFFSETANA3NORM_MASK) - offset; } else { offset += (int16_t)(DEVINFO->IADC0HISPDOFFSETCAL1 & _DEVINFO_IADC0HISPDOFFSETCAL1_OFFSETANA3HISPD_MASK) - offset; } offset /= iOsr / 2.0f; offset += (int16_t)(DEVINFO->IADC0OFFSETCAL0 & _DEVINFO_IADC0OFFSETCAL0_OFFSETANABASE_MASK); } // Compensate offset according to selected reference voltage. if (allConfigs->configs[config].reference == iadcCfgReferenceInt1V2) { // Internal reference voltage (VBGR) depends on the chip revision. offset *= 1.25f / (IADC_getReferenceVoltage(allConfigs->configs[config].reference) / 1000.0f); } else { offset *= 1.25f / (allConfigs->configs[config].vRef / 1000.0f); } // Compensate offset for systematic offset. offset = (offset * 4.0f) + (640.0f * (256.0f / iOsr)); // Apply gain error correction. if (scale != 0x80000000U) { offset = (uiGainCAna / 32768.0f) * (offset + 524288.0f) - 524288.0f; } iOffset = IADC_ROUND_D2I(-offset); // We only have 18 bits available for OFFSET in SCALE register. // OFFSET is a 2nd complement number. if (iOffset > 131071) { // Positive overflow at 0x0001FFFF ? scale |= 0x1FFFFU; } else if (iOffset < -131072) { // Negative overflow at 0xFFFE0000 ? scale |= 0x20000U; } else { scale |= (uint32_t)iOffset & 0x3FFFFU; } iadc->CFG[config].SCALE = scale; break; #if defined(_IADC_CFG_ADCMODE_HIGHACCURACY) case iadcCfgModeHighAccuracy: // Get reference voltage in volts refVoltage = IADC_getReferenceVoltage(allConfigs->configs[config].reference) / 1000.0f; // Get OSR from config register osrValue = (iadc->CFG[config].CFG & _IADC_CFG_OSRHA_MASK) >> _IADC_CFG_OSRHA_SHIFT; // 1. Calculate gain correction if ((uint32_t)osrHiAcc[osrValue] == 92U) { // for OSR = 92, gainSysHiAcc = 0.957457 gainSysHiAcc = 0.957457; } else { // for OSR != 92, gainSysHiAcc = OSR/(OSR + 1) gainSysHiAcc = osrHiAcc[osrValue] / (osrHiAcc[osrValue] + 1.0f); } anaGain = (float) uiGainCAna / 32768.0f * gainSysHiAcc; anaGainRound = IADC_ROUND_D2I(32768.0f * anaGain); IADC0->CFG[config].SCALE &= ~_IADC_SCALE_MASK; // Write GAIN3MSB if ((uint32_t)anaGainRound & 0x8000) { IADC0->CFG[config].SCALE |= IADC_SCALE_GAIN3MSB_GAIN100; } else { IADC0->CFG[config].SCALE |= IADC_SCALE_GAIN3MSB_GAIN011; } // Write GAIN13LSB IADC0->CFG[config].SCALE |= ((uint32_t)anaGainRound & 0x1FFF) << _IADC_SCALE_GAIN13LSB_SHIFT; // Get offset value for high accuracy mode from DEVINFO offsetAna1HiAccInt = (uint16_t)(DEVINFO->IADC0OFFSETCAL0 & _DEVINFO_IADC0OFFSETCAL0_OFFSETANA1HIACC_MASK) >> _DEVINFO_IADC0OFFSETCAL0_OFFSETANA1HIACC_SHIFT; // 2. OSR adjustment // Get offset from DEVINFO offsetAnaBase = (int16_t)(DEVINFO->IADC0OFFSETCAL0 & _DEVINFO_IADC0OFFSETCAL0_OFFSETANABASE_MASK) >> _DEVINFO_IADC0OFFSETCAL0_OFFSETANABASE_SHIFT; // 1 << osrValue is the same as pow(2, osrValue) offsetAna = offsetAnaBase + (offsetAna1HiAccInt) / (1 << osrValue); // 3. Reference voltage adjustment offsetAna = (offsetAna) * (1.25f / refVoltage); // 4. Calculate final offset offset2 = 262144.0f / osrHiAcc[osrValue] / (osrHiAcc[osrValue] + 1.0f) + offsetAna * 4.0f + 524288.0f; offset2 = (uiGainCAna / 32768.0f * (-1.0f)) * offset2 + 524288.0f; offsetLong = IADC_ROUND_D2I(offset2); // 5. Write offset to scale register IADC0->CFG[config].SCALE |= (uint32_t)(offsetLong & _IADC_SCALE_OFFSET_MASK); break; #endif default: // Mode not supported. EFM_ASSERT(false); break; } iadc->CFG[config].SCHED = ((adcClkPrescale << _IADC_SCHED_PRESCALE_SHIFT) & _IADC_SCHED_PRESCALE_MASK); } IADC_enable(iadc); } /***************************************************************************//** * @brief * Initialize IADC scan sequence. * * @details * This function will configure scan mode and set up entries in the scan * table. The scan table mask can be updated by calling IADC_updateScanMask. * * @note * This function will stop any ongoing conversions. * * @note If an even numbered pin is selected for the positive input, the * negative input must use an odd numbered pin and vice versa. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @param[in] init * Pointer to IADC initialization structure. * * @param[in] scanTable * Pointer to IADC scan table structure. ******************************************************************************/ void IADC_initScan(IADC_TypeDef *iadc, const IADC_InitScan_t *init, const IADC_ScanTable_t *scanTable) { uint32_t i; uint32_t tmp; EFM_ASSERT(IADC_REF_VALID(iadc)); #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_3) // Errata IADC_E305. Makes sure that DVL is equal or less than 7 entries. EFM_ASSERT(init->dataValidLevel <= iadcFifoCfgDvl7); #endif IADC_disable(iadc); iadc->SCANFIFOCFG = (((uint32_t) (init->alignment) << _IADC_SCANFIFOCFG_ALIGNMENT_SHIFT) & _IADC_SCANFIFOCFG_ALIGNMENT_MASK) | (init->showId ? IADC_SCANFIFOCFG_SHOWID : 0UL) | (((uint32_t) (init->dataValidLevel) << _IADC_SCANFIFOCFG_DVL_SHIFT) & _IADC_SCANFIFOCFG_DVL_MASK) | (init->fifoDmaWakeup ? IADC_SCANFIFOCFG_DMAWUFIFOSCAN : 0UL); // Clear bitfields for scan conversion in IADCn->TRIGGER and set new values iadc->TRIGGER = (iadc->TRIGGER & ~(_IADC_TRIGGER_SCANTRIGSEL_MASK | _IADC_TRIGGER_SCANTRIGACTION_MASK)) | (((uint32_t) (init->triggerSelect) << _IADC_TRIGGER_SCANTRIGSEL_SHIFT) & _IADC_TRIGGER_SCANTRIGSEL_MASK) | (((uint32_t) (init->triggerAction) << _IADC_TRIGGER_SCANTRIGACTION_SHIFT) & _IADC_TRIGGER_SCANTRIGACTION_MASK); // Write scan table for (i = 0; i < IADC_SCANENTRIES(iadc); i++) { iadc->SCANTABLE[i].SCAN = (((uint32_t) (scanTable->entries[i].negInput) << _IADC_SCAN_PINNEG_SHIFT) & (_IADC_SCAN_PORTNEG_MASK | _IADC_SCAN_PINNEG_MASK)) | (((uint32_t) (scanTable->entries[i].posInput) << _IADC_SCAN_PINPOS_SHIFT) & (_IADC_SCAN_PORTPOS_MASK | _IADC_SCAN_PINPOS_MASK)) | (((uint32_t) (scanTable->entries[i].configId) << _IADC_SCAN_CFG_SHIFT) & _IADC_SCAN_CFG_MASK) | (scanTable->entries[i].compare ? IADC_SCAN_CMP : 0UL); } IADC_enable(iadc); // Set scan mask tmp = 0; for (i = 0; i < IADC_SCANENTRIES(iadc); i++) { if (scanTable->entries[i].includeInScan) { tmp |= (1UL << i) << _IADC_MASKREQ_MASKREQ_SHIFT; } } iadc->MASKREQ = tmp; if (init->start) { IADC_command(iadc, iadcCmdStartScan); } } /***************************************************************************//** * @brief * Initialize single IADC conversion. * * @details * This function will initialize the single conversion and configure the * single input selection. * * @note * This function will stop any ongoing conversions. * * @note If an even numbered pin is selected for the positive input, the * negative input must use an odd numbered pin and vice versa. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @param[in] init * Pointer to IADC single initialization structure. * * @param[in] input * Pointer to IADC single input selection initialization structure. ******************************************************************************/ void IADC_initSingle(IADC_TypeDef *iadc, const IADC_InitSingle_t *init, const IADC_SingleInput_t *input) { EFM_ASSERT(IADC_REF_VALID(iadc)); #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_3) // Errata IADC_E305. Makes sure that DVL is equal or less than 7 entries. EFM_ASSERT(init->dataValidLevel <= iadcFifoCfgDvl7); #endif IADC_disable(iadc); iadc->SINGLEFIFOCFG = (((uint32_t) (init->alignment) << _IADC_SINGLEFIFOCFG_ALIGNMENT_SHIFT) & _IADC_SINGLEFIFOCFG_ALIGNMENT_MASK) | (init->showId ? IADC_SINGLEFIFOCFG_SHOWID : 0UL) | (((uint32_t) (init->dataValidLevel) << _IADC_SINGLEFIFOCFG_DVL_SHIFT) & _IADC_SINGLEFIFOCFG_DVL_MASK) | (init->fifoDmaWakeup ? IADC_SINGLEFIFOCFG_DMAWUFIFOSINGLE : 0UL); // Clear bitfields for single conversion in IADCn->TRIGGER and set new values iadc->TRIGGER = (iadc->TRIGGER & ~(_IADC_TRIGGER_SINGLETRIGSEL_MASK | _IADC_TRIGGER_SINGLETRIGACTION_MASK | _IADC_TRIGGER_SINGLETAILGATE_MASK)) | (((uint32_t) (init->triggerSelect) << _IADC_TRIGGER_SINGLETRIGSEL_SHIFT) & _IADC_TRIGGER_SINGLETRIGSEL_MASK) | (((uint32_t) (init->triggerAction) << _IADC_TRIGGER_SINGLETRIGACTION_SHIFT) & _IADC_TRIGGER_SINGLETRIGACTION_MASK) | (init->singleTailgate ? IADC_TRIGGER_SINGLETAILGATE : 0UL); IADC_updateSingleInput(iadc, input); IADC_enable(iadc); if (init->start) { IADC_command(iadc, iadcCmdStartSingle); } } /***************************************************************************//** * @brief * Update IADC single input selection. * * @details * This function updates the single input selection. The function can be * called while single and/or scan conversions are ongoing and the new input * configuration will take place on the next single conversion. * * @note If an even numbered pin is selected for the positive input, the * negative input must use an odd numbered pin and vice versa. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @param[in] input * Pointer to single input selection structure. ******************************************************************************/ void IADC_updateSingleInput(IADC_TypeDef *iadc, const IADC_SingleInput_t *input) { bool enabled; EFM_ASSERT(IADC_REF_VALID(iadc)); enabled = (iadc->EN & IADC_EN_EN) != 0UL; // IADCn->SINGLE has WSYNC type and can only be written while enabled IADC_enable(iadc); iadc->SINGLE = (((uint32_t) (input->negInput) << _IADC_SINGLE_PINNEG_SHIFT) & (_IADC_SINGLE_PORTNEG_MASK | _IADC_SINGLE_PINNEG_MASK)) | (((uint32_t) (input->posInput) << _IADC_SINGLE_PINPOS_SHIFT) & (_IADC_SINGLE_PORTPOS_MASK | _IADC_SINGLE_PINPOS_MASK)) | (((uint32_t) (input->configId) << _IADC_SINGLE_CFG_SHIFT) & _IADC_SINGLE_CFG_MASK) | (input->compare ? IADC_SINGLE_CMP : 0UL); // Restore enabled state if (!enabled) { IADC_disable(iadc); } } /***************************************************************************//** * @brief * Set mask of IADC scan table entries to include in scan. * * @details * Set mask of scan table entries to include in next scan. This function * can be called while scan conversions are ongoing, but the new scan mask * will take effect once the ongoing scan is completed. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @param[in] mask * Mask of scan table entries to include in scan. ******************************************************************************/ void IADC_setScanMask(IADC_TypeDef *iadc, uint32_t mask) { bool enabled; EFM_ASSERT(IADC_REF_VALID(iadc)); EFM_ASSERT(mask <= ((1UL << IADC_SCANENTRIES(iadc)) - 1UL)); enabled = (iadc->EN & IADC_EN_EN) != 0UL; // IADC must be enabled to update scan table mask IADC_enable(iadc); iadc->MASKREQ = (mask << _IADC_MASKREQ_MASKREQ_SHIFT) & _IADC_MASKREQ_MASKREQ_MASK; // Restore enabled state if (!enabled) { IADC_disable(iadc); } } /***************************************************************************//** * @brief * Add/update entry in scan table. * * @details * This function will update or add an entry in the scan table with a specific * ID. * * @note * This function will stop any ongoing conversions. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @param[in] id * ID of scan table entry to add. * * @param[in] entry * Pointer to scan table entry structure. ******************************************************************************/ void IADC_updateScanEntry(IADC_TypeDef *iadc, uint8_t id, IADC_ScanTableEntry_t *entry) { bool enabled; EFM_ASSERT(IADC_REF_VALID(iadc)); enabled = (iadc->EN & IADC_EN_EN) != 0UL; // IADC must be disabled to update scan table IADC_disable(iadc); // Update entry in scan table iadc->SCANTABLE[id].SCAN = (((uint32_t) (entry->negInput) << _IADC_SCAN_PINNEG_SHIFT) & (_IADC_SCAN_PORTNEG_MASK | _IADC_SCAN_PINNEG_MASK)) | (((uint32_t) (entry->posInput) << _IADC_SCAN_PINPOS_SHIFT) & (_IADC_SCAN_PORTPOS_MASK | _IADC_SCAN_PINPOS_MASK)) | (((uint32_t) (entry->configId) << _IADC_SCAN_CFG_SHIFT) & _IADC_SCAN_CFG_MASK) | (entry->compare ? IADC_SCAN_CMP : 0UL); // IADC must be enabled to update scan table mask IADC_enable(iadc); if (entry->includeInScan) { iadc->MASKREQ_SET = (1UL << (id & 0x1FUL)) << _IADC_MASKREQ_MASKREQ_SHIFT; } else { iadc->MASKREQ_CLR = (1UL << (id & 0x1FUL)) << _IADC_MASKREQ_MASKREQ_SHIFT; } // Restore enabled state if (!enabled) { IADC_disable(iadc); } } /***************************************************************************//** * @brief * Reset IADC to same state as after a HW reset. * * @param[in] iadc * Pointer to IADC peripheral register block. ******************************************************************************/ void IADC_reset(IADC_TypeDef *iadc) { uint32_t i; EFM_ASSERT(IADC_REF_VALID(iadc)); // Write all WSYNC registers to reset value while enabled IADC_enable(iadc); // Stop conversions and timer, before resetting other registers. iadc->CMD = IADC_CMD_SINGLESTOP | IADC_CMD_SCANSTOP | IADC_CMD_TIMERDIS; // Wait for all IADC operations to stop while ((iadc->STATUS & (IADC_STATUS_CONVERTING | IADC_STATUS_SCANQUEUEPENDING | IADC_STATUS_SINGLEQUEUEPENDING | IADC_STATUS_TIMERACTIVE)) != 0UL) { } // Reset all WSYNC registers iadc->MASKREQ = _IADC_MASKREQ_RESETVALUE; iadc->SINGLE = _IADC_SINGLE_RESETVALUE; // Wait for SINGLE and MASQREQ writes to propagate to working registers while ((iadc->STATUS & (IADC_STATUS_MASKREQWRITEPENDING | IADC_STATUS_SINGLEWRITEPENDING)) != 0UL) { } // Pull from FIFOs until they are empty // Errata IADC_E305: Check SINGLEFIFOSTAT to make sure that SINGLEFIFO is getting emptied in case // where STATUS register is incorrect. while (((iadc->STATUS & IADC_STATUS_SINGLEFIFODV) != 0UL) || (iadc->SINGLEFIFOSTAT > 0)) { (void) IADC_pullSingleFifoData(iadc); } // Errata IADC_E305: check SCANFIFOSTAT to make sure that SCANFIFO is getting emptied in case // where STATUS register is incorrect. while (((iadc->STATUS & IADC_STATUS_SCANFIFODV) != 0UL) || (iadc->SCANFIFOSTAT > 0)) { (void) IADC_pullScanFifoData(iadc); } // Read data registers to clear data valid flags (void) IADC_readSingleData(iadc); (void) IADC_readScanData(iadc); // Write all WSTATIC registers to reset value while disabled IADC_disable(iadc); // Reset all WSTATIC registers iadc->CTRL = _IADC_CTRL_RESETVALUE; iadc->TIMER = _IADC_TIMER_RESETVALUE; iadc->TRIGGER = _IADC_TRIGGER_RESETVALUE; iadc->CMPTHR = _IADC_CMPTHR_RESETVALUE; iadc->SINGLEFIFOCFG = _IADC_SINGLEFIFOCFG_RESETVALUE; iadc->SCANFIFOCFG = _IADC_SCANFIFOCFG_RESETVALUE; for (i = 0; i < IADC_CONFIGNUM(IADC_NUM(iadc)); i++) { iadc->CFG[i].CFG = _IADC_CFG_RESETVALUE; iadc->CFG[i].SCALE = _IADC_SCALE_RESETVALUE; iadc->CFG[i].SCHED = _IADC_SCHED_RESETVALUE; } for (i = 0; i < IADC_SCANENTRIES(iadc); i++) { iadc->SCANTABLE[i].SCAN = _IADC_SCAN_RESETVALUE; } // Clear interrupt flags and disable interrupts IADC_clearInt(iadc, _IADC_IF_MASK); IADC_disableInt(iadc, _IADC_IEN_MASK); } /***************************************************************************//** * @brief * Calculate timebase value in order to get a timebase providing at least 1us. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @param[in] srcClkFreq Frequency in Hz of reference CLK_SRC_ADC clock. Set to 0 to * derive srcClkFreq from CLK_CMU_ADC and prescaler HSCLKRATE. * * @return * Timebase value to use for IADC in order to achieve at least 1 us. ******************************************************************************/ uint8_t IADC_calcTimebase(IADC_TypeDef *iadc, uint32_t srcClkFreq) { EFM_ASSERT(IADC_REF_VALID(iadc)); if (srcClkFreq == 0UL) { // CLK_SRC_ADC is derived from CLK_CMU_ADC, and must be no faster than 40 MHz. Therefore we set // srcClkFreq's original value to CLK_CMU_ADC before evaluating the prescaling conditions. srcClkFreq = CMU_ClockFreqGet(cmuClock_IADC0); // Just in case, make sure we get non-zero frequency for below calculation if (srcClkFreq == 0UL) { srcClkFreq = 1; } // If srcClkFreq is greater than 40MHz, then divide by the prescaler HSCLKRATE if (srcClkFreq > IADC_CLK_MAX_FREQ) { uint32_t prescaler = (uint32_t)(IADC0->CTRL & _IADC_CTRL_HSCLKRATE_MASK) >> _IADC_CTRL_HSCLKRATE_SHIFT; srcClkFreq /= (prescaler + 1); } } // Determine number of ADCCLK cycle >= 1us srcClkFreq += 999999UL; srcClkFreq /= 1000000UL; // Convert to N+1 format srcClkFreq -= 1UL; // Limit to max allowed register setting srcClkFreq = SL_MIN(srcClkFreq, (_IADC_CTRL_TIMEBASE_MASK >> _IADC_CTRL_TIMEBASE_SHIFT)); // Return timebase value return (uint8_t) srcClkFreq; } /***************************************************************************//** * @brief * Calculate prescaler for CLK_SRC_ADC high speed clock * * @details * The IADC high speed clock is given by: CLK_SRC_ADC / (srcClkPrescaler + 1). * * @param[in] iadc * Pointer to IADC peripheral register block. * * @param[in] srcClkFreq CLK_SRC_ADC frequency wanted. The frequency will * automatically be adjusted to be within valid range according to reference * manual. * * @param[in] cmuClkFreq Frequency in Hz of reference CLK_CMU_ADC. Set to 0 * to use currently defined CMU clock setting for the IADC. * * @return * Divider value to use for IADC in order to achieve a high speed clock value * <= @p srcClkFreq. ******************************************************************************/ uint8_t IADC_calcSrcClkPrescale(IADC_TypeDef *iadc, uint32_t srcClkFreq, uint32_t cmuClkFreq) { uint32_t ret; EFM_ASSERT(IADC_REF_VALID(iadc)); EFM_ASSERT(srcClkFreq); // Make sure wanted CLK_SRC_ADC clock is below max allowed frequency srcClkFreq = SL_MIN(srcClkFreq, IADC_CLK_MAX_FREQ); // Use current CLK_CMU_ADC frequency? if (cmuClkFreq == 0UL) { cmuClkFreq = CMU_ClockFreqGet(IADC_CMU_CLOCK(iadc)); } ret = (cmuClkFreq + srcClkFreq - 1UL) / srcClkFreq; if (ret != 0UL) { ret--; } // Limit to max allowed register setting if (ret > _IADC_CTRL_HSCLKRATE_DIV4) { ret = _IADC_CTRL_HSCLKRATE_DIV4; } return (uint8_t)ret; } /***************************************************************************//** * @brief * Calculate prescaler for ADC_CLK clock. * * @details * The ADC_CLK is given by: CLK_SRC_ADC / (adcClkprescale + 1). * * @param[in] iadc * Pointer to IADC peripheral register block. * * @param[in] adcClkFreq ADC_CLK frequency wanted. The frequency will * automatically be adjusted to be within valid range according to reference * manual. * * @param[in] cmuClkFreq Frequency in Hz of CLK_CMU_ADC Set to 0 to * use currently defined IADC clock setting (in CMU). * * @param[in] adcMode Mode for IADC config. * * @param[in] srcClkPrescaler Precaler setting for ADC_CLK * * @return * Divider value to use for IADC in order to achieve a ADC_CLK frequency * <= @p adcClkFreq. ******************************************************************************/ uint32_t IADC_calcAdcClkPrescale(IADC_TypeDef *iadc, uint32_t adcClkFreq, uint32_t cmuClkFreq, IADC_CfgAdcMode_t adcMode, uint8_t srcClkPrescaler) { uint32_t ret; uint32_t resFreq; EFM_ASSERT(IADC_REF_VALID(iadc)); EFM_ASSERT(adcClkFreq); // Make sure wanted analog clock is below max allowed frequency for the given // mode. if (adcClkFreq > IADC_ANA_CLK_MAX_FREQ(adcMode)) { adcClkFreq = IADC_ANA_CLK_MAX_FREQ(adcMode); } // Use current CLK_CMU_ADC frequency? if (cmuClkFreq == 0UL) { resFreq = CMU_ClockFreqGet(IADC_CMU_CLOCK(iadc)); } else { resFreq = cmuClkFreq; } // Apply CLK_SRC_ADC prescaler resFreq /= srcClkPrescaler + 1UL; ret = (resFreq + adcClkFreq - 1UL) / adcClkFreq; if (ret != 0UL) { ret--; } // Limit to max allowed register setting ret = SL_MIN(ret, (_IADC_SCHED_PRESCALE_MASK >> _IADC_SCHED_PRESCALE_SHIFT)); return (uint16_t)ret; } /***************************************************************************//** * @brief * Pull result from single data FIFO. The result struct includes both the data * and the ID (0x20) if showId was set when initializing single mode. * * @note * Check data valid flag before calling this function. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @return * Single conversion result struct holding data and id. ******************************************************************************/ IADC_Result_t IADC_pullSingleFifoResult(IADC_TypeDef *iadc) { uint32_t alignment = (iadc->SINGLEFIFOCFG & _IADC_SINGLEFIFOCFG_ALIGNMENT_MASK) >> _IADC_SINGLEFIFOCFG_ALIGNMENT_SHIFT; return IADC_ConvertRawDataToResult(iadc->SINGLEFIFODATA, (IADC_Alignment_t) alignment); } /***************************************************************************//** * @brief * Read most recent single conversion result. The result struct includes both * the data and the ID (0x20) if showId was set when initializing single mode. * Calling this function will not affect the state of the single data FIFO. * * @note * Check data valid flag before calling this function. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @return * Single conversion result struct holding data and id. ******************************************************************************/ IADC_Result_t IADC_readSingleResult(IADC_TypeDef *iadc) { uint32_t alignment = (iadc->SINGLEFIFOCFG & _IADC_SINGLEFIFOCFG_ALIGNMENT_MASK) >> _IADC_SINGLEFIFOCFG_ALIGNMENT_SHIFT; return IADC_ConvertRawDataToResult(iadc->SINGLEDATA, (IADC_Alignment_t) alignment); } /***************************************************************************//** * @brief * Pull result from scan data FIFO. The result struct includes both the data * and the ID (0x20) if showId was set when initializing scan entry. * * @note * Check data valid flag before calling this function. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @return * Scan conversion result struct holding data and id. ******************************************************************************/ IADC_Result_t IADC_pullScanFifoResult(IADC_TypeDef *iadc) { uint32_t alignment = (iadc->SCANFIFOCFG & _IADC_SCANFIFOCFG_ALIGNMENT_MASK) >> _IADC_SCANFIFOCFG_ALIGNMENT_SHIFT; return IADC_ConvertRawDataToResult(iadc->SCANFIFODATA, (IADC_Alignment_t) alignment); } /***************************************************************************//** * @brief * Read most recent scan conversion result. The result struct includes both * the data and the ID (0x20) if showId was set when initializing scan entry. * Calling this function will not affect the state of the scan data FIFO. * * @note * Check data valid flag before calling this function. * * @param[in] iadc * Pointer to IADC peripheral register block. * * @return * Scan conversion result struct holding data and id. ******************************************************************************/ IADC_Result_t IADC_readScanResult(IADC_TypeDef *iadc) { uint32_t alignment = (iadc->SCANFIFOCFG & _IADC_SCANFIFOCFG_ALIGNMENT_MASK) >> _IADC_SCANFIFOCFG_ALIGNMENT_SHIFT; return IADC_ConvertRawDataToResult(iadc->SCANDATA, (IADC_Alignment_t) alignment); } /***************************************************************************//** * @brief * Get reference voltage selection. * * @param[in] reference * IADC Reference selection. * * @return * IADC reference voltage in millivolts. ******************************************************************************/ uint32_t IADC_getReferenceVoltage(IADC_CfgReference_t reference) { uint32_t refVoltage = 0; // Get chip revision SYSTEM_ChipRevision_TypeDef chipRev; SYSTEM_ChipRevisionGet(&chipRev); switch (reference) { case iadcCfgReferenceInt1V2: #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_1) if (chipRev.major == 1UL) { refVoltage = 1210; } else { refVoltage = 1180; } #else refVoltage = 1210; #endif break; case iadcCfgReferenceExt1V25: refVoltage = 1250; break; #if defined(_IADC_CFG_REFSEL_VREF2P5) case iadcCfgReferenceExt2V5: refVoltage = 2500; break; #endif case iadcCfgReferenceVddx: refVoltage = 3000; break; case iadcCfgReferenceVddX0P8Buf: refVoltage = 2400; break; #if defined(_IADC_CFG_REFSEL_VREFBUF) case iadcCfgReferenceBuf: refVoltage = 12500; break; #endif #if defined(_IADC_CFG_REFSEL_VREF0P8BUF) case iadcCfgReference0P8Buf: refVoltage = 1000; break; #endif default: EFM_ASSERT(false); break; } return refVoltage; } /** @} (end addtogroup iadc) */ /** @} (end addtogroup emlib) */ #endif /* defined(IADC_COUNT) && (IADC_COUNT > 0) */