Skip to content

Commit

Permalink
samd/machine_uart: Implement a Python UART IRQ handler.
Browse files Browse the repository at this point in the history
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 <robert@hammelrath.com>
  • Loading branch information
robert-hh authored and dpgeorge committed Aug 29, 2024
1 parent 01c046d commit 8e1123b
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 7 deletions.
123 changes: 116 additions & 7 deletions ports/samd/machine_uart.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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
Expand All @@ -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
);
}

Expand All @@ -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} },
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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) {
Expand All @@ -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++;
Expand Down
1 change: 1 addition & 0 deletions ports/samd/mcu/samd21/mpconfigmcu.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions ports/samd/mcu/samd51/mpconfigmcu.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 8e1123b

Please sign in to comment.