尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32 USB2.0转Type-C接口实战:硬件设计、固件配置与枚举调试

STM32 USB2.0转Type-C接口实战:硬件设计、固件配置与枚举调试 1. 项目缘起与整体设计思路STM32 的 USB 外设从 F103 那一代开始就是标配但真正把 USB2.0 跑通、再把它规规矩矩地引到一个 Type-C 母座上中间踩的坑远比想象中多。我最近在做一个桌面级的数据采集小盒子主控用的是 STM32F103C8T6需要对外提供一个既能供电、又能传数据的接口同时还得兼顾正反插的体验。于是就有了这个“STM32 USB2.0 转 Type-C 接口”的活儿。说白了就是让 STM32 的 USB 差分对D / D-通过一颗 Type-C 母座对外实现设备与主机之间的全速通信并且让接口在插拔、供电、识别上都稳得住。这个项目适合谁看如果你正在用 STM32 做 USB HID、CDC 虚拟串口、MSC 大容量存储或者单纯想把老式的 Type-A / Mini-USB 接口换成 Type-C那这篇内容基本可以照着抄。它不涉及高速 USB 的复杂阻抗控制也不碰 PD 协议那套高压协商核心就是 USB2.0 全速12Mbps在 Type-C 物理接口上的落地。关键词里提到的 UCPD其实是 STM32 部分型号内置的 Type-C Power Delivery 控制器但本文的重点不在 PD 协商而是先把基础的数据通道和 CC 配置做扎实PD 是后面可以叠加的扩展项。整体设计思路可以拆成三层第一层是 STM32 侧的 USB 外设配置包括时钟、引脚复用、中断和描述符第二层是 Type-C 接口的硬件连接重点是 CC 引脚的处理和 D/D- 的走线第三层是供电与保护涉及 VBUS 的引入、ESD 防护和电源域隔离。这三层任何一层出问题表现都是“电脑不认设备”或者“插上没反应”而排查起来又特别容易混淆所以我会在后面的章节里把每一层的判断方法都写清楚。为什么选 Type-C 而不是继续用 Micro-USB除了正反插的便利性Type-C 母座的贴片封装更矮、更适合现在的薄型外壳而且线材通用性好手机线就能直接拿来用。但 Type-C 不是“插上就能用”的它需要 CC 引脚给出正确的下拉或上拉主机才会认为有设备接入。很多新手直接把 D/D- 和 VBUS/GND 接上结果电脑毫无反应问题就出在 CC 上。这个细节我会在硬件章节里重点展开。另外要说明的是STM32 的 USB 外设是 USB2.0 全速设备控制器不是高速。全速的速率是 12Mbps对应到实际批量传输大概在 1MB/s 上下做虚拟串口、HID 键盘鼠标、小容量存储完全够用。如果你需要 480Mbps 的高速传输那得换带 USB HS 外设并外接 ULPI PHY 的型号那是另一个话题。本文的所有内容都围绕全速展开参数和走线要求也按全速来定这样对大多数中小项目来说是最务实的方案。2. STM32 USB 外设配置与描述符设计2.1 时钟与引脚复用的关键参数STM32F103 的 USB 外设挂在 APB1 上但它的时钟来源比较特殊必须把 USB 预分频设置为 1.5让 USB 模块得到 48MHz 时钟。系统时钟如果是 72MHz那么RCC_CFGR里的USBPRE位要配置成0即 1.5 分频这样 72 / 1.5 48MHz正好满足 USB 全速的时钟要求。这一步如果配错USB 枚举会直接失败设备管理器里可能连“未知设备”都不出现或者出现一个带感叹号的条目。引脚方面STM32F103 的 USB 差分对固定在 PA11D-和 PA12D。这两个引脚不能随便重映射也不建议在外部加上拉电阻因为芯片内部已经集成了 1.5kΩ 的上拉用于全速设备在 D 线上做速度标识。我见过有人在 PA12 上外接一个 1.5k 上拉到 3.3V结果枚举反而不稳定原因就是内外上拉并联改变了阻抗。所以记住一句话STM32 的 USB 引脚直接连到 Type-C 母座的 D/D-中间除了必要的 ESD 器件什么都不要加。配置的时候还要注意PA11 和 PA12 默认是普通 GPIO需要把GPIOA_CRH的高位配置成复用推挽输出并且开启 USB 时钟。用标准库的话调用RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE)和GPIO_Init把这两个脚设为GPIO_Mode_AF_PP。用 HAL 库的话在MX_USB_DEVICE_Init里会自动处理但前提是 CubeMX 里把 USB 设备选上并且时钟树里 USB 的时钟源要正确。注意如果你用的是 STM32F103 的某些小容量型号比如 C6T6它的 USB 外设可能被裁剪或者 RAM 不够跑 USB 描述符选型时一定要看数据手册里的 USB 设备控制器是否存在以及 RAM 是否至少 20KB 以上。2.2 描述符的编写与常见坑USB 设备能不能被主机正确识别描述符占了一半的功劳。STM32 的 USB 库通常把描述符放在usb_desc.c里包括设备描述符、配置描述符、接口描述符、端点描述符和字符串描述符。设备描述符里的idVendor和idProduct可以自定义但如果你不想自己写驱动最好用系统自带的类比如 HID 或者 CDC。CDC 虚拟串口在 Windows 10 以上免驱Linux 和 macOS 也原生支持是最省事的方案。配置描述符里的bMaxPower要写对单位是 2mA。比如你写0x32就是 100mA。如果实际电流超过这个值主机可能会在枚举后切断供电。我一般会按实际功耗留 20% 余量比如实测 80mA就写 100mA。端点描述符里的wMaxPacketSize对于全速批量端点最大是 64 字节中断端点最大也是 64 字节不要写超过这个值否则枚举会失败。字符串描述符要用 UTF-16LE 编码很多新手直接写 ASCII结果设备管理器里显示乱码。STM32 的 USB 库里有USB_StringDescriptor的示例照着改就行。还有一个容易忽略的点描述符的总长度要和wTotalLength一致配置描述符里包含接口、端点、类特定描述符少算一个字节都会导致主机解析失败。2.3 中断优先级与缓冲区管理USB 中断的优先级不要设得太低否则在系统繁忙时容易丢包。我一般把 USB 中断设为抢占优先级 1 或 2子优先级 0。如果用了 FreeRTOSUSB 中断里只做标志置位实际处理放到任务里避免在中断里做耗时操作。STM32F103 的 USB 缓冲区是 512 字节的专用 RAM通过PMA访问不能直接指针操作要用库函数USB_SIL_Write和USB_SIL_Read来读写。双缓冲端点能提高吞吐量但配置起来复杂对于虚拟串口这种应用单缓冲 64 字节已经够用。如果你发现串口速率上不去先检查是不是每次只发一个字节就触发一次中断改成攒够 64 字节再发效率会高很多。另外USB_EP_SetStatus里的USB_EP_TX_VALID和USB_EP_RX_VALID要正确设置否则端点会一直 NAK主机以为设备没准备好。3. Type-C 接口硬件设计与 CC 引脚处理3.1 Type-C 母座引脚定义与选型Type-C 母座有 24 个引脚但实际做 USB2.0 设备时很多引脚是用不到的。核心引脚是 A6/A7 的 D 和 D-B6/B7 的 D 和 D-以及 A5 的 CC1 和 B5 的 CC2还有 A4/A9/B4/B9 的 VBUS 和 A1/A12/B1/B12 的 GND。对于正反插D 和 D- 在母座上是对称的A6 和 B6 都是 DA7 和 B7 都是 D-所以只要把 STM32 的 D 接到 A6 和 B6D- 接到 A7 和 B7就能实现正反插都能通信。CC 引脚是 Type-C 的灵魂。设备端UFP需要在 CC1 和 CC2 上各接一个 5.1kΩ 的下拉电阻到 GND。主机端DFP则是在 CC 上接 56kΩ 或 22kΩ 的上拉电阻到 VBUS。如果你把 STM32 做成设备那就老老实实放两个 5.1k 下拉。我见过有人只在一个 CC 上放电阻结果只有一面能识别另一面插上没反应。所以两个 CC 都要处理这是正反插识别的硬件基础。选型上16 针的 Type-C 母座通常够用它保留了 USB2.0 的 D/D-、CC、VBUS 和 GND去掉了高速差分对和 SBU 引脚。如果你以后想扩展音频或者调试可以选 24 针的。封装上优先选沉板式或者贴片式根据外壳厚度决定。我用的是一款 16 针沉板母座焊接时注意引脚间距是 0.5mm手工焊需要细烙铁头和放大镜。3.2 VBUS 供电与 ESD 防护VBUS 的处理要看你的设备是自供电还是总线供电。如果 STM32 板子自己就有电源那 VBUS 可以只用来做插入检测通过一个分压电阻接到 GPIO检测到高电平就知道插入了。如果要总线供电那 VBUS 要接到 LDO 或者 DC-DC给整个系统供电。注意 Type-C 主机默认只给 500mAUSB2.0 标准如果你的系统峰值电流超过这个值要么申请更大的电流需要 PD 协商要么自供电。ESD 防护是必须的尤其是 D/D- 和 VBUS 上。我一般用一颗 USB 专用的 ESD 阵列比如 SRV05-4 或者 USBLC6-2放在 Type-C 母座和 STM32 之间越靠近母座越好。走线要短接地要粗。没有 ESD 防护的话插拔几次可能就把 STM32 的 USB 引脚打坏了这种损坏往往是不可逆的。还有一个细节VBUS 上最好加一个 100nF 和 10uF 的电容做滤波尤其是总线供电时能抑制插拔瞬间的浪涌。如果 VBUS 直接进 LDOLDO 的输入电容也要足够否则上电瞬间电压跌落可能导致 STM32 复位。3.3 差分走线与阻抗控制USB2.0 全速的差分阻抗要求是 90Ω ±15%虽然全速对阻抗的敏感度比高速低但走线还是尽量按差分对来走。D 和 D- 要等长误差控制在 5mil 以内尽量平行走不要跨分割地。如果板子只有两层差分线走在顶层底层铺完整地平面。线宽和间距可以用阻抗计算工具算一下比如 1.6mm 板厚、FR4 材质大概 0.2mm 线宽、0.2mm 间距能接近 90Ω但具体要看叠层。走线长度尽量短最好不超过 5cm。如果实在要长那就保证差分对的两根线始终在一起不要分开绕。过孔尽量少每个过孔都会引入阻抗不连续。如果必须换层两个过孔要对称放置并且在过孔附近加地过孔。我实测过全速 USB 对走线的容忍度比想象中高但如果你走线乱七八糟枚举失败的概率会明显上升。提示如果你没有阻抗计算工具一个经验法则是差分线宽等于板厚的 1/8 到 1/10间距等于线宽。比如 1.6mm 板厚线宽 0.2mm间距 0.2mm大致在 90Ω 附近。当然最好还是用工具算一下。4. 实操过程与核心环节实现4.1 硬件焊接与上电检查拿到 PCB 之后先别急着焊 STM32先把 Type-C 母座和 ESD 器件焊上然后用万用表测一遍。测 CC1 和 CC2 对 GND 的电阻应该是 5.1kΩ 左右。测 VBUS 对 GND不能短路。测 D 和 D- 对 GND也不能短路。这一步能排除焊接短路和器件贴反的问题。Type-C 母座的引脚很密焊完最好用放大镜看一遍有没有连锡。然后焊 STM32 和外围。上电之前先测 3.3V 电源是否正常用示波器看有没有纹波。如果 3.3V 正常再插上电脑看设备管理器有没有反应。如果没有任何反应先查 CC 电阻再查 D/D- 是否接反。STM32 的 PA11 是 D-PA12 是 D对应到 Type-C 母座的 A7/B7 是 D-A6/B6 是 D。接反了的话主机可能识别不到或者识别成未知设备。我踩过的一个坑是Type-C 母座的 A6 和 B6 在封装上可能是同一个焊盘但有些便宜母座内部并没有把 A6 和 B6 连在一起导致只有一面能通信。遇到这种情况要么换母座要么在 PCB 上把 A6 和 B6、A7 和 B7 分别短接。买母座的时候一定要看规格书里的内部连接图。4.2 固件烧录与枚举测试硬件没问题之后烧录一个最简单的 USB HID 或者 CDC 例程。我用的是 STM32CubeMX 生成 CDC 例程时钟配置成 72MHzUSB 时钟 48MHz然后编译下载。插上电脑后设备管理器里应该出现“USB 串行设备”或者“STMicroelectronics Virtual COM Port”。如果出现“未知 USB 设备设备描述符请求失败”通常是描述符有问题或者时钟不对。如果设备管理器里能看到设备但带感叹号右键看属性里的错误代码。代码 10 通常是设备无法启动可能是描述符里的bMaxPower太大或者端点配置有问题。代码 43 是设备被主机停止可能是之前枚举失败过需要在设备管理器里卸载设备再重新插拔。代码 28 是驱动未安装CDC 一般免驱如果出现这个检查一下 VID/PID 是不是被系统识别成了别的设备。枚举成功后用串口助手打开对应的 COM 口波特率随便设因为 CDC 是虚拟串口实际速率由 USB 决定。发一串数据能收到回显就说明数据通道通了。如果收不到检查端点地址和缓冲区配置。我一般会在CDC_Receive_FS回调里直接回发这样能最快验证双向通信。4.3 速率测试与优化CDC 虚拟串口的实际速率受限于 USB 全速的 12Mbps 和协议开销理论上限大概 1MB/s 左右。我实测下来用 64 字节包连续发大概能到 700KB/s 到 900KB/s。如果只有几十 KB/s那可能是每次只发一个字节或者中断处理太慢。优化方法是攒够 64 字节再调用CDC_Transmit_FS并且把发送放在主循环里不要放在中断里。接收方向也一样CDC_Receive_FS回调里把数据存到环形缓冲区主循环再处理。如果直接在回调里做复杂运算会阻塞 USB 中断导致丢包。我用一个 512 字节的环形缓冲区实测连续发 10MB 数据没有丢包。另外USB 中断的优先级要高于串口中断否则串口数据量大时会影响 USB 响应。如果你需要更高的速率可以考虑把 CDC 改成自定义的批量端点绕过 CDC 协议的开销。但那样就需要自己写上位机驱动对于大多数应用来说CDC 的便利性更重要。我个人的经验是全速 USB 做数据采集1MB/s 以内的速率用 CDC 完全够超过这个量级就得上高速 USB 或者以太网了。5. 常见问题与排查技巧实录5.1 枚举失败问题速查表现象可能原因排查方法电脑完全没反应CC 电阻未接或阻值错误测 CC1/CC2 对 GND 电阻应为 5.1k只有一面能识别母座 A6/B6 或 A7/B7 未连通测母座两侧 D/D- 是否导通未知设备描述符请求失败时钟不对或描述符错误查 USB 时钟是否为 48MHz查描述符长度设备带感叹号代码 10端点配置或 bMaxPower 问题检查端点地址和最大包长降低 bMaxPower设备带感叹号代码 43之前枚举失败被系统禁用卸载设备后重新插拔能识别但收不到数据端点方向或缓冲区配置错误检查 IN/OUT 端点地址和回调函数数据丢包中断处理太慢或缓冲区太小增大环形缓冲区减少中断内耗时操作这个表是我在实际调试中总结的基本上覆盖了 90% 的枚举问题。遇到问题先按表查能省很多时间。尤其是 CC 电阻和母座连通性这两个是硬件层面最容易出错的而且用万用表就能测不需要示波器。5.2 供电与复位问题有时候设备能枚举但跑一会儿就掉线这通常是供电问题。USB2.0 主机默认给 500mA如果你的板子上有电机、屏幕或者无线模块峰值电流可能超过这个值导致 VBUS 电压跌落STM32 复位。解决办法是自供电或者加一个大电容缓冲。我在 VBUS 上并了一个 220uF 的电解电容掉线问题明显减少。还有一种情况是 STM32 的复位引脚受到干扰导致意外复位。可以在 NRST 引脚上并一个 100nF 电容到 GND提高抗干扰能力。如果用了看门狗喂狗时间要留够USB 枚举过程中如果被看门狗复位主机会认为设备异常。5.3 实操心得与避坑技巧第一个心得画 PCB 的时候把 Type-C 母座放在板边D/D- 走线尽量短ESD 器件紧贴母座。我第一版把 ESD 放在 STM32 旁边结果插拔几次后 ESD 器件先坏了因为浪涌在走线上已经耦合到了其他地方。第二版把 ESD 移到母座旁边问题解决。第二个心得调试 USB 的时候先烧一个最简单的 HID 例程确认硬件没问题再换 CDC 或者自定义类。HID 的描述符最简单枚举成功率最高适合用来验证硬件。如果 HID 都枚举不了那肯定是硬件或者时钟问题不用怀疑描述符。第三个心得用 USB 分析仪或者软件抓包工具看枚举过程能快速定位是哪一步失败。如果没有分析仪可以在 STM32 的 USB 中断里加一个 GPIO 翻转用示波器看中断有没有触发。如果中断根本没进那就是硬件或者时钟问题如果进了但枚举失败那就是描述符问题。第四个心得Type-C 母座的焊接温度不要太高260°C 以下时间不要超过 10 秒否则塑料芯会变形导致接触不良。我焊坏过两个母座都是因为烙铁温度设到了 350°C。后来改用热风枪加低温锡膏成功率高很多。6. 扩展方向与个人体会这个项目做完之后我陆续加了一些扩展。比如在 VBUS 上接了一个分压电路到 ADC可以监测主机供电电压如果低于 4.5V 就降低功耗。还试过用 STM32 的 UCPD 外设做 Type-C 的 PD 协商申请 9V 或 12V 电压但那是另一个复杂的主题需要额外的 PD 协议栈和硬件支持。对于大多数项目来说先把 USB2.0 全速跑稳比什么都重要。另外如果你用的是 STM32G0 或者 STM32G4 系列它们内置了 UCPD 控制器可以硬件处理 CC 引脚的检测和 PD 协商比外部分立电阻方案更灵活。但 G 系列的 USB 外设和 F103 略有不同描述符和时钟配置需要重新适配。我建议新手还是从 F103 开始资料多、坑少等熟悉了再换平台。我个人在实际操作中的体会是USB 调试最怕的就是“想当然”。以为接上 D/D- 就能用结果 CC 没接以为描述符随便写写就行结果长度算错以为走线无所谓结果阻抗不连续导致枚举不稳定。每一个“想当然”背后都是一个坑。所以我的建议是每一步都按规范来该测的测该算的算不要跳步。USB 协议本身是严谨的你对它严谨它就对你稳定。最后再分享一个小技巧如果你手头没有 USB 分析仪可以用 Wireshark 加 USBPcap 在 Windows 上抓 USB 包能看到枚举的每一步请求和响应。虽然不如硬件分析仪直观但排查描述符问题足够用了。Linux 下可以用lsusb -v看设备描述符也能快速定位问题。这些工具都是免费的关键时刻能省不少时间。
返回列表