rp2/machine_uart: Add buffered transfer of data with rxbuf/txbuf kwargs.

Instantiation and init now support the rxbuf and txbuf keywords for setting
the buffer size.  The default size is 256 bytes.  The minimum and maximum
sizes are 32 and 32766 respectively.

uart.write() still includes checks for timeout, even if it is very unlikely
to happen due to a) lack of flow control support and b) the minimal timeout
values being longer than the time it needs to send a byte.
This commit is contained in:
robert-hh 2021-03-11 08:36:16 +01:00 committed by Damien George
parent 22554cf8e2
commit 1be74b94b6
2 changed files with 147 additions and 14 deletions

View File

@ -28,9 +28,12 @@
#include "py/stream.h"
#include "py/mphal.h"
#include "py/mperrno.h"
#include "py/ringbuf.h"
#include "modmachine.h"
#include "hardware/irq.h"
#include "hardware/uart.h"
#include "hardware/regs/uart.h"
#define DEFAULT_UART_BAUDRATE (115200)
#define DEFAULT_UART_BITS (8)
@ -39,6 +42,9 @@
#define DEFAULT_UART0_RX (1)
#define DEFAULT_UART1_TX (4)
#define DEFAULT_UART1_RX (5)
#define DEFAULT_BUFFER_SIZE (256)
#define MIN_BUFFER_SIZE (32)
#define MAX_BUFFER_SIZE (32766)
#define IS_VALID_PERIPH(uart, pin) (((((pin) + 4) & 8) >> 3) == (uart))
#define IS_VALID_TX(uart, pin) (((pin) & 3) == 0 && IS_VALID_PERIPH(uart, pin))
@ -61,28 +67,81 @@ typedef struct _machine_uart_obj_t {
uint16_t timeout; // timeout waiting for first char (in ms)
uint16_t timeout_char; // timeout waiting between chars (in ms)
uint8_t invert;
ringbuf_t read_buffer;
bool read_lock;
ringbuf_t write_buffer;
bool write_lock;
} machine_uart_obj_t;
STATIC machine_uart_obj_t machine_uart_obj[] = {
{{&machine_uart_type}, uart0, 0, 0, DEFAULT_UART_BITS, UART_PARITY_NONE, DEFAULT_UART_STOP, DEFAULT_UART0_TX, DEFAULT_UART0_RX, 0, 0, 0},
{{&machine_uart_type}, uart1, 1, 0, DEFAULT_UART_BITS, UART_PARITY_NONE, DEFAULT_UART_STOP, DEFAULT_UART1_TX, DEFAULT_UART1_RX, 0, 0, 0},
{{&machine_uart_type}, uart0, 0, 0, DEFAULT_UART_BITS, UART_PARITY_NONE, DEFAULT_UART_STOP,
DEFAULT_UART0_TX, DEFAULT_UART0_RX, 0, 0, 0, {NULL, 1, 0, 0}, 0, {NULL, 1, 0, 0}, 0},
{{&machine_uart_type}, uart1, 1, 0, DEFAULT_UART_BITS, UART_PARITY_NONE, DEFAULT_UART_STOP,
DEFAULT_UART1_TX, DEFAULT_UART1_RX, 0, 0, 0, {NULL, 1, 0, 0}, 0, {NULL, 1, 0, 0}, 0,},
};
STATIC const char *_parity_name[] = {"None", "0", "1"};
STATIC const char *_invert_name[] = {"None", "INV_TX", "INV_RX", "INV_TX|INV_RX"};
/******************************************************************************/
// IRQ and buffer handling
// take all bytes from the fifo and store them, if possible, in the buffer
STATIC void uart_drain_rx_fifo(machine_uart_obj_t *self) {
while (uart_is_readable(self->uart)) {
// try to write the data, ignore the fail
ringbuf_put(&(self->read_buffer), uart_get_hw(self->uart)->dr);
}
}
// take bytes from the buffer and put them into the UART FIFO
STATIC void uart_fill_tx_fifo(machine_uart_obj_t *self) {
while (uart_is_writable(self->uart) && ringbuf_avail(&self->write_buffer) > 0) {
// get a byte from the buffer and put it into the uart
uart_get_hw(self->uart)->dr = ringbuf_get(&(self->write_buffer));
}
}
STATIC inline void uart_service_interrupt(machine_uart_obj_t *self) {
if (uart_get_hw(self->uart)->mis & UART_UARTMIS_RXMIS_BITS) { // rx interrupt?
// clear all interrupt bits but tx
uart_get_hw(self->uart)->icr = UART_UARTICR_BITS & (~UART_UARTICR_TXIC_BITS);
if (!self->read_lock) {
uart_drain_rx_fifo(self);
}
}
if (uart_get_hw(self->uart)->mis & UART_UARTMIS_TXMIS_BITS) { // tx interrupt?
// clear all interrupt bits but rx
uart_get_hw(self->uart)->icr = UART_UARTICR_BITS & (~UART_UARTICR_RXIC_BITS);
if (!self->write_lock) {
uart_fill_tx_fifo(self);
}
}
}
STATIC void uart0_irq_handler(void) {
uart_service_interrupt(&machine_uart_obj[0]);
}
STATIC void uart1_irq_handler(void) {
uart_service_interrupt(&machine_uart_obj[1]);
}
/******************************************************************************/
// MicroPython bindings for UART
STATIC void machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, timeout=%u, timeout_char=%u, invert=%s)",
mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, "
"txbuf=%d, rxbuf=%d, timeout=%u, timeout_char=%u, invert=%s)",
self->uart_id, self->baudrate, self->bits, _parity_name[self->parity],
self->stop, self->tx, self->rx, self->timeout, self->timeout_char, _invert_name[self->invert]);
self->stop, self->tx, self->rx, self->write_buffer.size - 1, self->read_buffer.size - 1,
self->timeout, self->timeout_char, _invert_name[self->invert]);
}
STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
enum { ARG_id, ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_timeout, ARG_timeout_char, ARG_invert };
enum { ARG_id, ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx,
ARG_timeout, ARG_timeout_char, ARG_invert, ARG_txbuf, ARG_rxbuf};
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_baudrate, MP_ARG_INT, {.u_int = -1} },
@ -94,6 +153,8 @@ STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args,
{ MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_txbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
};
// Parse args.
@ -169,6 +230,30 @@ STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args,
self->invert = args[ARG_invert].u_int;
}
self->read_lock = false;
// Set the RX buffer size if configured.
size_t rxbuf_len = DEFAULT_BUFFER_SIZE;
if (args[ARG_rxbuf].u_int > 0) {
rxbuf_len = args[ARG_rxbuf].u_int;
if (rxbuf_len < MIN_BUFFER_SIZE) {
rxbuf_len = MIN_BUFFER_SIZE;
} else if (rxbuf_len > MAX_BUFFER_SIZE) {
mp_raise_ValueError(MP_ERROR_TEXT("rxbuf too large"));
}
}
// Set the TX buffer size if configured.
size_t txbuf_len = DEFAULT_BUFFER_SIZE;
if (args[ARG_txbuf].u_int > 0) {
txbuf_len = args[ARG_txbuf].u_int;
if (txbuf_len < MIN_BUFFER_SIZE) {
txbuf_len = MIN_BUFFER_SIZE;
} else if (txbuf_len > MAX_BUFFER_SIZE) {
mp_raise_ValueError(MP_ERROR_TEXT("txbuf too large"));
}
}
// Initialise the UART peripheral if any arguments given, or it was not initialised previously.
if (n_args > 1 || n_kw > 0 || self->baudrate == 0) {
if (self->baudrate == 0) {
@ -192,6 +277,25 @@ STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args,
if (self->invert & UART_INVERT_TX) {
gpio_set_outover(self->tx, GPIO_OVERRIDE_INVERT);
}
// Allocate the RX/TX buffers.
ringbuf_alloc(&(self->read_buffer), rxbuf_len + 1);
MP_STATE_PORT(rp2_uart_rx_buffer[uart_id]) = self->read_buffer.buf;
ringbuf_alloc(&(self->write_buffer), txbuf_len + 1);
MP_STATE_PORT(rp2_uart_tx_buffer[uart_id]) = self->write_buffer.buf;
// Set the irq handler.
if (self->uart_id == 0) {
irq_set_exclusive_handler(UART0_IRQ, uart0_irq_handler);
irq_set_enabled(UART0_IRQ, true);
} else {
irq_set_exclusive_handler(UART1_IRQ, uart1_irq_handler);
irq_set_enabled(UART1_IRQ, true);
}
// Enable the uart irq; this macro sets the rx irq level to 4.
uart_set_irq_enables(self->uart, true, true);
}
return MP_OBJ_FROM_PTR(self);
@ -199,7 +303,11 @@ STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args,
STATIC mp_obj_t machine_uart_any(mp_obj_t self_in) {
machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in);
return MP_OBJ_NEW_SMALL_INT(uart_is_readable(self->uart));
// get all bytes from the fifo first
self->read_lock = true;
uart_drain_rx_fifo(self);
self->read_lock = false;
return MP_OBJ_NEW_SMALL_INT(ringbuf_avail(&self->read_buffer));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_any_obj, machine_uart_any);
@ -236,7 +344,7 @@ STATIC mp_uint_t machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t siz
for (size_t i = 0; i < size; i++) {
// Wait for the first/next character
while (!uart_is_readable(self->uart)) {
while (ringbuf_avail(&self->read_buffer) == 0) {
if (time_us_64() > t) { // timed out
if (i <= 0) {
*errcode = MP_EAGAIN;
@ -246,8 +354,12 @@ STATIC mp_uint_t machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t siz
}
}
MICROPY_EVENT_POLL_HOOK
// Force a few incoming bytes to the buffer
self->read_lock = true;
uart_drain_rx_fifo(self);
self->read_lock = false;
}
*dest++ = uart_get_hw(self->uart)->dr;
*dest++ = ringbuf_get(&(self->read_buffer));
t = time_us_64() + timeout_char_us;
}
return size;
@ -258,10 +370,23 @@ STATIC mp_uint_t machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_uin
uint64_t t = time_us_64() + (uint64_t)self->timeout * 1000;
uint64_t timeout_char_us = (uint64_t)self->timeout_char * 1000;
const uint8_t *src = buf_in;
size_t i = 0;
for (size_t i = 0; i < size; i++) {
// wait for the first/next character
while (!uart_is_writable(self->uart)) {
// Put as many bytes as possible into the transmit buffer.
while (i < size && ringbuf_free(&(self->write_buffer)) > 0) {
ringbuf_put(&(self->write_buffer), *src++);
++i;
}
// Kickstart the UART transmit.
self->write_lock = true;
uart_fill_tx_fifo(self);
self->write_lock = false;
// Send the next characters while busy waiting.
while (i < size) {
// Wait for the first/next character to be sent.
while (ringbuf_free(&(self->write_buffer)) == 0) {
if (time_us_64() > t) { // timed out
if (i <= 0) {
*errcode = MP_EAGAIN;
@ -272,9 +397,15 @@ STATIC mp_uint_t machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_uin
}
MICROPY_EVENT_POLL_HOOK
}
uart_get_hw(self->uart)->dr = *src++;
ringbuf_put(&(self->write_buffer), *src++);
++i;
t = time_us_64() + timeout_char_us;
self->write_lock = true;
uart_fill_tx_fifo(self);
self->write_lock = false;
}
// Just in case the fifo was drained during refill of the ringbuf.
return size;
}
@ -284,10 +415,10 @@ STATIC mp_uint_t machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint
if (request == MP_STREAM_POLL) {
uintptr_t flags = arg;
ret = 0;
if ((flags & MP_STREAM_POLL_RD) && uart_is_readable(self->uart)) {
if ((flags & MP_STREAM_POLL_RD) && ringbuf_avail(&self->read_buffer) > 0) {
ret |= MP_STREAM_POLL_RD;
}
if ((flags & MP_STREAM_POLL_WR) && uart_is_writable(self->uart)) {
if ((flags & MP_STREAM_POLL_WR) && ringbuf_free(&self->write_buffer) > 0) {
ret |= MP_STREAM_POLL_WR;
}
} else {

View File

@ -174,6 +174,8 @@ extern const struct _mp_obj_module_t mp_module_utime;
void *machine_pin_irq_obj[30]; \
void *rp2_pio_irq_obj[2]; \
void *rp2_state_machine_irq_obj[8]; \
void *rp2_uart_rx_buffer[2]; \
void *rp2_uart_tx_buffer[2]; \
#define MP_STATE_PORT MP_STATE_VM