STM32F103C8T6 USB CDC Disconnecting After Wakeup from Stop Mode at 72MHz (HSE 8MHz) — Need Help

Hello everyone,

I’m debugging an intermittent USB CDC issue on an STM32F103C8T6 development board after waking from stop mode. After waking, the computer sometimes loses the virtual serial port (it won’t re-enumerate unless a hard reset is performed). I’d appreciate help checking whether my clock/USB initialization sequence is reasonable, and whether there are proven solutions.

Hardware and Components

  • MCU: STM32F103C8T6 running at 72MHz (8MHz HSE multiplied by 9 through PLL).
  • Board: “Blue Pill” form factor; LDO converts 5V to 3.3V, with 0.1µF + 1µF decoupling capacitors near VDD pins, 8MHz HC-49 crystal with 22pF load capacitors.
  • USB: Full-speed device, D+ pulled up to 3.3V through a 1.5kΩ resistor; D+/D- lines have 22Ω series resistors.

Problem Symptoms

  • First power-on: USB CDC enumerates normally on both Windows 10 and Ubuntu 22.
  • After waking from stop mode (after ~5-30 seconds), the host sometimes fails to recognize the CDC device (no new PID/VID events, serial port disappears). USB cable replugging may not work, but pressing the NRST reset pin always recovers. Reproducibility rate ~10%-30%.

Tried Solutions

  1. Re-enable HSE/PLL after waking from stop mode, then restart USB clock.
  2. Switch USB peripheral clock and execute USBD_DeInit() → USBD_Init() sequence.
  3. Ensure SystemCoreClockUpdate() is called after clock switching.
  4. Add 1-10ms delay between RCC and USB initialization steps.
  5. Oscilloscope measurement of 3.3V power ripple: <20mVpp during wake-up; D+ idle state ~3.3V.

Clock/RCC Configuration Sequence (HAL Library)

// Wake from stop mode: re-enable HSE/PLL and switch SYSCLK back to PLL
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);

HAL_RCC_OscConfig(&(RCC_OscInitTypeDef){
  .OscillatorType = RCC_OSCILLATORTYPE_HSE,
  .HSEState = RCC_HSE_ON,
  .PLL.PLLState = RCC_PLL_ON,
  .PLL.PLLSource = RCC_PLLSOURCE_HSE,
  .PLL.PLLMUL = RCC_PLL_MUL9
});

HAL_RCC_ClockConfig(&(RCC_ClkInitTypeDef){
  .ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2,
  .SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK,
  .AHBCLKDivider = RCC_SYSCLK_DIV1,
  .APB1CLKDivider = RCC_HCLK_DIV2,
  .APB2CLKDivider = RCC_HCLK_DIV1
}, FLASH_LATENCY_2);

SystemCoreClockUpdate();

USB Reinitialization Code Snippet

// Fully restart device stack after clock stabilization
USBD_DeInit(&hUsbDeviceFS);
__HAL_RCC_USB_FORCE_RESET();
HAL_Delay(2);
__HAL_RCC_USB_RELEASE_RESET();
__HAL_RCC_USB_CLK_ENABLE();

USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);
USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);
USBD_Start(&hUsbDeviceFS);

Observations

  • System remains stable if stop mode is skipped (switching only between run/sleep modes).
  • Stability improves when keeping SYSCLK at HSI (without PLL) after wake-up, but I need 72MHz for timing requirements.
  • Pulling D+ low for ~10ms via GPIO (simulating disconnection) improves recovery probability but not 100% reliable.

Questions

  1. For F103 chips, is there a standard enable sequence/timing (HSE→PLL→USBFS) after waking from stop mode to avoid CDC device anomalies?
  2. Is forcing USB “soft disconnect” (pulling D+ low) considered best practice for F1 series, or are there better protocol stack-level reset methods?
  3. Are there known errata about HSE startup + PLL lock affecting USB SOF (frame synchronization) or 48MHz clock domain after stop mode?
  4. Recommended practice: Should USB clock recovery first use HSI then switch to PLL, or prioritize PLL stabilization first?
  5. What board-level issues might cause this: D+ resistor tolerance, ESD diode/leakage, crystal load capacitance issues, LDO transient response problems?
1 Like

Hi there,

This is a classic and extremely frustrating issue with F1 series devices and USB re-enumeration after STOP mode! You’re definitely not crazy, and you’ve tried all the standard troubleshooting steps. The intermittent nature (10–30% repro rate) points to a subtle timing/metastability issue during the complex clock re-configuration.

Here’s my analysis and what I’d recommend focusing on, particularly around the USB peripheral’s clock domain and the required 48 MHz clock stability.


:alarm_clock: Clock Sequence Sanity Check & The 48 MHz Domain

Your RCC sequence looks standard and correct for re-enabling HSE/PLL. The issue isn’t likely the PLL itself, but how the USB peripheral (which needs the 48 MHz clock derived from PLL / 1.5) handles the transition from power-down to full-speed operation.

  1. USB Clock Source Constraint: The USB FS clock must be derived from the PLL, specifically PLLCLK / 1.5 to hit 48\text{ MHz}. Any instability in the PLL, even briefly, can corrupt the first few Start-of-Frame (SOF) packets the device sends, leading the host PC to ignore the device (zombie CDC).
  2. Timing the Reset: The __HAL_RCC_USB_FORCE_RESET() and __HAL_RCC_USB_RELEASE_RESET() sequence is good, but it might not be long enough or timed correctly relative to the PLL lock time after wake. The datasheet for the F103 shows a startup time for the HSE and a separate lock time for the PLL. You are essentially doing:
    • Wake \rightarrow Start HSE/PLL
    • … (PLL locks) …
    • USB Clock Enable + Reset Pulse
    • … (Immediate USBD_Init) …

If the PLL has just stabilized when you release the USB reset, the initial clocking could still be marginal. The 2ms delay might not be enough to ensure the entire PLL/USB clock chain is rock-solid.

:hammer: Recommended Workaround: The Detach/Attach Dance

Your observation about pulling D+ low improving recovery is the key. Forcing a physical detach/re-attach is often the only reliable way to signal to the host PC that the USB device has disappeared and then re-appeared.

Why it works: USB enumeration is handled by the host. If the device’s clock is unstable during the wake cycle, the host might see an unresponsive device (or garbage data) and simply drop it, keeping the port in a ‘suspended’ or ‘error’ state, awaiting a physical reset (which your NRST provides). A soft detach/attach forces the host’s USB controller to restart its port enumeration state machine.

Try this sequence after your clocks are stable:

  1. Clock Stability: Ensure HSE/PLL are fully stable and \text{SYSCLK} = 72\text{ MHz} (your current RCC block).
  2. USB Disable (Soft Detach):
    • Disable the internal D+ pull-up (if using the internal pull-up). For Blue Pill/external 1.5 k$\Omega$ on D+, you need to actively pull D+ low using a GPIO for a short period.
    • Configure the D+ pin as a GPIO output low.
    • HAL_Delay(10); (10ms is a common reliable minimum for detach time).
  3. USB Re-Init (Re-attach):
    • Configure the D+ pin back to its USB function (or re-enable the internal pull-up).
    • Perform your full USB re-initialization sequence:
      USBD_DeInit(&hUsbDeviceFS);
      __HAL_RCC_USB_FORCE_RESET();
      HAL_Delay(2); // Still a good idea
      __HAL_RCC_USB_RELEASE_RESET();
      __HAL_RCC_USB_CLK_ENABLE();
      
      USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
      // ... (rest of your init)
      USBD_Start(&hUsbDeviceFS);
      
    • The host should now see the D+ pull-up resistor and begin re-enumeration.

Is it best practice? For the F1 series specifically, yes, forcing a soft-detach on wake from STOP mode for reliable USB re-enumeration is a well-known, practical workaround.


:magnifying_glass_tilted_right: Board-Level Gotchas

While your power rail looks clean (<20\text{ mVpp} ripple is excellent), consider these:

  • Crystal Load Capacitors (22 pF): For an 8 MHz HC-49 crystal, 22\text{ pF} is on the high end and could slightly increase the HSE start-up time or affect frequency stability immediately after wake. Most 8 MHz crystals recommend 10\text{ pF} to 15\text{ pF} load caps. It’s a long shot, but worth checking the crystal’s datasheet for its recommended \text{C}_{\text{L}} (Load Capacitance).
  • D+ Resistor Tolerance: Your 1.5\text{ k}\Omega pull-up is correct. Ensure it’s not deviating significantly (e.g., \text{+/-} 5\%) as this resistance signals a Full-Speed device to the host.
  • ESD Diodes/Leakage: If you have external ESD protection diodes on D+/D-, ensure they aren’t adding significant capacitance or leakage current that could interfere with the delicate 3.3\text{ V} signaling.

Give the D+ GPIO detach/attach a solid try. It addresses the fundamental issue of the host being out of sync with the device’s wake-up state.

Hello. This is a classic pitfall on F103 + Blue Pill boards, primarily due to the host failing to detect device disconnection and Blue Pill’s hardware design flaws.

For your specific issue, here’s the direct solution:

1. Core Cause: D+ Pull-up Resistor and Soft Connection
The D+ pull-up resistor (R10) on Blue Pill boards is typically directly soldered to 3.3V instead of being controlled by MCU GPIO.

  • Phenomenon: When the MCU enters stop mode or resets, D+ remains pulled high through the resistor. The host (PC) assumes the device is still connected and idle, so it won’t re-enumerate.
  • Your action: You mentioned “pulling low for 10ms” – this duration is too short. Host USB stacks (especially Windows) typically require detecting D+ low for over 100ms (recommended 500ms-1s) to recognize disconnection (SE0 state).

2. Solution: Force Re-enumeration Sequence
Insert this “brute-force” reset code after SystemCoreClockUpdate() and before USB initialization:

// 1. Force configure PA12 (D+) as GPIO output low to simulate unplugging
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();

GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET); // Pull down D+

// 2. Delay long enough for host detection
HAL_Delay(600); // Recommended 500ms - 1000ms, 10ms is insufficient

// 3. Release pin back to USB peripheral control (the pull-up resistor will re-enable D+ high, triggering host enumeration)
// Note: Subsequent USBD_Init will reconfigure pin as alternate function, just ensure no forced low output

3. Clock Configuration Note
F103 USB clock must be exactly 48MHz.

  • Check: Ensure RCC_USBCLKSOURCE_PLL_DIV1_5 is properly configured in RCC settings.
  • 72MHz (SysClk) / 1.5 = 48MHz. Incorrect configuration causes USB communication failure due to timing issues, even if enumerated (shows “unrecognized device”).

4. Quick Answers for Your Case

  • Timing Sequence: HSE → PLL → Flash Latency → SysClk → Force D+ low for 600ms → USB Init.
  • Best Practice: For boards without transistor-controlled pull-up resistors, software-controlled PA12 low is the only standard “soft disconnect” method.
  • Errata Note: F103 stop mode wake-up typically doesn’t have silicon errata causing USB failure – the main issue is host state machine not resetting.

Recommended Action:
Change your 10ms delay to 600ms – this should resolve the issue.

Regarding your issue, I’ve encountered similar situations. The abnormal USB CDC behavior after waking from stop mode is typically caused by clock recovery timing and incomplete USB peripheral reset states. Here are the key points and recommendations:

1. Clock Recovery Sequence and Errata
After waking from stop mode, F103 defaults to using HSI (8MHz) as the system clock. Your steps to re-enable HSE and PLL are correct, but there’s a critical timing requirement: you must wait for PLL lock before enabling the USB clock. USB FS requires an exact 48MHz clock (provided by PLL). If USB clock is enabled before PLL stabilizes, USB PHY operation becomes abnormal and the host cannot recognize the device.

Recommendation: After HAL_RCC_OscConfig, add a wait for the PLLRDY flag:

while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET);

Then execute HAL_RCC_ClockConfig.

2. Complete USB Peripheral Reset
Your execution of USBD_DeInit and forced reset sequence is necessary, but the order can be optimized. ST’s errata sheet (for certain F1 models) indicates that after waking from stop mode, the USB peripheral may require a longer reset pulse. Recommend increasing USB clock disable-reset-re-enable delay to at least 10ms.

More reliable approach: After waking, keep USB clock disabled until system clock is fully stabilized (PLL locked and SYSCLK switched). Then execute this hard reset sequence:

__HAL_RCC_USB_CLK_DISABLE();
__HAL_RCC_USB_FORCE_RESET();
HAL_Delay(10);
__HAL_RCC_USB_RELEASE_RESET();
__HAL_RCC_USB_CLK_ENABLE();
// Then execute USBD_Init and other initializations

3. Regarding “Soft Disconnect” (DP Pull-down)
Forcing DP low for 10ms to simulate disconnection is an effective “hack” and actually one of the best practices for F1 series. This ensures the host detects a physical disconnection and triggers re-enumeration. To improve success rate, recommend:

  • Ensure USB peripheral clock is stopped before pulling DP low
  • Maintain sufficient low time (10-20ms) after pulling down
    This isn’t a protocol-level method but the most direct hardware solution.

4. Clock Source Switching Recommendation
Never switch to HSI while USB is operating. The correct approach is: After waking, prioritize quickly stabilizing the original 72MHz PLL clock. Stabilize PLL first, ensure lock, then handle USB. Do not provide any clock to USB peripheral before PLL stabilization.

5. Board-level Considerations
Based on your description, hardware issues are less likely but worth checking:

  • Crystal Load Capacitance: 22pF is typical for 8MHz HC-49, but slow HSE startup during wake-up may prolong PLL lock time. Try reducing capacitance to 18-20pF and check if waveforms on crystal terminals are clean.
  • USB Signal Lines: 22Ω series resistors are standard. Ensure the D+ pull-up resistor (1.5kΩ) directly comes from the MCU’s 3.3V and power remains stable during wake-up transients.
  • Power Supply: Current surge during wake-up may cause slight LDO droop. Although your ripple measurement is small, consider adding a 10µF tantalum capacitor between VDD and ground to enhance transient response.

Summary and Recommended Steps

  1. Clock Recovery After Wake-up: Enable HSE → Wait for HSE ready → Enable PLL → Wait for PLL lock → Switch SYSCLK to PLL → Update SystemCoreClock.
  2. Complete USB Reset: First disable USB clock, force reset for >10ms, then release reset and re-enable clock.
  3. Reinitialize USB Stack: Call USBD_DeInit and USBD_Init sequence.
  4. Optional “Soft Disconnect”: If intermittent issues persist after above steps, before USB reinitialization, configure DP (PA12) as push-pull output and pull low for ≥15ms, then initialize and pull-up.

Following this sequence should significantly improve USB CDC recovery reliability after wake-up.

2 Likes