From c76e823f7227e31ed898294c65df3477cc25c5ec Mon Sep 17 00:00:00 2001 From: Nate Karstens Date: Sun, 24 Dec 2023 07:21:56 -0600 Subject: [PATCH] Add support for transmitting stdout over USB Adds support for transmitting stdout messages over USB. Signed-off-by: Nate Karstens --- bricks/_common_stm32/mphalport.c | 38 +++++++++++++++++--- lib/pbio/drv/usb/usb_stm32.c | 59 ++++++++++++++++++++++++++++++++ lib/pbio/include/pbdrv/usb.h | 21 ++++++++++++ 3 files changed, 114 insertions(+), 4 deletions(-) diff --git a/bricks/_common_stm32/mphalport.c b/bricks/_common_stm32/mphalport.c index d2a2fc30c..00865fd34 100644 --- a/bricks/_common_stm32/mphalport.c +++ b/bricks/_common_stm32/mphalport.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -128,9 +129,36 @@ int mp_hal_stdin_rx_chr(void) { // Send string of given length void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { + uint32_t size; + pbio_error_t err; + + const char *usb_ptr = str; + mp_uint_t usb_len = len; + + while (usb_len) { + size = usb_len; + err = pbdrv_usb_stdout_tx((const uint8_t *)usb_ptr, &size); + + if (err == PBIO_SUCCESS) { + usb_ptr += size; + usb_len -= size; + continue; + } + + if (err != PBIO_ERROR_AGAIN) { + // Ignoring error for now. This means + // stdout lost if USB is disconnected. + break; + } + + if (usb_len) { + MICROPY_EVENT_POLL_HOOK + } + } + while (len) { - uint32_t size = len; - pbio_error_t err = pbsys_bluetooth_tx((const uint8_t *)str, &size); + size = len; + err = pbsys_bluetooth_tx((const uint8_t *)str, &size); if (err == PBIO_SUCCESS) { str += size; @@ -144,12 +172,14 @@ void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { return; } - MICROPY_EVENT_POLL_HOOK + if (len) { + MICROPY_EVENT_POLL_HOOK + } } } void mp_hal_stdout_tx_flush(void) { - while (!pbsys_bluetooth_tx_is_idle()) { + while (!pbsys_bluetooth_tx_is_idle() && !pbdrv_usb_stdout_tx_is_idle()) { MICROPY_EVENT_POLL_HOOK } } diff --git a/lib/pbio/drv/usb/usb_stm32.c b/lib/pbio/drv/usb/usb_stm32.c index b357848ac..225f893a0 100644 --- a/lib/pbio/drv/usb/usb_stm32.c +++ b/lib/pbio/drv/usb/usb_stm32.c @@ -31,8 +31,10 @@ PROCESS(pbdrv_usb_process, "USB"); static uint8_t usb_in_buf[USBD_PYBRICKS_MAX_PACKET_SIZE]; static uint8_t usb_out_buf[USBD_PYBRICKS_MAX_PACKET_SIZE]; +static uint8_t usb_stdout_buf[USBD_PYBRICKS_MAX_PACKET_SIZE]; static volatile uint32_t usb_in_sz; static volatile uint32_t usb_out_sz; +static volatile uint32_t usb_stdout_sz; static USBD_HandleTypeDef husbd; static PCD_HandleTypeDef hpcd; @@ -134,6 +136,53 @@ void pbdrv_usb_stm32_handle_vbus_irq(bool active) { process_poll(&pbdrv_usb_process); } +/** + * Queues data to be transmitted via USB. + * @param data [in] The data to be sent. + * @param size [in, out] The size of @p data in bytes. After return, @p size + * contains the number of bytes actually written. + * @return ::PBIO_SUCCESS if @p data was queued, ::PBIO_ERROR_AGAIN + * if @p data could not be queued at this time (e.g. buffer + * is full), ::PBIO_ERROR_INVALID_OP if there is not an + * active USB connection or ::PBIO_ERROR_NOT_SUPPORTED + * if this platform does not support USB. + */ +pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) { + uint8_t *ptr = usb_stdout_buf; + uint32_t ptr_len = sizeof(usb_stdout_buf); + + // TODO: return PBIO_ERROR_INVALID_OP if not connected + + if (usb_stdout_sz) { + return PBIO_ERROR_AGAIN; + } + + pbio_uuid128_le_copy(ptr, pbio_pybricks_command_event_char_uuid); + ptr += UUID_SZ; + ptr_len -= UUID_SZ; + + *ptr = PBIO_PYBRICKS_EVENT_WRITE_STDOUT; + ptr++; + ptr_len--; + + *size = MIN(*size, ptr_len); + memcpy(ptr, data, *size); + + usb_stdout_sz = UUID_SZ + 1 + *size; + + process_poll(&pbdrv_usb_process); + + return PBIO_SUCCESS; +} + +/** + * Indicates if there is stdout data waiting to be transmitted over USB. + * @retval false if stdout data is currently being transmitted. + */ +bool pbdrv_usb_stdout_tx_is_idle(void) { + return usb_stdout_sz == 0; +} + /** * @brief Pybricks_Itf_Init * Initializes the Pybricks media low layer @@ -145,6 +194,7 @@ static int8_t Pybricks_Itf_Init(void) { USBD_Pybricks_SetRxBuffer(&husbd, usb_in_buf); usb_in_sz = 0; usb_out_sz = 0; + usb_stdout_sz = 0; return USBD_OK; } @@ -289,6 +339,15 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) { // Prepare to receive the next packet USBD_Pybricks_ReceivePacket(&husbd); } + + if (!usb_out_sz && usb_stdout_sz) { + memcpy(usb_out_buf, usb_stdout_buf, usb_stdout_sz); + usb_out_sz = usb_stdout_sz; + usb_stdout_sz = 0; + + USBD_Pybricks_SetTxBuffer(&husbd, usb_out_buf, usb_out_sz); + USBD_Pybricks_TransmitPacket(&husbd); + } } PROCESS_END(); diff --git a/lib/pbio/include/pbdrv/usb.h b/lib/pbio/include/pbdrv/usb.h index e5e09053f..a65bfbab9 100644 --- a/lib/pbio/include/pbdrv/usb.h +++ b/lib/pbio/include/pbdrv/usb.h @@ -12,6 +12,7 @@ #include #include +#include /** * Indicates battery charging capabilites that were detected on a USB port. @@ -43,6 +44,18 @@ pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void); */ bool pbdrv_usb_get_vbus_status(void); +/** + * Transmits the given buffer over the USB stdout stream. + * @return The result of the operation. + */ +pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size); + +/** + * Indicates if the USB stdout stream is idle. + * @return true if the USB stdout stream is idle. +*/ +bool pbdrv_usb_stdout_tx_is_idle(void); + #else // PBDRV_CONFIG_USB static inline pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void) { @@ -53,6 +66,14 @@ static inline bool pbdrv_usb_get_vbus_status(void) { return false; } +static inline pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) { + return PBIO_SUCCESS; +} + +static inline bool pbdrv_usb_stdout_tx_is_idle(void) { + return true; +} + #endif // PBDRV_CONFIG_USB #endif // _PBDRV_USB_H_