福润 發表於 2026-1-5 14:23:44

深入解析Python如何利用PyUSB轻松操控USB设备

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">引言:为什么需要 PyUSB?</a></li><li><a href="#_label1">一、USB 基础知识回顾</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_0">1.1 USB 设备层级模型</a></li><li><a href="#_lab2_1_1">1.2 标准请求与 Class 规范</a></li></ul><li><a href="#_label2">二、PyUSB 安装与环境配置</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_2">2.1 安装 PyUSB</a></li><li><a href="#_lab2_2_3">2.2 后端选择与配置</a></li></ul><li><a href="#_label3">三、PyUSB 核心 API 详解</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_4">3.1 枚举与查找设备</a></li><li><a href="#_lab2_3_5">3.2 设备信息获取</a></li><li><a href="#_lab2_3_6">3.3 声明接口与释放内核驱动</a></li><li><a href="#_lab2_3_7">3.4 设置配置与声明接口</a></li><li><a href="#_lab2_3_8">3.5 端点通信:读写数据</a></li></ul><li><a href="#_label4">四、实战案例:与自定义 USB 设备通信</a></li><ul class="second_class_ul"><li><a href="#_lab2_4_9">4.1 设备信息分析</a></li><li><a href="#_lab2_4_10">4.2 编写通信脚本</a></li></ul><li><a href="#_label5">五、常见问题与调试技巧</a></li><ul class="second_class_ul"><li><a href="#_lab2_5_11">5.1 &ldquo;No backend available&rdquo; 错误</a></li><li><a href="#_lab2_5_12">5.2 &ldquo;Access denied&rdquo; 或 &ldquo;Operation not permitted&rdquo;</a></li><li><a href="#_lab2_5_13">5.3 读写超时或无响应</a></li><li><a href="#_lab2_5_14">5.4 设备被系统占用</a></li></ul><li><a href="#_label6">六、高级应用:构建 USB 测试框架</a></li><ul class="second_class_ul"></ul><li><a href="#_label7">七、PyUSB 与其他库的对比</a></li><ul class="second_class_ul"></ul><li><a href="#_label8">结语:让 Python 成为你的 USB 开发利器</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>引言:为什么需要 PyUSB?</h2>
<p>在嵌入式系统、工业自动化、物联网(IoT)以及硬件交互开发中,与 USB 设备通信是一项常见但技术门槛较高的任务。传统上,开发者需使用 C/C++ 编写底层驱动或调用操作系统特定的 API(如 Windows 的 WinUSB、Linux 的 libusb),不仅代码复杂,还缺乏跨平台能力。</p>
<p>而 PyUSB 的出现,彻底改变了这一局面。作为 Python 生态中用于 USB 通信的核心库,PyUSB 提供了一套简洁、统一、跨平台的接口,让开发者无需深入操作系统内核,即可通过几行 Python 代码读写 USB 设备、控制端点、枚举设备信息,甚至实现自定义 HID(人机接口设备)或 CDC(通信设备类)协议。</p>
<p>本文将从 基础概念、安装配置、核心 API、实战案例、调试技巧到高级应用,系统性地介绍 PyUSB,帮助你掌握如何用 Python 高效、安全地与 USB 设备交互。无论你是想读取 USB 温度传感器、控制 LED 灯带,还是开发自己的 USB 外设固件测试工具,PyUSB 都是你不可或缺的利器。</p>
<p class="maodian"><a name="_label1"></a></p><h2>一、USB 基础知识回顾</h2>
<p>在深入 PyUSB 之前,有必要简要了解 USB 协议的基本结构,这有助于理解后续的代码逻辑。</p>
<p class="maodian"><a name="_lab2_1_0"></a></p><h3>1.1 USB 设备层级模型</h3>
<p>一个 USB 设备由以下逻辑单元组成:</p>
<ul><li>设备(Device):物理 USB 设备本身,具有唯一的 Vendor ID(VID)和 Product ID(PID)。</li><li>配置(Configuration):一个设备可有多个配置(通常为1),描述供电、接口数量等。</li><li>接口(Interface):功能单元,如一个 USB 音频设备可能包含&ldquo;麦克风&rdquo;和&ldquo;扬声器&rdquo;两个接口。</li><li>端点(Endpoint):数据传输的终点。分四种类型:</li><li>Control(控制):用于设备配置和状态查询(端点0,双向);</li><li>Bulk(批量):高可靠、低实时性,如打印机、存储设备;</li><li>Interrupt(中断):低延迟、小数据量,如键盘、鼠标;</li><li>Isochronous(同步):高带宽、容忍丢包,如摄像头、音频流。</li></ul>
<p>注意:PyUSB 主要操作 端点 来进行数据收发。</p>
<p class="maodian"><a name="_lab2_1_1"></a></p><h3>1.2 标准请求与 Class 规范</h3>
<p>USB 协议定义了标准设备请求(如 GET_DESCRIPTOR、SET_CONFIGURATION),同时也允许厂商自定义命令。此外,USB-IF 制定了多种 设备类(Device Class) 规范,如:</p>
<ul><li>HID(Human Interface Device):键盘、鼠标、游戏手柄;</li><li>CDC(Communication Device Class):虚拟串口(如 Arduino);</li><li>MSC(Mass Storage Class):U 盘;</li><li>DFU(Device Firmware Upgrade):固件升级。</li></ul>
<p>PyUSB 不依赖这些高层协议,它工作在 传输层,因此适用于任何 USB 设备&mdash;&mdash;包括没有标准驱动的&ldquo;裸设备&rdquo;。</p>
<p class="maodian"><a name="_label2"></a></p><h2>二、PyUSB 安装与环境配置</h2>
<p class="maodian"><a name="_lab2_2_2"></a></p><h3>2.1 安装 PyUSB</h3>
<p>PyUSB 是纯 Python 库,但依赖底层 USB 后端(backend)。安装命令如下:</p>
<div class="jb51code"><pre class="brush:bash;">pip install pyusb
</pre></div>
<p>注意:PyUSB 本身不包含后端实现,需额外安装支持库。</p>
<p class="maodian"><a name="_lab2_2_3"></a></p><h3>2.2 后端选择与配置</h3>
<p>PyUSB 支持三种主流后端:</p>
<table><tbody><tr><th>后端</th><th>适用系统</th><th>安装方式</th></tr><tr><td>libusb1</td><td>Linux / macOS / Windows</td><td>sudo apt install libusb-1.0-0-dev&nbsp;(Linux) 或&nbsp;libusb.org&nbsp;下载 Windows DLL</td></tr><tr><td>libusb0</td><td>旧版 Linux / Windows</td><td>已基本淘汰,不推荐</td></tr><tr><td>OpenUSB</td><td>Solaris</td><td>极少使用</td></tr></tbody></table>
<p>推荐使用 libusb1,因其跨平台性好、维护活跃。</p>
<p><strong>Windows 用户特别说明:</strong></p>
<ul><li>下载 libusb-1.0.dll</li><li>将 DLL 文件放入 C:\Windows\System32 或 Python 脚本同目录</li><li>或使用 Zadig 工具为设备安装 WinUSB 驱动(关键步骤!)</li></ul>
<p>若未正确安装驱动,Windows 会阻止用户态程序访问 USB 设备,报错 Access denied 或 No backend available。</p>
<p><strong>Linux 用户权限问题:</strong></p>
<p>默认情况下,普通用户无权访问 /dev/bus/usb/*。解决方法:</p>
<div class="jb51code"><pre class="brush:bash;"># 创建 udev 规则(以 VID=0x1234, PID=0x5678 为例)
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="5678", MODE="0666"' | sudo tee /etc/udev/rules.d/99-myusb.rules
sudo udevadm control --reload-rules &amp;&amp; sudo udevadm trigger
</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>三、PyUSB 核心 API 详解</h2>
<p>PyUSB 的设计哲学是&ldquo;简单即强大&rdquo;。其核心模块为 usb.core 和 usb.util。</p>
<p class="maodian"><a name="_lab2_3_4"></a></p><h3>3.1 枚举与查找设备</h3>
<p>使用 usb.core.find() 查找设备:</p>
<div class="jb51code"><pre class="brush:py;">import usb.core
import usb.util

# 方法1:通过 VID/PID 查找
dev = usb.core.find(idVendor=0x1234, idProduct=0x5678)

# 方法2:查找所有设备
devices = usb.core.find(find_all=True)

# 方法3:自定义查找函数
def my_filter(dev):
    return dev.idVendor == 0x1234 and dev.manufacturer == "MyCompany"

dev = usb.core.find(custom_match=my_filter)
</pre></div>
<p>若未找到,返回 None;若找到多个且未用 find_all=True,抛出 ValueError。</p>
<p class="maodian"><a name="_lab2_3_5"></a></p><h3>3.2 设备信息获取</h3>
<p>一旦获得 dev 对象(usb.core.Device 实例),可读取其描述符:</p>
<div class="jb51code"><pre class="brush:py;">print(f"Manufacturer: {usb.util.get_string(dev, dev.iManufacturer)}")
print(f"Product: {usb.util.get_string(dev, dev.iProduct)}")
print(f"Serial: {usb.util.get_string(dev, dev.iSerialNumber)}")

# 打印所有配置
for cfg in dev:
    print(f"Config {cfg.bConfigurationValue}")
    for intf in cfg:
      print(f"Interface {intf.bInterfaceNumber}: {intf.bNumEndpoints} endpoints")
      for ep in intf:
            print(f"    EP {ep.bEndpointAddress:02x} ({'IN' if ep.bEndpointAddress &amp; 0x80 else 'OUT'}), Type: {ep.bmAttributes &amp; 0x03}")
</pre></div>
<p>注意:字符串描述符需通过 usb.util.get_string() 解码,直接访问 dev.iProduct 返回的是索引。</p>
<p class="maodian"><a name="_lab2_3_6"></a></p><h3>3.3 声明接口与释放内核驱动</h3>
<p>在 Linux/macOS 上,若设备已被系统驱动占用(如 HID 键盘被内核接管),需先 分离内核驱动:</p>
<div class="jb51code"><pre class="brush:py;">if dev.is_kernel_driver_active(0):# 接口0
    dev.detach_kernel_driver(0)
</pre></div>
<p>操作完成后,应重新附加驱动(良好实践):</p>
<div class="jb51code"><pre class="brush:py;">dev.attach_kernel_driver(0)
</pre></div>
<p>Windows 通常无需此步骤(因使用 WinUSB 驱动时内核未接管)。</p>
<p class="maodian"><a name="_lab2_3_7"></a></p><h3>3.4 设置配置与声明接口</h3>
<p>大多数设备只需使用默认配置:</p>
<div class="jb51code"><pre class="brush:py;">dev.set_configuration()# 使用第一个配置
cfg = dev.get_active_configuration()
intf = cfg[(0, 0)]       # 获取接口0,备用设置0
</pre></div>
<p class="maodian"><a name="_lab2_3_8"></a></p><h3>3.5 端点通信:读写数据</h3>
<p>写数据(OUT 端点):</p>
<div class="jb51code"><pre class="brush:py;"># 假设端点地址为 0x01(OUT)
ep_out = usb.util.find_descriptor(intf, custom_match=lambda e: \
    usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT)

if ep_out:
    data = b'\x01\x02\x03\x04'
    bytes_written = dev.write(ep_out.bEndpointAddress, data, timeout=1000)
    print(f"Sent {bytes_written} bytes")
</pre></div>
<p>读数据(IN 端点):</p>
<div class="jb51code"><pre class="brush:py;"># 假设端点地址为 0x81(IN)
ep_in = usb.util.find_descriptor(intf, custom_match=lambda e: \
    usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)

if ep_in:
    try:
      data = dev.read(ep_in.bEndpointAddress, ep_in.wMaxPacketSize, timeout=1000)
      print(f"Received: {data}")
    except usb.core.USBError as e:
      if e.errno == 110:# 超时
            print("Read timeout")
</pre></div>
<p>关键参数:</p>
<ul><li>timeout:单位毫秒,避免无限阻塞;</li><li>wMaxPacketSize:端点最大包长,可作为读缓冲区大小。</li></ul>
<p class="maodian"><a name="_label4"></a></p><h2>四、实战案例:与自定义 USB 设备通信</h2>
<p>假设我们有一个基于 STM32 的 USB CDC 设备(虚拟串口),但希望绕过操作系统串口驱动,直接通过 PyUSB 控制。</p>
<p class="maodian"><a name="_lab2_4_9"></a></p><h3>4.1 设备信息分析</h3>
<p>首先,用 lsusb -v(Linux)或 USBTreeView(Windows)查看设备描述符:</p>
<blockquote><p>Bus 001 Device 005: ID 0483:5740 STMicroelectronics Virtual COM Port<br />Device Descriptor:<br />&nbsp; bcdUSB &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2.00<br />&nbsp; bDeviceClass &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;2 Communications<br />&nbsp; bDeviceSubClass &nbsp; &nbsp; &nbsp; 0&nbsp;<br />&nbsp; bDeviceProtocol &nbsp; &nbsp; &nbsp; 0&nbsp;<br />&nbsp; idVendor &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0x0483 STMicroelectronics<br />&nbsp; idProduct &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0x5740 Virtual COM Port<br />&nbsp; ...<br />&nbsp; Interface Descriptor:<br />&nbsp; &nbsp; bInterfaceNumber &nbsp; &nbsp; 0<br />&nbsp; &nbsp; bInterfaceClass &nbsp; &nbsp; &nbsp;2 Communications<br />&nbsp; &nbsp; bInterfaceSubClass &nbsp; 2 Abstract (modem)<br />&nbsp; &nbsp; bInterfaceProtocol &nbsp; 1 AT-commands (v.25ter)<br />&nbsp; &nbsp; Endpoint Descriptor:<br />&nbsp; &nbsp; &nbsp; bEndpointAddress &nbsp; &nbsp; 0x81 &nbsp;EP 1 IN<br />&nbsp; &nbsp; &nbsp; bmAttributes &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;3<br />&nbsp; &nbsp; &nbsp; &nbsp; Transfer Type &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Interrupt<br />&nbsp; &nbsp; &nbsp; wMaxPacketSize &nbsp; &nbsp; 0x0008<br />&nbsp; &nbsp; Endpoint Descriptor:<br />&nbsp; &nbsp; &nbsp; bEndpointAddress &nbsp; &nbsp; 0x01 &nbsp;EP 1 OUT<br />&nbsp; &nbsp; &nbsp; bmAttributes &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;2<br />&nbsp; &nbsp; &nbsp; &nbsp; Transfer Type &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Bulk</p></blockquote>
<p>可见,该设备有两个端点:0x81(IN,中断)、0x01(OUT,批量)。</p>
<p class="maodian"><a name="_lab2_4_10"></a></p><h3>4.2 编写通信脚本</h3>
<div class="jb51code"><pre class="brush:py;">import usb.core
import usb.util
import sys

# 查找设备
dev = usb.core.find(idVendor=0x0483, idProduct=0x5740)
if dev is None:
    raise ValueError('Device not found')

# 分离内核驱动(Linux/macOS)
if sys.platform != "win32":
    if dev.is_kernel_driver_active(0):
      dev.detach_kernel_driver(0)

# 设置配置
dev.set_configuration()

# 获取接口
cfg = dev.get_active_configuration()
intf = cfg[(0, 0)]

# 查找端点
ep_out = usb.util.find_descriptor(
    intf,
    custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT
)
ep_in = usb.util.find_descriptor(
    intf,
    custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN
)

if not ep_out or not ep_in:
    raise ValueError("Endpoints not found")

# 发送 AT 命令
command = b"AT\r\n"
dev.write(ep_out.bEndpointAddress, command, timeout=1000)

# 读取响应
try:
    response = dev.read(ep_in.bEndpointAddress, 64, timeout=1000)
    print("Response:", response.tobytes().decode('utf-8', errors='replace'))
except usb.core.USBError as e:
    print("Read error:", e)
</pre></div>
<p>运行结果可能为:</p>
<blockquote><p>Response: AT<br />OK</p></blockquote>
<p>此例展示了如何绕过 pyserial,直接与 CDC 设备通信,适用于需要低延迟或自定义协议的场景。</p>
<p class="maodian"><a name="_label5"></a></p><h2>五、常见问题与调试技巧</h2>
<p class="maodian"><a name="_lab2_5_11"></a></p><h3>5.1 &ldquo;No backend available&rdquo; 错误</h3>
<p>原因:未安装 libusb 或 DLL 未找到。</p>
<p>解决:</p>
<ul><li>Linux:sudo apt install libusb-1.0-0-dev</li><li>Windows:将 libusb-1.0.dll 放入 PATH 或脚本目录;</li><li>指定后端:usb.core.find(backend=usb.backend.libusb1.get_backend())</li></ul>
<p class="maodian"><a name="_lab2_5_12"></a></p><h3>5.2 &ldquo;Access denied&rdquo; 或 &ldquo;Operation not permitted&rdquo;</h3>
<p>Linux:检查 udev 规则,确保用户有权限;</p>
<p>Windows:用 Zadig 为设备安装 WinUSB 驱动(替换原有驱动);</p>
<p>macOS:可能需要禁用 SIP 或使用 IOKit 后端(较少见)。</p>
<p class="maodian"><a name="_lab2_5_13"></a></p><h3>5.3 读写超时或无响应</h3>
<p>检查端点方向是否正确(IN/OUT);</p>
<p>确认设备固件是否处于接收状态;</p>
<p>增加 timeout 值;</p>
<p>使用 USB 协议分析仪(如 Wireshark + USBPcap)抓包验证。</p>
<p class="maodian"><a name="_lab2_5_14"></a></p><h3>5.4 设备被系统占用</h3>
<p>在 Windows 上,HID 设备常被 hidusb.sys 占用,需用 Zadig 替换为 WinUSB;</p>
<p>在 Linux 上,使用 detach_kernel_driver()。</p>
<p class="maodian"><a name="_label6"></a></p><h2>六、高级应用:构建 USB 测试框架</h2>
<p>PyUSB 可用于自动化测试嵌入式设备的 USB 功能。例如,验证设备在不同命令下的响应:</p>
<div class="jb51code"><pre class="brush:py;">class USBDeviceTester:
    def __init__(self, vid, pid):
      self.dev = usb.core.find(idVendor=vid, idProduct=pid)
      assert self.dev, "Device not found"
      self.dev.set_configuration()
      self._setup_endpoints()

    def _setup_endpoints(self):
      cfg = self.dev.get_active_configuration()
      intf = cfg[(0, 0)]
      self.ep_out = ...# find OUT
      self.ep_in = ...   # find IN

    def send_command(self, cmd: bytes) -&gt; bytes:
      self.dev.write(self.ep_out.bEndpointAddress, cmd, timeout=2000)
      return self.dev.read(self.ep_in.bEndpointAddress, 256, timeout=2000).tobytes()

    def test_led_on(self):
      resp = self.send_command(b"LED ON\n")
      assert b"OK" in resp, "LED ON failed"

    def test_temperature(self):
      resp = self.send_command(b"TEMP?\n")
      temp = float(resp.strip())
      assert 20 &lt;= temp &lt;= 30, f"Invalid temperature: {temp}"
</pre></div>
<p>此类框架可集成到 CI/CD 流程中,实现硬件回归测试。</p>
<p class="maodian"><a name="_label7"></a></p><h2>七、PyUSB 与其他库的对比</h2>
<table><tbody><tr><th>库</th><th>优点</th><th>缺点</th><th>适用场景</th></tr><tr><td><strong>PyUSB</strong></td><td>跨平台、底层控制、灵活</td><td>需处理 USB 协议细节</td><td>自定义设备、协议开发</td></tr><tr><td><strong>pyserial</strong></td><td>简单易用</td><td>仅限 CDC/串口设备</td><td>Arduino、ESP32 通信</td></tr><tr><td><strong>hidapi</strong>(via&nbsp;<code>hid</code>)</td><td>专为 HID 优化</td><td>仅支持 HID 类</td><td>键盘、游戏手柄</td></tr><tr><td><strong>libusb-py</strong></td><td>更底层</td><td>已停止维护</td><td>不推荐</td></tr></tbody></table>
<p><strong>结论:PyUSB 是通用 USB 通信的首选。</strong></p>
<p class="maodian"><a name="_label8"></a></p><h2>结语:让 Python 成为你的 USB 开发利器</h2>
<p>PyUSB 以极简的 API 封装了复杂的 USB 通信细节,使 Python 开发者能够轻松进入硬件交互领域。无论是快速原型验证、自动化测试,还是构建跨平台的设备管理工具,PyUSB 都提供了强大而可靠的支持。</p>
<p>然而,能力越大,责任越大。直接操作 USB 设备意味着你需要理解端点、传输类型、描述符等概念,并谨慎处理权限与资源释放。建议在开发中始终遵循:</p>
<ul><li>先枚举,再操作;</li><li>及时释放内核驱动;</li><li>设置合理超时;</li><li>异常处理全覆盖。</li></ul>
<p>最后,记住:最好的 USB 代码,是那些即使设备拔掉也不会崩溃的代码。</p>
頁: [1]
查看完整版本: 深入解析Python如何利用PyUSB轻松操控USB设备