[原创] [首发]边干边写之——USB设备开发过程解析(前台VB+驱动VC+固件GCC)[声明:] 1、本文为开发工作过程中的心得体会,认识粗浅表述不周之处请见谅; 2、本文内容供广大爱好者学习交流之用,如需转载请注明出处并告知本人。
KKND 08年11月21日清晨 发表于 VBGood 论坛
******************************************************************************
[正文]
其实弄清楚USB的工作流程后 开发USB设备是很简单的事情。。。
简单来说无非是这样的过程:
开发设备硬件 -> 编写设备固件程序 -> 编写驱动程序 -> 开发应用程序
其中后3项主要是软件编程工作,也是本文讨论的重点。
应用程序与USB硬件设备通信自底向上需要完成三部分程序的开发:
固件程序 <-USB总线驱动程序-> 设备驱动程序 <-系统I/O管理器-> 用户应用程序
以下分别将这三部分程序中用于实现通信的核心代码加以介绍。
固件程序:
固件程序也就是所谓的“下位机”程序,它运行于设备上,由设备上的单片机执行,用于控制USB接口芯片与主机进行通信。 开发环境:GCC + AVR Studio
…… /****************************************************************** * USB中断处理,该中断由USB接口芯片产生,由下位机CPU处理 ******************************************************************/ void usb_isr(void) { …… if ( D12_int_flags & D12_INT_ENDP0OUT ) control_out_handler(); // 产生USB控制端点接收中断时调用该函数 …… }
/****************************************************************** * 处理USB控制端点接收事件 ******************************************************************/ void control_out_handler( void ) { …… control_dispatcher(); // 调用分发处理函数 }
/****************************************************************** * 分发处理,根据 URB 的请求类型和请求号选择相应的处理函数 ******************************************************************/ void control_dispatcher( void ) { uchar type, req;
type = ControlData.DeviceRequest.bmRequestType & USB_REQUEST_TYPE_MASK; req = ControlData.DeviceRequest.bRequest;
……
if ( type == USB_VENDOR_REQUEST ) // 处理用户自定义请求 { // 根据请求号选择请求处理函数表入口 if ( req < NUMBER_VENDOR_REQ ) (*VendorDeviceRequest[req])(); }
…… }
/****************************************************************** * 用于处理用户请求的函数入口表 ******************************************************************/ const pfnvoid VendorDeviceRequest[] = { fun0, fun1, fun2, …… };
/****************************************************************** * 用户请求处理函数实现 ******************************************************************/ void fun0(void) { uint8_t txdata[LEN]; …… single_transmit(txdata, LEN); }
……
[未完待续]
[ 本帖最后由 DreamonII 于 2008-11-21 19:31 编辑 ] |
- 3
评分次数
我是坏蛋我怕谁
http://www.zaoyao.com/notfound.htm |
| | | | |
  
- 帖子
- 3760
- 精华
- 2
- 威望
- 1676
- 擂点
- 0
- 人气
- 332
- 注册时间
- 2007-9-1

| 2楼 发表于 2008-11-21 05:58 | 只看该作者 [续1]
驱动程序: 目前Windows系统下的驱动程序通常采用WDM模型(Win32 Driver Model,Win32驱动模型),采用Microsoft提供的DDK开发,借助第三方驱动程序开发工具比如 DriverStudio 可以大大简化开发过程。 驱动程序的概念和作用不是本文讨论的内容。利用DriverWorks生成USB设备驱动程序框架,然后自行编写IOCTL处理过程即可以很容易地编写出USB设备程序。以下结合前文中的设备给出核心代码示例。
开发环境:VC6 + DDK + DriverStudio 这里有两个概念比较重要,一个是IRP(I/O请求包),一个是URB(USB 请求块)。具体解释请查阅相关资料。 - // 处理应用程序通过由I/O管理器发送给驱动程序的 IRP
- NTSTATUS TestDevice::DeviceControl(KIrp I)
- {
- ……
- switch (I.IoctlCode())
- {
- case IOCTL_FUN0:
- status = IOCTL_FUN0_Handler(I); // 根据IRP中的信息选择处理函数
- break;
- case IOCTL_FUN1:
- status = IOCTL_FUN1_Handler(I);
- break;
- ……
- }
- ……
- }
- // 具体的功能处理函数,具体调用的功能参见前文固件程序中的相应功能函数
- NTSTATUS TestDevice::IOCTL_FUN0_Handler(KIrp I)
- {
- PURB pUrb;
- ……
- pUrb = m_Lower.BuildVendorRequest(
- (unsigned char *)I.IoctlBuffer(), // transfer buffer
- I.IoctlInputBufferSize(), // transfer buffer size
- 0, // RequestTypeReservedBits
- 0, // Request
- 0, // Value
- TRUE, // bIn
- TRUE, // bShortOK
- NULL, // Link
- 0, // Index
- URB_FUNCTION_VENDOR_DEVICE // Function
- );
-
- ……
- status = m_Lower.SubmitUrb(pUrb, NULL, NULL,300); // 将URB发送给下层驱动程序(USB总线驱动程序)
- ……
- }
复制代码 这里有必要深入说明一下,前文中所说的通信过程是简化了的通信过程, 实际上在整个通信过程中还至少要有两部分参与:USB总线驱动程序和系统I/O管理器,但这两部分都是由操作系统提供的,无需编写程序,对用户透明。 其中系统I/O管理器由用户应用程序调用,用于在用户编写的应用程序与驱动程序之间通信(通过传递IRP的方式); 而USB总线驱动程序由用户编写的设备驱动程序调用,它是系统中所有USB驱动程序的基础,负责对USB主控制器的操作,完成实际的数据传输。从这一点上看,用户驱动程序实际上只是根据应用程序的IRP构造URB,然后调用下层的总线驱动程序。
完整的WDM型USB驱动程序体系结构如下图所示:
[未完待续]
[ 本帖最后由 DreamonII 于 2008-11-21 06:38 编辑 ] |
附件: 您所在的用户组无法下载或查看附件 - 2
评分次数
我是坏蛋我怕谁
http://www.zaoyao.com/notfound.htm |
| | | | |
  
- 帖子
- 3760
- 精华
- 2
- 威望
- 1676
- 擂点
- 0
- 人气
- 332
- 注册时间
- 2007-9-1

| 3楼 发表于 2008-11-21 06:19 | 只看该作者 [续2]
应用程序: 开发应用程序可以采用多种开发语言,比如VC,VB等。这里以VB为例介绍一下应用程序与驱动程序通信的实现方法。
开发环境:VB6 SP5 + Win32 API - Private Sub Form_Load()
- ' 生成控制码(用于构造 IRP)
- IOCTL_TTS_FUN0 = CTL_CODE(FILE_DEVICE_LPTTS, TTS_IOCTL_INDEX + 0, METHOD_BUFFERED, FILE_READ_ACCESS)
- IOCTL_TTS_FUN1 = CTL_CODE(FILE_DEVICE_LPTTS, TTS_IOCTL_INDEX + 1, METHOD_BUFFERED, FILE_WRITE_ACCESS)
- ……
- End Sub
- ' 用于生成 CTL_CODE 的函数
- Public Function CTL_CODE(DeviceType As Long, _
- Functions As Long, _
- Method As Long, _
- Access As Long) _
- As Long
- CTL_CODE = DeviceType * 2 ^ 16 Or _
- Access * 2 ^ 14 Or _
- Functions * 2 ^ 2 Or _
- Method
- End Function
- ' 通信控制
- Private Sub Command1_Click()
- ……
- If (OpenDevice(HDevice, DevicePathName) = True) Then
- bResult = DeviceIoControl( _
- HDevice, _
- IOCTL_FUN0, _
- 0, _
- 0, _
- ReData(0), _
- LEN, _
- nBytes, _
- 0)
-
- If (bResult = True) Then
- Text1.Text = Hex(ReData(0)) & Hex(ReData(1)) ...
- Else
- Text1.Text = "与USB设备通信失败!" + vbCrLf + "USB数据传输错误,请重新启动硬件!"
- End If
- CloseHandle (HDevice)
- End If
- End Sub
复制代码 从代码中不难看出,应用程序与驱动程序通信主要是通过API调用系统I/O管理器实现的。 文中所涉及到的常量定义和API函数作用请自行查阅相关资料。
本文键接:http://www.vbgood.com/viewthread.php?tid=77363&page=1&extra=page%3D1#pid390393
[全文完]
|
|
[连载1] 费了九牛二虎的力气,终于把驱动调通了,前后整整一个月时间,有种欲哭无泪的感觉。。。
泡饭老版曾发过VB开发USB相关资料的的主题贴,当初看的时候完全是一头雾水。经过一个月的痛苦煎熬,终于领会了其中奥妙之处。。。
虽然现在VB部分的前台应用程序还没开始动手写,但前期设计硬件和开发驱动程序的部分也是开发人员难以回避的问题,因此想先挖个坑,把从写驱动开始直到完成VB前台的过程中遇到的问题和心得记录下来,供日后有需要的坛友参详。。。
进入正题吧。。。
硬件的设计这里就不具体说了,简单介绍一下配置CPU和接口芯片:
CPU:ATMega128 16AU 主频:14.7456MHz USB接口:PDIUSBD12
主要功能和资源如下: 4路高速AD(12bit,200KSPS),提供24V电源,可直接连接传感器; 8路PWM(带驱动,>2A/通道)可驱动电动机等; 16路开关量输出(带驱动>0.2A/通道),可驱动小型继电器等器件; 3*8位并行I/O(其中16路可作外部中断,8路可作10位低速A/D输入),可用于多机通讯或键盘/显示接口等; 2路频率/计数输入,可接转速或流量传感器等; 2路旋转编码器接口; 1个USB1.1接口; 1个COM接口; 2个ISP分别对应主/协处理器,工作时可用作外部SPI接口; 驱动电路提供双电压选择(<=12V),提供两路电源接口和一个电源选择控制接口;
两CPU以USART相连,也可通过外部SPI或并行总线相连,板上实现双机通信功能。 可多板级连实现扩展。
硬件电路如图:
 自制的全套开发套件如图:

[连载2] 要开发一个完整的USB设备,需要制定一个完整的开发计划,以本文所述的电路为例,大致有如下步骤:
硬件部分: 功能需求分析->器件选型->电路结构设计->电路原理图设计->PCB设计->PCB制板->
软件部分:
按照常规的设计方法,软件部分设计步骤如下:
固件程序设计->驱动程序设计->应用程序设计
但考虑到前台应用程序要使用VB语言实现,并且要为用户提供二次开发的接口,因此采用了如下开发步骤:
固件程序设计(WinAVR 4)->驱动程序设计(DriverWorks+DDK+VC6)->SDK开发接口设计(VC6)->应用程序设计(VB6)
目前的工作进行到固件程序和驱动程序的开发环节。 由于采用了USB接口,因此这里说的驱动开发就是USB设备驱动程序的开发。
查阅了大量的资料,了解了USB协议的基本原理,知道了USB通信的大致过程,遍查网络,总算找到了AVR+D12固件的例子。几经周折写出了能实现基本通讯功能的固件程序。于是开始写驱动。
安装DDK,DriverStudio3.2(过程及注意事项网上N多,从略)。
用DriverWizard创建了一个USB设备驱动程序的框架,修改了VID和PID,定义了接口和端点,参照示例修改了框架里一些代码,拼凑出一个基本的驱动程序,编译成SYS文件。
把自动生成的inf文件和编译得到的SYS文件复制到文件夹里,取名“Driver”。
插入设备,提示发现新硬件,安装驱动,设备无法启动。。。 郁闷,没有任何调试工具,根本不知道哪个环节出了问题。。。
忽然想到板子上有RS-232口,不如用它来反馈工作过程。于是修改固件程序,加入USART通信代码,并在控制USB通信的每个步骤中向串口发送调试信息。
由于串口速度相对较慢,担心发送详细文字描述信息会影响USB工作过程的正常进行(事实证明确实如此),因此定义了2~3个字母的代码方式,并设计了一个代码对照表,详见附件。
串口调试可采用本版坛友提供的串口调试工具。
[待续]
|
[连载3]
固件修改完后,下载到电路板。
打开串口调试软件,把电路板上的串口连到PC,插上USB线。。。
得到的调试信息如下(请参照前文附件中的《固件调试信息对照表》):
LP-TransTest 3. '设备正常启动,显示设备名 GD-DD 'get_descriptor()->USB_DEVICE_DESCRIPTOR_TYPE SA-1 'set_address()->1 GD-DD 'get_descriptor()->USB_DEVICE_DESCRIPTOR_TYPE GD-DD 'get_descriptor()->USB_DEVICE_DESCRIPTOR_TYPE GD-CD 'get_descriptor()->USB_CONFIGURATION_DESCRIPTOR_TYPE GD-SD-SL 'get_descriptor()->USB_STRING_DESCRIPTOR_TYPE->UNICODE_LANGUAGE_STR_ID GD-SD-SS 'get_descriptor()->USB_STRING_DESCRIPTOR_TYPE->SERIAL_NUM_STR_ID GD-CD 'get_descriptor()->USB_CONFIGURATION_DESCRIPTOR_TYPE GD-SD-SL 'get_descriptor()->USB_STRING_DESCRIPTOR_TYPE->UNICODE_LANGUAGE_STR_ID GD-SD-SP 'get_descriptor()->USB_STRING_DESCRIPTOR_TYPE->PRODUCT_NAME_STR_ID GD-SD-SL 'get_descriptor()->USB_STRING_DESCRIPTOR_TYPE->UNICODE_LANGUAGE_STR_ID GD-SD-SP 'get_descriptor()->USB_STRING_DESCRIPTOR_TYPE->PRODUCT_NAME_STR_ID GD-DD 'get_descriptor()->USB_DEVICE_DESCRIPTOR_TYPE GD-CD 'get_descriptor()->USB_CONFIGURATION_DESCRIPTOR_TYPE SC-0-0 set_configuration()->0->0 ?原来是这里出了问题。正常情况应该是SC-1-1
不了解USB协议和USB通信过程的朋友估计已经一头雾水了。没关系,转一个有用的资料给大家看看,这就叫理论联系实际。。。
**********[转载开始]***********
[转载] USB枚举详细过程剖析
(1)集线器检测新设备 主机集线器监视着每个端口的信号电压,当有新设备接入时便可觉察。(集线器端口的两根信号线的每一根都有15kΩ的下拉电阻,而每一个设备在D+都有一个1.5kΩ的上拉电阻。当用USB线将PC和设备接通后,设备的上拉电阻使信号线的电位升高,因此被主机集线器检测到。) (2)主机知道了新设备连接后 每个集线器用中断传输来报告在集线器上的事件。当主机知道了这个事件,它给集线器发送一个Get_Status请求来了解更多的消息。返回的消息告诉主机一个设备是什么时候连接的。 (3)集线器重新设置这个新设备 当主机知道有一个新的设备时,主机给集线器发送一个Set_Feature请求,请求集线器来重新设置端口。集线器使得设备的USB数据线处于重启(RESET)状态至少10ms。 (4)集线器在设备和主机之间建立一个信号通路 主机发送一个Get_Status请求来验证设备是否激起重启状态。返回的数据有一位表示设备仍然处于重启状态。当集线器释放了重启状态,设备就处于默认状态了,即设备已经准备好通过Endpoint 0 的默认流程响应控制传输。即设备现在使用默认地址0x0与主机通信。 (5)集线器检测设备速度 集线器通过测定那根信号线(D+或D-)在空闲时有更高的电压来检测设备是低速设备还是全速设备。(全速和高速设备D+有上拉电阻,低速设备D-有上拉电阻)。
以下,需要USB的firmware进行干预 [注:]上文中的调试信息就是从这里开始的。。。 (6)获取最大数据包长度 PC向address 0发送USB协议规定的Get_Device_Descriptor命令[GD-],以取得却缺省控制管道所支持的最大数据包长度,并在有限的时间内等待USB设备的响应,该长度包含在设备描述符[DD]的bMaxPacketSize0字段中,其地址偏移量为7,所以这时主机只需读取该描述符的前8个字节。注意,主机一次只能列举一个USB设备,所以同一时刻只能有一个USB设备使用缺省地址0。 以下操作雷同,不同操作系统设定时延是不一样的,比如说win2k大概是几毫秒,如果没有反应就再发送一次命令,重复三次。
(7)主机分配一个新的地址给设备 主机通过发送一个Set_Address请求来分配一个唯一的地址给设备[SA-1]。设备读取这个请求,返回一个确认,并保存新的地址。从此开始所有通信都使用这个新地址。
(8)主机向新地址重新发送Get_Device_Descriptor命令,此次读取其设备描述符的全部字段[GD-DD],以了解该设备的总体信息,如VID,PID。
(9)主机向设备循环发送Get_Device_Configuration命令[GD-CD],要求USB设备回答,以读取全部配置信息。
(10)主机发送Get_Device_String命令[GD-SD-],获得字符集描述(unicode),比如产商[SM]、产品描述[SP]、型号、序列号[SS]等等。
(11)如果没有安装驱动程序,此时主机将会弹出窗口,展示发现新设备的信息,产商、产品描述、型号等。
(12)根据Device_Descriptor和Device_Configuration应答,PC判断是否能够提供USB的Driver,一般win2k能提供几大类的设备,如游戏操作杆、存储、打印机、扫描仪等,操作就在后台运行。但是Win98却不可以,所以在此时将会弹出对话框,索要USB的Driver。
(13)加载了USB设备驱动以后,主机发送Set_Configuration(x)[SC-]命令请求为该设备选择一个合适的配置(x代表非0的配置值[SC-0-0???])。如果配置成功,USB设备进入“配置”状态,并可以和客户软件进行数据传输。
此时,常规的USB完成了其必须进行的配置和连接工作。查看注册表,能够发现相应的项目已经添加完毕,至此设备应当可以开始使用。不过,USB协议还提供了一些用户可选的协议,设备如果不应答,也不会出错,但是会影响到系统的功能。
*******[转载完毕]**********
看到了吧,第13步,“主机发送Set_Configuration(x)命令请求为该设备选择一个合适的配置(x代表非0的配置值)”。。。 也就是说,正常情况下,反馈的信息应该是 SC-1-1,而不应该是SC-0-0(第一个0表示Set_Configuration(x)中的x,第二个0表示固件接受了配置0)。
看样子问题不在固件,而在驱动程序。。。
[待续]
|
[连载4] 俗话说没病不死人。。。原来是个低级错误。。。
修改好后,编译SYS文件。。。
卸掉出错的驱动,刷新。。。 发现新硬件->安装驱动->设备正常工作。。。
哈!成功了。。。
以下把几种情况下采集到的工作流程贴出来,供大家参考。具体含义就不逐条翻译了,请参照前文附件中的《固件调试信息对照表》。
[情况1]驱动未安装,设备插入:
LP-TransTest 3. GD-DD SA-C2 GD-DD GD-DD GD-CD GD-SD-SL GD-SD-SS GD-CD GD-SD-SL GD-SD-SP GD-SD-SL GD-SD-SP * 这里停住了,系统提示发现新硬件,需要安装驱动程序 * 安装驱动后 GD-SD-SL GD-SD-SP GD-SD-SL GD-SD-SP GD-DD GD-CD SC-1-1 '终于成功了! GD-DD GD-SD-SL GD-SD-SL GD-SD-SM GD-SD-SM GD-SD-SP GD-SD-SP GD-SD-SS GD-SD-SS * 设备正常工作
[情况2]驱动已安装,设备重新插入: LP-TransTest 3. GD-DD SA-C2 GD-DD GD-DD GD-CD GD-SD-SL GD-SD-SS GD-CD GD-SD-SL GD-SD-SP GD-SD-SL GD-SD-SP GD-DD GD-CD SC-1-1 GD-DD GD-SD-SL GD-SD-SL GD-SD-SM GD-SD-SM GD-SD-SP GD-SD-SP GD-SD-SS GD-SD-SS
[情况3]驱动卸载后,扫描检测硬件改动->安装驱动程序: GD-DD SA-C2 GD-DD GD-DD GD-CD GD-DD GD-CD SC-1-1 GD-DD GD-SD-SL GD-SD-SL GD-SD-SM GD-SD-SM GD-SD-SP GD-SD-SP GD-SD-SS GD-SD-SS
由于条件有限(搞AVR连个仿真器都没有),只能用这样的笨办法调试了。实验中曾多次出现安装驱动、卸载驱动或插拔设备时死机甚至重启现象,而换另一台PC就没问题。郁闷一下。。。
先写到这吧,工作一会,有空继续写。。。
[待续]
[连载5] 基本的固件和驱动程序框架已经建好并测试成功后,参照网上找的样例程序写了一个MFC应用程序,通过调用驱动程序操作设备。相对来说,这部分是相当简单的了。 没费什么力气便通过了测试,但仅仅这样离我的预期目标还差了很远。
下一步的开发思路是:创建自己的API,并制定调用接口规范,这样就可以从VB程序中调用这个API实现对硬件的控制。 其实就是用VC创建一个DLL,把针对硬件的操作函数都封装在里面。
现在还有些问题没想清楚,比如若硬件主动发送数据而产生了中断,要怎么样设计DLL函数才能第一时间通知应用程序等等。 相信办法总会有的,创建了DLL框架,把刚刚测试好的设备类、接口定义和控制接口声明都移植过来,下一步该写具体的功能函数了。说干就干!
|
[连载6] 写DLL没有思路,试了N次都进展不下去了,换换思路。 VB应该也能够通过调用CreateFile等API函数直接访问设备驱动
程序。 上网查了一大圈,只找到了几个HID设备访问的例子(见附件) 那都是懒得写驱动程序的人才会用的方法,不甘心。。。
一行一行读下去。。。
第一步要先取得设备的GUID,
Public Type GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(7) As Byte End Type
示例中用 HidD_GetHidGuid(),而我的设备不是HID设备类型,
取不到。
自己填充一个GUID结构来代替它:
With TTSGuid .Data1 = &HXXXXXXXX .Data2 = &HXXXX .Data3 = &HXXXX .Data4(0) = &HXX .Data4(1) = &HXX .Data4(2) = &HXX .Data4(3) = &HXX .Data4(4) = &HXX .Data4(5) = &HXX .Data4(6) = &HXX .Data4(7) = &HXX End With
按下F5。。。 奇怪,没取到设备路径。。。见附图。。。
[待续] |
[连载7] CreateFile需要设备路径作为参数,那么我先想个不用GUID也能找到设备路径的办法应急。 打开DriverStudio中的Symbolic Link Viewer,按照VID和PID找到我的设备路径(见附图1) 奇怪,怎么会有两个呢?全部抄下来。
示例中DevicePathName是通过 SetupDiGetDeviceInterfaceDetail()取得的,如下: Result = SetupDiGetDeviceInterfaceDetail _ (DeviceInfoSet, _ TTSDeviceInterfaceData, _ VarPtr(DetailDataBuffer(0)), _ DetailData, _ Needed, _ 0)
DevicePathName = CStr(DetailDataBuffer()) DevicePathName = StrConv(DevicePathName, vbUnicode) DevicePathName = Right$(DevicePathName, Len(DevicePathName) - 4)
可取出来的一个空串(见附图2)。。。
不管它,在后面强制指字我自己的设备路径。。。
DevicePathName = "\\?\USB#Vid_XXXX&Pid_XXXX#XXXXXXXX#{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"
然后调用CreateFile() 。。。
TTSDevice = CreateFile _ (DevicePathName, _ GENERIC_READ Or GENERIC_WRITE, _ (FILE_SHARE_READ Or FILE_SHARE_WRITE), _ 0, _ OPEN_EXISTING, _ 0, _ 0) 再试一下。。。
成功了!(见附图3)
没病不死人,不知道是哪里出了问题,先不管它,设备打开了,下一步就是测试读写操作。 取不出设备路径的错误留到以后再解决。。。
[待续] |
[连载8] 电磁炉又坏了。。。二手市场买的,冒牌货。。。 由于煮粥煮过了火,炉盘炸裂。表面是一层钢化玻璃,已经裂成无数的颗粒。。。 不知道是疏忽还是故意节约成本,居然省去了过热保护的传感器!(一个热敏电阻,大概2毛钱)
又要饿肚子了。。。郁闷一下。。。
居然用了两天才搞定读写功能,现在VB程序终于能访问设备了。。。 现在看看真的很简单,可是想通这点事居然用掉了整整一天! 害我用VB重写了全部C++示例程序。。。
关键代码如下:
HTTS = CreateFile( _ TTSDevicePathName, _ GENERIC_READ Or GENERIC_WRITE, _ FILE_SHARE_READ Or FILE_SHARE_WRITE, _ 0, _ OPEN_EXISTING, _ FILE_ATTRIBUTE_NORMAL, _ 0)
vResult= DeviceIoControl( _ HTTS, _ IOCTL_TTS_GET_DEVICE_DESCRIPTOR, _ '取得设备描述符 0, _ 0, _ bData(0), _ 18, _ '设备描述符的长度(12H) bLength, _ 0)
读出的内容被存放在bData()中。。。
哭死。。。
修电磁炉去。。。
[待续]
|
|