成就更好的自己
時(shí)隔一年再次開(kāi)始撰寫博客,這一年的時(shí)間經(jīng)歷了很多,現(xiàn)在終于穩(wěn)定下來(lái)。以后很長(zhǎng)一段時(shí)間都能夠穩(wěn)定的學(xué)習(xí)和更新。時(shí)間將會(huì)聚焦于USB和PCIe的開(kāi)發(fā)進(jìn)行,能和大家共同進(jìn)步真的很高興。
本篇為USB系列的LibUSB使用指南的第一篇。
USB系列主要圍繞USB的知識(shí)、協(xié)議、開(kāi)發(fā)總結(jié)、使用說(shuō)明等進(jìn)行。
LibUSB使用指南主要圍繞LibUSB庫(kù)的使用進(jìn)行。
LibUSB中的描述符結(jié)構(gòu)分析
LibUSB中的描述符結(jié)構(gòu)主要分為一下幾種層次:
設(shè)備描述符->配置描述符->接口描述符(備用接口描述符)->端點(diǎn)描述符
看到這篇博客的都應(yīng)該知道上述描述符的包含關(guān)系和可能的存在數(shù)量,基礎(chǔ)知識(shí)不再贅述,到時(shí)候會(huì)專門寫對(duì)應(yīng)的基礎(chǔ)知識(shí)博客。
LibUSB中的描述符包含關(guān)系與結(jié)構(gòu)如下圖所示:
?
從圖中可以看出是沒(méi)有設(shè)備描述符libusb_device_descriptor的,因?yàn)樵O(shè)備描述符一般可以看做是以設(shè)備為對(duì)象的一種描述結(jié)構(gòu),因此每個(gè)設(shè)備只有一個(gè)而且地位特殊。
圖中最高層是配置描述符libusb_config_descripto,使用libusb_get_config_descriptor函數(shù)傳入索引可以獲得該設(shè)備中的某一個(gè)配置描述符。配置描述符中有一項(xiàng)bNumInterfaces為接口描述符的個(gè)數(shù),可通過(guò)該項(xiàng)獲得在該配置描述符中接口描述符的個(gè)數(shù)。
在配置描述符中還有一項(xiàng)為libusb_interface *interface,該項(xiàng)就是接口描述符的結(jié)構(gòu)體數(shù)組。這個(gè)接口描述符與我們廣義上理解的接口描述符不同,這里的接口描述符是由備用接口描述符結(jié)構(gòu)體數(shù)組和備用描述符個(gè)數(shù)組成的,也就是libusb_interface_descriptor *altsetting和num_altsetting這兩項(xiàng)。
要注意的是bNumInterfaces所指是接口描述符數(shù)組的個(gè)數(shù),num_altsetting所指的是每個(gè)接口描述符數(shù)組成員中的備用接口描述符的數(shù)量。因此實(shí)際的配置描述符是由備用接口描述符組成的一個(gè)二維數(shù)組,這個(gè)二維數(shù)組的主索引是bNumInterfaces,副索引是num_altsetting。
在備用接口描述符數(shù)組中的每個(gè)成員都攜帶著在該備用接口下所屬的若干端點(diǎn)的描述符結(jié)構(gòu)體。
整個(gè)上面的過(guò)程中只有這個(gè)備用接口的概念和廣義理解上的不太一樣,剩下各個(gè)描述符中的各個(gè)字段均與協(xié)議棧中我們所通識(shí)的一樣。實(shí)際上,這些結(jié)構(gòu)體的存在方式和使用LibUSB函數(shù)獲取描述符的方式與USB設(shè)備的枚舉過(guò)程和方式的有著本質(zhì)上的關(guān)聯(lián)(后期博客提到)。
下面就是以博主電腦上為例,輸出能探測(cè)到設(shè)備的詳細(xì)信息(太長(zhǎng)了,部分截圖):
Windows下的USB驅(qū)動(dòng)問(wèn)題
Windows下LibUSB產(chǎn)生的很多報(bào)錯(cuò)與驅(qū)動(dòng)有著很大關(guān)系。
我先言簡(jiǎn)意駭?shù)暮?jiǎn)要介紹一下在你的Windows中可能存在的各種USB驅(qū)動(dòng),我暫時(shí)驅(qū)動(dòng)分為高級(jí)的功能性驅(qū)動(dòng)和低級(jí)的設(shè)備性驅(qū)動(dòng):
高級(jí)的功能性驅(qū)動(dòng):比如插入MSD類設(shè)備、移動(dòng)硬盤、藍(lán)牙適配器、HID類設(shè)備、網(wǎng)卡等具有實(shí)際功能性意義的設(shè)備時(shí),Windows底層會(huì)自動(dòng)的識(shí)別該設(shè)備的類和功能,從而找到一款能夠直接適配該設(shè)備功能的通用高級(jí)驅(qū)動(dòng),這樣就實(shí)現(xiàn)了目前Windows下多數(shù)陌生設(shè)備的免驅(qū)。
低級(jí)的設(shè)備性驅(qū)動(dòng):當(dāng)插入的設(shè)備極為特殊,Windows認(rèn)不出來(lái)這個(gè)玩意是干嘛的(比如設(shè)備描述符和接口描述符中的類字段與子類字段全部為0xff,即廠家定義),這個(gè)時(shí)候僅僅只是能夠進(jìn)行設(shè)備的基礎(chǔ)枚舉操作和通信。此時(shí)能夠支撐設(shè)備枚舉和通信的驅(qū)動(dòng)就是設(shè)備性驅(qū)動(dòng),比如libusb-win32或WinUSB等。
假如我現(xiàn)在插入一個(gè)U盤就開(kāi)始調(diào)用LibUSB庫(kù)進(jìn)行Open操作,這樣是一定會(huì)返回錯(cuò)誤的,因?yàn)長(zhǎng)ibUSB本身不是驅(qū)動(dòng),實(shí)際上也是要調(diào)用驅(qū)動(dòng)留出的系統(tǒng)調(diào)用接口進(jìn)行數(shù)據(jù)和指令傳遞的(這里類比linux的特性,表述可能些許不合適)。當(dāng)這個(gè)設(shè)備底層根本不是LibUSB需要的接口的時(shí)候,當(dāng)然是無(wú)法正常調(diào)用。
此時(shí)需要將該設(shè)備的驅(qū)動(dòng)換成合適的低級(jí)驅(qū)動(dòng)(linux的優(yōu)勢(shì)就出來(lái)了),LibUSB庫(kù)在Windows平臺(tái)上是基于WinUSB底層驅(qū)動(dòng)去開(kāi)發(fā)的。因此最適合LibUSB開(kāi)發(fā)的底層驅(qū)動(dòng)當(dāng)然是WinUSB。博主嘗試過(guò)使用linusb-win32等其他的驅(qū)動(dòng),他們可以open設(shè)備但是在claim接口的時(shí)候會(huì)有掉設(shè)備的情況(一claim就聽(tīng)到Windows掉USB設(shè)備的聲音,正常后不會(huì)這樣),直接導(dǎo)致transfer的時(shí)候返回LIBUSB_ERROR_IO(-1)。因此,在進(jìn)行Windows下的LibUSB開(kāi)發(fā)之前先解決你的驅(qū)動(dòng)問(wèn)題。
驅(qū)動(dòng)問(wèn)題的解決方式是通過(guò)zadig工具進(jìn)行驅(qū)動(dòng)替換,首先插入你的設(shè)備,list一下獲得設(shè)備名單,選擇你的設(shè)備,選擇winUSB驅(qū)動(dòng),replace即可。前提是要有網(wǎng)(公司的機(jī)器沒(méi)有網(wǎng),GG)。如圖(博主使用的SMT32USBD虛擬為com):
注意,使用此種方式他只會(huì)根據(jù)選擇設(shè)備的設(shè)備號(hào)進(jìn)行定向更換,這樣就產(chǎn)生了兩個(gè)問(wèn)題:第一,替換的效果只能適用同一VID和PID的設(shè)備。第二,被替換的設(shè)備從此以后只能使用這個(gè)低級(jí)驅(qū)動(dòng),原來(lái)高級(jí)驅(qū)動(dòng)的功能將不能使用。若不小心將重要設(shè)備的驅(qū)動(dòng)進(jìn)行重置和降級(jí),那就去zadig工具的官網(wǎng)上尋找答案吧(當(dāng)時(shí)無(wú)知把移動(dòng)硬盤驅(qū)動(dòng)給降級(jí)了,人都裂開(kāi)了)。
zadig官網(wǎng)與技術(shù)支持:
??????https://www.baidu.com/link?url=UuDz3G0f6UbanYtYLC8SIC6sDu-aTCuBlW1BkriYHqK&wd=&eqid=cd9600c2000f6899000000046358207b
Open_device過(guò)程
這一節(jié)咱們直接面向?qū)ο髞?lái)說(shuō):
- libusb_open_device_with_vid_pid或者open_device:打開(kāi)設(shè)備,得到一個(gè)libusb_device_handle,沒(méi)啥說(shuō)的,驅(qū)動(dòng)沒(méi)安好你就是開(kāi)不了,氣不氣。
- libusb_get_device:由libusb_device_handle結(jié)構(gòu)體反推libusb_device_descriptor結(jié)構(gòu)體用的(open_device的不用)。
- libusb_get_device_descriptor:獲取端點(diǎn)信息的第一步—獲取設(shè)備描述符。
- libusb_get_config_descriptor:獲取端點(diǎn)信息的第二步—獲取配置描述符。、
- 使用上一步的信息,通過(guò)一系列if篩選,找到你想要建立通信的備用接口和備用接口中的端點(diǎn)地址。
- libusb_claim_interface和libusb_set_interface_alt_setting:聲明接口和備用接口,傳入?yún)?shù)為索引值,上一步得到的。驅(qū)動(dòng)沒(méi)安好你就是claim不了(ret=-5),氣不氣。
- libusb_clear_halt:清除端點(diǎn)和對(duì)應(yīng)通道中的殘存的數(shù)據(jù),為下一次通信做好準(zhǔn)備。
- 該free的free,該release的release。
到此為止,正常情況下你就應(yīng)該得到一個(gè)已經(jīng)open的設(shè)備和一些(>=1)準(zhǔn)備傳輸?shù)亩它c(diǎn)。注意:第六第七步的順序千萬(wàn)不能倒過(guò)來(lái),否則你的libusb_clear_halt會(huì)報(bào)錯(cuò)LIBUSB_ERROR_NOT_FOUND (ret=-5)。而且在Windows開(kāi)發(fā)中,LibUSB中的detach_kernel系列的函數(shù)都是多余的,人家Windows的原理就不需要這些操作。
Transfer過(guò)程
博主使用的是libusb_bulk_transfer傳輸?shù)?,中間出了點(diǎn)小問(wèn)題,簡(jiǎn)要說(shuō)一下:
- libusb_bulk_transfer返回LIBUSB_ERROR_IO(-1)大概率是因?yàn)槟愕尿?qū)動(dòng)沒(méi)有裝對(duì),libusb0 (v2.6.0)的驅(qū)動(dòng)能夠open和claim不報(bào)錯(cuò),但是就是在傳輸?shù)臅r(shí)候報(bào)錯(cuò)。
- libusb_bulk_transfer返回LIBUSB_ERROR_NOT_FOUND (-5)大概率是因?yàn)槟鉩laim這一步?jīng)]有正常claim。注意如果你的端點(diǎn)出自備用接口,記得claim備用接口(claim標(biāo)準(zhǔn)接口好像是默認(rèn)claim該接口下索引為0的備用接口,保險(xiǎn)起見(jiàn)不管備用接口索引是幾,記得claim一下備用接口)。
貼一下程序(bulk)
main.c
#include <stdio.h>
#include <main.h>
#include <libusb.h>
#include "log.h"
#include <stdint.h>
#include "usb_device_opt.h"
const uint16_t VID = 0x1234;
const uint16_t PID = 0x1234;
//const uint16_t VID = 0x0bda;
//const uint16_t PID = 0x8179;
int main(void)
{
libusb_device **devs;
USB_MSD_ST msd = {NULL, NULL, 0x00, 0x00};
uint8_t err = 0;
int16_t dev_num;
Log_Init(3);
err = libusb_init(NULL);
if(err > 0)
{
LOG_ERROR("libusb_init() err with %d", err);
goto init_err;
}
//libusb_set_option(NULL, LIBUSB_OPTION_LOG_LEVEL , LIBUSB_LOG_LEVEL_INFO);
dev_num = libusb_get_device_list(NULL, &devs);
if(dev_num < 0)
{
LOG_ERROR("libusb_get_device_list() err with %d", err);
goto getlist_err;
}
USB_Dev_Scan_A_Print(dev_num, devs);
err = USB_MSD_Open(VID, PID, &msd);
if(err > 0)
{
USB_MSD_Close(&msd);
goto getlist_err;
}
uint8_t buff[512];
int size = 0;
memset(buff, 9, 512);
err = USB_MSD_Bulk_Write(&msd, buff, 64, &size, 1000);
if(err > 0)
{
USB_MSD_Close(&msd);
goto getlist_err;
}
else
{
LOG_INFO("Send OK!");
}
err = USB_MSD_Close(&msd);
getlist_err:
libusb_free_device_list(devs, 1);
init_err:
libusb_exit(NULL);
return 0;
}
?usb_device_opt.c
#include <stdio.h>
#include <libusb.h>
#include "log.h"
#include <stdint.h>
#include "usb_device_opt.h"
uint8_t USB_Dev_Scan_A_Print(int16_t dev_num, libusb_device **devs)
{
struct libusb_device_descriptor usb_dev_desc;
struct libusb_config_descriptor *usb_cfg_desc;
uint8_t err = 0;
LOG_INFO("Dev_Num:%d", dev_num);
for(uint16_t i = 0;i < dev_num; i++)
{
err = libusb_get_device_descriptor(devs[i], &usb_dev_desc);
if(err > 0)
{
LOG_ERROR("libusb_get_device_descriptor() err with %d", err);
goto getdevdesc_err;
}
printf("|--[Vid:0x%04x, Pid:0x%04x]-[Class:0x%02x, SubClass:0x%02x]-[bus:%d, device:%d, port:%d]-[cfg_desc_num:%d]\n",
usb_dev_desc.idVendor, usb_dev_desc.idProduct, usb_dev_desc.bDeviceClass, usb_dev_desc.bDeviceSubClass,
libusb_get_bus_number(devs[i]), libusb_get_device_address(devs[i]), libusb_get_port_number(devs[i]), usb_dev_desc.bNumConfigurations);
//printf()
for(uint8_t j = 0;j < usb_dev_desc.bNumConfigurations; j++)
{
err = libusb_get_config_descriptor(devs[i], j, &usb_cfg_desc);
if(err > 0)
{
LOG_ERROR("libusb_get_config_descriptor(cfg_index:%d) err with %d", j, err);
goto getcfgdesc_err;
}
printf("| |--cfg_desc:%02d-[cfg_value:0x%01x]-[infc_desc_num:%02d]\n",
j, usb_cfg_desc->bConfigurationValue, usb_cfg_desc->bNumInterfaces);
for(uint8_t l = 0;l < usb_cfg_desc->bNumInterfaces; l++)
for(uint8_t n = 0;n < usb_cfg_desc->interface[l].num_altsetting; n++)
{
printf("| | |--intfc_desc: %02d:%02d-[Class:0x%02x, SubClass:0x%02x]-[ep_desc_num:%02d]\n",
l, n, usb_cfg_desc->interface[l].altsetting[n].bInterfaceClass, usb_cfg_desc->interface[l].altsetting[n].bInterfaceSubClass,
usb_cfg_desc->interface[l].altsetting[n].bNumEndpoints);
for(uint8_t m = 0;m < usb_cfg_desc->interface[l].altsetting[n].bNumEndpoints; m++)
{
printf("| | | |--ep_desc:%02d-[Add:0x%02x]-[Attr:0x%02x]-[MaxPkgLen:%02d]\n",
m, usb_cfg_desc->interface[l].altsetting[n].endpoint[m].bEndpointAddress,
usb_cfg_desc->interface[l].altsetting[n].endpoint[m].bmAttributes,
usb_cfg_desc->interface[l].altsetting[n].endpoint[m].wMaxPacketSize);
}
}
}
}
return 0;
getdevdesc_err:
return 0xff;
getcfgdesc_err:
return 0xff;
}
uint8_t USB_MSD_Open(uint16_t VID, uint16_t PID, USB_MSD_ST *msd)
{
struct libusb_device_descriptor usb_dev_desc;
struct libusb_config_descriptor *usb_cfg_desc;
uint8_t intfc_index = 0;
uint8_t err = 0;
msd->msd_handle = libusb_open_device_with_vid_pid(NULL, VID, PID);
if(msd->msd_handle == NULL)
{
LOG_ERROR("[0x%04x:0x%04x] MSD Open failed!", VID, PID);
goto opendev_err;
}
msd->msd_dev = libusb_get_device(msd->msd_handle);
if(msd->msd_dev == NULL)
{
LOG_ERROR("[0x%04x:0x%04x] get dev failed!", VID, PID);
goto getdev_err;
}
err = libusb_get_device_descriptor(msd->msd_dev, &usb_dev_desc);
if(err > 0)
{
LOG_ERROR("[0x%04x:0x%04x] get dev_desc failed err with %d", VID, PID, err);
goto getdevdesc_err;
}
err = libusb_get_config_descriptor(msd->msd_dev, 0, &usb_cfg_desc);
if(err > 0)
{
LOG_ERROR("[0x%04x:0x%04x] get cfg_desc failed err with %d", VID, PID, err);
goto getcfgdesc_err;
}
for(uint8_t m = 0;m < usb_cfg_desc->bNumInterfaces; m++)
{
for(uint8_t n = 0;n < usb_cfg_desc->interface[m].num_altsetting;n++)
{
if(usb_cfg_desc->interface[m].altsetting[n].bInterfaceClass == 0x0a && usb_cfg_desc->interface[m].altsetting[n].bInterfaceSubClass == 0x00)
{
for(uint8_t i = 0;i < usb_cfg_desc->interface[m].altsetting[n].bNumEndpoints;i++)
{
if((usb_cfg_desc->interface[m].altsetting[n].endpoint[i].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK)
{
if((usb_cfg_desc->interface[m].altsetting[n].endpoint[i].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN)
{
msd->endpoint_in = usb_cfg_desc->interface[m].altsetting[n].endpoint[i].bEndpointAddress;
}
if((usb_cfg_desc->interface[m].altsetting[n].endpoint[i].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_OUT)
{
msd->endpoint_out = usb_cfg_desc->interface[m].altsetting[n].endpoint[i].bEndpointAddress;
}
}
}
if(msd->endpoint_in != 0x00 && msd->endpoint_out != 0x00)
{
intfc_index = m;
}
else
{
msd->endpoint_in = 0x00;
msd->endpoint_out = 0x00;
}
}
}
}
if(msd->endpoint_in == 0x00 || msd->endpoint_out == 0x00)
{
LOG_ERROR("[0x%04x:0x%04x] get ep_addr failed!", VID, PID);
goto getepaddr_err;
}
err = libusb_claim_interface(msd->msd_handle, 1);
err = libusb_set_interface_alt_setting(msd->msd_handle, 1, 0);
err = libusb_claim_interface(msd->msd_handle, 0);
err = libusb_set_interface_alt_setting(msd->msd_handle, 0, 0);
if(err > 0)
{
LOG_ERROR("[0x%04x:0x%04x] claim intfc failed err with %d", VID, PID, err);
goto claimintfc_err;
}
err = libusb_clear_halt(msd->msd_handle, msd->endpoint_out);
if(err > 0)
{
LOG_ERROR("[0x%04x:0x%04x] ep_out:%x clear halt failed err with %d", VID, PID, msd->endpoint_out, (int8_t)err);
goto epclrhalt_err;
}
err = libusb_clear_halt(msd->msd_handle, msd->endpoint_in);
if(err > 0)
{
LOG_ERROR("[0x%04x:0x%04x] ep_in:%x clear halt failed err with %d", VID, PID, msd->endpoint_in, (int8_t)err);
goto epclrhalt_err;
}
libusb_free_config_descriptor(usb_cfg_desc);
libusb_reset_device(msd->msd_handle);
LOG_INFO("[0x%04x:0x%04x]-[EP_IN:0x%02x, EP_OUT:0x%02x] Open success!", VID, PID, msd->endpoint_in, msd->endpoint_out);
return 0;
//óD?êìa
claimintfc_err:
libusb_release_interface(msd->msd_handle, 1);
libusb_release_interface(msd->msd_handle, 0);
detachkernel_err:
epclrhalt_err:
getepaddr_err:
getcfgdesc_err:
getdevdesc_err:
getdev_err:
opendev_err:
libusb_free_config_descriptor(usb_cfg_desc);
libusb_close(msd->msd_handle);
return 0xff;
}
uint8_t USB_MSD_Close(USB_MSD_ST *msd)
{
if(msd->msd_handle != NULL)
{
libusb_release_interface(msd->msd_handle, 0);
libusb_close(msd->msd_handle);
}
return 0;
}
uint8_t USB_MSD_Bulk_Write(USB_MSD_ST* msd, uint8_t* buffer, int len, int* size, uint32_t ms)
{
int err = 0;
err = libusb_bulk_transfer(msd->msd_handle, msd->endpoint_out, buffer, len, size, ms);
if (err < 0)
{
LOG_ERROR("Write:%d", err);
return -1;
}
return 0;
}
uint8_t USB_MSD_Bulk_Read(USB_MSD_ST* msd, uint8_t* buffer, int len, int* size, uint32_t ms)
{
int err = 0;
err = libusb_bulk_transfer(msd->msd_handle, msd->endpoint_in, buffer, len, size, ms);
if (err < 0)
{
LOG_ERROR("Read:%d", err);
return -1;
}
return 0;
}
usb_device_opt.h文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-836972.html
#ifndef _USB_DEVICE_OPT_
#define _USB_DEVICE_OPT_
#include <stdio.h>
#include <libusb.h>
#include "log.h"
#include <stdint.h>
typedef struct USB_MSD
{
libusb_device* msd_dev;
libusb_device_handle* msd_handle;
uint8_t endpoint_in;
uint8_t endpoint_out;
} USB_MSD_ST;
extern uint8_t USB_Dev_Scan_A_Print(int16_t dev_num, libusb_device **devs);
extern uint8_t USB_MSD_Open(uint16_t VID, uint16_t PID, USB_MSD_ST *msd);
extern uint8_t USB_MSD_Bulk_Write(USB_MSD_ST* msd, uint8_t* buffer, int len, int* size, uint32_t ms);
extern uint8_t USB_MSD_Bulk_Read(USB_MSD_ST* msd, uint8_t* buffer, int len, int* size, uint32_t ms);
extern uint8_t USB_MSD_Close(USB_MSD_ST *msd);
#endif // _USB_DEVICE_OPT_
Good Night!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-836972.html
到了這里,關(guān)于USB系列-LibUSB使用指南(1)-Windows下的報(bào)錯(cuò)與踩坑的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!