summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrobert-hh <robert@hammelrath.com>2024-06-27 19:37:54 +0200
committerDamien George <damien@micropython.org>2024-08-29 16:27:43 +1000
commitef69d0f2d3a795666c2bf8c7e202604fc5cd5342 (patch)
treecc4b6260c8deb4b77a9161e34a147fc9171681a7
parenta86619fb6ffb4c860f4c1cc25643f13c6db92467 (diff)
samd/machine_uart: Implement UART.IRQ_RXIDLE based on the softtimer.
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 <robert@hammelrath.com>
-rw-r--r--ports/samd/machine_uart.c89
1 files changed, 82 insertions, 7 deletions
diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c
index aa781a170..b0dc4c576 100644
--- a/ports/samd/machine_uart.c
+++ b/ports/samd/machine_uart.c
@@ -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)
@@ -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;
@@ -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;
@@ -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) {
@@ -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;
@@ -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];
@@ -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;
@@ -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;
}
@@ -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;