小聪同学
1
大家好,
我正在调试 STM32F103C8T6 开发板上一个间歇性的 USB CDC 问题。从停止模式唤醒后,电脑有时会丢失虚拟串口(除非硬复位,否则不会重新枚举)。希望大家能帮忙检查我的时钟 / USB 初始化序列是否合理,以及是否有经过验证的解决方案。
硬件及器件
- MCU:STM32F103C8T6,基于 8MHz HSE 通过 PLL 倍频 9 倍运行在 72MHz。
- 开发板:“小蓝板”(Blue Pill)版型;LDO 将 5V 转为 3.3V,VDD 引脚附近配备 0.1µF+1µF 去耦电容,8MHz HC-49 晶振搭配 22pF 负载电容。
- USB:全速设备,D + 通过 1.5kΩ 电阻上拉至 3.3V;D+/D - 线路串联 22Ω 电阻。
故障现象
- 首次上电:USB CDC 在 Windows 10 和 Ubuntu 22 系统上均能正常枚举。
- 进入停止模式约 5-30 秒后唤醒,主机有时无法识别 CDC 设备(无新的 PID/VID 事件,串口消失)。插拔 USB 线不一定有效,按下 NRST 复位引脚总能恢复。复现率约 10%-30%。
已尝试的操作
- 停止模式唤醒后,重新使能 HSE/PLL,再重启 USB 时钟。
- 切换 USB 外设时钟,并执行 USBD_DeInit ()→USBD_Init () 序列。
- 时钟切换后确保调用 SystemCoreClockUpdate () 函数。
- 在 RCC 和 USB 初始化步骤之间延长 1-10ms 延时。
- 用示波器检测 3.3V 电源纹波:唤醒期间纹波 < 20mVpp;D + 空闲状态约 3.3V。
时钟 / RCC 配置序列(HAL 库)
// 从停止模式唤醒:重新使能HSE/PLL,并将SYSCLK切换回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 重新初始化代码片段
// 时钟稳定后完全重启设备栈
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);
观察结果
- 若跳过停止模式,仅在运行模式↔睡眠模式之间切换,系统稳定。
- 唤醒后若将 SYSCLK 保持在 HSI(不启用 PLL),系统似乎更稳定,但我需要 72MHz 时钟满足时序要求。
- 通过 GPIO 将 D + 拉低约 10ms(模拟断开)可提高恢复概率,但无法达到 100%。
求助问题
- 对于 F103 芯片,停止模式唤醒后,是否有标准的使能顺序 / 时序(HSE→PLL→USBFS)可避免 CDC 设备异常?
- 唤醒后强制 USB “软断开”(D + 拉低)是否属于 F1 系列的最佳实践,或是否有更优的协议栈级复位方法?
- 是否存在已知勘误,涉及停止模式后 HSE 启动 + PLL 锁定影响 USB SOF(帧同步信号)或 48MHz 时钟域?
- 推荐在 USB 时钟恢复时先使用 HSI,再切换到 PLL,还是始终优先稳定 PLL?
- 您遇到过哪些板级问题可能导致该现象:D + 电阻容差、ESD 二极管 / 泄漏、晶振负载过重、LDO 瞬态响应不良等?
1 个赞
CCC
2
你好,
这是一个F1系列设备在STOP模式后USB重新枚举时的经典且极其令人困扰的问题!你绝对没有发疯,而且已经尝试了所有标准的故障排除步骤。间歇性现象(10–30%复现率)表明在复杂的时钟重新配置过程中存在微妙的时序/亚稳态问题。
以下是本人的分析和建议重点,特别是围绕USB外设的时钟域和所需的48 MHz时钟稳定性。
时钟序列完整性检查与48 MHz时钟域
你的RCC序列在重新启用HSE/PLL时看起来标准且正确。问题可能不在PLL本身,而在于USB外设(需要由PLL / 1.5分频得到的48 MHz时钟)如何处理从断电到全速运行的过渡。
- USB时钟源限制:USB全速时钟必须来自PLL,具体为PLLCLK / 1.5以达到$48\text{ MHz}$。即使PLL短暂不稳定,也可能导致设备发送的前几个**帧起始(SOF)**数据包损坏,使主机PC忽略设备(僵尸CDC)。
- 复位时序:
__HAL_RCC_USB_FORCE_RESET()和__HAL_RCC_USB_RELEASE_RESET()序列是正确的,但相对于唤醒后的PLL锁定时间,复位脉冲可能不够长或时序不正确。F103的数据手册显示HSE启动时间和PLL锁定时间是分开的。你当前的操作流程为:
- 唤醒 → 启动HSE/PLL
- … (PLL锁定) …
- 使能USB时钟 + 复位脉冲
- … (立即执行USBD_Init) …
如果在释放USB复位时PLL刚刚稳定,初始时钟仍可能处于临界状态。2ms延迟可能不足以确保整个PLL/USB时钟链完全稳定。
推荐解决方案:软断开/重新连接操作
你观察到将D+拉低有助于恢复是关键。强制物理断开/重新连接通常是向主机PC发出设备消失并重新出现的唯一可靠方式。
工作原理:USB枚举由主机处理。如果设备在唤醒周期时钟不稳定,主机可能看到无响应设备(或垃圾数据),从而直接丢弃设备,保持端口处于“暂停”或“错误”状态,等待物理复位(你的NRST提供)。软断开/重新连接会强制主机USB控制器重启端口枚举状态机。
建议在时钟稳定后执行以下序列:
- 确保时钟稳定:确认HSE/PLL完全稳定且$\text{SYSCLK} = 72\text{ MHz}$(当前RCC配置)。
- USB禁用(软断开):
- 禁用内部D+上拉(若使用内部上拉)。对于Blue Pill/外部1.5k$\Omega$上拉到D+的情况,需使用GPIO主动将D+拉低一段时间。
- 将D+引脚配置为GPIO输出低电平。
HAL_Delay(10);(10ms是常见的可靠最小断开时间)。
- USB重新初始化(重新连接):
- 将D+引脚重新配置为USB功能(或重新启用内部上拉)。
- 执行完整的USB重新初始化序列:
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_Start(&hUsbDeviceFS);
- 主机现在应检测到D+上拉电阻并开始重新枚举。
这是最佳实践吗?对于F1系列而言,是的——在从STOP模式唤醒后强制软断开以实现可靠的USB重新枚举是众所周知且实用的解决方案。
板级潜在问题
虽然你的电源轨看起来干净($\lt20\text{ mVpp}$纹波极佳),但仍需考虑以下因素:
- 晶体负载电容(22 pF):对于8 MHz HC-49晶体,$22\text{ pF}$属于较高值,可能略微增加HSE启动时间或影响唤醒后的频率稳定性。大多数8 MHz晶体推荐$10\text{ pF}$至$15\text{ pF}负载电容。虽然可能性较低,但建议查阅晶体手册确认推荐的\text{C}_{\text{L}}$(负载电容)。
- D+电阻容差:你的$1.5\text{ k}\Omega$上拉电阻正确。确保其阻值未显著偏离(例如$\text{+/-} 5%$),因为该阻值向主机表明设备为全速模式。
- ESD二极管/漏电流:若D+/D-上使用外部ESD保护二极管,需确保其未引入显著电容或漏电流,这可能干扰$3.3\text{ V}$信号完整性。
请优先尝试D+ GPIO软断开/重新连接方案。它直接解决了主机与设备唤醒状态不同步的根本问题。
ABC
3
你好。这在 F103 + Blue Pill 上是个经典坑,主要原因在于Host 端没能识别到设备断开,以及 Blue Pill 的硬件设计缺陷。
针对你的问题,直接给解决方案:
1. 核心原因:D+ 上拉电阻与软连接
Blue Pill 开发板的 D+ 上拉电阻(R10)通常直接焊在 3.3V 上,而不是受 MCU GPIO 控制。
- 现象: 当 MCU 进入停止模式或复位时,D+ 依然通过电阻被拉高。主机(PC)认为设备一直连接且空闲,不会重新枚举。
- 你的操作: 你提到“拉低 10ms”,这时间太短了。PC 端的 USB 协议栈(尤其是 Windows)通常需要检测到 D+ 低电平持续 100ms 以上(建议 500ms-1s)才会判定为设备断开(SE0 状态)。
2. 解决方案:强制重枚举序列
在 SystemCoreClockUpdate() 之后,USB 初始化之前,加入这段“暴力”重置代码:
// 1. 强制配置 PA12 (D+) 为 GPIO 输出低电平,模拟拔线
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); // 拉低 D+
// 2. 延时足够长的时间让 Host 识别断开
HAL_Delay(600); // 建议 500ms - 1000ms,10ms 绝对不够
// 3. 释放引脚,交还给 USB 外设控制(此时上拉电阻会再次拉高 D+,触发 Host 枚举)
// 注意:后续的 USBD_Init 会重新配置引脚为复用功能,这里只要不再强制输出低即可
3. 关于时钟配置的补充
F103 的 USB 时钟必须是 48MHz。
- 检查点: 确保 RCC 配置中
RCC_USBCLKSOURCE_PLL_DIV1_5 被正确设置。
- 72MHz (SysClk) / 1.5 = 48MHz。如果这步错了,USB 会因为时序不对而无法通讯,即使枚举了也会报“无法识别的设备”。
4. 针对你问题的快速回答
- 时序顺序: HSE -> PLL -> Flash Latency -> SysClk -> 强制 D+ 拉低 600ms -> USB Init。
- 最佳实践: 对于没有三极管控制上拉电阻的板子,软件控制 PA12 拉低是唯一的“软断开”标准做法。
- 勘误: F103 停止模式唤醒通常没有会导致 USB 彻底挂掉的硅片勘误,问题主要出在 Host 状态机没复位。
建议下一步:
把那段 10ms 的拉低延时改成 600ms,问题应该就能解决。
海阔天空
4
针对你的问题,我遇到过类似情况。从停止模式唤醒后USB CDC异常,通常是时钟恢复时序和USB外设状态未完全复位导致的。以下是关键要点和建议:
1. 时钟恢复顺序与勘误
F103在停止模式唤醒后,默认使用HSI(8MHz)作为系统时钟。你重新启用HSE和PLL的步骤是正确的,但存在一个关键时序:必须等待PLL锁定后,再使能USB时钟。USB FS需要精确的48MHz时钟(由PLL提供)。如果USB时钟在PLL不稳定时被启用,会导致USB PHY工作异常,主机无法识别。
建议在HAL_RCC_OscConfig后,增加检查PLLRDY标志的等待:
while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET);
然后再执行HAL_RCC_ClockConfig。
2. USB外设的彻底复位
你执行的USBD_DeInit和强制复位序列是必要的,但顺序可以优化。ST的勘误表(针对某些F1型号)指出,从停止模式唤醒后,USB外设可能需要一个更长的复位脉冲。建议将USB时钟禁用、复位、再使能的延时增加到至少10ms。
更可靠的做法是,在唤醒后先保持USB时钟禁用,等系统时钟完全稳定(PLL锁定且SYSCLK切换完成)后,再执行以下硬复位序列:
__HAL_RCC_USB_CLK_DISABLE();
__HAL_RCC_USB_FORCE_RESET();
HAL_Delay(10);
__HAL_RCC_USB_RELEASE_RESET();
__HAL_RCC_USB_CLK_ENABLE();
// 然后再执行USBD_Init等初始化
3. 关于“软断开”DP拉低
强制DP拉低10ms模拟断开是有效的“土办法”,也确实是F1系列常用的最佳实践之一。这能确保主机检测到物理断开并触发重新枚举。为了提高成功率,建议在拉低DP前,先确保USB外设时钟已停止,并在拉低后保持足够时间(10-20ms)。这不是协议栈级的方法,但硬件上最直接。
4. 时钟源切换建议
不要在USB工作时切换到HSI。正确的做法是:唤醒后,目标就是快速稳定地恢复原来的72MHz PLL时钟。应优先稳定PLL,确保其锁定,然后再处理USB。在PLL稳定前,不要让USB外设有任何时钟。
5. 板级可能原因
根据你的描述,硬件问题可能性较低,但可以排查:
- 晶振负载电容:22pF对于8MHz HC-49是典型值,但唤醒时HSE启动慢可能导致PLL锁定时间变长。可尝试将电容减小到18-20pF,并检查晶振两端波形是否干净。
- USB信号线:串联22Ω电阻是标准的,确保D+的上拉电阻(1.5kΩ)直接来自MCU的3.3V,且电源在唤醒瞬态能保持稳定。
- 电源:唤醒瞬间电流增大可能导致LDO轻微跌落。虽然你测的纹波很小,但可以在VDD与地之间增加一个10µF的钽电容,增强瞬态响应。
总结与推荐步骤
- 唤醒后时钟恢复:启用HSE → 等待HSE就绪 → 启用PLL → 等待PLL锁定 → 切换SYSCLK到PLL → 更新
SystemCoreClock。
- 彻底复位USB:先禁用USB时钟,强制复位并保持10ms以上,再释放复位并重新使能时钟。
- 重新初始化USB栈:调用
USBD_DeInit和USBD_Init序列。
- 可选“软断开”:如果上述步骤后问题仍间歇出现,在USB重新初始化前,将DP(PA12)配置为推挽输出并拉低至少15ms,然后再初始化和上拉。
按这个顺序操作,应该能大幅提高唤醒后USB CDC的恢复可靠性。
2 个赞