嵌入式开发笔记:USB通信软件配置完全指南——从驱动安装到应用开发
嵌入式开发笔记:USB通信软件配置完全指南——从驱动安装到应用开发
文章目录
1. 前言:硬件连上了,软件怎么配?
在嵌入式开发中,USB硬件电路设计完成、PCB打样回来、焊接好接口之后,真正的挑战才刚刚开始——软件层面的配置。
你可能会遇到这样的场景:把设备插上电脑,设备管理器里出现了“未知设备”;或者设备能被识别,但无法通信;又或者Windows上跑得好好的,换到Linux下就完全不认了。
USB通信的软件配置是一个系统工程,涉及设备枚举、驱动安装、固件描述符配置、操作系统驱动栈匹配等多个环节。任何一个环节出了问题,都会导致通信失败。
本文将从设备端到主机端、从固件到驱动、从Windows到Linux,系统讲解USB通信的软件配置全流程。全程不涉及硬件电路设计,纯软件配置实战。

2. USB通信的软件架构
2.1 主机端的驱动栈
在主机端(PC),USB通信依赖于一套层次分明的驱动栈。
以Windows为例,USB驱动栈从下到上大致为:
| 层级 | 组件 | 功能 |
|---|---|---|
| 硬件层 | USB主机控制器(EHCI/xHCI) | 物理层数据收发 |
| 总线驱动层 | usbport.sys、usbhub.sys | 端口管理、设备枚举、电源管理 |
| 类驱动层 | usbstor.sys、hidclass.sys等 | 实现特定设备类的通用功能 |
| 客户端驱动层 | 用户自定义驱动 / WinUSB | 与具体设备通信的应用层驱动 |
当USB设备插入时,PnP(即插即用)管理器会枚举设备、读取描述符,然后根据设备的VID/PID(厂商ID/产品ID) 和设备类代码,在系统中搜索匹配的驱动程序。
2.2 设备端的固件架构
在设备端(嵌入式MCU),USB通信由固件程序驱动。固件需要实现:
- USB协议栈:处理标准请求(GET_DESCRIPTOR、SET_ADDRESS等)
- 端点(Endpoint)管理:配置各端点的方向和传输类型
- 设备类请求处理:实现特定设备类(CDC、HID、MSC等)的功能
2.3 USB通信的标准流程
USB通信的软件流程可以概括为三个阶段:
阶段一:设备枚举。主机识别设备、获取描述符、分配地址。
阶段二:驱动加载。主机根据设备信息加载对应的驱动程序。
阶段三:数据传输。应用程序通过驱动与设备进行数据交换。
3. 设备枚举:USB通信的“第一步”
3.1 什么是枚举
枚举是USB设备上电后与主机建立通信的第一步。你可以把枚举理解为一个“入职面试”——主机(老板)对新插入的设备(新员工)进行一系列的“提问”,了解它的身份和能力。
主机通过发送标准请求(如GET_DESCRIPTOR、SET_ADDRESS)获取设备信息并分配地址。整个流程由主机主动发起,设备被动响应。
3.2 枚举的完整流程
以下是USB枚举的标准步骤:
步骤1:设备插入与供电
设备接上USB线后上电,处于供电状态。
步骤2:主机检测设备插入
主机通过D+/D-的电平变化检测到设备插入。
步骤3:复位设备
主机对设备进行复位操作。
步骤4:获取设备描述符(8字节)
主机发送GET_DESCRIPTOR请求,获取设备描述符的前8字节。这8字节包含了设备的最大包长度信息,主机据此知道后续应该如何传输数据。
步骤5:设置设备地址
主机发送SET_ADDRESS请求,为设备分配一个1-127之间的唯一地址。
步骤6:获取完整设备描述符(18字节)
主机再次发送GET_DESCRIPTOR请求,获取完整的18字节设备描述符。
步骤7:获取配置描述符
主机获取配置描述符、接口描述符和端点描述符。这一步可能会获取多次。
步骤8:获取字符串描述符
主机获取设备相关的字符串描述符(如厂商名、产品名、序列号)。
步骤9:获取设备限定描述符
用于获取设备在其他速度模式下的信息。如果设备不支持,必须回复STALL。
步骤10:设置配置
主机发送SET_CONFIGURATION请求,对设备的端点进行配置。
步骤11:加载类驱动
主机根据接口描述符加载对应的类驱动程序(如CDC_ACM、HID、MSC等)。
步骤12:类特定请求
驱动加载完成后,执行该类相关的特定请求。
步骤13:数据传输
进入正常的数据流传输阶段。

3.3 描述符:USB设备的“身份证”
枚举过程中,主机获取的所有信息都以描述符(Descriptor) 的形式存在。描述符是USB通信的基石——主机通过它们决定加载哪个驱动程序、分配多少带宽和电源、以及理解数据的组织格式。
| 描述符类型 | 包含信息 | 作用 |
|---|---|---|
| 设备描述符 | VID/PID、设备类、最大包长度 | 标识设备身份 |
| 配置描述符 | 接口数量、供电方式、最大功耗 | 描述设备的一种工作配置 |
| 接口描述符 | 接口号、设备类/子类/协议 | 描述一个功能(Function) |
| 端点描述符 | 端点地址、传输类型、最大包大小 | 描述一个数据通道 |
| 字符串描述符 | 厂商名、产品名、序列号 | 提供人类可读的信息 |
💡 关键理解:一个USB设备可以有多个配置(Configuration),一个配置下可以有多个接口(Interface),一个接口下可以有多个端点(Endpoint)。这种层次结构让USB设备能够支持复杂的功能组合。
4. 设备端固件配置
4.1 USB协议栈的选择
在嵌入式MCU上实现USB通信,首先需要选择一个USB协议栈:
| 方案 | 特点 | 适用场景 |
|---|---|---|
| 厂商SDK自带协议栈 | 与芯片深度绑定、稳定性高、文档齐全 | STM32(STM32CubeUSB)、NXP、Infineon等官方开发 |
| 开源协议栈(CherryUSB) | 轻量级、支持多厂商MCU、活跃的社区支持 | 需要跨平台、轻量化的项目 |
| RTOS自带协议栈 | 与操作系统深度集成 | RT-Thread、Zephyr等RTOS环境 |
4.2 描述符的配置(以CDC虚拟串口为例)
对于最常见的CDC(通信设备类)虚拟串口设备,需要在固件中配置以下描述符:
(1)设备描述符:定义VID/PID、设备类(0x02表示CDC)、最大包长度等。
(2)配置描述符:定义接口数量(CDC通常需要2个接口:数据接口+控制接口)、供电方式等。
(3)接口描述符:定义每个接口的功能。CDC虚拟串口通常包含:
- 接口0(控制接口) :用于管理串口参数(波特率、数据位等)
- 接口1(数据接口) :用于实际的数据收发
(4)端点描述符:定义数据通道。CDC虚拟串口通常需要:
- 端点1(IN) :设备→主机的数据通道
- 端点2(OUT) :主机→设备的数据通道
(5)字符串描述符:定义厂商名、产品名和序列号。
4.3 端点0(控制端点)的处理
端点0是所有USB设备都必须实现的默认控制端点。在设备完成配置之前,只有端点0可用,主机通过端点0来发送命令、获取设备信息、配置设备。
固件必须正确处理端点0上的所有标准请求,包括:
GET_DESCRIPTOR:返回各种描述符SET_ADDRESS:设置设备地址SET_CONFIGURATION:选择配置GET_CONFIGURATION:获取当前配置CLEAR_FEATURE/SET_FEATURE:控制端点特性
💡 工程要点:端点0的最大包大小在设备描述符中定义。对于全速设备,通常为8/16/32/64字节;对于高速设备,通常为64字节。
4.4 CDC虚拟串口的特殊配置
CDC虚拟串口除了标准USB描述符外,还需要配置:
-
CDC类特定请求:处理SET_LINE_CODING(设置波特率)、GET_LINE_CODING(获取波特率)、SET_CONTROL_LINE_STATE(设置DTR/RTS)等请求
-
数据缓冲管理:实现IN端点和OUT端点的数据收发回调函数
-
串口参数映射:将USB CDC的波特率、数据位、停止位、奇偶校验等参数映射到实际的UART外设配置
4.5 STM32 CubeMX配置示例
以STM32F407实现USB CDC虚拟串口为例:
- 在CubeMX中启用USB_OTG_FS外设,模式选择Device_Only
- 在Middleware中选择USB_DEVICE,Class选择Communication Device Class (Virtual Port COM)
- 配置USB中断优先级
- 生成代码后,实现CDC接收回调函数
CDC_Receive_FS和发送函数CDC_Transmit_FS - 编译烧录后,插入电脑即可识别为虚拟串口
5. Windows平台驱动配置
5.1 通用驱动方案对比
Windows下访问USB设备,主要有三种驱动方案:
| 方案 | 特点 | 适用场景 |
|---|---|---|
| 系统内置类驱动 | Windows自带,免安装 | CDC虚拟串口、HID设备、大容量存储等标准设备类 |
| WinUSB | 通用USB驱动,无需编写INF | 自定义USB设备、批量/中断/同步传输 |
| 自定义驱动(KMDF/UMDF) | 完全控制、功能最强大 | 需要特殊功能、性能要求极高的场景 |
5.2 CDC虚拟串口驱动安装
对于CDC虚拟串口设备,Windows通常会自动识别并安装usbser.sys驱动。
如果系统没有自动识别:
- 打开设备管理器,找到带黄色感叹号的“未知设备”
- 右键选择 “更新驱动程序”
- 选择 “浏览计算机以查找驱动程序软件”
- 选择 “从计算机的设备驱动程序列表中选择”
- 选择 “通用串行总线设备” → “USB串行设备”
如果仍然无法识别,可能需要安装芯片厂商提供的专用驱动(如CH340的CH341SER.EXE)。
5.3 CH340驱动安装
CH340是最常见的USB转串口芯片。驱动安装方法:
方法一(自动安装) :
- 将CH340设备插入电脑USB口
- Windows会自动尝试安装驱动
方法二(手动安装) :
- 访问南京沁恒微电子网站,下载CH340驱动
- 解压驱动包,找到
CH341SER.EXE - 右键 “以管理员身份运行”
- 点击 “安装” ,等待完成
验证:打开设备管理器 → 端口(COM和LPT),应能看到“USB-SERIAL CH340 (COMx)”。
⚠️ 常见问题:如果安装失败,可能是系统兼容性问题、驱动冲突或权限不足。尝试卸载旧驱动后重新安装,或更换USB线缆。
5.4 WinUSB驱动安装
对于自定义USB设备,WinUSB是最便捷的驱动方案。
方法一(使用Zadig工具) :
- 下载并运行Zadig
- 选择目标USB设备
- 选择驱动为 WinUSB
- 点击 “Install Driver”
方法二(手动安装) :
- 在设备管理器中右键点击设备 → 更新驱动程序
- 选择“浏览计算机以查找驱动程序软件”
- 选择“从计算机的设备驱动程序列表中选择”
- 选择 “WinUSB Device”
💡 原理说明:WinUSB使用
Winusb.sys作为功能驱动,应用程序通过WinUSB API与设备通信。无需编写自定义INF文件即可完成安装。
5.5 自定义驱动开发(WDK)
如果需要开发自定义USB客户端驱动:
开发环境准备:
- 安装Visual Studio(勾选“C++桌面开发”)
- 安装Windows Driver Kit (WDK)
- 安装Windows SDK(版本10.0.19041.0+)
开发流程:
- 使用Visual Studio的USB驱动模板生成KMDF或UMDF驱动框架代码
- 实现驱动入口函数、设备添加回调、PNP和电源管理回调
- 实现读写、IO控制等分发函数
- 编写INF安装文件
- 编译、签名、安装
6. Linux平台驱动配置
6.1 Linux USB驱动框架
Linux USB子系统分为三层:
| 层级 | 组件 | 说明 |
|---|---|---|
| 主机控制器驱动(HCD) | dwc2、ehci-platform等 | 直接操作硬件,芯片原厂通常已做好 |
| USB核心层(USBCore) | 内核核心部分 | 实现USB协议、管理总线,无需修改 |
| 设备驱动层 | usb-storage、usbhid等 | 针对特定设备类,内核已集成大量通用驱动 |
对于嵌入式开发者,工作流程通常是:在设备树中正确描述USB控制器,然后在内核配置中启用对应的设备驱动。
6.2 内核USB支持配置
Linux内核需要启用USB支持:
# 在menuconfig中启用以下选项
Device Drivers → USB Support → <*> Support for Host-side USB
Device Drivers → USB Support → <*> EHCI HCD (USB 2.0) support
Device Drivers → USB Support → <*> xHCI HCD (USB 3.0) support
对于设备模式(Gadget),还需要启用:
Device Drivers → USB Support → USB Gadget Support → <*> USB Gadget Support
6.3 设备枚举与识别
设备插入后,可以通过以下命令查看枚举结果:
# 查看USB设备树
lsusb
# 查看详细设备信息
lsusb -v -d <vid>:<pid>
# 查看内核日志(观察枚举过程)
dmesg | tail -20
# 查看USB设备节点
ls /dev/bus/usb/
6.4 CDC虚拟串口驱动
CDC虚拟串口设备在Linux下使用cdc_acm驱动。
自动识别:大多数现代Linux发行版会自动加载cdc_acm驱动,设备节点为/dev/ttyACMx。
手动加载(如果未自动加载):
sudo modprobe cdc_acm
权限配置:默认情况下,普通用户无法访问/dev/ttyACMx。可以通过添加用户到dialout组解决:
sudo usermod -a -G dialout $USER
6.5 udev规则配置
udev规则用于在设备插入时自动执行特定操作,如设置权限、创建符号链接。
创建规则文件:
sudo vim /etc/udev/rules.d/99-usb-device.rules
规则示例(为特定VID/PID的设备设置权限):
SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="5678", MODE="0666"
重新加载规则:
sudo udevadm control --reload-rules
sudo udevadm trigger
对于FTDI等特定芯片,也可以创建规则允许普通用户访问:
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", MODE="0666"
6.6 USB Gadget配置(设备模式)
当嵌入式Linux作为USB设备(Gadget)时,可以使用Configfs进行配置。
配置CDC虚拟串口(ACM)的示例:
# 挂载configfs
mount -t configfs none /sys/kernel/config
# 创建gadget
mkdir /sys/kernel/config/usb_gadget/g1
cd /sys/kernel/config/usb_gadget/g1
# 设置VID/PID
echo 0x1234 > idVendor
echo 0x5678 > idProduct
# 创建配置
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo "CDC Serial" > configs/c.1/strings/0x409/configuration
# 创建功能(ACM)
mkdir functions/acm.usb0
# 关联功能到配置
ln -s functions/acm.usb0 configs/c.1
# 启用gadget
echo <udc_name> > UDC
7. 应用层USB通信库
7.1 libusb:跨平台的瑞士军刀
libusb是一个跨平台的、用户态的USB设备访问库。它绕过了操作系统自带的标准USB驱动,让开发者可以直接用代码和USB设备“聊天”,无需为每个设备编写内核驱动。
Linux下安装:
# Debian/Ubuntu
sudo apt install libusb-1.0-0-dev
# Fedora/RHEL
sudo dnf install libusbx-devel
Windows下安装:
- 使用vcpkg:
vcpkg install libusb - 下载预编译二进制,将
libusb-1.0.dll放入C:\Windows\System32 - 使用Zadig为设备安装WinUSB驱动
基本使用流程:
#include <libusb-1.0/libusb.h>
// 1. 初始化libusb
libusb_init(NULL);
// 2. 获取设备列表
libusb_device **devs;
ssize_t count = libusb_get_device_list(NULL, &devs);
// 3. 遍历设备,查找目标VID/PID
for (ssize_t i = 0; i < count; i++) {
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(devs[i], &desc);
if (desc.idVendor == 0x1234 && desc.idProduct == 0x5678) {
// 找到目标设备
}
}
// 4. 打开设备
libusb_device_handle *handle;
libusb_open(devs[i], &handle);
// 5. 进行数据传输...
// 6. 关闭设备、释放资源
libusb_close(handle);
libusb_free_device_list(devs, 1);
libusb_exit(NULL);
⚠️ 权限问题:在Linux下,普通用户直接访问USB设备会遇到“Permission denied”错误。解决方法:使用sudo运行,或配置udev规则。
7.2 libftdi1:FTDI芯片专用库
libftdi1是专门用于与FTDI芯片通信的开源库。
安装(Debian系):
sudo apt install libftdi1-dev
设备枚举示例:
#include <ftdi.h>
struct ftdi_context *ftdi = ftdi_new();
struct ftdi_device_list *devlist;
// 查找所有FTDI设备
int count = ftdi_usb_find_all(ftdi, &devlist, 0, 0);
printf("Found %d FTDI devices\n", count);
7.3 WinUSB API(Windows原生)
对于Windows平台,可以直接使用WinUSB API与设备通信。
基本流程:
- 使用
SetupDiGetClassDevs获取设备信息集 - 使用
SetupDiEnumDeviceInfo枚举设备 - 使用
SetupDiGetDeviceInterfaceDetail获取设备路径 - 使用
CreateFile打开设备 - 使用
WinUsb_Initialize初始化WinUSB - 使用
WinUsb_ReadPipe/WinUsb_WritePipe进行数据传输
8. 调试与排查工具
8.1 主机端调试工具
| 工具 | 平台 | 功能 |
|---|---|---|
| 设备管理器 | Windows | 查看设备状态、驱动信息、COM口号 |
| lsusb | Linux | 查看USB设备树和描述符信息 |
| dmesg | Linux | 查看内核日志,观察枚举过程 |
| USB View | Windows | 查看详细的USB描述符信息 |
| Wireshark + usbmon | Linux | USB数据包抓取与分析 |
| Zadig | Windows | 驱动安装与替换 |
| Bus Hound | Windows | USB总线监控 |
8.2 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 设备管理器显示“未知设备” | 驱动未安装或描述符错误 | 安装对应驱动;检查固件描述符配置 |
| CDC设备不显示COM口 | cdc_acm驱动未加载或INF缺失 | sudo modprobe cdc_acm;手动安装INF |
| libusb打开设备返回“Permission denied” | 权限不足 | 使用sudo;配置udev规则 |
| 设备枚举到一半失败 | 描述符格式错误或端点0处理异常 | 检查固件中的描述符数据;用USB分析仪抓包 |
| 数据传输不稳定 | 端点配置错误或缓冲区溢出 | 检查端点最大包大小;增加缓冲区 |
| CH340驱动安装失败 | 驱动冲突或权限问题 | 卸载旧驱动;以管理员身份运行 |
9. 总结
USB通信的软件配置是一项从设备端固件到主机端驱动、从底层协议栈到应用层API的系统工程。
| 配置环节 | 核心要点 |
|---|---|
| 设备端固件 | 正确配置描述符(设备/配置/接口/端点);正确处理端点0的标准请求;实现设备类特定功能 |
| Windows驱动 | CDC设备用系统内置驱动;自定义设备用WinUSB(Zadig安装);特殊需求开发WDK驱动 |
| Linux驱动 | 内核启用USB支持;cdc_acm自动识别;udev配置权限;Configfs配置Gadget模式 |
| 应用层通信 | libusb跨平台通用;libftdi1专用于FTDI芯片;WinUSB API用于Windows原生开发 |
核心原则:
- 枚举是第一步:设备必须正确响应主机的标准请求,才能被识别
- 描述符是身份证:描述符的任何错误都会导致枚举失败
- 驱动匹配靠VID/PID:确保固件中的VID/PID与驱动INF中的一致
- 权限是Linux的坑:配置udev规则解决普通用户访问问题
- 调试工具是救星:lsusb、dmesg、设备管理器是排查问题的第一道防线
掌握USB通信的软件配置,你就能从“硬件连上但系统不认”进阶到“插上就能用、想怎么通信就怎么通信” 。
📚 参考资料
- USB 2.0规范(USB-IF)
- Windows驱动程序工具包(WDK)文档
- Linux内核USB子系统文档
- libusb官方文档
- STM32CubeMX USB设备配置指南
- CherryUSB开源协议栈文档
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)