From 67689bfd7e28df8d4fa23c8f25065aeb0b9d9658 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Fri, 14 Dec 2018 17:07:12 +1100 Subject: [PATCH] stm32/usb: Add flow control option for USB VCP data received from host. It's off by default and can be enabled at run-time with: pyb.USB_VCP().init(flow=pyb.USB_VCP.RTS) --- ports/stm32/usb.c | 25 +++++++++++++ ports/stm32/usbd_cdc_interface.c | 35 ++++++++++++++++--- ports/stm32/usbd_cdc_interface.h | 6 ++++ .../stm32/usbdev/class/src/usbd_cdc_msc_hid.c | 7 ++-- 4 files changed, 65 insertions(+), 8 deletions(-) diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c index b73bfdc311..a5be6cd77d 100644 --- a/ports/stm32/usb.c +++ b/ports/stm32/usb.c @@ -410,6 +410,27 @@ STATIC mp_obj_t pyb_usb_vcp_make_new(const mp_obj_type_t *type, size_t n_args, s } } +// init(*, flow=-1) +STATIC mp_obj_t pyb_usb_vcp_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_flow }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_flow, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + pyb_usb_vcp_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // flow control + if (args[ARG_flow].u_int != -1) { + self->cdc_itf->flow = args[ARG_flow].u_int; + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_usb_vcp_init_obj, 1, pyb_usb_vcp_init); + STATIC mp_obj_t pyb_usb_vcp_setinterrupt(mp_obj_t self_in, mp_obj_t int_chr_in) { mp_hal_set_interrupt_char(mp_obj_get_int(int_chr_in)); return mp_const_none; @@ -510,6 +531,7 @@ mp_obj_t pyb_usb_vcp___exit__(size_t n_args, const mp_obj_t *args) { STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_usb_vcp___exit___obj, 4, 4, pyb_usb_vcp___exit__); STATIC const mp_rom_map_elem_t pyb_usb_vcp_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&pyb_usb_vcp_init_obj) }, { MP_ROM_QSTR(MP_QSTR_setinterrupt), MP_ROM_PTR(&pyb_usb_vcp_setinterrupt_obj) }, { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&pyb_usb_vcp_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&pyb_usb_vcp_any_obj) }, @@ -524,6 +546,9 @@ STATIC const mp_rom_map_elem_t pyb_usb_vcp_locals_dict_table[] = { { 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) }, + + // class constants + { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(USBD_CDC_FLOWCONTROL_RTS) }, }; 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 149971bfb5..bc35ff50c9 100644 --- a/ports/stm32/usbd_cdc_interface.c +++ b/ports/stm32/usbd_cdc_interface.c @@ -69,6 +69,7 @@ uint8_t *usbd_cdc_init(usbd_cdc_state_t *cdc_in) { // be filled (by usbd_cdc_tx_always) before the USB device is connected. cdc->rx_buf_put = 0; cdc->rx_buf_get = 0; + cdc->rx_buf_full = false; cdc->tx_buf_ptr_out = 0; cdc->tx_buf_ptr_out_shadow = 0; cdc->tx_need_empty_packet = 0; @@ -236,6 +237,25 @@ void HAL_PCD_SOFCallback(PCD_HandleTypeDef *hpcd) { } } +bool usbd_cdc_rx_buffer_full(usbd_cdc_itf_t *cdc) { + int get = cdc->rx_buf_get, put = cdc->rx_buf_put; + int remaining = (get - put) + (-((int) (get <= put)) & USBD_CDC_RX_DATA_SIZE); + return remaining < CDC_DATA_MAX_PACKET_SIZE + 1; +} + +void usbd_cdc_rx_check_resume(usbd_cdc_itf_t *cdc) { + uint32_t irq_state = disable_irq(); + if (cdc->rx_buf_full) { + if (!usbd_cdc_rx_buffer_full(cdc)) { + cdc->rx_buf_full = false; + enable_irq(irq_state); + USBD_CDC_ReceivePacket(&cdc->base, cdc->rx_packet_buf); + return; + } + } + enable_irq(irq_state); +} + // Data received over USB OUT endpoint is processed here. // len: number of bytes received into the buffer we passed to USBD_CDC_ReceivePacket // Returns USBD_OK if all operations are OK else USBD_FAIL @@ -257,10 +277,14 @@ int8_t usbd_cdc_receive(usbd_cdc_state_t *cdc_in, size_t len) { } } - // initiate next USB packet transfer - USBD_CDC_ReceivePacket(&cdc->base, cdc->rx_packet_buf); - - return USBD_OK; + if ((cdc->flow & USBD_CDC_FLOWCONTROL_RTS) && (usbd_cdc_rx_buffer_full(cdc))) { + cdc->rx_buf_full = true; + return USBD_BUSY; + } else { + // initiate next USB packet transfer + cdc->rx_buf_full = false; + return USBD_CDC_ReceivePacket(&cdc->base, cdc->rx_packet_buf); + } } int usbd_cdc_tx_half_empty(usbd_cdc_itf_t *cdc) { @@ -339,6 +363,7 @@ int usbd_cdc_rx_num(usbd_cdc_itf_t *cdc) { if (rx_waiting < 0) { rx_waiting += USBD_CDC_RX_DATA_SIZE; } + usbd_cdc_rx_check_resume(cdc); return rx_waiting; } @@ -359,6 +384,7 @@ int usbd_cdc_rx(usbd_cdc_itf_t *cdc, uint8_t *buf, uint32_t len, uint32_t timeou // IRQs disabled so buffer will never be filled; return immediately return i; } + usbd_cdc_rx_check_resume(cdc); __WFI(); // enter sleep mode, waiting for interrupt } @@ -366,6 +392,7 @@ int usbd_cdc_rx(usbd_cdc_itf_t *cdc, uint8_t *buf, uint32_t len, uint32_t timeou buf[i] = cdc->rx_user_buf[cdc->rx_buf_get]; cdc->rx_buf_get = (cdc->rx_buf_get + 1) & (USBD_CDC_RX_DATA_SIZE - 1); } + usbd_cdc_rx_check_resume(cdc); // Success, return number of bytes read return len; diff --git a/ports/stm32/usbd_cdc_interface.h b/ports/stm32/usbd_cdc_interface.h index fd69222f10..780d4816e8 100644 --- a/ports/stm32/usbd_cdc_interface.h +++ b/ports/stm32/usbd_cdc_interface.h @@ -39,6 +39,10 @@ #define USBD_CDC_CONNECT_STATE_CONNECTING (1) #define USBD_CDC_CONNECT_STATE_CONNECTED (2) +// Flow control settings +#define USBD_CDC_FLOWCONTROL_NONE (0) +#define USBD_CDC_FLOWCONTROL_RTS (1) + typedef struct _usbd_cdc_itf_t { usbd_cdc_state_t base; // state for the base CDC layer @@ -46,6 +50,7 @@ typedef struct _usbd_cdc_itf_t { uint8_t rx_user_buf[USBD_CDC_RX_DATA_SIZE]; // received data is buffered here until the user reads it volatile uint16_t rx_buf_put; // circular buffer index uint16_t rx_buf_get; // circular buffer index + uint8_t rx_buf_full; // rx from host will be blocked while this is true uint8_t tx_buf[USBD_CDC_TX_DATA_SIZE]; // data for USB IN endpoind is stored in this buffer uint16_t tx_buf_ptr_in; // increment this pointer modulo USBD_CDC_TX_DATA_SIZE when new data is available @@ -55,6 +60,7 @@ typedef struct _usbd_cdc_itf_t { 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 } usbd_cdc_itf_t; // This is implemented in usb.c diff --git a/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c b/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c index fee1b23ea1..a576796e27 100644 --- a/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c +++ b/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c @@ -1128,14 +1128,13 @@ static uint8_t USBD_CDC_MSC_HID_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum) /* USB data will be immediately processed, this allow next USB traffic being NAKed till the end of the application Xfer */ - usbd_cdc_receive(usbd->cdc, len); + return usbd_cdc_receive(usbd->cdc, len); - return USBD_OK; #if MICROPY_HW_USB_ENABLE_CDC2 } else if ((usbd->usbd_mode & USBD_MODE_CDC2) && epnum == (CDC2_OUT_EP & 0x7f)) { size_t len = USBD_LL_GetRxDataSize(pdev, epnum); - usbd_cdc_receive(usbd->cdc2, len); - return USBD_OK; + return usbd_cdc_receive(usbd->cdc2, len); + #endif } else if ((usbd->usbd_mode & USBD_MODE_MSC) && epnum == (MSC_OUT_EP & 0x7f)) { MSC_BOT_DataOut(pdev, epnum);