From 71e3538a3274322ea845959d75283d1e96fefb6b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 19 May 2021 23:43:21 +1000 Subject: [PATCH] stm32/usb: Add USB_VCP.irq method, to set a callback on USB data RX. Usage: usb = pyb.USB_VCP() usb.irq(lambda u:print(u, u.read()), usb.IRQ_RX) Signed-off-by: Damien George --- docs/library/pyb.USB_VCP.rst | 14 +++++++ ports/stm32/mpconfigport.h | 3 ++ ports/stm32/usb.c | 71 +++++++++++++++++++++++++++++++- ports/stm32/usbd_cdc_interface.c | 2 + ports/stm32/usbd_cdc_interface.h | 2 + 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/docs/library/pyb.USB_VCP.rst b/docs/library/pyb.USB_VCP.rst index bbcbc0701b..1e44e53fd8 100644 --- a/docs/library/pyb.USB_VCP.rst +++ b/docs/library/pyb.USB_VCP.rst @@ -109,6 +109,16 @@ Methods Return value: number of bytes sent. +.. method:: USB_VCP.irq(handler=None, trigger=0, hard=False) + + Register *handler* to be called whenever an event specified by *trigger* + occurs. The *handler* function must take exactly one argument, which will + be the USB VCP object. Pass in ``None`` to disable the callback. + + Valid values for *trigger* are: + + - ``USB_VCP.IRQ_RX``: new data is available for reading from the USB VCP object. + Constants --------- @@ -117,3 +127,7 @@ Constants USB_VCP.CTS to select the flow control type. + +.. data:: USB_VCP.IRQ_RX + + IRQ trigger values for :meth:`USB_VCP.irq`. diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 28a2d6a76d..ff58c515e3 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -383,6 +383,9 @@ struct _mp_bluetooth_btstack_root_pointers_t; /* pointers to all CAN objects (if they have been created) */ \ struct _pyb_can_obj_t *pyb_can_obj_all[MICROPY_HW_MAX_CAN]; \ \ + /* USB_VCP IRQ callbacks (if they have been set) */ \ + mp_obj_t pyb_usb_vcp_irq[MICROPY_HW_USB_CDC_NUM]; \ + \ /* list of registered NICs */ \ mp_obj_list_t mod_network_nic_list; \ \ diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c index 5003bb27cc..eba95de49f 100644 --- a/ports/stm32/usb.c +++ b/ports/stm32/usb.c @@ -39,6 +39,7 @@ #include "py/stream.h" #include "py/mperrno.h" #include "py/mphal.h" +#include "lib/utils/mpirq.h" #include "bufhelper.h" #include "storage.h" #include "sdcard.h" @@ -70,6 +71,9 @@ #define MAX_ENDPOINT(dev_id) (8) #endif +// Constants for USB_VCP.irq trigger. +#define USBD_CDC_IRQ_RX (1) + STATIC void pyb_usb_vcp_init0(void); // this will be persistent across a soft-reset @@ -219,6 +223,7 @@ const mp_rom_obj_tuple_t pyb_usb_hid_keyboard_obj = { void pyb_usb_init0(void) { for (int i = 0; i < MICROPY_HW_USB_CDC_NUM; ++i) { + usb_device.usbd_cdc_itf[i].cdc_idx = i; usb_device.usbd_cdc_itf[i].attached_to_repl = false; } #if MICROPY_HW_USB_HID @@ -644,14 +649,42 @@ const pyb_usb_vcp_obj_t pyb_usb_vcp_obj[MICROPY_HW_USB_CDC_NUM] = { #endif }; +STATIC bool pyb_usb_vcp_irq_scheduled[MICROPY_HW_USB_CDC_NUM]; + STATIC void pyb_usb_vcp_init0(void) { + for (size_t i = 0; i < MICROPY_HW_USB_CDC_NUM; ++i) { + MP_STATE_PORT(pyb_usb_vcp_irq)[i] = mp_const_none; + pyb_usb_vcp_irq_scheduled[i] = false; + } + // Activate USB_VCP(0) on dupterm slot 1 for the REPL MP_STATE_VM(dupterm_objs[1]) = MP_OBJ_FROM_PTR(&pyb_usb_vcp_obj[0]); usb_vcp_attach_to_repl(&pyb_usb_vcp_obj[0], true); } +STATIC mp_obj_t pyb_usb_vcp_irq_run(mp_obj_t self_in) { + pyb_usb_vcp_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint8_t idx = self->cdc_itf->cdc_idx; + mp_obj_t callback = MP_STATE_PORT(pyb_usb_vcp_irq)[idx]; + pyb_usb_vcp_irq_scheduled[idx] = false; + if (callback != mp_const_none && usbd_cdc_rx_num(self->cdc_itf)) { + mp_call_function_1(callback, self_in); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_usb_vcp_irq_run_obj, pyb_usb_vcp_irq_run); + +void usbd_cdc_rx_event_callback(usbd_cdc_itf_t *cdc) { + uint8_t idx = cdc->cdc_idx; + mp_obj_t self = MP_OBJ_FROM_PTR(&pyb_usb_vcp_obj[idx]); + mp_obj_t callback = MP_STATE_PORT(pyb_usb_vcp_irq)[idx]; + if (callback != mp_const_none && !pyb_usb_vcp_irq_scheduled[idx]) { + pyb_usb_vcp_irq_scheduled[idx] = mp_sched_schedule(MP_OBJ_FROM_PTR(&pyb_usb_vcp_irq_run_obj), self); + } +} + STATIC void pyb_usb_vcp_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - int id = ((pyb_usb_vcp_obj_t *)MP_OBJ_TO_PTR(self_in))->cdc_itf - &usb_device.usbd_cdc_itf[0]; + int id = ((pyb_usb_vcp_obj_t *)MP_OBJ_TO_PTR(self_in))->cdc_itf->cdc_idx; mp_printf(print, "USB_VCP(%u)", id); } @@ -796,6 +829,40 @@ STATIC mp_obj_t pyb_usb_vcp_recv(size_t n_args, const mp_obj_t *args, mp_map_t * } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_usb_vcp_recv_obj, 1, pyb_usb_vcp_recv); +// irq(handler=None, trigger=0, hard=False) +STATIC mp_obj_t pyb_usb_vcp_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mp_arg_val_t args[MP_IRQ_ARG_INIT_NUM_ARGS]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_IRQ_ARG_INIT_NUM_ARGS, mp_irq_init_args, args); + pyb_usb_vcp_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + if (n_args > 1 || kw_args->used != 0) { + // 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; + if (trigger == 0) { + handler = mp_const_none; + } else if (trigger != USBD_CDC_IRQ_RX) { + mp_raise_ValueError(MP_ERROR_TEXT("unsupported trigger")); + } + + // Check hard/soft. + if (args[MP_IRQ_ARG_INIT_hard].u_bool) { + mp_raise_ValueError(MP_ERROR_TEXT("hard unsupported")); + } + + // Reconfigure the IRQ. + MP_STATE_PORT(pyb_usb_vcp_irq)[self->cdc_itf->cdc_idx] = handler; + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_usb_vcp_irq_obj, 1, pyb_usb_vcp_irq); + mp_obj_t pyb_usb_vcp___exit__(size_t n_args, const mp_obj_t *args) { return mp_const_none; } @@ -814,6 +881,7 @@ STATIC const mp_rom_map_elem_t pyb_usb_vcp_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj)}, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_identity_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&pyb_usb_vcp_irq_obj) }, { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_identity_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&pyb_usb_vcp___exit___obj) }, @@ -821,6 +889,7 @@ STATIC const mp_rom_map_elem_t pyb_usb_vcp_locals_dict_table[] = { // class constants { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(USBD_CDC_FLOWCONTROL_RTS) }, { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(USBD_CDC_FLOWCONTROL_CTS) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(USBD_CDC_IRQ_RX) }, }; STATIC MP_DEFINE_CONST_DICT(pyb_usb_vcp_locals_dict, pyb_usb_vcp_locals_dict_table); diff --git a/ports/stm32/usbd_cdc_interface.c b/ports/stm32/usbd_cdc_interface.c index a465f608a6..a1ac5fd22f 100644 --- a/ports/stm32/usbd_cdc_interface.c +++ b/ports/stm32/usbd_cdc_interface.c @@ -308,6 +308,8 @@ int8_t usbd_cdc_receive(usbd_cdc_state_t *cdc_in, size_t len) { } } + usbd_cdc_rx_event_callback(cdc); + if ((cdc->flow & USBD_CDC_FLOWCONTROL_RTS) && (usbd_cdc_rx_buffer_full(cdc))) { cdc->rx_buf_full = true; return USBD_BUSY; diff --git a/ports/stm32/usbd_cdc_interface.h b/ports/stm32/usbd_cdc_interface.h index d0509b09f9..6b510f2399 100644 --- a/ports/stm32/usbd_cdc_interface.h +++ b/ports/stm32/usbd_cdc_interface.h @@ -63,6 +63,7 @@ typedef struct _usbd_cdc_itf_t { uint16_t tx_buf_ptr_out_next; // next position of above once transmission finished uint8_t tx_need_empty_packet; // used to flush the USB IN endpoint if the last packet was exactly the endpoint packet size + uint8_t cdc_idx; // between 0 and MICROPY_HW_USB_CDC_NUM-1 volatile uint8_t connect_state; // indicates if we are connected uint8_t attached_to_repl; // indicates if interface is connected to REPL uint8_t flow; // USBD_CDC_FLOWCONTROL_* setting flags @@ -82,5 +83,6 @@ void usbd_cdc_tx_always(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len); int usbd_cdc_rx_num(usbd_cdc_itf_t *cdc); int usbd_cdc_rx(usbd_cdc_itf_t *cdc, uint8_t *buf, uint32_t len, uint32_t timeout); +void usbd_cdc_rx_event_callback(usbd_cdc_itf_t *cdc); #endif // MICROPY_INCLUDED_STM32_USBD_CDC_INTERFACE_H