在前面的文章中,我们已经给出了USB协议的链接地址,从这篇文章起,我们会涉及到许多USB 1.1的内容,我们的指导思想是先从熟悉USB 1.1协议入手,先使用现成的HCD和USBD,直接面对客户端驱动编程,尽快看到成果,使读者对USB的开发充满信心,进而去研究USBD和HCD的编程方法。请读者自行阅读协议,文章中有关协议的详细情况,由于会涉及非常多的文字,恕不能过多解释。
1、USB系统主机端的软件结构
    一般来说,教科书或者协议上都会把USB主机端的软件说成有三层,第一层叫主机控制器驱动程序HCD(Host Controller Driver),第二层叫USB驱动程序USBD(USB Driver),第三层叫客户端驱动程序(Client Driver);实际上,我们实际看到的东西,往往HCD和USBD是由一个程序完成的,比如windows就提供了HCD和USBD,如果你自己开发了一个USB设备,只需要在HCD和USBD上面开发一个客户端驱动程序即可;linux也是同样,linux内核已经提供了HCD和USBD;所以在windows和linux下我们基本上没有开发HCD和USBD的必要,而且linux还提供源代码;但DOS就不一样了,DOS本身对USB没有任何支持,所以要想在DOS下彻底玩转USB,需要研究HCD、USBD和客户端驱动程序。
2、DOSUSB介绍
    很显然,HCD和USBD更加底层一些,需要理解的东西也更多一些;如果我们能够绕过HCD和USBD,直接从客户端驱动程序入手,将会容易许多。幸运的是我们可以找到一个免费的DOS下的USB驱动程序,叫DOSUSB,该驱动程序实现了大部分的HCD和USBD的功能,使我们进行USB编程的好帮手。
    DOSUSB目前还没有实现EHIC的驱动,也就是说还不支持USB2.0,这也是我们从USB 1.1开始的原因之一,另一方面,由于USB2.0是兼容USB1.1的,所以,即便你在USB2.0的设备下,仍然可以使用USB1.1的驱动程序,只不过不能实现480MB/秒的传送速度而已。
    下面我们介绍一下DOSUSB。DOSUSB的官方网站如下:
    http://www.usbdos.net

    可以从其官方网站上下载DOGUSB的最新版本,当前版本是1.1.1。或者在下面在下面网址下载这个版本的DOSUSB。

    http://blog.hengch.com/software/dosusb/dosusb.zip

    DOSUSB可以在非商业领域免费使用,如果肯花费费用,可以购买到源代码,从其官方网站的论坛上看到,在2006年9月作者开出的源代码的价格是1000欧元。

    DOSUSB的安装十分简便,只需要解压缩到某一个目录下即可,比如放在c:\dosusb目录下,请自行阅读DOSUSB自带的文档,使用也非常简单,在DOS提示符下键入dosusb执行即可。

    c:\dosusb>dosusb

    缺省情况下,DOSUSB使用int 65h作为其驱动的调用软中断,如果和你的系统有冲突,在运行dosusb时可以加参数/I,请自行阅读DOSUSB的文档。

    DOSUSB通过一个叫做URB(USB Request Block)的数据结构与客户端驱动程序进行通讯,这一点和linux非常相似,估计作者参考了linux下的源代码,在DOSUSB文档里给出了这个结构的定义,如下:
    struct {
      BYTE  transaction_type;   // 设置事务(控制传输)(2Dh),输入事务(69h)输出事务(E1h)
      BYTE  chain_end_flag;     // 备用
      BYTE  dev_add;            // 设备地址
      BYTE  end_point;          // 端点号
      BYTE  error_code;         // 错误吗
      BYTE  status;             // 设备状态
      WORD  transaction_flags;  // 备用
      WORD  buffer_off;         // 接收/发送缓冲区偏移地址
      WORD  buffer_seg;         // 接收/发送缓冲区段地址
      WORD  buffer_length;      // 接收/发送缓冲区长度
      WORD  actual_length;      // 接收/发送时每个包的最大长度
      WORD  setup_buffer_off;   // setup_request结构的偏移地址
      WORD  setup_buffer_seg;   // setup_request结构的段地址
      WORD  start_frame;        // 备用
      WORD  nr_of_packets;      // >0时会启动实时传输
      WORD  int_interval;       // 备用
      WORD  error_count;        // 重试的次数
      WORD  timeout;            // 备用
      WORD  next_urb_off;       // 备用
      WORD  next_urb_seg;       // 备用
    } urb                       // 32字节
    之所以列出这个结构,是因为我们将使用这个结构与USBD进行交互。关于结构中字段的定义,在DOSUSB的文档中有详细的说明。除备用字段不需要填以外,error_code和status由DOSUSB返回,故填0即可,后面还会介绍更详细的填写方法。

    在DOSUSB的发行包中,有一个sample目录,里面有很多例子,但大多是使用power basic写的,不过仍然有很好的参考价值。

3、USB 1.1协议中的一些内容

    USB协议为USB设备定义了一套描述设备功能和属性的固有结构的描述符,包括标准描述符(设备描述符、培植描述符、接口描述符、端点描述符和字符串描述符),还有费标准描述符,如类描述符等。按照协议,设备描述符下可以有若干个配置描述符,每个配置描述符可以有若干个接口描述符,每个接口描述符下又可以有若干个端点描述符,字符串描述符主要用于描述一些文字信息,比如厂家名称,产品名称等。这篇文章的目的就是要读出这些描述符。

    实际上,所谓描述符就是一个数据结构,不管是什么描述符,其前两个字节的含义都是一样的,第一个字节是描述符的长度,第二个字节是描述符的类型。

  • 设备描述符(Device Descriptor):

    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 设备描述符类型,0x01
      WORD    bcdUSB;             // 设备支持的USB协议版本,BCD码
      BYTE    bDeviceClass;       // 设备类代码(由USB-IF分配)
      BYTE    bDeviceSubClass;    // 子类代码
      BYTE    bDeviceProtocol;    // 协议码
      BYTE    bMaxPacketSize0;    // 端点0的最大包长度(仅为8,16,32,64)
      WORD    idVendor;           // 厂商ID(由USB-IF分配)
      WORD    idProduct;          // 产品ID(由制造商定义)
      WORD    bcdDevice;          // 设备发行号(BCD码)
      BYTE    iManufacture;       // 描述厂商信息的字符串描述符的索引值
      BYTE    iProduct;           // 描述产品信息的字符串描述符的索引值
      BYTE    iSerialNumber;      // 描述设备序列号信息的字符串描述符的索引值
      BYTE    bNumConfigurations; // 可能的配置描述符的数目
    } device_descriptor

  • 配置描述符(Configuration Descriptor)

    struct {
      BYTE    bLength;             // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;     // 配置描述符类型,0x02
      WORD    wTotalLength;        // 配置信息的总长
      BYTE    bNumInterfaces;      // 该配置所支持的接口数目
      BYTE    bConfigurationValue; // 被SetCongiguration()请求用做参数来选定该配置
      BYTE    bConfiguration;      // 描述该配置的字符串描述符的索引值
      BYTE    bmAttributes;        // 配置特性
      BYTE    MaxPower;            // 该配置下的总线电源耗费量,以2mA为一个单位 
    }configuration_descriptor;

    bmAttributes :b7:备用,b6:自供电,b5:远程唤醒,b4--b0:备用

    另外,在读取配置描述符时可以把该配置下的所有描述符全部读出,这些描述符的总长度就是wTotalLength字段的值,读出所有描述符后,在一个一个地拆分。

  • 接口描述符(Interface Descriptor):

    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 接口描述符类型,0x04
      BYTE    bInterfaceNumber;   // 接口号,从0开始
      BYTE    bAlternateSetting;  // 可选设置的索引值.
      BYTE    bNumEndpoints;      // 此接口的端点数量。 
      BYTE    bInterfaceClass;    // 接口类编码(由USB-IF分配)
      BYTE    bInterfaceSubClass; // 接口子类编码(由USB-IF分配)
      BYTE    bInterfaceProtocol; // 协议码(由USB-IF分配)
      BYTE    iInterface;         // 描述该接口的字符串描述符的索引值
    }interface_descriptor;

    bInterfaceClass:USB协议根据功能将不同的接口划分成不同的类,如下:

    1:音频类,2:CDC控制类,3:人机接口类(HID),5:物理类,6:图像类,7:打印机类,8:大数据存储类,9:集线器类,10:CDC数据类,11:智能卡类,13:安全类,220:诊断设备类,224:无线控制类,254:特定应用类,255厂商定义的设备。

  •  端点描述符(Endpoint Descriptor):

    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 端点描述符类型,0x05
      BYTE    bEndpointAddress;   // 端点地址
      BYTE    bmAttributes;       // 在bconfigurationValue所指的配置下的端点特性.
      WORD    wMaxPacketSize;     // 接收/发送的最大数据报长度. 
      BYTE    bInterval;          // 周期数据传输端点的时间间隙.
    }endpoint_descriptor;

    bmAttributes:bit 1:0--传送类型,00=控制传输,01=实时传输,10=批量传输,11=中断传输;所有其他位均保留。

  • 字符串描述符(String Descriptor):

    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 字符串描述符类型,0x03
      char    bString[];          // UNICODE编码的字符串
    }string_descriptor;

  • USB命令请求(USB DEVICE REQUEST)

    为了更好地协调USB主机与设备之间的数据通信,USB规范定义了一套命令请求,用于完成主机对总线上所有USB设备的统一控制,USB命令请求由统一的格式,其结构如下:

    struct {
      BYTE  bmRequestType;  // 请求类型
      BYTE  bRequest;       // 命令请求的编码
      WORD  wValue;         // 命令不同,含义不同
      WORD  wIndex;         // 命令不同,含义不同 
      WORD  wLength;        // 如果有数据阶段,此字段为数据字节数

    }
 setup_request;

    后面我们向设备发出指令就全靠这个结构了。作为我们本文要用到的读取USB设备描述符的命令请求,该结构各字段的填法如下。

    bmRequestType : b7--数据传输方向,0=主机到设备,1=设备到主机;b6:5--命令的类型,0=标准命令,1=类命令,2=厂商提供的命令,3=保留;b4:0--接收对象,0=设备,1=接口,2=端点,3=其他。我们发出的得到描述符的命令,数据传输方向为设备到主机,标准命令,接收对象为设备,所以该字段填0x80。

    bRequest : 标准命令的编码如下,GET_STATUS=0;CLEAR_FEATURE=1;SET_FEATURE=3;SET_ADDRESS=5;GET_DESCRIPTOR=6;SET_DESCRIPTOR=7;GET_CONFIGURATION=8;SET_CONFIGURATION=9;GET_INTERFACE=10;SET_INTERFACE=11;SYNCH_FRAME=12。我们的命令是GET_DESCRIPTOR,所以应该填6。

    wValue : 高字节表示描述符的类型,0x01=设备描述符,0x02=配置描述符,0x03=字符串描述符,0x04=接口描述符,0x05=端点描述符,0x29=集线器类描述符,0x21=人机接口类描述符,0xFF=厂商定义的描述符。

    低字节表示表示描述符的索引值。所以,当读取设备描述符时,该字段值为0x100,当读取配置描述符是,应为0x03yy,其中yy为描述符的索引值。

    wIndex : 当读取字符串描述符时,填0x0409,表示希望获得英文字符串,其他情况下填0。

    wLength : 数据长度,一般应该填写,描述符的第一个字节,bLength。由于我们在读描述符时,并不知道其实际长度,通常的做法是先读取8个字节,然后根据第一个字节bLength的值在重新读取一次完整的描述符;注意,当读取配置描述符的钱8个字节后,应该使用wTotalLength字段的值作为长度读取与该配置有关的所有描述符。

4、读取设备描述符的范例程序

    按照我们文章的习惯,几乎每篇文章都有一个范例程序,本文也不例外,本文的范例程序请在下面地址下载:

    http://blog.hengch.com/source/usbview.zip

    程序用C++写成,在DJGPP下编译通过,所以是32位保护模式下的代码,要注意的是,DOSUSB是实模式下的驱动,所以在申请内存块时要申请1M以内实模式可以读取的内存,否则,在使用int 65h调用DOSUSB时一定会出现问题。

    有4个头文件,public.h中定义了一些方便使用的数据类型,比如BYTE为char,WORD为short int等等,可以不必太关注;x86int.h中定义了调用DOS中断所需的函数和数据结构,直到怎么使用就可以了;dosmem.h中定义了一个DOS_MEM类,主要是为了在保护模式下申请和使用DOS内存块更为方便,也是知道其中的方法,能够明白程序中的意义就可以了;usb.h定义了与USB协议有关的所有常数,这些常数与前面介绍的各种数据结构一一对应,由于我们是在保护模式下使用DOS内存,所以把一个内存块映射到一个数据结构上有一些麻烦,读取各个字段主要靠在usb.h中定义的这些常数。

    主要程序在usbview.cc中,主要思路如下:

  • USB设备的地址从1--127,所以我们从1--127做一个循环,逐一读取USB设备描述符,直到出现“非法地址”为止。
  • 每一个USB设备的设备描述符只有一个,所以我们从读取某个地址下的设备描述符开始。
  • 开始我们并不知道设备描述符的长度,即便我们知道其长度为18个字符,但我们仍然不知道端点0允许的包长度(设备描述符中bMaxPacketSize0字段),但我们知道包长度的最小值是8,所以我们先读取8个字节的设备描述符。
  • 在我们得到8个字符的设备描述符后,我们就可以得到该描述符的长度和端点0的包长度,在后面发出的所有命令中,始终要把这个值填在URB结构的actual_length字段中。
  • buffer用于存放USBD返回的描述符,在使用前建议初始化一下,全部清0。
  • 要向设备发出命令请求(Request),需要先填setup_request结构,前面讲过,bmRequestType=0x80,bRequest=6,这两个字段的填法始终不变;我们现在读取设备描述符,所以wValue=0x100,wIndex=0,wLength在首次调用时填8,第二次调用时填返回的bLength字段(应该是18)。
  • 准备好buffer和setup_request后,我们要填URB一边与DOSUSB交互;读取描述符是一个控制传输,所以transaction=0x2D(后面一直是0x2D);dev_add填上面提到的循环变量;end_point=0,因为我们总对端点0(见USB协议);buffer_off和buffer_seg分别填buffer的便宜地址和段地址;setup_buffer_off和setup_buffer_seg分别填前面setup_request结构的偏移地址和段地址;buffer_length同setup_request结构中的wLength,也可以把值设在wLength和buferr的最大长度之间,如果buffer的最大长度小于wLength,我们只能填buffer的最大长度,但这种情况下我们将得不到完整的描述符;actual_length在第一次调用时填8,以后一直填返回的bMaxPacketsize0字段;其他字段均为0。
  • 让DS:DX指向刚刚填完的URB结构,调用软中断65h,DOSUSB将为你处理下面的事情。
  • 如果正常,error_code应该返回0,如果非0,含义如下:
    1--非法的设备地址;2--内部错误;3--非法的transation_type字段;4--非法的buffer长度。
  • 如果正常,status字段应该为0,该字段是是USB控制器返回的状态字节,不同的控制器(OHCI或UHCI)会返回不同的值。
  • 当我们得到设备描述符后,如果设备描述符中的iManufacturer字段不为0,我们可以根据这个所引值得到相应的字符串描述符,从而显示出厂家信息,要注意的是字符串描述符是UNICODE编码,对于ASCII而言,它是一个ASSCII码跟一个ASCII 0组成;同理我们可以得到产品信息和序列号信息。
  • 当我们得到了设备的设备描述符后,我们就可以知道这个设备上有多少个配置(设备描述符中的bNumConfigurations),进而通过一个循环得到所有的配置描述符及其配置下的所有描述符。
  • 读取配置描述符的方法与读取设备描述符大同小异,也是先读取8个字节,然后根据返回的内容再读取所有的描述符内容,要注意的是,实际上,我们不能单独得到接口描述符和端点描述符,唯一的办法是把一个配置下的描述符全部读出来,所以setup_request结构中的wLength字段一定要与配置描述符中返回的wTotalLength值一致才行。
  • 剩下的事情就是如何显示我们得到的描述符,我想这对每一位读者而言都不是什么困难的事。
posted @ 2010-11-24 14:06 wrh 阅读(2802) | 评论 (0)编辑 收藏
一、单符号
~

① 在for中表示使用增强的变量扩展。
② 在%var:~n,m%中表示使用扩展环境变量指定位置的字符串。
③ 在set/a中表示一元运算符,将操作数按位取反。

!
① 在set /a中一元运算符,表示逻辑非。比如set /a a=!0,这时a就表示逻辑1。

@
① 隐藏命令行本身的回显,常用于批处理中。

$
① 在findstr命令里面表示一行的结束。
② 在prompt命令里面,表示将其后的字符转义(符号化或者效果化)。

%
① 在set /a中的二元运算符,表示算术取余。
② 命令行环境下,在for命令in前,后面接一个字符(可以是字母、数字或者一些特定字符),表示指定一个循环或者遍历指标变量。
③ 批处理中,后接一个数字表示引用本批处理当前执行时的指定的参数。
④ 其它情况下,%将会被脱去(批处理)或保留(命令行)

^
① 取消特定字符的转义作用,比如& | > < ! "等,但不包括%。比如要在屏幕显示一些特殊的字符,比如> >> | ^ &等符号时,就可以在其前面加一个^符号来显示这个^后面的字符了,^^就是显示一个^,^|就是显示一个|字符了;
② 在set/a中的二元运算符,表示按位异或。
③ 在findstr/r的[]中表示不匹配指定的字符集。

&
① 命令连接字符。比如我要在一行文本上同时执行两个命令,就可以用&命令连接这两个命令。
② 在set/a中是按位与。

*
① 代表任意个任意字符,就是我们通常所说的"通配符";比如想在c盘的根目录查找c盘根目录里所有的文本文件(.txt),那么就可以输入命令"dir c:\*.txt"。
② 在set /a中的二元运算符,表示算术乘法。
③ 在findstr/r中表示将前一个字符多次匹配。

-
① 范围表示符,比如日期的查找,for命令里的tokens操作中就可以用到这个字符。
② 在findstr/r中连接两个字符表示匹配范围。
③ -跟在某些命令的/后表示取反向的开关。
④ 在set /a中:
1.表示一个负数。
2.表示算术减运算。

+
① 主要是在copy命令里面会用到它,表示将很多个文件合并为一个文件,就要用到这个+字符了。
② 在set/a中的二元运算符,表示算术加法。

:
① 标签定位符,表示其后的字符串为以标签,可以作为goto命令的作用对象。比如在批处理文件里面定义了一个":begin"标签,用"goto begin"命令就可以转到":begin"标签后面来执行批处理命令了。
② 在%var:string1=string2%中分隔变量名和被替换字串关系。

|
① 管道符,就是将上一个命令的输出,作为下一个命令的输入."dir /a/b |more"就可以逐屏的显示dir命令所输出的信息。
② 在set/a中的二元运算符,表示按位或。
③ 在帮助文档中表示其前后两个开关、选项或参数是二选一的。

/
① 表示其后的字符(串)是命令的功能开关(选项)。比如"dir /s/b/a-d"表示"dir"命令指定的不同的参数。
② 在set/a中表示除法。

>
① 命令重定向符,将其前面的命令的输出结果重新定向到其后面的设备中去,后面的设备中的内容被覆盖。比如可以用"dir > lxmxn.txt"将"dir"命令的结果输出到"lxmxn.txt"这个文本文件中去。
② 在findstr/r中表示匹配单词的右边界,需要配合转义字符\使用。

<
① 将其后面的文件的内容作为其前面命令的输入。
② 在findstr/r中表示匹配单词的左边界,需要配合转义字符\使用。

=
① 赋值符号,用于变量的赋值。比如"set a=windows"的意思意思是将"windows"这个字符串赋给变量"a"。
② 在set/a中表示算术运算,比如"set /a x=5-6*5"。

\
① 这个"\"符号在有的情况下,代表的是当前路径的根目录.比如当前目录在c:\windows\system32下,那么你"dir \"的话,就相当与"dir c:\"。
② 在findstr/r中表示正则转义字符。

,
① 在set /a中表示连续表达式的分割符。
② 在某些命令中分割元素。

.
① 在路径的\后紧跟或者单独出现时:
一个.表示当前目录。
两个.表示上一级目录。
② 在路径中的文件名中出现时:
最后的一个.表示主文件名与扩展文件名的分隔。

?
① 在findstr/r中表示在此位置匹配一个任意字符。
② 在路径中表示在此位置通配任意一个字符。
③ 紧跟在/后表示获取命令的帮助文档。

二、多符号(符号不能分隔)

&&
① 连接两个命令,当&&前的命令成功时,才执行&&后的命令。

||
① 连接两个命令,当||前的命令失败时,才执行||后的命令。

>&
① 将一个句柄的输出写入到另一个句柄的输入中。

<&
① 从一个句柄读取输入并将其写入到另一个句柄输出中。

%%
① 两个连续的%表示在预处理中脱为一个%。
② 批处理中,在for语句的in子句之前,连续两个%紧跟一个字符(可以是字母、数字和一些特定字符),表示指定一个循

环或者遍历指标变量。
③ 批处理中,在for语句中,使用与in之前指定的指标变量相同的串,表示引用这个指标变量。

>>
① 命令重定向符,将其前面的命令的输出结果追加到其后面的设备中去。
② 在set /a中的二元运算符,表示逻辑右移。

==
① 在if命令中判断==两边的元素是否相同。

<<
① 在set /a中的二元运算符,表示逻辑左移。

+=
① 在set /a中的二元运算符。例如set /a a+=b表示将a加上b的结果赋值给a。

-=
① 在set /a中的二元运算符。例如set /a a-=b表示将a减去b的结果赋值给a。

*=
① 在set /a中的二元运算符。例如set /a a*=b表示将a乘以b的结果赋值给a。

/=
① 在set /a中的二元运算符。例如set /a a/=b表示将a加上b的结果赋值给a。

%=
① 在set /a中的二元运算符。例如set /a a%=b表示将a除以b的余数赋值给a。
【注:命令行可以直接用 set /a a%=b ,在批处理里面可以用 set /a a%%=b 。】

^=
① 在set /a中的二元运算符。例如set /a a"^="b表示将a与b按位异的结果赋值给a。
【注:这里 "^=" 加引号是为了防止^被转义,下同。】

&=
① 在set /a中的二元运算符。例如set /a a"&="b表示将a与b按位与的结果赋值给a。

|=
① 在set /a中的二元运算符。例如set /a a"|="b表示将a与b按位或的结果赋值给a。

<<=
① 在set /a中的二元运算符。例如set /a a"<<="b表示将a按位左移b位的结果赋值给a。

>>=
① 在set /a中的二元运算符。例如set /a a">>="b表示将a按位右移b位的结果赋值给a。

\<
① 在findstr的一般表达式中表示字的开始处。

\>
① 在findstr的一般表达式中表示字的结束处。

三、双符号对(两个符号之间须指定字符串)

! !
① 当启用变量延迟时,使用!!将变量名扩起来表示对变量值的引用。

' '
① 在for/f中表示将它们包含的内容当作命令行执行并分析其输出。
② 在for/f "usebackq"中表示将它们包含的字符串当作字符串分析。

( )
① 命令包含或者是具有优先权的界定符,比如for命令要用到这个(),我们还可以在if,echo等命令中见到它的身影。
② 在set /a中表示表达式分组。

" "
① 界定符,在表示带有空格的路径时常要用""来将路径括起来,在一些命令里面也需要" "符号。
② 在for/f中将表示它们包含的内容当作字符串分析。
③ 在for/f "usebackq"表示它们包含的内容当作文件路径并分析其文件的内容。
④ 在其它情况下表示其中的内容是一个完整的字符串,其中的>、>>、<、&、|、空格等不再转义。

` `
① 在for/f中表示它们所包含的内容当作命令行执行并分析它的输出。

% %
① 使用两个单独的%包含一个字符串表示引用以此串为名的环境变量。比如一个%time%可以扩展到当前的系统时间。

[ ]
① 在帮助文档表示其中的开关、选项或参数是可选的。
② 在findstr /r中表示按其中指定的字符集匹配。




1.^取消特殊符号的作用
例子:echo ^> >1.txt  将“>”输出到1.txt中

2.","某些时候可以当空格使用
例子:echo,  dir,c:\

3.";"当命令相同时,可以将不同目标用;来隔离
例子:dir c:\;d:\

4.= 赋值符号,变量赋值。如"set a=windows"是将"windows"这个字符串赋给变量"a"。
在set/a中表示算术运算,比如"set /a x=5-6*5"。
命令:SET /P Choice=选择:
显示:选择:   提示输入“abc”
命令:echo %choice%
显示:abc

@echo off
set txt1=%time:~0,2%
::当前小时
set txt2=%time:~3,2%
::当前分钟
set txt3=%time:~6,2%
::当前秒
set time=%txt1%%txt2%%txt3%
echo 当前时间:%txt1%:%txt2%:%txt3%

5.%在for循环中,循环变量引用格式:%%变量名.
如:SUM.bat
@echo off
::求1+2+3+…
set sum=0
for /l %%i in (1,1,%1) do set /a sum+=%%i
echo 1+2+3+…+100=%sum%
说明:在命令行下输入SUM 100,显示结果为:
1+2+3+…+100=505

6.>>想用批处理实现向s.txt中多次分别导入文本例如:“aaaa","bbbb","cccc"
实现s.txt内效果如:
aaaabbbbcccc
可以执行
>>s.txt set/p="aaaa" <nul   同set/p="aaaa" >>s.txt <nul
>>s.txt set/p="bbbb" <nul
>>s.txt set/p="cccc" <nul

7.一个删除行的批处理
有一A.TXT文件,在其中找到HZF时,删除含有HZF字符串的行(不分大小写).
findstr /ivc:"HZF" a.txt >b.txt
@dir |findstr /n .*   给DIR文件打上行号
find /v "HZF" a.txt>b.txt

8.用批处理删除文本每行的前几个字符
现在有a.txt 内容如下
\t (00:00:02) 123856

\t (00:00:03) Hi!lg
\i (00:00:03) traps
\w (00:00:03) Diele
\i (00:00:07) open
现在想把 ) 以及前面的内容删除 只剩下
123856

Hi!ED
traps
Diele
open

for /f "tokens=3 delims= " %%a in (a.txt) do echo %%a>>b.txt     以空格为分隔符取第3个段内容,如果为"tokens=2 delims= "则会选取包括括号以内的内容tokens只能是数字 delims是多个字符,如()

9.一文本:D:\w\tongji.txt 共2000行,现在需要删除前1500行,保留最后的500行。
for /f "SKIP=1500 tokens=*" %%i in (D:\w\tongji.txt ) do (echo %%i>>hh.txt)
删除空白行
for /f "eol==" %%a in (aa.txt) do echo %%a>>b.txt 删除首字符为=的所有行及空白行
for /f "delims=" %%i in (aa.txt) do echo %%i>>b.txt 删除空白行
实际上"eol="和"delims="均可删除空白行,即只要""内有内容,即可删除空白行 (自注:如果缺少delims=项,则默认分隔符为空格)

10.我IP是随机的,我用以下命令:
ipconfig /all > c:\1.txt
find "IP Address" c:\1.txt >> c:\ip.txt
自注(可用ipconfig /all|find "IP Address")
那么就会得出ip.txt,里面的内容为
---------- C:\1.TXT
IP Address. . . . . . . . . . . . : 192.168.0.5
IP Address. . . . . . . . . . . . : 218.15.245.210

我只是想导出的内容只有IP,也就是说ip.txt里面只有192.168.0.5和218.15.245.210

@echo off
for /f "tokens=2 delims=:" %%a in ('ipconfig /all^|find "IP Address"') do (echo IP地址为:%%a)
pause 我IP是随机的,我用以下命令:
ipconfig /all > c:\1.txt
find "IP Address" c:\1.txt >> c:\ip.txt
自注(可用ipconfig /all|find "IP Address")
那么就会得出ip.txt,里面的内容为
---------- C:\1.TXT
IP Address. . . . . . . . . . . . : 192.168.0.5
IP Address. . . . . . . . . . . . : 218.15.245.210

我只是想导出的内容只有IP,也就是说ip.txt里面只有192.168.0.5和218.15.245.210

@echo off
for /f "tokens=2 delims=:" %%a in ('ipconfig /all^|find "IP Address"') do (echo IP地址为:%%a)
pause

 

11.用批处理命令删除文本文件的整行内容
用批处理命令bat解决
例如文本文件a.txt的内容如下:
001,李明,语文,90分
002,李明,数学,70分
003,李明,离散数学,63分
004,李明,英语,60分
005,陈红,语文,80分
006,陈红,数学,60分
007,陈红,离散数学,78分
008,陈红,英语,65分
求:如果某行有“数学”或者“英语”这个词,则删除该行的内容。
要求通过批处理得出如下结果:
001,李明,语文,90分
003,李明,离散数学,63分
005,陈红,语文,80分
007,陈红,离散数学,78分

@echo off
findstr /i /v ",数学 英语" "aa.txt">>jg.txt
findstr /iv /c:",数学" /c:"英语" "aa.txt">>jg.txt (此命令与上行等效)
findstr /iv /c:",数学 英语" "aa.txt">>jg.txt     (此命令失效,因为没有“,数学 英语”这样的内容)

12.不换行显示文本内容
for /f %%c in (aa.txt) do set /p=%%c>>c.txt<nul
for /f %%a in (aa.txt) do set /p d+=%%a<nul>>c.txt
   换行显示文本内容
for /f %%c in (aa.txt) do echo %%C>>c.txt

13.变量赋值
set hzf=abcd
echo %hzf%        显示为abcd

posted @ 2010-11-22 16:22 wrh 阅读(690) | 评论 (0)编辑 收藏
http://www.nirsoft.net/utils/usb_devices_view.html
USB类代码
http://www.usb.org/developers/defined_class/#BaseClassE0h
posted @ 2010-11-21 15:40 wrh 阅读(320) | 评论 (0)编辑 收藏
     摘要: # # List of USB ID's # # Maintained by Stephen J. Gowdy <linux.usb.ids@gmail.com> # If you have any new entries, please submit them via # http://www.linux-usb.org/usb-ids.html # or send e...  阅读全文
posted @ 2010-11-21 15:37 wrh 阅读(7399) | 评论 (0)编辑 收藏

#
# List of USB ID's
$Id: usb.ids,v 1.378 2009/03/08 12:48:06

#
# Maintained by Stephen J. Gowdy <sgowdy+usb.ids@gmail.com>
# If you have any new entries, send them to the maintainer.
# Send entries as patches (diff -u old new).
# The latest version can be obtained from
#  http://www.linux-usb.org/usb.ids
#
# $Id: usb.ids,v 1.378 2009/03/08 12:48:06 gowdy Exp $
#

# Vendors, devices and interfaces. Please keep sorted.

# Syntax:
# vendor vendor_name
# device device_name     <-- single tab
#   interface interface_name   <-- two tabs

Download 1

Download 2

posted @ 2010-11-21 15:35 wrh 阅读(468) | 评论 (0)编辑 收藏
代码:见光盘GetSerialNo

#include "windows.h"
#include "PlkUsbIo.h"
#include <malloc.h>

#define NUM_HCS_TO_CHECK 10

/******************************************************************/
bool EnumUsbDevice();
PCHAR GetDriverKeyName(HANDLE Hub, ULONG ConnectionIndex);
PCHAR GetHCDDriverKeyName(HANDLE HCD);
PCHAR GetRootHubName(HANDLE HostController);
PCHAR WideStrToMultiStr(PWCHAR WideStr);
bool GetStringDescriptor (
        HANDLE hHubDevice,
        ULONG   ConnectionIndex,
        UCHAR   DescriptorIndex    ,
        CHAR * outBuff);
   
/******************************************************************/

int main(int argc, char* argv[])
{
    EnumUsbDevice();
    return 0;
}

bool EnumUsbDevice()
{
    char        HCName[16];
    int         HCNum;
    HANDLE      hHCDev;
    PCHAR       rootHubName;

    ULONG       index;
    BOOL        success;

    PUSB_NODE_CONNECTION_INFORMATION    connectionInfo;
    HANDLE hHubDevice;

    for (HCNum = 0; HCNum < NUM_HCS_TO_CHECK; HCNum++)
    {
        wsprintf(HCName, "\\\\.\\HCD%d", HCNum);
        hHCDev = CreateFile(HCName,
            GENERIC_WRITE,
            FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            0,
            NULL);
        if (hHCDev == INVALID_HANDLE_VALUE)   
            break;
       
        PCHAR driverKeyName, deviceDesc;
        driverKeyName = GetHCDDriverKeyName(hHCDev);
        if(driverKeyName == NULL)
            goto end;   
       
   
        ULONG nBytes;
        rootHubName =(char*) GetRootHubName(hHCDev);
        if(rootHubName==NULL)
            goto end;
   
        PUSB_NODE_INFORMATION HubInfo;
        HubInfo = (PUSB_NODE_INFORMATION)malloc(sizeof(USB_NODE_INFORMATION));
        PCHAR deviceName;
        deviceName = (PCHAR)malloc(strlen(rootHubName) + sizeof("\\\\.\\"));
        if (rootHubName != NULL)
        {
            strcpy(deviceName, "\\\\.\\");
            strcpy(deviceName + sizeof("\\\\.\\") - 1, rootHubName);
            hHubDevice = CreateFile(deviceName,
                GENERIC_WRITE,
                FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                0,
                NULL);
            free(deviceName);
            if (hHubDevice == INVALID_HANDLE_VALUE)
                goto end;
           
            success = DeviceIoControl(hHubDevice,
                IOCTL_USB_GET_NODE_INFORMATION,
                HubInfo,
                sizeof(USB_NODE_INFORMATION),
                HubInfo,
                sizeof(USB_NODE_INFORMATION),
                &nBytes,
                NULL);
            if (!success)
                goto end;
           

        }

        int port;
        port=HubInfo->u.HubInformation.HubDescriptor.bNumberOfPorts;
        for (index=1; index <= port; index++)
        {
            ULONG nBytes;
            nBytes = sizeof(USB_NODE_CONNECTION_INFORMATION) +
                sizeof(USB_PIPE_INFO) * 30;
            connectionInfo = (PUSB_NODE_CONNECTION_INFORMATION)malloc(nBytes);
            if (connectionInfo == NULL)
                goto end;
           
            connectionInfo->ConnectionIndex = index;
            success = DeviceIoControl(hHubDevice,
                IOCTL_USB_GET_NODE_CONNECTION_INFORMATION,
                connectionInfo,
                nBytes,
                connectionInfo,
                nBytes,
                &nBytes,
                NULL);
            if (!success)
            {
                free(connectionInfo);
                goto end;
            }
       

            deviceDesc = NULL;
            if (connectionInfo->ConnectionStatus != NoDeviceConnected)
            {
                driverKeyName = GetDriverKeyName(hHubDevice,
                    index);
                if (driverKeyName)
                {
                    free(driverKeyName);
                }
            }

            if (connectionInfo->ConnectionStatus == DeviceConnected)
            {
                //取出序列号索引
                UCHAR nSerialno = connectionInfo->DeviceDescriptor.iSerialNumber;
                CHAR OutBuff[20] = {0};
                GetStringDescriptor(hHubDevice,connectionInfo->ConnectionIndex,nSerialno,OutBuff);

                //判断序列号是否有效
                if(
序列号是否有效)
                {
                      CloseHandle(hHubDevice);
                      CloseHandle(hHCDev);
                     return true;
                }
            }

        }
end:;
    }

    CloseHandle(hHubDevice);
    CloseHandle(hHCDev);
    return false;
}

PCHAR GetDriverKeyName(HANDLE Hub, ULONG ConnectionIndex)
{
    BOOL                                success;
    ULONG                               nBytes;
    USB_NODE_CONNECTION_DRIVERKEY_NAME driverKeyName;
    PUSB_NODE_CONNECTION_DRIVERKEY_NAME driverKeyNameW;
    PCHAR                               driverKeyNameA;

    driverKeyNameW = NULL;
    driverKeyNameA = NULL;

    driverKeyName.ConnectionIndex = ConnectionIndex;

    success = DeviceIoControl(Hub,
        IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME,
        &driverKeyName,
        sizeof(driverKeyName),
        &driverKeyName,
        sizeof(driverKeyName),
        &nBytes,
        NULL);

    if (!success)
    {
        goto GetDriverKeyNameError;
    }

    nBytes = driverKeyName.ActualLength;

    if (nBytes <= sizeof(driverKeyName))
    {
        goto GetDriverKeyNameError;
    }

    driverKeyNameW = (PUSB_NODE_CONNECTION_DRIVERKEY_NAME)malloc(nBytes);

    if (driverKeyNameW == NULL)
    {
        goto GetDriverKeyNameError;
    }

    driverKeyNameW->ConnectionIndex = ConnectionIndex;

    success = DeviceIoControl(Hub,
        IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME,
        driverKeyNameW,
        nBytes,
        driverKeyNameW,
        nBytes,
        &nBytes,
        NULL);

    if (!success)
    {
        goto GetDriverKeyNameError;
    }
    driverKeyNameA = WideStrToMultiStr(driverKeyNameW->DriverKeyName);
    free(driverKeyNameW);

    return driverKeyNameA;


GetDriverKeyNameError:
    if (driverKeyNameW != NULL)
    {
        free(driverKeyNameW);
        driverKeyNameW = NULL;
    }

    return NULL;
}
PCHAR GetRootHubName(HANDLE HostController)
{
    BOOL                success;
    ULONG               nBytes;
    USB_ROOT_HUB_NAME   rootHubName;
    PUSB_ROOT_HUB_NAME rootHubNameW;
    PCHAR               rootHubNameA;

    rootHubNameW = NULL;
    rootHubNameA = NULL;

    success = DeviceIoControl(HostController,
        IOCTL_USB_GET_ROOT_HUB_NAME,
        0,
        0,
        &rootHubName,
        sizeof(rootHubName),
        &nBytes,
        NULL);

    if (!success)
    {
        goto GetRootHubNameError;
    }

    nBytes = rootHubName.ActualLength;

    rootHubNameW =(PUSB_ROOT_HUB_NAME) malloc(nBytes);

    if (rootHubNameW == NULL)
    {

        goto GetRootHubNameError;
    }

    success = DeviceIoControl(HostController,
        IOCTL_USB_GET_ROOT_HUB_NAME,
        NULL,
        0,
        rootHubNameW,
        nBytes,
        &nBytes,
        NULL);

    if (!success)
    {
        goto GetRootHubNameError;
    }

    rootHubNameA = WideStrToMultiStr(rootHubNameW->RootHubName);
    free(rootHubNameW);

    return rootHubNameA;


GetRootHubNameError:
    if (rootHubNameW != NULL)
    {
        free(rootHubNameW);
        rootHubNameW = NULL;
    }

    return NULL;
}
PCHAR GetHCDDriverKeyName(HANDLE HCD)
{
    BOOL                    success;
    ULONG                   nBytes;
    USB_HCD_DRIVERKEY_NAME driverKeyName;
    PUSB_HCD_DRIVERKEY_NAME driverKeyNameW;
    PCHAR                   driverKeyNameA;

    driverKeyNameW = NULL;
    driverKeyNameA = NULL;

    success = DeviceIoControl(HCD,
        IOCTL_GET_HCD_DRIVERKEY_NAME,
        &driverKeyName,
        sizeof(driverKeyName),
        &driverKeyName,
        sizeof(driverKeyName),
        &nBytes,
        NULL);

    if (!success)
    {
        goto GetHCDDriverKeyNameError;
    }

    nBytes = driverKeyName.ActualLength;

    if (nBytes <= sizeof(driverKeyName))
    {
        goto GetHCDDriverKeyNameError;
    }

    driverKeyNameW =(PUSB_HCD_DRIVERKEY_NAME) malloc(nBytes);

    if (driverKeyNameW == NULL)
    {
        goto GetHCDDriverKeyNameError;
    }

    success = DeviceIoControl(HCD,
        IOCTL_GET_HCD_DRIVERKEY_NAME,
        driverKeyNameW,
        nBytes,
        driverKeyNameW,
        nBytes,
        &nBytes,
        NULL);

    if (!success)
    {
        goto GetHCDDriverKeyNameError;
    }

    driverKeyNameA = WideStrToMultiStr(driverKeyNameW->DriverKeyName);
    free(driverKeyNameW);

    return driverKeyNameA;


GetHCDDriverKeyNameError:
    if (driverKeyNameW != NULL)
    {
        free(driverKeyNameW);
        driverKeyNameW = NULL;
    }

    return NULL;
}

PCHAR WideStrToMultiStr(PWCHAR WideStr)
{
    ULONG nBytes;
    PCHAR MultiStr;
    nBytes = WideCharToMultiByte(
        CP_ACP,
        0,
        WideStr,
        -1,
        NULL,
        0,
        NULL,
        NULL);

    if (nBytes == 0)
    {
        return NULL;
    }
    MultiStr =(PCHAR) malloc(nBytes);

    if (MultiStr == NULL)
    {
        return NULL;
    }
    nBytes = WideCharToMultiByte(
        CP_ACP,
        0,
        WideStr,
        -1,
        MultiStr,
        nBytes,
        NULL,
        NULL);

    if (nBytes == 0)
    {
        free(MultiStr);
        return NULL;
    }

    return MultiStr;
}


bool    GetStringDescriptor (
        HANDLE hHubDevice,
        ULONG   ConnectionIndex,
        UCHAR   DescriptorIndex    ,
        CHAR * outBuff
        )
    {
        BOOL    success;
        ULONG   nBytes;
        ULONG   nBytesReturned;

        UCHAR   stringDescReqBuf[sizeof(USB_DESCRIPTOR_REQUEST) + MAXIMUM_USB_STRING_LENGTH];

        PUSB_DESCRIPTOR_REQUEST stringDescReq;
        PUSB_STRING_DESCRIPTOR stringDesc;

        nBytes = sizeof(stringDescReqBuf);

        stringDescReq = (PUSB_DESCRIPTOR_REQUEST)stringDescReqBuf;
        stringDesc = (PUSB_STRING_DESCRIPTOR)(stringDescReq+1);

   
        ::ZeroMemory(stringDescReq,nBytes);
        stringDescReq->ConnectionIndex = ConnectionIndex;
        stringDescReq->SetupPacket.wValue = (USB_STRING_DESCRIPTOR_TYPE << 8) | DescriptorIndex;
        stringDescReq->SetupPacket.wIndex = GetSystemDefaultLangID();
        stringDescReq->SetupPacket.wLength = (USHORT)(nBytes - sizeof(USB_DESCRIPTOR_REQUEST));

        success = DeviceIoControl(hHubDevice,IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
                                stringDescReq,nBytes,
                                stringDescReq,nBytes,
                                &nBytesReturned,NULL);

        if (!success || nBytesReturned < 2)
            return false;

        if (stringDesc->bDescriptorType != USB_STRING_DESCRIPTOR_TYPE)
            return false;

        if (stringDesc->bLength != nBytesReturned - sizeof(USB_DESCRIPTOR_REQUEST))
            return false;

        if (stringDesc->bLength % 2 != 0)
            return false;

        WCHAR * wChar = new WCHAR[stringDesc->bLength + 1];
        memcpy(wChar,stringDesc->bString,stringDesc->bLength);
        char *szTemp = WideStrToMultiStr(wChar);
        lstrcpy(outBuff, szTemp);
       
        if(szTemp)
        delete []szTemp;
       
        if(wChar)
        delete []wChar;
        return true;
    }

分析:

1)首先假定有10个usb接口
    #define NUM_HCS_TO_CHECK 10

2)循环打开这10个usb端口,如果端口没有这么多,调用
CreateFile,就会返回 INVALID_HANDLE_VALUE。
    for (HCNum = 0; HCNum < NUM_HCS_TO_CHECK; HCNum++)
    {
        wsprintf(HCName, "\\\\.\\HCD%d", HCNum);
        hHCDev = CreateFile(HCName,
            GENERIC_WRITE,
            FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            0,
            NULL);
        if (hHCDev == INVALID_HANDLE_VALUE)   
            break;

3)获取root hub
       ULONG nBytes;
        rootHubName =(char*) GetRootHubName(hHCDev);
        if(rootHubName==NULL)
            goto end;

4) 遍历连接在root hub上的节点
        int port;
        port=HubInfo->u.HubInformation.HubDescriptor.bNumberOfPorts;
        for (index=1; index <= port; index++)
        {
            ULONG nBytes;
            nBytes = sizeof(USB_NODE_CONNECTION_INFORMATION) +
                sizeof(USB_PIPE_INFO) * 30;
            connectionInfo = (PUSB_NODE_CONNECTION_INFORMATION)malloc(nBytes);
            if (connectionInfo == NULL)
                goto end;
           
            connectionInfo->ConnectionIndex = index;
            success = DeviceIoControl(hHubDevice,
                IOCTL_USB_GET_NODE_CONNECTION_INFORMATION,
                connectionInfo,
                nBytes,
                connectionInfo,
                nBytes,
                &nBytes,
                NULL);
            if (!success)
            {
                free(connectionInfo);
                goto end;
            }

5)根据节点的连接状态,获取节点信息,得到序列号。
    if (connectionInfo->ConnectionStatus == DeviceConnected)
            {
                //取出序列号索引
                UCHAR nSerialno = connectionInfo->DeviceDescriptor.iSerialNumber;
                CHAR OutBuff[20] = {0};
                GetStringDescriptor(hHubDevice,connectionInfo->ConnectionIndex,nSerialno,OutBuff);

6)得到序列号的方法在理论部分已经详细说明了,对应的函数是
GetStringDescriptor,这里不再重复
posted @ 2010-11-21 15:24 wrh 阅读(1538) | 评论 (0)编辑 收藏

在前面的文章中,我们已经给出了USB协议的链接地址,从这篇文章起,我们会涉及到许多USB 1.1的内容,我们的指导思想是先从熟悉USB 1.1协议入手,先使用现成的HCD和USBD,直接面对客户端驱动编程,尽快看到成果,使读者对USB的开发充满信心,进而去研究USBD和HCD的编程方法。请读者自行阅读协议,文章中有关协议的详细情况,由于会涉及非常多的文字,恕不能过多解释。
1、USB系统主机端的软件结构
    一般来说,教科书或者协议上都会把USB主机端的软件说成有三层,第一层叫主机控制器驱动程序HCD(Host Controller Driver),第二层叫USB驱动程序USBD(USB Driver),第三层叫客户端驱动程序(Client Driver);实际上,我们实际看到的东西,往往HCD和USBD是由一个程序完成的,比如windows就提供了HCD和USBD,如果你自己开发了一个USB设备,只需要在HCD和USBD上面开发一个客户端驱动程序即可;linux也是同样,linux内核已经提供了HCD和USBD;所以在 windows和linux下我们基本上没有开发HCD和USBD的必要,而且linux还提供源代码;但DOS就不一样了,DOS本身对USB没有任何支持,所以要想在DOS下彻底玩转USB,需要研究HCD、USBD和客户端驱动程序。
2、DOSUSB介绍
    很显然,HCD和USBD更加底层一些,需要理解的东西也更多一些;如果我们能够绕过HCD和USBD,直接从客户端驱动程序入手,将会容易许多。幸运的是我们可以找到一个免费的DOS下的USB驱动程序,叫DOSUSB,该驱动程序实现了大部分的HCD和USBD的功能,使我们进行USB编程的好帮手。
    DOSUSB目前还没有实现EHIC的驱动,也就是说还不支持USB2.0,这也是我们从USB 1.1开始的原因之一,另一方面,由于USB2.0是兼容USB1.1的,所以,即便你在USB2.0的设备下,仍然可以使用USB1.1的驱动程序,只不过不能实现480MB/秒的传送速度而已。
    下面我们介绍一下DOSUSB。DOSUSB的官方网站如下:
    http://www.usbdos.net

    可以从其官方网站上下载DOGUSB的最新版本,当前版本是1.1.1。或者在下面在下面网址下载这个版本的DOSUSB。

    http://blog.hengch.com/software/dosusb/dosusb.zip

    DOSUSB可以在非商业领域免费使用,如果肯花费费用,可以购买到源代码,从其官方网站的论坛上看到,在2006年9月作者开出的源代码的价格是 1000欧元。

    DOSUSB的安装十分简便,只需要解压缩到某一个目录下即可,比如放在c:\dosusb目录下,请自行阅读DOSUSB自带的文档,使用也非常简单,在DOS提示符下键入dosusb执行即可。

    c:\dosusb>dosusb

    缺省情况下,DOSUSB使用int 65h作为其驱动的调用软中断,如果和你的系统有冲突,在运行dosusb时可以加参数/I,请自行阅读DOSUSB的文档。

    DOSUSB通过一个叫做URB(USB Request Block)的数据结构与客户端驱动程序进行通讯,这一点和linux非常相似,估计作者参考了linux下的源代码,在DOSUSB文档里给出了这个结构的定义,如下:
    struct {
      BYTE  transaction_type;   // 设置事务(控制传输)(2Dh),输入事务(69h)输出事务(E1h)
      BYTE  chain_end_flag;     // 备用
      BYTE  dev_add;            // 设备地址
      BYTE  end_point;          // 端点号
      BYTE  error_code;         // 错误吗
      BYTE  status;             // 设备状态
      WORD  transaction_flags;  // 备用
      WORD  buffer_off;         // 接收/发送缓冲区偏移地址
      WORD  buffer_seg;         // 接收/发送缓冲区段地址
      WORD  buffer_length;      // 接收/发送缓冲区长度
      WORD  actual_length;      // 接收/发送时每个包的最大长度
      WORD  setup_buffer_off;   // setup_request结构的偏移地址
      WORD  setup_buffer_seg;   // setup_request结构的段地址
      WORD  start_frame;        // 备用
      WORD  nr_of_packets;      // >0时会启动实时传输
      WORD  int_interval;       // 备用
      WORD  error_count;        // 重试的次数
      WORD  timeout;            // 备用
      WORD  next_urb_off;       // 备用
      WORD  next_urb_seg;       // 备用
    } urb                       // 32字节
    之所以列出这个结构,是因为我们将使用这个结构与USBD进行交互。关于结构中字段的定义,在DOSUSB的文档中有详细的说明。除备用字段不需要填以外,error_code和status由DOSUSB返回,故填0即可,后面还会介绍更详细的填写方法。

    在DOSUSB的发行包中,有一个sample目录,里面有很多例子,但大多是使用power basic写的,不过仍然有很好的参考价值。

3、 USB 1.1协议中的一些内容

    USB协议为USB设备定义了一套描述设备功能和属性的固有结构的描述符,包括标准描述符(设备描述符、培植描述符、接口描述符、端点描述符和字符串描述符),还有费标准描述符,如类描述符等。按照协议,设备描述符下可以有若干个配置描述符,每个配置描述符可以有若干个接口描述符,每个接口描述符下又可以有若干个端点描述符,字符串描述符主要用于描述一些文字信息,比如厂家名称,产品名称等。这篇文章的目的就是要读出这些描述符。

    实际上,所谓描述符就是一个数据结构,不管是什么描述符,其前两个字节的含义都是一样的,第一个字节是描述符的长度,第二个字节是描述符的类型。

  • 设备描述符(Device Descriptor):

    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 设备描述符类型,0x01
      WORD    bcdUSB;             // 设备支持的USB协议版本,BCD码
      BYTE    bDeviceClass;       // 设备类代码(由USB-IF分配)
      BYTE    bDeviceSubClass;    // 子类代码
      BYTE    bDeviceProtocol;    // 协议码
      BYTE    bMaxPacketSize0;    // 端点0的最大包长度(仅为8,16,32,64)
      WORD    idVendor;           // 厂商ID(由USB-IF分配)
      WORD    idProduct;          // 产品ID(由制造商定义)
      WORD    bcdDevice;          // 设备发行号(BCD码)
      BYTE    iManufacture;       // 描述厂商信息的字符串描述符的索引值
      BYTE    iProduct;           // 描述产品信息的字符串描述符的索引值
      BYTE    iSerialNumber;      // 描述设备序列号信息的字符串描述符的索引值
      BYTE    bNumConfigurations; // 可能的配置描述符的数目
    } device_descriptor

  • 配置描述符 (Configuration Descriptor)

    struct {
      BYTE    bLength;             // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;     // 配置描述符类型,0x02
      WORD    wTotalLength;        // 配置信息的总长
      BYTE    bNumInterfaces;      // 该配置所支持的接口数目
      BYTE    bConfigurationValue; // 被SetCongiguration()请求用做参数来选定该配置
      BYTE    bConfiguration;      // 描述该配置的字符串描述符的索引值
      BYTE    bmAttributes;        // 配置特性
      BYTE    MaxPower;            // 该配置下的总线电源耗费量,以2mA为一个单位 
    }configuration_descriptor;

    bmAttributes :b7:备用,b6:自供电,b5:远程唤醒,b4--b0:备用

    另外,在读取配置描述符时可以把该配置下的所有描述符全部读出,这些描述符的总长度就是wTotalLength字段的值,读出所有描述符后,在一个一个地拆分。

  • 接口描述符(Interface Descriptor):

    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 接口描述符类型,0x04
      BYTE    bInterfaceNumber;   // 接口号,从0开始
      BYTE    bAlternateSetting;  // 可选设置的索引值.
      BYTE    bNumEndpoints;      // 此接口的端点数量。 
      BYTE    bInterfaceClass;    // 接口类编码(由USB-IF分配)
      BYTE    bInterfaceSubClass; // 接口子类编码(由USB-IF分配)
      BYTE    bInterfaceProtocol; // 协议码(由USB-IF分配)
      BYTE    iInterface;         // 描述该接口的字符串描述符的索引值
    }interface_descriptor;

    bInterfaceClass:USB协议根据功能将不同的接口划分成不同的类,如下:

    1:音频类,2:CDC控制类,3:人机接口类(HID),5:物理类,6:图像类,7:打印机类,8:大数据存储类,9:集线器类,10:CDC数据类,11:智能卡类,13:安全类,220:诊断设备类,224:无线控制类,254:特定应用类,255厂商定义的设备。

  •  端点描述符(Endpoint Descriptor):

    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 端点描述符类型,0x05
      BYTE    bEndpointAddress;   // 端点地址
      BYTE    bmAttributes;       // 在bconfigurationValue所指的配置下的端点特性.
      WORD    wMaxPacketSize;     // 接收/发送的最大数据报长度. 
      BYTE    bInterval;          // 周期数据传输端点的时间间隙.
    }endpoint_descriptor;

    bmAttributes:bit 1:0--传送类型,00=控制传输,01=实时传输,10=批量传输,11=中断传输;所有其他位均保留。

  • 字符串描述符(String Descriptor):

    struct {
      BYTE    bLength;            // 描述符的长度,以字节为单位
      BYTE    bDescriptorType;    // 字符串描述符类型,0x03
      char    bString[];          // UNICODE编码的字符串
    }string_descriptor;

  • USB命令请求(USB DEVICE REQUEST)

    为了更好地协调USB主机与设备之间的数据通信,USB规范定义了一套命令请求,用于完成主机对总线上所有USB设备的统一控制,USB命令请求由统一的格式,其结构如下:

    struct {
      BYTE  bmRequestType;  // 请求类型
      BYTE  bRequest;       // 命令请求的编码
      WORD  wValue;         // 命令不同,含义不同
      WORD  wIndex;         // 命令不同,含义不同 
      WORD  wLength;        // 如果有数据阶段,此字段为数据字节数

    }
 setup_request;

    后面我们向设备发出指令就全靠这个结构了。作为我们本文要用到的读取USB设备描述符的命令请求,该结构各字段的填法如下。

    bmRequestType : b7--数据传输方向,0=主机到设备,1=设备到主机;b6:5--命令的类型,0=标准命令,1=类命令,2=厂商提供的命令,3=保留;b4:0--接收对象,0=设备,1=接口,2=端点,3=其他。我们发出的得到描述符的命令,数据传输方向为设备到主机,标准命令,接收对象为设备,所以该字段填0x80。

    bRequest : 标准命令的编码如下,GET_STATUS=0;CLEAR_FEATURE=1;SET_FEATURE=3; SET_ADDRESS=5;GET_DESCRIPTOR=6;SET_DESCRIPTOR=7;GET_CONFIGURATION=8; SET_CONFIGURATION=9;GET_INTERFACE=10;SET_INTERFACE=11;SYNCH_FRAME=12。我们的命令是GET_DESCRIPTOR,所以应该填6。

    wValue : 高字节表示描述符的类型,0x01=设备描述符,0x02=配置描述符,0x03=字符串描述符,0x04=接口描述符,0x05=端点描述符,0x29=集线器类描述符,0x21=人机接口类描述符,0xFF=厂商定义的描述符。

    低字节表示表示描述符的索引值。所以,当读取设备描述符时,该字段值为0x100,当读取配置描述符是,应为0x03yy,其中yy为描述符的索引值。

    wIndex : 当读取字符串描述符时,填0x0409,表示希望获得英文字符串,其他情况下填0。

    wLength : 数据长度,一般应该填写,描述符的第一个字节,bLength。由于我们在读描述符时,并不知道其实际长度,通常的做法是先读取8个字节,然后根据第一个字节bLength的值在重新读取一次完整的描述符;注意,当读取配置描述符的钱8个字节后,应该使用wTotalLength字段的值作为长度读取与该配置有关的所有描述符。

4、读取设备描述符的范例程序

    按照我们文章的习惯,几乎每篇文章都有一个范例程序,本文也不例外,本文的范例程序请在下面地址下载:

    http://blog.hengch.com/source/usbview.zip

    程序用C++写成,在DJGPP下编译通过,所以是32位保护模式下的代码,要注意的是,DOSUSB是实模式下的驱动,所以在申请内存块时要申请1M以内实模式可以读取的内存,否则,在使用int 65h调用DOSUSB时一定会出现问题。

    有4个头文件,public.h中定义了一些方便使用的数据类型,比如BYTE为char,WORD为short int等等,可以不必太关注;x86int.h中定义了调用DOS中断所需的函数和数据结构,直到怎么使用就可以了;dosmem.h中定义了一个 DOS_MEM类,主要是为了在保护模式下申请和使用DOS内存块更为方便,也是知道其中的方法,能够明白程序中的意义就可以了;usb.h定义了与 USB协议有关的所有常数,这些常数与前面介绍的各种数据结构一一对应,由于我们是在保护模式下使用DOS内存,所以把一个内存块映射到一个数据结构上有一些麻烦,读取各个字段主要靠在usb.h中定义的这些常数。

    主要程序在usbview.cc中,主要思路如下:

  • USB 设备的地址从1--127,所以我们从1--127做一个循环,逐一读取USB设备描述符,直到出现“非法地址”为止。
  • 每一个USB设备的设备描述符只有一个,所以我们从读取某个地址下的设备描述符开始。
  • 开始我们并不知道设备描述符的长度,即便我们知道其长度为18个字符,但我们仍然不知道端点0允许的包长度(设备描述符中bMaxPacketSize0字段),但我们知道包长度的最小值是8,所以我们先读取8个字节的设备描述符。
  • 在我们得到8个字符的设备描述符后,我们就可以得到该描述符的长度和端点0的包长度,在后面发出的所有命令中,始终要把这个值填在URB结构的actual_length字段中。
  • buffer用于存放USBD返回的描述符,在使用前建议初始化一下,全部清0。
  • 要向设备发出命令请求(Request),需要先填setup_request结构,前面讲过,bmRequestType=0x80,bRequest=6,这两个字段的填法始终不变;我们现在读取设备描述符,所以 wValue=0x100,wIndex=0,wLength在首次调用时填8,第二次调用时填返回的bLength字段(应该是18)。
  • 准备好buffer和setup_request后,我们要填URB一边与DOSUSB交互;读取描述符是一个控制传输,所以transaction=0x2D(后面一直是0x2D);dev_add填上面提到的循环变量;end_point=0,因为我们总对端点0(见USB协议);buffer_off和buffer_seg分别填buffer的便宜地址和段地址;setup_buffer_off和setup_buffer_seg分别填前面setup_request结构的偏移地址和段地址;buffer_length同setup_request结构中的wLength,也可以把值设在wLength和buferr的最大长度之间,如果 buffer的最大长度小于wLength,我们只能填buffer的最大长度,但这种情况下我们将得不到完整的描述符;actual_length在第一次调用时填8,以后一直填返回的bMaxPacketsize0字段;其他字段均为0。
  • 让DS:DX指向刚刚填完的URB结构,调用软中断65h,DOSUSB将为你处理下面的事情。
  • 如果正常,error_code应该返回0,如果非0,含义如下:
    1--非法的设备地址;2--内部错误;3--非法的transation_type字段;4--非法的buffer长度。
  • 如果正常,status字段应该为0,该字段是是USB控制器返回的状态字节,不同的控制器(OHCI或 UHCI)会返回不同的值。
  • 当我们得到设备描述符后,如果设备描述符中的iManufacturer字段不为0,我们可以根据这个所引值得到相应的字符串描述符,从而显示出厂家信息,要注意的是字符串描述符是 UNICODE编码,对于ASCII而言,它是一个ASSCII码跟一个ASCII 0组成;同理我们可以得到产品信息和序列号信息。
  • 当我们得到了设备的设备描述符后,我们就可以知道这个设备上有多少个配置(设备描述符中的 bNumConfigurations),进而通过一个循环得到所有的配置描述符及其配置下的所有描述符。
  • 读取配置描述符的方法与读取设备描述符大同小异,也是先读取8个字节,然后根据返回的内容再读取所有的描述符内容,要注意的是,实际上,我们不能单独得到接口描述符和端点描述符,唯一的办法是把一个配置下的描述符全部读出来,所以setup_request结构中的wLength字段一定要与配置描述符中返回的wTotalLength值一致才行。
  • 剩下的事情就是如何显示我们得到的描述符,我想这对每一位读者而言都不是什么困难的事。

 

 原文地址 http://hengch.blog.163.com/blog/static/107800672008423111324286/
posted @ 2010-11-21 13:16 wrh 阅读(970) | 评论 (0)编辑 收藏
一、基本概念  

  1、USB协议本身很复杂,但方便在提供了统一的接口方式,使得驱动程序在使用设备的时候,工作简化到了类似操作串行接口。

  2、USB设备可以看作提供了多个串口的设备,依据USB的规范,我们将每个串口称作端点(Endpoint),要和这个端点通信,我们就要打开到这个端点的连接,这个连接就是管道(Pipe)。

  3、打开端点之后,就可以像串口一样进行数据传输了。USB有4种不同类型的传输方式:控制传输(Control Transfer),批量传输(Bulk Transfer),中断传输(Interrupt Transfer)和实时传输(IsochTransfer)。

  4、由于一个设备可能要适应多种情况,端点的设置会有多套,以备使用。端点设置称为接口(Interface)。USB设备展现给我们能够找到的东西就是这些Interface,我们选择要用的Interface,就可以找到Endpoint,再打开Endpoint,就可以传输数据了。所以,在驱动程序开始的时候,需要记录下这些Interface。

  5、例如:OV511+的端点0是控制端点,用来设置参数以及起停设备;端点1是实时传输端点,用来传输视频。端点1有8套不同的设置,主要区别就在于一次传输的数据帧的大小,所以在USBDeviceAttach的时候,要记录这些设置到驱动程序中,后面才能够选用。

二、描述符介绍
   标准的USB设备有5种USB描述符:设备描述符,配置描述符,字符串描述符,接口描述符,端点描述符。下面详解:

1、设备描述符:一个设备只有一个设备描述符

typedef struct _USB_DEVICE_DESCRIPTOR_
{
    BYTE        bLength,
    BYTE        bDescriptorType,
    WORD      bcdUSB,
    BYTE        bDeviceClass,
    BTYE        bDeviceSubClass,
    BYTE        bDeviceProtol,
    BYTE        bMaxPacketSize0,
    WORD      idVenderI,
    WORD      idProduct,
    WORD      bcdDevice,
    BYTE        iManufacturer,
    BYTE        iProduct,
    BYTE        iSerialNumber,
    BYTE        iNumConfiguations
}USB_DEVICE_DESCRIPTOR;

bLength : 描述符大小.固定为0x12.
bDescriptorType : 设备描述符类型.固定为0x01.
bcdUSB : USB 规范发布号.表示了本设备能适用于那种协议,如2.0=0200,1.1=0110等.

bDeviceClass : 类型代码(由USB指定)。当它的值是0时,表示所有接口在配置描述符里,并且所有接口是独立的。当它的值是1到FEH时,表示不同的接口关联的。当它的值是FFH时,它是厂商自己定义的.
bDeviceSubClass : 子类型代码(由USB分配).如果bDeviceClass值是0,一定要设置为0.其它情况就跟据USB-IF组织定义的编码.
bDeviceProtocol : 协议代码(由USB分配).如果使用USB-IF组织定义的协议,就需要设置这里的值,否则直接设置为0。如果厂商自己定义的可以设置为FFH.
bMaxPacketSize0 : 端点0最大分组大小(只有8,16,32,64有效).
idVendor : 供应商ID(由USB分配).

idProduct : 产品ID(由厂商分配).由供应商ID和产品ID,就可以让操作系统加载不同的驱动程序.
bcdDevice : 设备出产编码.由厂家自行设置.

iManufacturer : 厂商描述符字符串索引.索引到对应的字符串描述符. 为0则表示没有.
iProduct : :产品描述符字符串索引.同上.
iSerialNumber : 设备序列号字符串索引.同上.
bNumConfigurations : 可能的配置数.指配置字符串的个数
2、配置描述符:配置描述符定义了设备的配置信息,一个设备可以有多个配置描述符
typedef struct _USB_CONFIGURATION_DESCRIPTOR_
{
    BYTE      bLength,
    BYTE      bDescriptorType,
    WORD    wTotalLength,
    BYTE      bNumInterfaces,
    BYTE      bConfigurationValue,
    BYTE      iConfiguration,
    BYTE      bmAttributes,
    BYTE      MaxPower
}USB_CONFIGURATION_DESCRIPTOR;

bLength : 描述符大小.固定为0x09.
bDescriptorType : 配置描述符类型.固定为0x02.
wTotalLength : 返回整个数据的长度.指此配置返回的配置描述符,接口描述符以及端点描述符的全部大小.
bNumInterfaces : 配置所支持的接口数.指该配置配备的接口数量,也表示该配置下接口描述符数量.
bConfigurationValue : 作为Set Configuration的一个参数选择配置值.
iConfiguration : 用于描述该配置字符串描述符的索引.
bmAttributes : 供电模式选择.Bit4-0保留,D7:总线供电,D6:自供电,D5:远程唤醒.
MaxPower : 总线供电的USB设备的最大消耗电流.以2mA为单位.
3、接口描述符:接口描述符说明了接口所提供的配置,一个配置所拥有的接口数量通过配置描述符的bNumInterfaces决定
typedef struct _USB_INTERFACE_DESCRIPTOR_
{
    BYTE      bLength,
    BYTE      bDescriptorType,
    BYTE      bInterfaceNumber,
    BYTE      bAlternateSetting,
    BYTE      bNumEndpoint,
    BYTE      bInterfaceClass,
    BYTE      bInterfaceSubClass,
    BYTE      bInterfaceProtocol,
    BYTE      iInterface
}USB_INTERFACE_DESCRIPTOR;

bLength : 描述符大小.固定为0x09.
bDescriptorType : 接口描述符类型.固定为0x04.
bInterfaceNumber: 该接口的编号.
bAlternateSetting : 用于为上一个字段选择可供替换的位置.即备用的接口描述符标号.
bNumEndpoint : 使用的端点数目.端点0除外.
bInterfaceClass : 类型代码(由USB分配).
bInterfaceSunClass : 子类型代码(由USB分配).
bInterfaceProtocol : 协议代码(由USB分配).
iInterface : 字符串描述符的索引
4、端点描述符:USB设备中的每个端点都有自己的端点描述符,由接口描述符中的bNumEndpoint决定其数量
typedef struct _USB_ENDPOINT_DESCRIPTOR_
{
    BYTE        bLength,
    BYTE        bDescriptorType,
    BYTE        bEndpointAddress,
    BYTE        bmAttributes,
    WORD      wMaxPacketSize,
    BYTE        bInterval
}USB_ENDPOINT_DESCRIPTOR;

bLength : 描述符大小.固定为0x07.
bDescriptorType : 接口描述符类型.固定为0x05.
bEndpointType : USB设备的端点地址.Bit7,方向,对于控制端点可以忽略,1/0:IN/OUT.Bit6-4,保留.BIt3-0:端点号.
bmAttributes : 端点属性.Bit7-2,保留.BIt1-0:00控制,01同步,02批量,03中断.
wMaxPacketSize : 本端点接收或发送的最大信息包大小.
bInterval : 轮训数据传送端点的时间间隔.对于批量传送和控制传送的端点忽略.对于同步传送的端点,必须为1,对于中断传送的端点,范围为1-255.
5、字符串描述符:其中字符串描述符是可选的.如果不支持字符串描述符,其设备,配置,接口描述符内的所有字符串描述符索引都必须为0
typedef struct _USB_STRING_DESCRIPTION_
{
    BYTE      bLength,
    BYTE      bDescriptionType,
    BYTE      bString[1];
}USB_STRING_DESCRIPTION;

bLength : 描述符大小.由整个字符串的长度加上bLength和bDescriptorType的长度决定.
bDescriptorType : 接口描述符类型.固定为0x03.
bString[1] : Unicode编码字符串.

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/alien75/archive/2009/09/30/4622319.aspx

posted @ 2010-11-18 11:30 wrh 阅读(1996) | 评论 (0)编辑 收藏

  Windows主机端与自定义USB HID设备通信详解 收藏

Windows主机端与自定义USB HID设备通信详解

 

说明:

-          以下结论都是基于 Windows XP 系统所得出的,不保证在其他系统的适用性。

-          在此讨论的是 HID 自定义设备,对于标准设备,譬如 USB 鼠标和键盘,由于操作系统对其独占,许多操作未必能正确执行。

 

1 .   所使用的典型 Windows API

CreateFile

ReadFile

WriteFile

以下函数是 DDK 的内容:

HidD_SetFeature

HidD_GetFeature

HidD_SetOutputReport

HidD_GetInputReport

其中, CreateFile 用于打开设备; ReadFile 、 HidD_GetFeature 、 HidD_GetInputReport 用于设备到主机方向的数据通信; WriteFile 、 HidD_SetFeature 、 HidD_SetOutputReport 用于主机到设备方向的数据通信。鉴于实际应用,后文主要讨论 CreateFile , WriteFile , ReadFile , HidD_SetFeature 四个函数,明白了这四个函数,其它的可以类推之。

 

2 .   几个常见错误

       当使用以上 API 时,如果操作失败,调用 GetLastError() 会得到以下常见错误:

       6 :          句柄无效

       23 :        数据错误(循环冗余码检查)

       87 :        参数错误

       1784 :     用户提供的 buffer 无效

       后文将会详细说明这些错误情况。

 

3.         主机端设备枚举程序流程

 

 

4.         函数使用说明

CreateFile(devDetail->DevicePath,                                         // 设备路径

               GENERIC_READ | GENERIC_WRITE,                    // 访问方式

               FILE_SHARE_READ | FILE_SHARE_WRITE,         // 共享模式

               NULL,

               OPEN_EXISTING,                                           // 文件不存在时,返回失败

               FILE_FLAG_OVERLAPPED,                                 // 以重叠(异步)模式打开

               NULL);

 

在这里, CreateFile 用于打开 HID 设备,其中设备路径通过函数 SetupDiGetInterfaceDeviceDetail 取得。 CreateFile 有以下几点需要注意:

 

-     访问方式: 如果是系统独占设备,例如鼠标、键盘等等,应将此参数设置为 0 ,否则后续函数操作将失败(譬如 HidD_GetAttributes );也就是说,不能对独占设备进行除了查询以外的任何操作,所以能够使用的函数也是很有限的,下文的一些函数并不一定适合这些设备。在此顺便列出 MSDN 上关于此参数的说明:

If this parameter is zero, the application can query file and device attributes without accessing the device. This is useful if an application wants to determine the size of a floppy disk drive and the formats it supports without requiring a floppy in the drive. It can also be used to test for the file's or directory's existence without opening it for read or write access 。

-          重叠(异步)模式:此参数并不会在此处表现出明显的意义,它主要是对后续的 WriteFile , ReadFile 有影响。如果这里设置为重叠(异步)模式,那么在使用 WriteFile , ReadFile 时也应该使用重叠(异步)模式,反之亦然。这首先要求 WriteFile , ReadFile 的最后一个参数不能为空( NULL )。否则,便会返回 87 (参数错误)错误号。当然, 87 号错误并不代表就是此参数不正确,更多的信息将在具体讲述这两个函数时指出。此参数为 0 时,代表同步模式,即 WriteFile , ReadFile 操作会在数据处理完成之后才返回,否则阻塞在函数内部。

 

ReadFile(hDev,                                 // 设备句柄,即 CreateFile 的返回值

              recvBuffer,                          // 用于接收数据的 buffer

              IN_REPORT_LEN,              // 要读取数据的长度

              &recvBytes,                         // 实际收到的数据的字节数

              &ol);                                  // 异步模式

 

在这里, ReadFile 用于读取 HID 设备通过中断 IN 传输发来的输入报告 。有以下几点要注意:

 

1 、 ReadFile 的调用不会引起设备的任何反应,即 HID 设备与主机之间的中断 IN 传输不与 ReadFile 打交道。实际上主机会在最大间隔时间(由设备的端点描述符来指定)内轮询设备,发出中断 IN 传输的请求。“读取”即意味着从某个 buffer 里面取回数据,实际上这个 buffer 就是 HID 设备驱动中的 buffer 。这个 buffer 的大小可以通过 HidD_SetNumInputBuffers 来改变。在 XP 上缺省值是 32 (个报告)。

 

2 、读取的数据对象是输入报告,也即通过中断输入管道传入的数据。所以,如果设备不支持中断 IN 传输,那么是无法使用此函数来得到预期结果的。实际上这种情况不可能在 HID 中出现,因为协议指明了至少要有一个中断 IN 端点。

 

3 、 IN_REPORT_LEN 代表要读取的数据的长度(实际的数据正文 + 一个 byte 的报告 ID ),这里是一个常数,主要是因为设备固件的信息我是完全知道的,当然知道要读取多少数据(也就是报告的长度);不过也可以通过另外的函数( HidD_GetPreparsedData )来事先取得报告的长度,这里不做详细讨论。因为很难想象在不了解固件信息的情况下来做自定义设备的 HID 通信,在实际应用中一般来说就是固件与 PC 程序匹配着来开发。此参数如果设置过大,不会有实质性的错误,在 recvBytes 参数中会输出实际读到的长度;如果设置过小,即小于报告的长度,会返回 1784 号错误(用户提供的 buffer 无效)。

 

4 、关于异步模式。前面已经提过,此参数的设置必须与 CreateFile 时的设置相对应,否则会返回 87 号错误(参数错误)。如果不需要异步模式,此参数需置为 NULL 。在这种情况下, ReadFile 会一直等待直到数据读取成功,所以会阻塞住程序的当前过程。

 

       WriteFile(hDev,                                 // 设备句柄,即 CreateFile 的返回值

                     reportBuf,                           // 存有待发送数据的 buffer

                     OUT_REPORT_LEN,           // 待发送数据的长度

                     &sendBytes,                        // 实际收到的数据的字节数

                     &ol);                                  // 异步模式

 

       在这里, WriteFile 用于传输一个输出报告 给 HID 设备。有以下几点要注意:

 

1、  与 ReadFile 不同, WriteFile 函数被调用后,虽然也是经过驱动程序,但是最终会反映到设备中。也就是说,调用 WriteFile 后,设备会接收到输出报告的请求。如果设备使用了中断 OUT 传输,则 WriteFile 会通过中断 OUT 管道来进行传输;否则会使用 SetReport 请求通过控制管道来传输。

 

2、  OUT_REPORT_LEN 代表要写入的数据长度(实际的数据正文 + 一个 byte 的报告 ID )。如果大于实际报告的长度,则使用实际报告长度;如果小于实际报告长度,会返回 1784 号错误(用户提供的 buffer 无效)。

 

3、  reportBuf [0] 必须存有待发送报告的 ID ,并且此报告 ID 指示的必须是输出报告,否则会返回 87 号错误(参数错误)。这种情况可能容易被程序员忽略,结果不知错误号所反映的是什么,网上也经常有类似疑问的帖子。顺便指出,输入报告、输入报告、特征报告这些报告类型,是反映在 HID 设备的报告描述符中。后文将做举例讨论。

 

4、  关于异步模式。前面已经提过,此参数的设置必须与 CreateFile 时的设置相对应,否则会返回 87 号错误(参数错误)。如果不需要异步模式,此参数需置为 NULL 。在这种情况下, WriteFile 会一直等待直到数据读取成功,所以会阻塞住程序的当前过程。

 

HidD_SetFeature(hDev,                                    // 设备句柄,即 CreateFile 的返回值

                     reportBuf,                                   // 存有待发送数据的 buffer

                     FEATURE_REPORT_LEN);        //buffer 的长度

HidD_SetOutputReport(hDev,                            // 设备句柄,即 CreateFile 的返回值

                     reportBuf,                                   // 存有待发送数据的 buffer

                     OUT_REPORT_LEN);                //buffer 的长度

 

HidD_SetFeature 发送一个特征报告 给设备, HidD_ SetOutputReport 发送一个输出报告 给设备。注意以下几点:

 

1、  跟 WriteFile 类似,必须在 reportBuf [0] 中指明要发送的报告的 ID ,并且和各自适合的类型相对应。也就是说, HidD_SetFeature 只能发送特征报告,因此报告 ID 必须是特征报告的 ID ; HidD_SetOutputReport 只能发送输出报告,因此报告 ID 只能是输出报告的 ID 。

2、  这两个函数最常返回的错误代码是 23 (数据错误)。包括但不仅限于以下情况:

- 报告 ID 与固件描述的不符。

- 传入的 buffer 长度少于固件描述的报告的长度。

据有关资料反映(非官方文档),只要是驱动程序对请求无反应,都会产生此错误。

 

5.         常见错误汇总

- HID ReadFile

  - Error Code 6 (handle is invalid)

    传入的句柄无效

  - Error Code 87 ( 参数错误 )

    很可能是 createfile 时声明了异步方式,但是读取时按同步读取。

  - Error Code 1784 ( 用户提供的 buffer 无效 ):

    传参时传入的“读取 buffer 长度”与实际的报告长度不符。

 

- HID WriteFile

  - Error Code 6 (handle is invalid)

    传入的句柄无效

  - Error Code 87 (参数错误)

    - CreateFile 时声明的同步 / 异步方式与实际调用 WriteFile 时传入的不同。

    - 报告 ID 与固件中定义的不一致( buffer 的首字节是报告 ID )

  - Error Code 1784 ( 用户提供的 buffer 无效 )

    传参时传入的“写入 buffer 长度”与实际的报告长度不符。

 

- HidD_SetFeature

- HidD_SetOutputReport

  - Error Code 1 (incorrect function)

    不支持此函数,很可能是设备的报告描述符中未定义这样的报告类型(输入、输出、特征)

  - Error Code 6 (handle is invalid)

    传入的句柄无效

  - Error Code 23 (数据错误(循环冗余码检查))

    - 报告 ID 与固件中定义的不相符( buffer 的首字节是报告 ID )

    - 传入的 buffer 长度少于固件定义的报告长度(报告正文 +1byte, 1byte 为报告 ID )

    - 据相关资料反映(非官方文档),只要是驱动程序不接受此请求(对请求无反应),都会产生此错误

 

6.         报告描述符及数据通信程序示例

报告描述符(由于是汇编代码,所以不必留意其语法,仅需注意表中的每个数据都占 1 个字节):

 

_ReportDescriptor:                              // 报告描述符

       .dw 0x06,  0x00, 0xff               // 用法页

    .dw 0x09,  0x01                     // 用法 ( 供应商用法 1)

    .dw 0xa1,  0x01                      // 集合开始

    .dw 0x85,  0x01                         // 报告 ID(1)

    .dw 0x09,  0x01                  // 用法 ( 供应商用法 1)  

    .dw 0x15,  0x00                 // 逻辑最小值 (0)

    .dw 0x26,  0xff, 0x0                     // 逻辑最大值 (255)

    .dw 0x75,  0x08               // 报告大小 (8)

    .dw 0x95,  0x07                        // 报告计数 (7)

    .dw 0x81,  0x06                // 输入 (数据,变量,相对值)

 

    .dw 0x09,  0x01                     // 用法 ( 供应商用法 1)  

    .dw 0x85,  0x03                         // 报告 ID ( 3 )

    .dw 0xb1,   0x06                         // 特征 (数据,变量,相对值)

 

       .dw 0x09,  0x01                    // 用法 ( 供应商用法 1)

    .dw 0x85,  0x02                         // 报告 ID ( 2 )

    .dw 0xb1,  0x06                         // 特征 (数据,变量,相对值)

 

     .dw 0x09,  0x01                     // 用法 ( 供应商用法 1)  

    .dw 0x85,  0x04                         // 报告 ID ( 4 )

    .dw 0x91,   0x06                         // 输出 (数据,变量,相对值)

    .dw   0xc0                    // 结合结束

_ReportDescriptor_End:

 

这个报告描述符,定义了 4 个不同的报告:输入报告 1 ,特征报告 2 ,特征报告 3 ,输出报告 4 (数字代表其报告 ID )。为了简化,每个报告都是 7 个字节(加上报告 ID 就是 8 个字节)。下面用一个简单的示例来描述 PC 端与 USB HID 设备进行通信的一般方法。

 

view plaincopy to clipboardprint?
#define     USB_VID       0xFC0   
#define     USB_PID       0x420   
HANDLE OpenMyHIDDevice(int overlapped);   
void HIDSampleFunc()   
{   
    HANDLE       hDev;   
    BYTE         recvDataBuf[8];   
    BYTE         reportBuf[8];   
    DWORD        bytes;   
    hDev = OpenMyHIDDevice(0);                                // 打开设备,不使用重叠(异步)方式 ;   
    if (hDev == INVALID_HANDLE_VALUE)   
        return;   
    reportBuf[0] = 4;                                         // 输出报告的报告 ID 是 4   
    memset(reportBuf, 0, 8);   
    reportBuf[1] = 1;   
    if (!WriteFile(hDev, reportBuf, 8, &bytes, NULL))         // 写入数据到设备   
         return;   
    ReadFile(hDev, recvDatatBuf, 8, &bytes, NULL);            // 读取设备发给主机的数据   
}   
    
HANDLE OpenMyHIDDevice(int overlapped)   
{   
    HANDLE hidHandle;   
    GUID hidGuid;   
    HidD_GetHidGuid(&hidGuid);   
    HDEVINFO hDevInfo = SetupDiGetClassDevs(&hidGuid,   
                                            NULL,   
                                            NULL,   
                                            (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));   
    if (hDevInfo == INVALID_HANDLE_VALUE)   
    {   
        return INVALID_HANDLE_VALUE;   
    }   
    SP_DEVICE_INTERFACE_DATA devInfoData;   
    devInfoData.cbSize = sizeof (SP_DEVICE_INTERFACE_DATA);   
    int deviceNo = 0;   
    SetLastError(NO_ERROR);   
    while (GetLastError() != ERROR_NO_MORE_ITEMS)   
    {   
        if (SetupDiEnumInterfaceDevice (hDevInfo,   
                                        0,   
                                       &hidGuid,   
                                       deviceNo,   
                                       &devInfoData))   
        {   
            ULONG  requiredLength = 0;   
            SetupDiGetInterfaceDeviceDetail(hDevInfo,   
                                            &devInfoData,   
                                            NULL,   
                                            0,   
                                            &requiredLength,   
                                             NULL);  
            PSP_INTERFACE_DEVICE_DETAIL_DATA devDetail = (SP_INTERFACE_DEVICE_DETAIL_DATA*) malloc (requiredLength);   
            devDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);   
            if(!SetupDiGetInterfaceDeviceDetail(hDevInfo,   
                                                 &devInfoData,   
                                                 devDetail,   
                                                 requiredLength,   
                                                 NULL,   
                                                 NULL))   
            {   
                free(devDetail);   
                SetupDiDestroyDeviceInfoList(hDevInfo);   
                return INVALID_HANDLE_VALUE;   
            }   
            if (overlapped)   
            {   
                hidHandle = CreateFile(devDetail->DevicePath,   
                                       GENERIC_READ | GENERIC_WRITE,   
                                       FILE_SHARE_READ | FILE_SHARE_WRITE,   
                                       NULL,   
                                       OPEN_EXISTING,           
                                       FILE_FLAG_OVERLAPPED,   
                                       NULL);   
            }   
            else   
            {   
                hidHandle = CreateFile(devDetail->DevicePath,   
                                       GENERIC_READ | GENERIC_WRITE,   
                                       FILE_SHARE_READ | FILE_SHARE_WRITE,   
                                       NULL,   
                                       OPEN_EXISTING,           
                                       0,   
                                       NULL);   
            }   
            free(devDetail);   
            if (hidHandle==INVALID_HANDLE_VALUE)   
            {   
                SetupDiDestroyDeviceInfoList(hDevInfo);   
                free(devDetail);   
                return INVALID_HANDLE_VALUE;   
            }   
            _HIDD_ATTRIBUTES hidAttributes;   
            if(!HidD_GetAttributes(hidHandle, &hidAttributes))   
            {   
                CloseHandle(hidHandle);   
                SetupDiDestroyDeviceInfoList(hDevInfo);   
                return INVALID_HANDLE_VALUE;   
            }   
            if (USB_VID == hidAttributes.VendorID   
                && USB_PID  == hidAttributes.ProductID)   
            {   
                break;   
            }   
            else   
            {   
                CloseHandle(hidHandle);   
                ++deviceNo;   
            }   
        }   
    }   
    SetupDiDestroyDeviceInfoList(hDevInfo);   
    return hidHandle;   
}


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/kevinyujm/archive/2009/06/12/4264506.aspx

posted @ 2010-11-18 09:12 wrh 阅读(6190) | 评论 (0)编辑 收藏
1.添加消息
消息映射
BEGIN_MESSAGE_MAP(CAFT_1394Dlg, CDialog)
//{{AFX_MSG_MAP(CAFT_1394Dlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
...
ON_WM_DEVICECHANGE()
//}}AFX_MSG_MAP
声明消息响应函数:
afx_msg BOOL OnDeviceChange(UINT nEventType, DWORD dwData);
定义函数内容:
BOOL XXXXXXX::OnDeviceChange(UINT nEventType,DWORD dwData)
{
DEV_BROADCAST_DEVICEINTERFACE* dbd = (DEV_BROADCAST_DEVICEINTERFACE*) dwData;
//这里进行信息匹配,比如guid等
//针对各个事件进行处理.
switch (nEventType)
{
case DBT_DEVICEREMOVECOMPLETE:
...
break;
case DBT_DEVICEARRIVAL:
...
break;
...
...
...
default:
break;
}
return TRUE;
}
2.注册设备
if (Handle == NULL)
return FALSE;
DEV_BROADCAST_DEVICEINTERFACE DevInt;
memset(&DevInt,0,sizeof(DEV_BROADCAST_DEVICEINTERFACE));
DevInt.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
DevInt.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
DevInt.dbcc_classguid = GetCurrentUSBGUID();//m_usb->GetDriverGUID();
if (!RegisterDeviceNotification(m_hWnd, &DevInt,DEVICE_NOTIFY_WINDOW_HANDLE) )
return FALSE;
只有注册了该设备,OnDeviceChange才能获得详细的信息,否则收到的参数都是0007.
手动添加吧
我也没找到 
posted @ 2010-11-18 08:52 wrh 阅读(3228) | 评论 (0)编辑 收藏
仅列出标题
共25页: First 3 4 5 6 7 8 9 10 11 Last 

导航

<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

统计

常用链接

留言簿(19)

随笔档案

文章档案

收藏夹

搜索

最新评论

阅读排行榜

评论排行榜