added prescaler for 16 bit pwm in LPC1347 target

Fork of mbed-dev by mbed official

Revision:
0:9b334a45a8ff
Child:
50:a417edff4437
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/emlib/src/em_adc.c	Thu Oct 01 15:25:22 2015 +0300
@@ -0,0 +1,524 @@
+/***************************************************************************//**
+ * @file em_adc.c
+ * @brief Analog to Digital Converter (ADC) Peripheral API
+ * @version 3.20.12
+ *******************************************************************************
+ * @section License
+ * <b>(C) Copyright 2014 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_adc.h"
+#if defined(ADC_COUNT) && (ADC_COUNT > 0)
+
+#include "em_cmu.h"
+#include "em_assert.h"
+
+/***************************************************************************//**
+ * @addtogroup EM_Library
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup ADC
+ * @brief Analog to Digital Converter (ADC) Peripheral API
+ * @{
+ ******************************************************************************/
+
+/*******************************************************************************
+ *******************************   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_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.
+ *
+ * @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_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.
+ *
+ * @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)
+{
+  /* 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;
+
+  /* Load calibration values for the 1V25 internal reference. */
+  ADC_CalibrateLoadSingle(adc, adcRef1V25);
+  ADC_CalibrateLoadScan(adc, adcRef1V25);
+
+  /* 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;
+    }
+  }
+#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 EM_Library) */
+#endif /* defined(ADC_COUNT) && (ADC_COUNT > 0) */