diff --git a/src/main/drivers/serial.c b/src/main/drivers/serial.c index dc625aaa354..590a36002b2 100644 --- a/src/main/drivers/serial.c +++ b/src/main/drivers/serial.c @@ -111,7 +111,7 @@ void serialEndWrite(serialPort_t *instance) bool serialIsConnected(const serialPort_t *instance) { if (instance->vTable->isConnected) - instance->vTable->isConnected(instance); + return(instance->vTable->isConnected(instance)); // If API is not defined - assume connected return true; diff --git a/src/main/drivers/serial_usb_vcp.c b/src/main/drivers/serial_usb_vcp.c index 7fdbad2a114..f076e0bfee2 100644 --- a/src/main/drivers/serial_usb_vcp.c +++ b/src/main/drivers/serial_usb_vcp.c @@ -51,6 +51,17 @@ USBD_HandleTypeDef USBD_Device; static vcpPort_t vcpPort; +// Track DTR (Data Terminal Ready) state - indicates if host has COM port open +// Default to true - assume connected until host explicitly clears DTR +static volatile bool cdcPortOpened = true; + +static void cdcCtrlLineStateCallback(void *context, uint16_t ctrlLineState) +{ + UNUSED(context); + // DTR is bit 0 of control line state + cdcPortOpened = (ctrlLineState & 0x01) != 0; +} + static void usbVcpSetBaudRate(serialPort_t *instance, uint32_t baudRate) { UNUSED(instance); @@ -103,7 +114,8 @@ static uint8_t usbVcpRead(serialPort_t *instance) static bool usbVcpIsConnected(const serialPort_t *instance) { (void)instance; - return usbIsConnected() && usbIsConfigured(); + // Check USB hardware state AND whether host has opened the COM port (DTR) + return usbIsConnected() && usbIsConfigured() && cdcPortOpened; } static void usbVcpWriteBuf(serialPort_t *instance, const void *data, int count) @@ -209,6 +221,9 @@ void usbVcpInitHardware(void) IOInit(IOGetByTag(IO_TAG(PA11)), OWNER_USB, RESOURCE_INPUT, 0); IOInit(IOGetByTag(IO_TAG(PA12)), OWNER_USB, RESOURCE_OUTPUT, 0); USBD_Init(&USB_OTG_dev, USB_OTG_FS_CORE_ID, &USR_desc, &USBD_CDC_cb, &USR_cb); + + // Register callback for DTR state changes + CDC_SetCtrlLineStateCb(cdcCtrlLineStateCallback, NULL); #elif defined(STM32F7) || defined(STM32H7) usbGenerateDisconnectPulse(); @@ -225,7 +240,10 @@ void usbVcpInitHardware(void) /* Start Device Process */ USBD_Start(&USBD_Device); - + + // Register callback for DTR state changes + CDC_SetCtrlLineStateCb(cdcCtrlLineStateCallback, NULL); + #ifdef STM32H7 HAL_PWREx_EnableUSBVoltageDetector(); delay(100); // Cold boot failures observed without this, even when USB cable is not connected diff --git a/src/main/vcp_hal/usbd_cdc_interface.c b/src/main/vcp_hal/usbd_cdc_interface.c index dbc093fb641..7ad16084ad9 100644 --- a/src/main/vcp_hal/usbd_cdc_interface.c +++ b/src/main/vcp_hal/usbd_cdc_interface.c @@ -415,6 +415,8 @@ uint32_t CDC_Send_FreeBytes(void) * @param sendLength: Number of data to be sent (in bytes) * @retval Bytes sent */ +#define VCP_WRITE_TIMEOUT_MS 50 + uint32_t CDC_Send_DATA(const uint8_t *ptrBuffer, uint32_t sendLength) { #if defined(STM32H7) @@ -423,13 +425,22 @@ uint32_t CDC_Send_DATA(const uint8_t *ptrBuffer, uint32_t sendLength) USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)USBD_Device.pCDC_ClassData; #endif - while (hcdc->TxState != 0); + uint32_t start = millis(); + + while (hcdc->TxState != 0) { + if (millis() - start > VCP_WRITE_TIMEOUT_MS) { + return 0; + } + } for (uint32_t i = 0; i < sendLength; i++) { UserTxBuffer[UserTxBufPtrIn] = ptrBuffer[i]; UserTxBufPtrIn = (UserTxBufPtrIn + 1) % APP_TX_DATA_SIZE; while (CDC_Send_FreeBytes() == 0) { + if (millis() - start > VCP_WRITE_TIMEOUT_MS) { + return i; // Return partial count + } delay(1); } } diff --git a/src/main/vcpf4/usbd_cdc_vcp.c b/src/main/vcpf4/usbd_cdc_vcp.c index 15c1f7e39d9..7511f617eb4 100644 --- a/src/main/vcpf4/usbd_cdc_vcp.c +++ b/src/main/vcpf4/usbd_cdc_vcp.c @@ -178,13 +178,24 @@ static uint16_t VCP_Ctrl(uint32_t Cmd, uint8_t* Buf, uint32_t Len) *******************************************************************************/ uint32_t CDC_Send_DATA(const uint8_t *ptrBuffer, uint32_t sendLength) { - VCP_DataTx(ptrBuffer, sendLength); + if (VCP_DataTx(ptrBuffer, sendLength) != USBD_OK) { + return 0; + } return sendLength; } uint32_t CDC_Send_FreeBytes(void) { - return APP_RX_DATA_SIZE - CDC_Receive_BytesAvailable(); + // Calculate free space in APP_Rx_Buffer (outbound to host) + // Using correct circular buffer math with volatile pointers + uint32_t ptr_in = APP_Rx_ptr_in; + uint32_t ptr_out = APP_Rx_ptr_out; + + if (ptr_out > ptr_in) { + return ptr_out - ptr_in - 1; + } else { + return APP_RX_DATA_SIZE + ptr_out - ptr_in - 1; + } } /** @@ -194,18 +205,29 @@ uint32_t CDC_Send_FreeBytes(void) * @param Len: Number of data to be sent (in bytes) * @retval Result of the operation: USBD_OK if all operations are OK else VCP_FAIL */ +#define VCP_WRITE_TIMEOUT_MS 50 + static uint16_t VCP_DataTx(const uint8_t* Buf, uint32_t Len) { + uint32_t start = millis(); + /* make sure that any paragraph end frame is not in play could just check for: USB_CDC_ZLP, but better to be safe and wait for any existing transmission to complete. */ - while (USB_Tx_State != 0); + while (USB_Tx_State != 0) { + if (millis() - start > VCP_WRITE_TIMEOUT_MS) { + return USBD_FAIL; + } + } for (uint32_t i = 0; i < Len; i++) { // Stall if the ring buffer is full while (((APP_Rx_ptr_in + 1) % APP_RX_DATA_SIZE) == APP_Rx_ptr_out) { + if (millis() - start > VCP_WRITE_TIMEOUT_MS) { + return USBD_FAIL; + } delay(1); } diff --git a/src/main/vcpf4/usbd_usr.c b/src/main/vcpf4/usbd_usr.c index 24c5f17c206..15aa47fb257 100644 --- a/src/main/vcpf4/usbd_usr.c +++ b/src/main/vcpf4/usbd_usr.c @@ -21,6 +21,7 @@ #include "usbd_usr.h" #include "usbd_ioreq.h" +#include "usbd_cdc_vcp.h" USBD_Usr_cb_TypeDef USR_cb = { @@ -102,13 +103,17 @@ void USBD_USR_DeviceDisconnected (void) /** * @brief USBD_USR_DeviceSuspended -* Displays the message on LCD on device suspend Event +* Handle device suspend event - treat as disconnect for safety. +* This helps on boards without VBUS sensing where hardware +* disconnect detection may not work. * @param None * @retval None */ void USBD_USR_DeviceSuspended(void) { - /* Users can do their application actions here for the USB-Reset */ + // Treat suspend as disconnect - prevents blocking on USB writes + // when cable is unplugged on boards without VBUS sensing + bDeviceState = UNCONNECTED; }