mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
3feb1a369b
Fixed compilation errors for pointer casting.
329 lines
8.1 KiB
C
329 lines
8.1 KiB
C
/*
|
|
* 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 <benjamin.valentin@ml-pa.com>
|
|
*/
|
|
|
|
#include <stdio.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; /**< current 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)(uintptr_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)(uintptr_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)(uintptr_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 (!gpio_is_valid(cfg->rx_pin)) {
|
|
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 (gpio_is_valid(cfg->tx_pin)) {
|
|
timer_init(cfg->tx_timer, cfg->timer_freq, _tx_timer_cb, (void *)(uintptr_t)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 *)(uintptr_t)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*)(uintptr_t)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(soft_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 */
|
|
}
|