博客 / 詳情

返回

Linux USB應用開發學習筆記

三、USB應用編程

經過前面內容的學習,我們學習了 USB 的基礎知識,接下來我們將探討如何藉助 USB 總線實現對 USB 外設的操作。與之前 I2C SPI 操作外設不同,usb應用採用主要是使用 libusb 庫,在這章中,主要對libusb庫的使用進行學習。

3.1 libusb庫簡介

libusb 是一個使用 C 編寫的庫,它提供 USB 設備的通用訪問方法。APP 通過它,可以方便地訪問 USB 設備,不需要編寫 USB 設備驅動程序。libusb 庫有三個特點:
1、可移植性好,支持 Linux,macOS,Windows 系統。
2、簡單易用,APP 不需要特權模式,也不需要提升自己權限即可訪問 USB 設備
3、支持所有的 USB 協議,從 USB1.0 到 USB3.1,並且 API 接口保持不變,方便開發。

3.2 libusb編譯與測試

我們可以先從github上下載libusb庫,在終端中輸入如下命令

wzy@wzy-JiaoLong:~$ git clone https://github.com/libusb/libusb.git  
Cloning into 'libusb'...  
remote: Enumerating objects: 18197, done.  
remote: Counting objects: 100% (31/31), done.  
remote: Compressing objects: 100% (24/24), done.  
remote: Total 18197 (delta 11), reused 7 (delta 7), pack-reused 18166 (from 2)  
Receiving objects: 100% (18197/18197), 5.51 MiB | 4.23 MiB/s, done.  
Resolving deltas: 100% (13031/13031), done.  
wzy@wzy-JiaoLong:~$

進入到libusb源碼文件夾中,安裝對應的依賴包準備編譯

sudo apt install autoconf automake libtool libudev-dev m4

接着輸入以下命令進行編譯和安裝

./bootstrap.sh
./autogen.sh
make
sudo make install

執行完之後,在libusb源文件下會生產一些中間文件
Pasted image 20260213113449.png
並且make install之後,會將編譯好的庫放到/usr/local/lib目錄下
Pasted image 20260213113514.png

同時在源碼文件夾examples路徑下執行的腳本會自動將一些例程自動編譯
Pasted image 20260213120121.png
可以運行其中的listdevs可執行文件來顯示系統當前的USB設備信息,包括VID、PID、端口號等

wzy@wzy-JiaoLong:~/softpack/libusb/examples$ ./listdevs    
026d:0002 (bus 5, device 6) path: 1.4  
048d:8910 (bus 5, device 5) path: 1.3  
1bcf:28c4 (bus 5, device 4) path: 1.2  
13d3:3585 (bus 5, device 3) path: 1.1  
05e3:0610 (bus 5, device 2) path: 1  
1d6b:0002 (bus 5, device 1)  
0bda:9210 (bus 4, device 2) path: 1  
1d6b:0003 (bus 4, device 1)  
1d6b:0002 (bus 3, device 1)  
1d6b:0003 (bus 2, device 1)  
24ae:1861 (bus 1, device 2) path: 2  
1d6b:0002 (bus 1, device 1)  
wzy@wzy-JiaoLong:~/softpack/libusb/examples$

上述的libusb的測試是在x86的環境下進行的測試,在實際的嵌入式開發中,我們通常會需要在嵌入式平台上使用libusb庫,這時我們就需要對libusb庫進行交叉編譯,先在剛剛編譯的libusb文件夾中執行make clean清除剛剛的編譯結果,再執行configure腳本,配置編譯環境,並且指定相應的目標平台

./configure --host=aarch64-linux-gnu --disable-udev --prefix=$PWD/install CC=/home/wzy/sdk/rk3576_linux6.1.99_release-rkr5/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-20  
21.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc CXX=/home/wzy/sdk/rk3576_linux6.1.99_release-rkr5/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aa  
rch64-none-linux-gnu-g++

通過configure腳本指定完hostprefixCCCXX變量之後,就可以直接執行make命令進行編譯了,再通過make instal進行安裝,libusb庫會安裝到我們指定的prefix路徑下面來
Pasted image 20260213182845.png
我們也可以通過file命令查看當前庫的平台信息,可以看見已經成功編譯到了aarch64平台下面了
Pasted image 20260213183102.png
這些庫編譯完成之後,我們直接通過adb push命令將庫文件和頭文件拷貝到開發板中

Pasted image 20260213184522.png
這裏需要注意的是,既可以將動態庫拷貝到/lib路徑下,也可以拷貝到/usr/lib路徑下,優先會使用/lib路徑下的libusb動態庫,如果存在的話,如果配套開發板的/lib路徑下有動態庫了,可能會出現和自己編譯時版本不匹配的情況,可以直接進行替換。接着拷貝頭文件

Pasted image 20260213184632.png
這裏需要注意的是,拷貝頭文件的時候,直接將.h文件拷貝到/usr/include目錄下,不要帶libusb-1.0這一層路徑
接着我們交叉編譯示例程序,切換到examples路徑下,使用交叉工具編譯listdevs.c源文件,命令如下

wzy@wzy-JiaoLong:~/softpack/libusb/examples$ /home/wzy/sdk/rk3576_linux6.1.99_release-rkr5/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc  
listdevs.c -o listdevs -L/home/wzy/softpack/libusb/install/lib -I/home/wzy/softpack/libusb/install/include/libusb-1.0 -lusb-1.0 -lpthread

可以看見可執行程序成功編譯出來
Pasted image 20260213185935.png
接着將該可執行二進制文件拷貝到開發板上,直接運行,發現可執行文件報錯,這是因為lisusb庫的版本不一致導致的
Pasted image 20260213192531.png

這裏遇到的這個坑,是因為開發板中動態庫與我們交叉編譯可執行文件使用的動態庫版本不同,導致函數聲明差異,這裏是通過ldd命令查看了程序連接動態庫的路徑,發現是在/lib/aarch64-linux-gnu路徑下,導致libusb動態庫的版本一直不對,一直報錯,所以我們需要將編譯出來的動態庫文件替換到這個路徑下才行

root@linaro-alip:/# ldd ./listdevs  
       linux-vdso.so.1 (0x0000007f9ad93000)  
       libusb-1.0.so.0 => /lib/aarch64-linux-gnu/libusb-1.0.so.0 (0x0000007f9ad00000)  
       libpthread.so.0 => /lib/aarch64-linux-gnu/libpthread.so.0 (0x0000007f9acd0000)  
       libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000007f9ab20000)  
       libudev.so.1 => /lib/aarch64-linux-gnu/libudev.so.1 (0x0000007f9aad0000)  
       /lib/ld-linux-aarch64.so.1 (0x0000007f9ad56000)root@linaro-alip:/# ldd ./listdevs  
       linux-vdso.so.1 (0x0000007f9ad93000)  
       libusb-1.0.so.0 => /lib/aarch64-linux-gnu/libusb-1.0.so.0 (0x0000007f9ad00000)  
       libpthread.so.0 => /lib/aarch64-linux-gnu/libpthread.so.0 (0x0000007f9acd0000)  
       libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000007f9ab20000)  
       libudev.so.1 => /lib/aarch64-linux-gnu/libudev.so.1 (0x0000007f9aad0000)  
       /lib/ld-linux-aarch64.so.1 (0x0000007f9ad56000)

可以看到,這裏連接的動態庫是存放在/lib/aarch64-linux-gnu路徑下的,所以需要替換這裏的動態庫,替換完成之後重新執行,可執行文件成功執行,我們的libusb動態庫與示例程序也就移植完成了,接下里可以開發自己的應用程序通過相同的流程部署到嵌入式開發板上
Pasted image 20260213192242.png

3.3 libusb庫應用程序編寫

在前面的章節中講解 libusb 庫的安裝和使用,交叉編譯了上層應用 demo 程序,並運行在開發板上。本章節將參考之前的 demo 程序,學習 libusb 庫應用程序的編寫。在前面的章節中講解 libusb 庫的安裝和使用,交叉編譯了上層應用 demo 程序,並運行在開發板上。本章節將參考之前的 demo 程序,學習 libusb 庫應用程序的編寫。

3.3.1 libusb庫常用API接口介紹

1.libusb_init
該函數用於初始化libusb庫

int libusb_init(libusb_context **ctx);

參數:

  • libusb_context **ctx:上下文指針,可為 NULL 使用默認上下文。
    返回值:
  • 成功返回0,失敗返回負數

2.libusb_get_device_list
該函數用於獲取當前連接的USB設備列表

 ssize_t libusb_get_device_list(libusb_context *ctx, libusb_device ***list)

參數:

  • libusb_context ctx:上下文指針
  • libusb_device ***list:輸出設備指針數組
    返回值:
  • 成功時返回設備數量,負值表示錯誤。

3.libusb_open_device_with_vid_pid
該函數用於獲取當前連接的USB設備列表

libusb_device_handle *libusb_open_device_with_vid_pid(libusb_context *ctx, uint16_t vid, uint16_t pid)

參數:

  • libusb_context *ctx:上下文指針
  • libusb_device ***list:輸出設備指針數組
    返回值:
  • 成功時返回設備數量,負值表示錯誤。

4.libusb_open_device_with_vid_pid
該函數用來根據idVendor和idProduct的值打開對應的USB設備。

libusb_device_handle *libusb_open_device_with_vid_pid(libusb_context *ctx, uint16_t vid, uint16_t pid)

參數:

  • libusb_context *ctx:上下文指針
  • uint16_t vid:要搜索的idVendor值
  • uint16_t pid:要搜索的 idProduct 值
    返回值:
  • 成功返回設備句柄(libusb_device_handle *),失敗返回 NULL。

5.libusb_open
該函數用來通過 libusb_device指針打開一個usb設備,並返回一個libusb_device_handle句柄

 int libusb_open(libusb_device *dev, libusb_device_handle **dev_handle);

參數:

  • libusb_device *dev:要打開的 USB 設備指針。
  • libusb_device_handle **dev_handle:返回的設備句柄的指針。

返回值:

  • 成功時返回 0,失敗時返回負數,表示具體的錯誤碼。

6.libusb_bulk_transfer
該函數用來實現USB的批量傳輸

int libusb_bulk_transfer(
	libusb_device_handle *dev_handle,
	unsigned char endpoint,
	unsigned char *data,
	int length,
	int *actual_length,
	unsigned int timeout
);

參數:

  • libusb_device_handle **dev_handle:指向設備句柄的指針。
  • unsigned char endpoint:端點地址,最高位為 1 表示輸入。
  • unsigned char *data:發送或接收緩衝區指針。
  • int length:緩衝區長度。
  • int *actual_length:實際傳輸長度。
  • unsigned int timeout:超時的毫秒數,0 表示永不超時

返回值:

  • 成功時返回 0,失敗時返回負數,表示具體的錯誤碼。

7.libusb_interrupt_transfer 函數
該函數用來實現USB的中斷傳輸

int libusb_interrupt_transfer(
	libusb_device_handle *dev_handle,
	unsigned char endpoint,
	unsigned char *data,
	int length,
	int *actual_length,
	unsigned int timeout
);

參數:

  • libusb_device_handle dev_handle:指向設備句柄的指針
  • unsigned char endpoint:端點地址,最高位為 1 表示輸入
  • unsigned char *data:發送或接收緩衝區指針。
  • int length:緩衝區長度。
  • int *actual_length:實際傳輸長度。
  • unsigned int timeout:超時的毫秒數,0 表示永不超時。

返回值:

  • 成功時返回 0,失敗時返回負數,表示具體的錯誤碼。

8.libusb_get_device_descriptor 函數
該函數用於獲取USB設備描述符。

int libusb_get_device_descriptor(
	libusb_device *dev,
	struct libusb_device_descriptor *desc
);

參數:

  • libusb_device *dev:要讀取的設備。
  • struct libusb_device_descriptor *desc:獲取到的設備描述符

返回值:

  • 成功時返回 0,失敗時返回負數,表示具體的錯誤碼。

9.libusb_get_config_descriptor 函數
該函數用於根據索引值獲取配置描述符。

int libusb_get_config_descriptor(
	libusb_device *dev,
	uint8_t config_index,
	struct libusb_config_descriptor **config
);

參數:

  • libusb_device *dev:要讀取的設備。
  • uint8_t config_index:配置描述符的索引(一個 USB 設備可能有多個配置)。
  • struct libusb_config_descriptor **config:獲取到的配置描述符

返回值:

  • 成功時返回 0,失敗時返回負數,表示具體的錯誤碼。

10. libusb_free_device_list 函數
該函數用於釋放設備列表。

void libusb_free_device_list(
	libusb_device **list,
	int unref_devices
);

參數:

  • libusb_device **list:要釋放的設備列表。
  • int unref_devices:如果設置成 1,則設備的引用計數-1。

返回值:

  • 成功時返回 0,失敗時返回負數,表示具體的錯誤碼。

11.libusb_free_config_descriptor函數
該函數用於釋放配置描述符。

 void libusb_free_config_descriptor(
 struct libusb_config_descriptor *config
);

參數:

  • struct libusb_config_descriptor *config:要釋放的配置描述符。
    返回值:
  • 成功時返回 0,失敗時返回負數,表示具體的錯誤碼。

12 .libusb_set_auto_detach_kernel_driver函數
該函數用於解決“內核驅動佔用 USB 設備”的問題,讓程序員可以直接用 libusb 操作設備,並且自動處理 detach / attach,針對USB設備的接口而言不是針對某一個USB設備

int libusb_set_auto_detach_kernel_driver(
	libusb_device_handle *dev_handle,
	int enable
);

參數:

  • libusb_device_handle *dev_handle:已打開的 USB 設備句柄。
  • int enable:啓用(1)或禁用(0)自動分離功能。
    返回值:
  • 成功時返回 0,失敗時返回負數,表示具體的錯誤碼。

舉例:

//打開一個usb設備
libusb_open(dev, &handle);
//自動內核分離
libusb_set_auto_detach_kernel_driver(handle, 1);
//如果申請的接口已經被內核佔用,會自動分離
libusb_claim_interface(handle, 0);

13.libusb_claim_interface
該函數用來給設備申請接口。

int libusb_claim_interface(
	libusb_device_handle *dev_handle,
	int interface_number
);

參數:

  • libusb_device_handle *dev_handle:已打開的 USB 設備句柄。
  • int interface_number:要聲明的接口編號(從 0 開始),對應接口描述符的 bInterfaceNumber 成員。
    返回值:
  • 成功時返回 0,失敗時返回負數,表示具體的錯誤碼。

14.libusb_alloc_transfer
該函數用於分配一個用於異步傳輸的結構體,即 libusb_transfer 結構體。

struct libusb_transfer *libusb_alloc_transfer( int iso_packets);

參數:

  • int iso_packets:等時傳輸包數量(非等時傳輸設為 0)。
    返回值:
  • 成功時返回 struct libusb_transfer *結構體指針,失敗時返回負數,表示具體的錯誤碼。

15.libusb_fill_interrupt_transfer
該函數用於配置一個異步中斷傳輸對象。

void libusb_fill_interrupt_transfer(
	struct libusb_transfer *transfer,
	libusb_device_handle *dev_handle,
	unsigned char endpoint,
	unsigned char *buffer,
	int length,
	libusb_transfer_cb_fn callback,
	void *user_data,
	unsigned int timeout
);

參數:

  • struct libusb_transfer *transfer:已分配的傳輸對象(需先通過libusb_alloc_transfer 分配)。
  • libusb_device_handle *dev_handle: 已打開的 USB 設備句柄。
  • unsigned char endpoint:端點地址。
  • unsigned char *buffer:數據緩衝區指針。
  • int length:緩衝區長度(字節數)
  • libusb_transfer_cb_fn callback:傳輸完成時的回調函數。
  • void *user_data:用户自定義數據(會傳遞給回調函數)。
  • unsigned int timeout:超時時間(毫秒,0 表示不超時),單位 ms。
    返回值:
  • void

16.libusb_submit_transfer
該函數用於提交一個異步傳輸請求,傳輸完成或者出錯時候調用回調函數進行處理。

 int libusb_submit_transfer(struct libusb_transfer *transfer);

參數:

  • struct libusb_transfer *transfer:已通過 libusb_fill_interrupt_transfer 配置的傳輸對象。
    返回值:
  • 成功返回 0,失敗返回負數。

17.libusb_cancel_transfer
該函數用於取消已提交但未完成的異步 USB 傳輸。

 int libusb_submit_transfer(struct libusb_transfer *transfer);

參數:

  • struct libusb_transfer *transfer:要取消的傳輸對象指針。
    返回值:
  • 成功返回 0,失敗返回負數。

18.libusb_free_transfer
該函數用於釋放傳輸結構。

void libusb_free_transfer(struct libusb_transfer *transfer);

參數:

  • struct libusb_transfer *transfer:要取消的傳輸對象指針。
    返回值:
  • 成功返回 0,失敗返回負數。

19.libusb_handle_events
該函數用於在阻塞模式下處理任何待處理事件。

int libusb_handle_events(libusb_context *ctx);

參數:

  • libusb_context *ctx:USB上下文
    返回值:
  • 成功返回 0,失敗返回負數。

3.3.2 USB設備枚舉應用程序實驗

接下來實現一個基於libusb庫的USB設備枚舉程序,主要功能是掃描並列出當前系統中所有已連接的USB設備的基本信息。

#include <stdio.h>
#include "libusb.h"

int main(void)
{
	int ret; // 函數返回值存儲
	int cnt; // 設備計數
	libusb_device **device = NULL; // 設備列表指針
	libusb_device *dev; // 單個設備指針
	int i = 0; // 循環計數器
	
	// 初始化libusb庫
	ret = libusb_init(NULL);
	if (ret < 0) {
		printf("libusb_init error \n");
		return ret;
	}
	
	// 獲取USB設備列表
	cnt = libusb_get_device_list(NULL, &device);
	if (cnt < 0) {
		printf("libusb_get_device_list error \n");
		return cnt;
	}
	
	// 遍歷設備列表
	while (dev = device[i++]) {
		struct libusb_device_descriptor desc; // 設備描述符
		// 獲取設備描述符
		ret = libusb_get_device_descriptor(dev, &desc);
		if (ret < 0) {
			printf("libusb_get_device_descriptor error \n");
			return ret;
		}
		// 打印廠商ID和產品ID
		printf("%04x : %04x\n", desc.idVendor, desc.idProduct);
	}
	
	// 釋放設備列表(unref=1表示減少引用計數)
	libusb_free_device_list(device, 1);
	// 釋放 libusb 庫佔用的所有資源
	libusb_exit(NULL);
	return 0;
}

使用如下命令編譯程序,再將可執行文件拷貝到開發板上執行

wzy@wzy-JiaoLong:~/Downloads$ /home/wzy/sdk/rk3576_linux6.1.99_release-rkr5/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc usb1.  
c -o usb1 -I/home/wzy/softpack/libusb/install/include/libusb-1.0 -L/home/wzy/softpack/libusb/install/lib -lusb-1.0 -lpthread

得到正確結果

root@linaro-alip:/# ./usb1    
05e3 : 0626  
1d6b : 0003  
2c7c : 0125  
1a86 : 8091  
05e3 : 0610  
1d6b : 0002  
root@linaro-alip:/#v

3.3.3 USB HID鍵盤設備檢測程序實驗

#include <stdio.h>
#include "libusb.h"

int main(void)
{
	// 變量聲明區
	int ret; // 存儲函數返回值
	int cnt; // 設備計數器
	libusb_device **device = NULL; // USB設備列表指針
	libusb_device *dev; // 單個USB設備指針
	int i = 0, k = 0, j = 0; // 循環計數器
	int endpoint; // 端點地址
	int flag = 0; // 標誌位

	// 描述符結構體
	struct libusb_config_descriptor *config_desc = NULL; // USB配置描述符
	const struct libusb_interface_descriptor *interface_descriptor = NULL; // 接口描述符
	libusb_device_handle *dev_handle = NULL; // USB設備句柄

	// 數據緩衝區
	unsigned char buffer[16]; // 數據接收緩衝區
	int transferred; // 實際傳輸字節數

	// 初始化libusb庫
	ret = libusb_init(NULL);
	if (ret < 0) {
		printf("libusb初始化失敗\n");
		return ret;
	}

	// 獲取USB設備列表
	cnt = libusb_get_device_list(NULL, &device);
	if (cnt < 0) {
		printf("獲取設備列表失敗\n");
		return cnt;
	}

	// 遍歷設備列表查找HID設備
	while (dev = device[i++]) {
		// 獲取設備配置描述符
		ret = libusb_get_config_descriptor(dev, 0, &config_desc);
		if (ret < 0) {
			printf("獲取配置描述符失敗\n");
			return ret;
		}

		// 遍歷所有接口
		for (k = 0; k < config_desc->bNumInterfaces; k++) {
			interface_descriptor = &config_desc->interface[k].altsetting[0];
			// 查找HID類設備(Class=3),3為HID人機交互設備且協議為鍵盤(Protocol=1),在USB協議中,是固定的規定
			if (interface_descriptor->bInterfaceClass == 3 && interface_descriptor->bInterfaceProtocol == 1) {
			// 遍歷端點查找中斷輸入端點
				for (j = 0; j < interface_descriptor->bNumEndpoints; j++) {
					if ((interface_descriptor->endpoint[j].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_INTERRUPT && (interface_descriptor->endpoint[j].bmAttributes & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) {
						endpoint = interface_descriptor->endpoint[j].bEndpointAddress;
						flag = 1; // 找到目標設備
						break;
					}
				}
			}
			if (flag) break;
		}
		if (flag) break;
	}

	// 打開目標設備
	ret = libusb_open(dev, &dev_handle);
	if (ret < 0) {
		printf("打開設備失敗\n");
		return ret;
	}

	// 設置自動卸載內核驅動
	libusb_set_auto_detach_kernel_driver(dev_handle, 1);
	// 聲明接口,一般和libusb_set_auto_detach_kernel_driver函數搭配使用
	libusb_claim_interface(dev_handle, interface_descriptor->bInterfaceNumber);
	
	// 主循環:持續讀取鍵盤數據
	while (1) {
	// 中斷傳輸讀取鍵盤數據
		ret = libusb_interrupt_transfer(dev_handle, endpoint, buffer, 16, &transferred, 5000);
		if (ret >= 0) { // 讀取成功
		// 打印接收到的鍵盤數據
			for (i = 0; i < transferred; i++) {
				printf("%02x ", buffer[i]);
			}
			printf("\n");
		}
	}

	// 資源釋放
	libusb_free_config_descriptor(config_desc); // 釋放配置描述符
	libusb_free_device_list(device, 1); // 釋放設備列表
	libusb_exit(NULL); // 釋放libusb資源
	return 0;
}

交叉編譯之後可執行程序拷貝到開發板上,執行之後插入鍵盤,按下按鍵後可以看見打印出了鍵盤的按鍵信息
Pasted image 20260214164005.png

上面的版本是使用的libusb_interrupt_transfer接口函數接收USB數據,這種方式是一種阻塞同步的方式,當前線程會完全停住,接收到數據之後才會繼續執行代碼,這種方式性能差,拓展性差,下面對接收方式進行優化,使用異步方式來處理,主要使用到以下四個API函數

libusb_alloc_transfer()
libusb_fill_interrupt_transfer()
libusb_submit_transfer()
libusb_handle_events()

完整優化代碼如下

#include <stdio.h>
#include "libusb.h"

// 全局變量聲明
struct libusb_transfer *keyboard_transfer = NULL; // 異步傳輸控制塊
unsigned char buffer[16]; // 鍵盤數據接收緩衝區
int ret; // 函數返回值存儲

// 異步傳輸回調函數
void callback_recv(struct libusb_transfer *transfer) {
	int k;
	if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
		if (transfer->actual_length > 0) {
			// 打印接收到的鍵盤數據(16進制)
			for (k = 0; k < transfer->actual_length; k++) {
				printf("%02x ", buffer[k]);
			}
			printf("\n");
		}
	}
	// 重新提交傳輸請求(實現持續監聽)
	ret = libusb_submit_transfer(keyboard_transfer);
	if (ret < 0) {
		libusb_cancel_transfer(keyboard_transfer);
		libusb_free_transfer(keyboard_transfer);
	}
}

int main(void) {
	// 設備枚舉相關變量
	int cnt; // 設備數量計數器
	libusb_device **device = NULL; // USB設備列表
	libusb_device *dev = NULL; // 當前設備指針
	int i = 0, k = 0, j = 0; // 循環計數器
	int endpoint; // 端點地址
	int flag = 0; // 設備查找標誌
	
	// USB描述符指針
	struct libusb_config_descriptor *config_desc = NULL; // 配置描述符
	const struct libusb_interface_descriptor *interface_descriptor = NULL; // 接口描述符
	libusb_device_handle *dev_handle = NULL; // 設備操作句柄

	// 初始化libusb庫
	ret = libusb_init(NULL);
	if (ret < 0) {
		printf("libusb初始化失敗\n");
		return ret;
	}

	// 獲取USB設備列表
	cnt = libusb_get_device_list(NULL, &device);
	if (cnt < 0) {
		printf("獲取設備列表失敗\n");
		return cnt;
	}

	// 遍歷設備查找HID鍵盤
	while ((dev = device[i++])) {
		// 獲取默認配置描述符
		ret = libusb_get_config_descriptor(dev, 0, &config_desc);
		if (ret < 0) {
			printf("獲取配置描述符失敗\n");
			return ret;
		}
		// 遍歷所有接口
		for (k = 0; k < config_desc->bNumInterfaces; k++) {
		interface_descriptor = &config_desc->interface[k].altsetting[0];
		// 檢查是否為HID鍵盤設備(Class=3, Protocol=1)
		if (interface_descriptor->bInterfaceClass == 3 &&
		interface_descriptor->bInterfaceProtocol == 1) {
				// 查找中斷輸入端點
				for (j = 0; j < interface_descriptor->bNumEndpoints; j++) {
					if ((interface_descriptor->endpoint[j].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_INTERRUPT && 
					(interface_descriptor->endpoint[j].bmAttributes & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN)
					{
						endpoint = interface_descriptor->endpoint[j].bEndpointAddress;
						flag = 1; // 標記已找到目標設備
						break;
					}
				}
			}
			if (flag) break;
		}
		if (flag) break;
	}

	// 打開找到的鍵盤設備
	ret = libusb_open(dev, &dev_handle);
	if (ret < 0) {
		printf("打開設備失敗\n");
		return ret;
	}
	
	// 設置自動卸載內核驅動(避免衝突)
	libusb_set_auto_detach_kernel_driver(dev_handle, 1);
	// 聲明使用該接口
	libusb_claim_interface(dev_handle, interface_descriptor->bInterfaceNumber);
	
	// 配置異步傳輸
	keyboard_transfer = libusb_alloc_transfer(0); // 分配傳輸控制塊
	libusb_fill_interrupt_transfer( // 填充中斷傳輸參數
		keyboard_transfer, 
		dev_handle, 
		endpoint,
		buffer, 
		16, 
		callback_recv, 
		NULL, 5000);
		
	// 提交異步傳輸請求
	ret = libusb_submit_transfer(keyboard_transfer);
	if (ret < 0) {
		libusb_cancel_transfer(keyboard_transfer);
		libusb_free_transfer(keyboard_transfer);
		return ret;
	}

	// 主事件循環(處理USB事件)
	while (1) {
		libusb_handle_events(NULL);
	}
	
	// 資源清理(實際不會執行到此處)
	libusb_free_config_descriptor(config_desc);
	libusb_free_device_list(device, 1);
	libusb_exit(NULL);
	return 0;
}

這種異步的方法可以理解為

1、預提交一個或多個 USB 傳輸任務
2、然後不斷輪詢事件
3、在事件循環中處理完成,如果任務完成,就執行回調函數

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.