为MCU的全速USB添加WinUSB免驱支持 CH32X035系列WinUSB免驱通信配置
接触过USB开发的用户或许都知道,编写Windows下的USB驱动程序较为繁琐,Win8.1+还有强制数字签名的问题(虽然可以让最终用户使用Zadig等自签名工具绕过);而免驱通信常用的HID自定义设备又面临HID通信速率较低的问题。是否有一种可以不必编写驱动程序,又能使用批量端点快速传输的方法呢?答案是有的,微软在Win7及更高版本操作系统上提供了WinUSB驱动程序,并在Win8.1+或更高版本的操作系统上不必提供额外的inf文件,实现真正意义上的免安装驱动。(较早版本的Win8可能需要安装几个特定的系统更新才能使用)
但这并不意味着WinUSB完全不需要额外的操作,实际上这需要在USB枚举阶段响应特定的请求,并提供特定的信息供操作系统初始化WinUSB。本文将帮助你通过几个步骤为全速USB设备添加WinUSB支持。本文以CH32X035系列芯片为例,使用USBFS外设。CH32系的USBD外设可按同样原理操作。
WinUSB的配置过程
WinUSB的自动配置依赖于操作系统在枚举USB设备时的一些描述符,下面将简单介绍WinUSB的配置过程和相关的描述符。
由于测试时MSOS1.0描述符在笔者的Win11计算机上未按预期作用,故只介绍通过MSOS2.0描述符配置WinUSB。该方式下,描述符的获取过程是通过以下方式进行的:
1 | // 以伪代码方式描述 |
不难发现,上面的过程涉及3个描述符,分别是设备描述符、BOS描述符、MSOS2.0描述符。每个描述符为操作系统请求下一个描述符提供引导指示。我们可以列出每个描述符被请求到的条件和被请求失败的后果:
- 设备描述符
- 条件:无条件
- 请求失败后果:设备无法枚举,报“代码43,请求 USB 设备描述符失败。”
- BOS描述符
- 条件:设备描述符中bcdUSB字段大于等于2.01(也有资料写大于等于2.10)
- 请求失败后果:设备无法枚举,报“代码43,请求 USB BOS 描述符失败。”或“代码10,指定不存在的设备。”
- MSOS2.0描述符
- 条件:BOS描述符中有有效的WCID信息
- 请求失败后果:报“代码43,USB 设备返回的 Microsoft 操作系统 2.0 描述符集无效。”或“代码10,指定不存在的设备。”
编写描述符
那么,为了使得WinUSB可以正确初始化,我们需要编写相应的描述符。
设备描述符的编写
设备描述符绝大多数USB应用中已经存在,只需要修改bcdUSB为2.01或更高(①处)即可。以下是一个样例:
1 | const uint8_t deviceDesc[] = |
BOS描述符的编写
BOS描述符的编写格式相对固定,只有2处需要按实际情况修改。分别是MSOS2.0描述符的总长度(①处)和厂商码(②处)。厂商码是自定义的一个byte数值,用于和设备可能使用的其他厂商请求区分,本例中为0x01
。
1 | const uint8_t bosDesc[] = |
MSOS2.0描述符的编写
该部分是最为复杂的部分。编写时,需要根据该设备具有多少个接口(不论其中是否有不被WinUSB管理的接口)决定其格式。
若设备只有一个接口,则MSOS2.0描述符应有以下条目:
- Descriptor Set Descriptor(描述符集描述符
这什么鬼名字) - Compatible ID Descriptor(兼容ID描述符)
- Registry Property Descriptor(注册表属性描述符)
若设备有多于一个接口,则MSOS2.0描述符应有以下条目:
- Descriptor Set Descriptor(描述符集描述符
这什么鬼名字) - 对于每个需要被WinUSB管理的接口:
- Function Subset Descriptor(功能子集描述符)
- Compatible ID Descriptor(兼容ID描述符)
- Registry Property Descriptor(注册表属性描述符)
开发时需要注意修改的主要有MSOS2.0描述符的总长度(①处)和设备的接口ID(③处)。设备的接口ID可用于多个VID/PID不同的设备通过同一接口ID适配于同一应用程序,例子中为CMSIS-DAP的ID,请在实际开发中替换为自己的ID。该ID遵循UTF-16 LE格式,使用双NULL结束字符串。若设备有多于一个接口,还需要注意修改每个功能子集描述符所描述接口的接口序号(②处)。
1 | const uint8_t msos2Desc[] = |
向主机提供描述符
前面提到,主机需要在特定的条件下才会获取BOS描述符和MSOS2.0描述符。我们需要让USB框架响应特定的请求,并回复主机正确的描述符。
具体来说,需要额外添加2条逻辑。添加逻辑时请务必注意协议栈中的逻辑层级关系,部分例程的USB协议栈代码可能与文中不同,请按正确的逻辑层级适当补充所需要的代码。
首先添加响应请求BOS描述符的逻辑。主机请求BOS描述符时,发出的请求是标准请求-获取描述符-获取BOS描述符(请求码0x0f
)。在CH32X035的USB协议栈实现中处理该请求位于该处:(部分例程可能缺失这一个case,手动添加即可)
1 | void USBFS_IRQHandler(void) |
然后添加响应MSOS2.0描述符的逻辑。这里需要用到编写BOS描述符时指定的厂商码。本文例子中厂商码为0x01
。
1 | void USBFS_IRQHandler(void) |
CH32X035的部分例程可能未处理非标准请求上行数据,可参考以下代码补充。
1 | void USBFS_IRQHandler(void) |
Done!
至此,如果描述符编写没有错误,响应描述符请求的逻辑没有问题,编译上传复位一把梭后,计算机将会识别到该设备并自动配置WinUSB驱动。
如果没有,重点检查以下几个问题:
- 各个描述符中涉及长度的字段是否正确
- 添加的两条逻辑是否被正确调用到
- 设备是否在上行阶段发送了正确长度的数据
另外需要补充的一点是,调试枚举阶段问题时,Bus Hound等纯软件抓包方法可能起不到很大作用。如果设备无法正常枚举完成,Bus Hound是无法捕获数据的。
为MCU的全速USB添加WinUSB免驱支持 CH32X035系列WinUSB免驱通信配置