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:
parent
60def88929
commit
96c67b0fa5
@ -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
|
||||
|
@ -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
130
drivers/include/soft_uart.h
Normal 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 */
|
||||
/** @} */
|
1
drivers/soft_uart/Makefile
Normal file
1
drivers/soft_uart/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
67
drivers/soft_uart/include/soft_uart_params.h
Normal file
67
drivers/soft_uart/include/soft_uart_params.h
Normal 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 */
|
||||
/** @} */
|
329
drivers/soft_uart/soft_uart.c
Normal file
329
drivers/soft_uart/soft_uart.c
Normal 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 */
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user