Thanks.
There is some weird code there which might be working by luck, like this one which is not checking TxState (can work by luck because I found experimentally that USB can accept data at a very high speed, like 10k packets per second, and the TxState flag goes busy for only 20us at a time)
USBD_CDC_SetTxBuffer(hUsbDevice_0, Buf, Len);
// Busy wait if USB is busy or exit on success or disconnection happens
while(1)
{
//Check if USB went offline while retrying
if ((hUsbDevice_0->dev_state != USBD_STATE_CONFIGURED)
|| (hUsbDevice_0->ep0_state == USBD_EP0_STATUS_IN))
{
result = USBD_FAIL;
break;
}
// Try send
result = USBD_CDC_TransmitPacket(hUsbDevice_0);
I also wonder why he does a set_buffer, then other checks, and then conditionally does a transmit. Surely those two lines should always go together, and only once you are actually outputting data.
The other thing is that they appear to be transmitting from a "foreground code" context, while the USB code all runs under interrupts, so interrupts need to be disabled around the buffer set and data output - like I do below
if ( g_USB_started && (Len>0) && (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9)==1) )
{
osMutexAcquire(g_CDC_transmit_mutex, osWaitForever);
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassDataCDC;
uint32_t timeout=0;
bool error=false;
// Wait on USB Host accepting the data.
// A simple 1 sec timeout prevents a hang.
if ( flow_control )
{
// Loop around until USB Host has picked up the previous data
while (hcdc->TxState != 0) // This gets changed by USB interrupt
{
osDelay(2);
timeout++;
if (timeout>500)
{
hcdc->TxState=0;
error=true;
break;
}
}
}
if (!error)
{
// Output the data to USB.
// USB interrupts must be disabled around this.
__disable_irq();
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
USBD_CDC_TransmitPacket(&hUsbDeviceFS);
__enable_irq();
}
osMutexRelease(g_CDC_transmit_mutex);
g_comms_act[0]=LED_COM_TC; // indicate data flow on LED 0
// If flow control is selected OFF, we do a crude wait to make CDC output work
// even with a slow Host.
// In the usage context (outputting debugs to Teraterm) this actually needs to be only
// a ~100-200us wait, but is host dependent. This gives us a 1-2ms delay between
// *blocks* (typically whole lines).
if ( !flow_control )
{
asm("nop"); // for breakpoints
osDelay(2);
}
}
I will look at the other code and see if I can extract some additional "USB active" qualifiers. However so much of this seems to be Host OS dependent.
I can see stuff like
USBD_StatusTypeDef USBD_LL_SOF(USBD_HandleTypeDef *pdev)
{
if(pdev->dev_state == USBD_STATE_CONFIGURED)
{
if(pdev->pClass->SOF != NULL)
{
pdev->pClass->SOF(pdev);
}
}
return USBD_OK;
}
but can't see how to get to pdev from within CDC_Transmit_FS().
Somebody on the ST forum wrote that __disable_irq(); cannot be used within an RTOS task but I totally don't get why that should be. Obviously it will stop RTOS task switching. If you just want to stop RTOS task switching you use portENTER_CRITICAL().
After more digging I found that
/**
* @brief Opens an endpoint of the low level driver.
* @param pdev: Device handle
* @param ep_addr: Endpoint number
* @param ep_type: Endpoint type
* @param ep_mps: Endpoint max packet size
* @retval USBD status
*/
USBD_StatusTypeDef USBD_LL_OpenEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_mps)
{
gets called when a USB connection is made, and
/**
* @brief Closes an endpoint of the low level driver.
* @param pdev: Device handle
* @param ep_addr: Endpoint number
* @retval USBD status
*/
USBD_StatusTypeDef USBD_LL_CloseEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
TopLED(false);
HAL_StatusTypeDef hal_status = HAL_OK;
USBD_StatusTypeDef usb_status = USBD_OK;
hal_status = HAL_PCD_EP_Close(pdev->pData, ep_addr);
usb_status = USBD_Get_USB_Status(hal_status);
return usb_status;
}
gets called when it is broken, so these two functions could be used to toggle a flag.
Another pair of functions which does a similar thing but specifically for CDC is
CDC_Init_FS()
CDC_DeInit_FS()These, in the original ST code, do a malloc() and free() which nicely crashes your target after a few days as windoze goes to sleep periodically - due to heap fragmentation
This was replaced by a static buffer, which incidentally makes the 2nd function an empty one.
I am now using these to toggle a flag. No indication however if there is an
application on the VCP COM port.