《圈圈教你玩USB》 第三章 USB鼠标的实现——看书笔记( 2 )

3.5 USB标准请求

标准请求的用途:
    主要用于设备的枚举过程。
标准请求的内容:
    8字节的数据,包含了数据过程所需要传输的数据传输方向、长度、数据类型等信息。
    正式由于8字节标准请求的原因,USB协议规定,端点0的最大包长至少为8字节。即任何USB设备都能够(且必须要)接收8字节的标准请求。    
标准请求的传输过程:
    在控制传输的建立过程通过默认控制端点0发出的。

3.5.1 USB标准设备请求的数据结构

1) USB协议规定的标准设备请求的数据结构
偏移量/字节 大小/字节 取值 描述
0 bmRequestType 1 位图 请求的特征:
 D7:数据传输方向
  0 = 主机到设备(输出)
  1 = 设备到主机(输入)
 D6~5:请求的类型
  0 = 标准
  1 = 类
  2 = 厂商
  3 = 保留
 D4~0:请求的接收者
  0 = 设备
  1 = 接口
  2 = 端点
  3 = 其他
  4~31 = 保留
1 bRequest 1 数值 请求代码
2 wValue 2 数值 该域的意义由具体的请求决定
4 wIndex 2 索引或偏移量 该域的意义由具体的请求决定
6 wLength 2 字节数 数据过程(如果有)所需要传输的字节数
本节只介绍USB协议的标准请求,即bmRequestType的D6~5位为00的请求。
2) USB协议定义了11个标准请求(bRequest),其 名字 请求代码 如下表所示:
序号 bRequest Value
1 GET_STATUS 0
2 CLEAR_FEATURE 1
3 SET_FEATURE 3
4 SET_ADDRESS 5
5 GET_DESCRIPTOR 6
6 SET_DESCRIPTOR 7
7 GET_CONFIGURATION 8
8 SET_CONFIGURATION 9
9 GET_INTERFACE 10
10 SET_INTERFACE 11
11 SYNCH_FRAME 12
3)各种标准请求的结构及需要传输的数据
不同的请求对于其接收者、wValue和wIndex,其各字段的意义是不一样的。
下表是各种标准请求的结构和数据过程需要传输的数据。
其中第一列有的有多个,主要是最低5位不同,即表示接收者不同。
有的请求只能发送到设备,而有的请求可以发送到设备、接口和端点。
常用的请求为:GET_DESCRIPTOR、SET_ADDRESS、SET_CONFIGURATION。
序号 bmRequestType bRequest wValue wIndex wLength 数据过程
1 0000 0000b CLEAR_FEATURE 特征选择 0 0 没有
2 0000 0001b 接口号
3 0000 0010b 端点号
4 1000 0000b GET_CONFIGURATION 0 0 1 配置值
5 1000 0000b GET_DESCRIPTOR 描述符类型和索引 0或语言ID 描述符长度 描述符
6 1000 0001b GET_INTERFACE 0 接口号 1 备用接口号
7 1000 0000b GET_STATUS 0 0 2 设备、
接口或
端点状态
8 1000 0001b 接口号
9 1000 0010b 端点号
10 0000 0000b SET_ADDRESS 设备地址 0 0 没有
11 0000 0000b SET_CONFIGURATION 配置值 0 0 没有
12 0000 0000b SET_DESCRIPTOR 描述符类型和索引 0或者语言ID 描述符的长度 描述符
13 0000 0000b SET_FEATURE 特性选择 0 0 没有
14 0000 0001b 接口号
15 0000 0010b 端点号
16 0000 0001b SET_INTERFACE 备用接口号 接口号 0 没有
17 1000 0010b SYNCH_FRAME 0 端点号 2 帧号

3.5.2 GET_DESCRIPTOR请求

    GET_DESCRIPOTOR(获取描述符)请求是在枚举过程中用的最多的一个请求。主机通过发送获取描述符
请求读取设备的各种描述符,从而可以获知设备类型、端点情况等众多重要信息。
    获取描述符的接收者只能是设备,从bmRequestType的Bit7可以看出,它是请求数据输入的。
    bRequest的值为0x06(GET_DESCRIPTOR)。
   GET_DESCRIPTOR请求的结构和需要传输的数据如下表所示:
bmRequestType bRequest wValue wIndex wLength 数据过程
1000 0000b(0x80) GET_DESCRIPTOR(0x06) 描述符类型和索引 0或语言ID 描述符的长度 描述符
   注:wValue、wIndex、wLength这三个域都是两字节的,在USB协议中使用小端结构,即低字节在先。
    wValue域的第一字节(低字节)表示的是索引号,用来选择同一种描述符(例如字符串描述符和配置描述符)中具体的某个描述符。
    wValue域的第二字节表示描述符的类型编号。
描述符类型 编号
设备描述符 1
配置描述符 2
字符串描述符 3
接口描述符 4
端点描述符 5
    wIndex域只在获取字符串描述符中有用,表示字符串的语言ID号,获取除字符串描述符的其他描述符时,wIndex的值为0。
    wLength域为请求设备返回数据的字节数,设备实际返回的字节数可以比该域指定的字节数少。
    对于全速和低速模式,获取描述符的标准请求只有三种:获取设备描述符、获取配置描述符和获取字符串描述符。
另外的接口描述符和端点描述符是跟随配置描述符一并返回的,不能单独请求返回(如果单独返回,主机无
法确认它们属于哪个配置)。
    前面的调试信息中,收到的数据为 0x80 0x06 0x00 0x01 0x00 0x00 0x40 0x00,对照标准请求的结构可
知,它是一个请求设备描述符的标准请求,请求的数据长度为64字节。

3.5.3 SET_ADDRESS请求

    设置地址请求的作用:
        主机指定设备使用指定的地址。
    如何设置地址:
        指定的地址包含在8字节数据中的wValue字段中。
         在设备使用默认地址0时,先根据需要发送设备描述符,之后主机发送设置地址请求。
        设置地址请求没有数据过程,故wLength值为0。wIndex也用不到,为0。
        设备收到设置地址请求后,直接进入状态过程,等待主机读取0长状态数据包。
        主机成功读取到状态数据包(用ACK响应设备)后,设备将启用新的地址。这以后的传输中,主机就将
使用新的地址与设备进行通信。
    SET_ADDRESS请求的结构:
bmRequestType bRequest wValue wIndex wLength 数据过程
0000 0000b(0x00) SET_ADDRESS(0x05) 设备地址 0x0000 0x0000 没有

3.5.4 SET_CONFIGURATION请求

    如何设置配置:
        设置配置的请求和设置地址请求类似,区别在于wValue域的意义。
在设置地址请求中,wValue第一字节(低字节)为设备地址;
    在设置配置请求中,wValue第一字节为配置的值。 当该值与某配置描述符中的配置编号一致时,表示
选中该配置。该值通常为1,因为大多数USB设备只有一种配置,配置编号为1;如果该值为0,则让设备进
入设置地址状态。
        设备只有在收到非0的配置值之后,才能启用它的非0端点。
    SET_CONFIGURATION请求的结构:
bmRequestType bRequest wValue wIndex wLength 数据过程
0000 0000b(0x00) SET_CONFIGURATION(0x09) 配置值 0x0000 0x0000 没有

3.6 设备描述符的实现

    已知每个设备都必须有且只有一个设备描述符,它的结构如下表所示。
序号 偏移量/字节 大小/字节 说明 备注
1 0 bLength 1 该描述符的长度(18字节)  
2 1 bDescriptorType 1 描述符的类型(设备描述符为0x01)  
3 2 bcdUSB 2 本设备所使用的USB协议版本 它使用的是BCD码,例如USB2.0就是0x0200,而USB1.1就是0x0110。因为使用的是小端结构,所以实际传输时USB2.0的bcdUSB拆成两字节0x00和0x02,而USB1.1的bcdUSB拆成两个字节0x10和0x01。
4 4 bDeviceClass 1 类代码 大多数标准的USB设备类,该字段通常为0,而在接口描述符中的bInterfaceClass中指定接口所实现的功能。
当bDeviceClass为0时,下面的bDeviceSubClass也为0。
如果bDeviceClass为0xFF,表示是厂商自定义的设备类。
5 5 bDeviceSubClass 1 子类代码 当类代码不是0和0xFF时,子类代码有USB协议规定。
当类代码为0时,此处必须也为0.
6 6 bDeviceProtocol 1 设备所使用的协议 当该字段为0时,表示设备不使用类所定义的协议;
当该字段为0xFF时,表示设备使用厂商自定义的协议。
本字段必须结合类代码和子类代码一起使用才有意义,因此,当类代码为0时,bDeviceProtocol也要为0.
7 7 bMaxPackeSize0 1 端点0最大包长 可以取值:8、16、32、64
8 8 idVender 2 厂商ID 主机通常靠VID、PID和产品序列号来安装和加载驱动,如果使用了别人的ID号,可能导致驱动程序安装或加载错误,从而导致设备无法工作。注意小端结构。
9 10 idProduct 2 产品ID  
10 12 bcdDevice 2 设备版本号 当同一个产品升级后(例如修改了固件增加了新功能),可以通过修改设备版本号来区别
11 14 iManufacturer 1 描述厂商的字符串索引 当该值为0时,表示没有厂商字符串。
主机在获取设备描述符时,会将索引值放在wValue的第一字节中,用来选择不同的字符串。
12 15 iProduct 1 描述产品的字符串的索引 当该值为0时,表示没有产品字符串。
当第一次插入某个USB设备时,会在windows的右下角弹出一个对话框,显示发现新硬件,并且会显示该设备的名称。这里显示的信息就是从产品字符串中获取的。
13 16 iSerialNumber 1 产品序列号字符串索引 当该值为0时,表示没有序列号字符串。
用于区分多个VID和PID相同的设备,防止设备无法正确识别。
14 17 bNumConfigurations 1 可能的配置数 每种配置都有一个配置描述符,主机通过发送设置配置来选择某种配置。大部分USB设备只有一个配置,即该字段的值为1。
    USB鼠标实例的设备描述符:
   
   
  1. //USB设备描述符的定义
  2. code uint8 DeviceDescriptor[0x12]= //设备描述符为18字节
  3. {
  4. 0x12,    //bLength字段。设备描述符的长度为18(0x12)字节
  5. 0x01,    //bDescriptorType字段。设备描述符的编号为0x01
  6. 0x10,    //bcdUSB字段。这里设置版本为USB1.1,即0x0110。由于是小端结构,所以低字节在先,即0x10,0x01。
  7. 0x01,
  8. 0x00,    //bDeviceClass字段。我们不在设备描述符中定义设备类,而在接口描述符中定义设备类,所以该字段的值为0。
  9. 0x00,    //bDeviceSubClass字段。bDeviceClass字段为0时,该字段也为0。
  10. 0x00,    //bDeviceProtocol字段。bDeviceClass字段为0时,该字段也为0。
  11. 0x10,    //bMaxPacketSize0字段。PDIUSBD12的端点0大小的16字节。
  12. 0x88,    //idVender字段。厂商ID号,我们这里取0x8888,仅供实验用。实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。注意小端模式,低字节在先。
  13. 0x88,
  14. 0x01,    //idProduct字段。产品ID号,由于是第一个实验,我们这里取0x0001。注意小端模式,低字节应该在前。
  15. 0x00,
  16. 0x00,    //bcdDevice字段。我们这个USB鼠标刚开始做,就叫它1.0版吧,即0x0100。小端模式,低字节在先。
  17. 0x01,
  18. 0x01,    //iManufacturer字段。厂商字符串的索引值,为了方便记忆和管理,字符串索引就从1开始吧。
  19.  
  20. 0x02,    //iProduct字段。产品字符串的索引值。刚刚用了1,这里就取2吧。注意字符串索引值不要使用相同的值。
  21. 0x03,    //iSerialNumber字段。设备的序列号字符串索引值。这里取3就可以了。
  22.  
  23. 0x01     //bNumConfigurations字段。该设备所具有的配置数。我们只需要一种配置就行了,因此该值设置为1。
  24. };

3.7设备描述符的返回

1. 如何返回设备描述符

    通过控制输入端点0返回。
    1)返回设备描述符的大致流程:
          在端点0的输出中断处理函数中,
        先对接收到的建立过程的数据进行判断,
        如果是获取设备描述符的请求, 则将设备描述符的内容写入端点0输入缓冲区中,并使能端点发送。
        当主机在下一次发送IN令牌包后,D12将会自动将端点0输入缓冲区中的数据返回给主机,这样就实
    现了获取设备描述符的请求。
     2)如何将数据写入端点缓冲区:
    ① 选择端点;
    ② 写数据到端点缓冲区
    Write Buffer命令 ,命令代码是0xF0。
    写入的数据结构:1(0x00)+1(len)+n(data)。
    ③ 将端点的发送缓冲区中的数据设置为有效,数据才在主机发送IN令牌包后发送出去。
    Validate Buffer命令,命令代码为0xFA。
     3)将数据写入缓冲区的代码:
   
   
  1. //函数功能:将数据写入端点缓冲区函数。
  2. //入口参数:Endp:端点号;Len:需要发送的长度;Buf:保存数据的缓冲区。
  3. //返 回:Len的值。
  4. uint8 D12WriteEndpointBuffer(uint8 Endp,uint8 Len,uint8 * Buf)
  5. {
  6. uint8 i;
  7. D12SelectEndpoint(Endp);             //选择端点
  8. D12WriteCommand(D12_WRITE_BUFFER);   //写Write Buffer命令
  9. D12WriteByte(0);                     //该字节必须写0
  10. D12WriteByte(Len);                  //写需要发送数据的长度
  11. #ifdef DEBUG1                         //如果定义了DEBUG1,则需要显示调试信息
  12. Prints("写端点");
  13. PrintLongInt(Endp/2);                 //端点号。由于D12特殊的端点组织形式,
  14.                  //这里的0和1分别表示端点0的输出和输入;
  15.                 //而2、3分别表示端点1的输出和输入;
  16.                 //3、4分别表示端点2的输出和输入。
  17.                 //因此要除以2才显示对应的端点。
  18. Prints("缓冲区");
  19. PrintLongInt(Len);                 //写入的字节数
  20. Prints("字节。\r\n");
  21. #endif
  22. D12SetPortOut();                     //将数据口设置为输出状态(注意这里为空宏,移植时可能有用)
  23. for(i=0;i<Len;i++)
  24. {
  25. //这里不直接调用写一字节的函数,而直接在这里模拟时序,可以节省时间
  26. D12ClrWr();                         //WR置低
  27. D12SetData(*(Buf+i));               //将数据放到数据线上
  28. D12SetWr();                        //WR置高,完成一字节写
  29. #ifdef DEBUG1
  30. PrintHex(*(Buf+i));                //如果需要显示调试信息,则显示发送的数据
  31. if((i+1)%16==0)Prints("\r\n");      //每16字节换行一次
  32. #endif
  33. }
  34. #ifdef DEBUG1
  35. if((Len%16)!=0)Prints("\r\n");       //换行
  36. #endif
  37. D12SetPortIn();                      //数据口切换到输入状态
  38. D12ValidateBuffer();                 //使端点数据有效
  39. return Len;                          //返回Len
  40. }
     4)使能端点缓冲区的代码
   
   
  1. //函数功能:使能发送端点缓冲区数据有效的函数。
  2. //备 注:只有使用该函数使能发送端点数据有效之后,数据才能发送出去。
  3. ********************************************************************/
  4. void D12ValidateBuffer(void)
  5. {
  6. D12WriteCommand(D12_VALIDATE_BUFFER);
  7. }
    5 )返回设备描述符的详细流程:
    分析建立过程数据包的内容,以确定何时返回设备描述符。
    分析数据包的内容主要使用if和switch语句来散转,对不同的请求做不同的处理。

    在分析数据前,先申请一些变量,用来保存这些标准请求的不同字段。另外,还需要一个用来保存发送位
置的指针变量和一个保存剩余字节数的变量,以及一个是否需要返回0长度数据包的标志。
    变量定义代码如下:
   
   
  1. idata uint8 Buffer[16]; //读端点0用的缓冲区
  2. //USB设备请求的各字段
  3. uint8 bmRequestType;
  4. uint8 bRequest;
  5. uint16 wValue;
  6. uint16 wIndex;
  7. uint16 wLength;
  8. uint8 * pSendData;//当前发送数据的位置
  9. uint16 SendLength;//需要发送数据的长度
  10. uint8 NeedZeroPacket;
  11. //是否需要发送0数据包的标志。在USB控制传输的数据过程中,当返回的数据包字节数少于最大包长时,会认为数据过程结束。
  12. //当请求的字节数比实际需要返回的字节数长,而实际返回的字节数又刚好是端点0大小的整数倍时,就需要返回一个0长度的
  13. //数据包来结束数据过程。因此这里增加一个标志,供程序决定是否需要返回一个0长度的数据包。
    接着,将从缓冲区中读出的数据分别添加到设备请求的各字段中
    
    
  1. //将缓冲数据填到设备请求的各字段中
  2. bmRequestType=Buffer[0];
  3. bRequest=Buffer[1];
  4. wValue=Buffer[2]+(((uint16)Buffer[3])<<8);
  5. wIndex=Buffer[4]+(((uint16)Buffer[5])<<8);
  6. wLength=Buffer[6]+(((uint16)Buffer[7])<<8);
    具体散转顺序:
    首先,判断 bmRequestType的Bit7,如果是1,则说明是输入请求;如果是0,则说明是输出请求。
    再判断 Bit6~5,如果是00,则说明是标准请求;如果是01,则说明是类请求;如果是10,则说明是厂商请求。
    再写上所有的标准请求情况,如果是获取描述符的请求,则对bRequest进行散转,看具体是请求什么描述符;
    如果是获取字符串描述符,则还需要对wValue值的低字节散转,看是哪个字符串描述符。
    散转的代码实现:
    //判断具体的请求,根据不同的请求进行相关操作。如果D7位为1,则说明是输入请求。
    //还需要对接收者进行散转,因为不同的请求接收者不同。接收者在bmRequestType的Bit4~0中定义。
    //此处为了简化操作,省略了对接收者的判断。例如获取描述符的请求,只根据描述符的类型来区别。
   
   
  1. //下面的代码判断具体的请求,并根据不同的请求进行相关操作,如果D7位为1,则说明是输入请求
  2. if((bmRequestType&0x80)==0x80)
  3. {
  4. //根据bmRequestType的D6~5位散转,D6~5位表示请求的类型
  5. //0为标准请求,1为类请求,2为厂商请求。
  6. switch((bmRequestType>>5)&0x03)
  7. {
  8. case 0: //标准请求
  9. #ifdef DEBUG0
  10. Prints("USB标准输入请求:");
  11. #endif
  12. //USB协议定义了几个标准输入请求,我们实现这些标准请求即可
  13. //请求的代码在bRequest中,对不同的请求代码进行散转
  14. switch(bRequest)
  15. {
  16. case GET_CONFIGURATION: //获取配置
  17. #ifdef DEBUG0
  18. Prints("获取配置。\r\n");
  19. #endif
  20. break;
  21. case GET_DESCRIPTOR: //获取描述符
  22. #ifdef DEBUG0
  23. Prints("获取描述符——");
  24. #endif
  25.      /*对具体描述符的散转和描述符的返回*/
  26. break;
  27. case GET_INTERFACE: //获取接口
  28. #ifdef DEBUG0
  29. Prints("获取接口。\r\n");
  30. #endif
  31. break;
  32. case GET_STATUS: //获取状态
  33. #ifdef DEBUG0
  34. Prints("获取状态。\r\n");
  35. #endif
  36. break;
  37. case SYNCH_FRAME: //同步帧
  38. #ifdef DEBUG0
  39. Prints("同步帧。\r\n");
  40. #endif
  41. break;
  42. default: //未定义的标准请求
  43. #ifdef DEBUG0
  44. Prints("错误:未定义的标准输入请求。\r\n");
  45. #endif
  46. break;
  47. }
  48. break;
  49. case 1: //类请求
  50. #ifdef DEBUG0
  51. Prints("USB类输入请求:\r\n");
  52. #endif
  53. break;
  54. case 2: //厂商请求
  55. #ifdef DEBUG0
  56. Prints("USB厂商输入请求:\r\n");
  57. #endif
  58. break;
  59. default: //未定义的请求。这里只显示一个报错信息。
  60. #ifdef DEBUG0
  61. Prints("错误:未定义的输入请求。\r\n");
  62. #endif
  63. break;
  64. }
  65. }
  66. //否则说明是输出请求
  67. else //if(bmRequestType&0x80==0x80)之else
  68. {
  69. //根据bmRequestType的D6~5位散转,D6~5位表示请求的类型
  70. //0为标准请求,1为类请求,2为厂商请求。
  71. switch((bmRequestType>>5)&0x03)
  72. {
  73. case 0: //标准请求
  74. #ifdef DEBUG0
  75. Prints("USB标准输出请求:");
  76. #endif
  77. //USB协议定义了几个标准输出请求,我们实现这些标准请求即可
  78. //请求的代码在bRequest中,对不同的请求代码进行散转
  79. switch(bRequest)
  80. {
  81. case CLEAR_FEATURE: //清除特性
  82. #ifdef DEBUG0
  83. Prints("清除特性。\r\n");
  84. #endif
  85. break;
  86. case SET_ADDRESS: //设置地址
  87. #ifdef DEBUG0
  88. Prints("设置地址。地址为:");
  89. PrintHex(wValue&0xFF); //显示所设置的地址
  90. Prints("\r\n");
  91. #endif
  92.      /*设置地址的具体操作*/
  93. break;
  94. case SET_CONFIGURATION: //设置配置
  95. #ifdef DEBUG0
  96. Prints("设置配置。\r\n");
  97. #endif
  98. break;
  99. case SET_DESCRIPTOR: //设置描述符
  100. #ifdef DEBUG0
  101. Prints("设置描述符。\r\n");
  102. #endif
  103. break;
  104. case SET_FEATURE: //设置特性
  105. #ifdef DEBUG0
  106. Prints("设置特性。\r\n");
  107. #endif
  108. break;
  109. case SET_INTERFACE: //设置接口
  110. #ifdef DEBUG0
  111. Prints("设置接口。\r\n");
  112. #endif
  113. break;
  114. default: //未定义的标准请求
  115. #ifdef DEBUG0
  116. Prints("错误:未定义的标准输出请求。\r\n");
  117. #endif
  118. break;
  119. }
  120. break;
  121. case 1: //类请求
  122. #ifdef DEBUG0
  123. Prints("USB类输出请求:");
  124. #endif
  125. break;
  126. case 2: //厂商请求
  127. #ifdef DEBUG0
  128. Prints("USB厂商输出请求:\r\n");
  129. #endif
  130. break;
  131. default: //未定义的请求。这里只显示一个报错信息。
  132. #ifdef DEBUG0
  133. Prints("错误:未定义的输出请求。\r\n");
  134. #endif
  135. break;
  136. }
  137. }
    具体描述符的散转和数据返回的代码实现:
    //在获取描述符的处理中,增加对具体描述符散转的处理。描述符的类型保存在wValue中的高字节中。
    //由于暂时只实现了设备描述符,所以这里仅有对设备描述符的处理。
   
   
  1. case GET_DESCRIPTOR: //获取描述符
  2. #ifdef DEBUG0
  3. Prints("获取描述符——");
  4. #endif
  5. //对描述符类型进行散转,对于全速设备,标准请求只支持发送到设备的设备、配置、字符串三种描述符
  6. switch((wValue>>8)&0xFF)
  7. {
  8. case DEVICE_DESCRIPTOR: //设备描述符
  9. #ifdef DEBUG0
  10. Prints("设备描述符。\r\n");
  11. #endif
  12. pSendData=DeviceDescriptor; //需要发送的数据
  13. //判断请求的字节数是否比实际需要发送的字节数多
  14. //这里请求的是设备描述符,因此数据长度就是
  15. //DeviceDescriptor[0]。如果请求的比实际的长,
  16. //那么只返回实际长度的数据
  17. if(wLength>DeviceDescriptor[0])
  18. {
  19. SendLength=DeviceDescriptor[0];
  20. if(SendLength%DeviceDescriptor[7]==0) //并且刚好是整数个数据包时
  21. {
  22. NeedZeroPacket=1; //需要返回0长度的数据包
  23. }
  24. }
  25. else
  26. {
  27. SendLength=wLength;
  28. }
  29. //将数据通过EP0返回
  30. UsbEp0SendData();
  31. break;
  32. case CONFIGURATION_DESCRIPTOR: //配置描述符
  33. #ifdef DEBUG0
  34. Prints("配置描述符。\r\n");
  35. #endif
  36. break;
  37. case STRING_DESCRIPTOR: //字符串描述符
  38. #ifdef DEBUG0
  39. Prints("字符串描述符");
  40. #endif
  41. break;
  42. default: //其它描述符
  43. #ifdef DEBUG0
  44. Prints("其他描述符,描述符代码:");
  45. PrintHex((wValue>>8)&0xFF);
  46. Prints("\r\n");
  47. #endif
  48. break;
  49. }
  50. break;
    具体将数据发送给PC的函数:
    在上面的代码中,用到了UsbEp0SendData()函数。其功能是根据需要发送数据的长度和位置,将数据写
入端点0去。以后需要往端点0写数据时,先设置好需要发送数据的位置和长度,再调用这个函数即可。
   
   
  1. //函数功能:根据pData和SendLength将数据发送到端点0的函数。
  2. void UsbEp0SendData(void)
  3. {
  4. //将数据写到端点中去准备发送
  5. //写之前要先判断一下需要发送的数据是否比端点0
  6. //最大长度大,如果超过端点大小,则一次只能发送
  7. //最大包长的数据。端点0的最大包长在DeviceDescriptor[7]
  8. if(SendLength>DeviceDescriptor[7])
  9. {
  10. //按最大包长度发送
  11. D12WriteEndpointBuffer(1,DeviceDescriptor[7],pSendData);
  12. //发送后剩余字节数减少最大包长
  13. SendLength-=DeviceDescriptor[7];
  14. //发送一次后指针位置要调整
  15. pSendData+= DeviceDescriptor[7];
  16. }
  17. else
  18. {
  19. if(SendLength!=0)
  20. {
  21. //不够最大包长,可以直接发送
  22. D12WriteEndpointBuffer(1,SendLength,pSendData);
  23. //发送完毕后,SendLength长度变为0
  24. SendLength=0;
  25. }
  26. else //如果要发送的数据包长度为0
  27. {
  28. if(NeedZeroPacket==1) //如果需要发送0长度数据
  29. {
  30. D12WriteEndpointBuffer(1,0,pSendData); //发送0长度数据包
  31. NeedZeroPacket=0; //清需要发送0长度数据包标志
  32. }
  33. }
  34. }
  35. }
    怎么处理要发送的数据比端点0还多的情况
    因为函数UsbEp0SendData()一次只能发送最大包长的数据,所以当要发送的字节数比端点0还多时,需要
在端点0输入中断函数UsbEp0In()中处理。
    当前面的数据成功发送后,端点0输入中断机会产生。因此只要在端点0输入中断中发送剩下的数据即可。
记得还要调用D12ReadEndpointLastStatus()清楚端点0输入中断。
    注:前面提到过,主机第一次获取设备描述符时,只会读一个数据包的设备描述符,但此处设备描述符长
度比端点0的大小要大,因而就会发送两次数据包。此时不会出问题,因为D12在收到下一个SETUP包之
后,会自动将端点0输入缓冲区的数据置为无效,即第二次加进去的数据实际上并没有起作用。所以,这里
不对端点0的输入做特殊处理。
    端点0输入中断处理函数如下所示:
   
   
  1. //函数功能:端点0输入中断处理函数。
  2. void UsbEp0In(void)
  3. {
  4. #ifdef DEBUG0
  5. Prints("USB端点0输入中断。\r\n");
  6. #endif
  7. //读最后发送状态,这将清除端点0的中断标志位
  8. D12ReadEndpointLastStatus(1);
  9. //发送剩余的字节数
  10. UsbEp0SendData();
  11. }
     6)分析调试信息
    将程序下载到开发板中,可以看到端点0的写入操作。如果主机正确收到了设备描述符,应该接着对总线复位一次,然后发送 设置地址的标准请求。
    实际调试信息如下图所示:
    从返回的调试信息看出:
    ① 确实已经成功返回了设备描述符
    ② 主机发出了设置地址的标准请求。设置地址值存在wValue的低字节,即上图中倒数第二行的0x04。
    ③ 接下来就要对设置地址这个标准请求进行处理了。

3.8 设置地址请求的处理

1. 设置地址请求的流程及如何确定设置成功

    每个USB设备都有一个唯一的设备地址,这个地址是主机在设置地址请求时分配给设备的。
    设备在收到设置地址请求后, 应该返回一个0长度的状态数据包(因为设置地址请求是没有数据过程的),
然后等待主机确认这个数据包(即ACK应答设备)。
     设备在正确接收到状态数据包的ACK之后,开始使用新的设备地址。

2. D12芯片如何设置地址

    D12芯片提供了一条设置地址的命令:Set Address/Enable命令,代码是0xD0。
    设置地址命令后跟一字节数据写入操作,该字节的Bit7用来控制设备是否使能,只有当Bit7设置为1时,
D12的普通端点才能通过使能端点(Set Endpoint Enable)命令启用。Bit6~Bit0是设备的7位地址,应该将
接收到的标准请求中的地址写入Bit6~0中。代码实现如下:
   
   
  1. //函数功能:设置地址函数。
  2. //入口参数:Addr:要设置的地址值。
  3. void D12SetAddress(uint8 Addr)
  4. {
  5. D12WriteCommand(D12_SET_ADDRESS_ENABLE); //写设置地址命令
  6. D12WriteByte(0x80 | Addr); //写一字节数据:使能及地址
  7. }

3. 何时设置地址

    在上面的端点0输出中断中添加设置地址的处理。
    注意,在D12芯片中,设置地址后要等待主机返回ACK之后新地址才生效。也就是说,在主机返回0长度
的状态数据包之前写D12的地址寄存器,当主机取走状态数据包并用ACK应答时,D12才自动启用新设置的
地址。这之前使用的都是原来的 地址(虽然已经写了地址寄存器)。
    代码实现如下:
   
   
  1. //在上面的case SET_ADDRESS:中调试信息后面添加如下代码:
  2. D12SetAddress(wValue&0xFF); //wValue中的低字节是设置的地址值
  3. SendLength=0;                //设置地址没有数据过程,直接进入到状态过程,返回一个0长度的数据包
  4. NeedZeroPacket=1;
  5. UsbEp0SendData();            //将数据通过EP0返回

4. 分析调试信息

    编译下载上面的代码后,显示的调试信息如下:
    
     1)此时显示分配的地址和前面的0x04不一样,变成0x14了。这是因为不同时刻主机管理分配的地址
号不同,只要分配的是一个唯一的设备地址即可。
    2)地址设置好之后,调试信息显示又接收到了主机获取设备描述符的请求,这说明设置地址操作已
经成功了。因为这是主机已经使用新地址来发送请求了,如果不成功,主机会检测到超时,从而复位总
线。 这次获取 设备 描述符将会完整第获取。
    3)之后,主机接着发送获取配置描述符的请求,请求长度为9字节。
          标准配置描述符的长度就是9字节的,通常主机第一次先获取9字节长度的配置描述符(即标准配置
描述符),然后根据配置描述符中的配置集合长度,再次获取配置描述符。第二次获取配置描述符时,会
将配置描述符、接口描述符、类特殊描述符(如果有)、端点描述符等一起读回。因此接下来就是实现配置
描述符和接口描述符、类特殊描述符、端点描述符这个大集合——配置描述符集合。
圈圈USB 第三版PDF》是一本关于USB接口的电子书籍。本书通过简单易懂的语言和详细的示意图,介绍了USB接口的原理、使用方法以及相关的知识和技巧。 首先,本书从USB接口的起源和发展背景讲起,介绍了USB接口的优点和应用场景。随后,详细介绍了USB接口的物理结构,包括插头、连接器和电缆等组成部分。读者可以通过本书了解USB接口的各个部分的功能和特点。 接着,本书详细介绍了USB接口的工作原理。读者可以了解到USB接口是如何进行数据传输和电源供应的。同时,本书还介绍了USB的不同版本和速度等方面的知识,帮助读者选择适合自己需求的USB设备。 此外,本书还特别强调了USB接口的常见问题和解决方法。比如,介绍了USB接口的故障排除和维修技巧,帮助读者在使用过程中遇到问题时能够迅速解决。 最后,本书还提供了一些实用的技巧和应用案例,帮助读者更好地利用USB接口进行数据传输、设备连接和充电等功能。通过本书的学习,读者可以提高使用USB接口的效率和便利性。 总之,《圈圈USB 第三版PDF》是一本非常实用的电子书籍,内容全面、易懂,不仅适合初学者学习USB接口的原理和使用方法,也适合具有一定基础的读者提高技能和解决问题。如果你对USB接口感兴趣或者需要使用USB接口进行工作和学习,这本书将会是一个很好的参考资料。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值