STM32F103C8T6 USB CDC se desconecta después de salir del modo de parada a 72 MHz (HSE 8 MHz) — Ayuda

Hola a todos,

Estoy depurando un problema intermitente con USB CDC en una placa de desarrollo STM32F103C8T6. Después de despertar desde el modo de parada, la computadora a veces pierde el puerto serial virtual (no se vuelve a enumerar a menos que se haga un reinicio forzado). Espero que puedan ayudarme a revisar si mi secuencia de inicialización de relojes/USB es adecuada, y si existen soluciones verificadas.

Hardware y componentes

  • MCU: STM32F103C8T6, operando a 72MHz mediante PLL multiplicado por 9 desde un HSE de 8MHz.
  • Placa: Diseño “Blue Pill” (pequeña placa azul); LDO convierte 5V a 3.3V, capacitores de desacoplamiento de 0.1µF+1µF cerca del pin VDD, cristal HC-49 de 8MHz con capacitores de carga de 22pF.
  • USB: Dispositivo de velocidad completa, D+ tiene una resistencia de pull-up de 1.5kΩ a 3.3V; líneas D+/D- con resistencias en serie de 22Ω.

Síntomas del fallo

  • Al primer encendido: USB CDC se enumera correctamente en Windows 10 y Ubuntu 22.
  • Después de despertar tras 5-30 segundos en modo de parada, el host a veces no reconoce el dispositivo CDC (sin eventos de nuevo PID/VID, el puerto serial desaparece). Reconectar el cable USB no siempre funciona, pero presionar el pin de reinicio NRST siempre lo recupera. Tasa de reproducción aproximada: 10%-30%.

Acciones intentadas

  1. Después de despertar del modo de parada, reactivar HSE/PLL y reiniciar el reloj USB.
  2. Alternar el reloj del periférico USB y ejecutar la secuencia USBD_DeInit() → USBD_Init().
  3. Asegurar la llamada a SystemCoreClockUpdate() después del cambio de reloj.
  4. Añadir retardos de 1-10ms entre los pasos de inicialización de RCC y USB.
  5. Medir rizado de la fuente de 3.3V con osciloscopio: durante el despertar el rizado es <20mVpp; D+ en estado inactivo muestra ~3.3V.

Secuencia de configuración de reloj/RCC (biblioteca HAL)

// Al despertar del modo de parada: volver a habilitar HSE/PLL y cambiar SYSCLK de vuelta al reloj 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();

Fragmento de código para reinicialización USB

// Reiniciar completamente la pila del dispositivo después de que el reloj se estabilice
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);

Resultados observados

  • Si se salta el modo de parada y solo se cambia entre modo de ejecución ↔ modo de espera, el sistema es estable.
  • Al mantener SYSCLK en HSI (sin activar PLL) después del despertar, el sistema parece más estable, pero necesito el reloj de 72MHz para cumplir requisitos temporales.
  • Forzar D+ a nivel bajo durante ~10ms mediante GPIO (simulando desconexión) mejora la probabilidad de recuperación, pero no garantiza el 100%.

Preguntas específicas

  1. ¿Existe una secuencia estándar de habilitación/temporización (HSE→PLL→USBFS) después del despertar desde stop mode en chips F103 para evitar anomalías en CDC?
  2. ¿Es una práctica recomendada en la serie F1 forzar un “desconexión suave” USB (poner D+ a nivel bajo) después del despertar, o hay mejores métodos de reinicio a nivel de protocolo?
  3. ¿Hay erratas conocidas relacionadas con la interacción entre inicio de HSE + bloqueo de PLL y la señal SOF (señal de sincronización de trama) o dominio de reloj de 48MHz después de stop mode?
  4. ¿Se recomienda usar HSI temporalmente antes de estabilizar PLL al recuperar el reloj USB, o siempre priorizar estabilizar PLL primero?
  5. ¿Han encontrado problemas en el diseño de la placa que podrían causar esto: tolerancia de resistencia D+, diodos ESD/fugas, carga excesiva en el cristal, respuesta transitoria deficiente en LDO, etc.?
1 me gusta

¡Hola!

Este es un problema clásico y extremadamente frustrante con dispositivos de la serie F1 y la reenumeración USB después del modo STOP. Definitivamente no estás loco y has probado todos los pasos estándar de solución de problemas. La naturaleza intermitente (tasa de reproducción del 10–30%) apunta a un problema sutil de temporización/metestabilidad durante la compleja reconfiguración del reloj.

Aquí está mi análisis y lo que recomiendo enfocar, especialmente en el dominio del reloj del periférico USB y la estabilidad requerida de 48 MHz.


:alarm_clock: Verificación de secuencia de reloj y el dominio de 48 MHz

Tu secuencia RCC parece estándar y correcta para reactivar HSE/PLL. El problema probablemente no sea el PLL en sí, sino cómo el periférico USB (que requiere el reloj 48 MHz derivado de PLL / 1.5) maneja la transición desde el apagado hasta la operación a velocidad máxima.

  1. Restricción de la fuente de reloj USB: El reloj de USB FS debe derivarse del PLL, específicamente PLLCLK / 1.5 para alcanzar 48\\text{ MHz}. Cualquier inestabilidad en el PLL, incluso brevemente, puede corromper los primeros paquetes Start-of-Frame (SOF) que envía el dispositivo, haciendo que el PC host lo ignore (CDC zombie).
  2. Temporización del reinicio: La secuencia __HAL_RCC_USB_FORCE_RESET() y __HAL_RCC_USB_RELEASE_RESET() es correcta, pero podría no ser suficientemente larga o temporizada correctamente en relación con el tiempo de bloqueo del PLL después del reanudado. La hoja de datos del F103 muestra un tiempo de arranque para el HSE y un tiempo de bloqueo separado para el PLL. Esencialmente, estás haciendo:
    • Reanudado \\rightarrow Iniciar HSE/PLL
    • … (PLL se bloquea) …
    • Habilitar reloj USB + Pulso de reinicio
    • … (Inmediato USBD_Init) …

Si el PLL acaba de estabilizarse cuando liberas el reinicio del USB, el reloj inicial aún podría ser marginal. El retraso de 2 ms podría no ser suficiente para garantizar que toda la cadena de reloj PLL/USB sea completamente estable.

:hammer: Solución recomendada: El baile de desconexión/reconexión

Tu observación sobre forzar D+ a bajo para mejorar la recuperación es clave. Obligar una desconexión/reconexión física suele ser la única forma confiable de indicar al PC host que el dispositivo USB ha desaparecido y reaparecido.

¿Por qué funciona? La enumeración USB la maneja el host. Si el reloj del dispositivo es inestable durante el reanudado, el host podría ver un dispositivo no respondiente (o datos corruptos) y simplemente lo descartará, manteniendo el puerto en un estado ‘suspendido’ o ‘error’, esperando un reinicio físico (que tu NRST proporciona). Una desconexión/reconexión suave fuerza al controlador USB del host a reiniciar su máquina de estados de enumeración del puerto.

Prueba esta secuencia después de que los relojes sean estables:

  1. Estabilidad del reloj: Asegurar que HSE/PLL estén completamente estables y \\text{SYSCLK} = 72\\text{ MHz} (tu bloque RCC actual).
  2. Deshabilitar USB (desconexión suave):
    • Deshabilitar la resistencia pull-up interna de D+ (si usas pull-up interno). Para Blue Pill/resistencia externa de 1.5 k$\Omega$ en D+, necesitas forzar D+ a bajo usando un GPIO durante un breve período.
    • Configurar el pin D+ como GPIO de salida a bajo.
    • HAL_Delay(10); (10 ms es un mínimo común y confiable para el tiempo de desconexión).
  3. Reinicializar USB (reconexión):
    • Configurar el pin D+ de vuelta a su función USB (o reactivar el pull-up interno).
    • Realizar tu secuencia completa de reinicialización USB:
      USBD_DeInit(\u0026hUsbDeviceFS);  
      __HAL_RCC_USB_FORCE_RESET();  
      HAL_Delay(2); // Still a good idea  
      __HAL_RCC_USB_RELEASE_RESET();  
      __HAL_RCC_USB_CLK_ENABLE();  
      
      USBD_Init(\u0026hUsbDeviceFS, \u0026FS_Desc, DEVICE_FS);  
      // ... (rest of your init)  
      USBD_Start(\u0026hUsbDeviceFS);  
      
    • El host ahora debería ver la resistencia pull-up de D+ y comenzar la reenumeración.

¿Es una buena práctica? Para la serie F1 específicamente, sí. Forzar una desconexión suave al salir del modo STOP para una reenumeración USB confiable es una solución conocida y práctica.


:magnifying_glass_tilted_right: Problemas a nivel de placa

Aunque tu fuente de alimentación parece limpia (ondulación \\u003c 20\\text{ mVpp} es excelente), considera estos aspectos:

  • Capacitores de carga del cristal (22 pF): Para un cristal HC-49 de 8 MHz, 22\\text{ pF} está en el límite superior y podría aumentar ligeramente el tiempo de arranque del HSE o afectar la estabilidad de frecuencia inmediatamente después del reanudado. La mayoría de los cristales de 8 MHz recomiendan capacitores de carga entre 10\\text{ pF} y 15\\text{ pF}. Es poco probable, pero vale la pena revisar la hoja de datos del cristal para su \\text{C}_{\\text{L}} (Capacitancia de Carga) recomendada.
  • Tolerancia de la resistencia D+: Tu pull-up de 1.5\\text{ k}\\Omega es correcto. Asegúrate de que no tenga desviaciones significativas (ej. \\text{+/-} 5\\%), ya que esta resistencia indica al host que es un dispositivo Full-Speed.
  • Diodos de protección ESD/Corriente de fuga: Si usas diodos ESD externos en D+/D-, asegúrate de que no añadan capacitancia significativa o corriente de fuga que pueda interferir con la señalización delicada de 3.3\\text{ V}.

Prueba con firmeza la desconexión/reconexión GPIO D+. Esto aborda el problema fundamental de que el host está desincronizado con el estado de reanudado del dispositivo.

Hola. Este es un problema clásico en F103 + Blue Pill, principalmente debido a que el host no puede detectar la desconexión del dispositivo y al defecto de diseño de hardware en Blue Pill.

1. Causa principal: Resistencia de pull-up en D+ y conexión suave
La resistencia de pull-up de D+ (R10) en la placa Blue Pill generalmente está soldada directamente a 3.3V, en lugar de ser controlada por GPIO del MCU.

  • Fenómeno: Cuando el MCU entra en modo de detención o se reinicia, D+ sigue siendo elevado por la resistencia. El host (PC) considera que el dispositivo está siempre conectado e inactivo, por lo que no vuelve a enumerarlo.
  • Tu operación: Mencionaste “forzar D+ a bajo durante 10ms”, lo cual es demasiado corto. La pila de protocolo USB del lado del PC (especialmente en Windows) suele requerir detectar un nivel bajo en D+ durante más de 100ms (se recomienda 500ms-1s) para considerarlo como desconexión del dispositivo (estado SE0).

2. Solución: Secuencia de reenumeración forzada
Después de SystemCoreClockUpdate() y antes de la inicialización de USB, agrega este código “violento” de reinicio:

// 1. Configurar PA12 (D+) como GPIO de salida a nivel bajo, simular desconexión
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); // Establecer D+ a bajo

// 2. Retardo suficientemente largo para que el host detecte la desconexión
HAL_Delay(600); // Recomendado 500ms - 1000ms, 10ms es definitivamente insuficiente

// 3. Liberar el pin y devolverlo al control del periférico USB (en este momento la resistencia de pull-up volverá a elevar D+, activando la enumeración del host)
// Nota: USBD_Init posterior reconfigurará el pin como función alternativa, aquí solo necesitas dejar de forzar la salida a bajo

3. Complemento sobre la configuración del reloj
El reloj USB de F103 debe ser 48MHz.

  • Verificación: Asegúrate de que en la configuración de RCC se haya establecido correctamente RCC_USBCLKSOURCE_PLL_DIV1_5.
  • 72MHz (SysClk) / 1.5 = 48MHz. Si este paso es incorrecto, USB no podrá comunicarse debido a problemas de temporización, incluso si se logra la enumeración, el dispositivo aparecerá como “no reconocido”.

4. Respuesta rápida a tu problema

  • Orden de temporización: HSE -\u003e PLL -\u003e Flash Latency -\u003e SysClk -\u003e Forzar D+ a bajo durante 600ms -\u003e Inicialización USB.
  • Práctica recomendada: Para placas sin transistor para controlar la resistencia de pull-up, el control de software de PA12 es la única práctica estándar para lograr una “desconexión suave”.
  • Errata: La reanudación desde el modo de detención en F103 generalmente no tiene erratas de silicio que provoquen un fallo completo de USB, el problema principal está en que la máquina de estados del host no se reinicia.

Siguiente paso recomendado:
Cambia el retraso de 10ms a 600ms, y el problema debería resolverse.

Para tu problema, he experimentado situaciones similares. La anomalía en USB CDC después de despertar del modo de detención suele deberse a la secuencia de recuperación del reloj y al estado del periférico USB que no se restablece completamente. A continuación, se detallan los puntos clave y recomendaciones:

1. Secuencia de recuperación del reloj y errata
Después de despertar del modo de detención, F103 utiliza por defecto HSI (8MHz) como reloj del sistema. Tu procedimiento para reactivar HSE y PLL es correcto, pero existe un punto crítico: debes esperar a que PLL se estabilice antes de habilitar el reloj de USB. USB FS requiere un reloj preciso de 48MHz (proporcionado por PLL). Si el reloj de USB se activa cuando PLL aún no es estable, el PHY de USB funcionará incorrectamente y el host no podrá reconocerlo.

Recomendación: después de HAL_RCC_OscConfig, agrega una espera para verificar la bandera PLLRDY:

while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET);

Luego ejecuta HAL_RCC_ClockConfig.

2. Restablecimiento completo del periférico USB
La secuencia USBD_DeInit y el restablecimiento forzado que realizaste son necesarios, pero el orden puede optimizarse. La errata de ST (para ciertos modelos F1) indica que después de despertar del modo de detención, el periférico USB puede requerir un pulso de restablecimiento más prolongado. Recomendamos aumentar el retardo en la secuencia de deshabilitar, restablecer y reactivar el reloj de USB a al menos 10ms.

Un enfoque más confiable es, tras el despertar, mantener deshabilitado el reloj de USB hasta que el sistema estabilice completamente el reloj (PLL bloqueado y SYSCLK conmutado). Luego ejecuta esta secuencia de restablecimiento duro:

__HAL_RCC_USB_CLK_DISABLE();
__HAL_RCC_USB_FORCE_RESET();
HAL_Delay(10);
__HAL_RCC_USB_RELEASE_RESET();
__HAL_RCC_USB_CLK_ENABLE();
// Luego ejecuta USBD_Init y otras inicializaciones

3. Sobre la “desconexión suave” (pull-down en DP)
Forzar un pull-down en DP durante 10ms para simular una desconexión es una solución efectiva y común en la serie F1. Esto asegura que el host detecte la desconexión física y active el reenumeración. Para mejorar la confiabilidad, antes de activar el pull-down en DP, asegúrate de que el reloj del periférico USB esté detenido, y mantén el estado durante suficiente tiempo (10-20ms). Este método no opera a nivel del stack del protocolo, pero es directo a nivel de hardware.

4. Recomendación para conmutar la fuente de reloj
No cambies a HSI mientras USB está activo. La práctica correcta es: después del despertar, priorizar la recuperación rápida y estable del reloj PLL original de 72MHz. Debes estabilizar PLL primero, asegurar su bloqueo, y luego manejar USB. No permitas que el periférico USB reciba reloj antes de que PLL sea estable.

5. Posibles causas a nivel de hardware
Según tu descripción, la probabilidad de un problema hardware es baja, pero puedes verificar:

  • Capacitancia de carga del cristal: 22pF es típico para cristales HC-49 de 8MHz, pero un arranque lento de HSE tras el despertar podría prolongar el tiempo de bloqueo de PLL. Prueba reducir la capacitancia a 18-20pF y verifica que las formas de onda en los terminales del cristal sean limpias.
  • Líneas de señal USB: Las resistencias en serie de 22Ω son estándar. Asegura que la resistencia de pull-up en D+ (1.5kΩ) esté directamente conectada a los 3.3V del MCU y que la alimentación mantenga estabilidad durante el despertar.
  • Alimentación eléctrica: El pico de corriente durante el despertar podría causar una caída leve en el LDO. Aunque el rizado medido sea pequeño, agrega un capacitor de tantalio de 10µF entre VDD y tierra para mejorar la respuesta transitoria.

Resumen y pasos recomendados

  1. Recuperación del reloj tras despertar: Habilita HSE → espera a que HSE esté listo → habilita PLL → espera el bloqueo de PLL → conmuta SYSCLK a PLL → actualiza SystemCoreClock.
  2. Restablecimiento completo de USB: Primero deshabilita el reloj de USB, fuerza el restablecimiento durante al menos 10ms, luego libera el restablecimiento y reactiva el reloj.
  3. Reinicialización del stack USB: Llama a USBD_DeInit y a la secuencia USBD_Init.
  4. Opcional “desconexión suave”: Si aún hay problemas intermitentes tras los pasos anteriores, antes de reinicializar USB, configura DP (PA12) como salida push-pull y mantén el pull-down al menos 15ms, luego inicializa y activa el pull-up.

Seguir este orden debería mejorar significativamente la confiabilidad de recuperación de USB CDC tras el despertar.

2 Me gusta