mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
158a8300c4
By not forcing a fixed oversampling rate we can achieve better baud rate accuracy than otherwise possible. For example, when requesting 115200 with a module clock of 4 MHz, picking an oversampling rate of 17 (instead of hardware default 16) yields 117647 baud instead of 125000 baud as the best matching rate. Better matching baud rate between receiver and transmitter results in a lower probability of transmission errors.
457 lines
12 KiB
C
457 lines
12 KiB
C
/*
|
|
* Copyright (C) 2017 Eistec AB
|
|
* Copyright (C) 2014 PHYTEC Messtechnik GmbH
|
|
* Copyright (C) 2014 Freie Universität Berlin
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser General
|
|
* Public License v2.1. See the file LICENSE in the top level directory for more
|
|
* details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup cpu_kinetis
|
|
* @ingroup drivers_periph_uart
|
|
*
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Low-level UART driver implementation
|
|
*
|
|
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
|
|
* @author Johann Fischer <j.fischer@phytec.de>
|
|
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include "cpu.h"
|
|
#include "bit.h"
|
|
#include "periph_conf.h"
|
|
#include "periph/uart.h"
|
|
|
|
#define ENABLE_DEBUG (0)
|
|
#include "debug.h"
|
|
|
|
#ifndef KINETIS_HAVE_LPUART
|
|
#ifdef LPUART0
|
|
#define KINETIS_HAVE_LPUART 1
|
|
#else
|
|
#define KINETIS_HAVE_LPUART 0
|
|
#endif
|
|
#endif /* KINETIS_HAVE_LPUART */
|
|
|
|
#ifndef KINETIS_HAVE_UART
|
|
#ifdef UART0
|
|
#define KINETIS_HAVE_UART 1
|
|
#else
|
|
#define KINETIS_HAVE_UART 0
|
|
#endif
|
|
#endif /* KINETIS_HAVE_LPUART */
|
|
|
|
#ifndef KINETIS_UART_ADVANCED
|
|
/**
|
|
* Attempts to determine the type of the UART,
|
|
* using the BRFA field in the UART C4 register.
|
|
*/
|
|
#ifdef UART_C4_BRFA
|
|
#define KINETIS_UART_ADVANCED 1
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef LPUART_OVERSAMPLING_RATE_MIN
|
|
/* Use 10x oversampling at minimum, this will be iterated to achieve a better
|
|
* baud rate match than otherwise possible with a fixed oversampling rate */
|
|
/* Hardware reset default value is an oversampling rate of 16 */
|
|
#define LPUART_OVERSAMPLING_RATE_MIN (10)
|
|
#endif
|
|
#ifndef LPUART_OVERSAMPLING_RATE_MAX
|
|
#define LPUART_OVERSAMPLING_RATE_MAX (32)
|
|
#endif
|
|
|
|
/* Default LPUART clock setting to avoid compilation failures, define this in
|
|
* periph_conf.h to set board specific configuration if using the LPUART. */
|
|
#ifndef LPUART_0_SRC
|
|
#define LPUART_0_SRC 0
|
|
#endif
|
|
#ifndef LPUART_1_SRC
|
|
#define LPUART_1_SRC 0
|
|
#endif
|
|
|
|
/**
|
|
* @brief Runtime configuration space, holds pointers to callback functions for RX
|
|
*/
|
|
static uart_isr_ctx_t config[UART_NUMOF];
|
|
|
|
static inline void uart_init_pins(uart_t uart);
|
|
|
|
#if KINETIS_HAVE_UART
|
|
static inline void uart_init_uart(uart_t uart, uint32_t baudrate);
|
|
#endif
|
|
#if KINETIS_HAVE_LPUART
|
|
static inline void uart_init_lpuart(uart_t uart, uint32_t baudrate);
|
|
#endif
|
|
|
|
/* Only use the dispatch function for uart_write if both UART and LPUART are
|
|
* available at the same time */
|
|
#if KINETIS_HAVE_UART && KINETIS_HAVE_LPUART
|
|
#define KINETIS_UART_WRITE_INLINE static inline
|
|
KINETIS_UART_WRITE_INLINE void uart_write_uart(uart_t uart, const uint8_t *data, size_t len);
|
|
KINETIS_UART_WRITE_INLINE void uart_write_lpuart(uart_t uart, const uint8_t *data, size_t len);
|
|
#else
|
|
#define KINETIS_UART_WRITE_INLINE
|
|
#if KINETIS_HAVE_UART
|
|
#define uart_write_uart uart_write
|
|
#elif KINETIS_HAVE_LPUART
|
|
#define uart_write_lpuart uart_write
|
|
#endif
|
|
#endif
|
|
|
|
int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg)
|
|
{
|
|
assert(uart < UART_NUMOF);
|
|
|
|
/* remember callback addresses */
|
|
config[uart].rx_cb = rx_cb;
|
|
config[uart].arg = arg;
|
|
|
|
uart_init_pins(uart);
|
|
|
|
/* Turn on module clock gate */
|
|
bit_set32(uart_config[uart].scgc_addr, uart_config[uart].scgc_bit);
|
|
|
|
switch (uart_config[uart].type) {
|
|
#if KINETIS_HAVE_UART
|
|
case KINETIS_UART:
|
|
uart_init_uart(uart, baudrate);
|
|
break;
|
|
#endif
|
|
#if KINETIS_HAVE_LPUART
|
|
case KINETIS_LPUART:
|
|
uart_init_lpuart(uart, baudrate);
|
|
break;
|
|
#endif
|
|
default:
|
|
return UART_NODEV;
|
|
}
|
|
return UART_OK;
|
|
}
|
|
|
|
void uart_poweron(uart_t uart)
|
|
{
|
|
(void)uart;
|
|
/* not implemented (yet) */
|
|
}
|
|
|
|
void uart_poweroff(uart_t uart)
|
|
{
|
|
(void)uart;
|
|
/* not implemented (yet) */
|
|
}
|
|
|
|
#if KINETIS_HAVE_UART && KINETIS_HAVE_LPUART
|
|
/* Dispatch function to pass to the proper write function depending on UART type
|
|
* This function is only used when the CPU supports both UART and LPUART. */
|
|
void uart_write(uart_t uart, const uint8_t *data, size_t len)
|
|
{
|
|
switch (uart_config[uart].type) {
|
|
case KINETIS_UART:
|
|
uart_write_uart(uart, data, len);
|
|
break;
|
|
case KINETIS_LPUART:
|
|
uart_write_lpuart(uart, data, len);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static inline void uart_init_pins(uart_t uart)
|
|
{
|
|
/* initialize pins */
|
|
if (uart_config[uart].pin_rx != GPIO_UNDEF) {
|
|
gpio_init_port(uart_config[uart].pin_rx, uart_config[uart].pcr_rx);
|
|
}
|
|
if (uart_config[uart].pin_tx != GPIO_UNDEF) {
|
|
gpio_init_port(uart_config[uart].pin_tx, uart_config[uart].pcr_tx);
|
|
}
|
|
}
|
|
|
|
#if KINETIS_HAVE_UART
|
|
static inline void uart_init_uart(uart_t uart, uint32_t baudrate)
|
|
{
|
|
/* do basic initialization */
|
|
UART_Type *dev = uart_config[uart].dev;
|
|
|
|
uint32_t clk;
|
|
uint16_t ubd;
|
|
|
|
clk = uart_config[uart].freq;
|
|
|
|
/* disable transmitter and receiver */
|
|
dev->C2 &= ~(UART_C2_TE_MASK | UART_C2_RE_MASK);
|
|
|
|
/* Select mode */
|
|
dev->C1 = uart_config[uart].mode;
|
|
|
|
/* calculate baudrate */
|
|
ubd = (uint16_t)(clk / (baudrate * 16));
|
|
|
|
/* set baudrate */
|
|
dev->BDH = (uint8_t)UART_BDH_SBR(ubd >> 8);
|
|
dev->BDL = (uint8_t)UART_BDL_SBR(ubd);
|
|
|
|
#if KINETIS_UART_ADVANCED
|
|
/* set baudrate fine adjust (brfa) */
|
|
uint8_t brfa = ((((4 * clk) / baudrate) + 1) / 2) % 32;
|
|
dev->C4 = UART_C4_BRFA(brfa);
|
|
|
|
/* Enable FIFO buffers */
|
|
dev->PFIFO |= UART_PFIFO_RXFE_MASK | UART_PFIFO_TXFE_MASK;
|
|
/* Set level to trigger TDRE flag whenever there is space in the TXFIFO */
|
|
/* FIFO size is 2^(PFIFO_TXFIFOSIZE + 1) (4, 8, 16 ...) for values != 0.
|
|
* TXFIFOSIZE == 0 means size = 1 (i.e. only one byte, no hardware FIFO) */
|
|
if ((dev->PFIFO & UART_PFIFO_TXFIFOSIZE_MASK) != 0) {
|
|
uint8_t txfifo_size =
|
|
(2 << ((dev->PFIFO & UART_PFIFO_TXFIFOSIZE_MASK) >>
|
|
UART_PFIFO_TXFIFOSIZE_SHIFT));
|
|
dev->TWFIFO = UART_TWFIFO_TXWATER(txfifo_size - 1);
|
|
}
|
|
else {
|
|
/* Missing hardware support */
|
|
dev->TWFIFO = 0;
|
|
}
|
|
/* Trigger RX interrupt when there is 1 byte or more in the RXFIFO */
|
|
dev->RWFIFO = 1;
|
|
/* Clear all hardware buffers now, this must be done whenever the FIFO
|
|
* enable flags are modified. */
|
|
dev->CFIFO = UART_CFIFO_RXFLUSH_MASK | UART_CFIFO_TXFLUSH_MASK;
|
|
#endif /* KINETIS_UART_ADVANCED */
|
|
|
|
/* enable transmitter and receiver + RX interrupt */
|
|
dev->C2 |= UART_C2_TE_MASK | UART_C2_RE_MASK | UART_C2_RIE_MASK;
|
|
|
|
/* enable receive interrupt */
|
|
NVIC_EnableIRQ(uart_config[uart].irqn);
|
|
}
|
|
#endif /* KINETIS_HAVE_UART */
|
|
|
|
#if KINETIS_HAVE_UART
|
|
KINETIS_UART_WRITE_INLINE void uart_write_uart(uart_t uart, const uint8_t *data, size_t len)
|
|
{
|
|
UART_Type *dev = uart_config[uart].dev;
|
|
|
|
for (size_t i = 0; i < len; i++) {
|
|
while (!(dev->S1 & UART_S1_TDRE_MASK)) {}
|
|
dev->D = data[i];
|
|
}
|
|
}
|
|
|
|
#if defined(UART_0_ISR) || defined(UART_1_ISR) || defined(UART_2_ISR) || \
|
|
defined(UART_3_ISR) || defined(UART_4_ISR)
|
|
static inline void irq_handler_uart(uart_t uart)
|
|
{
|
|
UART_Type *dev = uart_config[uart].dev;
|
|
|
|
/*
|
|
* On Cortex-M0, it happens that S1 is read with LDR
|
|
* instruction instead of LDRB. This will read the data register
|
|
* at the same time and arrived byte will be lost. Maybe it's a GCC bug.
|
|
*
|
|
* Observed with: arm-none-eabi-gcc (4.8.3-8+..)
|
|
* It does not happen with: arm-none-eabi-gcc (4.8.3-9+11)
|
|
*/
|
|
|
|
if (dev->S1 & UART_S1_RDRF_MASK) {
|
|
/* RDRF flag will be cleared when dev-D was read */
|
|
uint8_t data = dev->D;
|
|
|
|
if (config[uart].rx_cb != NULL) {
|
|
config[uart].rx_cb(config[uart].arg, data);
|
|
}
|
|
}
|
|
|
|
#if (KINETIS_UART_ADVANCED == 0)
|
|
/* clear overrun flag */
|
|
if (dev->S1 & UART_S1_OR_MASK) {
|
|
dev->S1 = UART_S1_OR_MASK;
|
|
}
|
|
#endif
|
|
|
|
cortexm_isr_end();
|
|
}
|
|
#endif
|
|
|
|
#ifdef UART_0_ISR
|
|
void UART_0_ISR(void)
|
|
{
|
|
irq_handler_uart(UART_DEV(0));
|
|
}
|
|
#endif
|
|
|
|
#ifdef UART_1_ISR
|
|
void UART_1_ISR(void)
|
|
{
|
|
irq_handler_uart(UART_DEV(1));
|
|
}
|
|
#endif
|
|
|
|
#ifdef UART_2_ISR
|
|
void UART_2_ISR(void)
|
|
{
|
|
irq_handler_uart(UART_DEV(2));
|
|
}
|
|
#endif
|
|
|
|
#ifdef UART_3_ISR
|
|
void UART_3_ISR(void)
|
|
{
|
|
irq_handler_uart(UART_DEV(3));
|
|
}
|
|
#endif
|
|
|
|
#ifdef UART_4_ISR
|
|
void UART_4_ISR(void)
|
|
{
|
|
irq_handler_uart(UART_DEV(4));
|
|
}
|
|
#endif
|
|
|
|
#endif /* KINETIS_HAVE_UART */
|
|
|
|
#if KINETIS_HAVE_LPUART
|
|
static inline void uart_init_lpuart(uart_t uart, uint32_t baudrate)
|
|
{
|
|
LPUART_Type *dev = uart_config[uart].dev;
|
|
|
|
/* Set LPUART clock source */
|
|
#ifdef SIM_SOPT2_LPUART0SRC
|
|
if (dev == LPUART0) {
|
|
SIM->SOPT2 = (SIM->SOPT2 & ~SIM_SOPT2_LPUART0SRC_MASK) |
|
|
SIM_SOPT2_LPUART0SRC(LPUART_0_SRC);
|
|
}
|
|
#endif
|
|
#ifdef SIM_SOPT2_LPUART1SRC
|
|
if (dev == LPUART1) {
|
|
SIM->SOPT2 = (SIM->SOPT2 & ~SIM_SOPT2_LPUART1SRC_MASK) |
|
|
SIM_SOPT2_LPUART1SRC(LPUART_1_SRC);
|
|
}
|
|
#endif
|
|
|
|
/* Select mode */
|
|
/* transmitter and receiver disabled */
|
|
dev->CTRL = uart_config[uart].mode;
|
|
|
|
/* calculate baud rate divisor */
|
|
uint32_t clk = uart_config[uart].freq;
|
|
uint32_t best_err = baudrate;
|
|
uint32_t best_osr = LPUART_OVERSAMPLING_RATE_MIN;
|
|
/* Use the oversampling rate as a baud rate fine adjust tool */
|
|
for (uint32_t osr = LPUART_OVERSAMPLING_RATE_MIN; osr <= LPUART_OVERSAMPLING_RATE_MAX; ++osr) {
|
|
uint32_t div = clk / (osr * baudrate);
|
|
uint32_t actual_baud = clk / (osr * div);
|
|
uint32_t err = ((actual_baud > baudrate) ? actual_baud - baudrate : baudrate - actual_baud);
|
|
if (err < best_err) {
|
|
best_err = err;
|
|
best_osr = osr;
|
|
}
|
|
}
|
|
|
|
uint32_t sbr = clk / (best_osr * baudrate);
|
|
/* set baud rate */
|
|
dev->BAUD = LPUART_BAUD_OSR(best_osr - 1) | LPUART_BAUD_SBR(sbr);
|
|
|
|
/* enable transmitter and receiver + RX interrupt */
|
|
dev->CTRL |= LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK | LPUART_CTRL_RIE_MASK;
|
|
|
|
/* enable receive interrupt */
|
|
NVIC_EnableIRQ(uart_config[uart].irqn);
|
|
}
|
|
|
|
KINETIS_UART_WRITE_INLINE void uart_write_lpuart(uart_t uart, const uint8_t *data, size_t len)
|
|
{
|
|
LPUART_Type *dev = uart_config[uart].dev;
|
|
|
|
for (size_t i = 0; i < len; i++) {
|
|
while ((dev->STAT & LPUART_STAT_TDRE_MASK) == 0) {}
|
|
dev->DATA = data[i];
|
|
}
|
|
}
|
|
|
|
#if defined(LPUART_0_ISR) || defined(LPUART_1_ISR) || defined(LPUART_2_ISR) || \
|
|
defined(LPUART_3_ISR) || defined(LPUART_4_ISR)
|
|
static inline void irq_handler_lpuart(uart_t uart)
|
|
{
|
|
LPUART_Type *dev = uart_config[uart].dev;
|
|
uint32_t stat = dev->STAT;
|
|
/* Clear all IRQ flags */
|
|
dev->STAT = stat;
|
|
|
|
if (stat & LPUART_STAT_RDRF_MASK) {
|
|
/* RDRF flag will be cleared when LPUART_DATA is read */
|
|
uint8_t data = dev->DATA;
|
|
if (stat & (LPUART_STAT_FE_MASK | LPUART_STAT_PF_MASK)) {
|
|
if (stat & LPUART_STAT_FE_MASK) {
|
|
DEBUG("LPUART framing error %08" PRIx32 "\n", stat);
|
|
}
|
|
if (stat & LPUART_STAT_PF_MASK) {
|
|
DEBUG("LPUART parity error %08" PRIx32 "\n", stat);
|
|
}
|
|
/* FE is set whenever the next character to be read from LPUART_DATA
|
|
* was received with logic 0 detected where a stop bit was expected. */
|
|
/* PF is set whenever the next character to be read from LPUART_DATA
|
|
* was received when parity is enabled (PE = 1) and the parity bit in
|
|
* the received character does not agree with the expected parity value. */
|
|
}
|
|
/* Only run callback if no error occurred */
|
|
else if (config[uart].rx_cb != NULL) {
|
|
config[uart].rx_cb(config[uart].arg, data);
|
|
}
|
|
}
|
|
if (stat & LPUART_STAT_OR_MASK) {
|
|
/* Input buffer overflow, means that the software was too slow to
|
|
* receive the data */
|
|
DEBUG("LPUART overrun %08" PRIx32 "\n", stat);
|
|
}
|
|
|
|
cortexm_isr_end();
|
|
}
|
|
#endif
|
|
|
|
#ifdef LPUART_0_ISR
|
|
void LPUART_0_ISR(void)
|
|
{
|
|
irq_handler_lpuart(UART_DEV(0));
|
|
}
|
|
#endif
|
|
|
|
#ifdef LPUART_1_ISR
|
|
void LPUART_1_ISR(void)
|
|
{
|
|
irq_handler_lpuart(UART_DEV(1));
|
|
}
|
|
#endif
|
|
|
|
#ifdef LPUART_2_ISR
|
|
void LPUART_2_ISR(void)
|
|
{
|
|
irq_handler_lpuart(UART_DEV(2));
|
|
}
|
|
#endif
|
|
|
|
#ifdef LPUART_3_ISR
|
|
void LPUART_3_ISR(void)
|
|
{
|
|
irq_handler_lpuart(UART_DEV(3));
|
|
}
|
|
#endif
|
|
|
|
#ifdef LPUART_4_ISR
|
|
void LPUART_4_ISR(void)
|
|
{
|
|
irq_handler_lpuart(UART_DEV(4));
|
|
}
|
|
#endif
|
|
|
|
#endif /* KINETIS_HAVE_LPUART */
|