diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index cc728a0040..d5feed1f52 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -760,6 +760,11 @@ ifneq (,$(filter soft_spi,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter soft_uart,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio_irq + FEATURES_REQUIRED += periph_timer_periodic +endif + ifneq (,$(filter sps30,$(USEMODULE))) FEATURES_REQUIRED += periph_i2c USEMODULE += checksum diff --git a/drivers/Makefile.include b/drivers/Makefile.include index f5094341ab..a2d20bd3dd 100644 --- a/drivers/Makefile.include +++ b/drivers/Makefile.include @@ -364,6 +364,10 @@ ifneq (,$(filter soft_spi,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/soft_spi/include endif +ifneq (,$(filter soft_uart,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/drivers/soft_uart/include +endif + ifneq (,$(filter sps30,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/sps30/include endif diff --git a/drivers/include/soft_uart.h b/drivers/include/soft_uart.h new file mode 100644 index 0000000000..f2f30fc17d --- /dev/null +++ b/drivers/include/soft_uart.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 ML!PA Consulting GmbH + * + * 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. + */ + +/** + * @defgroup drivers_soft_uart Software UART + * @ingroup drivers_soft_periph + * @brief Software implemented UART + * + * This module provides a software implemented Universal Asynchronous Receiver Transmitter. + * It is intended to be used in situation where hardware UART is not available. + * The signatures of the functions are similar to the functions declared in uart.h + * + * Currently sending and receiving is not possible at the same time, so loopback operation + * is not possible. + * + * @{ + * + * @file + * @brief Software UART port descriptor definition + * + * @author Benjamin Valentin + */ + +#ifndef SOFT_UART_H +#define SOFT_UART_H + +#include "periph/gpio.h" +#include "periph/uart.h" +#include "periph/timer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Software UART port descriptor + */ +typedef struct { + gpio_t rx_pin; /**< RX pin */ + gpio_t tx_pin; /**< TX pin */ + tim_t rx_timer; /**< Hardware timer used for RX */ + tim_t tx_timer; /**< Hardware timer used for TX */ + uint32_t timer_freq; /**< Operating frequency of the timer. + Should be a multiple of baudrate */ +} soft_uart_conf_t; + +/** + * @brief Software UART type definition + */ +typedef unsigned soft_uart_t; + +/** + * @brief Initialize a given UART device + * + * The UART device will be initialized with the following configuration: + * - 8 data bits + * - no parity + * - 1 stop bit + * - baudrate as given + * + * If no callback parameter is given (rx_cb := NULL), the UART will be + * initialized in TX only mode. + * + * @param[in] uart UART device to initialize + * @param[in] baudrate desired symbol rate in baud + * @param[in] rx_cb receive callback, executed in interrupt context once + * for every byte that is received (RX buffer filled), + * set to NULL for TX only mode + * @param[in] arg optional context passed to the callback functions + * + * @return UART_OK on success + * @return UART_NODEV on invalid UART device + * @return UART_NOBAUD on inapplicable baudrate + * @return UART_INTERR on other errors + */ +int soft_uart_init(soft_uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg); + +/** + * @brief Setup parity, data and stop bits for a given UART device + * + * @param[in] uart UART device to configure + * @param[in] data_bits number of data bits in a UART frame + * @param[in] parity parity mode + * @param[in] stop_bits number of stop bits in a UART frame + * + * @return UART_OK on success + * @return UART_NOMODE on other errors + */ +int soft_uart_mode(soft_uart_t uart, uart_data_bits_t data_bits, uart_parity_t parity, + uart_stop_bits_t stop_bits); + +/** + * @brief Write data from the given buffer to the specified UART device + * + * This function is blocking, as it will only return after @p len bytes from the + * given buffer have been send. The way this data is send is up to the + * implementation: active waiting, interrupt driven, DMA, etc. + * + * @param[in] uart UART device to use for transmission + * @param[in] data data buffer to send + * @param[in] len number of bytes to send + * + */ +void soft_uart_write(soft_uart_t uart, const uint8_t *data, size_t len); + +/** + * @brief Power on the given UART device + * + * @param[in] uart the UART device to power on + */ +void soft_uart_poweron(soft_uart_t uart); + +/** + * @brief Power off the given UART device + * + * @param[in] uart the UART device to power off + */ +void soft_uart_poweroff(soft_uart_t uart); + +#ifdef __cplusplus +} +#endif + +#endif /* SOFT_UART_H */ +/** @} */ diff --git a/drivers/soft_uart/Makefile b/drivers/soft_uart/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/soft_uart/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/soft_uart/include/soft_uart_params.h b/drivers/soft_uart/include/soft_uart_params.h new file mode 100644 index 0000000000..3de62ccda3 --- /dev/null +++ b/drivers/soft_uart/include/soft_uart_params.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 ML!PA Consulting GmbH + * + * 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 drivers_soft_uart + * @{ + * + * @file + * @brief Software UART configuration + * + * @author Benjamin Valentin + */ + +#ifndef SOFT_UART_PARAMS_H +#define SOFT_UART_PARAMS_H + +#include "soft_uart.h" +#include "macros/units.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef SOFT_UART_PARAM_RX +#define SOFT_UART_PARAM_RX GPIO_UNDEF +#endif +#ifndef SOFT_UART_PARAM_TX +#define SOFT_UART_PARAM_TX GPIO_UNDEF +#endif +#ifndef SOFT_UART_PARAM_TIMER_RX +#define SOFT_UART_PARAM_TIMER_RX (0) +#endif +#ifndef SOFT_UART_PARAM_TIMER_TX +#define SOFT_UART_PARAM_TIMER_TX (1) +#endif +#ifndef SOFT_UART_PARAM_FREQ +#define SOFT_UART_PARAM_FREQ MHZ(1) +#endif + +#ifndef SOFT_UART_PARAMS +#define SOFT_UART_PARAMS { .rx_pin = SOFT_UART_PARAM_RX, \ + .tx_pin = SOFT_UART_PARAM_TX, \ + .rx_timer = SOFT_UART_PARAM_TIMER_RX, \ + .tx_timer = SOFT_UART_PARAM_TIMER_TX, \ + .timer_freq = SOFT_UART_PARAM_FREQ } +#endif + +/** + * @brief Sotware UART port descriptor array + */ +static const soft_uart_conf_t soft_uart_config[] = { + SOFT_UART_PARAMS, +}; + +#define SOFT_UART_NUMOF ARRAY_SIZE(soft_uart_config) + +#ifdef __cplusplus +} +#endif + +#endif /* SOFT_UART_PARAMS_H */ +/** @} */ diff --git a/drivers/soft_uart/soft_uart.c b/drivers/soft_uart/soft_uart.c new file mode 100644 index 0000000000..0140ec9c98 --- /dev/null +++ b/drivers/soft_uart/soft_uart.c @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2020 ML!PA Consulting GmbH + * + * 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 drivers_soft_uart + * @{ + * + * @file + * @brief Software UART implementation + * + * @author Benjamin Valentin + */ + +#include +#include + +#include "mutex.h" +#include "soft_uart.h" +#include "soft_uart_params.h" + +enum { + STATE_RX_IDLE, + STATE_RX_HIGH, + STATE_RX_LOW +}; + +enum { + PARITY_NONE, + PARITY_EVEN, + PARITY_ODD, + PARITY_MARK, + PARITY_SPACE, +}; + +struct uart_ctx { + mutex_t lock; /**< UART mutex */ + mutex_t sync; /**< TX byte done signal */ + uart_rx_cb_t rx_cb; /**< RX callback */ + void* rx_cb_arg; /**< RX callback arg */ + uint32_t bit_time; /**< timer ticks per bit */ + uint16_t byte_tx; /**< current TX byte */ + uint16_t byte_rx; /**< curretn RX byte */ + uint8_t bits_tx; /**< TX bit pos */ + uint8_t state_rx; /**< RX state */ +#ifdef MODULE_SOFT_UART_MODECFG + uint8_t data_bits; /**< number of data bits */ + uint8_t stop_bits; /**< number of stop bits */ + uint8_t parity; /**< parity mode */ +#endif +} soft_uart_ctx[SOFT_UART_NUMOF]; + +#ifdef MODULE_SOFT_UART_MODECFG +#define BITS_DATA(ctx) (ctx)->data_bits +#define BITS_STOP(ctx) (ctx)->stop_bits +#define BITS_PARITY(ctx) ((ctx)->parity != PARITY_NONE) +#else +#define BITS_DATA(ctx) 8 +#define BITS_STOP(ctx) 1 +#define BITS_PARITY(ctx) 0 +#endif + +static void _tx_timer_cb(void *arg, int chan) +{ + soft_uart_t uart = (soft_uart_t)arg; + + const soft_uart_conf_t *cfg = &soft_uart_config[uart]; + struct uart_ctx *ctx = &soft_uart_ctx[uart]; + + gpio_write(cfg->tx_pin, ctx->byte_tx & 1); + ctx->byte_tx >>= 1; + + if (--ctx->bits_tx == 0) { + timer_clear(cfg->tx_timer, chan); + mutex_unlock(&ctx->sync); + } +} + +static void _rx_timer_cb(void *arg, int chan) +{ + soft_uart_t uart = (soft_uart_t)arg; + + const soft_uart_conf_t *cfg = &soft_uart_config[uart]; + struct uart_ctx *ctx = &soft_uart_ctx[uart]; + + (void)chan; + + timer_stop(cfg->rx_timer); + + /* ignore spurious interrupts */ + if (ctx->state_rx == STATE_RX_IDLE) { + return; + } + + ctx->state_rx = STATE_RX_IDLE; + ctx->rx_cb(ctx->rx_cb_arg, ctx->byte_rx); +} + +static void _rx_gpio_cb(void *arg) +{ + soft_uart_t uart = (soft_uart_t)arg; + + const soft_uart_conf_t *cfg = &soft_uart_config[uart]; + struct uart_ctx *ctx = &soft_uart_ctx[uart]; + + /* TODO: use Timer Capture feature */ + const uint32_t now = timer_read(cfg->rx_timer); + + if (ctx->state_rx == STATE_RX_IDLE) { + timer_start(cfg->rx_timer); + ctx->state_rx = STATE_RX_LOW; + ctx->byte_rx = 0; + return; + } + + /* we only get interrupts on flanks, so all bits + * till the next interrupt will have the same level. */ + uint8_t bit = now / ctx->bit_time; + uint8_t mask = 0xff << bit; + + if (ctx->state_rx == STATE_RX_HIGH) { + ctx->byte_rx &= ~mask; + ctx->state_rx = STATE_RX_LOW; + } else { + ctx->byte_rx |= mask; + ctx->state_rx = STATE_RX_HIGH; + } +} + +int soft_uart_init(soft_uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg) +{ + if (uart >= SOFT_UART_NUMOF) { + return UART_NODEV; + } + + const soft_uart_conf_t *cfg = &soft_uart_config[uart]; + struct uart_ctx *ctx = &soft_uart_ctx[uart]; + + mutex_init(&ctx->lock); + static const mutex_t init_locked = MUTEX_INIT_LOCKED; + ctx->sync = init_locked; + + ctx->bit_time = (cfg->timer_freq + baudrate / 2) / baudrate; + + unsigned accuracy = (100 * cfg->timer_freq / ctx->bit_time) / baudrate; + if (accuracy > 110 || accuracy < 90) { + return UART_NOBAUD; + } + + if (cfg->rx_pin == GPIO_UNDEF) { + rx_cb = NULL; + } + +#ifdef MODULE_SOFT_UART_MODECFG + ctx->data_bits = 8; + ctx->stop_bits = 1; + ctx->parity = PARITY_NONE; +#endif + + ctx->rx_cb = rx_cb; + ctx->rx_cb_arg = arg; + + ctx->state_rx = STATE_RX_IDLE; + + if (cfg->tx_pin != GPIO_UNDEF) { + timer_init(cfg->tx_timer, cfg->timer_freq, _tx_timer_cb, (void *)uart); + gpio_write(cfg->tx_pin, !(cfg->flags & SOFT_UART_FLAG_INVERT_TX)); + gpio_init(cfg->tx_pin, GPIO_OUT); + } + + if (rx_cb) { + timer_init(cfg->rx_timer, cfg->timer_freq, _rx_timer_cb, (void *)uart); + timer_stop(cfg->rx_timer); + /* timer should fire at the end of the byte */ + timer_set_periodic(cfg->rx_timer, 0, ctx->bit_time * (BITS_DATA(ctx) + BITS_PARITY(ctx) + 1), + TIM_FLAG_RESET_ON_MATCH | TIM_FLAG_RESET_ON_SET); + gpio_init_int(cfg->rx_pin, GPIO_IN, GPIO_BOTH, _rx_gpio_cb, (void*) uart); + } + + return 0; +} + +#ifdef MODULE_SOFT_UART_MODECFG +int soft_uart_mode(soft_uart_t uart, uart_data_bits_t data_bits, uart_parity_t parity, + uart_stop_bits_t stop_bits) +{ + if (uart >= SOFT_UART_NUMOF) { + return UART_NODEV; + } + + struct uart_ctx *ctx = &soft_uart_ctx[uart]; + + switch (data_bits) { + case UART_DATA_BITS_5: + ctx->data_bits = 5; + break; + case UART_DATA_BITS_6: + ctx->data_bits = 6; + break; + case UART_DATA_BITS_7: + ctx->data_bits = 7; + break; + default: + case UART_DATA_BITS_8: + ctx->data_bits = 8; + break; + } + + switch (parity) { + case UART_PARITY_EVEN: + ctx->parity = PARITY_EVEN; + break; + case UART_PARITY_ODD: + ctx->parity = PARITY_ODD; + break; + case UART_PARITY_MARK: + ctx->parity = PARITY_MARK; + break; + case UART_PARITY_SPACE: + ctx->parity = PARITY_SPACE; + break; + default: + case UART_PARITY_NONE: + ctx->parity = PARITY_NONE; + break; + } + + switch (stop_bits) { + case UART_STOP_BITS_2: + ctx->stop_bits = 2; + break; + default: + case UART_STOP_BITS_1: + ctx->stop_bits = 1; + break; + } + + return 0; +} +#endif /* MODULE_SOFT_UART_MODECF */ + +static void soft_uart_write_byte(soft_uart_t uart, uint8_t data) +{ + const soft_uart_conf_t *cfg = &soft_uart_config[uart]; + struct uart_ctx *ctx = &soft_uart_ctx[uart]; + + /* start bit (LOW) + data bits */ + ctx->bits_tx = 1 + BITS_DATA(ctx); + ctx->byte_tx = data << 1; + +#ifdef MODULE_SOFT_UART_MODECFG + if (ctx->parity != PARITY_NONE) { + uint8_t parity = 0; + + switch (ctx->parity) { + case PARITY_EVEN: + parity = __builtin_parity(data); + break; + case PARITY_ODD: + parity = !__builtin_parity(data); + break; + case PARITY_MARK: + parity = 1; + break; + case PARITY_SPACE: + parity = 0; + break; + } + + ctx->byte_tx |= parity << ctx->bits_tx++; + } +#endif + + for (int i = 0; i < BITS_STOP(ctx); ++i) { + ctx->byte_tx |= 1 << ctx->bits_tx++; + } + + if (cfg->flags & SOFT_UART_FLAG_INVERT_TX) { + ctx->byte_tx = ~ctx->byte_tx; + } + + timer_set_periodic(cfg->tx_timer, 0, ctx->bit_time, + TIM_FLAG_RESET_ON_MATCH | TIM_FLAG_RESET_ON_SET); + mutex_lock(&ctx->sync); +} + +void soft_uart_write(uart_t uart, const uint8_t *data, size_t len) +{ + const soft_uart_conf_t *cfg = &soft_uart_config[uart]; + struct uart_ctx *ctx = &soft_uart_ctx[uart]; + + const uint8_t *end = data + len; + + mutex_lock(&ctx->lock); + timer_start(cfg->tx_timer); + + while (data != end) { + soft_uart_write_byte(uart, *data++); + } + + timer_stop(cfg->tx_timer); + mutex_unlock(&soft_uart_ctx[uart].lock); +} + +void soft_uart_poweron(soft_uart_t uart) +{ + const soft_uart_conf_t *cfg = &soft_uart_config[uart]; + struct uart_ctx *ctx = &soft_uart_ctx[uart]; + + if (ctx->rx_cb) { + gpio_irq_enable(cfg->rx_pin); + } +} + +void soft_uart_poweroff(soft_uart_t uart) +{ + const soft_uart_conf_t *cfg = &soft_uart_config[uart]; + struct uart_ctx *ctx = &soft_uart_ctx[uart]; + + if (ctx->rx_cb) { + gpio_irq_disable(cfg->rx_pin); + } + + /* timers are already stopped after RX/TX */ +} diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index be3ef61738..4cf00c3ab4 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -105,6 +105,7 @@ PSEUDOMODULES += sock_dtls PSEUDOMODULES += sock_ip PSEUDOMODULES += sock_tcp PSEUDOMODULES += sock_udp +PSEUDOMODULES += soft_uart_modecfg PSEUDOMODULES += stdin PSEUDOMODULES += stdio_ethos PSEUDOMODULES += stdio_cdc_acm