Skip to content

Commit

Permalink
samd/machine_uart: Implement UART.IRQ_RXIDLE based on the softtimer.
Browse files Browse the repository at this point in the history
With the softtimer the minimal delay between the end of a message and the
trigger is 2 ms.  For baud rates <= 9600 baud it's three character times.
Tested with baud rates up tp 115200 baud.  The timer used for RXIDLE is
running only during UART receive, saving execution cycles when the timer is
not needed.

The irq.flags() value is changed only with an expected event.  Do not
change it otherwise.

Signed-off-by: robert-hh <[email protected]>
  • Loading branch information
robert-hh authored and dpgeorge committed Aug 29, 2024
1 parent a86619f commit ef69d0f
Showing 1 changed file with 82 additions and 7 deletions.
89 changes: 82 additions & 7 deletions ports/samd/machine_uart.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "py/ringbuf.h"
#include "samd_soc.h"
#include "pin_af.h"
#include "shared/runtime/softtimer.h"

#define DEFAULT_UART_BAUDRATE (115200)
#define DEFAULT_BUFFER_SIZE (256)
Expand All @@ -40,17 +41,31 @@
#define FLOW_CONTROL_RTS (1)
#define FLOW_CONTROL_CTS (2)

#define MP_UART_ALLOWED_FLAGS (SERCOM_USART_INTFLAG_RXC | SERCOM_USART_INTFLAG_TXC)

#if MICROPY_PY_MACHINE_UART_IRQ
#define UART_IRQ_RXIDLE (4096)
#define RXIDLE_TIMER_MIN (1)
#define MP_UART_ALLOWED_FLAGS (SERCOM_USART_INTFLAG_RXC | SERCOM_USART_INTFLAG_TXC | UART_IRQ_RXIDLE)

#define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS \
{ MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(SERCOM_USART_INTFLAG_RXC) }, \
{ MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_IRQ_RXIDLE) }, \
{ MP_ROM_QSTR(MP_QSTR_IRQ_TXIDLE), MP_ROM_INT(SERCOM_USART_INTFLAG_TXC) }, \

enum {
RXIDLE_INACTIVE,
RXIDLE_STANDBY,
RXIDLE_ARMED,
RXIDLE_ALERT,
};
#else
#define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS
#endif

typedef struct _soft_timer_entry_extended_t {
soft_timer_entry_t base;
void *context;
} soft_timer_entry_extended_t;

typedef struct _machine_uart_obj_t {
mp_obj_base_t base;
uint8_t id;
Expand Down Expand Up @@ -80,6 +95,9 @@ typedef struct _machine_uart_obj_t {
uint16_t mp_irq_trigger; // user IRQ trigger mask
uint16_t mp_irq_flags; // user IRQ active IRQ flags
mp_irq_obj_t *mp_irq_obj; // user IRQ object
soft_timer_entry_extended_t rxidle_timer;
uint8_t rxidle_state;
uint16_t rxidle_ms;
#endif
} machine_uart_obj_t;

Expand Down Expand Up @@ -108,14 +126,24 @@ void common_uart_irq_handler(int uart_id) {
if (self != NULL) {
Sercom *uart = sercom_instance[self->id];
#if MICROPY_PY_MACHINE_UART_IRQ
self->mp_irq_flags = 0;
uint16_t mp_irq_flags = 0;
#endif
if (uart->USART.INTFLAG.bit.RXC != 0) {
// Now handler the incoming data
uart_drain_rx_fifo(self, uart);
#if MICROPY_PY_MACHINE_UART_IRQ
if (ringbuf_avail(&self->read_buffer) > 0) {
self->mp_irq_flags = SERCOM_USART_INTFLAG_RXC;
if (self->mp_irq_trigger & UART_IRQ_RXIDLE) {
if (self->rxidle_state != RXIDLE_INACTIVE) {
if (self->rxidle_state == RXIDLE_STANDBY) {
self->rxidle_timer.base.mode = SOFT_TIMER_MODE_PERIODIC;
soft_timer_insert(&self->rxidle_timer.base, self->rxidle_ms);
}
self->rxidle_state = RXIDLE_ALERT;
}
} else {
mp_irq_flags = SERCOM_USART_INTFLAG_RXC;
}
}
#endif
} else if (uart->USART.INTFLAG.bit.DRE != 0) {
Expand All @@ -126,7 +154,7 @@ void common_uart_irq_handler(int uart_id) {
} else {
#if MICROPY_PY_MACHINE_UART_IRQ
// Set the TXIDLE flag
self->mp_irq_flags |= SERCOM_USART_INTFLAG_TXC;
mp_irq_flags |= SERCOM_USART_INTFLAG_TXC;
#endif
// Stop the DRE interrupt if there is no more data
uart->USART.INTENCLR.reg = SERCOM_USART_INTENCLR_DRE;
Expand All @@ -137,14 +165,34 @@ void common_uart_irq_handler(int uart_id) {
uart->USART.INTENCLR.reg = (uint8_t) ~(SERCOM_USART_INTENCLR_DRE | SERCOM_USART_INTENCLR_RXC);

#if MICROPY_PY_MACHINE_UART_IRQ
// Check the flags to see if the user handler should be called
if (self->mp_irq_trigger & self->mp_irq_flags) {
// Check the flags to see if the uart user handler should be called
// The handler for RXIDLE is called in the timer callback
if (self->mp_irq_trigger & mp_irq_flags) {
self->mp_irq_flags = mp_irq_flags;
mp_irq_handler(self->mp_irq_obj);
}
#endif
}
}

#if MICROPY_PY_MACHINE_UART_IRQ
static void uart_soft_timer_callback(soft_timer_entry_t *self) {
machine_uart_obj_t *uart = ((soft_timer_entry_extended_t *)self)->context;
if (uart->rxidle_state == RXIDLE_ALERT) {
// At the first call, just switch the state
uart->rxidle_state = RXIDLE_ARMED;
} else if (uart->rxidle_state == RXIDLE_ARMED) {
// At the second call, run the irq callback and stop the timer
// by setting the mode to SOFT_TIMER_MODE_ONE_SHOT.
// Calling soft_timer_remove() would fail here.
self->mode = SOFT_TIMER_MODE_ONE_SHOT;
uart->rxidle_state = RXIDLE_STANDBY;
uart->mp_irq_flags = UART_IRQ_RXIDLE;
mp_irq_handler(uart->mp_irq_obj);
}
}
#endif

// Configure the Sercom device
static void machine_sercom_configure(machine_uart_obj_t *self) {
Sercom *uart = sercom_instance[self->id];
Expand Down Expand Up @@ -424,6 +472,7 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg
#endif
#if MICROPY_PY_MACHINE_UART_IRQ
self->mp_irq_obj = NULL;
self->rxidle_state = RXIDLE_INACTIVE;
#endif
self->new = true;
MP_STATE_PORT(sercom_table[uart_id]) = self;
Expand Down Expand Up @@ -486,8 +535,33 @@ static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) {

#if MICROPY_PY_MACHINE_UART_IRQ

// Configure the timer used for IRQ_RXIDLE
static void uart_irq_configure_timer(machine_uart_obj_t *self, mp_uint_t trigger) {
self->rxidle_state = RXIDLE_INACTIVE;

if (trigger & UART_IRQ_RXIDLE) {
// The RXIDLE event is always a soft IRQ.
self->mp_irq_obj->ishard = false;
mp_int_t ms = 13000 / self->baudrate + 1;
if (ms < RXIDLE_TIMER_MIN) {
ms = RXIDLE_TIMER_MIN;
}
self->rxidle_ms = ms;
self->rxidle_timer.context = self;
soft_timer_static_init(
&self->rxidle_timer.base,
SOFT_TIMER_MODE_PERIODIC,
ms,
uart_soft_timer_callback
);
self->rxidle_state = RXIDLE_STANDBY;
}
}

static mp_uint_t uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) {
machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in);

uart_irq_configure_timer(self, new_trigger);
self->mp_irq_trigger = new_trigger;
return 0;
}
Expand Down Expand Up @@ -526,6 +600,7 @@ static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args
if (trigger != 0 && not_supported) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%04x unsupported"), not_supported);
}
uart_irq_configure_timer(self, trigger);

self->mp_irq_obj->handler = handler;
self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool;
Expand Down

0 comments on commit ef69d0f

Please sign in to comment.