经常做嵌入式开发或者DIY电子制作的朋友,应该都遇到过这种场景:USB口插着一堆开发板和自制设备,打开设备管理器一看,满屏的“USB 串行设备(COM3)”“USB Serial Device(COM7)”,你根本分不清哪个对应哪块板子。今天这篇文章,我就借着一个真实需求,完整演示怎么用STM32CubeMX配合Zadig,给STM32的USB虚拟串口定制一个专属名称,让设备管理器里一眼就能认出是谁。整个过程不绕弯子,先把思路讲清楚,再给出可以直接抄作业的配置和代码,最后把我踩过的坑一并列出来。
1. 先搞清楚设备管理器里的名称到底是谁在起作用
1.1 从USB枚举到显示名称,Windows做了哪些事
很多教程一上来就让人改代码,但改完发现设备管理器里还是老样子,原因就是没想明白Windows的设备名称机制。USB设备插上电脑后,会先被主机枚举,设备本身会向主机上报一串描述符信息,里面包含厂商ID(VID)、产品ID(PID)、产品字符串(iProduct)、厂商字符串(iManufacturer)等。这些信息相当于设备的“身份证”。
但设备管理器里显示的名字,却不一定是这个“身份证”上的名字。Windows在识别到一个USB设备后,会通过VID/PID去匹配驱动,真正决定设备管理器列表里显示什么名称的,是驱动安装包里的INF文件,里面写着类似FriendlyName = "STMicroelectronics Virtual COM Port"这样的字段。换句话说,设备自己说“我叫张三”没用,系统配发的工牌上写“李四”,那设备管理器里就显示“李四”。
这是一个非常关键的认知:想让设备管理器显示你想要的名称,要么让系统加载一个自定义的INF驱动,要么让设备描述符里的字符串恰好成为Windows最终采用的名称来源。对STM32虚拟串口来说,默认加载的是Windows自带的usbser.sys驱动,系统会统一显示为“USB 串行设备(COMx)”,所以哪怕你在STM32工程里把产品字符串改出花来,设备管理器该显示“USB 串行设备”还是显示“USB 串行设备”。这也是为什么纯改STM32代码往往无法直接解决问题的原因。
1.2 Zadig在这个流程里到底是什么角色
Zadig是一个开源驱动安装工具,网上很多人叫它“USB驱动装换神器”。它本身不负责改名字,它的核心作用是:把一个USB设备从系统当前绑定的驱动,强制切换到另一个驱动。比如把设备从系统自带的usbser.sys切到WinUSB或libusbK,或者反过来。
那Zadig和“定制专属虚拟串口名称”有什么关系?关系在于,Zadig能把设备在系统里的驱动绑定状态“洗”一遍。在我们想安装自定义INF的时候,系统经常因为已经记住了“USB 串行设备”这个旧状态而拒绝换名字,这时先通过Zadig把设备切到WinUSB,再换回串口驱动并安装自定义INF,成功率会高很多。这个技巧我后面会专门讲,属于网上教程不太会写但在实际项目中非常救命的路子。
另外,Zadig在设备列表中会显示设备上报的产品字符串,所以你在STM32CubeMX里改好USBD_PRODUCT_STRING后,可以在Zadig界面里直接看到自己取的名字。这既方便确认设备枚举状态,也是排查描述符是否修改成功的一种简单手段。
2. CubeMX快速搭出一个USB虚拟串口工程
2.1 配置STM32CubeMX生成工程,时钟树是关键
先说前提:我这边用来演示的芯片是STM32F103C8T6,也就是大家最常用的蓝药丸开发板。用其他型号比如F407、F429,操作路径略有不同,但思路完全一致。如果你还没装STM32CubeMX,先去官网下载对应版本,安装过程没什么坑,汉化包也有,这里不展开。
新建工程后,首先在SYS里把Debug选成Serial Wire,不然烧录器连不上。然后配置时钟源RCC -> HSE -> Crystal/Ceramic Resonator,再打开USB外设。F103的外设叫USB,在Pinout页面找到USB,把它的工作模式选成Device (FS),这时候右侧芯片引脚会自动分配PA11和PA12。如果你是F407等带OTG的芯片,对应的是USB_OTG_FS,同样选择Device (FS)。
接下来是很多初学者第一次翻车的地方:时钟树。USB外设对时钟的要求非常死,必须工作在48MHz。以F103为例,系统主频一般是72MHz,USB时钟来自PLL的1.5分频,也就是 72/1.5=48MHz。在CubeMX的Clock Configuration页面里,把PLL Source选择为HSE,倍频到72MHz,然后检查USB Clock那个位置是不是48MHz。如果不是,调整预分频,让USB Clock变成48.0MHz。这一步错了,设备插上电脑后要么没有任何反应,要么设备管理器里出现黄色的“未知设备(Code 10)”图标。
时钟配置好之后,中间件部分需要开启USB设备。在Middleware and Software Packs里找到USB_DEVICE,勾选启用,然后在Class for FS IP下拉框中选择Communication Device Class (Virtual Port COM)。这就是虚拟串口的核心,它让STM32在USB协议层模拟一个标准CDC类串口设备,Windows不需要装额外驱动就能识别出COM口。最后在Project Manager里设置好工程名、路径和IDE(我一般用MDK-ARM),代码生成器选项里建议勾选“生成独立的.c/.h文件”,这样改起来更清晰。
2.2 修改产品描述符,让Zadig和系统看到你的自定义名字
生成工程后,打开usbd_desc.c,这是USB设备描述符的核心文件。不同CubeMX版本生成的代码略有差别,但关键宏基本都在文件头部。默认情况下你会看到类似这样的定义:
#define USBD_VID 0x0483 #define USBD_PID 0x5740 #define USBD_LANGID_STRING 0x409 #define USBD_MANUFACTURER_STRING "STMicroelectronics" #define USBD_PRODUCT_STRING "STM32 Virtual COM Port"如果想让设备在Zadig列表里显示成自己的名字,就把厂商字符串和产品字符串都改掉,序列号也可以顺手改成一个固定值。比如:
#define USBD_LANGID_STRING 0x409 #define USBD_MANUFACTURER_STRING "MyLab" #define USBD_PRODUCT_STRING "My STM32 Virtual COM"这里有一点需要说明:USBD_PRODUCT_STRING有些版本叫USBD_PRODUCT_FS_STRING,原因是一个工程里可能同时定义了FS和HS两套字符串。你只需要找到实际被调用的那个宏并修改即可。改完之后重新编译烧录,然后插上板子,打开Zadig,勾选Options -> List All Devices,下拉框里就能看到My STM32 Virtual COM。这说明描述符已经生效了。
但请注意,到了这一步,设备管理器里显示的还不一定是这个名字。Windows的usbser.sys驱动有自己的显示逻辑,它会按照大类统称这些设备,所以你在设备管理器里大概率还是看到“USB 串行设备(COMx)”。别着急,这是正常现象,接下来的驱动替换和INF安装才是真正把名字钉死在设备管理器上的关键操作。
3. Zadig重装驱动的关键操作与驱动选择
3.1 三种驱动类型,选错了COM口会消失
用Zadig给STM32虚拟串口换驱动时,会遇到几种驱动类型:usbser、WinUSB、libusbK、libusb0。它们各有各的适用场景,必须先搞清楚再动手。
usbser是Windows自带的串口驱动,加载后设备管理器里会生成COM口,可以随便用串口助手打开收发数据。我们的最终目标其实是“让设备管理器显示自定义名称,同时保留COM口”,所以最终要绑定的还是usbser。WinUSB是微软提供的通用USB驱动,加载后设备管理器里显示为“WinUSB device”,整个设备已经不再是一个串口,而是变成一个通用USB设备,系统不会分配COM口。如果你想用WinUSB API或者libusb自己写上位机来和STM32通信,选这个没问题,但普通串口助手是打不开的。libusbK和libusb0是开源USB库提供的驱动,适合配合libusb库使用,同样不会保留COM口。
所以结论很明确:如果你想保持“虚拟串口”的易用性,不要长期把设备切到WinUSB,否则COM口就没了。但这不等于Zadig没用,后面会讲到如何利用Zadig把设备驱动状态重置一遍,再装回usbser,从而让自定义INF生效。先把Zadig的基本操作流程走一遍。
3.2 Zadig实操:如何替换驱动以及如何恢复
打开Zadig,界面非常简洁。第一次使用的人经常遇到一个问题:设备列表里找不到自己的STM32设备。这是因为Zadig默认不显示当前已有标准驱动的设备,你需要点击菜单栏的Options,勾选List All Devices。这时下拉框里就会列出所有USB设备,包括被Windows当成未知设备的那部分。
找到我们在CubeMX里设置的My STM32 Virtual COM,确认右边显示的VID和PID是0483:5740(如果你改过VID/PID就显示你自己的值)。接下来在目标驱动那一栏,选择你想切换的驱动。这里刻意不用“正确”或“错误”来形容,因为要看你的目标:如果你要为后续自定义INF做准备,可以先选WinUSB,然后点击Replace Driver。Zadig会提示即将替换驱动,确认后等待完成。整个过程大概几秒钟。
替换完成后,打开设备管理器,你会发现原来的COM口不见了,取而代之的是一个名为“WinUSB device”的条目。这是正常的。如果后面想恢复成串口,操作也很简单:再次打开Zadig,选中该设备,在目标驱动下拉里选择usbser,再点Replace Driver,设备就会重新变成COM口。这一步在实际项目里很常用,相当于一个“驱动状态重置开关”。
有一点要提醒:Zadig操作过程中必须用管理员权限运行,否则会提示驱动安装失败。另外,如果你电脑装了很多USB设备,替换驱动前一定要确认选中的设备确实是自己的STM32,不要顺手把鼠标键盘的驱动给换了,虽然可以换回来,但过程中鼠标可能短暂失灵,挺闹心的。
4. 定制专属设备管理器名称:最稳的INF方案
4.1 INF文件在Windows驱动体系中的角色
Windows给USB设备分配驱动、显示名称的依据是INF文件。INF是一个文本文件,里面描述了设备的VID/PID、要加载的驱动文件、设备类别、以及最重要的人类可读名称。你可以把它理解成一张“派工单”:系统看到一个USB设备,就去INF里查这个VID/PID应该怎么处理、显示什么名字。
对CDC虚拟串口来说,最稳妥的改名方案不是从零写一个驱动,而是写一个“引用现有串口驱动、只改显示名称”的INF。这样做的好处是底层仍然使用微软签名的usbser.sys,能保留COM口,同时系统在设备管理器里显示的DeviceName是我们在INF里自己定义的文字。很多厂商的驱动,比如ST官方驱动,本质上就是这个思路。
4.2 手写一个虚拟串口改名INF并安装
下面给出一个可以直接套用的INF模板,假设你的设备VID是0483,PID是5740,你想让设备管理器显示成“My STM32 Virtual COM”。
[Version] Signature = "$WINDOWS NT$" Class = Ports ClassGUID = {4D36E978-E325-11CE-BFC1-08002BE10318} Provider = %MfgName% DriverVer = 06/26/2024,1.0.0.0 [Manufacturer] %MfgName%=DeviceList,NTamd64 [DeviceList.NTamd64] %DeviceName%=USB_Install, USB\VID_0483&PID_5740 [USB_Install] Include = usbser.inf Needs = USBSER_Install [USB_Install.Services] Include = usbser.inf Needs = USBSER_Install.Services [Strings] MfgName = "MyLab" DeviceName = "My STM32 Virtual COM"这个INF文件的关键点是:Class必须设为Ports,这样设备管理器里会归类到“端口(COM和LPT)”下;USB_Install段通过Include和Needs引用了系统自带的usbser.inf中的安装流程,这样不需要自己拷贝任何驱动文件,系统会加载标准的串口驱动;而设备管理器右侧显示的设备名称,则由[Strings]段里的DeviceName决定。
安装这个INF有两种方式。第一种是在INF文件上右键选择“安装”,但这种方式往往只在设备处于“未安装驱动”状态时有效;如果设备已经被系统识别成“USB 串行设备”,直接右键安装很可能会提示“指定位置不包含有关硬件的信息”。第二种方式是我强烈推荐的:先用Zadig把设备切到WinUSB,然后再打开设备管理器,找到那个变名后的设备,右键选择“更新驱动程序”,接着选择“浏览我的电脑以查找驱动程序”,再点“从磁盘安装”,选中你写的INF文件。这时Windows会读取INF里的VID/PID匹配信息,加载串口驱动,设备管理器里的名称就会变成INF里指定的“My STM32 Virtual COM”,并且后面还会带上系统分配的COM号。
关于数字签名问题,这里多说一句。因为INF里引用的是系统自带的usbser.inf,驱动本体是微软签名的,所以Windows 10/11通常不会报签名错误。但如果你改了驱动本体或者用了未签名文件,就可能遇到“第三方INF不包含数字签名信息”的提示。个人实验时可以重启进入“禁用驱动程序强制签名”模式后安装,但正式给客户批量用,建议还是走签名流程或者沿用系统自带驱动。
4.3 用Zadig“绕一圈”安装法的原理与实测效果
实际操作中我发现一个现象:如果设备当前已经绑定了usbser.sys,你打开设备管理器想手动更新驱动到自定义INF,很多情况下会失败,系统会提示“已安装最适合该设备的驱动程序”,压根不给你换。这时候Zadig的价值就体现出来了。
我的常规套路是:先用Zadig把设备替换成WinUSB驱动,这一步能强制清掉Windows对“USB 串行设备”这个名称的驱动缓存。然后不需要急着用INF,而是再次打开Zadig,把目标驱动选回usbser,同时把INF文件准备好。注意,不要直接在Zadig里切回,因为Zadig切回usbser时用的是系统默认的INF,设备管理器还是会显示“USB 串行设备”。正确做法是:Zadig切到WinUSB之后,去设备管理器手动安装我们的自定义INF,Windows在更新驱动的过程中会识别到新的INF,并按照这个INF重新加载usbser.sys。
这一步我多次在不同电脑上实测,成功率非常高。原因推测是:Windows在加载WinUSB后,原来的设备实例被删掉重建,系统回不到“已经选择过默认驱动”的状态,只能老老实实按照用户指定的INF重新匹配。类似的思路也适用于CH340、CP2102等USB转串口芯片的改名,只不过那些芯片的INF结构不同,需要单独适配。
5. 实战排查:设备管理器名称不生效的常见坑
5.1 常见问题速查表
在实际操作中,最让人头疼的不是操作步骤看不懂,而是做完之后设备管理器里还是老样子。为了节省大家时间,我把过去踩过的、以及帮朋友排查过的典型问题整理成了表格。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 插上板子后设备管理器出现“未知设备”或Code 10 | USB时钟不是48MHz;PA11/PA12引脚配置冲突;晶振没起振 | 检查CubeMX时钟树,确保USB Clock为48MHz;检查整体工程引脚配置;用示波器或逻辑分析仪确认晶振工作 |
| Zadig下拉框里找不到STM32设备 | 设备没有被Windows枚举到;Zadig没勾选“List All Devices”;USB口供电不足 | 勾选Options -> List All Devices;换个后端USB口直插;重新插拔设备 |
改了USBD_PRODUCT_STRING,Zadig里还是旧名字 | 设备管理器/系统缓存了旧描述符;固件没烧录成功 | 重新编译烧录;拔掉USB线重新插;设备管理器卸载设备后重新扫描;在Zadig里刷新设备列表 |
| 安装自定义INF提示“指定位置不包含有关硬件的信息” | 设备已经被系统用默认驱动绑定,系统不认新INF;VID/PID与INF不匹配 | 先卸载设备并勾选“删除此设备的驱动程序软件”;检查INF里的VID/PID是否正确;用Zadig切换驱动后再装INF |
| 设备管理器名称没变,还是“USB 串行设备” | 安装INF时系统仍沿用旧驱动缓存;INF没安装成功 | 按4.3节的“Zadig绕一圈”套路操作;确认INF文件在64位系统的[DeviceList.NTamd64]段下有对应条目 |
| 换完WinUSB后COM口不见了 | WinUSB不是串口驱动,系统不会创建COM口 | 如果想用串口收发数据,用Zadig切回usbser,或继续用自定义INF重装驱动 |
| 多个同型号STM32板子插入,设备管理器还是分不清 | 产品字符串、序列号都相同,Windows无法区分 | 每块板子在usbd_desc.c里设置不同的USBD_SERIALNUMBER_STRING或不同的产品字符串,然后再用INF分别为不同VID/PID或序列号定义不同名称 |
5.2 几个容易被忽视的细节
除了表格里的常见问题,还有几个细节值得单独拿出来讲。第一个是关于设备描述符缓存的。USB设备描述符不是每次插上都会重新完整读取的,Windows会对相同的VID/PID组合做缓存。如果之前这块板子插入过电脑,你修改了产品字符串后再插上,系统可能还是显示旧名字。解决办法是在设备管理器里找到对应的设备,右键卸载,并且在卸载对话框中勾选“删除此设备的驱动程序软件”,然后重新插拔,这样才能强制系统重新枚举并读取新的描述符信息。
第二个是关于usbd_cdc_desc.c里的接口描述符。有些CubeMX版本生成的代码中,USBD_PRODUCT_STRING虽然改了,但设备管理器端口页里还是显示通用名,这可能是INF文件需要同时匹配CDC的通信接口(MI_00)。如果你在设备管理器里看到设备被拆成了两个条目,比如一个是“USB 串行设备(COMx)”,另一个是“USB 输入设备”,那就要考虑对INF进行更精细的接口匹配,比如把设备路径写成USB\VID_xxxx&PID_xxxx&MI_00。不过对于大多数F103 + CubeMX生成的CDC单接口设备,前面给出的完整INF模板已经够用。
第三个是COM号乱跳的问题。就算名称显示对了,如果系统把COM号从COM5一会儿跳到COM10,照样心烦。这里可以用一个小技巧:设备管理器里选中设备,右键进入“属性 -> 端口设置 -> 高级”,然后指定一个固定的COM端口号。这个操作不需要改注册表,也不用写脚本,Windows会记住设备实例和COM号的绑定关系。如果你的设备被识别成未知设备,无法进入端口设置页面,那就要先解决驱动安装问题,再回来固定COM号。
6. 一点实操心得和进阶技巧
按我自己的开发习惯,现在只要是自研USB板子,都不会让设备管理器里出现一堆“USB 串行设备”。一套顺手的工作流:CubeMX里把VID/PID改成自己专门申请或内部统一的编号,同时修改产品字符串和序列号;工程代码里预留好版本号字段;烧录后用Zadig配合一个写好的INF模板完成驱动替换和名称定制。整个过程在工程配置好的前提下,换一块新板子只需要两三分钟。
如果要给客户交付,我会把自定义INF文件连同烧录器固件一起打包,客户插上设备后只需要手动更新驱动就能看到专属名称,体验好很多。不过要注意,改VID/PID这件事是有讲究的。如果产品要过USB-IF认证,VID需要正式申请;如果只是自己玩或者内部使用,用默认的0483/5740倒也不影响操作。但一旦同一台电脑上插了多块同VID/PID的设备,区分度就会变差,所以序列号字符串最好每台都不同,可以在固件里用芯片唯一ID(比如F103的96位UID)来生成序列号字符串,这样Windows里就能精确区分每一台设备。
最后分享一个注册表层面的进阶玩法,适合想彻底统一设备名称和COM号的朋友。在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_0483&PID_5740\设备实例路径下,可以找到FriendlyName和PortName等值。手动修改这些值后,设备管理器里显示的名称和COM号会跟着变。这个方法比INF更“暴力”,改之前一定要备份注册表,而且设备更新驱动后可能被系统重置回去,只能算临时方案。正常项目开发中,用自定义INF一次配好才是长久之计。
这篇内容写到这里,核心的“STM32CubeMX配置虚拟串口 + 修改描述符 + Zadig换驱动 + 自定义INF改名”四步流程已经完整走了一遍。从一个满屏“USB 串行设备”的状态,到现在每块板子插上去都显示专属名字,整个过程并不复杂,关键是把每个工具背后的原理弄明白。按我这个流程操作,你也能省掉在设备管理器里反复拔插USB线确认端口的痛苦。