blob: 0ab9bdea29eba00617322491cd2f74b83cfe7bba [file] [log] [blame]
/***************************************************************************//**
* @file em_leuart.c
* @brief Low Energy Universal Asynchronous Receiver/Transmitter (LEUART)
* 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_leuart.h"
#if defined(LEUART_COUNT) && (LEUART_COUNT > 0)
#include "em_cmu.h"
#include "em_assert.h"
/***************************************************************************//**
* @addtogroup emlib
* @{
******************************************************************************/
/***************************************************************************//**
* @addtogroup LEUART
* @brief Low Energy Universal Asynchronous Receiver/Transmitter (LEUART)
* Peripheral API
* @details
* This module contains functions to control the LEUART peripheral of Silicon
* Labs 32-bit MCUs and SoCs. The LEUART module provides the full UART communication using
* a low frequency 32.768 kHz clock and has special features for communication
* without the CPU intervention.
* @{
******************************************************************************/
/*******************************************************************************
******************************* DEFINES ***********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/** A validation of the LEUART register block pointer reference
* for assert statements. */
#if (LEUART_COUNT == 1)
#define LEUART_REF_VALID(ref) ((ref) == LEUART0)
#elif (LEUART_COUNT == 2)
#define LEUART_REF_VALID(ref) (((ref) == LEUART0) || ((ref) == LEUART1))
#else
#error "Undefined number of low energy UARTs (LEUART)."
#endif
/** @endcond */
/*******************************************************************************
************************** LOCAL FUNCTIONS ********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/***************************************************************************//**
* @brief
* Wait for ongoing sync of register(s) to the low-frequency domain to complete.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @param[in] mask
* A bitmask corresponding to SYNCBUSY register defined bits, indicating
* registers that must complete any ongoing synchronization.
******************************************************************************/
__STATIC_INLINE void LEUART_Sync(LEUART_TypeDef *leuart, uint32_t mask)
{
/* Avoid deadlock if modifying the same register twice when freeze mode is */
/* activated. */
if (leuart->FREEZE & LEUART_FREEZE_REGFREEZE) {
return;
}
/* Wait for any pending previous write operation to have been completed */
/* in the low-frequency domai. */
while ((leuart->SYNCBUSY & mask) != 0U) {
}
}
/** @endcond */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Calculate the baudrate for the LEUART given reference frequency and clock division.
*
* @details
* This function returns the baudrate that a LEUART module will use if
* configured with the given frequency and clock divisor. Notice that
* this function will not use the hardware configuration. It can be used
* to determine if a given configuration is sufficiently accurate for the
* application.
*
* @param[in] refFreq
* The LEUART peripheral frequency used.
*
* @param[in] clkdiv
* The clock division factor to be used.
*
* @return
* A baudrate with given settings.
******************************************************************************/
uint32_t LEUART_BaudrateCalc(uint32_t refFreq, uint32_t clkdiv)
{
uint32_t divisor;
uint32_t remainder;
uint32_t quotient;
uint32_t br;
/* Mask out unused bits. */
clkdiv &= _LEUART_CLKDIV_MASK;
/* Use integer division to avoid forcing in float division */
/* utils, and yet keep rounding effect errors to a minimum. */
/*
* Baudrate is given by:
*
* br = fLEUARTn/(1 + (CLKDIV / 256))
*
* which can be rewritten to
*
* br = (256 * fLEUARTn)/(256 + CLKDIV)
*
* Normally, with fLEUARTn appr 32768 Hz, there is no problem with overflow
* if using 32 bit arithmetic. However, since fLEUARTn may be derived from
* HFCORECLK, consider the overflow when using integer arithmetic.
*/
/*
* The basic problem with integer division in the above formula is that
* the dividend (256 * fLEUARTn) may become higher than the maximum 32 bit
* integer. Yet we want to evaluate the dividend first before dividing
* to get as small rounding effects as possible.
* Also, harsh restrictions should be avoided on the maximum fLEUARTn value.
*
* For division a/b:
*
* a = qb + r
*
* where q is the quotient and r is the remainder, both integers.
*
* The orignal baudrate formula can be rewritten as:
*
* br = 256a / b = 256(qb + r)/b = 256q + 256r/b
*
* where a is 'refFreq' and b is 'divisor', referring to variable names.
*/
divisor = 256 + clkdiv;
quotient = refFreq / divisor;
remainder = refFreq % divisor;
/* Since the divisor >= 256, the below cannot exceed the maximum 32 bit value. */
br = 256 * quotient;
/*
* A remainder < (256 + clkdiv), which means the dividend (256 * remainder) worst case is
* 256*(256 + 0x7ff8) = 0x80F800.
*/
br += (256 * remainder) / divisor;
return br;
}
/***************************************************************************//**
* @brief
* Get the current baudrate for LEUART.
*
* @details
* This function returns the actual baudrate (not considering the oscillator
* inaccuracies) used by the LEUART peripheral.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @return
* The current baudrate.
******************************************************************************/
uint32_t LEUART_BaudrateGet(LEUART_TypeDef *leuart)
{
uint32_t freq;
CMU_Clock_TypeDef clock;
/* Get the current frequency. */
if (leuart == LEUART0) {
clock = cmuClock_LEUART0;
}
#if (LEUART_COUNT > 1)
else if (leuart == LEUART1) {
clock = cmuClock_LEUART1;
}
#endif
else {
EFM_ASSERT(0);
return 0;
}
freq = CMU_ClockFreqGet(clock);
return LEUART_BaudrateCalc(freq, leuart->CLKDIV);
}
/***************************************************************************//**
* @brief
* Configure the baudrate (or as close as possible to a specified baudrate).
*
* @note
* The baudrate setting requires synchronization into the
* low-frequency domain. If the same register is modified before a previous
* update has completed, this function will stall until the previous
* synchronization has completed.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @param[in] refFreq
* The LEUART reference clock frequency in Hz that will be used. If set to 0,
* the currently configured reference clock is assumed.
*
* @param[in] baudrate
* A baudrate to try to achieve for LEUART.
******************************************************************************/
void LEUART_BaudrateSet(LEUART_TypeDef *leuart,
uint32_t refFreq,
uint32_t baudrate)
{
uint32_t clkdiv;
CMU_Clock_TypeDef clock;
/* Prevent dividing by 0. */
EFM_ASSERT(baudrate);
/*
* Use integer division to avoid forcing in float division
* utils, and yet keep rounding effect errors to a minimum.
*
* CLKDIV in asynchronous mode is given by:
*
* CLKDIV = 256*(fLEUARTn/br - 1) = ((256*fLEUARTn)/br) - 256
*
* Normally, with fLEUARTn appr 32768 Hz, there is no problem with overflow
* if using 32 bit arithmetic. However, since fLEUARTn may be derived from
* HFCORECLK, consider the overflow when using integer arithmetic.
*
* The basic problem with integer division in the above formula is that
* the dividend (256 * fLEUARTn) may become higher than the maximum 32 bit
* integer. Yet, the dividend should be evaluated first before dividing
* to get as small rounding effects as possible.
* Also, harsh restrictions on the maximum fLEUARTn value should not be made.
*
* Since the last 3 bits of CLKDIV are don't care, base the
* integer arithmetic on the below formula:
*
* CLKDIV/8 = ((32*fLEUARTn)/br) - 32
*
* and calculate 1/8 of CLKDIV first. This allows for fLEUARTn
* up to 128 MHz without overflowing a 32 bit value.
*/
/* Get the current frequency. */
if (!refFreq) {
if (leuart == LEUART0) {
clock = cmuClock_LEUART0;
}
#if (LEUART_COUNT > 1)
else if (leuart == LEUART1) {
clock = cmuClock_LEUART1;
}
#endif
else {
EFM_ASSERT(0);
return;
}
refFreq = CMU_ClockFreqGet(clock);
}
/* Calculate and set the CLKDIV with fractional bits. */
clkdiv = (32 * refFreq) / baudrate;
clkdiv -= 32;
clkdiv *= 8;
/* Verify that the resulting clock divider is within limits. */
EFM_ASSERT(clkdiv <= _LEUART_CLKDIV_MASK);
/* If the EFM_ASSERT is not enabled, make sure not to write to reserved bits. */
clkdiv &= _LEUART_CLKDIV_MASK;
/* LF register about to be modified requires sync; busy check. */
LEUART_Sync(leuart, LEUART_SYNCBUSY_CLKDIV);
leuart->CLKDIV = clkdiv;
}
/***************************************************************************//**
* @brief
* Enable/disable the LEUART receiver and/or transmitter.
*
* @details
* Notice that this function does not do any configuration. Enabling should
* normally be done after the initialization is done (if not enabled as part
* of initialization).
*
* @note
* Enabling/disabling requires synchronization into the low-frequency domain.
* If the same register is modified before a previous update has completed,
* this function will stall until the previous synchronization has completed.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @param[in] enable
* Select status for receiver/transmitter.
******************************************************************************/
void LEUART_Enable(LEUART_TypeDef *leuart, LEUART_Enable_TypeDef enable)
{
uint32_t tmp;
/* Make sure that the module exists on the selected chip. */
EFM_ASSERT(LEUART_REF_VALID(leuart));
/* Disable as specified. */
tmp = ~((uint32_t)(enable));
tmp &= (_LEUART_CMD_RXEN_MASK | _LEUART_CMD_TXEN_MASK);
tmp <<= 1;
/* Enable as specified. */
tmp |= (uint32_t)(enable);
/* LF register about to be modified requires sync; busy check. */
LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD);
leuart->CMD = tmp;
}
/***************************************************************************//**
* @brief
* LEUART register synchronization freeze control.
*
* @details
* Some LEUART registers require synchronization into the low-frequency (LF)
* domain. The freeze feature allows for several such registers to be
* modified before passing them to the LF domain simultaneously (which
* takes place when the freeze mode is disabled).
*
* @note
* When enabling freeze mode, this function will wait for all current
* ongoing LEUART synchronization to the LF domain to complete (Normally
* synchronization will not be in progress.) However, for this reason, when
* using freeze mode, modifications of registers requiring LF synchronization
* should be done within one freeze enable/disable block to avoid unnecessary
* stalling.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @param[in] enable
* @li True - enable freeze, modified registers are not propagated to the
* LF domain
* @li False - disables freeze, modified registers are propagated to the LF
* domain
******************************************************************************/
void LEUART_FreezeEnable(LEUART_TypeDef *leuart, bool enable)
{
if (enable) {
/*
* Wait for any ongoing LF synchronization to complete to
* protect against the rare case when a user
* - modifies a register requiring LF sync
* - then enables freeze before LF sync completed
* - then modifies the same register again
* since modifying a register while it is in sync progress should be
* avoided.
*/
while (leuart->SYNCBUSY != 0U) {
}
leuart->FREEZE = LEUART_FREEZE_REGFREEZE;
} else {
leuart->FREEZE = 0;
}
}
/***************************************************************************//**
* @brief
* Initialize LEUART.
*
* @details
* This function will configure basic settings to operate in normal
* asynchronous mode. Consider using LEUART_Reset() prior to this function if
* the state of configuration is not known, since only configuration settings
* specified by @p init are set.
*
* Special control setup not covered by this function may be done either
* before or after using this function (but normally before enabling)
* by direct modification of the CTRL register.
*
* Notice that pins used by the LEUART module must be properly configured
* by the user explicitly for the LEUART to work as intended.
* (When configuring pins consider the sequence of
* configuration to avoid unintended pulses/glitches on output
* pins.)
*
* @note
* Initializing requires synchronization into the low-frequency domain.
* If the same register is modified before a previous update has completed,
* this function will stall until the previous synchronization has completed.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @param[in] init
* A pointer to the initialization structure used to configure basic async setup.
******************************************************************************/
void LEUART_Init(LEUART_TypeDef *leuart, LEUART_Init_TypeDef const *init)
{
/* Make sure the module exists on the selected chip. */
EFM_ASSERT(LEUART_REF_VALID(leuart));
/* LF register about to be modified requires sync; busy check. */
LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD);
/* Ensure disabled while configuring. */
leuart->CMD = LEUART_CMD_RXDIS | LEUART_CMD_TXDIS;
/* Freeze registers to avoid stalling for the LF synchronization. */
LEUART_FreezeEnable(leuart, true);
/* Configure databits and stopbits. */
leuart->CTRL = (leuart->CTRL & ~(_LEUART_CTRL_PARITY_MASK
| _LEUART_CTRL_STOPBITS_MASK))
| (uint32_t)(init->databits)
| (uint32_t)(init->parity)
| (uint32_t)(init->stopbits);
/* Configure the baudrate. */
LEUART_BaudrateSet(leuart, init->refFreq, init->baudrate);
/* Finally enable (as specified). */
leuart->CMD = (uint32_t)init->enable;
/* Unfreeze registers and pass new settings on to LEUART. */
LEUART_FreezeEnable(leuart, false);
}
/***************************************************************************//**
* @brief
* Reset LEUART to the same state that it was in after a hardware reset.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
******************************************************************************/
void LEUART_Reset(LEUART_TypeDef *leuart)
{
/* Make sure the module exists on the selected chip. */
EFM_ASSERT(LEUART_REF_VALID(leuart));
/* Freeze registers to avoid stalling for LF synchronization. */
LEUART_FreezeEnable(leuart, true);
/* Make sure disabled first, before resetting other registers. */
leuart->CMD = LEUART_CMD_RXDIS | LEUART_CMD_TXDIS | LEUART_CMD_RXBLOCKDIS
| LEUART_CMD_CLEARTX | LEUART_CMD_CLEARRX;
leuart->CTRL = _LEUART_CTRL_RESETVALUE;
leuart->CLKDIV = _LEUART_CLKDIV_RESETVALUE;
leuart->STARTFRAME = _LEUART_STARTFRAME_RESETVALUE;
leuart->SIGFRAME = _LEUART_SIGFRAME_RESETVALUE;
leuart->IEN = _LEUART_IEN_RESETVALUE;
leuart->IFC = _LEUART_IFC_MASK;
leuart->PULSECTRL = _LEUART_PULSECTRL_RESETVALUE;
#if defined(_LEUART_ROUTEPEN_MASK)
leuart->ROUTEPEN = _LEUART_ROUTEPEN_RESETVALUE;
leuart->ROUTELOC0 = _LEUART_ROUTELOC0_RESETVALUE;
#else
leuart->ROUTE = _LEUART_ROUTE_RESETVALUE;
#endif
/* Unfreeze registers and pass new settings on to LEUART. */
LEUART_FreezeEnable(leuart, false);
}
/***************************************************************************//**
* @brief
* Receive one 8 bit frame, (or part of 9 bit frame).
*
* @details
* This function is normally used to receive one frame when operating with
* frame length 8 bits. See LEUART_RxExt() for reception of
* 9 bit frames.
*
* Notice that possible parity/stop bits are not considered a part of the specified
* frame bit length.
*
* @note
* This function will stall if the buffer is empty until data is received.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @return
* Data received.
******************************************************************************/
uint8_t LEUART_Rx(LEUART_TypeDef *leuart)
{
while (!(leuart->STATUS & LEUART_STATUS_RXDATAV)) {
}
return (uint8_t)leuart->RXDATA;
}
/***************************************************************************//**
* @brief
* Receive one 8-9 bit frame with extended information.
*
* @details
* This function is normally used to receive one frame and additional RX
* status information is required.
*
* @note
* This function will stall if buffer is empty until data is received.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @return
* Data received.
******************************************************************************/
uint16_t LEUART_RxExt(LEUART_TypeDef *leuart)
{
while (!(leuart->STATUS & LEUART_STATUS_RXDATAV)) {
}
return (uint16_t)leuart->RXDATAX;
}
/***************************************************************************//**
* @brief
* Transmit one frame.
*
* @details
* Depending on the frame length configuration, 8 (least significant) bits from
* @p data are transmitted. If the frame length is 9, 8 bits are transmitted from
* @p data and one bit as specified by the CTRL register, BIT8DV field.
* See LEUART_TxExt() for transmitting 9 bit frame with full control of
* all 9 bits.
*
* Notice that possible parity/stop bits in asynchronous mode are not
* considered a part of the specified frame bit length.
*
* @note
* This function will stall if buffer is full until the buffer becomes available.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @param[in] data
* Data to transmit. See details above for more info.
******************************************************************************/
void LEUART_Tx(LEUART_TypeDef *leuart, uint8_t data)
{
/* Check that transmit buffer is empty. */
while (!(leuart->STATUS & LEUART_STATUS_TXBL)) {
}
/* LF register about to be modified requires sync; busy check. */
LEUART_Sync(leuart, LEUART_SYNCBUSY_TXDATA);
leuart->TXDATA = (uint32_t)data;
}
/***************************************************************************//**
* @brief
* Transmit one 8-9 bit frame with extended control.
*
* @details
* Notice that possible parity/stop bits in asynchronous mode are not
* considered a part of the specified frame bit length.
*
* @note
* This function will stall if the buffer is full until the buffer becomes available.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @param[in] data
* Data to transmit with extended control. Least significant bit contains
* frame bits and additional control bits are available as documented in
* the reference manual (set to 0 if not used).
******************************************************************************/
void LEUART_TxExt(LEUART_TypeDef *leuart, uint16_t data)
{
/* Check that transmit buffer is empty. */
while (!(leuart->STATUS & LEUART_STATUS_TXBL)) {
}
/* LF register about to be modified requires sync; busy check. */
LEUART_Sync(leuart, LEUART_SYNCBUSY_TXDATAX);
leuart->TXDATAX = (uint32_t)data;
}
/***************************************************************************//**
* @brief
* Enables handling of LEUART TX by DMA in EM2.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @param[in] enable
* True - enables functionality
* False - disables functionality
*
******************************************************************************/
void LEUART_TxDmaInEM2Enable(LEUART_TypeDef *leuart, bool enable)
{
/* LF register about to be modified requires sync; busy check. */
LEUART_Sync(leuart, LEUART_SYNCBUSY_CTRL | LEUART_SYNCBUSY_CMD);
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
/* LEUART_E201: Changing the value of TXDMAWU while TXEN=1 could potentially
* cause unpredictable behavior. */
bool txEnabled = (leuart->STATUS & _LEUART_STATUS_TXENS_MASK) != 0U;
if (txEnabled) {
/* Wait for potential transmit to complete. */
while ((leuart->STATUS & LEUART_STATUS_TXIDLE) == 0U) {
}
leuart->CMD = LEUART_CMD_TXDIS;
LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD);
}
if (enable) {
leuart->CTRL |= LEUART_CTRL_TXDMAWU;
} else {
leuart->CTRL &= ~LEUART_CTRL_TXDMAWU;
}
if (txEnabled) {
leuart->CMD = LEUART_CMD_TXEN;
}
#else
if (enable) {
leuart->CTRL |= LEUART_CTRL_TXDMAWU;
} else {
leuart->CTRL &= ~LEUART_CTRL_TXDMAWU;
}
#endif
}
/***************************************************************************//**
* @brief
* Enables handling of LEUART RX by DMA in EM2.
*
* @param[in] leuart
* A pointer to the LEUART peripheral register block.
*
* @param[in] enable
* True - enables functionality
* False - disables functionality
*
******************************************************************************/
void LEUART_RxDmaInEM2Enable(LEUART_TypeDef *leuart, bool enable)
{
/* LF register about to be modified requires sync; busy check. */
LEUART_Sync(leuart, LEUART_SYNCBUSY_CTRL | LEUART_SYNCBUSY_CMD);
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
/* LEUART_E201: Changing the value of RXDMAWU while RXEN=1 could potentially
* cause unpredictable behavior. */
bool rxEnabled = (leuart->STATUS & _LEUART_STATUS_RXENS_MASK) != 0U;
if (rxEnabled) {
leuart->CMD = LEUART_CMD_RXDIS;
LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD);
}
if (enable) {
leuart->CTRL |= LEUART_CTRL_RXDMAWU;
} else {
leuart->CTRL &= ~LEUART_CTRL_RXDMAWU;
}
if (rxEnabled) {
leuart->CMD = LEUART_CMD_RXEN;
}
#else
if (enable) {
leuart->CTRL |= LEUART_CTRL_RXDMAWU;
} else {
leuart->CTRL &= ~LEUART_CTRL_RXDMAWU;
}
#endif
}
/** @} (end addtogroup LEUART) */
/** @} (end addtogroup emlib) */
#endif /* defined(LEUART_COUNT) && (LEUART_COUNT > 0) */