文章目錄
- 1、聲明
- 2、HID協議
- 2.1、描述符
- 2.2、鼠標數據格式
- 3、應用程序
- 4、編譯應用程序
- 5、測試
- 6、其它
1、聲明
本文是在學習韋東山《驅動大全》USB子系統時,為梳理知識點和自己回看而記錄,全部內容高度復制粘貼。
韋老師的《驅動大全》:商品詳情
其對應的講義資料:https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
libusb api:https://libusb.sourceforge.io/api-1.0/libusb_api.html
2、HID協議
HID:Human Interface Devices, 人類用來跟計算機交互的設備。就是鼠標、鍵盤、游戲手柄等設備。對于USB接口的HID設備,有一套協議。
2.1、描述符
HID設備有如下描述符:
- HID設備的"設備描述符"并無實際意義,沒有使用"設備描述符"來表示自己是HID設備。
- HID設備只有一個配置,所以只有一個配置描述符。
- 接口描述符:
- bInterfaceClass為3,表示它是HID設備。
- bInterfaceSubClass是0或1,1表示它支持"Boot Interface"(表示PC的BIOS能識別、使用它),0表示必須等操作系統啟動后通過驅動程序來使用它。
- bInterfaceProtocol:0-None, 1-鍵盤, 2-鼠標。
- 端點描述符:HID設備有一個控制端點、一個中斷端點。
對于鼠標,HOST可以通過中斷端點讀到數據。
2.2、鼠標數據格式
通過中斷傳輸可以讀到鼠標數據,它是8字節的數據,格式如下:
偏移 | 大小 | 描述 |
---|---|---|
0 | 1字節 | |
1 | 1字節 | 按鍵狀態 |
2 | 2字節 | X位移 |
4 | 2字節 | Y位移 |
6 | 1字節或2字節 | 滾輪 |
按鍵狀態里,每一位對應鼠標的一個按鍵,等1時表示對應按鍵被點擊了,格式如下:
位 | 長度 | 描述 |
---|---|---|
0 | 1 | 鼠標的左鍵 |
1 | 1 | 鼠標的右鍵 |
2 | 1 | 鼠標的中間鍵 |
3 | 5 | 保留,設備自己定義bit3: 鼠標的側邊按鍵bit4: |
X位移、Y位移都是8位的有符號數。對于X位移,負數表示鼠標向左移動,正數表示鼠標向右移動,移動的幅度就使用這個8位數據表示。對于Y位移,負數表示鼠標向上移動,正數表示鼠標向下移動,移動的幅度就使用這個8位數據表示。
3、應用程序
本次應用程序使用的是同步接口讀取鼠標數據。
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libusb-1.0/libusb.h>int main(int argc, char **argv)
{int err;libusb_device *dev, **devs;int num_devices;int endpoint;int interface_num;int transferred;int count = 0;unsigned char buffer[8];struct libusb_config_descriptor *config_desc;struct libusb_device_handle *dev_handle = NULL;int found = 0;/* libusb init */err = libusb_init(NULL);if (err < 0) {fprintf(stderr, "failed to initialise libusb %d - %s\n", err, libusb_strerror(err));exit(1);}/* get device list */if ((num_devices = libusb_get_device_list(NULL, &devs)) < 0) // 獲取設備描述符列表(函數返回設備描述符數量){fprintf(stderr, "libusb_get_device_list() failed\n");libusb_exit(NULL);exit(1);} fprintf(stdout, "libusb_get_device_list() ok\n");/* for each device, get config descriptor */for (int i = 0; i < num_devices; i++){dev = devs[i];err = libusb_get_config_descriptor(dev, 0, &config_desc); // 獲取配置描述符if (err) {fprintf(stderr, "could not get configuration descriptor\n");continue;}fprintf(stdout, "libusb_get_config_descriptor() ok\n");/* parse interface descriptor, find usb mouse */for (int interface = 0; interface < config_desc->bNumInterfaces; interface++) // 枚舉所有接口描述符{const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0]; // 獲取配置描述符里的第一個接口描述符interface_num = intf_desc->bInterfaceNumber; // 記錄該接口描述符的編號(編號是從0開始)if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2) // 判斷是否是HID設備和是否是鼠標協議continue;/* 找到了USB鼠標 */fprintf(stdout, "find usb mouse ok\n");for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++) // 枚舉所有端點描述符{// 判斷是否是中斷傳輸,是否是輸入端點(輸入輸出都是以USB Host來討論,所以該端點是USB Device輸出到USB Host)if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT || (intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN){/* 找到了輸入的中斷端點 */fprintf(stdout, "find in int endpoint\n");endpoint = intf_desc->endpoint[ep].bEndpointAddress;found = 1;break;}}if (found)break;}libusb_free_config_descriptor(config_desc);if (found)break; }if (!found){/* free device list */libusb_free_device_list(devs, 1);libusb_exit(NULL);exit(1);}/* libusb open */if (found){err = libusb_open(dev, &dev_handle);if (err){fprintf(stderr, "failed to open usb mouse\n");exit(1);}fprintf(stdout, "libusb_open ok\n");}/* free device list */libusb_free_device_list(devs, 1);/* claim interface */libusb_set_auto_detach_kernel_driver(dev_handle, 1); err = libusb_claim_interface(dev_handle, interface_num);if (err){fprintf(stderr, "failed to libusb_claim_interface\n");exit(1);}fprintf(stdout, "libusb_claim_interface ok\n");/* libusb interrupt transfer */while (1){err = libusb_interrupt_transfer(dev_handle, endpoint, buffer, 8, &transferred, 5000); // 發起中斷傳輸,阻塞等待,5s超時時間if (!err) {/* parser data */printf("%04d datas: ", count++);printf("recv datas len = %d\n", transferred);for (int i = 0; i < transferred; i++){printf("%02x ", buffer[i]);}printf("\n");} else if (err == LIBUSB_ERROR_TIMEOUT){fprintf(stderr, "libusb_interrupt_transfer timout\n");} else {const char *errname = libusb_error_name(err);fprintf(stderr, "libusb_interrupt_transfer err : %d, %s\n", err, errname);//exit(1);}}/* libusb close */libusb_release_interface(dev_handle, interface_num);libusb_close(dev_handle);libusb_exit(NULL);
}
4、編譯應用程序
假設你的開發板是ubuntu系統:
# 安裝libusb庫
$ sudo apt install libusb-1.0-0-dev# 編譯程序
$ gcc -o readmouse readmouse.c -lusb-1.0
5、測試
將usb鼠標插入開發板:
執行程序:
$ sudo ./readmouse
移動鼠標:
滾輪滑動:
按鍵狀態:
另外,每個鼠標的數據格式是不一樣的。以上測試結果只是我使用的鼠標。
6、其它
以下是使用異步接口讀取鼠標數據的測試程序。
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libusb-1.0/libusb.h>struct usb_mouse {struct libusb_device_handle *handle;int interface;int endpoint;unsigned char buf[16];int transferred;struct libusb_transfer *transfer;struct usb_mouse *next;
};static struct usb_mouse *usb_mouse_list;void free_usb_mouses(struct usb_mouse *usb_mouse_list)
{struct usb_mouse *pnext;while (usb_mouse_list){pnext = usb_mouse_list->next;free(usb_mouse_list);usb_mouse_list = pnext;}
}int get_usb_mouse(libusb_device **devs, int num_devices, struct usb_mouse **usb_mouse_list)
{int err;libusb_device *dev;int endpoint;int interface_num;struct libusb_config_descriptor *config_desc;struct libusb_device_handle *dev_handle = NULL;struct usb_mouse *pmouse;struct usb_mouse *list = NULL;int mouse_cnt = 0;/* for each device, get config descriptor */for (int i = 0; i < num_devices; i++) {dev = devs[i];/* parse interface descriptor, find usb mouse */ err = libusb_get_config_descriptor(dev, 0, &config_desc); // 獲取配置描述符if (err) {fprintf(stderr, "could not get configuration descriptor\n");continue;}fprintf(stdout, "libusb_get_config_descriptor() ok\n");for (int interface = 0; interface < config_desc->bNumInterfaces; interface++) { // 枚舉所有接口描述符const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0];interface_num = intf_desc->bInterfaceNumber; // 記錄該接口描述符的編號(編號是從0開始)if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2) // 判斷是否是HID設備和是否是鼠標協議 continue;else{/* 找到了USB鼠標 */fprintf(stdout, "find usb mouse ok\n");for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++) // 枚舉所有端點描述符{// 判斷是否是中斷傳輸,是否是輸入端點(輸入輸出都是以USB Host來討論,所以該端點是USB Device輸出到USB Host)if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT ||(intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) {/* 找到了輸入的中斷端點 */fprintf(stdout, "find in int endpoint\n");endpoint = intf_desc->endpoint[ep].bEndpointAddress;/* libusb_open */err = libusb_open(dev, &dev_handle);if (err){fprintf(stderr, "failed to open usb mouse\n");return -1;}fprintf(stdout, "libusb_open ok\n");/* 記錄下來: 放入鏈表 */pmouse = malloc(sizeof(struct usb_mouse));if (!pmouse){fprintf(stderr, "can not malloc\n");return -1;}pmouse->endpoint = endpoint;pmouse->interface = interface_num;pmouse->handle = dev_handle;pmouse->next = NULL;if (!list)list = pmouse;else{pmouse->next = list;list = pmouse;}mouse_cnt++;break;}}}}libusb_free_config_descriptor(config_desc);}*usb_mouse_list = list;return mouse_cnt;
}static void mouse_irq(struct libusb_transfer *transfer)
{static int count = 0;if (transfer->status == LIBUSB_TRANSFER_COMPLETED){/* parser data */printf("%04d datas: ", count++);for (int i = 0; i < transfer->actual_length; i++){printf("%02x ", transfer->buffer[i]);}printf("\n");}if (libusb_submit_transfer(transfer) < 0){fprintf(stderr, "libusb_submit_transfer err\n");}
}int main(int argc, char **argv)
{int err;libusb_device **devs;int num_devices, num_mouse;struct usb_mouse *pmouse;/* libusb init */err = libusb_init(NULL);if (err < 0) {fprintf(stderr, "failed to initialise libusb %d - %s\n", err, libusb_strerror(err));exit(1);}/* get device list */if ((num_devices = libusb_get_device_list(NULL, &devs)) < 0) // 獲取設備描述符列表(函數返回設備描述符數量){fprintf(stderr, "libusb_get_device_list() failed\n");libusb_exit(NULL);exit(1);} fprintf(stdout, "libusb_get_device_list() ok\n");/* get usb mouse */num_mouse = get_usb_mouse(devs, num_devices, &usb_mouse_list);if (num_mouse <= 0){/* free device list */libusb_free_device_list(devs, 1);libusb_exit(NULL);exit(1);}fprintf(stdout, "get %d mouses\n", num_mouse);/* free device list */libusb_free_device_list(devs, 1);/* claim interface */pmouse = usb_mouse_list;while (pmouse){libusb_set_auto_detach_kernel_driver(pmouse->handle, 1); err = libusb_claim_interface(pmouse->handle, pmouse->interface);if (err){fprintf(stderr, "failed to libusb_claim_interface\n");exit(1);}pmouse = pmouse->next;}fprintf(stdout, "libusb_claim_interface ok\n");/* for each mouse, alloc transfer, fill transfer, submit transfer */pmouse = usb_mouse_list;while (pmouse){/* alloc transfer */pmouse->transfer = libusb_alloc_transfer(0);/* fill transfer */libusb_fill_interrupt_transfer(pmouse->transfer, pmouse->handle, pmouse->endpoint, pmouse->buf,sizeof(pmouse->buf), mouse_irq, pmouse, 0);/* submit transfer */libusb_submit_transfer(pmouse->transfer);pmouse = pmouse->next;}/* handle events */while (1) {struct timeval tv = { 5, 0 };int r;r = libusb_handle_events_timeout(NULL, &tv);if (r < 0) {fprintf(stderr, "libusb_handle_events_timeout err\n");break;}}/* libusb_close */pmouse = usb_mouse_list;while (pmouse){libusb_release_interface(pmouse->handle, pmouse->interface);libusb_close(pmouse->handle); pmouse = pmouse->next;}free_usb_mouses(usb_mouse_list);libusb_exit(NULL);}
運行程序前,先把多個鼠標插入開發板,然后運行測試程序,移動鼠標,查看打印結果。