rt-thread/bsp/efm32/Libraries/emlib/src/em_leuart.c

640 lines
21 KiB
C

/***************************************************************************//**
* @file
* @brief Low Energy Universal Asynchronous Receiver/Transmitter (LEUART)
* Peripheral API
* @author Energy Micro AS
* @version 3.0.0
*******************************************************************************
* @section License
* <b>(C) Copyright 2012 Energy Micro AS, http://www.energymicro.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: Energy Micro AS has no
* obligation to support this Software. Energy Micro AS 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.
*
* Energy Micro AS 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"
#include "em_cmu.h"
#include "em_assert.h"
/***************************************************************************//**
* @addtogroup EM_Library
* @{
******************************************************************************/
/***************************************************************************//**
* @addtogroup LEUART
* @brief Low Energy Universal Asynchronous Receiver/Transmitter (LEUART)
* Peripheral API
* @{
******************************************************************************/
/*******************************************************************************
******************************* DEFINES ***********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/** Validation of 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 low frequency domain to complete.
*
* @param[in] leuart
* Pointer to LEUART peripheral register block
*
* @param[in] mask
* 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 low frequency domain */
while (leuart->SYNCBUSY & mask)
;
}
/** @endcond */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Calculate baudrate for 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 actual HW configuration. It can be used
* to determinate if a given configuration is sufficiently accurate for the
* application.
*
* @param[in] refFreq
* LEUART peripheral frequency used.
*
* @param[in] clkdiv
* Clock division factor to be used.
*
* @return
* 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;
/* We want to 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 32768Hz, there is no problem with overflow
* if using 32 bit arithmetic. However, since fLEUARTn may be derived from
* HFCORECLK as well, we must consider 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 max 32 bit
* integer. Yet we want to evaluate dividend first before dividing in
* order to get as small rounding effects as possible. We do not want
* to make too harsh restrictions on max fLEUARTn value either.
*
* For division a/b, we can write
*
* 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 divisor >= 256, the below cannot exceed max 32 bit value. */
br = 256 * quotient;
/*
* Remainder < (256 + clkdiv), which means dividend (256 * remainder) worst case is
* 256*(256 + 0x7ff8) = 0x80F800.
*/
br += (256 * remainder) / divisor;
return br;
}
/***************************************************************************//**
* @brief
* Get current baudrate for LEUART.
*
* @details
* This function returns the actual baudrate (not considering oscillator
* inaccuracies) used by a LEUART peripheral.
*
* @param[in] leuart
* Pointer to LEUART peripheral register block.
*
* @return
* Current baudrate.
******************************************************************************/
uint32_t LEUART_BaudrateGet(LEUART_TypeDef *leuart)
{
uint32_t freq;
CMU_Clock_TypeDef clock;
/* Get 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 baudrate (or as close as possible to specified baudrate).
*
* @note
* The setting of a baudrate 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
* Pointer to LEUART peripheral register block.
*
* @param[in] refFreq
* LEUART reference clock frequency in Hz that will be used. If set to 0,
* the currently configured reference clock is assumed.
*
* @param[in] baudrate
* 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;
/* Inhibit divide by 0 */
EFM_ASSERT(baudrate);
/*
* We want to 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 32768Hz, there is no problem with overflow
* if using 32 bit arithmetic. However, since fLEUARTn may be derived from
* HFCORECLK as well, we must consider 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 max 32 bit
* integer. Yet, we want to evaluate dividend first before dividing in
* order to get as small rounding effects as possible. We do not want
* to make too harsh restrictions on max fLEUARTn value either.
*
* Since the last 3 bits of CLKDIV are don't care, we can base our
* 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 128MHz without overflowing a 32 bit value!
*/
/* Get 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 CLKDIV with fractional bits */
clkdiv = (32 * refFreq) / baudrate;
clkdiv -= 32;
clkdiv *= 8;
/* LF register about to be modified require sync. busy check */
LEUART_Sync(leuart, LEUART_SYNCBUSY_CLKDIV);
leuart->CLKDIV = clkdiv;
}
/***************************************************************************//**
* @brief
* Enable/disable LEUART receiver and/or transmitter.
*
* @details
* Notice that this function does not do any configuration. Enabling should
* normally be done after initialization is done (if not enabled as part
* of init).
*
* @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
* Pointer to 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 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 require 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 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 unecessary
* stalling.
*
* @param[in] leuart
* Pointer to 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 LF
* domain
******************************************************************************/
void LEUART_FreezeEnable(LEUART_TypeDef *leuart, bool enable)
{
if (enable)
{
/*
* Wait for any ongoing LF synchronization to complete. This is just 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)
;
leuart->FREEZE = LEUART_FREEZE_REGFREEZE;
}
else
{
leuart->FREEZE = 0;
}
}
/***************************************************************************//**
* @brief
* Init LEUART.
*
* @details
* This function will configure basic settings in order to operate in normal
* asynchronous mode. Consider using LEUART_Reset() prior to this function if
* 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, in order for the LEUART to work as intended.
* (When configuring pins, one should remember to consider the sequence of
* configuration, in order 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
* Pointer to LEUART peripheral register block.
*
* @param[in] init
* Pointer to initialization structure used to configure basic async setup.
******************************************************************************/
void LEUART_Init(LEUART_TypeDef *leuart, LEUART_Init_TypeDef *init)
{
/* Make sure the module exists on the selected chip */
EFM_ASSERT(LEUART_REF_VALID(leuart));
/* LF register about to be modified require sync. busy check */
LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD);
/* Ensure disabled while doing config */
leuart->CMD = LEUART_CMD_RXDIS | LEUART_CMD_TXDIS;
/* Freeze registers to avoid stalling for 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 baudrate */
LEUART_BaudrateSet(leuart, init->refFreq, init->baudrate);
/* Finally enable (as specified) */
leuart->CMD = (uint32_t)(init->enable);
/* Unfreeze registers, pass new settings on to LEUART */
LEUART_FreezeEnable(leuart, false);
}
/***************************************************************************//**
* @brief
* Reset LEUART to same state as after a HW reset.
*
* @param[in] leuart
* Pointer to 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;
leuart->ROUTE = _LEUART_ROUTE_RESETVALUE;
/* Do not reset route register, setting should be done independently */
/* Unfreeze registers, 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. Please refer to LEUART_RxExt() for reception of
* 9 bit frames.
*
* Notice that possible parity/stop bits are not considered part of specified
* frame bit length.
*
* @note
* This function will stall if buffer is empty, until data is received.
*
* @param[in] leuart
* Pointer to 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
* Pointer to 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 frame length configuration, 8 (least significant) bits from
* @p data are transmitted. If frame length is 9, 8 bits are transmitted from
* @p data and one bit as specified by CTRL register, BIT8DV field. Please
* refer to 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 part of specified frame bit length.
*
* @note
* This function will stall if buffer is full, until buffer becomes available.
*
* @param[in] leuart
* Pointer to LEUART peripheral register block.
*
* @param[in] data
* Data to transmit. See details above for further 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 require 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 part of specified frame bit length.
*
* @note
* This function will stall if buffer is full, until buffer becomes available.
*
* @param[in] leuart
* Pointer to LEUART peripheral register block.
*
* @param[in] data
* Data to transmit with extended control. Least significant bits contains
* frame bits, and additional control bits are available as documented in
* the EFM32 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 require sync. busy check */
LEUART_Sync(leuart, LEUART_SYNCBUSY_TXDATAX);
leuart->TXDATAX = (uint32_t)data;
}
/** @} (end addtogroup LEUART) */
/** @} (end addtogroup EM_Library) */