| /***************************************************************************//** |
| * @file em_vdac.c |
| * @brief Digital to Analog Converter (VDAC) Peripheral API |
| * @version 5.6.0 |
| ******************************************************************************* |
| * # License |
| * <b>Copyright 2016 Silicon Laboratories, Inc. 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_vdac.h" |
| #if defined(VDAC_COUNT) && (VDAC_COUNT > 0) |
| #include "em_cmu.h" |
| |
| /***************************************************************************//** |
| * @addtogroup emlib |
| * @{ |
| ******************************************************************************/ |
| |
| /***************************************************************************//** |
| * @addtogroup VDAC |
| * @{ |
| ******************************************************************************/ |
| |
| /******************************************************************************* |
| ******************************* DEFINES *********************************** |
| ******************************************************************************/ |
| |
| /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ |
| |
| /** Validation of the VDAC channel for assert statements. */ |
| #define VDAC_CH_VALID(ch) ((ch) <= 1) |
| |
| /** A maximum VDAC clock. */ |
| #define VDAC_MAX_CLOCK 1000000 |
| |
| /** The maximum clock frequency of the internal clock oscillator, 10 MHz + 20%. */ |
| #define VDAC_INTERNAL_CLOCK_FREQ 12000000 |
| |
| /** @endcond */ |
| |
| /******************************************************************************* |
| ************************** GLOBAL FUNCTIONS ******************************* |
| ******************************************************************************/ |
| |
| /***************************************************************************//** |
| * @brief |
| * Enable/disable the VDAC channel. |
| * |
| * @param[in] vdac |
| * A pointer to the VDAC peripheral register block. |
| * |
| * @param[in] ch |
| * A channel to enable/disable. |
| * |
| * @param[in] enable |
| * True to enable VDAC channel, false to disable. |
| ******************************************************************************/ |
| void VDAC_Enable(VDAC_TypeDef *vdac, unsigned int ch, bool enable) |
| { |
| EFM_ASSERT(VDAC_REF_VALID(vdac)); |
| EFM_ASSERT(VDAC_CH_VALID(ch)); |
| |
| if (ch == 0) { |
| if (enable) { |
| vdac->CMD = VDAC_CMD_CH0EN; |
| } else { |
| vdac->CMD = VDAC_CMD_CH0DIS; |
| while (vdac->STATUS & VDAC_STATUS_CH0ENS) ; |
| } |
| } else { |
| if (enable) { |
| vdac->CMD = VDAC_CMD_CH1EN; |
| } else { |
| vdac->CMD = VDAC_CMD_CH1DIS; |
| while (vdac->STATUS & VDAC_STATUS_CH1ENS) ; |
| } |
| } |
| } |
| |
| /***************************************************************************//** |
| * @brief |
| * Initialize VDAC. |
| * |
| * @details |
| * Initializes the common parts for both channels. This function will also load |
| * calibration values from the Device Information (DI) page into the VDAC |
| * calibration register. |
| * To complete a VDAC setup, channel control configuration must also be done. |
| * See VDAC_InitChannel(). |
| * |
| * @note |
| * This function will disable both channels prior to configuration. |
| * |
| * @param[in] vdac |
| * A pointer to the VDAC peripheral register block. |
| * |
| * @param[in] init |
| * A pointer to the VDAC initialization structure. |
| ******************************************************************************/ |
| void VDAC_Init(VDAC_TypeDef *vdac, const VDAC_Init_TypeDef *init) |
| { |
| uint32_t cal, tmp = 0; |
| uint32_t const volatile *calData; |
| |
| EFM_ASSERT(VDAC_REF_VALID(vdac)); |
| |
| /* Make sure both channels are disabled. */ |
| vdac->CMD = VDAC_CMD_CH0DIS | VDAC_CMD_CH1DIS; |
| while (vdac->STATUS & (VDAC_STATUS_CH0ENS | VDAC_STATUS_CH1ENS)) ; |
| |
| /* Get the OFFSETTRIM calibration value. */ |
| cal = ((DEVINFO->VDAC0CH1CAL & _DEVINFO_VDAC0CH1CAL_OFFSETTRIM_MASK) |
| >> _DEVINFO_VDAC0CH1CAL_OFFSETTRIM_SHIFT) |
| << _VDAC_CAL_OFFSETTRIM_SHIFT; |
| |
| if (init->mainCalibration) { |
| calData = &DEVINFO->VDAC0MAINCAL; |
| } else { |
| calData = &DEVINFO->VDAC0ALTCAL; |
| } |
| |
| /* Get the correct GAINERRTRIM calibration value. */ |
| switch (init->reference) { |
| case vdacRef1V25Ln: |
| tmp = (*calData & _DEVINFO_VDAC0MAINCAL_GAINERRTRIM1V25LN_MASK) |
| >> _DEVINFO_VDAC0MAINCAL_GAINERRTRIM1V25LN_SHIFT; |
| break; |
| |
| case vdacRef2V5Ln: |
| tmp = (*calData & _DEVINFO_VDAC0MAINCAL_GAINERRTRIM2V5LN_MASK) |
| >> _DEVINFO_VDAC0MAINCAL_GAINERRTRIM2V5LN_SHIFT; |
| break; |
| |
| case vdacRef1V25: |
| tmp = (*calData & _DEVINFO_VDAC0MAINCAL_GAINERRTRIM1V25_MASK) |
| >> _DEVINFO_VDAC0MAINCAL_GAINERRTRIM1V25_SHIFT; |
| break; |
| |
| case vdacRef2V5: |
| tmp = (*calData & _DEVINFO_VDAC0MAINCAL_GAINERRTRIM2V5_MASK) |
| >> _DEVINFO_VDAC0MAINCAL_GAINERRTRIM2V5_SHIFT; |
| break; |
| |
| case vdacRefAvdd: |
| case vdacRefExtPin: |
| tmp = (*calData & _DEVINFO_VDAC0MAINCAL_GAINERRTRIMVDDANAEXTPIN_MASK) |
| >> _DEVINFO_VDAC0MAINCAL_GAINERRTRIMVDDANAEXTPIN_SHIFT; |
| break; |
| } |
| |
| /* Set the sGAINERRTRIM calibration value. */ |
| cal |= tmp << _VDAC_CAL_GAINERRTRIM_SHIFT; |
| |
| /* Get the GAINERRTRIMCH1 calibration value. */ |
| switch (init->reference) { |
| case vdacRef1V25Ln: |
| case vdacRef1V25: |
| case vdacRefAvdd: |
| case vdacRefExtPin: |
| tmp = (DEVINFO->VDAC0CH1CAL & _DEVINFO_VDAC0CH1CAL_GAINERRTRIMCH1A_MASK) |
| >> _DEVINFO_VDAC0CH1CAL_GAINERRTRIMCH1A_SHIFT; |
| break; |
| |
| case vdacRef2V5Ln: |
| case vdacRef2V5: |
| tmp = (DEVINFO->VDAC0CH1CAL & _DEVINFO_VDAC0CH1CAL_GAINERRTRIMCH1B_MASK) |
| >> _DEVINFO_VDAC0CH1CAL_GAINERRTRIMCH1B_SHIFT; |
| break; |
| } |
| |
| /* Set the GAINERRTRIM calibration value. */ |
| cal |= tmp << _VDAC_CAL_GAINERRTRIMCH1_SHIFT; |
| |
| tmp = ((uint32_t)init->asyncClockMode << _VDAC_CTRL_DACCLKMODE_SHIFT) |
| | ((uint32_t)init->warmupKeepOn << _VDAC_CTRL_WARMUPMODE_SHIFT) |
| | ((uint32_t)init->refresh << _VDAC_CTRL_REFRESHPERIOD_SHIFT) |
| | (((uint32_t)init->prescaler << _VDAC_CTRL_PRESC_SHIFT) |
| & _VDAC_CTRL_PRESC_MASK) |
| | ((uint32_t)init->reference << _VDAC_CTRL_REFSEL_SHIFT) |
| | ((uint32_t)init->ch0ResetPre << _VDAC_CTRL_CH0PRESCRST_SHIFT) |
| | ((uint32_t)init->outEnablePRS << _VDAC_CTRL_OUTENPRS_SHIFT) |
| | ((uint32_t)init->sineEnable << _VDAC_CTRL_SINEMODE_SHIFT) |
| | ((uint32_t)init->diff << _VDAC_CTRL_DIFF_SHIFT); |
| |
| /* Write to VDAC registers. */ |
| vdac->CAL = cal; |
| vdac->CTRL = tmp; |
| } |
| |
| /***************************************************************************//** |
| * @brief |
| * Initialize a VDAC channel. |
| * |
| * @param[in] vdac |
| * A pointer to the VDAC peripheral register block. |
| * |
| * @param[in] init |
| * A pointer to the VDAC channel initialization structure. |
| * |
| * @param[in] ch |
| * A channel number to initialize. |
| ******************************************************************************/ |
| void VDAC_InitChannel(VDAC_TypeDef *vdac, |
| const VDAC_InitChannel_TypeDef *init, |
| unsigned int ch) |
| { |
| uint32_t vdacChCtrl, vdacStatus; |
| |
| EFM_ASSERT(VDAC_REF_VALID(vdac)); |
| EFM_ASSERT(VDAC_CH_VALID(ch)); |
| |
| /* Make sure both channels are disabled. */ |
| vdacStatus = vdac->STATUS; |
| vdac->CMD = VDAC_CMD_CH0DIS | VDAC_CMD_CH1DIS; |
| while (vdac->STATUS & (VDAC_STATUS_CH0ENS | VDAC_STATUS_CH1ENS)) ; |
| |
| vdacChCtrl = ((uint32_t)init->prsSel << _VDAC_CH0CTRL_PRSSEL_SHIFT) |
| | ((uint32_t)init->prsAsync << _VDAC_CH0CTRL_PRSASYNC_SHIFT) |
| | ((uint32_t)init->trigMode << _VDAC_CH0CTRL_TRIGMODE_SHIFT) |
| | ((uint32_t)init->sampleOffMode << _VDAC_CH0CTRL_CONVMODE_SHIFT); |
| |
| if (ch == 0) { |
| vdac->CH0CTRL = vdacChCtrl; |
| } else { |
| vdac->CH1CTRL = vdacChCtrl; |
| } |
| |
| /* Check if the channel must be enabled. */ |
| if (init->enable) { |
| if (ch == 0) { |
| vdac->CMD = VDAC_CMD_CH0EN; |
| } else { |
| vdac->CMD = VDAC_CMD_CH1EN; |
| } |
| } |
| |
| /* Check if the other channel had to be turned off above |
| * and needs to be turned on again. */ |
| if (ch == 0) { |
| if (vdacStatus & VDAC_STATUS_CH1ENS) { |
| vdac->CMD = VDAC_CMD_CH1EN; |
| } |
| } else { |
| if (vdacStatus & VDAC_STATUS_CH0ENS) { |
| vdac->CMD = VDAC_CMD_CH0EN; |
| } |
| } |
| } |
| |
| /***************************************************************************//** |
| * @brief |
| * Set the output signal of a VDAC channel to a given value. |
| * |
| * @details |
| * This function sets the output signal of a VDAC channel by writing @p value |
| * to the corresponding CHnDATA register. |
| * |
| * @param[in] vdac |
| * A pointer to the VDAC peripheral register block. |
| * |
| * @param[in] channel |
| * A channel number to set the output of. |
| * |
| * @param[in] value |
| * A value to write to the channel output register CHnDATA. |
| ******************************************************************************/ |
| void VDAC_ChannelOutputSet(VDAC_TypeDef *vdac, |
| unsigned int channel, |
| uint32_t value) |
| { |
| switch (channel) { |
| case 0: |
| VDAC_Channel0OutputSet(vdac, value); |
| break; |
| case 1: |
| VDAC_Channel1OutputSet(vdac, value); |
| break; |
| default: |
| EFM_ASSERT(0); |
| break; |
| } |
| } |
| |
| /***************************************************************************//** |
| * @brief |
| * Calculate the prescaler value used to determine VDAC clock. |
| * |
| * @details |
| * The VDAC clock is given by the input clock divided by the prescaler+1. |
| * |
| * VDAC_CLK = IN_CLK / (prescale + 1) |
| * |
| * The maximum VDAC clock is 1 MHz. The input clock is HFPERCLK when VDAC synchronous |
| * mode is selected, or an internal oscillator of 10 MHz +/- 20% when |
| * asynchronous mode is selected. |
| * |
| * @note |
| * If the requested VDAC frequency is low and the maximum prescaler value can't |
| * adjust the actual VDAC frequency lower than requested, the maximum prescaler |
| * value is returned resulting in a higher VDAC frequency than requested. |
| * |
| * @param[in] vdacFreq VDAC frequency target. The frequency will automatically |
| * be adjusted to be below maximum allowed VDAC clock. |
| * |
| * @param[in] syncMode Set to true if you intend to use VDAC in synchronous |
| * mode. |
| * |
| * @param[in] hfperFreq Frequency in Hz of HFPERCLK oscillator. Set to 0 to |
| * use the currently defined HFPERCLK clock setting. This parameter is only used |
| * when syncMode is set to true. |
| * |
| * @return |
| * A prescaler value to use for VDAC to achieve a clock value less than |
| * or equal to @p vdacFreq. |
| ******************************************************************************/ |
| uint32_t VDAC_PrescaleCalc(uint32_t vdacFreq, bool syncMode, uint32_t hfperFreq) |
| { |
| uint32_t ret, refFreq; |
| |
| /* Make sure that the selected VDAC clock is below the maximum value. */ |
| if (vdacFreq > VDAC_MAX_CLOCK) { |
| vdacFreq = VDAC_MAX_CLOCK; |
| } |
| |
| if (!syncMode) { |
| refFreq = VDAC_INTERNAL_CLOCK_FREQ; |
| } else { |
| if (hfperFreq) { |
| refFreq = hfperFreq; |
| } else { |
| refFreq = CMU_ClockFreqGet(cmuClock_HFPER); |
| } |
| } |
| |
| /* Iterate to determine the best prescaler value. Start with the lowest */ |
| /* prescaler value to get the first equal or less VDAC */ |
| /* frequency value. */ |
| for (ret = 0; ret <= _VDAC_CTRL_PRESC_MASK >> _VDAC_CTRL_PRESC_SHIFT; ret++) { |
| if ((refFreq / (ret + 1)) <= vdacFreq) { |
| break; |
| } |
| } |
| |
| /* If ret is higher than the maximum prescaler value, make sure to return |
| the maximum value. */ |
| if (ret > (_VDAC_CTRL_PRESC_MASK >> _VDAC_CTRL_PRESC_SHIFT)) { |
| ret = _VDAC_CTRL_PRESC_MASK >> _VDAC_CTRL_PRESC_SHIFT; |
| } |
| |
| return ret; |
| } |
| |
| /***************************************************************************//** |
| * @brief |
| * Reset VDAC to same state that it was in after a hardwares reset. |
| * |
| * @param[in] vdac |
| * A pointer to the VDAC peripheral register block. |
| ******************************************************************************/ |
| void VDAC_Reset(VDAC_TypeDef *vdac) |
| { |
| /* Disable channels before resetting other registers. */ |
| vdac->CMD = VDAC_CMD_CH0DIS | VDAC_CMD_CH1DIS; |
| while (vdac->STATUS & (VDAC_STATUS_CH0ENS | VDAC_STATUS_CH1ENS)) ; |
| vdac->CH0CTRL = _VDAC_CH0CTRL_RESETVALUE; |
| vdac->CH1CTRL = _VDAC_CH1CTRL_RESETVALUE; |
| vdac->CH0DATA = _VDAC_CH0DATA_RESETVALUE; |
| vdac->CH1DATA = _VDAC_CH1DATA_RESETVALUE; |
| vdac->CTRL = _VDAC_CTRL_RESETVALUE; |
| vdac->IEN = _VDAC_IEN_RESETVALUE; |
| vdac->IFC = _VDAC_IFC_MASK; |
| vdac->CAL = _VDAC_CAL_RESETVALUE; |
| } |
| |
| /** @} (end addtogroup VDAC) */ |
| /** @} (end addtogroup emlib) */ |
| #endif /* defined(VDAC_COUNT) && (VDAC_COUNT > 0) */ |