From 01c046d2a803ead4d320ed533938fcc878a5959e Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 20 Feb 2024 18:52:57 +0100 Subject: [PATCH] rp2/machine_uart: Implement a Python UART IRQ handler. Supported trigger names: IRQ_RXIDLE, IRQ_TXIDLE, IRQ_BREAK - IRQ_RXIDLE: The handler for IRQ_RXIDLE is called reliably 31 UART bit times after the last incoming data. - IRQ_TXIDLE: This IRQ is triggered after at least >5 characters are sent at once. It is triggered when the TX FIFO falls below 4 elements. At that time, up to 5 bytes may still be in the FIFO and output shift register. - IRQ_BREAK: The IRQ triggers if a BREAK state is detected at RX. Properties & side effects: - After a BREAK, a valid character must be received before another break can be detected. - Each break puts a 0xff character into the input buffer. The irq.flags() value is cleared only with a new wanted event. Do not change the flags otherwise. Signed-off-by: robert-hh --- ports/rp2/machine_uart.c | 136 ++++++++++++++++++++++++++++++++++----- ports/rp2/main.c | 1 + ports/rp2/modmachine.h | 1 + ports/rp2/mpconfigport.h | 1 + 4 files changed, 124 insertions(+), 15 deletions(-) diff --git a/ports/rp2/machine_uart.c b/ports/rp2/machine_uart.c index d9e97685f..689906003 100644 --- a/ports/rp2/machine_uart.c +++ b/ports/rp2/machine_uart.c @@ -80,6 +80,10 @@ #define UART_HWCONTROL_CTS (1) #define UART_HWCONTROL_RTS (2) +// OR-ed IRQ flags which are allowed to be used by the user +#define MP_UART_ALLOWED_FLAGS (UART_UARTMIS_RTMIS_BITS | UART_UARTMIS_TXMIS_BITS | UART_UARTMIS_BEMIS_BITS) +#define UART_FIFO_SIZE_RX (32) +#define UART_FIFO_TRIGGER_LEVEL_RX (24) static mutex_t write_mutex_0; static mutex_t write_mutex_1; @@ -111,12 +115,15 @@ typedef struct _machine_uart_obj_t { mutex_t *read_mutex; ringbuf_t write_buffer; mutex_t *write_mutex; + 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 } 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, MICROPY_HW_UART0_TX, MICROPY_HW_UART0_RX, MICROPY_HW_UART0_CTS, MICROPY_HW_UART0_RTS, - 0, 0, 0, 0, {NULL, 1, 0, 0}, &read_mutex_0, {NULL, 1, 0, 0}, &write_mutex_0}, + 0, 0, 0, 0, {NULL, 1, 0, 0}, &read_mutex_0, {NULL, 1, 0, 0}, &write_mutex_0, 0, 0, NULL}, {{&machine_uart_type}, uart1, 1, 0, DEFAULT_UART_BITS, UART_PARITY_NONE, DEFAULT_UART_STOP, MICROPY_HW_UART1_TX, MICROPY_HW_UART1_RX, MICROPY_HW_UART1_CTS, MICROPY_HW_UART1_RTS, 0, 0, 0, 0, {NULL, 1, 0, 0}, &read_mutex_1, {NULL, 1, 0, 0}, &write_mutex_1}, @@ -144,14 +151,15 @@ static inline void read_mutex_unlock(machine_uart_obj_t *u) { mutex_exit(u->read_mutex); } -// take all bytes from the fifo and store them in the buffer -static void uart_drain_rx_fifo(machine_uart_obj_t *self) { +// take at most max_items bytes from the fifo and store them in the buffer +static void uart_drain_rx_fifo(machine_uart_obj_t *self, uint32_t max_items) { if (read_mutex_try_lock(self)) { - while (uart_is_readable(self->uart) && ringbuf_free(&self->read_buffer) > 0) { + while (uart_is_readable(self->uart) && ringbuf_free(&self->read_buffer) > 0 && max_items > 0) { // Get a byte from uart and put into the buffer. Every entry from // the FIFO is accompanied by 4 error bits, that may be used for // error handling. uint16_t c = uart_get_hw(self->uart)->dr; + max_items -= 1; if (c & UART_UARTDR_OE_BITS) { // Overrun Error: We missed at least one byte. Not much we can do here. } @@ -187,15 +195,30 @@ static void uart_fill_tx_fifo(machine_uart_obj_t *self) { } static inline void uart_service_interrupt(machine_uart_obj_t *self) { - if (uart_get_hw(self->uart)->mis & (UART_UARTMIS_RXMIS_BITS | UART_UARTMIS_RTMIS_BITS)) { // rx interrupt? - // clear all interrupt bits but tx - uart_get_hw(self->uart)->icr = UART_UARTICR_BITS & (~UART_UARTICR_TXIC_BITS); - uart_drain_rx_fifo(self); + uint16_t mp_irq_flags = uart_get_hw(self->uart)->mis & (UART_UARTMIS_RXMIS_BITS | UART_UARTMIS_RTMIS_BITS); + if (mp_irq_flags) { // rx interrupt? + // clear all interrupt bits but tx and break + uart_get_hw(self->uart)->icr = UART_UARTICR_BITS & ~(UART_UARTICR_TXIC_BITS | UART_UARTICR_BEIC_BITS); + uart_drain_rx_fifo(self, UART_FIFO_TRIGGER_LEVEL_RX - 1); } 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 | UART_UARTICR_RTIC_BITS); - uart_fill_tx_fifo(self); + // clear all interrupt bits but rx and break + uart_get_hw(self->uart)->icr = UART_UARTICR_BITS & ~(UART_UARTICR_RXIC_BITS | UART_UARTICR_RTIC_BITS | UART_UARTICR_BEIC_BITS); + if (ringbuf_avail(&self->write_buffer) == 0) { + mp_irq_flags |= UART_UARTMIS_TXMIS_BITS; + } else { + uart_fill_tx_fifo(self); + } + } + if (uart_get_hw(self->uart)->mis & UART_UARTMIS_BEMIS_BITS) { // break interrupt? + // CLear the event + hw_set_bits(&uart_get_hw(self->uart)->icr, UART_UARTICR_BEIC_BITS); + mp_irq_flags |= UART_UARTMIS_BEMIS_BITS; + } + // Check the flags to see if the user handler should be called + if (self->mp_irq_trigger & mp_irq_flags) { + self->mp_irq_flags = mp_irq_flags; + mp_irq_handler(self->mp_irq_obj); } } @@ -215,14 +238,17 @@ static void uart1_irq_handler(void) { { MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_INVERT_RX) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HWCONTROL_CTS) }, \ { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HWCONTROL_RTS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_UARTMIS_RTMIS_BITS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_TXIDLE), MP_ROM_INT(UART_UARTMIS_TXMIS_BITS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_BREAK), MP_ROM_INT(UART_UARTMIS_BEMIS_BITS) }, \ static void mp_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, " - "txbuf=%d, rxbuf=%d, timeout=%u, timeout_char=%u, invert=%s)", + "txbuf=%d, rxbuf=%d, timeout=%u, timeout_char=%u, invert=%s, irq=%d)", self->uart_id, self->baudrate, self->bits, _parity_name[self->parity], 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]); + self->timeout, self->timeout_char, _invert_name[self->invert], self->mp_irq_trigger); } static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -441,11 +467,19 @@ static void mp_machine_uart_deinit(machine_uart_obj_t *self) { self->baudrate = 0; MP_STATE_PORT(rp2_uart_rx_buffer[self->uart_id]) = NULL; MP_STATE_PORT(rp2_uart_tx_buffer[self->uart_id]) = NULL; + MP_STATE_PORT(rp2_uart_irq_obj)[self->uart_id] = NULL; + self->mp_irq_obj = NULL; + self->mp_irq_trigger = 0; +} + +void machine_uart_deinit_all() { + mp_machine_uart_deinit((machine_uart_obj_t *)&machine_uart_obj[0]); + mp_machine_uart_deinit((machine_uart_obj_t *)&machine_uart_obj[1]); } static mp_int_t mp_machine_uart_any(machine_uart_obj_t *self) { // get all bytes from the fifo first - uart_drain_rx_fifo(self); + uart_drain_rx_fifo(self, UART_FIFO_SIZE_RX + 1); return ringbuf_avail(&self->read_buffer); } @@ -460,6 +494,77 @@ static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) { uart_set_break(self->uart, false); } +static void uart_set_irq_level(machine_uart_obj_t *self, uint16_t trigger) { + if (trigger & UART_UARTMIS_BEMIS_BITS) { + // Enable the break Interrupt + hw_set_bits(&uart_get_hw(self->uart)->imsc, UART_UARTIMSC_BEIM_BITS); + } else { + // Disable the break Interrupt + hw_clear_bits(&uart_get_hw(self->uart)->imsc, UART_UARTIMSC_BEIM_BITS); + } + if (trigger & UART_UARTMIS_RTMIS_BITS) { + // Set the RX trigger level to 3/4 FIFO_size + hw_write_masked(&uart_get_hw(self->uart)->ifls, 0b011 << UART_UARTIFLS_RXIFLSEL_LSB, + UART_UARTIFLS_RXIFLSEL_BITS); + } else { + // Set the RX trigger level to 1/8 FIFO_size + hw_write_masked(&uart_get_hw(self->uart)->ifls, 0 << UART_UARTIFLS_RXIFLSEL_LSB, + UART_UARTIFLS_RXIFLSEL_BITS); + } +} + +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); + self->mp_irq_trigger = new_trigger; + uart_set_irq_level(self, new_trigger); + return 0; +} + +static mp_uint_t uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return self->mp_irq_flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->mp_irq_trigger; + } + return 0; +} + +static const mp_irq_methods_t uart_irq_methods = { + .trigger = uart_irq_trigger, + .info = uart_irq_info, +}; + +static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { + if (self->mp_irq_obj == NULL) { + self->mp_irq_trigger = 0; + self->mp_irq_obj = mp_irq_new(&uart_irq_methods, MP_OBJ_FROM_PTR(self)); + MP_STATE_PORT(rp2_uart_irq_obj)[self->uart_id] = self->mp_irq_obj; + } + + if (any_args) { + // Check the handler + mp_obj_t handler = args[MP_IRQ_ARG_INIT_handler].u_obj; + if (handler != mp_const_none && !mp_obj_is_callable(handler)) { + mp_raise_ValueError(MP_ERROR_TEXT("handler must be None or callable")); + } + + // Check the trigger + mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; + mp_uint_t not_supported = trigger & ~MP_UART_ALLOWED_FLAGS; + if (trigger != 0 && not_supported) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%04x unsupported"), not_supported); + } + + self->mp_irq_obj->handler = handler; + self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; + self->mp_irq_trigger = trigger; + uart_set_irq_level(self, trigger); + } + + return self->mp_irq_obj; +} + static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_uint_t start = mp_hal_ticks_ms(); @@ -471,7 +576,7 @@ static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t while (ringbuf_avail(&self->read_buffer) == 0) { if (uart_is_readable(self->uart)) { // Force a few incoming bytes to the buffer - uart_drain_rx_fifo(self); + uart_drain_rx_fifo(self, UART_FIFO_SIZE_RX + 1); break; } mp_uint_t elapsed = mp_hal_ticks_ms() - start; @@ -572,3 +677,4 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint MP_REGISTER_ROOT_POINTER(void *rp2_uart_rx_buffer[2]); MP_REGISTER_ROOT_POINTER(void *rp2_uart_tx_buffer[2]); +MP_REGISTER_ROOT_POINTER(void *rp2_uart_irq_obj[2]); diff --git a/ports/rp2/main.c b/ports/rp2/main.c index fa5495bf4..98417e8da 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -224,6 +224,7 @@ int main(int argc, char **argv) { #endif machine_pwm_deinit_all(); machine_pin_deinit(); + machine_uart_deinit_all(); #if MICROPY_PY_THREAD mp_thread_deinit(); #endif diff --git a/ports/rp2/modmachine.h b/ports/rp2/modmachine.h index e17ad67b0..1ed812aff 100644 --- a/ports/rp2/modmachine.h +++ b/ports/rp2/modmachine.h @@ -8,6 +8,7 @@ void machine_pin_deinit(void); void machine_i2s_init0(void); void machine_i2s_deinit_all(void); void machine_pwm_deinit_all(void); +void machine_uart_deinit_all(void); struct _machine_spi_obj_t *spi_from_mp_obj(mp_obj_t o); diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index abe25d700..a428721d7 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -146,6 +146,7 @@ #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/rp2/machine_uart.c" #define MICROPY_PY_MACHINE_UART_SENDBREAK (1) +#define MICROPY_PY_MACHINE_UART_IRQ (1) #define MICROPY_PY_MACHINE_WDT (1) #define MICROPY_PY_MACHINE_WDT_INCLUDEFILE "ports/rp2/machine_wdt.c" #define MICROPY_PY_MACHINE_FREQ_NUM_ARGS_MAX (2)