From 9a567b04e7c43024a2a4dce2618b24f822dfe5bc Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 9 Jun 2022 13:46:54 +0200 Subject: [PATCH] samd/machine_uart: Support buffered TX for UART. It can be enabled/disabled by a configuration switch. The code size increase is 308 bytes, but it requires RAM space for buffers, the larger UART object and root pointers. --- ports/samd/machine_uart.c | 78 +++++++++++++++++++++++------ ports/samd/mcu/samd21/mpconfigmcu.h | 2 + ports/samd/mcu/samd51/mpconfigmcu.h | 2 + 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index 0ed7fb95d0..92e63ee51e 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -37,7 +37,6 @@ #define DEFAULT_BUFFER_SIZE (256) #define MIN_BUFFER_SIZE (32) #define MAX_BUFFER_SIZE (32766) -#define USART_BUFFER_TX (0) typedef struct _machine_uart_obj_t { mp_obj_base_t base; @@ -54,7 +53,7 @@ typedef struct _machine_uart_obj_t { uint16_t timeout_char; // timeout waiting between chars (in ms) bool new; ringbuf_t read_buffer; - #if USART_BUFFER_TX + #if MICROPY_HW_UART_TXBUF ringbuf_t write_buffer; #endif } machine_uart_obj_t; @@ -91,7 +90,15 @@ void common_uart_irq_handler(int uart_id) { // Now handler the incoming data uart_drain_rx_fifo(self, uart); } 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 + uart->USART.INTENCLR.bit.DRE = 1; + } + #endif } else { // Disable the other interrupts, if set by error uart->USART.INTENCLR.reg = (uint8_t) ~(SERCOM_USART_INTENCLR_DRE | SERCOM_USART_INTENCLR_RXC); @@ -127,7 +134,7 @@ STATIC mp_obj_t 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 USART_BUFFER_TX + #if MICROPY_HW_UART_TXBUF { MP_QSTR_txbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, #endif }; @@ -191,7 +198,7 @@ STATIC mp_obj_t machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args } } - #if USART_BUFFER_TX + #if MICROPY_HW_UART_TXBUF // Set the TX buffer size if configured. size_t txbuf_len = DEFAULT_BUFFER_SIZE; if (args[ARG_txbuf].u_int > 0) { @@ -224,7 +231,7 @@ STATIC mp_obj_t machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args ringbuf_alloc(&(self->read_buffer), rxbuf_len + 1); MP_STATE_PORT(samd_uart_rx_buffer[self->id]) = self->read_buffer.buf; - #if USART_BUFFER_TX + #if MICROPY_HW_UART_TXBUF ringbuf_alloc(&(self->write_buffer), txbuf_len + 1); MP_STATE_PORT(samd_uart_tx_buffer[self->id]) = self->write_buffer.buf; #endif @@ -274,6 +281,8 @@ STATIC mp_obj_t machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args uint32_t baud = 65536 - ((uint64_t)(65536 * 16) * self->baudrate + get_apb_freq() / 2) / get_apb_freq(); uart->USART.BAUD.bit.BAUD = baud; // Set Baud + sercom_register_irq(self->id, &common_uart_irq_handler); + // Enable RXC interrupt uart->USART.INTENSET.bit.RXC = 1; #if defined(MCU_SAMD21) @@ -281,7 +290,13 @@ STATIC mp_obj_t machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args #elif defined(MCU_SAMD51) NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * self->id + 2); #endif - sercom_register_irq(self->id, &common_uart_irq_handler); + #if MICROPY_HW_UART_TXBUF + // Enable DRE interrupt + // SAMD21 has just 1 IRQ for all USART events, so no need for an additional NVIC enable + #if defined(MCU_SAMD51) + NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * self->id + 0); + #endif + #endif sercom_enable(uart, 1); } @@ -330,7 +345,7 @@ STATIC mp_obj_t machine_uart_deinit(mp_obj_t self_in) { // Disable interrupts uart->USART.INTENCLR.reg = 0xff; MP_STATE_PORT(samd_uart_rx_buffer[self->id]) = NULL; - #if USART_BUFFER_TX + #if MICROPY_HW_UART_TXBUF MP_STATE_PORT(samd_uart_tx_buffer[self->id]) = NULL; #endif return mp_const_none; @@ -339,7 +354,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_deinit_obj, machine_uart_deinit); STATIC mp_obj_t machine_uart_any(mp_obj_t self_in) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - // get all bytes from the fifo first + // get all bytes from the fifo first. May be obsolete. uart_drain_rx_fifo(self, sercom_instance[self->id]); return MP_OBJ_NEW_SMALL_INT(ringbuf_avail(&self->read_buffer)); } @@ -349,9 +364,14 @@ STATIC mp_obj_t machine_uart_sendbreak(mp_obj_t self_in) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); uint32_t break_time_us = 13 * 1000000 / self->baudrate; + // Wait for the tx buffer to drain. + #if MICROPY_HW_UART_TXBUF + while (ringbuf_avail(&self->write_buffer) > 0) { + MICROPY_EVENT_POLL_HOOK + } + #endif // Wait for the TX queue & register to clear // Since the flags are not safe, just wait sufficiently long. - // Once tx buffering is implemented, wait as well for the buffer to clear. mp_hal_delay_us(2 * break_time_us); // Disable MUX PORT->Group[self->tx / 32].PINCFG[self->tx % 32].bit.PMUXEN = 0; @@ -389,7 +409,7 @@ STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_t STATIC mp_uint_t 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 = mp_hal_ticks_ms() + self->timeout; + uint64_t t = mp_hal_ticks_ms_64() + self->timeout; uint64_t timeout_char = self->timeout_char; uint8_t *dest = buf_in; Sercom *uart = sercom_instance[self->id]; @@ -403,7 +423,7 @@ STATIC mp_uint_t machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t siz uart_drain_rx_fifo(self, uart); break; } - if (mp_hal_ticks_ms() > t) { // timed out + if (mp_hal_ticks_ms_64() > t) { // timed out if (i <= 0) { *errcode = MP_EAGAIN; return MP_STREAM_ERROR; @@ -421,17 +441,40 @@ STATIC mp_uint_t machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t siz STATIC mp_uint_t machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - size_t remaining = size; + size_t i = 0; const uint8_t *src = buf_in; Sercom *uart = sercom_instance[self->id]; - while (remaining--) { - while (!(uart->USART.INTFLAG.bit.DRE)) { + #if MICROPY_HW_UART_TXBUF + uint64_t t = mp_hal_ticks_ms_64() + self->timeout; + + while (i < size) { + // Wait for the first/next character to be sent. + while (ringbuf_free(&(self->write_buffer)) == 0) { + 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; - src += 1; + ringbuf_put(&(self->write_buffer), *src++); + i++; + uart->USART.INTENSET.bit.DRE = 1; // kick off the IRQ } + #else + + while (i < size) { + while (!(uart->USART.INTFLAG.bit.DRE)) { + } + uart->USART.DATA.bit.DATA = *src++; + i++; + } + #endif return size; } @@ -473,3 +516,6 @@ MP_DEFINE_CONST_OBJ_TYPE( ); MP_REGISTER_ROOT_POINTER(void *samd_uart_rx_buffer[SERCOM_INST_NUM]); +#if MICROPY_HW_UART_TXBUF +MP_REGISTER_ROOT_POINTER(void *samd_uart_tx_buffer[SERCOM_INST_NUM]); +#endif diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index 587f3f4b3a..7b91be1430 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -5,6 +5,8 @@ #define MICROPY_HW_FLASH_STORAGE_BYTES (0xFFFF) #define VFS_BLOCK_SIZE_BYTES (1536) // 24x 64B flash pages; +#define MICROPY_HW_UART_TXBUF (1) + #define CPU_FREQ (48000000) #define APB_FREQ (48000000) diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 95fc635e5e..089ca48de8 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -19,6 +19,8 @@ unsigned long trng_random_u32(void); #define MICROPY_HW_FLASH_STORAGE_BYTES (0x1FFFF) #define VFS_BLOCK_SIZE_BYTES (1536) // +#define MICROPY_HW_UART_TXBUF (1) + #define CPU_FREQ (120000000) #define APB_FREQ (48000000) #define DPLLx_REF_FREQ (32768)