/***************************************************************************//** * @file em_acmp.c * @brief Analog Comparator (ACMP) Peripheral API * @version 4.2.1 ******************************************************************************* * @section License * <b>(C) Copyright 2015 Silicon Labs, 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_acmp.h" #if defined(ACMP_COUNT) && (ACMP_COUNT > 0) #include <stdbool.h> #include "em_bus.h" #include "em_assert.h" /***************************************************************************//** * @addtogroup EM_Library * @{ ******************************************************************************/ /***************************************************************************//** * @addtogroup ACMP * @brief Analog comparator (ACMP) Peripheral API * @{ ******************************************************************************/ /******************************************************************************* ******************************* DEFINES *********************************** ******************************************************************************/ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ /** Validation of ACMP register block pointer reference * for assert statements. */ #if (ACMP_COUNT == 1) #define ACMP_REF_VALID(ref) ((ref) == ACMP0) #elif (ACMP_COUNT == 2) #define ACMP_REF_VALID(ref) (((ref) == ACMP0) || ((ref) == ACMP1)) #else #error Undefined number of analog comparators (ACMP). #endif /** The maximum value that can be inserted in the route location register * for the specific device. */ #if defined(_ACMP_ROUTE_LOCATION_LOC3) #define _ACMP_ROUTE_LOCATION_MAX _ACMP_ROUTE_LOCATION_LOC3 #elif defined(_ACMP_ROUTE_LOCATION_LOC2) #define _ACMP_ROUTE_LOCATION_MAX _ACMP_ROUTE_LOCATION_LOC2 #elif defined(_ACMP_ROUTE_LOCATION_LOC1) #define _ACMP_ROUTE_LOCATION_MAX _ACMP_ROUTE_LOCATION_LOC1 #elif defined(_ACMP_ROUTELOC0_OUTLOC_LOC31) #define _ACMP_ROUTE_LOCATION_MAX _ACMP_ROUTELOC0_OUTLOC_LOC31 #else #error Undefined max route locations #endif /** @endcond */ /******************************************************************************* ************************** GLOBAL FUNCTIONS ******************************* ******************************************************************************/ /***************************************************************************//** * @brief * Sets up the ACMP for use in capacative sense applications. * * @details * This function sets up the ACMP for use in capacacitve sense applications. * To use the capacative sense functionality in the ACMP you need to use * the PRS output of the ACMP module to count the number of oscillations * in the capacative sense circuit (possibly using a TIMER). * * @note * A basic example of capacative sensing can be found in the STK BSP * (capsense demo). * * @param[in] acmp * Pointer to ACMP peripheral register block. * * @param[in] init * Pointer to initialization structure used to configure ACMP for capacative * sensing operation. ******************************************************************************/ void ACMP_CapsenseInit(ACMP_TypeDef *acmp, const ACMP_CapsenseInit_TypeDef *init) { /* Make sure the module exists on the selected chip */ EFM_ASSERT(ACMP_REF_VALID(acmp)); /* Make sure that vddLevel is within bounds */ #if defined(_ACMP_INPUTSEL_VDDLEVEL_MASK) EFM_ASSERT(init->vddLevel < 64); #else EFM_ASSERT(init->vddLevelLow < 64); EFM_ASSERT(init->vddLevelHigh < 64); #endif /* Make sure biasprog is within bounds */ EFM_ASSERT(init->biasProg <= (_ACMP_CTRL_BIASPROG_MASK >> _ACMP_CTRL_BIASPROG_SHIFT)); /* Set control register. No need to set interrupt modes */ acmp->CTRL = (init->fullBias << _ACMP_CTRL_FULLBIAS_SHIFT) #if defined(_ACMP_CTRL_HALFBIAS_MASK) | (init->halfBias << _ACMP_CTRL_HALFBIAS_SHIFT) #endif | (init->biasProg << _ACMP_CTRL_BIASPROG_SHIFT) #if defined(_ACMP_CTRL_WARMTIME_MASK) | (init->warmTime << _ACMP_CTRL_WARMTIME_SHIFT) #endif #if defined(_ACMP_CTRL_HYSTSEL_MASK) | (init->hysteresisLevel << _ACMP_CTRL_HYSTSEL_SHIFT) #endif #if defined(_ACMP_CTRL_ACCURACY_MASK) | ACMP_CTRL_ACCURACY_HIGH #endif ; #if defined(_ACMP_HYSTERESIS0_MASK) acmp->HYSTERESIS0 = (init->vddLevelHigh << _ACMP_HYSTERESIS0_DIVVA_SHIFT) | (init->hysteresisLevel_0 << _ACMP_HYSTERESIS0_HYST_SHIFT); acmp->HYSTERESIS1 = (init->vddLevelLow << _ACMP_HYSTERESIS1_DIVVA_SHIFT) | (init->hysteresisLevel_1 << _ACMP_HYSTERESIS1_HYST_SHIFT); #endif /* Select capacative sensing mode by selecting a resistor and enabling it */ acmp->INPUTSEL = (init->resistor << _ACMP_INPUTSEL_CSRESSEL_SHIFT) | ACMP_INPUTSEL_CSRESEN #if defined(_ACMP_INPUTSEL_LPREF_MASK) | (init->lowPowerReferenceEnabled << _ACMP_INPUTSEL_LPREF_SHIFT) #endif #if defined(_ACMP_INPUTSEL_VDDLEVEL_MASK) | (init->vddLevel << _ACMP_INPUTSEL_VDDLEVEL_SHIFT) #endif #if defined(ACMP_INPUTSEL_NEGSEL_CAPSENSE) | ACMP_INPUTSEL_NEGSEL_CAPSENSE #else | ACMP_INPUTSEL_VASEL_VDD | ACMP_INPUTSEL_NEGSEL_VADIV #endif ; /* Enable ACMP if requested. */ BUS_RegBitWrite(&(acmp->CTRL), _ACMP_CTRL_EN_SHIFT, init->enable); } /***************************************************************************//** * @brief * Sets the ACMP channel used for capacative sensing. * * @note * A basic example of capacative sensing can be found in the STK BSP * (capsense demo). * * @param[in] acmp * Pointer to ACMP peripheral register block. * * @param[in] channel * The ACMP channel to use for capacative sensing (Possel). ******************************************************************************/ void ACMP_CapsenseChannelSet(ACMP_TypeDef *acmp, ACMP_Channel_TypeDef channel) { /* Make sure the module exists on the selected chip */ EFM_ASSERT(ACMP_REF_VALID(acmp)); #if defined(_ACMP_INPUTSEL_POSSEL_CH7) /* Make sure that only external channels are used */ EFM_ASSERT(channel <= _ACMP_INPUTSEL_POSSEL_CH7); #elif defined(_ACMP_INPUTSEL_POSSEL_BUS4XCH31) /* Make sure that only external channels are used */ EFM_ASSERT(channel <= _ACMP_INPUTSEL_POSSEL_BUS4XCH31); #endif /* Set channel as positive channel in ACMP */ BUS_RegMaskedWrite(&acmp->INPUTSEL, _ACMP_INPUTSEL_POSSEL_MASK, channel << _ACMP_INPUTSEL_POSSEL_SHIFT); } /***************************************************************************//** * @brief * Disables the ACMP. * * @param[in] acmp * Pointer to ACMP peripheral register block. ******************************************************************************/ void ACMP_Disable(ACMP_TypeDef *acmp) { /* Make sure the module exists on the selected chip */ EFM_ASSERT(ACMP_REF_VALID(acmp)); acmp->CTRL &= ~ACMP_CTRL_EN; } /***************************************************************************//** * @brief * Enables the ACMP. * * @param[in] acmp * Pointer to ACMP peripheral register block. ******************************************************************************/ void ACMP_Enable(ACMP_TypeDef *acmp) { /* Make sure the module exists on the selected chip */ EFM_ASSERT(ACMP_REF_VALID(acmp)); acmp->CTRL |= ACMP_CTRL_EN; } /***************************************************************************//** * @brief * Reset ACMP 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] acmp * Pointer to the ACMP peripheral register block. ******************************************************************************/ void ACMP_Reset(ACMP_TypeDef *acmp) { /* Make sure the module exists on the selected chip */ EFM_ASSERT(ACMP_REF_VALID(acmp)); acmp->CTRL = _ACMP_CTRL_RESETVALUE; acmp->INPUTSEL = _ACMP_INPUTSEL_RESETVALUE; #if defined(_ACMP_HYSTERESIS0_HYST_MASK) acmp->HYSTERESIS0 = _ACMP_HYSTERESIS0_RESETVALUE; acmp->HYSTERESIS1 = _ACMP_HYSTERESIS1_RESETVALUE; #endif acmp->IEN = _ACMP_IEN_RESETVALUE; acmp->IFC = _ACMP_IF_MASK; } /***************************************************************************//** * @brief * Sets up GPIO output from the ACMP. * * @note * GPIO must be enabled in the CMU before this function call, i.e. * @verbatim CMU_ClockEnable(cmuClock_GPIO, true); @endverbatim * * @param[in] acmp * Pointer to the ACMP peripheral register block. * * @param location * The pin location to use. See the datasheet for location to pin mappings. * * @param enable * Enable or disable pin output. * * @param invert * Invert output. ******************************************************************************/ void ACMP_GPIOSetup(ACMP_TypeDef *acmp, uint32_t location, bool enable, bool invert) { /* Make sure the module exists on the selected chip */ EFM_ASSERT(ACMP_REF_VALID(acmp)); /* Sanity checking of location */ EFM_ASSERT(location <= _ACMP_ROUTE_LOCATION_MAX); /* Set GPIO inversion */ BUS_RegMaskedWrite(&acmp->CTRL, _ACMP_CTRL_GPIOINV_MASK, invert << _ACMP_CTRL_GPIOINV_SHIFT); #if defined(_ACMP_ROUTE_MASK) acmp->ROUTE = (location << _ACMP_ROUTE_LOCATION_SHIFT) | (enable << _ACMP_ROUTE_ACMPPEN_SHIFT); #endif #if defined(_ACMP_ROUTELOC0_MASK) acmp->ROUTELOC0 = location << _ACMP_ROUTELOC0_OUTLOC_SHIFT; acmp->ROUTEPEN = enable ? ACMP_ROUTEPEN_OUTPEN : 0; #endif } /***************************************************************************//** * @brief * Sets which channels should be used in ACMP comparisons. * * @param[in] acmp * Pointer to the ACMP peripheral register block. * * @param negSel * Channel to use on the negative input to the ACMP. * * @param posSel * Channel to use on the positive input to the ACMP. ******************************************************************************/ void ACMP_ChannelSet(ACMP_TypeDef *acmp, ACMP_Channel_TypeDef negSel, ACMP_Channel_TypeDef posSel) { /* Make sure the module exists on the selected chip */ EFM_ASSERT(ACMP_REF_VALID(acmp)); /* Make sure that posSel and negSel channel selectors are valid. */ #if defined(_ACMP_INPUTSEL_NEGSEL_DAC0CH1) EFM_ASSERT(negSel <= _ACMP_INPUTSEL_NEGSEL_DAC0CH1); #elif defined(_ACMP_INPUTSEL_NEGSEL_CAPSENSE) EFM_ASSERT(negSel <= _ACMP_INPUTSEL_NEGSEL_CAPSENSE); #endif #if defined(_ACMP_INPUTSEL_POSSEL_CH7) EFM_ASSERT(posSel <= _ACMP_INPUTSEL_POSSEL_CH7); #endif acmp->INPUTSEL = (acmp->INPUTSEL & ~(_ACMP_INPUTSEL_POSSEL_MASK | _ACMP_INPUTSEL_NEGSEL_MASK)) | (negSel << _ACMP_INPUTSEL_NEGSEL_SHIFT) | (posSel << _ACMP_INPUTSEL_POSSEL_SHIFT); } /***************************************************************************//** * @brief * Initialize ACMP. * * @param[in] acmp * Pointer to the ACMP peripheral register block. * * @param[in] init * Pointer to initialization structure used to configure ACMP for capacative * sensing operation. ******************************************************************************/ void ACMP_Init(ACMP_TypeDef *acmp, const ACMP_Init_TypeDef *init) { /* Make sure the module exists on the selected chip */ EFM_ASSERT(ACMP_REF_VALID(acmp)); /* Make sure biasprog is within bounds */ EFM_ASSERT(init->biasProg < 16); /* Make sure the ACMP is disable since we might be changing the * ACMP power source */ BUS_RegBitWrite(&acmp->CTRL, _ACMP_CTRL_EN_SHIFT, 0); /* Set control register. No need to set interrupt modes */ acmp->CTRL = (init->fullBias << _ACMP_CTRL_FULLBIAS_SHIFT) #if defined(_ACMP_CTRL_HALFBIAS_MASK) | (init->halfBias << _ACMP_CTRL_HALFBIAS_SHIFT) #endif | (init->biasProg << _ACMP_CTRL_BIASPROG_SHIFT) | (init->interruptOnFallingEdge << _ACMP_CTRL_IFALL_SHIFT) | (init->interruptOnRisingEdge << _ACMP_CTRL_IRISE_SHIFT) #if defined(_ACMP_CTRL_INPUTRANGE_MASK) | (init->inputRange << _ACMP_CTRL_INPUTRANGE_SHIFT) #endif #if defined(_ACMP_CTRL_ACCURACY_MASK) | (init->accuracy << _ACMP_CTRL_ACCURACY_SHIFT) #endif #if defined(_ACMP_CTRL_PWRSEL_MASK) | (init->powerSource << _ACMP_CTRL_PWRSEL_SHIFT) #endif #if defined(_ACMP_CTRL_WARMTIME_MASK) | (init->warmTime << _ACMP_CTRL_WARMTIME_SHIFT) #endif #if defined(_ACMP_CTRL_HYSTSEL_MASK) | (init->hysteresisLevel << _ACMP_CTRL_HYSTSEL_SHIFT) #endif | (init->inactiveValue << _ACMP_CTRL_INACTVAL_SHIFT); acmp->INPUTSEL = (0) #if defined(_ACMP_INPUTSEL_VLPSEL_MASK) | (init->vlpInput << _ACMP_INPUTSEL_VLPSEL_SHIFT) #endif #if defined(_ACMP_INPUTSEL_LPREF_MASK) | (init->lowPowerReferenceEnabled << _ACMP_INPUTSEL_LPREF_SHIFT) #endif #if defined(_ACMP_INPUTSEL_VDDLEVEL_MASK) | (init->vddLevel << _ACMP_INPUTSEL_VDDLEVEL_SHIFT) #endif ; /* Enable ACMP if requested. */ BUS_RegBitWrite(&(acmp->CTRL), _ACMP_CTRL_EN_SHIFT, init->enable); } #if defined(_ACMP_INPUTSEL_VASEL_MASK) /***************************************************************************//** * @brief * Setup the VA Source. * * @param[in] acmp * Pointer to the ACMP peripheral register block. * * @param[in] vaconfig * Pointer to the structure used to configure the VA source. This structure * contains the input source as well as the 2 divider values. ******************************************************************************/ void ACMP_VASetup(ACMP_TypeDef *acmp, const ACMP_VAConfig_TypeDef *vaconfig) { EFM_ASSERT(vaconfig->div0 < 64); EFM_ASSERT(vaconfig->div1 < 64); BUS_RegMaskedWrite(&acmp->INPUTSEL, _ACMP_INPUTSEL_VASEL_MASK, vaconfig->input << _ACMP_INPUTSEL_VASEL_SHIFT); BUS_RegMaskedWrite(&acmp->HYSTERESIS0, _ACMP_HYSTERESIS0_DIVVA_MASK, vaconfig->div0 << _ACMP_HYSTERESIS0_DIVVA_SHIFT); BUS_RegMaskedWrite(&acmp->HYSTERESIS1, _ACMP_HYSTERESIS1_DIVVA_MASK, vaconfig->div1 << _ACMP_HYSTERESIS1_DIVVA_SHIFT); } #endif #if defined(_ACMP_INPUTSEL_VBSEL_MASK) /***************************************************************************//** * @brief * Setup the VB Source. * * @param[in] acmp * Pointer to the ACMP peripheral register block. * * @param[in] vbconfig * Pointer to the structure used to configure the VB source. This structure * contains the input source as well as the 2 divider values. ******************************************************************************/ void ACMP_VBSetup(ACMP_TypeDef *acmp, const ACMP_VBConfig_TypeDef *vbconfig) { EFM_ASSERT(vbconfig->div0 < 64); EFM_ASSERT(vbconfig->div1 < 64); BUS_RegMaskedWrite(&acmp->INPUTSEL, _ACMP_INPUTSEL_VBSEL_MASK, vbconfig->input << _ACMP_INPUTSEL_VBSEL_SHIFT); BUS_RegMaskedWrite(&acmp->HYSTERESIS0, _ACMP_HYSTERESIS0_DIVVB_MASK, vbconfig->div0 << _ACMP_HYSTERESIS0_DIVVB_SHIFT); BUS_RegMaskedWrite(&acmp->HYSTERESIS1, _ACMP_HYSTERESIS1_DIVVB_MASK, vbconfig->div1 << _ACMP_HYSTERESIS1_DIVVB_SHIFT); } #endif /** @} (end addtogroup ACMP) */ /** @} (end addtogroup EM_Library) */ #endif /* defined(ACMP_COUNT) && (ACMP_COUNT > 0) */