1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/drivers/soft_uart/soft_uart.c
Frederik Haxel 3feb1a369b drivers: 64 bit compatibility
Fixed compilation errors for pointer casting.
2024-01-17 16:05:12 +01:00

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 */
}