Electronic – STM32F070 – firmware-controlled USB connection and HSE activation

stm32stm32f0usbusb device

I'm developing firmware for a low power USB 2.0 Full Speed device (12 Mbps) which uses an STM32F070CB microcontroller. The clock source for the USB module will be the High Speed External (HSE) oscillator. I'm using STM32CubeMX with version 1.7.0 of STM32CubeF0 to generate the codebase.

To conserve our power budget, we need to turn off the HSE when the USB is not connected. I'm thinking we should make the firmware delay USB detection by the host until the device is ready, so that the events in the firmware would go something like this:

  • Event – Detect physical USB connection
    • Enable HSE
    • Initialize USB
    • Connect Data+ pull-up (DPPU) to trigger USB enumeration by host
  • Event – Detect physical USB disconnection
    • Disconnect DPPU (simulate physical disconnection to host)
    • De-initialize USB
    • Disable HSE (drive PF0-OSC_IN low)

Questions

  1. Is the above the right way to go about this? Are the actions all required? Are they in the right order?

  2. Do I need to call any functions besides USBD_DeInit() to de-init USB?

  3. How can I make the device appear disconnected on the host while physically connected? (i.e. It should disappear from Windows Device Manager.) Why doesn't calling HAL_PCD_DevDisconnect() cause that to happen?

Details

In the following paragraphs, to "physically connect" means to connect the USB cable to the device and the host PC.

I would expect all calls to MX_USB_DEVICE_Init() and MX_USB_DEVICE_DeInit() to take effect immediately, i.e. that I would see the device appear/disappear in Windows Device Manager if the function was called while the device is physically connected. However, I've observed this only on the first call to MX_USB_DEVICE_Init(). All calls to MX_USB_DEVICE_DeInit() and subsequent calls to MX_USB_DEVICE_Init() don't cause the device to appear/disappear from Windows Device Manager until after physical reconnection. (i.e. If it's connected, when the function is called, no change in Device Manager until after disconnect/reconnect.)

Code

As generated by STM32CubeMX:

/* USB Device Core handle declaration */
USBD_HandleTypeDef hUsbDeviceFS;

/* init function */                     
void MX_USB_DEVICE_Init(void)
{
  /* Init Device Library,Add Supported Class and Start the library*/
  USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);

  USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);

  USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);

  USBD_Start(&hUsbDeviceFS);
}

I created:

void MX_USB_DEVICE_DeInit(void)
{
   HAL_PCD_DevDisconnect((PCD_HandleTypeDef *)&hUsbDeviceFS.pData);
   USBD_Stop  (&hUsbDeviceFS);
   USBD_DeInit(&hUsbDeviceFS);
}

It makes no difference whether or when I call HAL_PCD_DevDisconnect(). I have never observed the device disappear from Windows Device Manager while physically connected.

Best Answer

  1. How can I make the device appear disconnected on the host while physically connected? (i.e. It should disappear from Windows Device Manager.) Why doesn't calling HAL_PCD_DevDisconnect() cause that to happen?

Calling HAL_PCD_DevDisconnect() with the proper parameters does indeed disconnect DPPU, thus making the device disappear from Windows Device Manager. The error in the question is that in MX_USB_DEVICE_DeInit(),

HAL_PCD_DevDisconnect((PCD_HandleTypeDef *)&hUsbDeviceFS.pData);

should be

HAL_PCD_DevDisconnect((PCD_HandleTypeDef *)hUsbDeviceFS.pData);

(without the address operator &).

Here is a revised version of the USB de-init function:

void MX_USB_DEVICE_DeInit(void)
{
   PCD_HandleTypeDef *hpcd = (PCD_HandleTypeDef *)hUsbDeviceFS.pData;

   if (hpcd != NULL)
   {
      HAL_PCD_DevDisconnect(hpcd);
      USBD_DeInit(&hUsbDeviceFS);
   }
}
  1. Is the above the right way to go about this? Are the actions all required? Are they in the right order?

Close. There's more to the clock configuration than just turning HSE on or off.

STM32CubeMX generates SystemClock_Config(). You can use it as a model for the "Configure system clocks" steps in the list below.

  • Event - Detect physical USB connection
    • Configure system clocks
      • Enable HSE
      • Turn on PLL
      • Set SYSCLKSOURCE to PLLCLK
      • Set UsbClockSelection to PLL
      • Configure systick
    • Initialize USB and connect Data+ pull-up (DPPU) to trigger USB enumeration by host - MX_USB_DEVICE_Init() does all of this
  • Event - Detect physical USB disconnection
    • Disconnect DPPU (HAL_PCD_DevDisconnect())
    • De-initialize USB (USBD_DeInit())
    • Configure system clocks (note many steps in reverse order from above)
      • Turn off USB peripheral
      • Switch sysclk back to HSI
      • Disable HSE (not sure if driving PF0-OSC_IN low is necessary)
      • Turn off PLL
      • Configure systick
  1. Do I need to call any functions besides USBD_DeInit() to de-init USB?

No, that does it. Dig into the call stacks and you'll find that USBD_DeInit() calls everything that USBD_Stop() does and more.