Yes it's strange that there isn't a simple check function or couple of callbacks for this.
What I did (using an STM32G0B1) was add a function to usb_device.c to read the dev_state of the USB peripheral. In usb_device.c there is already the handle hUsbDeviceFS and there are blocks where user code can be added, so our code won't be removed next time we edit the .ioc file.
In usb_device.h add a prototype:
/*
* -- Insert functions declaration here --
*/
/* USER CODE BEGIN FD */
USB_CABLE_e usb_device_check_plug_state(void);
/* USER CODE END FD */
And we'll need an enum for state tracking, I put this in the variables section of the same header file:
/*
* -- Insert your variables declaration here --
*/
/* USER CODE BEGIN VARIABLES */
typedef enum
{
USB_CABLE_UNPLUGGED = 0U,
USB_CABLE_PLUGGED,
} USB_CABLE_e;
/* USER CODE END VARIABLES */
Then add something like this to usb_device.c:
/*
* -- Insert your variables declaration here --
*/
/* USER CODE BEGIN 0 */
static USB_CABLE_e lastState = USB_CABLE_UNPLUGGED;
/* USER CODE END 0 */
/*
* -- Insert your external function declaration here --
*/
/* USER CODE BEGIN 1 */
USB_CABLE_e usb_device_check_plug_state(void)
{
uint8_t stateNow = hUsbDeviceFS.dev_state;
if((stateNow == USBD_STATE_CONFIGURED) && (lastState == USB_CABLE_UNPLUGGED))
{
lastState = USB_CABLE_PLUGGED;
}
else if((stateNow == USBD_STATE_SUSPENDED) && (lastState == USB_CABLE_PLUGGED))
{
lastState = USB_CABLE_UNPLUGGED;
}
return lastState;
}
/* USER CODE END 1 */
Then you can use this function in your main loop and do what you want with the returned state. I have a global to track the USB port state:
usbPortState = usb_device_check_plug_state();
if(usbPortState == USB_CABLE_PLUGGED)
{
HAL_GPIO_WritePin(DEBUG_LED2_GPIO_Port, DEBUG_LED2_Pin, GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(DEBUG_LED2_GPIO_Port, DEBUG_LED2_Pin, GPIO_PIN_RESET);
}
Unfortunately it requires polling but using the current state / last state logic we can detect when the cable has just been unplugged or plugged in.
This works because when there is a connection/disconnection event interrupt, a chain of calls happens ending at USBD_LL_Suspend() and USBD_LL_Resume() which set the dev_state appropriately.
If there are any suggestions for improvement I am all ears! This is just what I've found to work.