1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

drivers/soft_uart: add software based UART implementation

This commit is contained in:
Benjamin Valentin 2020-06-18 16:40:46 +02:00 committed by Benjamin Valentin
parent 60def88929
commit 96c67b0fa5
7 changed files with 537 additions and 0 deletions

View File

@ -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

View File

@ -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

130
drivers/include/soft_uart.h Normal file
View File

@ -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 <benjjamin.valentin@ml-pa.com>
*/
#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 */
/** @} */

View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -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 <benjjamin.valentin@ml-pa.com>
*/
#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 */
/** @} */

View File

@ -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 <benjjamin.valentin@ml-pa.com>
*/
#include <stdio.h>
#include <assert.h>
#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 */
}

View File

@ -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