深入解析Python如何利用PyUSB輕松操控USB設備
引言:為什么需要 PyUSB?
在嵌入式系統(tǒng)、工業(yè)自動化、物聯(lián)網(wǎng)(IoT)以及硬件交互開發(fā)中,與 USB 設備通信是一項常見但技術門檻較高的任務。傳統(tǒng)上,開發(fā)者需使用 C/C++ 編寫底層驅動或調用操作系統(tǒng)特定的 API(如 Windows 的 WinUSB、Linux 的 libusb),不僅代碼復雜,還缺乏跨平臺能力。
而 PyUSB 的出現(xiàn),徹底改變了這一局面。作為 Python 生態(tài)中用于 USB 通信的核心庫,PyUSB 提供了一套簡潔、統(tǒng)一、跨平臺的接口,讓開發(fā)者無需深入操作系統(tǒng)內核,即可通過幾行 Python 代碼讀寫 USB 設備、控制端點、枚舉設備信息,甚至實現(xiàn)自定義 HID(人機接口設備)或 CDC(通信設備類)協(xié)議。
本文將從 基礎概念、安裝配置、核心 API、實戰(zhàn)案例、調試技巧到高級應用,系統(tǒng)性地介紹 PyUSB,幫助你掌握如何用 Python 高效、安全地與 USB 設備交互。無論你是想讀取 USB 溫度傳感器、控制 LED 燈帶,還是開發(fā)自己的 USB 外設固件測試工具,PyUSB 都是你不可或缺的利器。
一、USB 基礎知識回顧
在深入 PyUSB 之前,有必要簡要了解 USB 協(xié)議的基本結構,這有助于理解后續(xù)的代碼邏輯。
1.1 USB 設備層級模型
一個 USB 設備由以下邏輯單元組成:
- 設備(Device):物理 USB 設備本身,具有唯一的 Vendor ID(VID)和 Product ID(PID)。
- 配置(Configuration):一個設備可有多個配置(通常為1),描述供電、接口數(shù)量等。
- 接口(Interface):功能單元,如一個 USB 音頻設備可能包含“麥克風”和“揚聲器”兩個接口。
- 端點(Endpoint):數(shù)據(jù)傳輸?shù)慕K點。分四種類型:
- Control(控制):用于設備配置和狀態(tài)查詢(端點0,雙向);
- Bulk(批量):高可靠、低實時性,如打印機、存儲設備;
- Interrupt(中斷):低延遲、小數(shù)據(jù)量,如鍵盤、鼠標;
- Isochronous(同步):高帶寬、容忍丟包,如攝像頭、音頻流。
注意:PyUSB 主要操作 端點 來進行數(shù)據(jù)收發(fā)。
1.2 標準請求與 Class 規(guī)范
USB 協(xié)議定義了標準設備請求(如 GET_DESCRIPTOR、SET_CONFIGURATION),同時也允許廠商自定義命令。此外,USB-IF 制定了多種 設備類(Device Class) 規(guī)范,如:
- HID(Human Interface Device):鍵盤、鼠標、游戲手柄;
- CDC(Communication Device Class):虛擬串口(如 Arduino);
- MSC(Mass Storage Class):U 盤;
- DFU(Device Firmware Upgrade):固件升級。
PyUSB 不依賴這些高層協(xié)議,它工作在 傳輸層,因此適用于任何 USB 設備——包括沒有標準驅動的“裸設備”。
二、PyUSB 安裝與環(huán)境配置
2.1 安裝 PyUSB
PyUSB 是純 Python 庫,但依賴底層 USB 后端(backend)。安裝命令如下:
pip install pyusb
注意:PyUSB 本身不包含后端實現(xiàn),需額外安裝支持庫。
2.2 后端選擇與配置
PyUSB 支持三種主流后端:
| 后端 | 適用系統(tǒng) | 安裝方式 |
|---|---|---|
| libusb1 | Linux / macOS / Windows | sudo apt install libusb-1.0-0-dev (Linux) 或 libusb.org 下載 Windows DLL |
| libusb0 | 舊版 Linux / Windows | 已基本淘汰,不推薦 |
| OpenUSB | Solaris | 極少使用 |
推薦使用 libusb1,因其跨平臺性好、維護活躍。
Windows 用戶特別說明:
- 下載 libusb-1.0.dll
- 將 DLL 文件放入 C:\Windows\System32 或 Python 腳本同目錄
- 或使用 Zadig 工具為設備安裝 WinUSB 驅動(關鍵步驟?。?/li>
若未正確安裝驅動,Windows 會阻止用戶態(tài)程序訪問 USB 設備,報錯 Access denied 或 No backend available。
Linux 用戶權限問題:
默認情況下,普通用戶無權訪問 /dev/bus/usb/*。解決方法:
# 創(chuàng)建 udev 規(guī)則(以 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 && sudo udevadm trigger
三、PyUSB 核心 API 詳解
PyUSB 的設計哲學是“簡單即強大”。其核心模塊為 usb.core 和 usb.util。
3.1 枚舉與查找設備
使用 usb.core.find() 查找設備:
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:自定義查找函數(shù)
def my_filter(dev):
return dev.idVendor == 0x1234 and dev.manufacturer == "MyCompany"
dev = usb.core.find(custom_match=my_filter)
若未找到,返回 None;若找到多個且未用 find_all=True,拋出 ValueError。
3.2 設備信息獲取
一旦獲得 dev 對象(usb.core.Device 實例),可讀取其描述符:
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 & 0x80 else 'OUT'}), Type: {ep.bmAttributes & 0x03}")
注意:字符串描述符需通過 usb.util.get_string() 解碼,直接訪問 dev.iProduct 返回的是索引。
3.3 聲明接口與釋放內核驅動
在 Linux/macOS 上,若設備已被系統(tǒng)驅動占用(如 HID 鍵盤被內核接管),需先 分離內核驅動:
if dev.is_kernel_driver_active(0): # 接口0
dev.detach_kernel_driver(0)
操作完成后,應重新附加驅動(良好實踐):
dev.attach_kernel_driver(0)
Windows 通常無需此步驟(因使用 WinUSB 驅動時內核未接管)。
3.4 設置配置與聲明接口
大多數(shù)設備只需使用默認配置:
dev.set_configuration() # 使用第一個配置 cfg = dev.get_active_configuration() intf = cfg[(0, 0)] # 獲取接口0,備用設置0
3.5 端點通信:讀寫數(shù)據(jù)
寫數(shù)據(jù)(OUT 端點):
# 假設端點地址為 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")
讀數(shù)據(jù)(IN 端點):
# 假設端點地址為 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")
關鍵參數(shù):
- timeout:單位毫秒,避免無限阻塞;
- wMaxPacketSize:端點最大包長,可作為讀緩沖區(qū)大小。
四、實戰(zhàn)案例:與自定義 USB 設備通信
假設我們有一個基于 STM32 的 USB CDC 設備(虛擬串口),但希望繞過操作系統(tǒng)串口驅動,直接通過 PyUSB 控制。
4.1 設備信息分析
首先,用 lsusb -v(Linux)或 USBTreeView(Windows)查看設備描述符:
Bus 001 Device 005: ID 0483:5740 STMicroelectronics Virtual COM Port
Device Descriptor:
bcdUSB 2.00
bDeviceClass 2 Communications
bDeviceSubClass 0
bDeviceProtocol 0
idVendor 0x0483 STMicroelectronics
idProduct 0x5740 Virtual COM Port
...
Interface Descriptor:
bInterfaceNumber 0
bInterfaceClass 2 Communications
bInterfaceSubClass 2 Abstract (modem)
bInterfaceProtocol 1 AT-commands (v.25ter)
Endpoint Descriptor:
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
wMaxPacketSize 0x0008
Endpoint Descriptor:
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
可見,該設備有兩個端點:0x81(IN,中斷)、0x01(OUT,批量)。
4.2 編寫通信腳本
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")
# 發(fā)送 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)
運行結果可能為:
Response: AT
OK
此例展示了如何繞過 pyserial,直接與 CDC 設備通信,適用于需要低延遲或自定義協(xié)議的場景。
五、常見問題與調試技巧
5.1 “No backend available” 錯誤
原因:未安裝 libusb 或 DLL 未找到。
解決:
- Linux:sudo apt install libusb-1.0-0-dev
- Windows:將 libusb-1.0.dll 放入 PATH 或腳本目錄;
- 指定后端:usb.core.find(backend=usb.backend.libusb1.get_backend())
5.2 “Access denied” 或 “Operation not permitted”
Linux:檢查 udev 規(guī)則,確保用戶有權限;
Windows:用 Zadig 為設備安裝 WinUSB 驅動(替換原有驅動);
macOS:可能需要禁用 SIP 或使用 IOKit 后端(較少見)。
5.3 讀寫超時或無響應
檢查端點方向是否正確(IN/OUT);
確認設備固件是否處于接收狀態(tài);
增加 timeout 值;
使用 USB 協(xié)議分析儀(如 Wireshark + USBPcap)抓包驗證。
5.4 設備被系統(tǒng)占用
在 Windows 上,HID 設備常被 hidusb.sys 占用,需用 Zadig 替換為 WinUSB;
在 Linux 上,使用 detach_kernel_driver()。
六、高級應用:構建 USB 測試框架
PyUSB 可用于自動化測試嵌入式設備的 USB 功能。例如,驗證設備在不同命令下的響應:
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) -> 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 <= temp <= 30, f"Invalid temperature: {temp}"
此類框架可集成到 CI/CD 流程中,實現(xiàn)硬件回歸測試。
七、PyUSB 與其他庫的對比
| 庫 | 優(yōu)點 | 缺點 | 適用場景 |
|---|---|---|---|
| PyUSB | 跨平臺、底層控制、靈活 | 需處理 USB 協(xié)議細節(jié) | 自定義設備、協(xié)議開發(fā) |
| pyserial | 簡單易用 | 僅限 CDC/串口設備 | Arduino、ESP32 通信 |
hidapi(via hid) | 專為 HID 優(yōu)化 | 僅支持 HID 類 | 鍵盤、游戲手柄 |
| libusb-py | 更底層 | 已停止維護 | 不推薦 |
結論:PyUSB 是通用 USB 通信的首選。
結語:讓 Python 成為你的 USB 開發(fā)利器
PyUSB 以極簡的 API 封裝了復雜的 USB 通信細節(jié),使 Python 開發(fā)者能夠輕松進入硬件交互領域。無論是快速原型驗證、自動化測試,還是構建跨平臺的設備管理工具,PyUSB 都提供了強大而可靠的支持。
然而,能力越大,責任越大。直接操作 USB 設備意味著你需要理解端點、傳輸類型、描述符等概念,并謹慎處理權限與資源釋放。建議在開發(fā)中始終遵循:
- 先枚舉,再操作;
- 及時釋放內核驅動;
- 設置合理超時;
- 異常處理全覆蓋。
最后,記?。鹤詈玫?USB 代碼,是那些即使設備拔掉也不會崩潰的代碼。
到此這篇關于深入解析Python如何利用PyUSB輕松操控USB設備的文章就介紹到這了,更多相關Python PyUSB控制USB設備內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
python 統(tǒng)計文件中的字符串數(shù)目示例
今天小編就為大家分享一篇python 統(tǒng)計文件中的字符串數(shù)目示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12
Python使用Vagrant搭建開發(fā)環(huán)境的具體步驟
使用 Vagrant 搭建開發(fā)環(huán)境是一個非常方便的方式,它可以幫助你快速創(chuàng)建、配置和管理虛擬機,確保開發(fā)環(huán)境的一致性,以下是使用 Vagrant 搭建開發(fā)環(huán)境的具體步驟,需要的朋友可以參考下2024-09-09
pycharm配置anaconda環(huán)境時找不到python.exe的兩種解決辦法
如果你在Anaconda中創(chuàng)建了虛擬環(huán)境,但是無法找到python.exe,可能是因為虛擬環(huán)境的Python路徑?jīng)]有添加到系統(tǒng)環(huán)境變量中,這篇文章主要給大家介紹了關于pycharm配置anaconda環(huán)境時找不到python.exe的兩種解決辦法,需要的朋友可以參考下2024-07-07
Python2.7版os.path.isdir中文路徑返回false的解決方法
這篇文章主要為大家詳細介紹了Python2.7版os.path.isdir中文路徑返回false的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-06-06

