From 01c046d2a803ead4d320ed533938fcc878a5959e Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 20 Feb 2024 18:52:57 +0100 Subject: [PATCH 01/16] 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 d9e97685f1f25..689906003e7dc 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 fa5495bf4100d..98417e8da6b0b 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 e17ad67b0301d..1ed812afff8f5 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 abe25d7009e1e..a428721d763da 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) From 8e1123b25bca23a3334211546d4164c9c343ec5b Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 22 Feb 2024 13:21:06 +0100 Subject: [PATCH 02/16] samd/machine_uart: Implement a Python UART IRQ handler. Supported for all SAMD51 devices and SAMD21 with external flash. For interrupt events, IRQ_RX and IRQ_TXIDLE are provided. IRQ_RX is called for every received byte. This may not be useful for high data rates, but can be used to build a wrapper class providing an IRQ_RXIDLE event or to signal just the first byte of a message. IRQ_TXIDLE is called only when messages are longer than 5 bytes and triggers when still 5 bytes are due to be sent. The SAMD hardware does not support implementing IRQ_RXIDLE. Signed-off-by: robert-hh --- ports/samd/machine_uart.c | 123 ++++++++++++++++++++++++++-- ports/samd/mcu/samd21/mpconfigmcu.h | 1 + ports/samd/mcu/samd51/mpconfigmcu.h | 1 + 3 files changed, 118 insertions(+), 7 deletions(-) diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index b560e0e8e6924..aa781a17073ae 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -40,7 +40,16 @@ #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 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_TXIDLE), MP_ROM_INT(SERCOM_USART_INTFLAG_TXC) }, \ + +#else #define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS +#endif typedef struct _machine_uart_obj_t { mp_obj_base_t base; @@ -67,6 +76,11 @@ typedef struct _machine_uart_obj_t { #if MICROPY_HW_UART_TXBUF ringbuf_t write_buffer; #endif + #if MICROPY_PY_MACHINE_UART_IRQ + 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 + #endif } machine_uart_obj_t; static const char *_parity_name[] = {"None", "", "0", "1"}; // Is defined as 0, 2, 3 @@ -93,23 +107,41 @@ void common_uart_irq_handler(int uart_id) { // Handle IRQ if (self != NULL) { Sercom *uart = sercom_instance[self->id]; + #if MICROPY_PY_MACHINE_UART_IRQ + self->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; + } + #endif } else if (uart->USART.INTFLAG.bit.DRE != 0) { #if MICROPY_HW_UART_TXBUF // handle the outgoing data if (ringbuf_avail(&self->write_buffer) > 0) { uart->USART.DATA.bit.DATA = ringbuf_get(&self->write_buffer); } else { - // Stop the interrupt if there is no more data + #if MICROPY_PY_MACHINE_UART_IRQ + // Set the TXIDLE flag + self->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; } #endif - } else { - // Disable the other interrupts, if set by error - uart->USART.INTENCLR.reg = (uint8_t) ~(SERCOM_USART_INTENCLR_DRE | SERCOM_USART_INTENCLR_RXC); } + // Disable the other interrupts, if set by error + 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) { + mp_irq_handler(self->mp_irq_obj); + } + #endif } } @@ -202,6 +234,9 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ #if MICROPY_HW_UART_RTSCTS ", rts=%q, cts=%q" #endif + #if MICROPY_PY_MACHINE_UART_IRQ + ", irq=%d" + #endif ")", self->id, self->baudrate, self->bits, _parity_name[self->parity], self->stop + 1, self->timeout, self->timeout_char, self->read_buffer.size - 1 @@ -212,6 +247,9 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ , self->rts != 0xff ? pin_find_by_id(self->rts)->name : MP_QSTR_None , self->cts != 0xff ? pin_find_by_id(self->cts)->name : MP_QSTR_None #endif + #if MICROPY_PY_MACHINE_UART_IRQ + , self->mp_irq_trigger + #endif ); } @@ -228,9 +266,7 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, 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_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - #if MICROPY_HW_UART_TXBUF { MP_QSTR_txbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - #endif #if MICROPY_HW_UART_RTSCTS { MP_QSTR_rts, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_cts, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, @@ -386,6 +422,9 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->rts = 0xff; self->cts = 0xff; #endif + #if MICROPY_PY_MACHINE_UART_IRQ + self->mp_irq_obj = NULL; + #endif self->new = true; MP_STATE_PORT(sercom_table[uart_id]) = self; @@ -445,6 +484,59 @@ static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) { mp_hal_set_pin_mux(self->tx, self->tx_pad_config.alt_fct); } +#if MICROPY_PY_MACHINE_UART_IRQ + +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; + 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)); + } + + 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; + } + + return self->mp_irq_obj; +} + +#endif + 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); Sercom *uart = sercom_instance[self->id]; @@ -481,9 +573,17 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ const uint8_t *src = buf_in; Sercom *uart = sercom_instance[self->id]; - #if MICROPY_HW_UART_TXBUF uint64_t t = mp_hal_ticks_ms_64() + self->timeout; + #if MICROPY_HW_UART_TXBUF + #if MICROPY_PY_MACHINE_UART_IRQ + // Prefill the FIFO to get rid of the initial IRQ_TXIDLE event + while (i < size && ringbuf_free(&(self->write_buffer)) > 0) { + ringbuf_put(&(self->write_buffer), *src++); + i++; + } + uart->USART.INTENSET.reg = SERCOM_USART_INTENSET_DRE; // kick off the IRQ + #endif while (i < size) { // Wait for the first/next character to be sent. while (ringbuf_free(&(self->write_buffer)) == 0) { @@ -506,6 +606,15 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ while (i < size) { while (!(uart->USART.INTFLAG.bit.DRE)) { + if (mp_hal_ticks_ms_64() > t) { // timed out + if (i <= 0) { + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; + } else { + return i; + } + } + MICROPY_EVENT_POLL_HOOK } uart->USART.DATA.bit.DATA = *src++; i++; diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index fbb5b28e2b95a..29965f50f63a8 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -51,6 +51,7 @@ unsigned long trng_random_u32(int delay); #ifndef MICROPY_HW_UART_RTSCTS #define MICROPY_HW_UART_RTSCTS (SAMD21_EXTRA_FEATURES) #endif +#define MICROPY_PY_MACHINE_UART_IRQ (SAMD21_EXTRA_FEATURES) // selected extensions of the extra features set #define MICROPY_PY_OS_URANDOM (1) diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index d567f28eb423a..9a7b8528f3573 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -15,6 +15,7 @@ #define MICROPY_PY_ONEWIRE (1) #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (trng_random_u32()) unsigned long trng_random_u32(void); +#define MICROPY_PY_MACHINE_UART_IRQ (1) // fatfs configuration used in ffconf.h #define MICROPY_FATFS_ENABLE_LFN (1) From b7fa4e2fc87481180c7e36e80f2518856bfa0540 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 24 Sep 2023 21:42:06 +0200 Subject: [PATCH 03/16] mimxrt/machine_uart: Implement a Python UART IRQ handler. Supported triggers are: IRQ_RXIDLE and IRQ_TXIDLE. When IRQ_RXIDLE is set, the handler will be called 3 character times after the data in burst stopped. When IRQ_TXIDLE is set, the handler will be called immediately after the data has been sent. This commit requires a change to fsl_lpuart.c, because the existing code does not support under-run appropriately. The irq.flags() value is cleared only at an expected event. Do not change it otherwise. Signed-off-by: robert-hh --- ports/mimxrt/Makefile | 2 +- ports/mimxrt/hal/fsl_lpuart.c | 2013 +++++++++++++++++++++++++++++++++ ports/mimxrt/machine_uart.c | 83 +- ports/mimxrt/mpconfigport.h | 1 + 4 files changed, 2094 insertions(+), 5 deletions(-) create mode 100644 ports/mimxrt/hal/fsl_lpuart.c diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 75cf9caec962c..e98073d33626d 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -132,7 +132,6 @@ SRC_HAL_IMX_C += \ $(MCU_DIR)/drivers/fsl_lpi2c.c \ $(MCU_DIR)/drivers/fsl_lpspi.c \ $(MCU_DIR)/drivers/fsl_lpspi_edma.c \ - $(MCU_DIR)/drivers/fsl_lpuart.c \ $(MCU_DIR)/drivers/fsl_pit.c \ $(MCU_DIR)/drivers/fsl_pwm.c \ $(MCU_DIR)/drivers/fsl_sai.c \ @@ -191,6 +190,7 @@ SRC_C += \ eth.c \ fatfs_port.c \ flash.c \ + hal/fsl_lpuart.c \ hal/pwm_backport.c \ help.c \ led.c \ diff --git a/ports/mimxrt/hal/fsl_lpuart.c b/ports/mimxrt/hal/fsl_lpuart.c new file mode 100644 index 0000000000000..54651c3efbcd4 --- /dev/null +++ b/ports/mimxrt/hal/fsl_lpuart.c @@ -0,0 +1,2013 @@ +/* + * Copyright (c) 2015-2016, Freescale Semiconductor, Inc. + * Copyright 2016-2020 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "fsl_lpuart.h" + +/******************************************************************************* + * Definitions + ******************************************************************************/ + +/* Component ID definition, used by tools. */ +#ifndef FSL_COMPONENT_ID +#define FSL_COMPONENT_ID "platform.drivers.lpuart" +#endif + +/* LPUART transfer state. */ +enum +{ + kLPUART_TxIdle, /*!< TX idle. */ + kLPUART_TxBusy, /*!< TX busy. */ + kLPUART_RxIdle, /*!< RX idle. */ + kLPUART_RxBusy /*!< RX busy. */ +}; + +/******************************************************************************* + * Prototypes + ******************************************************************************/ +/*! + * @brief Check whether the RX ring buffer is full. + * + * @userData handle LPUART handle pointer. + * @retval true RX ring buffer is full. + * @retval false RX ring buffer is not full. + */ +static bool LPUART_TransferIsRxRingBufferFull(LPUART_Type *base, lpuart_handle_t *handle); + +/*! + * @brief Write to TX register using non-blocking method. + * + * This function writes data to the TX register directly, upper layer must make + * sure the TX register is empty or TX FIFO has empty room before calling this function. + * + * @note This function does not check whether all the data has been sent out to bus, + * so before disable TX, check kLPUART_TransmissionCompleteFlag to ensure the TX is + * finished. + * + * @param base LPUART peripheral base address. + * @param data Start address of the data to write. + * @param length Size of the buffer to be sent. + */ +static void LPUART_WriteNonBlocking(LPUART_Type *base, const uint8_t *data, size_t length); + +/*! + * @brief Read RX register using non-blocking method. + * + * This function reads data from the TX register directly, upper layer must make + * sure the RX register is full or TX FIFO has data before calling this function. + * + * @param base LPUART peripheral base address. + * @param data Start address of the buffer to store the received data. + * @param length Size of the buffer. + */ +static void LPUART_ReadNonBlocking(LPUART_Type *base, uint8_t *data, size_t length); + +/******************************************************************************* + * Variables + ******************************************************************************/ +/* Array of LPUART peripheral base address. */ +static LPUART_Type *const s_lpuartBases[] = LPUART_BASE_PTRS; +/* Array of LPUART handle. */ +void *s_lpuartHandle[ARRAY_SIZE(s_lpuartBases)]; +/* Array of LPUART IRQ number. */ +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +static const IRQn_Type s_lpuartRxIRQ[] = LPUART_RX_IRQS; +const IRQn_Type s_lpuartTxIRQ[] = LPUART_TX_IRQS; +#else +const IRQn_Type s_lpuartIRQ[] = LPUART_RX_TX_IRQS; +#endif +#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) +/* Array of LPUART clock name. */ +static const clock_ip_name_t s_lpuartClock[] = LPUART_CLOCKS; + +#if defined(LPUART_PERIPH_CLOCKS) +/* Array of LPUART functional clock name. */ +static const clock_ip_name_t s_lpuartPeriphClocks[] = LPUART_PERIPH_CLOCKS; +#endif + +#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */ + +/* LPUART ISR for transactional APIs. */ +#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) +lpuart_isr_t s_lpuartIsr = (lpuart_isr_t)DefaultISR; +#else +lpuart_isr_t s_lpuartIsr; +#endif + +/******************************************************************************* + * Code + ******************************************************************************/ +/*! + * brief Get the LPUART instance from peripheral base address. + * + * param base LPUART peripheral base address. + * return LPUART instance. + */ +uint32_t LPUART_GetInstance(LPUART_Type *base) { + uint32_t instance; + + /* Find the instance index from base address mappings. */ + for (instance = 0U; instance < ARRAY_SIZE(s_lpuartBases); instance++) { + if (s_lpuartBases[instance] == base) { + break; + } + } + + assert(instance < ARRAY_SIZE(s_lpuartBases)); + + return instance; +} + +/*! + * brief Get the length of received data in RX ring buffer. + * + * userData handle LPUART handle pointer. + * return Length of received data in RX ring buffer. + */ +size_t LPUART_TransferGetRxRingBufferLength(LPUART_Type *base, lpuart_handle_t *handle) { + assert(NULL != handle); + + size_t size; + size_t tmpRxRingBufferSize = handle->rxRingBufferSize; + uint16_t tmpRxRingBufferTail = handle->rxRingBufferTail; + uint16_t tmpRxRingBufferHead = handle->rxRingBufferHead; + + if (tmpRxRingBufferTail > tmpRxRingBufferHead) { + size = ((size_t)tmpRxRingBufferHead + tmpRxRingBufferSize - (size_t)tmpRxRingBufferTail); + } else { + size = ((size_t)tmpRxRingBufferHead - (size_t)tmpRxRingBufferTail); + } + + return size; +} + +static bool LPUART_TransferIsRxRingBufferFull(LPUART_Type *base, lpuart_handle_t *handle) { + assert(NULL != handle); + + bool full; + + if (LPUART_TransferGetRxRingBufferLength(base, handle) == (handle->rxRingBufferSize - 1U)) { + full = true; + } else { + full = false; + } + return full; +} + +static void LPUART_WriteNonBlocking(LPUART_Type *base, const uint8_t *data, size_t length) { + assert(NULL != data); + + size_t i; + + /* The Non Blocking write data API assume user have ensured there is enough space in + peripheral to write. */ + for (i = 0; i < length; i++) { + base->DATA = data[i]; + } +} + +static void LPUART_ReadNonBlocking(LPUART_Type *base, uint8_t *data, size_t length) { + assert(NULL != data); + + size_t i; + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + uint32_t ctrl = base->CTRL; + bool isSevenDataBits = (((ctrl & LPUART_CTRL_M7_MASK) != 0U) || + (((ctrl & LPUART_CTRL_M_MASK) == 0U) && ((ctrl & LPUART_CTRL_PE_MASK) != 0U))); + #endif + + /* The Non Blocking read data API assume user have ensured there is enough space in + peripheral to write. */ + for (i = 0; i < length; i++) { + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + if (isSevenDataBits) { + data[i] = (uint8_t)(base->DATA & 0x7FU); + } else { + data[i] = (uint8_t)base->DATA; + } + #else + data[i] = (uint8_t)(base->DATA); + #endif + } +} + +/*! + * brief Initializes an LPUART instance with the user configuration structure and the peripheral clock. + * + * This function configures the LPUART module with user-defined settings. Call the LPUART_GetDefaultConfig() function + * to configure the configuration structure and get the default configuration. + * The example below shows how to use this API to configure the LPUART. + * code + * lpuart_config_t lpuartConfig; + * lpuartConfig.baudRate_Bps = 115200U; + * lpuartConfig.parityMode = kLPUART_ParityDisabled; + * lpuartConfig.dataBitsCount = kLPUART_EightDataBits; + * lpuartConfig.isMsb = false; + * lpuartConfig.stopBitCount = kLPUART_OneStopBit; + * lpuartConfig.txFifoWatermark = 0; + * lpuartConfig.rxFifoWatermark = 1; + * LPUART_Init(LPUART1, &lpuartConfig, 20000000U); + * endcode + * + * param base LPUART peripheral base address. + * param config Pointer to a user-defined configuration structure. + * param srcClock_Hz LPUART clock source frequency in HZ. + * retval kStatus_LPUART_BaudrateNotSupport Baudrate is not support in current clock source. + * retval kStatus_Success LPUART initialize succeed + */ +status_t LPUART_Init(LPUART_Type *base, const lpuart_config_t *config, uint32_t srcClock_Hz) { + assert(NULL != config); + assert(0U < config->baudRate_Bps); + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + assert((uint8_t)FSL_FEATURE_LPUART_FIFO_SIZEn(base) >= config->txFifoWatermark); + assert((uint8_t)FSL_FEATURE_LPUART_FIFO_SIZEn(base) >= config->rxFifoWatermark); + #endif + + status_t status = kStatus_Success; + uint32_t temp; + uint16_t sbr, sbrTemp; + uint8_t osr, osrTemp; + uint32_t tempDiff, calculatedBaud, baudDiff; + + /* This LPUART instantiation uses a slightly different baud rate calculation + * The idea is to use the best OSR (over-sampling rate) possible + * Note, OSR is typically hard-set to 16 in other LPUART instantiations + * loop to find the best OSR value possible, one that generates minimum baudDiff + * iterate through the rest of the supported values of OSR */ + + baudDiff = config->baudRate_Bps; + osr = 0U; + sbr = 0U; + for (osrTemp = 4U; osrTemp <= 32U; osrTemp++) { + /* calculate the temporary sbr value */ + sbrTemp = (uint16_t)((srcClock_Hz * 10U / (config->baudRate_Bps * (uint32_t)osrTemp) + 5U) / 10U); + /*set sbrTemp to 1 if the sourceClockInHz can not satisfy the desired baud rate*/ + if (sbrTemp == 0U) { + sbrTemp = 1U; + } + /* Calculate the baud rate based on the temporary OSR and SBR values */ + calculatedBaud = (srcClock_Hz / ((uint32_t)osrTemp * (uint32_t)sbrTemp)); + tempDiff = calculatedBaud > config->baudRate_Bps ? (calculatedBaud - config->baudRate_Bps) : + (config->baudRate_Bps - calculatedBaud); + + if (tempDiff <= baudDiff) { + baudDiff = tempDiff; + osr = osrTemp; /* update and store the best OSR value calculated */ + sbr = sbrTemp; /* update store the best SBR value calculated */ + } + } + + /* Check to see if actual baud rate is within 3% of desired baud rate + * based on the best calculate OSR value */ + if (baudDiff > ((config->baudRate_Bps / 100U) * 3U)) { + /* Unacceptable baud rate difference of more than 3%*/ + status = kStatus_LPUART_BaudrateNotSupport; + } else { + #if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) + + uint32_t instance = LPUART_GetInstance(base); + + /* Enable lpuart clock */ + (void)CLOCK_EnableClock(s_lpuartClock[instance]); + #if defined(LPUART_PERIPH_CLOCKS) + (void)CLOCK_EnableClock(s_lpuartPeriphClocks[instance]); + #endif + + #endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */ + + #if defined(FSL_FEATURE_LPUART_HAS_GLOBAL) && FSL_FEATURE_LPUART_HAS_GLOBAL + /*Reset all internal logic and registers, except the Global Register */ + LPUART_SoftwareReset(base); + #else + /* Disable LPUART TX RX before setting. */ + base->CTRL &= ~(LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK); + #endif + + temp = base->BAUD; + + /* Acceptable baud rate, check if OSR is between 4x and 7x oversampling. + * If so, then "BOTHEDGE" sampling must be turned on */ + if ((osr > 3U) && (osr < 8U)) { + temp |= LPUART_BAUD_BOTHEDGE_MASK; + } + + /* program the osr value (bit value is one less than actual value) */ + temp &= ~LPUART_BAUD_OSR_MASK; + temp |= LPUART_BAUD_OSR((uint32_t)osr - 1UL); + + /* write the sbr value to the BAUD registers */ + temp &= ~LPUART_BAUD_SBR_MASK; + base->BAUD = temp | LPUART_BAUD_SBR(sbr); + + /* Set bit count and parity mode. */ + base->BAUD &= ~LPUART_BAUD_M10_MASK; + + temp = base->CTRL & ~(LPUART_CTRL_PE_MASK | LPUART_CTRL_PT_MASK | LPUART_CTRL_M_MASK | LPUART_CTRL_ILT_MASK | + LPUART_CTRL_IDLECFG_MASK); + + temp |= (uint8_t)config->parityMode | LPUART_CTRL_IDLECFG(config->rxIdleConfig) | + LPUART_CTRL_ILT(config->rxIdleType); + + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + if (kLPUART_SevenDataBits == config->dataBitsCount) { + if (kLPUART_ParityDisabled != config->parityMode) { + temp &= ~LPUART_CTRL_M7_MASK; /* Seven data bits and one parity bit */ + } else { + temp |= LPUART_CTRL_M7_MASK; + } + } else + #endif + { + if (kLPUART_ParityDisabled != config->parityMode) { + temp |= LPUART_CTRL_M_MASK; /* Eight data bits and one parity bit */ + } + } + + base->CTRL = temp; + + #if defined(FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT) && FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT + /* set stop bit per char */ + temp = base->BAUD & ~LPUART_BAUD_SBNS_MASK; + base->BAUD = temp | LPUART_BAUD_SBNS((uint8_t)config->stopBitCount); + #endif + + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Set tx/rx WATER watermark + Note: + Take care of the RX FIFO, RX interrupt request only assert when received bytes + equal or more than RX water mark, there is potential issue if RX water + mark larger than 1. + For example, if RX FIFO water mark is 2, upper layer needs 5 bytes and + 5 bytes are received. the last byte will be saved in FIFO but not trigger + RX interrupt because the water mark is 2. + */ + base->WATER = (((uint32_t)(config->rxFifoWatermark) << 16U) | config->txFifoWatermark); + + /* Enable tx/rx FIFO */ + base->FIFO |= (LPUART_FIFO_TXFE_MASK | LPUART_FIFO_RXFE_MASK); + + /* Flush FIFO */ + base->FIFO |= (LPUART_FIFO_TXFLUSH_MASK | LPUART_FIFO_RXFLUSH_MASK); + #endif + + /* Clear all status flags */ + temp = (LPUART_STAT_RXEDGIF_MASK | LPUART_STAT_IDLE_MASK | LPUART_STAT_OR_MASK | LPUART_STAT_NF_MASK | + LPUART_STAT_FE_MASK | LPUART_STAT_PF_MASK); + + #if defined(FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT) && FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT + temp |= LPUART_STAT_LBKDIF_MASK; + #endif + + #if defined(FSL_FEATURE_LPUART_HAS_ADDRESS_MATCHING) && FSL_FEATURE_LPUART_HAS_ADDRESS_MATCHING + temp |= (LPUART_STAT_MA1F_MASK | LPUART_STAT_MA2F_MASK); + #endif + + #if defined(FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT) && FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT + /* Set the CTS configuration/TX CTS source. */ + base->MODIR |= LPUART_MODIR_TXCTSC(config->txCtsConfig) | LPUART_MODIR_TXCTSSRC(config->txCtsSource); + if (true == config->enableRxRTS) { + /* Enable the receiver RTS(request-to-send) function. */ + base->MODIR |= LPUART_MODIR_RXRTSE_MASK; + } + if (true == config->enableTxCTS) { + /* Enable the CTS(clear-to-send) function. */ + base->MODIR |= LPUART_MODIR_TXCTSE_MASK; + } + #endif + + /* Set data bits order. */ + if (true == config->isMsb) { + temp |= LPUART_STAT_MSBF_MASK; + } else { + temp &= ~LPUART_STAT_MSBF_MASK; + } + + base->STAT |= temp; + + /* Enable TX/RX base on configure structure. */ + temp = base->CTRL; + if (true == config->enableTx) { + temp |= LPUART_CTRL_TE_MASK; + } + + if (true == config->enableRx) { + temp |= LPUART_CTRL_RE_MASK; + } + + base->CTRL = temp; + } + + return status; +} +/*! + * brief Deinitializes a LPUART instance. + * + * This function waits for transmit to complete, disables TX and RX, and disables the LPUART clock. + * + * param base LPUART peripheral base address. + */ +void LPUART_Deinit(LPUART_Type *base) { + uint32_t temp; + + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Wait tx FIFO send out*/ + while (0U != ((base->WATER & LPUART_WATER_TXCOUNT_MASK) >> LPUART_WATER_TXWATER_SHIFT)) { + } + #endif + /* Wait last char shift out */ + while (0U == (base->STAT & LPUART_STAT_TC_MASK)) { + } + + /* Clear all status flags */ + temp = (LPUART_STAT_RXEDGIF_MASK | LPUART_STAT_IDLE_MASK | LPUART_STAT_OR_MASK | LPUART_STAT_NF_MASK | + LPUART_STAT_FE_MASK | LPUART_STAT_PF_MASK); + + #if defined(FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT) && FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT + temp |= LPUART_STAT_LBKDIF_MASK; + #endif + + #if defined(FSL_FEATURE_LPUART_HAS_ADDRESS_MATCHING) && FSL_FEATURE_LPUART_HAS_ADDRESS_MATCHING + temp |= (LPUART_STAT_MA1F_MASK | LPUART_STAT_MA2F_MASK); + #endif + + base->STAT |= temp; + + /* Disable the module. */ + base->CTRL = 0U; + + #if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) + uint32_t instance = LPUART_GetInstance(base); + + /* Disable lpuart clock */ + (void)CLOCK_DisableClock(s_lpuartClock[instance]); + + #if defined(LPUART_PERIPH_CLOCKS) + (void)CLOCK_DisableClock(s_lpuartPeriphClocks[instance]); + #endif + + #endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */ +} + +/*! + * brief Gets the default configuration structure. + * + * This function initializes the LPUART configuration structure to a default value. The default + * values are: + * lpuartConfig->baudRate_Bps = 115200U; + * lpuartConfig->parityMode = kLPUART_ParityDisabled; + * lpuartConfig->dataBitsCount = kLPUART_EightDataBits; + * lpuartConfig->isMsb = false; + * lpuartConfig->stopBitCount = kLPUART_OneStopBit; + * lpuartConfig->txFifoWatermark = 0; + * lpuartConfig->rxFifoWatermark = 1; + * lpuartConfig->rxIdleType = kLPUART_IdleTypeStartBit; + * lpuartConfig->rxIdleConfig = kLPUART_IdleCharacter1; + * lpuartConfig->enableTx = false; + * lpuartConfig->enableRx = false; + * + * param config Pointer to a configuration structure. + */ +void LPUART_GetDefaultConfig(lpuart_config_t *config) { + assert(NULL != config); + + /* Initializes the configure structure to zero. */ + (void)memset(config, 0, sizeof(*config)); + + config->baudRate_Bps = 115200U; + config->parityMode = kLPUART_ParityDisabled; + config->dataBitsCount = kLPUART_EightDataBits; + config->isMsb = false; + #if defined(FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT) && FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT + config->stopBitCount = kLPUART_OneStopBit; + #endif + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + config->txFifoWatermark = 0U; + config->rxFifoWatermark = 0U; + #endif + #if defined(FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT) && FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT + config->enableRxRTS = false; + config->enableTxCTS = false; + config->txCtsConfig = kLPUART_CtsSampleAtStart; + config->txCtsSource = kLPUART_CtsSourcePin; + #endif + config->rxIdleType = kLPUART_IdleTypeStartBit; + config->rxIdleConfig = kLPUART_IdleCharacter1; + config->enableTx = false; + config->enableRx = false; +} + +/*! + * brief Sets the LPUART instance baudrate. + * + * This function configures the LPUART module baudrate. This function is used to update + * the LPUART module baudrate after the LPUART module is initialized by the LPUART_Init. + * code + * LPUART_SetBaudRate(LPUART1, 115200U, 20000000U); + * endcode + * + * param base LPUART peripheral base address. + * param baudRate_Bps LPUART baudrate to be set. + * param srcClock_Hz LPUART clock source frequency in HZ. + * retval kStatus_LPUART_BaudrateNotSupport Baudrate is not supported in the current clock source. + * retval kStatus_Success Set baudrate succeeded. + */ +status_t LPUART_SetBaudRate(LPUART_Type *base, uint32_t baudRate_Bps, uint32_t srcClock_Hz) { + assert(0U < baudRate_Bps); + + status_t status = kStatus_Success; + uint32_t temp, oldCtrl; + uint16_t sbr, sbrTemp; + uint8_t osr, osrTemp; + uint32_t tempDiff, calculatedBaud, baudDiff; + + /* This LPUART instantiation uses a slightly different baud rate calculation + * The idea is to use the best OSR (over-sampling rate) possible + * Note, OSR is typically hard-set to 16 in other LPUART instantiations + * loop to find the best OSR value possible, one that generates minimum baudDiff + * iterate through the rest of the supported values of OSR */ + + baudDiff = baudRate_Bps; + osr = 0U; + sbr = 0U; + for (osrTemp = 4U; osrTemp <= 32U; osrTemp++) { + /* calculate the temporary sbr value */ + sbrTemp = (uint16_t)((srcClock_Hz * 10U / (baudRate_Bps * (uint32_t)osrTemp) + 5U) / 10U); + /*set sbrTemp to 1 if the sourceClockInHz can not satisfy the desired baud rate*/ + if (sbrTemp == 0U) { + sbrTemp = 1U; + } + /* Calculate the baud rate based on the temporary OSR and SBR values */ + calculatedBaud = srcClock_Hz / ((uint32_t)osrTemp * (uint32_t)sbrTemp); + + tempDiff = calculatedBaud > baudRate_Bps ? (calculatedBaud - baudRate_Bps) : (baudRate_Bps - calculatedBaud); + + if (tempDiff <= baudDiff) { + baudDiff = tempDiff; + osr = osrTemp; /* update and store the best OSR value calculated */ + sbr = sbrTemp; /* update store the best SBR value calculated */ + } + } + + /* Check to see if actual baud rate is within 3% of desired baud rate + * based on the best calculate OSR value */ + if (baudDiff < (uint32_t)((baudRate_Bps / 100U) * 3U)) { + /* Store CTRL before disable Tx and Rx */ + oldCtrl = base->CTRL; + + /* Disable LPUART TX RX before setting. */ + base->CTRL &= ~(LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK); + + temp = base->BAUD; + + /* Acceptable baud rate, check if OSR is between 4x and 7x oversampling. + * If so, then "BOTHEDGE" sampling must be turned on */ + if ((osr > 3U) && (osr < 8U)) { + temp |= LPUART_BAUD_BOTHEDGE_MASK; + } + + /* program the osr value (bit value is one less than actual value) */ + temp &= ~LPUART_BAUD_OSR_MASK; + temp |= LPUART_BAUD_OSR((uint32_t)osr - 1UL); + + /* write the sbr value to the BAUD registers */ + temp &= ~LPUART_BAUD_SBR_MASK; + base->BAUD = temp | LPUART_BAUD_SBR(sbr); + + /* Restore CTRL. */ + base->CTRL = oldCtrl; + } else { + /* Unacceptable baud rate difference of more than 3%*/ + status = kStatus_LPUART_BaudrateNotSupport; + } + + return status; +} + +/*! + * brief Enable 9-bit data mode for LPUART. + * + * This function set the 9-bit mode for LPUART module. The 9th bit is not used for parity thus can be modified by user. + * + * param base LPUART peripheral base address. + * param enable true to enable, false to disable. + */ +void LPUART_Enable9bitMode(LPUART_Type *base, bool enable) { + assert(base != NULL); + + uint32_t temp = 0U; + + if (enable) { + /* Set LPUART_CTRL_M for 9-bit mode, clear LPUART_CTRL_PE to disable parity. */ + temp = base->CTRL & ~((uint32_t)LPUART_CTRL_PE_MASK | (uint32_t)LPUART_CTRL_M_MASK); + temp |= (uint32_t)LPUART_CTRL_M_MASK; + base->CTRL = temp; + } else { + /* Clear LPUART_CTRL_M. */ + base->CTRL &= ~(uint32_t)LPUART_CTRL_M_MASK; + } + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + /* Clear LPUART_CTRL_M7 to disable 7-bit mode. */ + base->CTRL &= ~(uint32_t)LPUART_CTRL_M7_MASK; + #endif + #if defined(FSL_FEATURE_LPUART_HAS_10BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_10BIT_DATA_SUPPORT + /* Clear LPUART_BAUD_M10 to disable 10-bit mode. */ + base->BAUD &= ~(uint32_t)LPUART_BAUD_M10_MASK; + #endif +} + +/*! + * brief Transmit an address frame in 9-bit data mode. + * + * param base LPUART peripheral base address. + * param address LPUART slave address. + */ +void LPUART_SendAddress(LPUART_Type *base, uint8_t address) { + assert(base != NULL); + + uint32_t temp = base->DATA & 0xFFFFFC00UL; + temp |= ((uint32_t)address | (1UL << LPUART_DATA_R8T8_SHIFT)); + base->DATA = temp; +} + +/*! + * brief Enables LPUART interrupts according to a provided mask. + * + * This function enables the LPUART interrupts according to a provided mask. The mask + * is a logical OR of enumeration members. See the ref _lpuart_interrupt_enable. + * This examples shows how to enable TX empty interrupt and RX full interrupt: + * code + * LPUART_EnableInterrupts(LPUART1,kLPUART_TxDataRegEmptyInterruptEnable | kLPUART_RxDataRegFullInterruptEnable); + * endcode + * + * param base LPUART peripheral base address. + * param mask The interrupts to enable. Logical OR of ref _uart_interrupt_enable. + */ +void LPUART_EnableInterrupts(LPUART_Type *base, uint32_t mask) { + /* Only consider the real interrupt enable bits. */ + mask &= (uint32_t)kLPUART_AllInterruptEnable; + + /* Check int enable bits in base->BAUD */ + uint32_t tempReg = base->BAUD; + #if defined(FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT) && FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT + tempReg |= ((mask << 8U) & LPUART_BAUD_LBKDIE_MASK); + /* Clear bit 7 from mask */ + mask &= ~(uint32_t)kLPUART_LinBreakInterruptEnable; + #endif + tempReg |= ((mask << 8U) & LPUART_BAUD_RXEDGIE_MASK); + /* Clear bit 6 from mask */ + mask &= ~(uint32_t)kLPUART_RxActiveEdgeInterruptEnable; + base->BAUD = tempReg; + + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Check int enable bits in base->FIFO */ + base->FIFO = (base->FIFO & ~(LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK)) | + (mask & (LPUART_FIFO_TXOFE_MASK | LPUART_FIFO_RXUFE_MASK)); + /* Clear bit 9 and bit 8 from mask */ + mask &= ~((uint32_t)kLPUART_TxFifoOverflowInterruptEnable | (uint32_t)kLPUART_RxFifoUnderflowInterruptEnable); + #endif + + /* Check int enable bits in base->CTRL */ + base->CTRL |= mask; +} + +/*! + * brief Disables LPUART interrupts according to a provided mask. + * + * This function disables the LPUART interrupts according to a provided mask. The mask + * is a logical OR of enumeration members. See ref _lpuart_interrupt_enable. + * This example shows how to disable the TX empty interrupt and RX full interrupt: + * code + * LPUART_DisableInterrupts(LPUART1,kLPUART_TxDataRegEmptyInterruptEnable | kLPUART_RxDataRegFullInterruptEnable); + * endcode + * + * param base LPUART peripheral base address. + * param mask The interrupts to disable. Logical OR of ref _lpuart_interrupt_enable. + */ +void LPUART_DisableInterrupts(LPUART_Type *base, uint32_t mask) { + /* Only consider the real interrupt enable bits. */ + mask &= (uint32_t)kLPUART_AllInterruptEnable; + /* Check int enable bits in base->BAUD */ + uint32_t tempReg = base->BAUD; + #if defined(FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT) && FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT + tempReg &= ~((mask << 8U) & LPUART_BAUD_LBKDIE_MASK); + /* Clear bit 7 from mask */ + mask &= ~(uint32_t)kLPUART_LinBreakInterruptEnable; + #endif + tempReg &= ~((mask << 8U) & LPUART_BAUD_RXEDGIE_MASK); + /* Clear bit 6 from mask */ + mask &= ~(uint32_t)kLPUART_RxActiveEdgeInterruptEnable; + base->BAUD = tempReg; + + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Check int enable bits in base->FIFO */ + base->FIFO = (base->FIFO & ~(LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK)) & + ~(mask & (LPUART_FIFO_TXOFE_MASK | LPUART_FIFO_RXUFE_MASK)); + /* Clear bit 9 and bit 8 from mask */ + mask &= ~((uint32_t)kLPUART_TxFifoOverflowInterruptEnable | (uint32_t)kLPUART_RxFifoUnderflowInterruptEnable); + #endif + + /* Check int enable bits in base->CTRL */ + base->CTRL &= ~mask; +} + +/*! + * brief Gets enabled LPUART interrupts. + * + * This function gets the enabled LPUART interrupts. The enabled interrupts are returned + * as the logical OR value of the enumerators ref _lpuart_interrupt_enable. To check + * a specific interrupt enable status, compare the return value with enumerators + * in ref _lpuart_interrupt_enable. + * For example, to check whether the TX empty interrupt is enabled: + * code + * uint32_t enabledInterrupts = LPUART_GetEnabledInterrupts(LPUART1); + * + * if (kLPUART_TxDataRegEmptyInterruptEnable & enabledInterrupts) + * { + * ... + * } + * endcode + * + * param base LPUART peripheral base address. + * return LPUART interrupt flags which are logical OR of the enumerators in ref _lpuart_interrupt_enable. + */ +uint32_t LPUART_GetEnabledInterrupts(LPUART_Type *base) { + /* Check int enable bits in base->CTRL */ + uint32_t temp = (uint32_t)(base->CTRL & (uint32_t)kLPUART_AllInterruptEnable); + + /* Check int enable bits in base->BAUD */ + temp = (temp & ~(uint32_t)kLPUART_RxActiveEdgeInterruptEnable) | ((base->BAUD & LPUART_BAUD_RXEDGIE_MASK) >> 8U); + #if defined(FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT) && FSL_FEATURE_LPUART_HAS_LIN_BREAK_DETECT + temp = (temp & ~(uint32_t)kLPUART_LinBreakInterruptEnable) | ((base->BAUD & LPUART_BAUD_LBKDIE_MASK) >> 8U); + #endif + + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Check int enable bits in base->FIFO */ + temp = + (temp & ~((uint32_t)kLPUART_TxFifoOverflowInterruptEnable | (uint32_t)kLPUART_RxFifoUnderflowInterruptEnable)) | + (base->FIFO & (LPUART_FIFO_TXOFE_MASK | LPUART_FIFO_RXUFE_MASK)); + #endif + + return temp; +} + +/*! + * brief Gets LPUART status flags. + * + * This function gets all LPUART status flags. The flags are returned as the logical + * OR value of the enumerators ref _lpuart_flags. To check for a specific status, + * compare the return value with enumerators in the ref _lpuart_flags. + * For example, to check whether the TX is empty: + * code + * if (kLPUART_TxDataRegEmptyFlag & LPUART_GetStatusFlags(LPUART1)) + * { + * ... + * } + * endcode + * + * param base LPUART peripheral base address. + * return LPUART status flags which are ORed by the enumerators in the _lpuart_flags. + */ +uint32_t LPUART_GetStatusFlags(LPUART_Type *base) { + uint32_t temp; + temp = base->STAT; + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + temp |= (base->FIFO & + (LPUART_FIFO_TXEMPT_MASK | LPUART_FIFO_RXEMPT_MASK | LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK)) >> + 16U; + #endif + /* Only keeps the status bits */ + temp &= (uint32_t)kLPUART_AllFlags; + return temp; +} + +/*! + * brief Clears status flags with a provided mask. + * + * This function clears LPUART status flags with a provided mask. Automatically cleared flags + * can't be cleared by this function. + * Flags that can only cleared or set by hardware are: + * kLPUART_TxDataRegEmptyFlag, kLPUART_TransmissionCompleteFlag, kLPUART_RxDataRegFullFlag, + * kLPUART_RxActiveFlag, kLPUART_NoiseErrorInRxDataRegFlag, kLPUART_ParityErrorInRxDataRegFlag, + * kLPUART_TxFifoEmptyFlag,kLPUART_RxFifoEmptyFlag + * Note: This API should be called when the Tx/Rx is idle, otherwise it takes no effects. + * + * param base LPUART peripheral base address. + * param mask the status flags to be cleared. The user can use the enumerators in the + * _lpuart_status_flag_t to do the OR operation and get the mask. + * return 0 succeed, others failed. + * retval kStatus_LPUART_FlagCannotClearManually The flag can't be cleared by this function but + * it is cleared automatically by hardware. + * retval kStatus_Success Status in the mask are cleared. + */ +status_t LPUART_ClearStatusFlags(LPUART_Type *base, uint32_t mask) { + uint32_t temp; + status_t status; + + /* Only deal with the clearable flags */ + mask &= (uint32_t)kLPUART_AllClearFlags; + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + /* Status bits in FIFO register */ + if ((mask & ((uint32_t)kLPUART_TxFifoOverflowFlag | (uint32_t)kLPUART_RxFifoUnderflowFlag)) != 0U) { + /* Get the FIFO register value and mask the rx/tx FIFO flush bits and the status bits that can be W1C in case + they are written 1 accidentally. */ + temp = (uint32_t)base->FIFO; + temp &= (uint32_t)( + ~(LPUART_FIFO_TXFLUSH_MASK | LPUART_FIFO_RXFLUSH_MASK | LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK)); + temp |= (mask << 16U) & (LPUART_FIFO_TXOF_MASK | LPUART_FIFO_RXUF_MASK); + base->FIFO = temp; + } + #endif + /* Status bits in STAT register */ + /* First get the STAT register value and mask all the bits that not represent status, then OR with the status bit + * that is to be W1C */ + temp = (base->STAT & 0x3E000000UL) | mask; + base->STAT = temp; + /* If some flags still pending. */ + if (0U != (mask & LPUART_GetStatusFlags(base))) { + status = kStatus_LPUART_FlagCannotClearManually; + } else { + status = kStatus_Success; + } + + return status; +} + +/*! + * brief Writes to the transmitter register using a blocking method. + * + * This function polls the transmitter register, first waits for the register to be empty or TX FIFO to have room, + * and writes data to the transmitter buffer, then waits for the data to be sent out to bus. + * + * param base LPUART peripheral base address. + * param data Start address of the data to write. + * param length Size of the data to write. + * retval kStatus_LPUART_Timeout Transmission timed out and was aborted. + * retval kStatus_Success Successfully wrote all data. + */ +status_t LPUART_WriteBlocking(LPUART_Type *base, const uint8_t *data, size_t length) { + assert(NULL != data); + + const uint8_t *dataAddress = data; + size_t transferSize = length; + + #if UART_RETRY_TIMES + uint32_t waitTimes; + #endif + + while (0U != transferSize) { + #if UART_RETRY_TIMES + waitTimes = UART_RETRY_TIMES; + while ((0U == (base->STAT & LPUART_STAT_TDRE_MASK)) && (0U != --waitTimes)) + #else + while (0U == (base->STAT & LPUART_STAT_TDRE_MASK)) + #endif + { + } + #if UART_RETRY_TIMES + if (0U == waitTimes) { + return kStatus_LPUART_Timeout; + } + #endif + base->DATA = *(dataAddress); + dataAddress++; + transferSize--; + } + /* Ensure all the data in the transmit buffer are sent out to bus. */ + #if UART_RETRY_TIMES + waitTimes = UART_RETRY_TIMES; + while ((0U == (base->STAT & LPUART_STAT_TC_MASK)) && (0U != --waitTimes)) + #else + while (0U == (base->STAT & LPUART_STAT_TC_MASK)) + #endif + { + } + #if UART_RETRY_TIMES + if (0U == waitTimes) { + return kStatus_LPUART_Timeout; + } + #endif + return kStatus_Success; +} + +/*! + * brief Reads the receiver data register using a blocking method. + * + * This function polls the receiver register, waits for the receiver register full or receiver FIFO + * has data, and reads data from the TX register. + * + * param base LPUART peripheral base address. + * param data Start address of the buffer to store the received data. + * param length Size of the buffer. + * retval kStatus_LPUART_RxHardwareOverrun Receiver overrun happened while receiving data. + * retval kStatus_LPUART_NoiseError Noise error happened while receiving data. + * retval kStatus_LPUART_FramingError Framing error happened while receiving data. + * retval kStatus_LPUART_ParityError Parity error happened while receiving data. + * retval kStatus_LPUART_Timeout Transmission timed out and was aborted. + * retval kStatus_Success Successfully received all data. + */ +status_t LPUART_ReadBlocking(LPUART_Type *base, uint8_t *data, size_t length) { + assert(NULL != data); + + status_t status = kStatus_Success; + uint32_t statusFlag; + uint8_t *dataAddress = data; + + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + uint32_t ctrl = base->CTRL; + bool isSevenDataBits = (((ctrl & LPUART_CTRL_M7_MASK) != 0U) || + (((ctrl & LPUART_CTRL_M_MASK) == 0U) && ((ctrl & LPUART_CTRL_PE_MASK) != 0U))); + #endif + + #if UART_RETRY_TIMES + uint32_t waitTimes; + #endif + + while (0U != (length--)) { + #if UART_RETRY_TIMES + waitTimes = UART_RETRY_TIMES; + #endif + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + while (0U == ((base->WATER & LPUART_WATER_RXCOUNT_MASK) >> LPUART_WATER_RXCOUNT_SHIFT)) + #else + while (0U == (base->STAT & LPUART_STAT_RDRF_MASK)) + #endif + { + #if UART_RETRY_TIMES + if (0U == --waitTimes) { + status = kStatus_LPUART_Timeout; + break; + } + #endif + statusFlag = LPUART_GetStatusFlags(base); + + if (0U != (statusFlag & (uint32_t)kLPUART_RxOverrunFlag)) { + status = ((kStatus_Success == LPUART_ClearStatusFlags(base, (uint32_t)kLPUART_RxOverrunFlag)) ? + (kStatus_LPUART_RxHardwareOverrun) : + (kStatus_LPUART_FlagCannotClearManually)); + /* Other error flags(FE, NF, and PF) are prevented from setting once OR is set, no need to check other + * error flags*/ + break; + } + + if (0U != (statusFlag & (uint32_t)kLPUART_ParityErrorFlag)) { + status = ((kStatus_Success == LPUART_ClearStatusFlags(base, (uint32_t)kLPUART_ParityErrorFlag)) ? + (kStatus_LPUART_ParityError) : + (kStatus_LPUART_FlagCannotClearManually)); + } + + if (0U != (statusFlag & (uint32_t)kLPUART_FramingErrorFlag)) { + status = ((kStatus_Success == LPUART_ClearStatusFlags(base, (uint32_t)kLPUART_FramingErrorFlag)) ? + (kStatus_LPUART_FramingError) : + (kStatus_LPUART_FlagCannotClearManually)); + } + + if (0U != (statusFlag & (uint32_t)kLPUART_NoiseErrorFlag)) { + status = ((kStatus_Success == LPUART_ClearStatusFlags(base, (uint32_t)kLPUART_NoiseErrorFlag)) ? + (kStatus_LPUART_NoiseError) : + (kStatus_LPUART_FlagCannotClearManually)); + } + if (kStatus_Success != status) { + break; + } + } + + if (kStatus_Success == status) { + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + if (isSevenDataBits) { + *(dataAddress) = (uint8_t)(base->DATA & 0x7FU); + dataAddress++; + } else { + *(dataAddress) = (uint8_t)base->DATA; + dataAddress++; + } + #else + *(dataAddress) = (uint8_t)base->DATA; + dataAddress++; + #endif + } else { + break; + } + } + + return status; +} + +/*! + * brief Initializes the LPUART handle. + * + * This function initializes the LPUART handle, which can be used for other LPUART + * transactional APIs. Usually, for a specified LPUART instance, + * call this API once to get the initialized handle. + * + * The LPUART driver supports the "background" receiving, which means that user can set up + * an RX ring buffer optionally. Data received is stored into the ring buffer even when the + * user doesn't call the LPUART_TransferReceiveNonBlocking() API. If there is already data received + * in the ring buffer, the user can get the received data from the ring buffer directly. + * The ring buffer is disabled if passing NULL as p ringBuffer. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param callback Callback function. + * param userData User data. + */ +void LPUART_TransferCreateHandle(LPUART_Type *base, + lpuart_handle_t *handle, + lpuart_transfer_callback_t callback, + void *userData) { + assert(NULL != handle); + + uint32_t instance; + + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + uint32_t ctrl = base->CTRL; + bool isSevenDataBits = (((ctrl & LPUART_CTRL_M7_MASK) != 0U) || + (((ctrl & LPUART_CTRL_M_MASK) == 0U) && ((ctrl & LPUART_CTRL_PE_MASK) != 0U))); + #endif + + /* Zero the handle. */ + (void)memset(handle, 0, sizeof(lpuart_handle_t)); + + /* Set the TX/RX state. */ + handle->rxState = (uint8_t)kLPUART_RxIdle; + handle->txState = (uint8_t)kLPUART_TxIdle; + + /* Set the callback and user data. */ + handle->callback = callback; + handle->userData = userData; + + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + /* Initial seven data bits flag */ + handle->isSevenDataBits = isSevenDataBits; + #endif + + /* Get instance from peripheral base address. */ + instance = LPUART_GetInstance(base); + + /* Save the handle in global variables to support the double weak mechanism. */ + s_lpuartHandle[instance] = handle; + + s_lpuartIsr = LPUART_TransferHandleIRQ; + +/* Enable interrupt in NVIC. */ + #if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ + (void)EnableIRQ(s_lpuartRxIRQ[instance]); + (void)EnableIRQ(s_lpuartTxIRQ[instance]); + #else + (void)EnableIRQ(s_lpuartIRQ[instance]); + #endif +} + +/*! + * brief Sets up the RX ring buffer. + * + * This function sets up the RX ring buffer to a specific UART handle. + * + * When the RX ring buffer is used, data received is stored into the ring buffer even when + * the user doesn't call the UART_TransferReceiveNonBlocking() API. If there is already data received + * in the ring buffer, the user can get the received data from the ring buffer directly. + * + * note When using RX ring buffer, one byte is reserved for internal use. In other + * words, if p ringBufferSize is 32, then only 31 bytes are used for saving data. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param ringBuffer Start address of ring buffer for background receiving. Pass NULL to disable the ring buffer. + * param ringBufferSize size of the ring buffer. + */ +void LPUART_TransferStartRingBuffer(LPUART_Type *base, + lpuart_handle_t *handle, + uint8_t *ringBuffer, + size_t ringBufferSize) { + assert(NULL != handle); + assert(NULL != ringBuffer); + + /* Setup the ring buffer address */ + handle->rxRingBuffer = ringBuffer; + handle->rxRingBufferSize = ringBufferSize; + handle->rxRingBufferHead = 0U; + handle->rxRingBufferTail = 0U; + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. */ + uint32_t irqMask = DisableGlobalIRQ(); + /* Enable the interrupt to accept the data when user need the ring buffer. */ + base->CTRL |= (uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); +} + +/*! + * brief Aborts the background transfer and uninstalls the ring buffer. + * + * This function aborts the background transfer and uninstalls the ring buffer. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + */ +void LPUART_TransferStopRingBuffer(LPUART_Type *base, lpuart_handle_t *handle) { + assert(NULL != handle); + + if (handle->rxState == (uint8_t)kLPUART_RxIdle) { + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. + */ + uint32_t irqMask = DisableGlobalIRQ(); + base->CTRL &= ~(uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); + } + + handle->rxRingBuffer = NULL; + handle->rxRingBufferSize = 0U; + handle->rxRingBufferHead = 0U; + handle->rxRingBufferTail = 0U; +} + +/*! + * brief Transmits a buffer of data using the interrupt method. + * + * This function send data using an interrupt method. This is a non-blocking function, which + * returns directly without waiting for all data written to the transmitter register. When + * all data is written to the TX register in the ISR, the LPUART driver calls the callback + * function and passes the ref kStatus_LPUART_TxIdle as status parameter. + * + * note The kStatus_LPUART_TxIdle is passed to the upper layer when all data are written + * to the TX register. However, there is no check to ensure that all the data sent out. Before disabling the TX, + * check the kLPUART_TransmissionCompleteFlag to ensure that the transmit is finished. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param xfer LPUART transfer structure, see #lpuart_transfer_t. + * retval kStatus_Success Successfully start the data transmission. + * retval kStatus_LPUART_TxBusy Previous transmission still not finished, data not all written to the TX register. + * retval kStatus_InvalidArgument Invalid argument. + */ +status_t LPUART_TransferSendNonBlocking(LPUART_Type *base, lpuart_handle_t *handle, lpuart_transfer_t *xfer) { + assert(NULL != handle); + assert(NULL != xfer); + assert(NULL != xfer->txData); + assert(0U != xfer->dataSize); + + status_t status; + + /* Return error if current TX busy. */ + if ((uint8_t)kLPUART_TxBusy == handle->txState) { + status = kStatus_LPUART_TxBusy; + } else { + handle->txData = xfer->txData; + handle->txDataSize = xfer->dataSize; + handle->txDataSizeAll = xfer->dataSize; + handle->txState = (uint8_t)kLPUART_TxBusy; + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. + */ + uint32_t irqMask = DisableGlobalIRQ(); + /* Enable transmitter interrupt. */ + base->CTRL |= (uint32_t)LPUART_CTRL_TIE_MASK; + EnableGlobalIRQ(irqMask); + + status = kStatus_Success; + } + + return status; +} + +/*! + * brief Aborts the interrupt-driven data transmit. + * + * This function aborts the interrupt driven data sending. The user can get the remainBtyes to find out + * how many bytes are not sent out. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + */ +void LPUART_TransferAbortSend(LPUART_Type *base, lpuart_handle_t *handle) { + assert(NULL != handle); + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. */ + uint32_t irqMask = DisableGlobalIRQ(); + base->CTRL &= ~(uint32_t)(LPUART_CTRL_TIE_MASK | LPUART_CTRL_TCIE_MASK); + EnableGlobalIRQ(irqMask); + + handle->txDataSize = 0; + handle->txState = (uint8_t)kLPUART_TxIdle; +} + +/*! + * brief Gets the number of bytes that have been sent out to bus. + * + * This function gets the number of bytes that have been sent out to bus by an interrupt method. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param count Send bytes count. + * retval kStatus_NoTransferInProgress No send in progress. + * retval kStatus_InvalidArgument Parameter is invalid. + * retval kStatus_Success Get successfully through the parameter \p count; + */ +status_t LPUART_TransferGetSendCount(LPUART_Type *base, lpuart_handle_t *handle, uint32_t *count) { + assert(NULL != handle); + assert(NULL != count); + + status_t status = kStatus_Success; + size_t tmptxDataSize = handle->txDataSize; + + if ((uint8_t)kLPUART_TxIdle == handle->txState) { + status = kStatus_NoTransferInProgress; + } else { + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + *count = handle->txDataSizeAll - tmptxDataSize - + ((base->WATER & LPUART_WATER_TXCOUNT_MASK) >> LPUART_WATER_TXCOUNT_SHIFT); + #else + if ((base->STAT & (uint32_t)kLPUART_TxDataRegEmptyFlag) != 0U) { + *count = handle->txDataSizeAll - tmptxDataSize; + } else { + *count = handle->txDataSizeAll - tmptxDataSize - 1U; + } + #endif + } + + return status; +} + +/*! + * brief Receives a buffer of data using the interrupt method. + * + * This function receives data using an interrupt method. This is a non-blocking function + * which returns without waiting to ensure that all data are received. + * If the RX ring buffer is used and not empty, the data in the ring buffer is copied and + * the parameter p receivedBytes shows how many bytes are copied from the ring buffer. + * After copying, if the data in the ring buffer is not enough for read, the receive + * request is saved by the LPUART driver. When the new data arrives, the receive request + * is serviced first. When all data is received, the LPUART driver notifies the upper layer + * through a callback function and passes a status parameter ref kStatus_UART_RxIdle. + * For example, the upper layer needs 10 bytes but there are only 5 bytes in ring buffer. + * The 5 bytes are copied to xfer->data, which returns with the + * parameter p receivedBytes set to 5. For the remaining 5 bytes, the newly arrived data is + * saved from xfer->data[5]. When 5 bytes are received, the LPUART driver notifies the upper layer. + * If the RX ring buffer is not enabled, this function enables the RX and RX interrupt + * to receive data to xfer->data. When all data is received, the upper layer is notified. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param xfer LPUART transfer structure, see #uart_transfer_t. + * param receivedBytes Bytes received from the ring buffer directly. + * retval kStatus_Success Successfully queue the transfer into the transmit queue. + * retval kStatus_LPUART_RxBusy Previous receive request is not finished. + * retval kStatus_InvalidArgument Invalid argument. + */ +status_t LPUART_TransferReceiveNonBlocking(LPUART_Type *base, + lpuart_handle_t *handle, + lpuart_transfer_t *xfer, + size_t *receivedBytes) { + assert(NULL != handle); + assert(NULL != xfer); + assert(NULL != xfer->rxData); + assert(0U != xfer->dataSize); + + uint32_t i; + status_t status; + uint32_t irqMask; + /* How many bytes to copy from ring buffer to user memory. */ + size_t bytesToCopy = 0U; + /* How many bytes to receive. */ + size_t bytesToReceive; + /* How many bytes currently have received. */ + size_t bytesCurrentReceived; + + /* How to get data: + 1. If RX ring buffer is not enabled, then save xfer->data and xfer->dataSize + to lpuart handle, enable interrupt to store received data to xfer->data. When + all data received, trigger callback. + 2. If RX ring buffer is enabled and not empty, get data from ring buffer first. + If there are enough data in ring buffer, copy them to xfer->data and return. + If there are not enough data in ring buffer, copy all of them to xfer->data, + save the xfer->data remained empty space to lpuart handle, receive data + to this empty space and trigger callback when finished. */ + + if ((uint8_t)kLPUART_RxBusy == handle->rxState) { + status = kStatus_LPUART_RxBusy; + } else { + bytesToReceive = xfer->dataSize; + bytesCurrentReceived = 0; + + /* If RX ring buffer is used. */ + if (NULL != handle->rxRingBuffer) { + /* Disable and re-enable the global interrupt to protect the interrupt enable register during + * read-modify-wrte. */ + irqMask = DisableGlobalIRQ(); + /* Disable LPUART RX IRQ, protect ring buffer. */ + base->CTRL &= ~(uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); + + /* How many bytes in RX ring buffer currently. */ + bytesToCopy = LPUART_TransferGetRxRingBufferLength(base, handle); + + if (0U != bytesToCopy) { + bytesToCopy = MIN(bytesToReceive, bytesToCopy); + + bytesToReceive -= bytesToCopy; + + /* Copy data from ring buffer to user memory. */ + for (i = 0U; i < bytesToCopy; i++) { + xfer->rxData[bytesCurrentReceived] = handle->rxRingBuffer[handle->rxRingBufferTail]; + bytesCurrentReceived++; + + /* Wrap to 0. Not use modulo (%) because it might be large and slow. */ + if (((uint32_t)handle->rxRingBufferTail + 1U) == handle->rxRingBufferSize) { + handle->rxRingBufferTail = 0U; + } else { + handle->rxRingBufferTail++; + } + } + } + + /* If ring buffer does not have enough data, still need to read more data. */ + if (0U != bytesToReceive) { + /* No data in ring buffer, save the request to LPUART handle. */ + handle->rxData = &xfer->rxData[bytesCurrentReceived]; + handle->rxDataSize = bytesToReceive; + handle->rxDataSizeAll = xfer->dataSize; + handle->rxState = (uint8_t)kLPUART_RxBusy; + } + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during + * read-modify-wrte. */ + irqMask = DisableGlobalIRQ(); + /* Re-enable LPUART RX IRQ. */ + base->CTRL |= (uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); + + /* Call user callback since all data are received. */ + if (0U == bytesToReceive) { + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_RxIdle, handle->userData); + } + } + } + /* Ring buffer not used. */ + else { + handle->rxData = &xfer->rxData[bytesCurrentReceived]; + handle->rxDataSize = bytesToReceive; + handle->rxDataSizeAll = bytesToReceive; + handle->rxState = (uint8_t)kLPUART_RxBusy; + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during + * read-modify-wrte. */ + irqMask = DisableGlobalIRQ(); + /* Enable RX interrupt. */ + base->CTRL |= (uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ILIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); + } + + /* Return the how many bytes have read. */ + if (NULL != receivedBytes) { + *receivedBytes = bytesCurrentReceived; + } + + status = kStatus_Success; + } + + return status; +} + +/*! + * brief Aborts the interrupt-driven data receiving. + * + * This function aborts the interrupt-driven data receiving. The user can get the remainBytes to find out + * how many bytes not received yet. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + */ +void LPUART_TransferAbortReceive(LPUART_Type *base, lpuart_handle_t *handle) { + assert(NULL != handle); + + /* Only abort the receive to handle->rxData, the RX ring buffer is still working. */ + if (NULL == handle->rxRingBuffer) { + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. + */ + uint32_t irqMask = DisableGlobalIRQ(); + /* Disable RX interrupt. */ + base->CTRL &= ~(uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ILIE_MASK | LPUART_CTRL_ORIE_MASK); + EnableGlobalIRQ(irqMask); + } + + handle->rxDataSize = 0U; + handle->rxState = (uint8_t)kLPUART_RxIdle; +} + +/*! + * brief Gets the number of bytes that have been received. + * + * This function gets the number of bytes that have been received. + * + * param base LPUART peripheral base address. + * param handle LPUART handle pointer. + * param count Receive bytes count. + * retval kStatus_NoTransferInProgress No receive in progress. + * retval kStatus_InvalidArgument Parameter is invalid. + * retval kStatus_Success Get successfully through the parameter \p count; + */ +status_t LPUART_TransferGetReceiveCount(LPUART_Type *base, lpuart_handle_t *handle, uint32_t *count) { + assert(NULL != handle); + assert(NULL != count); + + status_t status = kStatus_Success; + size_t tmprxDataSize = handle->rxDataSize; + + if ((uint8_t)kLPUART_RxIdle == handle->rxState) { + status = kStatus_NoTransferInProgress; + } else { + *count = handle->rxDataSizeAll - tmprxDataSize; + } + + return status; +} + +/*! + * brief LPUART IRQ handle function. + * + * This function handles the LPUART transmit and receive IRQ request. + * + * param base LPUART peripheral base address. + * param irqHandle LPUART handle pointer. + */ +void LPUART_TransferHandleIRQ(LPUART_Type *base, void *irqHandle) { + assert(NULL != irqHandle); + + uint8_t count; + uint8_t tempCount; + uint32_t status = LPUART_GetStatusFlags(base); + uint32_t enabledInterrupts = LPUART_GetEnabledInterrupts(base); + uint16_t tpmRxRingBufferHead; + uint32_t tpmData; + uint32_t irqMask; + lpuart_handle_t *handle = (lpuart_handle_t *)irqHandle; + + /* If RX overrun. */ + if ((uint32_t)kLPUART_RxOverrunFlag == ((uint32_t)kLPUART_RxOverrunFlag & status)) { + /* Clear overrun flag, otherwise the RX does not work. */ + base->STAT = ((base->STAT & 0x3FE00000U) | LPUART_STAT_OR_MASK); + + /* Trigger callback. */ + if (NULL != (handle->callback)) { + handle->callback(base, handle, kStatus_LPUART_RxHardwareOverrun, handle->userData); + } + } + /* Receive data register full */ + if ((0U != ((uint32_t)kLPUART_RxDataRegFullFlag & status)) && + (0U != ((uint32_t)kLPUART_RxDataRegFullInterruptEnable & enabledInterrupts))) { + /* Get the size that can be stored into buffer for this interrupt. */ + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + count = ((uint8_t)((base->WATER & LPUART_WATER_RXCOUNT_MASK) >> LPUART_WATER_RXCOUNT_SHIFT)); + #else + count = 1; + #endif + + /* If handle->rxDataSize is not 0, first save data to handle->rxData. */ + while ((0U != handle->rxDataSize) && (0U != count)) { + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + tempCount = (uint8_t)MIN(handle->rxDataSize, count); + #else + tempCount = 1; + #endif + + /* Using non block API to read the data from the registers. */ + LPUART_ReadNonBlocking(base, handle->rxData, tempCount); + handle->rxData = &handle->rxData[tempCount]; + handle->rxDataSize -= tempCount; + count -= tempCount; + + /* If all the data required for upper layer is ready, trigger callback. */ + if (0U == handle->rxDataSize) { + handle->rxState = (uint8_t)kLPUART_RxIdle; + + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_RxIdle, handle->userData); + } + } + } + + /* If use RX ring buffer, receive data to ring buffer. */ + if (NULL != handle->rxRingBuffer) { + while (0U != count--) { + /* If RX ring buffer is full, trigger callback to notify over run. */ + if (LPUART_TransferIsRxRingBufferFull(base, handle)) { + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_RxRingBufferOverrun, handle->userData); + } + } + + /* If ring buffer is still full after callback function, the oldest data is overridden. */ + if (LPUART_TransferIsRxRingBufferFull(base, handle)) { + /* Increase handle->rxRingBufferTail to make room for new data. */ + if (((uint32_t)handle->rxRingBufferTail + 1U) == handle->rxRingBufferSize) { + handle->rxRingBufferTail = 0U; + } else { + handle->rxRingBufferTail++; + } + } + + /* Read data. */ + tpmRxRingBufferHead = handle->rxRingBufferHead; + tpmData = base->DATA; + #if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT + if (handle->isSevenDataBits) { + handle->rxRingBuffer[tpmRxRingBufferHead] = (uint8_t)(tpmData & 0x7FU); + } else { + handle->rxRingBuffer[tpmRxRingBufferHead] = (uint8_t)tpmData; + } + #else + handle->rxRingBuffer[tpmRxRingBufferHead] = (uint8_t)tpmData; + #endif + + /* Increase handle->rxRingBufferHead. */ + if (((uint32_t)handle->rxRingBufferHead + 1U) == handle->rxRingBufferSize) { + handle->rxRingBufferHead = 0U; + } else { + handle->rxRingBufferHead++; + } + } + } + /* If no receive request pending, stop RX interrupt. */ + else if (0U == handle->rxDataSize) { + /* Disable and re-enable the global interrupt to protect the interrupt enable register during + * read-modify-wrte. */ + irqMask = DisableGlobalIRQ(); + base->CTRL &= ~(uint32_t)(LPUART_CTRL_RIE_MASK | LPUART_CTRL_ORIE_MASK | LPUART_CTRL_ILIE_MASK); + EnableGlobalIRQ(irqMask); + } else { + } + } + + /* Send data register empty and the interrupt is enabled. */ + if ((0U != ((uint32_t)kLPUART_TxDataRegEmptyFlag & status)) && + (0U != ((uint32_t)kLPUART_TxDataRegEmptyInterruptEnable & enabledInterrupts))) { +/* Get the bytes that available at this moment. */ + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + count = (uint8_t)FSL_FEATURE_LPUART_FIFO_SIZEn(base) - + (uint8_t)((base->WATER & LPUART_WATER_TXCOUNT_MASK) >> LPUART_WATER_TXCOUNT_SHIFT); + #else + count = 1; + #endif + + while ((0U != handle->txDataSize) && (0U != count)) { + #if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO + tempCount = (uint8_t)MIN(handle->txDataSize, count); + #else + tempCount = 1; + #endif + + /* Using non block API to write the data to the registers. */ + LPUART_WriteNonBlocking(base, handle->txData, tempCount); + handle->txData = &handle->txData[tempCount]; + handle->txDataSize -= tempCount; + count -= tempCount; + + /* If all the data are written to data register, notify user with the callback, then TX finished. */ + if (0U == handle->txDataSize) { + /* Disable and re-enable the global interrupt to protect the interrupt enable register during + * read-modify-wrte. */ + irqMask = DisableGlobalIRQ(); + /* Disable TX register empty interrupt and enable transmission completion interrupt. */ + base->CTRL = (base->CTRL & ~LPUART_CTRL_TIE_MASK) | LPUART_CTRL_TCIE_MASK; + EnableGlobalIRQ(irqMask); + } + } + } + + /* Transmission complete and the interrupt is enabled. */ + if ((0U != ((uint32_t)kLPUART_TransmissionCompleteFlag & status)) && + (0U != ((uint32_t)kLPUART_TransmissionCompleteInterruptEnable & enabledInterrupts))) { + /* Set txState to idle only when all data has been sent out to bus. */ + handle->txState = (uint8_t)kLPUART_TxIdle; + + /* Disable and re-enable the global interrupt to protect the interrupt enable register during read-modify-wrte. + */ + irqMask = DisableGlobalIRQ(); + /* Disable transmission complete interrupt. */ + base->CTRL &= ~(uint32_t)LPUART_CTRL_TCIE_MASK; + EnableGlobalIRQ(irqMask); + + /* Trigger callback. */ + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_TxIdle, handle->userData); + } + } + + /* If IDLE flag is set and the IDLE interrupt is enabled. */ + if ((0U != ((uint32_t)kLPUART_IdleLineFlag & status)) && + (0U != ((uint32_t)kLPUART_IdleLineInterruptEnable & enabledInterrupts))) { + /* Clear IDLE flag.*/ + base->STAT |= LPUART_STAT_IDLE_MASK; + if (NULL != handle->callback) { + handle->callback(base, handle, kStatus_LPUART_IdleLineDetected, handle->userData); + } else { + /* Avoid MISRA 15.7 */ + } + } + +} + +/*! + * brief LPUART Error IRQ handle function. + * + * This function handles the LPUART error IRQ request. + * + * param base LPUART peripheral base address. + * param irqHandle LPUART handle pointer. + */ +void LPUART_TransferHandleErrorIRQ(LPUART_Type *base, void *irqHandle) { + /* To be implemented by User. */ +} +#if defined(FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1) && FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1 +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART0_LPUART1_RX_DriverIRQHandler(void); +void LPUART0_LPUART1_RX_DriverIRQHandler(void) { + /* If handle is registered, treat the transfer function is enabled. */ + if (NULL != s_lpuartHandle[0]) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + } + if (NULL != s_lpuartHandle[1]) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + } + SDK_ISR_EXIT_BARRIER; +} +void LPUART0_LPUART1_TX_DriverIRQHandler(void); +void LPUART0_LPUART1_TX_DriverIRQHandler(void) { + /* If handle is registered, treat the transfer function is enabled. */ + if (NULL != s_lpuartHandle[0]) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + } + if (NULL != s_lpuartHandle[1]) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + } + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART0_LPUART1_DriverIRQHandler(void); +void LPUART0_LPUART1_DriverIRQHandler(void) { + /* If handle is registered, treat the transfer function is enabled. */ + if (NULL != s_lpuartHandle[0]) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + } + if (NULL != s_lpuartHandle[1]) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + } + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART0) +#if !(defined(FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1) && FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART0_TX_DriverIRQHandler(void); +void LPUART0_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART0_RX_DriverIRQHandler(void); +void LPUART0_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART0_DriverIRQHandler(void); +void LPUART0_DriverIRQHandler(void) { + s_lpuartIsr(LPUART0, s_lpuartHandle[0]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif +#endif + +#if defined(LPUART1) +#if !(defined(FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1) && FSL_FEATURE_LPUART_HAS_SHARED_IRQ0_IRQ1) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART1_TX_DriverIRQHandler(void); +void LPUART1_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART1_RX_DriverIRQHandler(void); +void LPUART1_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART1_DriverIRQHandler(void); +void LPUART1_DriverIRQHandler(void) { + s_lpuartIsr(LPUART1, s_lpuartHandle[1]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif +#endif + +#if defined(LPUART2) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART2_TX_DriverIRQHandler(void); +void LPUART2_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART2, s_lpuartHandle[2]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART2_RX_DriverIRQHandler(void); +void LPUART2_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART2, s_lpuartHandle[2]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART2_DriverIRQHandler(void); +void LPUART2_DriverIRQHandler(void) { + s_lpuartIsr(LPUART2, s_lpuartHandle[2]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART3) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART3_TX_DriverIRQHandler(void); +void LPUART3_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART3, s_lpuartHandle[3]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART3_RX_DriverIRQHandler(void); +void LPUART3_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART3, s_lpuartHandle[3]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART3_DriverIRQHandler(void); +void LPUART3_DriverIRQHandler(void) { + s_lpuartIsr(LPUART3, s_lpuartHandle[3]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART4) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART4_TX_DriverIRQHandler(void); +void LPUART4_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART4, s_lpuartHandle[4]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART4_RX_DriverIRQHandler(void); +void LPUART4_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART4, s_lpuartHandle[4]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART4_DriverIRQHandler(void); +void LPUART4_DriverIRQHandler(void) { + s_lpuartIsr(LPUART4, s_lpuartHandle[4]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART5) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART5_TX_DriverIRQHandler(void); +void LPUART5_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART5, s_lpuartHandle[5]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART5_RX_DriverIRQHandler(void); +void LPUART5_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART5, s_lpuartHandle[5]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART5_DriverIRQHandler(void); +void LPUART5_DriverIRQHandler(void) { + s_lpuartIsr(LPUART5, s_lpuartHandle[5]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART6) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART6_TX_DriverIRQHandler(void); +void LPUART6_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART6, s_lpuartHandle[6]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART6_RX_DriverIRQHandler(void); +void LPUART6_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART6, s_lpuartHandle[6]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART6_DriverIRQHandler(void); +void LPUART6_DriverIRQHandler(void) { + s_lpuartIsr(LPUART6, s_lpuartHandle[6]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART7) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART7_TX_DriverIRQHandler(void); +void LPUART7_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART7, s_lpuartHandle[7]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART7_RX_DriverIRQHandler(void); +void LPUART7_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART7, s_lpuartHandle[7]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART7_DriverIRQHandler(void); +void LPUART7_DriverIRQHandler(void) { + s_lpuartIsr(LPUART7, s_lpuartHandle[7]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART8) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART8_TX_DriverIRQHandler(void); +void LPUART8_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART8, s_lpuartHandle[8]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART8_RX_DriverIRQHandler(void); +void LPUART8_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART8, s_lpuartHandle[8]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART8_DriverIRQHandler(void); +void LPUART8_DriverIRQHandler(void) { + s_lpuartIsr(LPUART8, s_lpuartHandle[8]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART9) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART9_TX_DriverIRQHandler(void); +void LPUART9_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART9, s_lpuartHandle[9]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART9_RX_DriverIRQHandler(void); +void LPUART9_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART9, s_lpuartHandle[9]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART9_DriverIRQHandler(void); +void LPUART9_DriverIRQHandler(void) { + s_lpuartIsr(LPUART9, s_lpuartHandle[9]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART10) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART10_TX_DriverIRQHandler(void); +void LPUART10_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART10, s_lpuartHandle[10]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART10_RX_DriverIRQHandler(void); +void LPUART10_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART10, s_lpuartHandle[10]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART10_DriverIRQHandler(void); +void LPUART10_DriverIRQHandler(void) { + s_lpuartIsr(LPUART10, s_lpuartHandle[10]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(LPUART11) +#if defined(FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ) && FSL_FEATURE_LPUART_HAS_SEPARATE_RX_TX_IRQ +void LPUART11_TX_DriverIRQHandler(void); +void LPUART11_TX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART11, s_lpuartHandle[11]); + SDK_ISR_EXIT_BARRIER; +} +void LPUART11_RX_DriverIRQHandler(void); +void LPUART11_RX_DriverIRQHandler(void) { + s_lpuartIsr(LPUART11, s_lpuartHandle[11]); + SDK_ISR_EXIT_BARRIER; +} +#else +void LPUART11_DriverIRQHandler(void); +void LPUART11_DriverIRQHandler(void) { + s_lpuartIsr(LPUART11, s_lpuartHandle[11]); + SDK_ISR_EXIT_BARRIER; +} +#endif +#endif + +#if defined(CM4_0__LPUART) +void M4_0_LPUART_DriverIRQHandler(void); +void M4_0_LPUART_DriverIRQHandler(void) { + s_lpuartIsr(CM4_0__LPUART, s_lpuartHandle[LPUART_GetInstance(CM4_0__LPUART)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(CM4_1__LPUART) +void M4_1_LPUART_DriverIRQHandler(void); +void M4_1_LPUART_DriverIRQHandler(void) { + s_lpuartIsr(CM4_1__LPUART, s_lpuartHandle[LPUART_GetInstance(CM4_1__LPUART)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(CM4__LPUART) +void M4_LPUART_DriverIRQHandler(void); +void M4_LPUART_DriverIRQHandler(void) { + s_lpuartIsr(CM4__LPUART, s_lpuartHandle[LPUART_GetInstance(CM4__LPUART)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(DMA__LPUART0) +void DMA_UART0_INT_DriverIRQHandler(void); +void DMA_UART0_INT_DriverIRQHandler(void) { + s_lpuartIsr(DMA__LPUART0, s_lpuartHandle[LPUART_GetInstance(DMA__LPUART0)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(DMA__LPUART1) +void DMA_UART1_INT_DriverIRQHandler(void); +void DMA_UART1_INT_DriverIRQHandler(void) { + s_lpuartIsr(DMA__LPUART1, s_lpuartHandle[LPUART_GetInstance(DMA__LPUART1)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(DMA__LPUART2) +void DMA_UART2_INT_DriverIRQHandler(void); +void DMA_UART2_INT_DriverIRQHandler(void) { + s_lpuartIsr(DMA__LPUART2, s_lpuartHandle[LPUART_GetInstance(DMA__LPUART2)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(DMA__LPUART3) +void DMA_UART3_INT_DriverIRQHandler(void); +void DMA_UART3_INT_DriverIRQHandler(void) { + s_lpuartIsr(DMA__LPUART3, s_lpuartHandle[LPUART_GetInstance(DMA__LPUART3)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(DMA__LPUART4) +void DMA_UART4_INT_DriverIRQHandler(void); +void DMA_UART4_INT_DriverIRQHandler(void) { + s_lpuartIsr(DMA__LPUART4, s_lpuartHandle[LPUART_GetInstance(DMA__LPUART4)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(ADMA__LPUART0) +void ADMA_UART0_INT_DriverIRQHandler(void); +void ADMA_UART0_INT_DriverIRQHandler(void) { + s_lpuartIsr(ADMA__LPUART0, s_lpuartHandle[LPUART_GetInstance(ADMA__LPUART0)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(ADMA__LPUART1) +void ADMA_UART1_INT_DriverIRQHandler(void); +void ADMA_UART1_INT_DriverIRQHandler(void) { + s_lpuartIsr(ADMA__LPUART1, s_lpuartHandle[LPUART_GetInstance(ADMA__LPUART1)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(ADMA__LPUART2) +void ADMA_UART2_INT_DriverIRQHandler(void); +void ADMA_UART2_INT_DriverIRQHandler(void) { + s_lpuartIsr(ADMA__LPUART2, s_lpuartHandle[LPUART_GetInstance(ADMA__LPUART2)]); + SDK_ISR_EXIT_BARRIER; +} +#endif + +#if defined(ADMA__LPUART3) +void ADMA_UART3_INT_DriverIRQHandler(void); +void ADMA_UART3_INT_DriverIRQHandler(void) { + s_lpuartIsr(ADMA__LPUART3, s_lpuartHandle[LPUART_GetInstance(ADMA__LPUART3)]); + SDK_ISR_EXIT_BARRIER; +} +#endif diff --git a/ports/mimxrt/machine_uart.c b/ports/mimxrt/machine_uart.c index 7ae584fdd7454..9f9d6c8fd256f 100644 --- a/ports/mimxrt/machine_uart.c +++ b/ports/mimxrt/machine_uart.c @@ -50,6 +50,10 @@ #define UART_INVERT_RX (2) #define UART_INVERT_MASK (UART_INVERT_TX | UART_INVERT_RX) +#define UART_IRQ_RXIDLE (1) +#define UART_IRQ_TXIDLE (2) +#define MP_UART_ALLOWED_FLAGS (UART_IRQ_RXIDLE | UART_IRQ_TXIDLE) + typedef struct _machine_uart_obj_t { mp_obj_base_t base; struct _lpuart_handle handle; @@ -63,6 +67,9 @@ typedef struct _machine_uart_obj_t { uint8_t *txbuf; uint16_t txbuf_len; bool new; + 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; typedef struct _iomux_table_t { @@ -137,11 +144,21 @@ bool lpuart_set_iomux_cts(int8_t uart) { void LPUART_UserCallback(LPUART_Type *base, lpuart_handle_t *handle, status_t status, void *userData) { machine_uart_obj_t *self = userData; - if (kStatus_LPUART_TxIdle == status) { + + uint16_t mp_irq_flags = 0; + if (status == kStatus_LPUART_TxIdle) { self->tx_status = kStatus_LPUART_TxIdle; + mp_irq_flags = UART_IRQ_TXIDLE; + } else if (status == kStatus_LPUART_IdleLineDetected) { + mp_irq_flags = UART_IRQ_RXIDLE; + } + // 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); } - if (kStatus_LPUART_RxRingBufferOverrun == status) { + if (status == kStatus_LPUART_RxRingBufferOverrun) { ; // Ringbuffer full, deassert RTS if flow control is enabled } } @@ -170,16 +187,18 @@ void machine_uart_set_baudrate(mp_obj_t uart_in, uint32_t baudrate) { { 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_IRQ_RXIDLE) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_TXIDLE), MP_ROM_INT(UART_IRQ_TXIDLE) }, \ 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, flow=%s, " - "rxbuf=%d, txbuf=%d, timeout=%u, timeout_char=%u, invert=%s)", + "rxbuf=%d, txbuf=%d, timeout=%u, timeout_char=%u, invert=%s, irq=%d)", self->id, self->config.baudRate_Bps, 8 - self->config.dataBitsCount, _parity_name[self->config.parityMode], self->config.stopBitCount + 1, _flow_name[(self->config.enableTxCTS << 1) | self->config.enableRxRTS], self->handle.rxRingBufferSize, self->txbuf_len, self->timeout, self->timeout_char, - _invert_name[self->invert]); + _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) { @@ -313,12 +332,17 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, #else LPUART_Init(self->lpuart, &self->config, CLOCK_GetClockRootFreq(kCLOCK_UartClkRoot)); #endif + self->config.rxIdleType = kLPUART_IdleTypeStartBit; + self->config.rxIdleConfig = kLPUART_IdleCharacter4; + LPUART_Init(self->lpuart, &self->config, BOARD_BOOTCLOCKRUN_UART_CLK_ROOT); LPUART_TransferCreateHandle(self->lpuart, &self->handle, LPUART_UserCallback, self); uint8_t *buffer = m_new(uint8_t, rxbuf_len + 1); LPUART_TransferStartRingBuffer(self->lpuart, &self->handle, buffer, rxbuf_len); self->txbuf = m_new(uint8_t, txbuf_len); // Allocate the TX buffer. self->txbuf_len = txbuf_len; + LPUART_EnableInterrupts(self->lpuart, kLPUART_IdleLineInterruptEnable); + // The Uart supports inverting, but not the fsl API, so it has to coded directly // And it has to be done after LPUART_Init. if (self->invert & UART_INVERT_RX) { @@ -356,6 +380,8 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->timeout = 1; self->timeout_char = 1; self->new = true; + self->mp_irq_obj = NULL; + LPUART_GetDefaultConfig(&self->config); // Configure board-specific pin MUX based on the hardware device number. @@ -401,6 +427,55 @@ void machine_uart_deinit_all(void) { } } +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; + 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)); + } + + 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; + } + + 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); uint64_t t = ticks_us64() + (uint64_t)self->timeout * 1000; diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index ac918ba4da8d5..5ef6695c1425c 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -114,6 +114,7 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/mimxrt/machine_uart.c" #define MICROPY_PY_MACHINE_UART_SENDBREAK (1) +#define MICROPY_PY_MACHINE_UART_IRQ (1) #define MICROPY_PY_ONEWIRE (1) // fatfs configuration used in ffconf.h From 324c6753479b003d00a6c78a39cb078e73165f30 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 6 Mar 2024 21:05:13 +0100 Subject: [PATCH 04/16] renesas-ra/machine_uart: Add the UART.IRQ_RX class constant. The renesas-ra port supports calling a handler to be called on every byte received by UART. For consistency with other ports, the symbol IRQ_RX is added as the trigger name. Side change: Add the received UART data to the REPL input buffer only if it is the REPL UART. Otherwise, every UART would act as REPL input. Signed-off-by: robert-hh --- ports/renesas-ra/machine_uart.c | 1 + ports/renesas-ra/uart.c | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ports/renesas-ra/machine_uart.c b/ports/renesas-ra/machine_uart.c index 26ed625404374..4a659d107af39 100644 --- a/ports/renesas-ra/machine_uart.c +++ b/ports/renesas-ra/machine_uart.c @@ -41,6 +41,7 @@ #define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS \ { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HWCONTROL_RTS) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HWCONTROL_CTS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(0x10) }, \ static const char *_parity_name[] = {"None", "ODD", "EVEN"}; diff --git a/ports/renesas-ra/uart.c b/ports/renesas-ra/uart.c index 30707552b3efb..83b3394f7b453 100644 --- a/ports/renesas-ra/uart.c +++ b/ports/renesas-ra/uart.c @@ -71,14 +71,18 @@ static void uart_rx_cb(uint32_t ch, int d) { // even disable the IRQ. This should never happen. return; } - #if MICROPY_KBD_EXCEPTION - if (keyex_cb[ch]) { - (*keyex_cb[ch])(d); - } - #endif + #if defined(MICROPY_HW_UART_REPL) + if (ch == MICROPY_HW_UART_REPL) { + #if MICROPY_KBD_EXCEPTION + if (keyex_cb[ch]) { + (*keyex_cb[ch])(d); + } + #endif - #if MICROPY_HW_ENABLE_UART_REPL - ringbuf_put(&stdin_ringbuf, d); + #if MICROPY_HW_ENABLE_UART_REPL + ringbuf_put(&stdin_ringbuf, d); + #endif + } #endif // Check the flags to see if the user handler should be called From 1027b5f0835d8fba9ccc290f0333c86f7299620a Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 9 Mar 2024 18:24:20 +0100 Subject: [PATCH 05/16] cc3200/mods/pybuart: Add the UART.IRQ_RX class constant. As alternative to RX_ANY to match the names used by the other ports. Signed-off-by: robert-hh --- ports/cc3200/mods/pybuart.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/cc3200/mods/pybuart.c b/ports/cc3200/mods/pybuart.c index 6ab2371ba708f..eb2b3754f7473 100644 --- a/ports/cc3200/mods/pybuart.c +++ b/ports/cc3200/mods/pybuart.c @@ -590,6 +590,7 @@ static const mp_rom_map_elem_t pyb_uart_locals_dict_table[] = { // class constants { MP_ROM_QSTR(MP_QSTR_RX_ANY), MP_ROM_INT(UART_TRIGGER_RX_ANY) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_TRIGGER_RX_ANY) }, }; static MP_DEFINE_CONST_DICT(pyb_uart_locals_dict, pyb_uart_locals_dict_table); From a04a14163b79753c4c5ee16d80468ce475fd8b23 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 10 Mar 2024 15:14:20 +0100 Subject: [PATCH 06/16] esp32/machine_uart: Implement Python UART IRQ with IRQ_RX and IRQ_BREAK. Supported trigger events: IRQ_RX and IRQ_BREAK. Hard IRQ is not supported. Signed-off-by: robert-hh --- ports/esp32/esp32_common.cmake | 1 + ports/esp32/machine_uart.c | 108 ++++++++++++++++++++++++++++++++- ports/esp32/mpconfigport.h | 1 + 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index e928fb439da5e..7c5089639a5f2 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -32,6 +32,7 @@ list(APPEND MICROPY_SOURCE_SHARED ${MICROPY_DIR}/shared/netutils/netutils.c ${MICROPY_DIR}/shared/timeutils/timeutils.c ${MICROPY_DIR}/shared/runtime/interrupt_char.c + ${MICROPY_DIR}/shared/runtime/mpirq.c ${MICROPY_DIR}/shared/runtime/stdout_helpers.c ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c ${MICROPY_DIR}/shared/runtime/pyexec.c diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index 50c9a19bef1bb..1a02594132c0c 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -29,6 +29,10 @@ #include "driver/uart.h" #include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_task.h" +#include "shared/runtime/mpirq.h" #include "py/runtime.h" #include "py/stream.h" @@ -49,6 +53,9 @@ #define UART_INV_CTS UART_SIGNAL_CTS_INV #define UART_INV_MASK (UART_INV_TX | UART_INV_RX | UART_INV_RTS | UART_INV_CTS) +#define UART_IRQ_RX (1 << UART_DATA) +#define UART_IRQ_BREAK (1 << UART_BREAK) +#define MP_UART_ALLOWED_FLAGS (UART_IRQ_RX | UART_IRQ_BREAK) typedef struct _machine_uart_obj_t { mp_obj_base_t base; @@ -66,6 +73,11 @@ 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) uint32_t invert; // lines to invert + TaskHandle_t uart_event_task; + QueueHandle_t uart_queue; + 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 const char *_parity_name[] = {"None", "1", "0"}; @@ -80,14 +92,43 @@ static const char *_parity_name[] = {"None", "1", "0"}; { MP_ROM_QSTR(MP_QSTR_INV_CTS), MP_ROM_INT(UART_INV_CTS) }, \ { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HW_FLOWCTRL_RTS) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HW_FLOWCTRL_CTS) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_IRQ_RX) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_BREAK), MP_ROM_INT(UART_IRQ_BREAK) }, \ + +static void uart_event_task(void *self_in) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + uart_event_t event; + for (;;) { + // Waiting for an UART event. + if (xQueueReceive(self->uart_queue, (void *)&event, (TickType_t)portMAX_DELAY)) { + self->mp_irq_flags = 0; + switch (event.type) { + // Event of UART receiving data + case UART_DATA: + self->mp_irq_flags |= UART_IRQ_RX; + break; + case UART_BREAK: + self->mp_irq_flags |= UART_IRQ_BREAK; + break; + default: + break; + } + // Check the flags to see if the user handler should be called + if (self->mp_irq_trigger & self->mp_irq_flags) { + mp_irq_handler(self->mp_irq_obj); + mp_hal_wake_main_task_from_isr(); + } + } + } +} 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); uint32_t baudrate; check_esp_err(uart_get_baudrate(self->uart_num, &baudrate)); - mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, rts=%d, cts=%d, txbuf=%u, rxbuf=%u, timeout=%u, timeout_char=%u", + mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, rts=%d, cts=%d, txbuf=%u, rxbuf=%u, timeout=%u, timeout_char=%u, irq=%d", self->uart_num, baudrate, self->bits, _parity_name[self->parity], - self->stop, self->tx, self->rx, self->rts, self->cts, self->txbuf, self->rxbuf, self->timeout, self->timeout_char); + self->stop, self->tx, self->rx, self->rts, self->cts, self->txbuf, self->rxbuf, self->timeout, self->timeout_char, self->mp_irq_trigger); if (self->invert) { mp_printf(print, ", invert="); uint32_t invert_mask = self->invert; @@ -348,6 +389,7 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->timeout_char = 0; self->invert = 0; self->flowcontrol = 0; + self->uart_event_task = 0; switch (uart_num) { case UART_NUM_0: @@ -378,7 +420,7 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg // Setup check_esp_err(uart_param_config(self->uart_num, &uartcfg)); - check_esp_err(uart_driver_install(uart_num, self->rxbuf, self->txbuf, 0, NULL, 0)); + check_esp_err(uart_driver_install(uart_num, self->rxbuf, self->txbuf, 3, &self->uart_queue, 0)); } mp_map_t kw_args; @@ -422,6 +464,66 @@ static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) { check_esp_err(uart_set_baudrate(self->uart_num, baudrate)); } +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; + 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)); + } + + 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; + if (args[MP_IRQ_ARG_INIT_hard].u_bool) { + mp_raise_ValueError(MP_ERROR_TEXT("hard IRQ is not supported")); + } + self->mp_irq_obj->ishard = false; + self->mp_irq_trigger = trigger; + // Start a task for handling events + if (handler != mp_const_none && self->uart_event_task == NULL) { + xTaskCreatePinnedToCore(uart_event_task, "uart_event_task", 2048, self, + ESP_TASKD_EVENT_PRIO, (TaskHandle_t *)&self->uart_event_task, MP_TASK_COREID); + } else if (handler == mp_const_none && self->uart_event_task != NULL) { + vTaskDelete(self->uart_event_task); + self->uart_event_task = NULL; + } + } + + 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); diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 5051afb799ead..a5c6b9c014492 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -155,6 +155,7 @@ #define MICROPY_PY_MACHINE_UART (1) #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/esp32/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/esp32/machine_wdt.c" #define MICROPY_PY_NETWORK (1) From 4da5de94bbfa675fb59917827942074307694bde Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 21 Aug 2024 17:35:25 +0200 Subject: [PATCH 07/16] nrf/modules/machine/uart: Allow changing the UART baud rate w/o reset. This commit fixes a bug in the existing driver, that the UART baud rate could not be changed without reset or power cycle. It adds as well functionality to UART.deinit(). Signed-off-by: robert-hh --- ports/nrf/modules/machine/uart.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ports/nrf/modules/machine/uart.c b/ports/nrf/modules/machine/uart.c index 90a67bcbc08ad..b4fa0aff1c969 100644 --- a/ports/nrf/modules/machine/uart.c +++ b/ports/nrf/modules/machine/uart.c @@ -62,6 +62,7 @@ typedef struct _machine_uart_buf_t { #define nrfx_uart_tx nrfx_uarte_tx #define nrfx_uart_tx_in_progress nrfx_uarte_tx_in_progress #define nrfx_uart_init nrfx_uarte_init +#define nrfx_uart_uninit nrfx_uarte_uninit #define nrfx_uart_event_t nrfx_uarte_event_t #define NRFX_UART_INSTANCE NRFX_UARTE_INSTANCE @@ -255,7 +256,11 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->buf.rx_ringbuf.iput = 0; // Enable event callback and start asynchronous receive + if (self->initialized) { + nrfx_uart_uninit(self->p_uart); + } nrfx_uart_init(self->p_uart, &config, uart_event_handler); + self->initialized = true; nrfx_uart_rx(self->p_uart, &self->buf.rx_buf[0], 1); #if NRFX_UART_ENABLED @@ -266,7 +271,10 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg } static void mp_machine_uart_deinit(machine_uart_obj_t *self) { - (void)self; + if (self->initialized) { + nrfx_uart_uninit(self->p_uart); + } + self->initialized = false; } // Write a single character on the bus. `data` is an integer to write. From bae809070e0d62cda1b04e97652cf80d513fe86f Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 11 Mar 2024 16:27:36 +0100 Subject: [PATCH 08/16] nrf/modules/machine/uart: Implement Python UART IRQ for nrf52840 boards. Supported triggers: UART.IRQ_RX and UART.IRQ_TXIDLE. It will probably work on other boards as well, but so far untested. The irq.flags() value is changed only when requested by a triggered event. Do not change it otherwise. Signed-off-by: robert-hh --- ports/nrf/Makefile | 1 + ports/nrf/modules/machine/uart.c | 103 ++++++++++++++++++++++++++++++- ports/nrf/mpconfigport.h | 4 ++ 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index c4150c292cb7c..59e74dce4215a 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -191,6 +191,7 @@ endif SRC_SHARED_C += $(addprefix shared/,\ libc/string0.c \ readline/readline.c \ + runtime/mpirq.c \ runtime/pyexec.c \ runtime/stdout_helpers.c \ runtime/sys_stdio_mphal.c \ diff --git a/ports/nrf/modules/machine/uart.c b/ports/nrf/modules/machine/uart.c index b4fa0aff1c969..8d5a73e095f5e 100644 --- a/ports/nrf/modules/machine/uart.c +++ b/ports/nrf/modules/machine/uart.c @@ -70,6 +70,7 @@ typedef struct _machine_uart_buf_t { #define NRF_UART_HWFC_DISABLED NRF_UARTE_HWFC_DISABLED #define NRF_UART_PARITY_EXCLUDED NRF_UARTE_PARITY_EXCLUDED #define NRFX_UART_EVT_RX_DONE NRFX_UARTE_EVT_RX_DONE +#define NRFX_UART_EVT_TX_DONE NRFX_UARTE_EVT_TX_DONE #define NRFX_UART_EVT_ERROR NRFX_UARTE_EVT_ERROR #define NRF_UART_BAUDRATE_1200 NRF_UARTE_BAUDRATE_1200 @@ -89,18 +90,31 @@ typedef struct _machine_uart_buf_t { #endif +#if MICROPY_PY_MACHINE_UART_IRQ +#define NRFX_UART_IRQ_RX (1 << NRFX_UART_EVT_RX_DONE) +#define NRFX_UART_IRQ_TXIDLE (1 << NRFX_UART_EVT_TX_DONE) +#define MP_UART_ALLOWED_FLAGS (NRFX_UART_IRQ_RX | NRFX_UART_IRQ_TXIDLE) +#endif + typedef struct _machine_uart_obj_t { mp_obj_base_t base; const nrfx_uart_t *p_uart; // Driver instance machine_uart_buf_t buf; uint16_t timeout; // timeout waiting for first char (in ms) uint16_t timeout_char; // timeout waiting between chars (in ms) + uint8_t uart_id; + bool initialized; // static flag. Initialized to False + #if MICROPY_PY_MACHINE_UART_IRQ + 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 + #endif } machine_uart_obj_t; static const nrfx_uart_t instance0 = NRFX_UART_INSTANCE(0); static machine_uart_obj_t machine_uart_obj[] = { - {{&machine_uart_type}, .p_uart = &instance0} + {{&machine_uart_type}, .p_uart = &instance0, .uart_id = 0} }; void uart_init0(void) { @@ -117,6 +131,9 @@ static int uart_find(mp_obj_t id) { static void uart_event_handler(nrfx_uart_event_t const *p_event, void *p_context) { machine_uart_obj_t *self = p_context; + #if MICROPY_PY_MACHINE_UART_IRQ + uint16_t mp_irq_flags = 0; + #endif if (p_event->type == NRFX_UART_EVT_RX_DONE) { nrfx_uart_rx(self->p_uart, &self->buf.rx_buf[0], 1); int chr = self->buf.rx_buf[0]; @@ -130,10 +147,24 @@ static void uart_event_handler(nrfx_uart_event_t const *p_event, void *p_context { ringbuf_put((ringbuf_t *)&self->buf.rx_ringbuf, chr); } + #if MICROPY_PY_MACHINE_UART_IRQ + mp_irq_flags |= NRFX_UART_IRQ_RX; + #endif } else if (p_event->type == NRFX_UART_EVT_ERROR) { // Perform a read to unlock UART in case of an error nrfx_uart_rx(self->p_uart, &self->buf.rx_buf[0], 1); + } else if (p_event->type == NRFX_UART_EVT_TX_DONE) { + #if MICROPY_PY_MACHINE_UART_IRQ + mp_irq_flags |= NRFX_UART_IRQ_TXIDLE; + #endif + } + #if MICROPY_PY_MACHINE_UART_IRQ + // 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); } + #endif } bool uart_rx_any(machine_uart_obj_t *self) { @@ -171,8 +202,14 @@ void uart_tx_strn_cooked(machine_uart_obj_t *uart_obj, const char *str, uint len /******************************************************************************/ /* MicroPython bindings */ -// The UART class doesn't have any constants for this port. +#if MICROPY_PY_MACHINE_UART_IRQ +#define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(NRFX_UART_IRQ_RX) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_TXIDLE), MP_ROM_INT(NRFX_UART_IRQ_TXIDLE) }, \ + +#else #define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS +#endif static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { mp_printf(print, "UART(0)"); @@ -255,6 +292,12 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->buf.rx_ringbuf.iget = 0; self->buf.rx_ringbuf.iput = 0; + #if MICROPY_PY_MACHINE_UART_IRQ + self->mp_irq_trigger = 0; + self->mp_irq_obj = NULL; + MP_STATE_PORT(nrf_uart_irq_obj)[self->uart_id] = NULL; + #endif + // Enable event callback and start asynchronous receive if (self->initialized) { nrfx_uart_uninit(self->p_uart); @@ -301,6 +344,60 @@ static bool mp_machine_uart_txdone(machine_uart_obj_t *self) { return !nrfx_uart_tx_in_progress(self->p_uart); } +#if MICROPY_PY_MACHINE_UART_IRQ + +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; + 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(nrf_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; + } + + return self->mp_irq_obj; +} + +#endif + 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 = self_in; byte *buf = buf_in; @@ -383,3 +480,5 @@ static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uint } return MP_STREAM_ERROR; } + +MP_REGISTER_ROOT_POINTER(void *nrf_uart_irq_obj[1]); diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 1c789f779b8e7..7cc8a66d9840c 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -230,6 +230,10 @@ #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/nrf/modules/machine/uart.c" #define MICROPY_PY_MACHINE_UART_READCHAR_WRITECHAR (1) +#if defined(NRF52840) +#define MICROPY_PY_MACHINE_UART_IRQ (1) +#endif + #ifndef MICROPY_PY_MACHINE_TIMER_NRF #define MICROPY_PY_MACHINE_TIMER_NRF (1) #endif From a86619fb6ffb4c860f4c1cc25643f13c6db92467 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 13 Mar 2024 09:17:50 +0100 Subject: [PATCH 09/16] stm32/machine_uart: Add the UART.IRQ_RX event for UART.irq(). Just adding the event symbol. No code change required, and no impact on code execution time when the event is not selected. Tested with STM32F4xx, STM32F7xx and STM32H7xx. Signed-off-by: robert-hh --- ports/stm32/machine_uart.c | 1 + ports/stm32/uart.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/machine_uart.c b/ports/stm32/machine_uart.c index 0f139ae83272f..277ae67ef2c54 100644 --- a/ports/stm32/machine_uart.c +++ b/ports/stm32/machine_uart.c @@ -41,6 +41,7 @@ { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HWCONTROL_RTS) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HWCONTROL_CTS) }, \ { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_FLAG_IDLE) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_FLAG_RXNE) }, \ 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); diff --git a/ports/stm32/uart.h b/ports/stm32/uart.h index 956cbb044f6c0..de4b70cdea738 100644 --- a/ports/stm32/uart.h +++ b/ports/stm32/uart.h @@ -52,7 +52,7 @@ typedef enum { #define CHAR_WIDTH_9BIT (1) // OR-ed IRQ flags which are allowed to be used by the user -#define MP_UART_ALLOWED_FLAGS UART_FLAG_IDLE +#define MP_UART_ALLOWED_FLAGS (UART_FLAG_IDLE | UART_FLAG_RXNE) // OR-ed IRQ flags which should not be touched by the user #define MP_UART_RESERVED_FLAGS UART_FLAG_RXNE From ef69d0f2d3a795666c2bf8c7e202604fc5cd5342 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 27 Jun 2024 19:37:54 +0200 Subject: [PATCH 10/16] 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 --- ports/samd/machine_uart.c | 89 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index aa781a17073ae..b0dc4c5768d13 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; From 7045975d046196e57efb79dc70e6b715155b9bca Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 3 Jul 2024 16:32:35 +0200 Subject: [PATCH 11/16] renesas-ra/machine_uart: Implement UART.IRQ_RXIDLE based on softtimer. Allowing to define the trigger UART.IRQ_RXIDLE as well as UART.IRQ_RX. The delay for the IRQ_RXIDLE interrupt is about 3 character times or 1-2 ms, whichever is larger. The irq.flags() value is changed only with an expected event. Do not change it otherwise. Signed-off-by: robert-hh --- ports/renesas-ra/machine_uart.c | 29 ++++++++++++++++++++++++++++- ports/renesas-ra/uart.c | 27 ++++++++++++++++++++++++++- ports/renesas-ra/uart.h | 24 +++++++++++++++++++++++- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/ports/renesas-ra/machine_uart.c b/ports/renesas-ra/machine_uart.c index 4a659d107af39..b70978ad7a5fe 100644 --- a/ports/renesas-ra/machine_uart.c +++ b/ports/renesas-ra/machine_uart.c @@ -41,7 +41,8 @@ #define MICROPY_PY_MACHINE_UART_CLASS_CONSTANTS \ { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HWCONTROL_RTS) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HWCONTROL_CTS) }, \ - { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(0x10) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_IRQ_RX) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_IRQ_RXIDLE) }, \ static const char *_parity_name[] = {"None", "ODD", "EVEN"}; @@ -297,6 +298,8 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg // reference existing UART object self = MP_STATE_PORT(machine_uart_obj_all)[uart_id]; } + self->mp_irq_obj = NULL; + self->rxidle_state = RXIDLE_INACTIVE; // start the peripheral mp_map_t kw_args; @@ -351,6 +354,29 @@ static mp_int_t mp_machine_uart_readchar(machine_uart_obj_t *self) { } } +// Configure the timer used for IRQ_RXIDLE +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_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; @@ -376,6 +402,7 @@ static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args 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_irq_configure_timer(self, trigger); uart_irq_config(self, true); } diff --git a/ports/renesas-ra/uart.c b/ports/renesas-ra/uart.c index 83b3394f7b453..d17a1fc913fe0 100644 --- a/ports/renesas-ra/uart.c +++ b/ports/renesas-ra/uart.c @@ -85,12 +85,36 @@ static void uart_rx_cb(uint32_t ch, int d) { } #endif + if ((self->mp_irq_trigger & UART_IRQ_RXIDLE) && (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; + } // Check the flags to see if the user handler should be called - if (self->mp_irq_trigger) { + if (self->mp_irq_trigger & UART_IRQ_RX) { + self->mp_irq_flags = UART_IRQ_RX; mp_irq_handler(self->mp_irq_obj); } } +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); + } +} + void uart_init0(void) { } @@ -513,6 +537,7 @@ void uart_tx_strn(machine_uart_obj_t *uart_obj, const char *str, uint len) { 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_config(self, false); + uart_irq_configure_timer(self, new_trigger); self->mp_irq_trigger = new_trigger; uart_irq_config(self, true); return 0; diff --git a/ports/renesas-ra/uart.h b/ports/renesas-ra/uart.h index ee8eb321d552c..1f74df412cb43 100644 --- a/ports/renesas-ra/uart.h +++ b/ports/renesas-ra/uart.h @@ -29,6 +29,7 @@ #define MICROPY_INCLUDED_RA_UART_H #include "shared/runtime/mpirq.h" +#include "shared/runtime/softtimer.h" #include "pin.h" typedef enum { @@ -57,12 +58,28 @@ typedef enum { #define UART_HWCONTROL_CTS (1) #define UART_HWCONTROL_RTS (2) +#define UART_IRQ_RX (0x10) +#define UART_IRQ_RXIDLE (0x1000) +#define RXIDLE_TIMER_MIN (1) + // OR-ed IRQ flags which are allowed to be used by the user -#define MP_UART_ALLOWED_FLAGS ((uint32_t)0x00000010) +#define MP_UART_ALLOWED_FLAGS ((uint32_t)(UART_IRQ_RX | UART_IRQ_RXIDLE)) // OR-ed IRQ flags which should not be touched by the user #define MP_UART_RESERVED_FLAGS ((uint16_t)0x0020) +enum { + RXIDLE_INACTIVE, + RXIDLE_STANDBY, + RXIDLE_ARMED, + RXIDLE_ALERT, +}; + +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; machine_uart_t uart_id : 8; @@ -87,6 +104,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; } machine_uart_obj_t; extern const mp_irq_methods_t uart_irq_methods; @@ -100,6 +120,8 @@ void uart_irq_config(machine_uart_obj_t *self, bool enable); void uart_set_rxbuf(machine_uart_obj_t *self, size_t len, void *buf); void uart_deinit(machine_uart_obj_t *uart_obj); // void uart_irq_handler(mp_uint_t uart_id); +void uart_irq_configure_timer(machine_uart_obj_t *self, mp_uint_t trigger); +void uart_soft_timer_callback(soft_timer_entry_t *self); void uart_attach_to_repl(machine_uart_obj_t *self, bool attached); uint32_t uart_get_baudrate(machine_uart_obj_t *self); From a38b4f42876cf274d77752f5f8cc559deb87f9fa Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 4 Jul 2024 16:57:29 +0200 Subject: [PATCH 12/16] esp32/machine_uart: Implement UART.RX_IDLE based on machine.Timer. The UART.IRQ_IDLE callback is called about two character times after the last byte, or 1 ms, whichever is larger. For the irq, timer 0 is used. machine_timer.c had to be reworked to make it's mechanisms available for machine_uart.c. The irq.flags() value is change only at a requested event. Otherwise keep the state. Signed-off-by: robert-hh --- ports/esp32/machine_timer.c | 49 ++++++++----------- ports/esp32/machine_timer.h | 66 ++++++++++++++++++++++++++ ports/esp32/machine_uart.c | 93 +++++++++++++++++++++++++++++++++++-- 3 files changed, 173 insertions(+), 35 deletions(-) create mode 100644 ports/esp32/machine_timer.h diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index 011f87ba9eb30..278deb10649d4 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -38,6 +38,7 @@ #include "hal/timer_hal.h" #include "hal/timer_ll.h" #include "soc/timer_periph.h" +#include "machine_timer.h" #define TIMER_DIVIDER 8 @@ -46,27 +47,8 @@ #define TIMER_FLAGS 0 -typedef struct _machine_timer_obj_t { - mp_obj_base_t base; - - timer_hal_context_t hal_context; - mp_uint_t group; - mp_uint_t index; - - mp_uint_t repeat; - // ESP32 timers are 64 or 54-bit - uint64_t period; - - mp_obj_t callback; - - intr_handle_t handle; - - struct _machine_timer_obj_t *next; -} machine_timer_obj_t; - const mp_obj_type_t machine_timer_type; -static void machine_timer_disable(machine_timer_obj_t *self); static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); void machine_timer_deinit_all(void) { @@ -91,18 +73,17 @@ static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_pr #endif } -static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); +machine_timer_obj_t *machine_timer_create(mp_uint_t timer) { + + machine_timer_obj_t *self = NULL; #if CONFIG_IDF_TARGET_ESP32C3 - mp_uint_t group = mp_obj_get_int(args[0]) & 1; + mp_uint_t group = timer & 1; mp_uint_t index = 0; #else - mp_uint_t group = (mp_obj_get_int(args[0]) >> 1) & 1; - mp_uint_t index = mp_obj_get_int(args[0]) & 1; + mp_uint_t group = (timer >> 1) & 1; + mp_uint_t index = timer & 1; #endif - machine_timer_obj_t *self = NULL; - // Check whether the timer is already initialized, if so use it for (machine_timer_obj_t *t = MP_STATE_PORT(machine_timer_obj_head); t; t = t->next) { if (t->group == group && t->index == index) { @@ -120,6 +101,14 @@ static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, self->next = MP_STATE_PORT(machine_timer_obj_head); MP_STATE_PORT(machine_timer_obj_head) = self; } + return self; +} + +static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // Create the new timer. + machine_timer_obj_t *self = machine_timer_create(mp_obj_get_int(args[0])); if (n_args > 1 || n_kw > 0) { mp_map_t kw_args; @@ -130,7 +119,7 @@ static mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, return self; } -static void machine_timer_disable(machine_timer_obj_t *self) { +void machine_timer_disable(machine_timer_obj_t *self) { if (self->hal_context.dev != NULL) { // Disable the counter and alarm. timer_ll_enable_counter(self->hal_context.dev, self->index, false); @@ -162,7 +151,7 @@ static void machine_timer_isr(void *self_in) { } } -static void machine_timer_enable(machine_timer_obj_t *self) { +void machine_timer_enable(machine_timer_obj_t *self, void (*timer_isr)) { // Initialise the timer. timer_hal_init(&self->hal_context, self->group, self->index); timer_ll_enable_counter(self->hal_context.dev, self->index, false); @@ -176,7 +165,7 @@ static void machine_timer_enable(machine_timer_obj_t *self) { timer_ll_clear_intr_status(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index)); ESP_ERROR_CHECK( esp_intr_alloc(timer_group_periph_signals.groups[self->group].timer_irq_id[self->index], - TIMER_FLAGS, machine_timer_isr, self, &self->handle) + TIMER_FLAGS, timer_isr, self, &self->handle) ); timer_ll_enable_intr(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index), true); @@ -234,7 +223,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n self->callback = args[ARG_callback].u_obj; self->handle = NULL; - machine_timer_enable(self); + machine_timer_enable(self, machine_timer_isr); return mp_const_none; } diff --git a/ports/esp32/machine_timer.h b/ports/esp32/machine_timer.h new file mode 100644 index 0000000000000..914bedd86baa9 --- /dev/null +++ b/ports/esp32/machine_timer.h @@ -0,0 +1,66 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2015 Damien P. George + * Copyright (c) 2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ESP32_MACHINE_TIMER_H +#define MICROPY_INCLUDED_ESP32_MACHINE_TIMER_H + +#include "hal/timer_hal.h" +#include "hal/timer_ll.h" +#include "soc/timer_periph.h" + +#define TIMER_DIVIDER 8 + +// TIMER_BASE_CLK is normally 80MHz. TIMER_DIVIDER ought to divide this exactly +#define TIMER_SCALE (APB_CLK_FREQ / TIMER_DIVIDER) + +#define TIMER_FLAGS 0 + +typedef struct _machine_timer_obj_t { + mp_obj_base_t base; + + timer_hal_context_t hal_context; + mp_uint_t group; + mp_uint_t index; + + mp_uint_t repeat; + // ESP32 timers are 64-bit + uint64_t period; + + mp_obj_t callback; + + intr_handle_t handle; + + struct _machine_timer_obj_t *next; +} machine_timer_obj_t; + +machine_timer_obj_t *machine_timer_create(mp_uint_t timer); +void machine_timer_enable(machine_timer_obj_t *self, void (*timer_isr)); +void machine_timer_disable(machine_timer_obj_t *self); + +#endif // MICROPY_INCLUDED_ESP32_MACHINE_TIMER_H diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index 1a02594132c0c..d260c45c48f80 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -39,6 +39,7 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "uart.h" +#include "machine_timer.h" #if SOC_UART_SUPPORT_XTAL_CLK // Works independently of APB frequency, on ESP32C3, ESP32S3. @@ -54,8 +55,17 @@ #define UART_INV_MASK (UART_INV_TX | UART_INV_RX | UART_INV_RTS | UART_INV_CTS) #define UART_IRQ_RX (1 << UART_DATA) +#define UART_IRQ_RXIDLE (0x1000) #define UART_IRQ_BREAK (1 << UART_BREAK) -#define MP_UART_ALLOWED_FLAGS (UART_IRQ_RX | UART_IRQ_BREAK) +#define MP_UART_ALLOWED_FLAGS (UART_IRQ_RX | UART_IRQ_RXIDLE | UART_IRQ_BREAK) +#define RXIDLE_TIMER_MIN (5000) // 500 us + +enum { + RXIDLE_INACTIVE, + RXIDLE_STANDBY, + RXIDLE_ARMED, + RXIDLE_ALERT, +}; typedef struct _machine_uart_obj_t { mp_obj_base_t base; @@ -78,6 +88,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 + machine_timer_obj_t *rxidle_timer; + uint8_t rxidle_state; + uint16_t rxidle_period; } machine_uart_obj_t; static const char *_parity_name[] = {"None", "1", "0"}; @@ -93,28 +106,67 @@ static const char *_parity_name[] = {"None", "1", "0"}; { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HW_FLOWCTRL_RTS) }, \ { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HW_FLOWCTRL_CTS) }, \ { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_IRQ_RX) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_IRQ_RXIDLE) }, \ { MP_ROM_QSTR(MP_QSTR_IRQ_BREAK), MP_ROM_INT(UART_IRQ_BREAK) }, \ +static void uart_timer_callback(void *self_in) { + machine_timer_obj_t *self = self_in; + + uint32_t intr_status = timer_ll_get_intr_status(self->hal_context.dev); + + if (intr_status & TIMER_LL_EVENT_ALARM(self->index)) { + timer_ll_clear_intr_status(self->hal_context.dev, TIMER_LL_EVENT_ALARM(self->index)); + if (self->repeat) { + timer_ll_enable_alarm(self->hal_context.dev, self->index, true); + } + } + + // The UART object is referred here by the callback field. + machine_uart_obj_t *uart = (machine_uart_obj_t *)self->callback; + 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 + uart->rxidle_state = RXIDLE_STANDBY; + uart->mp_irq_flags = UART_IRQ_RXIDLE; + mp_irq_handler(uart->mp_irq_obj); + mp_hal_wake_main_task_from_isr(); + machine_timer_disable(uart->rxidle_timer); + } +} + static void uart_event_task(void *self_in) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); uart_event_t event; for (;;) { // Waiting for an UART event. if (xQueueReceive(self->uart_queue, (void *)&event, (TickType_t)portMAX_DELAY)) { - self->mp_irq_flags = 0; + uint16_t mp_irq_flags = 0; switch (event.type) { // Event of UART receiving data case UART_DATA: - self->mp_irq_flags |= UART_IRQ_RX; + if (self->mp_irq_trigger & UART_IRQ_RXIDLE) { + if (self->rxidle_state != RXIDLE_INACTIVE) { + if (self->rxidle_state == RXIDLE_STANDBY) { + self->rxidle_timer->repeat = true; + self->rxidle_timer->handle = NULL; + machine_timer_enable(self->rxidle_timer, uart_timer_callback); + } + } + self->rxidle_state = RXIDLE_ALERT; + } + mp_irq_flags |= UART_IRQ_RX; break; case UART_BREAK: - self->mp_irq_flags |= UART_IRQ_BREAK; + mp_irq_flags |= UART_IRQ_BREAK; break; default: break; } // Check the flags to see if the user handler should be called - if (self->mp_irq_trigger & self->mp_irq_flags) { + if (self->mp_irq_trigger & mp_irq_flags) { + self->mp_irq_flags = mp_irq_flags; mp_irq_handler(self->mp_irq_obj); mp_hal_wake_main_task_from_isr(); } @@ -390,6 +442,7 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->invert = 0; self->flowcontrol = 0; self->uart_event_task = 0; + self->rxidle_state = RXIDLE_INACTIVE; switch (uart_num) { case UART_NUM_0: @@ -464,8 +517,35 @@ static void mp_machine_uart_sendbreak(machine_uart_obj_t *self) { check_esp_err(uart_set_baudrate(self->uart_num, baudrate)); } +// 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; + uint32_t baudrate; + uart_get_baudrate(self->uart_num, &baudrate); + mp_int_t period = TIMER_SCALE * 20 / baudrate + 1; + if (period < RXIDLE_TIMER_MIN) { + period = RXIDLE_TIMER_MIN; + } + self->rxidle_period = period; + self->rxidle_timer->period = period; + // The Python callback is not used. So use this + // data field to hold a reference to the UART object. + self->rxidle_timer->callback = self; + self->rxidle_timer->repeat = true; + self->rxidle_timer->handle = NULL; + 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; } @@ -511,6 +591,9 @@ static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args } self->mp_irq_obj->ishard = false; self->mp_irq_trigger = trigger; + self->rxidle_timer = machine_timer_create(0); + uart_irq_configure_timer(self, trigger); + // Start a task for handling events if (handler != mp_const_none && self->uart_event_task == NULL) { xTaskCreatePinnedToCore(uart_event_task, "uart_event_task", 2048, self, From 9bbe61607ac3e97d96294d588afd09d82613c6a9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Mar 2024 13:51:31 +1100 Subject: [PATCH 13/16] docs/library/machine.UART: Fix UART.irq docs to match current code. These docs now match the code in `extmod/machine_uart.c`. IRQ trigger support still need to be updated for each port (to be done in a follow-up commit). Signed-off-by: Damien George --- docs/library/machine.UART.rst | 62 +++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/docs/library/machine.UART.rst b/docs/library/machine.UART.rst index 072bdb7188a3d..6f47eb9abb7c4 100644 --- a/docs/library/machine.UART.rst +++ b/docs/library/machine.UART.rst @@ -152,31 +152,6 @@ Methods Send a break condition on the bus. This drives the bus low for a duration longer than required for a normal transmission of a character. -.. method:: UART.irq(trigger, priority=1, handler=None, wake=machine.IDLE) - - Create a callback to be triggered when data is received on the UART. - - - *trigger* can only be ``UART.RX_ANY`` - - *priority* level of the interrupt. Can take values in the range 1-7. - Higher values represent higher priorities. - - *handler* an optional function to be called when new characters arrive. - - *wake* can only be ``machine.IDLE``. - - .. note:: - - The handler will be called whenever any of the following two conditions are met: - - - 8 new characters have been received. - - At least 1 new character is waiting in the Rx buffer and the Rx line has been - silent for the duration of 1 complete frame. - - This means that when the handler function is called there will be between 1 to 8 - characters waiting. - - Returns an irq object. - - Availability: WiPy. - .. method:: UART.flush() Waits until all data has been sent. In case of a timeout, an exception is raised. The timeout @@ -203,11 +178,42 @@ Methods Availability: rp2, esp32, esp8266, mimxrt, cc3200, stm32, nrf ports, renesas-ra +.. method:: UART.irq(handler=None, trigger=0, hard=False) + + Configure an interrupt handler to be called when a UART event occurs. + + The arguments are: + + - *handler* is an optional function to be called when the interrupt event + triggers. The handler must take exactly one argument which is the + ``UART`` instance. + + - *trigger* configures the event which can generate an interrupt. + Possible values are: + + - ``UART.IRQ_RXIDLE`` interrupt after receiving at least one character + and then the RX line goes idle. + + - *hard* if true a hardware interrupt is used. This reduces the delay + between the pin change and the handler being called. Hard interrupt + handlers may not allocate memory; see :ref:`isr_rules`. + + Returns an irq object. + + Availability: renesas-ra, stm32. + Constants --------- -.. data:: UART.RX_ANY +.. data:: UART.RTS + UART.CTS + + Flow control options. + + Availability: esp32, mimxrt, renesas-ra, rp2, stm32. + +.. data:: UART.IRQ_RXIDLE - IRQ trigger sources + IRQ trigger sources. - Availability: WiPy. + Availability: stm32. From 03b1b6d8e6a968a2aec002466e516952c6683f53 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 11 Mar 2024 12:31:17 +0100 Subject: [PATCH 14/16] docs/library/machine.UART: Extend the documentation for UART.irq. For more ports and trigger options, based on the current state of the code. Signed-off-by: robert-hh --- docs/library/machine.UART.rst | 53 +++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/docs/library/machine.UART.rst b/docs/library/machine.UART.rst index 6f47eb9abb7c4..0f3e77ec471a9 100644 --- a/docs/library/machine.UART.rst +++ b/docs/library/machine.UART.rst @@ -188,19 +188,59 @@ Methods triggers. The handler must take exactly one argument which is the ``UART`` instance. - - *trigger* configures the event which can generate an interrupt. - Possible values are: + - *trigger* configures the event(s) which can generate an interrupt. + Possible values are a mask of one or more of the following: - ``UART.IRQ_RXIDLE`` interrupt after receiving at least one character and then the RX line goes idle. + - ``UART.IRQ_RX`` interrupt after each received character. + - ``UART.IRQ_TXIDLE`` interrupt after or while the last character(s) of + a message are or have been sent. + - ``UART.IRQ_BREAK`` interrupt when a break state is detected at RX - *hard* if true a hardware interrupt is used. This reduces the delay - between the pin change and the handler being called. Hard interrupt + between the pin change and the handler being called. Hard interrupt handlers may not allocate memory; see :ref:`isr_rules`. Returns an irq object. - Availability: renesas-ra, stm32. + Due to limitations of the hardware not all trigger events are available on all ports. + + .. table:: Availability of triggers + :align: center + + ============== ========== ====== ========== ========= + Port / Trigger IRQ_RXIDLE IRQ_RX IRQ_TXIDLE IRQ_BREAK + ============== ========== ====== ========== ========= + CC3200 yes + ESP32 yes yes yes + MIMXRT yes yes + NRF yes yes + RENESAS-RA yes yes + RP2 yes yes yes + SAMD yes yes yes + STM32 yes yes + ============== ========== ====== ========== ========= + + + .. note:: + - The ESP32 port does not support the option hard=True. + + - The rp2 port's UART.IRQ_TXIDLE is only triggered when the message + is longer than 5 characters and the trigger happens when still 5 characters + are to be sent. + + - The rp2 port's UART.IRQ_BREAK needs receiving valid characters for triggering + again. + + - The SAMD port's UART.IRQ_TXIDLE is triggered while the last character is sent. + + - On STM32F4xx MCU's, using the trigger UART.IRQ_RXIDLE the handler will be called once + after the first character and then after the end of the message, when the line is + idle. + + + Availability: cc3200, esp32, mimxrt, nrf, renesas-ra, rp2, samd, stm32. Constants --------- @@ -213,7 +253,10 @@ Constants Availability: esp32, mimxrt, renesas-ra, rp2, stm32. .. data:: UART.IRQ_RXIDLE + UART.IRQ_RX + UART.IRQ_TXIDLE + UART.IRQ_BREAK IRQ trigger sources. - Availability: stm32. + Availability: renesas-ra, stm32, esp32, rp2040, mimxrt, samd, cc3200. From b8513e6137faaed4b540b0be7756b30fa06a217a Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 16 Aug 2024 23:07:12 +1000 Subject: [PATCH 15/16] tests/extmod: Add test for machine.UART.IRQ_TXIDLE. The test checks whether the message created by the IRQ handler appears about at the end of the data sent by UART. Supported MCUs resp. boards: - RP2040 - Teensy 4.x - Adafruit ItsyBitsy M0 - Adafruit ItsyBitsy M4 - NRF52 (Arduino Nano Connect 33 BLE) Signed-off-by: Damien George --- tests/extmod/machine_uart_irq_txidle.py | 76 +++++++++++++++++++++ tests/extmod/machine_uart_irq_txidle.py.exp | 12 ++++ 2 files changed, 88 insertions(+) create mode 100644 tests/extmod/machine_uart_irq_txidle.py create mode 100644 tests/extmod/machine_uart_irq_txidle.py.exp diff --git a/tests/extmod/machine_uart_irq_txidle.py b/tests/extmod/machine_uart_irq_txidle.py new file mode 100644 index 0000000000000..3e69bb43141aa --- /dev/null +++ b/tests/extmod/machine_uart_irq_txidle.py @@ -0,0 +1,76 @@ +# Test machine.UART.IRQ_TXIDLE firing after a transmission. +# Does not require any external connections. + +try: + from machine import UART + + UART.IRQ_TXIDLE +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +import time, sys + +# Configure pins based on the target. +if "rp2" in sys.platform: + uart_id = 0 + tx_pin = "GPIO0" + rx_pin = "GPIO1" + min_window = 1 +elif "samd" in sys.platform and "ItsyBitsy M0" in sys.implementation._machine: + uart_id = 0 + tx_pin = "D1" + rx_pin = "D0" + # For SAMD delay_ms has to be used for the trailing window, and the + # mininmal time is 11 ms to allow for scheduling. + min_window = 11 +elif "samd" in sys.platform and "ItsyBitsy M4" in sys.implementation._machine: + uart_id = 3 + tx_pin = "D1" + rx_pin = "D0" + min_window = 11 +elif "mimxrt" in sys.platform: + uart_id = 1 + tx_pin = None + min_window = 0 +elif "nrf" in sys.platform: + uart_id = 0 + tx_pin = None + min_window = 0 +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def irq(u): + print("IRQ_TXIDLE:", u.irq().flags() == u.IRQ_TXIDLE) + + +text = "Hello World" * 20 + +# Test that the IRQ is called after the write has completed. +for bits_per_s in (2400, 9600, 115200): + if tx_pin is None: + uart = UART(uart_id, bits_per_s) + else: + uart = UART(uart_id, bits_per_s, tx=tx_pin, rx=rx_pin) + + uart.irq(irq, uart.IRQ_TXIDLE) + + # The IRQ_TXIDLE shall trigger after the message has been sent. Thus + # the test marks a time window close to the expected of the sending + # and the time at which the IRQ should have been fired. + # It is just a rough estimation of 10 characters before and + # 20 characters after the data's end, unless there is a need to + # wait a minimal time, like for SAMD devices. + + bits_per_char = 10 # 1(startbit) + 8(bits) + 1(stopbit) + 0(parity) + start_time_us = (len(text) - 10) * bits_per_char * 1_000_000 // bits_per_s + window_ms = max(min_window, 20 * bits_per_char * 1_000 // bits_per_s + 1) + + print("write", bits_per_s) + uart.write(text) + time.sleep_us(start_time_us) + print("ready") + time.sleep_ms(window_ms) + print("done") diff --git a/tests/extmod/machine_uart_irq_txidle.py.exp b/tests/extmod/machine_uart_irq_txidle.py.exp new file mode 100644 index 0000000000000..2da51d10faada --- /dev/null +++ b/tests/extmod/machine_uart_irq_txidle.py.exp @@ -0,0 +1,12 @@ +write 2400 +ready +IRQ_TXIDLE: True +done +write 9600 +ready +IRQ_TXIDLE: True +done +write 115200 +ready +IRQ_TXIDLE: True +done From 09d070aa55891587ddbd60fa2b8f926ebb1705cd Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 16 Aug 2024 23:07:34 +1000 Subject: [PATCH 16/16] tests/extmod_hardware: Add tests for machine.UART.IRQ_RX/RXIDLE/BREAK. These all require hardware connections, so live in a different directory. Except for the IRQ_BREAK test of ESP32 devices a single UART with loopback is sufficient. General: SAMD21: Due to the limited flash size only SAMD21 devices with external flash support uart.irq(). IRQ_BREAK: ESP32 needs different UART devices for creating and sensing a break. Lacking a second UART the test is skipped for ESP32S2 and ESP32C3. RP2 does not pass the test reliable at 115200 baud, reason to be found. Thus the upper limit is set to 57600 Baud. Coverage: esp32 pass when different UART devices are used. rp2 pass up to 57600 baud IRQ_RX: SAMD21: Being a slow device it needs data to be sent byte-by-byte at 9600 baud, since the IRQ callback is scheduled delayed and then the flags do not match any more. The data matches since it is queued in the FIFO resp. ringbuffer. CC3200: The test cannot be performed since no calls are accepted in the IRQ handler like u.read(). Skipped. Coverage: cc3200 fail due to major differences in the implementation. esp32 pass nrf pass renesas-ra pass samd pass see the notes. stm32 pass IRQ_RXIDLE: STM32: With PyBoard the IRQ is called several times, but only once with the flag IRQ_RXIDLE set. Coverage: esp32 pass mimxrt pass renesas-ra pass rp2 pass samd pass for both SAMD21 and SAMD51 stm32 fail. see notes. Signed-off-by: Damien George Signed-off-by: robert-hh --- .../extmod_hardware/machine_uart_irq_break.py | 62 +++++++++++++++ .../machine_uart_irq_break.py.exp | 15 ++++ tests/extmod_hardware/machine_uart_irq_rx.py | 78 +++++++++++++++++++ .../machine_uart_irq_rx.py.exp | 12 +++ .../machine_uart_irq_rxidle.py | 70 +++++++++++++++++ .../machine_uart_irq_rxidle.py.exp | 12 +++ 6 files changed, 249 insertions(+) create mode 100644 tests/extmod_hardware/machine_uart_irq_break.py create mode 100644 tests/extmod_hardware/machine_uart_irq_break.py.exp create mode 100644 tests/extmod_hardware/machine_uart_irq_rx.py create mode 100644 tests/extmod_hardware/machine_uart_irq_rx.py.exp create mode 100644 tests/extmod_hardware/machine_uart_irq_rxidle.py create mode 100644 tests/extmod_hardware/machine_uart_irq_rxidle.py.exp diff --git a/tests/extmod_hardware/machine_uart_irq_break.py b/tests/extmod_hardware/machine_uart_irq_break.py new file mode 100644 index 0000000000000..82879c1d6e4a8 --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_break.py @@ -0,0 +1,62 @@ +# Test machine.UART.IRQ_BREAK firing after a break is received. +# +# IMPORTANT: This test requires hardware connections: the UART TX and RX +# pins must be wired together. + +try: + from machine import UART + + UART.IRQ_BREAK +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +import time, sys + +# Configure pins based on the target. +if "esp32" in sys.platform: + if "ESP32S2" in sys.implementation._machine or "ESP32C3" in sys.implementation._machine: + print("SKIP") + raise SystemExit + # ESP32 needs separate UART instances for the test + recv_uart_id = 1 + recv_tx_pin = 14 + recv_rx_pin = 5 + send_uart_id = 2 + send_tx_pin = 4 + send_rx_pin = 12 +elif "rp2" in sys.platform: + recv_uart_id = 0 + send_uart_id = 0 + recv_tx_pin = "GPIO0" + recv_rx_pin = "GPIO1" +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def irq(u): + print("IRQ_BREAK:", bool(u.irq().flags() & u.IRQ_BREAK), "data:", u.read(1)) + + +# Test that the IRQ is called for each break received. +for bits_per_s in (2400, 9600, 57600): + recv_uart = UART(recv_uart_id, bits_per_s, tx=recv_tx_pin, rx=recv_rx_pin) + if recv_uart_id != send_uart_id: + send_uart = UART(send_uart_id, bits_per_s, tx=send_tx_pin, rx=send_rx_pin) + else: + send_uart = recv_uart + + recv_uart.irq(irq, recv_uart.IRQ_BREAK) + + print("write", bits_per_s) + for i in range(3): + send_uart.write(str(i)) + send_uart.flush() + time.sleep_ms(10) + send_uart.sendbreak() + time.sleep_ms(10) + if "esp32" in sys.platform: + # On esp32 a read is needed to read in the break byte. + recv_uart.read() + print("done") diff --git a/tests/extmod_hardware/machine_uart_irq_break.py.exp b/tests/extmod_hardware/machine_uart_irq_break.py.exp new file mode 100644 index 0000000000000..ca8afe8f2b6bf --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_break.py.exp @@ -0,0 +1,15 @@ +write 2400 +IRQ_BREAK: True data: b'0' +IRQ_BREAK: True data: b'1' +IRQ_BREAK: True data: b'2' +done +write 9600 +IRQ_BREAK: True data: b'0' +IRQ_BREAK: True data: b'1' +IRQ_BREAK: True data: b'2' +done +write 57600 +IRQ_BREAK: True data: b'0' +IRQ_BREAK: True data: b'1' +IRQ_BREAK: True data: b'2' +done diff --git a/tests/extmod_hardware/machine_uart_irq_rx.py b/tests/extmod_hardware/machine_uart_irq_rx.py new file mode 100644 index 0000000000000..bf34900bd0826 --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_rx.py @@ -0,0 +1,78 @@ +# Test machine.UART.IRQ_RX firing for each character received. +# +# IMPORTANT: This test requires hardware connections: the UART TX and RX +# pins must be wired together. + +try: + from machine import UART + + UART.IRQ_RX +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +import time, sys + +byte_by_byte = False +# Configure pins based on the target. +if "esp32" in sys.platform: + uart_id = 1 + tx_pin = 4 + rx_pin = 5 +elif "pyboard" in sys.platform: + uart_id = 4 + tx_pin = None # PA0 + rx_pin = None # PA1 +elif "samd" in sys.platform and "ItsyBitsy M0" in sys.implementation._machine: + uart_id = 0 + tx_pin = "D1" + rx_pin = "D0" + byte_by_byte = True +elif "samd" in sys.platform and "ItsyBitsy M4" in sys.implementation._machine: + uart_id = 3 + tx_pin = "D1" + rx_pin = "D0" +elif "nrf" in sys.platform: + uart_id = 0 + tx_pin = None + rx_pin = None +elif "renesas-ra" in sys.platform: + uart_id = 9 + tx_pin = None # P602 @ RA6M2 + rx_pin = None # P601 @ RA6M2 +elif "CC3200" in sys.implementation._machine: + # CC3200 doesn't work because it's too slow and has an allocation error in the handler. + print("SKIP") + raise SystemExit +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def irq(u): + print("IRQ_RX:", bool(u.irq().flags() & u.IRQ_RX), "data:", u.read(1)) + + +text = "1234" + +# Test that the IRQ is called for each byte received. +# Use slow baudrates so that the IRQ has time to run. +for bits_per_s in (2400, 9600): + if tx_pin is None: + uart = UART(uart_id, bits_per_s) + else: + uart = UART(uart_id, bits_per_s, tx=tx_pin, rx=rx_pin) + + uart.irq(irq, uart.IRQ_RX) + + print("write", bits_per_s) + if byte_by_byte: + # slow devices need data to be sent slow + for c in text: + uart.write(c) + uart.flush() + else: + uart.write(text) + uart.flush() + time.sleep_ms(100) + print("done") diff --git a/tests/extmod_hardware/machine_uart_irq_rx.py.exp b/tests/extmod_hardware/machine_uart_irq_rx.py.exp new file mode 100644 index 0000000000000..945b1e508fa7c --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_rx.py.exp @@ -0,0 +1,12 @@ +write 2400 +IRQ_RX: True data: b'1' +IRQ_RX: True data: b'2' +IRQ_RX: True data: b'3' +IRQ_RX: True data: b'4' +done +write 9600 +IRQ_RX: True data: b'1' +IRQ_RX: True data: b'2' +IRQ_RX: True data: b'3' +IRQ_RX: True data: b'4' +done diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py b/tests/extmod_hardware/machine_uart_irq_rxidle.py new file mode 100644 index 0000000000000..182ab24ebe0c4 --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py @@ -0,0 +1,70 @@ +# Test machine.UART.IRQ_RXIDLE firing after a set of characters are received. +# +# IMPORTANT: This test requires hardware connections: the UART TX and RX +# pins must be wired together. + +try: + from machine import UART + + UART.IRQ_RXIDLE +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +import time, sys + +# Configure pins based on the target. +if "esp32" in sys.platform: + uart_id = 1 + tx_pin = 4 + rx_pin = 5 +elif "mimxrt" in sys.platform: + uart_id = 1 + tx_pin = None +elif "pyboard" in sys.platform: + uart_id = 4 + tx_pin = None # PA0 + rx_pin = None # PA1 +elif "renesas-ra" in sys.platform: + uart_id = 9 + tx_pin = None # P602 @ RA6M2 + rx_pin = None # P601 @ RA6M2 +elif "rp2" in sys.platform: + uart_id = 0 + tx_pin = "GPIO0" + rx_pin = "GPIO1" +elif "samd" in sys.platform and "ItsyBitsy M0" in sys.implementation._machine: + uart_id = 0 + tx_pin = "D1" + rx_pin = "D0" + byte_by_byte = True +elif "samd" in sys.platform and "ItsyBitsy M4" in sys.implementation._machine: + uart_id = 3 + tx_pin = "D1" + rx_pin = "D0" +else: + print("Please add support for this test on this platform.") + raise SystemExit + + +def irq(u): + print("IRQ_RXIDLE:", bool(u.irq().flags() & u.IRQ_RXIDLE), "data:", u.read()) + + +text = "12345678" + +# Test that the IRQ is called for each set of byte received. +for bits_per_s in (2400, 9600, 115200): + if tx_pin is None: + uart = UART(uart_id, bits_per_s) + else: + uart = UART(uart_id, bits_per_s, tx=tx_pin, rx=rx_pin) + + uart.irq(irq, uart.IRQ_RXIDLE) + + print("write", bits_per_s) + uart.write(text) + uart.flush() + print("ready") + time.sleep_ms(100) + print("done") diff --git a/tests/extmod_hardware/machine_uart_irq_rxidle.py.exp b/tests/extmod_hardware/machine_uart_irq_rxidle.py.exp new file mode 100644 index 0000000000000..ce1890a06a4b2 --- /dev/null +++ b/tests/extmod_hardware/machine_uart_irq_rxidle.py.exp @@ -0,0 +1,12 @@ +write 2400 +ready +IRQ_RXIDLE: True data: b'12345678' +done +write 9600 +ready +IRQ_RXIDLE: True data: b'12345678' +done +write 115200 +ready +IRQ_RXIDLE: True data: b'12345678' +done pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy