6932|8

67

帖子

2

TA的资源

一粒金砂(中级)

楼主
 

悍马1788USB学习笔记 [复制链接]

LPC1788USB学习
第一章;开发板和PC实现连接
第一篇:了解硬件
本开发板是基于NXPLPC1788FBD208基础上研发的一款开发板。LPC1788内部集成ARM Cortex-M3微控制器,NXP 半导体针对各种高级通讯,高质量图像显示等应用场合而设计的一款具有高集成度的SOC。居然是针对各种高级通讯,那么学习USB那就是可以的。
USB电路图如下图;
图(111
如果想要开发USB Device需要把J19J20两个拨码开关的“1”和“2”接通,使USBD2-D2+连接到J16USB Device接口。下面的J17USB主机的,有人就会问现在学USB DeviceUSB Host电路图贴出来干嘛啊!不急等下我们就会说到,现在先不用管它。
第二篇:USB基础知识
当我们每次用到带有USB接口的设备感觉就是两个字“方便”。拿到USB数据线两头一插就OK。现在我们就从把数据线插入电脑的那一瞬间开始说起。那些什么同步啊拓扑结构啊啥的先抛开,嫩得我们费脑子胡思乱想,到后来想了半天不知道干嘛。
有细心的朋友可能都看到图(11)电路图,USB DeviceUSB Host两个电路的不同,在USB Device电路中D2+的线上连接了一个1.5K电阻经过了一个三级管Q13.3V(高电平),而在USB Host电路中D2-D2+上各经过了一个10K的电阻到地(低电平)。我们电脑上的USB接口就是USB Host,内部电路就和上图的USB Host类似。当我们把USB Device插入到电脑时,电脑上的USB接口就会有一个5V电压给我们的USB DeviceUSB Device内部硬件初始化,而内部的硬件就会从USB_CONNECT2输出一个信号给三极管Q1让三极管导通把USB DeviceD2+拉为高电平,这时电脑上USB HostD2+由原来的低电平也变为高电平。这时在电脑上就会检测到发现新硬件,而且是一个无法正常运行的设备,如图121。这设备管理器中的通用串行总线控制器会出现一个未知设备,如图122
图(121
图(122
这个过程当中主机检测到有设备插入就会向设备发送一些数据,而这些数据是设备刚插入主机时主机所要设备描述符的控制传输。现在我们的设备没有任何驱动程序来响应“组织的号召”,所以我们的“中央”就不知道如何来加载主机驱动来管理我们的设备,“中央”就会把它拉入黑名单说,硬件安装出现问题,设备不能正常运行,所以在设备管理器中就冒出了一个未知设备。在未知设备上点右键属性,在详细信息选项卡中会看到VIDPID都是0。如图23a,图23b是枚举成功的大容量存储设备。
                                            
                             图(123a                                                                                                  图(123b
第三篇:软件配置
我们来看一下程序在悍马1788是如何实现连接的。工程创建不讲述。
/**********************************************************************
函数功能;主函数
函 数 名;main
函数参数;无
函数返回;无
***********************************************************************/
int main (void)
{
        debug_frmwrk_init();                /*串口调试初始化,
用于打印调试信息
和主机发给开发板
的数据还有开发板
发给主机的数据。*/
        print_menu();                                        //打印字符串。
        USB_Init();                              // 初始化USB
        USB_SetDevCondition(DEV_CON);        // 链接USB                     
        while (1)
        {       
                ;
        }
}
看起来好像很简单,main函数里面关于USB的代码就两行。现在我们就来看下USB_Init();
/**********************************************************************
函数功能;初始化USB函数
函 数 名;USB_Init
函数参数;无
函数返回;无
***********************************************************************/
void USB_Init (void)
{       
        PINSEL_ConfigPin ( 0, 31, 1);                        //P0.31管脚设置为USB_D2+的功能
        PINSEL_ConfigPin ( 0, 14, 3);                        //P0.14管脚设置为USB_CONNECT2功能
        CLKPWR_ConfigPPWR (CLKPWR_PCONP_PCUSB, ENABLE);        //配置USB时钟/电源
          LPC_USB->USBClkCtrl = 0x1A;        /*USB时钟控制寄存器,
它控制了AHB、端口选
择寄存器、设备时钟,
这些时钟的使能和失能。*/
          while ((LPC_USB->USBClkSt & 0x1A) != 0x1A);                /*USBClkSt 寄存器是
时钟状态寄存器,检查
是否设置为0x1A*/
          LPC_USB->StCtrl = 0x3;                             //端口选择寄存器我们用的USB2所以是就是0x3
}
在初始化函数里面就只有USB_D2+USB_CONNECT2引脚被配置了,而USB_D2-VBUS没有配置。我们来看下LPC1788Data Sheet里对USB Device的管脚描述。
VBUS引脚描述的大概意思是,VBUS是一个输入状态,在没有IOCON寄存器,它的内部是上拉的。反正大概意思就是这样。如果是上拉那么它就可以触发内部的硬件。为了程序的简单我们就不对它进行配置。VBUS可以不管那USB_D2-咋不配置呢?现在来看一下原理图,如图24
图(124
这个管脚是默认为USB_D2-,可以不用配置。LED管脚也不用配置,应为我们没有用到LED灯。
接下来就是USB基本配置用库函数的CLKPWR_ConfigPPWR (CLKPWR_PCONP_PCUSB, ENABLE);来使能时钟和电源,下一行是USBClkCtrl寄存器配置,这个寄存器是USB时钟控制寄存器,它控制了AHB、端口选择寄存器、设备时钟,这些时钟的使能和失能,下面就是USBClkSt寄存器,这个寄存器是时钟状态寄存器,只是用来检测USBClkCtrl寄存器是否成功配置,还有一个是StCtrl寄存器,它是用来选择是用那一个USB端口,我们用的USB2看下数据手册,从寄存器里看设置为3是对的。
实现简单的连接功能,初始化这些就可以搞定。现在分析USB_SetDevCondition(DEV_CON)函数;
/**********************************************************************
函数功能;设置USB设备状态
函 数 名;USB_SetDevCondition
函数参数;conDEV_CON      0x01连接位表示设备的当前连接状态。用于CONNECT输出
DEV_CON_CH  0x02连接发生改变
  DEV_SUS      0x04挂起位表示当前的挂起状态
                          DEV_SUS_CH  0x08挂起位(DEV_SUS)的变化指示,
                                                                  设备进入挂起状态、设备断开连接、                                                                                                  设备在其上行端口上接收到恢复信号,                                                                                                  会发生翻转
                           DEV_RST      0x10总线复位位
函数返回;无
***********************************************************************/
void USB_SetDevCondition(uint32_t con)
{
  WrCmdDat(CMD_SET_DEV_STAT, DAT_WR_BYTE(con));
}
我们看到USB_SetDevCondition(DEV_CON);函数里面还调用了WrCmdDat(CMD_SET_DEV_STAT, DAT_WR_BYTE(con));函数这个函数是一个写命令数据函数。
/**********************************************************************
函数功能;写命令数据函数
函 数 名;WrCmdDat
函数参数;cmd;命令
                  val: 数据
函数返回;无
***********************************************************************/
void WrCmdDat (uint32_t cmd, uint32_t val)
{
  LPC_USB->DevIntClr = 1<<4;                /*DevIntClr寄存器是设备中断清除寄存器
                                                                        写相应的位就会清除DevIntSt设备中断状态
                                                                寄存器相应位,应为我们要写命令,所以我
                                                                们要把命令代码寄存器为空中断标志位清除*/
  LPC_USB->CmdCode = cmd;                /*写入命令*/
  while ((LPC_USB->DevIntSt & 1<<4) == 0);        /*等待命令代码寄存器
为空中断标志位置位*/
  LPC_USB->DevIntClr = 1<<4;                                /*清除中断*/                                               
  LPC_USB->CmdCode = val;                                /*写入数据*/       
  while ((LPC_USB->DevIntSt & 1<<4) == 0);        /*清除中断*/
}
在写命令数据函数里DevIntClr 寄存器有很详细的注释,关键要看的就是CmdCode寄存器,下面就是CmdCode寄存器的描述。
/*命令格式
低八位保留。
8--15位是命令段,
0x02代表读、0x01代表写、0x05代表命令。
16--23位是多用途字段,
当命令阶段为0x05命令或0x02读时,该域包含着命令代码,
当命令阶段为0x01写状态时,该域包含着命令写数据。*/
现在来看第一次给CmdCode寄存器赋值,后面的注释是说,/*写入命令*/而我们的命令是这样定义的,如下代码。
#define CMD_SET_DEV_STAT  0x00FE0500        //FE设置设备状态,05命令
按照上面的命令格式分析。815位是命令段值是0x05说明代表的是命令,1623位是就是命令代码了值是0xFE。现在看手册上说FE命令代表的是啥。
再看描述的时候一定要注意,上面写的Command: 0xFE, Data: write 1 byte
意思是,命令0xFE,数据写入一个字节,而写入的一个字节是下一次向CmdCode寄存器执行写数据时就可以操作0xFE命令所提供的功能,注释/*写入数据*/的那一行代码。就是我们要实现的连接功能,现在来看下定义的数据是多少!
#define DAT_WR_BYTE(x)   (0x00000100 | ((x) << 16))        //x为要写入的数据,01写。
用前面说的命令格式分析就知道这条命令是写命令。在main函数中调用的USB_SetDevCondition(DEV_CON);函数的参数DEV_CON的定义是;
#define DEV_CON  0x01        //连接位表示设备的当前连接状态。用于CONNECT输出
这说明写入0xFE命令所提供功能的数据是0x01,对照前面0xFE提供的功能可以知道这就是让CONNECT输出低电平。这样就实现了D2+拉高了。
到此第一章的知识讲完。结合代码和数据手册可以更好的理解。不足之处还请多多指点。此帖会不断更新,以实现一个USB HID的整个过程。附件有实现本章的代码!
开发环境:集成开发环境μVision4 IDE版本4.60.0.0。
主机系统:Microsoft Windows XP
开发平台:旺宝悍马1788开发板。

USB例程.rar

6.4 MB, 下载次数: 60

此帖出自NXP MCU论坛

最新回复

楼主的脚步太快,,,,第三章给跳过去了。。。  详情 回复 发表于 2017-2-22 14:48

赞赏

2

查看全部赞赏

点赞 关注(1)
 

回复
举报

37

帖子

0

TA的资源

一粒金砂(中级)

沙发
 
抢到一楼,顶!
此帖出自NXP MCU论坛
 
 
 

回复

305

帖子

0

TA的资源

一粒金砂(高级)

板凳
 
嘿嘿,板凳
此帖出自NXP MCU论坛
 
个人签名http://openmcu.taobao.com]77[/url]
 
 

回复

67

帖子

2

TA的资源

一粒金砂(中级)

4
 
LPC1788USB学习
第二章;让程序进入中断
第一篇;寄存器配置
上一次就说了让USBPC机连接,但是USB的处理工作大部分都是在中断里完成。想要让LPC1788USB进入中断还有很多寄存器需要配置。这次我们继续来讲解寄存器那点儿事儿。现在我们不从main函数开始,从初始化函数USB_Init开始。
/**********************************************************************
函数功能;初始化USB函数
函 数 名;USB_Init
函数参数;无
函数返回;无
***********************************************************************/
void USB_Init (void)
{       
        PINSEL_ConfigPin ( 0, 31, 1);                        //P0.31管脚设置为USB_D2+的功能
        PINSEL_ConfigPin ( 0, 14, 3);                        //P0.14管脚设置为USB_CONNECT2功能
        CLKPWR_ConfigPPWR (CLKPWR_PCONP_PCUSB, ENABLE);        //配置USB时钟/电源
          LPC_USB->USBClkCtrl = 0x1A;        /*USB时钟控制寄存器,
它控制了AHB、端口选
择寄存器、设备时钟,
这些时钟的使能和失能。*/
          while ((LPC_USB->USBClkSt & 0x1A) != 0x1A);                /*USBClkSt 寄存器是
时钟状态寄存器,检查
是否设置为0x1A*/
          LPC_USB->StCtrl = 0x3;                             //端口选择寄存器我们用的USB2所以是就是0x3
NVIC_EnableIRQ(USB_IRQn);                        //使能USB中断
USB_Reset();                                                //USB复位
}
现在的初始化函数就多了使能USB中断和USB复位,中断的一些寄存器都是在复位函数里配置的。在USB Device连接到电脑时,电脑会发命令让USB Device再次复位,让设备重新配置。这个目的是为了设备接收电脑发来的0地址数据,因为USB Device的地址是由电脑分配的,所以在没有分配地址的时候电脑会首先让USB Device复位以便接收来自电脑的0地址数据,这在USB的枚举时所需要的过程。这里要提一点在以后的学习中要注意;电脑发送给USB设备的数据叫OUT帧(输出帧),USB设备发送给电脑的数据叫IN帧(输入帧)。所有的数据方向都是以主机为主来决定方向。以后说OUT帧就知道是电脑到设备的数据,IN帧就知道是设备到电脑的数据。接下来开始分析USB_Reset();函数。
/**********************************************************************
函数功能;复位USB函数
函 数 名;USB_Reset
函数参数;无
函数返回;无
作                者:        旺宝电子科技有限公司
***********************************************************************/
void USB_Reset (void)
{
        /*EpInd寄存器和MaxPSize寄存器是一个
                寄存器组,可以把它们两看成一个数组
                EpInd就像数组的一个索引号,
                MaxPSize就像数组的元素*/
  LPC_USB->EpInd = 0;                                                                //物理端点0                               
  LPC_USB->MaxPSize = USB_MAX_PACKET0;                        //物理端点0的大小
  LPC_USB->EpInd = 1;                                                                //物理端点1
  LPC_USB->MaxPSize = USB_MAX_PACKET0;                        //物理端点1的大小
       
  while ((LPC_USB->DevIntSt & 1<<8) == 0);                /*DevIntSt是个设备中断状态寄存器,
                                                                                        在这里是检测有没有端点被使能*/
  LPC_USB->EpIntClr  = 0xFFFFFFFF;                        //端点中断寄存器清零
  LPC_USB->EpIntEn  = 0xFFFFFFFF;                        //使能所有端点中断
  LPC_USB->DevIntClr = 0xFFFFFFFF;                        //设备中断寄存器清零
  LPC_USB->DevIntEn = (1<<3)|(1<<2);                /*设置设备中断使能寄存器。设置了,
                                                                                总线复位usb挂起改变或链接改变时置位、
                                                                                端点的慢速中断置位。*/
        USB_SetAddress(0);                                                //设置USB地址为0
#if  PRINTF
        _DBG_("复位\r\n");
#endif       
}
从第一个EpInd寄存器开始介绍,在介绍之前我们先来看下1788USB有哪些端点。
数据手册上说的端点分两类,一类是逻辑端点,一类是物理端点。逻辑端点是成对出现的一个是OUT一个是IN,在程序里就可以说,那个端点的输入和输出,但是在物理上是一个输入端点和一个输出端点。就像谈恋爱是一对,要一个男的和一个女的才叫谈恋爱。要是两个男的那就叫一对屌丝。从手册上看到端点很多有16个逻辑端点32个物理端点,端点的类型有中断传输、批量传输、同步传输和控制传输,唯有控制传输只有一个,而且这个控制传输端点是0这就是我们以后要讲到的枚举过程用到的端点0。电脑对设备复位后和设备进行枚举数据收发都是用的端点0,所以端点0很重要,而且多它一个没用少它一个不行。以后我们大部分的工作都是围绕着端点0开展。
程序里的EpInd 寄存器就是USB端点索引寄存器每个物理端点都是用它来找到的,而EpInd 寄存器和MaxPSize寄存器是一个寄存器组。就像程序里的注释你可以把他们理解成为一个数组,相信会点C语言的都知道数组,用索引号来找到后面的数据元素就可以对这个元素赋值。MaxPSize寄存器用来记录端点的最大包长度值。因此在写操作之前,要通过EpInd 寄存器“寻址”该寄存器。如果修改MaxPSize的值,在结束时DevIntSt 中的EP_RLZED 位也就是第8位将置位。while ((LPC_USB->DevIntSt & 1<<8) == 0);语句就是用来判断的。
        EpIntClr端点中断寄存器清除       
EpIntEn使能所有端点中断
          DevIntClr设备中断寄存器清零
          DevIntEn设置设备中断使能寄存器。设置了,总线复位usb挂起改变或链接改变时置位、
端点的慢速中断置位。
下面就是设置地址函数,设置地址的命令是D0
#define CMD_SET_ADDR   0x00D00500        //D0设置地址,05命令。
#define DAT_WR_BYTE(x)  (0x00000100 | ((x) << 16))        //x为要写入的数据,01写。       
/**********************************************************************
函数功能;设置USB地址函数
函 数 名;USB_SetAddress
函数参数;adr;要写入的地址
函数返回;无
                者:        旺宝电子科技有限公司
***********************************************************************/
void USB_SetAddress(uint32_t adr)
{
WrCmdDat(CMD_SET_ADDR, DAT_WR_BYTE((1<<7) | adr)); //设置地址
#if PRINTF
        _DBG_("地址");
        _DBH(adr);
        _DBG_("\r\n");
#endif       
}
WrCmdDat(CMD_SET_ADDR, DAT_WR_BYTE((1<<7) | adr)); 这条语句里的((1<<7)|adr)是在设置地址命令功能里面的第7位是设置地址使能位,06位才是地址位。程序里面的#if PRINTF预编译是调试输出信息所用,可以在USB_core.h文件里的#define PRINTF 1来控制。下面是WrCmdDat函数代码。
/**********************************************************************
函数功能;写命令数据函数
函 数 名;WrCmdDat
函数参数;cmd;命令
                  val: 数据
函数返回;无
作                者:        旺宝电子科技有限公司
***********************************************************************/
void WrCmdDat(uint32_t cmd, uint32_t val)
{
  LPC_USB->DevIntClr = 1<<4;        /*DevIntClr寄存器是设备中断清除寄存器
                                                        写相应的位就会清除DevIntSt设备中断状态
                                                        寄存器相应位,应为我们要写命令,所以我
                                                        们要把命令代码寄存器为空中断标志位清除*/
  LPC_USB->CmdCode = cmd;                /*写入命令*/
  while ((LPC_USB->DevIntSt & 1<<4) == 0);/*等待命令代码寄存器为空中断标志位置位*/
  LPC_USB->DevIntClr = 1<<4;                                                                                               
  LPC_USB->CmdCode = val;                /*写入数据*/       
  while ((LPC_USB->DevIntSt & 1<<4) == 0);
}
第二篇;中断
把前面的工作都搞定了接下来就是中断,如果把中断函数加上能进入中断就说明我们之前的工作没有白费下面就是中断函数。
/**********************************************************************
函数功能;USB中断函数
函 数 名;USB_IRQHandler
函数参数;无
函数返回;无
作                者:        旺宝电子科技有限公司
***********************************************************************/
void USB_IRQHandler (void)
{
  uint32_t disr;
  disr = LPC_USB->DevIntSt;  /*DevIntSt寄存器是设备中断状态寄
存器,这句就是读设备中断状态*/
        #if PRINTF
                _DBG_("中断号");
                _DBH32(disr);        //打印中断号,可以在数据手册中查到中断的原因和功能。
                _DBG_("\r\n");
        #endif       
}       
把程序下载到开发板中运行,在串口调试助手中显示如图(2—1—1)。
图(221
可以看到显示的一大堆中断号为0x00000019。把它转换为二进制就是“11001”。现在在看数据手册上对这几位的中断解释。
从中断号中分别是第0位第3位和第4位产生了中断。他们的大概意思是:
0位,每隔1ms产生一次帧中断。这一位在同步包的传输里。
3位,这一位是在USB总线复位、USB挂起改变或者连接改变时会置位。
4位,命令代码寄存器(USBCmdCode)为空(可以写入新的命令)。
在第3位的描述里还说了这一位的复位、挂起改变或连接改变在13.12.6的设置设备状态(命令:0xFE,写1字节)还有什么关系,但是我们现在不用管它。我们先把中断状态寄存器清了看会有什么情况。清中断就只需要把读到的中断状态信息给清中断寄存器。LPC_USB->DevIntClr = disr;程序运行的结果如图(222
图(222
现在是0x00000019没了,但是出来了一大堆的0x00000005。转换位二进制为“101”。前面的手册说,第2位是端点的慢速中断。如果端点中断在端点中断优先级寄存器USBEpIntPri中相应的位没有置位,则该端点中断与EP_SLOW 位相关。可以这样去理解这句话,就是端点中断优先级寄存器USBEpIntPri中没有把相应的端点位置位,它就会触发EP_SLOW中断。那说明现在端点缓冲区中有数据了。
好了到此第二章的东西讲完了。第三章会将读端点缓冲区的数据,分析数据。结合代码和数据手册可以更好的理解。不足之处还请多多指点。此帖会不断更新,以实现一个USB HID的整个过程。附件有实现本章的代码!
开发环境:集成开发环境μVision4 IDE版本4.60.0.0。
主机系统:Microsoft Windows XP
开发平台:旺宝悍马1788开发板。

USB例程2.rar

6.4 MB, 下载次数: 19

此帖出自NXP MCU论坛

点评

楼主又更新了  详情 回复 发表于 2014-12-24 11:47
 
 
 

回复

305

帖子

0

TA的资源

一粒金砂(高级)

5
 
cxmdz 发表于 2014-12-23 09:48
LPC1788—USB学习第二章;让程序进入中断第一篇;寄存器配置上一次就说了让USB和PC机连接,但是USB的处理工作大部分都是在中断里完成。想要让LPC1788USB进入中断还有很多寄存器需要配置。这次我们继续来讲解寄存器那点儿事儿。现在我们不从main函数开始,从初始化函数USB_Init开始。/**********************************************************************函数功能;初始化USB函数函 数 名;USB_Init函数参数;无函数返回;无***********************************************************************/void USB_Init (void) {                PINSEL_ConfigPin ( 0, 31, 1);                        //P0.31管脚设置为USB_D2+的功能        PINSEL_ConfigPin ( 0, 14, 3);                        //P0.14管脚设置为USB_CONNECT2功能        CLKPWR_ConfigPPWR (CLKPWR_PCONP_PCUSB, ENABLE);        //配置USB时钟/电源          LPC_USB->USBClkCtrl = 0x1A;        /*USB时钟控制寄存器,它控制了AHB、端口选择寄存器、设备时钟,这些时钟的使能和失能。*/          while ((LPC_USB->USBClkSt & 0x1A) != 0x1A);                /*USBClkSt 寄存器是时钟状态寄存器,检查是否设置为0x1A。*/          LPC_USB->StCtrl = 0x3;                             //端口选择寄存器我们用的USB2所以是就是0x3NVIC_EnableIRQ(USB_IRQn);                        //使能USB中断USB_Reset();                                                //USB复位}现在的初始化函数就多了使能USB中断和USB复位,中断的一些寄存器都是在复位函数里配置的。在USB Device连接到电脑时,电脑会发命令让USB Device再次复位,让设备重新配置。这个目的是为了设备接收电脑发来的0地址数据,因为USB Device的地址是由电脑分配的,所以在没有分配地址的时候电脑会首先让USB Device复位以便接收来自电脑的0地址数据,这在USB的枚举时所需要的过程。这里要提一点在以后的学习中要注意;电脑发送给USB设备的数据叫OUT帧(输出帧),USB设备发送给电脑的数据叫IN帧(输入帧)。所有的数据方向都是以主机为主来决定方向。以后说OUT帧就知道是电脑到设备的数据,IN帧就知道是设备到电脑的数据。接下来开始分析USB_Reset();函数。/**********************************************************************函数功能;复位USB函数函 数 名;USB_Reset函数参数;无函数返回;无作                者:        旺宝电子科技有限公司***********************************************************************/void USB_Reset (void) {        /*EpInd寄存器和MaxPSize寄存器是一个                寄存器组,可以把它们两看成一个数组                EpInd就像数组的一个索引号,                MaxPSize就像数组的元素*/  LPC_USB->EpInd = 0;                                                                //物理端点0                                  LPC_USB->MaxPSize = USB_MAX_PACKET0;                        //物理端点0的大小  LPC_USB->EpInd = 1;                                                                //物理端点1  LPC_USB->MaxPSize = USB_MAX_PACKET0;                        //物理端点1的大小          while ((LPC_USB->DevIntSt & 1EpIntEn  = 0xFFFFFFFF;                        //使能所有端点中断  LPC_USB->DevIntClr = 0xFFFFFFFF;                        //设备中断寄存器清零  LPC_USB->DevIntEn = (1
楼主又更新了
此帖出自NXP MCU论坛
 
个人签名http://openmcu.taobao.com]77[/url]
 
 

回复

37

帖子

0

TA的资源

一粒金砂(中级)

6
 
内容详细,精华!
此帖出自NXP MCU论坛
 
 
 

回复

67

帖子

2

TA的资源

一粒金砂(中级)

7
 
LPC1788—USB学习
第四章;USB设备枚举
第一篇;分析设备请求数据结构包
在上一章说到,把主机发过来的数据接收到。现在就根据USB标准设备请求数据结构来分析,接收到的数据是要让设备干嘛!接收到的数据是,0x80 0x06 0x00 0x01 0x00 0x00 0x0040。注意;数据传输为小端格式,低字节在前高字节在后。后面的0x0040是使用一个16位无符号短整型接收,所以低字节在后高字节在前。
第一个域是bmRequestType这个域占用了一个字节,0x80转换为二进制为“1000 0000”,在USB标准设备请求数据结构中,在这个字节中每一位都有不同的作用如下:
D7:数据传输方向
   0—主机到设备
   1—设备到主机
D5~6:请求类型
   0—标准
   1—类
   2—厂商
   3—保留
D0~4:请求连接者
   0—设备
   1—接口
   2—端点
   3—其他
4~31—保留
可以看出来主机所要的数据方向是设备到主机,请求类型是标准类型,请求连接者是设备。
第二个域是bRequest这个域占用了一个字节,这个就是标准请求域,USB协议定义了11个标准请求。如表4—1—1:
表4—1—1 标准请求码
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        同步帧
主机发的这个SETUP包bRequest域是0x06,就是要获取描述符。

后面的第三第四个域是wValue和wIndex域它们各占用两个字节,它们的作用和前面bRequest域的请求码有关,请求码不同wValue和wIndex域代表的功能也不同。表4—1—2就是几个常用传给设备的标准请求结构。
表4—1—2
bmRequestType        bRequest        wValue        wIndex        wLength        返回给主机的数据
10000000B        GET_DESCRIPTOR (0x06)        描述符类型和索引        0或语言ID        描述符长度        返回给主机描述符
00000000B        SET_ADDRESS (0x05)        设备地址        0        0        不会回给主机数据
00000000B        SET_CONFIGURATION (0x09)        配置值        0        0        不会回给主机数据

从前面的输出包分析可以知道是一个获取描述符的请求,而wValue和wIndex域分别是0x00 0x01 和 0x00 0x00。wValue域是1,wIndex域是0。
bRequest域指明请求号0x06,GET_DESCRIPTOR 的请求号为0x06。  
wValue域的高字节表示所要读取描述符类型的值;低字节表示描述符索引的值,只对配置描述符和字符串描述符有效,对于其他描述符该字节为0。表4—1—3是就个常用的描述符类型编号。
表4—1—3
描述符类型        编号
设备描述符(DEVICE_DESCRIPTOR_TYPE)        1
配置描述符(CONFIGURATION_DESCRIPTOR_TYPE)        2
字符串描述符(STRING_DESCRIPTOR_TYPE)        3
接口描述符(INTERFACE_DESCRIPTOR_TYPE )        4
端点描述符(ENDPOINT_DESCRIPTOR_TYPE)        5
       
wIndex域指表示符串描述符的语言ID,对于其他描述符该字段为0。
wLength域表示这个请求所返回的描述符长度是多少(字节)。如果wLength大于描述符实际长度,就返回实际描述符的长度;如果wLength小于实际描述符长度,就返回描述符前wLength个长度的字节数据。
接收到的0x80 0x06 0x00 0x01 0x00 0x00 0x0040这包数据按照上面的分析,主机是要向设备请求设备描述符,请求的长度是0x0040也就是64字节。

第二篇;设备描述符
设备描述符是每个设备都必须具有的,而且必须只有一个设备描述符。因为设备就只有一个,所以设备描述符也就一个了。不会平白无故飞一个设备出来。接下来就来分析设备描述符的结构,表4—2—1就是设备描述符该有的东西。
域        大小(字节)        说明
bLength        1        设备描述符长度(18字节)
bDescriptorType        1        描述符类型
bcdUSB        2        该设备使用的USB协议版本(BCD码)
bDeviceClass         1        类代码
bDeviceSubClass        1        子类代码
bDeviceProtocol        1        设备所使用的协议
bMaxPacketSize0        1        端点0最大长度
idVendor        2        厂商ID
idProduct        2        产品ID
bcdDevice        2        设备版本号
iManufacturer         1        供应商字符串描述符索引值
iProduct        1        产品字符串描述符索引值
iSerialNumber        1        设备序列号字符串描述符索引值
bNumConfigurations        1        所支持的配置数

现在就结合代码来分析设备描述符的数据。下面就是设备描述符,我们用一个数组来封装描述符。
/****************************设备描述符*******************************/
const uint8_t USB_DeviceDescriptor[0x12] = {
  0x12,       /* bLength 大小1字节。表示描述符的长度(字节),
设备描述符的长度是18字节,十六进制“0x12”。*/
       
  0x01,       /* bDescriptorType 大小1字节。表示描述符的类型,
大部分都是表4—1—3的那些东东了。主机请求的是
设备描述符那就该是“0x01”。*/
       
  0x10,0x01,   /* bcdUSB 大小2字节。表示这个设备使用的USB协
议版本。也就是我们常说的“傻逼2.0”或者“傻逼1.1”
等版本号。它们用BCD码表示,例如USB2.0协议就是
0x0200,USB1.1协议就是0x0110。数据传输是小端格式,
所以USB2.0就是0x00 0x20,USB1.1就是0x10 0x01。*/       
       
  0x00,              /* bDeviceClass 大小1字节。设备类码是有USB协会规
定的,表示的是接口所能实现的功能。当此域为0时下面
的子类也必须为0,当为0xFF表示的是厂商自定义设备类。*/       

  0x00,        /* bDeviceSubClass 大小1字节。子类代码这个码值的
意思是根据设备类码来看。如设备类码为零,这字段也要零
如设备类码为0xFF,此域的所有 值保留。*/       

  0x00,        /* bDeviceProtocol 大小1字节。表示协议码   这些码的值视
设备码和子类代码的值而定。   当该字段为0是,表示设备
不使用类所定义的协议,当该FFH字段的值为是,表示使用
设备厂商自定义的协议 。*/
       
  0x10,        /* bMaxPacketSize0 大小1字节。是端点0的最大包长度,
0x10是16 */       

  0x88,0x88,   /* idVendor  大小2字节。表示厂商ID号。这个ID号是由
USB协会分配,可以跟USB协会申请一个厂商ID号,但
是是要给钱的,这里我们用0x8888来做为学习用。*/
               
  0x01,0x00,    /* idProduct  大小2个字节。表示产品ID。这个ID比较
  自由这里用0x0001来表示*/
               
  0x00,0x01,    /* bcdDevice 大小2个字节。表示设备版本号*/
               
  0x01,              /* iManufacturer  大小1个字节。表示厂商字符串的索引值。*/       

  0x02,               /* iProduct  大小1个字节。表示产品字符串索引值。*/
       
  0x03,               /* iSerialNumber  大小1个字节。表示设备序列号字符串
  索引值。*/       

  0x01        /* bNumConfigurations 大小1个字节。表示设备所具有的配,
一种配置就可以了,所以这个值取0x01*/
};

第三篇;返回设备描述符
有了描述符现在就该返回描述符。在上一章中用了一个void USB_EndPoint0 (uint32_t Event)函数打印端点0接收到的数据,现在就要利用这个函数返回描述符了代码如下:
/**********************************************************************
函数功能;USB端点0函数
函 数 名;USB_EndPoint0
函数参数;Event; 需要端点0执行的事件。
函数返回;无
作                者:        旺宝电子科技有限公司
***********************************************************************/
void USB_EndPoint0 (uint32_t Event)
{
  switch (Event) {
    case USB_EVT_SETUP:
                        USB_ReadEP (0, (uint8_t *)&SetupPacket);
                        EP0Data.Count = SetupPacket.wLength;
                                #if PRINTF
                                _DBH(SetupPacket.bmRequestType.B);
                                _DBG_(" ");
               
                                _DBH(SetupPacket.bRequest);
                                _DBG_(" ");
               
                                _DBH(SetupPacket.wValue.WB.L);
                                _DBG_(" ");

                                _DBH(SetupPacket.wValue.WB.H);
                                _DBG_(" ");
               
                                _DBH(SetupPacket.wIndex.WB.L);
                                _DBG_(" ");

                                _DBH(SetupPacket.wIndex.WB.H);
                                _DBG_(" ");
                               
                                _DBH16(SetupPacket.wLength);
                                _DBG_(" ");
                                _DBG_("\r\n");
                                #endif
switch (SetupPacket.bmRequestType.BM.Type){
   case REQUEST_STANDARD:                                        //标准
      switch (SetupPacket.bRequest) {                                //请求代码
        case REQUEST_SET_ADDRESS:                        //设置地址
                                #if PRINTF
                                _DBG_("设置地址\r\n");
                                #endif                                                       
           break;
        case REQUEST_GET_DESCRIPTOR:                        //获取描述符
                                #if PRINTF
                                        _DBG_("获取描述符\r\n");
                                #endif
            if (!USB_ReqGetDescriptor()){
                                #if PRINTF
                                        _DBG_("写入描述符错误\r\n");
                                #endif
            }       
                        USB_DataInStage();                                                //将数据写入到端点缓冲区中。
              break;
        case REQUEST_SET_CONFIGURATION:                //设置配置
                                #if PRINTF
                                        _DBG_("设置配置\r\n");
                                #endif
              break;
       }
       break;                                                                                  //USB标准请求结束               
}
break;
case USB_EVT_OUT:
break;
case USB_EVT_IN :
      if (SetupPacket.bmRequestType.BM.Dir == REQUEST_DEVICE_TO_HOST){
        USB_DataInStage();                               //发送数据
              }
                  else {
                if(DeviceAddress & 0x80) {                        /*判断DeviceAddress变量里
是否有地址。*/
                  DeviceAddress &= 0x7F;
                  USB_SetAddress(DeviceAddress);        //设置地址
                }
                }
                        break;   
        }
}
在void USB_EndPoint0 (uint32_t Event)函数里用分支语句来判断定义的标准请求宏
#define REQUEST_SET_ADDRESS                5                        //设置地址
#define REQUEST_GET_DESCRIPTOR             6                        //获取描述符
#define REQUEST_SET_CONFIGURATION          9                        //设置设备配置
在相应的分支里添加相应的代码,在当前我们分析出来的请求是获取描述,描述符类型是设备描述符,所以在获取描述符分支里添加了一个判断获取描述符类型的函数USB_ReqGetDescriptor ();函数源码如下:
/**********************************************************************
函数功能;判断USB请求描述符类型,
                  并写入到USB端点数据结
                  构体全局变量EP0Data中。
函 数 名;USB_ReqGetDescriptor
函数参数;无
函数返回;返回(TRUE)写入成功。
                  返回(FALSE)写入失败。
作                者:        旺宝电子科技有限公司
***********************************************************************/
__inline uint32_t USB_ReqGetDescriptor (void)
{
  uint8_t  *pD;
  uint32_t len, n;
  switch (SetupPacket.bmRequestType.BM.Recipient) {
    case REQUEST_TO_DEVICE:                                                                //设备
      switch (SetupPacket.wValue.WB.H) {               
        case DEVICE_DESCRIPTOR_TYPE:
                                #if PRINTF
                                        _DBG_("\r\n写入设备描述符\r\n");
                                #endif
          EP0Data.pData = (uint8_t *)USB_DeviceDescriptor;                //设备描述符首地址
          len = sizeof(USB_DeviceDescriptor);                                        //描述符大小
          break;
                               
        case CONFIGURATION_DESCRIPTOR_TYPE:
                                #if PRINTF
                                        _DBG_("\r\n写入配置描述符\r\n");
                                #endif
  ///////////////////添加写入配置描述符代码///////////////////
          break;               
        case STRING_DESCRIPTOR_TYPE:
                                #if PRINTF
                                        _DBG_("\r\n字符串描述符\r\n");
                                #endif
                     ////////////////////添加写入字符串描述符代码///////////////////
          break;
        default:
                                #if PRINTF
                                        _DBG_("\r\n未知字符串描述符\r\n");
                                #endif
          return (FALSE);
      }
      break;
    case REQUEST_TO_INTERFACE:                                //接口
      switch (SetupPacket.wValue.WB.H) {
        case HID_REPORT_DESCRIPTOR_TYPE:        //报告描述符       
#if PRINTF
                                        _DBG_("\r\n写入报告描述符\r\n");
#endif
  ////////////////////////////////添加写入报告描述符代码///////////////////
          return (FALSE);   

         default:
#if PRINTF
                                        _DBG_("\r\n未知接口描述符\r\n");
#endif
           return (FALSE);
     }
      break;
default:
#if PRINTF
                                _DBG_("\r\n未知接口描述符\r\n");
#endif
      return (FALSE);
  }

  if (EP0Data.Count > len) {        /*如果索要描述符大于实际描述符就赋予实际描述符大小
                                                        如果索要描述符小于实际描述符就赋予索要描述符大小*/
    EP0Data.Count = len;
  }

  return (TRUE);
}
在这个函数里用到一个结构体变量EP0Data原型如下:
typedef struct _USB_EP_DATA {
  uint8_t  *pData;                               
  uint16_t Count;
} USB_EP_DATA;
用这个结构体定义的全局变量EP0Data。这个函数的主要功能就是把需要的描述符赋给这个全局变量。如果描述符赋值成功USB_EndPoint0函数就会调用USB_DataInStage()函数把描述符数据写入端点0。函数原型如下:
/**********************************************************************
函数功能;USB请求数据输入阶段函数
函 数 名;USB_DataInStage
函数参数;无
函数返回;无
作                者:        旺宝电子科技有限公司
***********************************************************************/
void USB_DataInStage (void)
{
  uint32_t cnt;
  if (EP0Data.Count > USB_MAX_PACKET0) {        /*所要发送的包如果大于端点缓冲区就                                                                                        写入端点缓冲区大小的数据如果小于端                                                                                        点缓冲就写入所有描述符大小的数据*/
    cnt = USB_MAX_PACKET0;
  } else {
    cnt = EP0Data.Count;
  }
  cnt = USB_WriteEP(0x80, EP0Data.pData, cnt);        //数据写入端点0
  EP0Data.pData += cnt;
  EP0Data.Count -= cnt;
}
这里面还调用了一个函数就是USB_WriteEP这个函数就是向指定端点写入数据,细心的可就会看到,明明是向端点0写数据,而参数却是0x80。这里先不管先看USB_WriteEP函数原型。代码如下:
/**********************************************************************
函数功能;写USB端点数据函数
函 数 名;USB_WriteEP
函数参数;EPNum;要写的端点。
                        *pData;指向缓冲区的指针。
                        cnt;写入的字节数。
函数返回;写入的字节数。
作                者:旺宝电子科技有限公司
***********************************************************************/
uint32_t USB_WriteEP(uint32_t EPNum, uint8_t *pData, uint32_t cnt)
{
  uint32_t n;
  LPC_USB->Ctrl = ((EPNum & 0x0F) << 2) | CTRL_WR_EN;
  LPC_USB->TxPLen = cnt;
  for (n = 0; n < (cnt + 3) / 4; n++){
    LPC_USB->TxData = *((__packed uint32_t *)pData);
                _DBH32(*((__packed uint32_t *)pData));
                _DBG_(" ");
    pData += 4;
  }
        _DBG_("\r\n");
  LPC_USB->Ctrl = 0;                                                //禁止向端点读写数据
  WrCmdEP(EPNum, CMD_VALID_BUF);                //确认缓冲区
  return (cnt);
}
现在USB_WriteEP函数里又调用了一个WrCmdEP函数
/**********************************************************************
函数功能;向端点写命令函数
函 数 名;WrCmdEP
函数参数;EPNum: 端点号
                                        cmd;命令
函数返回;无
作                者:        旺宝电子科技有限公司
***********************************************************************/
void WrCmdEP(uint32_t EPNum, uint32_t cmd)
{
  uint32_t val;
  val = (EPNum & 0x0F) << 1;
  if (EPNum & 0x80) {                                //如果端点参数EPNum为0x80说明是输入端点
    val += 1;
  }
  LPC_USB->DevIntClr = CCEMTY_INT;
  LPC_USB->CmdCode = CMD_SEL_EP(val);                //选择端点准备向该端点写命令
  while ((LPC_USB->DevIntSt & CCEMTY_INT) == 0);
  LPC_USB->DevIntClr = CCEMTY_INT;
  LPC_USB->CmdCode = cmd;                                        //写入命令
  while ((LPC_USB->DevIntSt & CCEMTY_INT) == 0);
}
0x80就是在确认缓冲区的时候来判断物理输出端点。好了现在下载程序运行看串口调试助手输出的数据。如图4—3—1:

图4—3—1
现在程序跑到了设置地址的地方。        
case REQUEST_SET_ADDRESS:                        //设置地址
                                #if PRINTF
                                _DBG_("设置地址\r\n");
                                #endif                                                       
                   break;
现在在设置地址的地方没有代码,所以主机就会连续的向设备索要设备描述符,然后在来设置地址。反复几次主机就会然设备挂起!而不管它。
现在就来说添加设置地址的代码。之前我们在复位设备时就讲了一个设置地址的函数USB_SetAddress(uint32_t adr);函数就是设置地址的函数,但是在这里面要连续设置两次地址才能正常,这个我也不太明白是啥原因,如果有那位大神知道的可以讨论一下。代码如下。
/**********************************************************************
函数功能;设置USB地址函数
函 数 名;USB_SetAddress
函数参数;adr;要写入的地址
函数返回;无
作                者:        旺宝电子科技有限公司
***********************************************************************/
void USB_SetAddress(uint32_t adr)
{       
  WrCmdDat(CMD_SET_ADDR, DAT_WR_BYTE(0x80 | adr)); //(1<<7)的意思是使能0—6位的地址。
  WrCmdDat(CMD_SET_ADDR, DAT_WR_BYTE(0x80 | adr));
#if PRINTF
        _DBG_("地址");
        _DBH(adr);
        _DBG_("\r\n");
#endif       
}
在设置地址前先要把输出包的地址提取出来,主机发来的地址就在wValue域的低字节中。只需要把wValue域的低字节提出来赋值给一个全局变量,然后再发一个0个数据的输入包,让主机知道地址已经成功接收到。这时设备就会产生一个主机到设备的输入中断启用设备地址。以下代码就是提取地址的源代码:
/**********************************************************************
函数功能;设置USB设备地址
函 数 名;USB_ReqSetAddress
函数参数;无
函数返回;无
作                者:        旺宝电子科技有限公司
***********************************************************************/
__inline uint32_t USB_ReqSetAddress (void)
{
switch (SetupPacket.bmRequestType.BM.Recipient){
   case REQUEST_TO_DEVICE:
     DeviceAddress = 0x80 | SetupPacket.wValue.WB.L;        /*或上0x80的目的是在主                                                                                                                机到设备的输入中断里判断                                                                                                        DeviceAddress里有地址*/
     break;
   default:
     return (FALSE);
  }
return (TRUE);
}
下载程序运行正常就会出现主机在次获取设备描述符,这次获取是用设置好了的地址获取的设备描述符,而且获取的描述符长度也变成了0x0012也就是现在的设备描述符长度十进制18字节。现象如图4—3—2:

图4—3—2
在之前已经写好了返回给主机设备描述符的程序,所以程序还会继续运行把设备描述符在次返回给主机如图4—3—3:

图4—3—3
运行到这里主机又输出了一个索要配置描述的SETUP数据包。

第四篇;配置描述符
/***************************** USB配置描述符 *************************/
const uint8_t USB_ConfigDescriptor[0x09+0x09+0x09+0x07] = {
  0x09,     /* bLength 大小1字节。表示配置符的长度。*/

  0x02,                 /* bDescriptorType 大小2字节。表示描述符的类型。
配置描述符的类型编码为0x02*/

  sizeof(USB_ConfigDescriptor) & 0xff, (sizeof(USB_ConfigDescriptor)>>8) & 0xff,                                                                                 /* wTotalLength 大小2字节。表示整个                                                                                        配置描述符长度,有配置描述符、类特殊                                                                                描述符和端点描述符。*/
  
  0x01,     /* bNumInterfaces 大小1字节。表示这个配置所支持的接口数量。
现在用到的接口数量就一个,所以是0x01。*/

  0x01,     /* bConfigurationValue 大小1字节。表示配置值。USB支持多少种配置,                        bConfigurationValue就是每个配置的标志。在配置请求时会发送配置值,
发送的配置值与bConfigurationValue相匹配,那么该配置就会被激活为当
前配置。*/

  0x00,     /* iConfiguration 大小1字节。表示描述该配置的字符串索引值。*/

  0x80,            /* bmAttributes 大小1字节。描述设备的一些特性。第7位必须为1。
第6位表示供电方式为1是自供电,为0是总线供电。第5位表示是
否支持远程唤醒。第4—0位保留为0。*/

  0x32,     /* bMaxPower 大小1字节。表示设备从总线上获取的最大电流,单位
以2mA为单位。例如100mA 就是2*50,50转换为十六进制为0x32。*/


/*******************************接口描述符**************************************************/
  0x09,     /* bLength 大小1字节。表示接口描述符的长度。*/

  0x04,     /* bDescriptorType 大小1字节。表示描述符类型。接口描述符类型编码
为0x04。*/

  0x00,     /* bInterfaceNumber 大小1字节。表示接口编号。当配置具有多个接口时,                        每个接口编号都不一样。都是从0递增对第一个编号配置的接口进行编号。 */

  0x00,     /* bAlternateSetting大小1字节。表示接口的备用编号。很少会用到。它的规                        律和bInterfaceNumber一样。*/

  0x01,     /* bNumEndpoints 大小1字节。表示该接口使用的端点数(不包括0端点)。                        如果为0表示没有非0端点,只使用默认的控制端点。*/

  0x03,                 /* bInterfaceClass 大小1字节。表示接口类。USB鼠标是HID类,编码为                        0x03。 */

  0x01,     /* bInterfaceSubClass 大小1字节。表示接口使用的子类。HID1.1只规定了                        了一种子类:支持BIOS引导启动的子类。USB鼠标、键盘属于该之类,代                        码为0x01.*/

  0x02,     /* bInterfaceProtocol 大小1字节。表示子类支持引导启动的子类,协议可选                        择鼠标和键盘,鼠标码为0x02,键盘码为0x01。*/

  0x00,     /*iConfiguration 大小1字节。表示接口字符串索引值。没有就为0。*/

       
/*******************************HID描述符****************************************************/
  0x09,     /* bLength 大小1字节。表示HID描述符长度 */

  0x21,     /* bDescriptorType 大小1字节。表示描述符编号。HID的编号为0x21。 */

  0x10,0x01,        /*bcdHID 大小2字节。表示HID协议号,这里是HID1.1协议,为0x10,0x01                   (小端格式低字节在前高字节在后)*/
               
  0x21,                /*bCountyCode 大小1字节。表示设备所适用国家。0x21是美国*/

  0x01,                /*bNumDescriptors 大小1字节。表示下级描述符的数目。这里就只有一个报                        告描述符。为0x01。*/

  0x22,                /*bDescriptorsType 大小1字节。表示下级描述符的类型。报告描述符编号为                        0x22*/

  0x34,0x00,        /*bDescriptorsLength 大小2字节。表示下级报告描述符的长度*/                                                               
       
/******************************端点描述符****************************************************/
  0x07,      /* bLength 大小1字节。表示端点描述符长度*/

  0x05,              /* bDescriptorType 大小1字节。表示描述符的类型。端点描述符类型为码                        为0x05。*/

  0x81,      /* bEndpointAddress 大小1字节。表示端点的地址。最高位第7位为端点                        传输方向,1为输入。0为输出。第6—4位保留,值为0。第3—0位为端点                        号*/

  0x03,      /* bmAttributes 大小1字节。表示端点属性。最低位第1—0位表示端点传                        输类型,0为控制传输,1为等时传输,2为批量传输,3为中断传输。如果                        端点为非等时传输的端点,第7—2位就为保留值0。端点类型为等时传输时,                        第3—2位表示同步的类型,0为无同步,1为异步,2为适配,3为同步。第                        5—4位表示端点用途,0为数据端点,1为反馈端点,2为暗含反馈的数据端                        点,3为保留。最高位第7—6位保留。现在是用的中断传输编号为3,其他                        保留为0。*/

  0x10,0x00, /* wMaxPacketSize 大小2字节。表示端点最大包长度。设为16字节。*/

  0x0a,      /* bInterval 大小1字节。表示端点的查询时间。这里设为10ms*/

};
有了配置描述符现在就应该输入给主机配置描述符了,这个过程和输入设备描述符一样在__inline uint32_t USB_ReqGetDescriptor (void)请求描述符函数里的配置描述符类型分支里添加写入配置描述符的代码。代码如下:
case CONFIGURATION_DESCRIPTOR_TYPE:                //配置描述符
                                        #if PRINTF
                                                _DBG_("\r\n写入配置描述符\r\n");
                                        #endif
          EP0Data.pData = (uint8_t *)USB_ConfigDescriptor;
          len = sizeof(USB_ConfigDescriptor);
          break;
编译运行查看串口调试助手输出现象如图4—3—4:

图4—3—4
从串口打印的数据可以看出索要了9字节的配置描述符,而打印了12个字节多了09H 04H 00H(十六进制),那是应为在写入端点函数是按照字写入的也就是四字节写入,如果写两次就会少一个字节,所以不能少,而多出的三个字节可以不用管它。在写如的配置描述符里可以看到有02H 22H 两个十六进制数,这两个就是表示的是描述符的总长度34个字节。现在主机又输出了获取描述符的请求,这次是获取字符串描述符请求,请求的字符串索引号为0,而我们在设备描述符里定义的索引号没有索引号为0的字符串描述符。主机在索取索引号为0的字符串的目的是索取语言ID用的。接下来就说字符串描述符的事!



第四篇;字符串描述符
在USB协议里字符串描述符是可选的。在设备描述符里的字符串索引号不为0时,就表示它具有那个字符串描述符,要注意的是索引号不能重复。我们在设备描述符里申请了3个字符串描述符索引号分别是01H代表厂商字符串索引号、02H代表产品字符串索引号、03H代表设备序列号字符串索引号。主机就通过索引号来获取相应的字符串描述符。字符串索引号为0时,表示获取语言ID。语言ID是用来描述设备支持的语言种类。每个ID号占用两个字节。在字符串描述符中的bString域是用UNICODE编码的字符串。一下就是各个字符串描述符的代码。
/**************************************字符串描述符*******************************************/
/*语言ID*/
const uint8_t languageID[4] = {
  0x04,                /* bLength 大小1字节。表示描述符长度*/
  0x03,                /* bDescriptorType 大小1字节。表示描述符类型。字符串描述符的类型                                为0x03。*/
       
  0x09,        0x04        /* wLANGID 大小2字节。表示语言ID号。0x0409表示美式英语。*/
};

/*厂商字符串*/
const uint8_t ManufacturStringDescriptor[13*2 + 2] = {
  (13*2 + 2),           /* bLength 大小1字节。表示描述符长度 */
  0x03,                /* bDescriptorType 大小1字节。表示描述符类型。字符串描述符的类型                                为0x03*/
  'N',0,                        /*bString 大小2字节。这个字段用UNICODE编码的*/
  'X',0,
  'P',0,
  ' ',0,
  'S',0,
  'E',0,
  'M',0,
  'I',0,
  'C',0,
  'O',0,
  'N',0,
  'D',0,
  ' ',0
};

/*产品字符串*/
const uint8_t ProductDescriptor[18*2 + 2] = {
  (18*2 + 2),   /* bLength ( 17 Char + Type + lenght) */
  0x03,                /* bDescriptorType */
        ' ',0,
  0xFA,0x65,                //旺
  0x9D,0x5B,                //宝
  0x35,0x75,                        //电
  0x50,0x5B,                //子
  ' ',0,
  'L',0,
  'P',0,
  'C',0,
  '1',0,
  '7',0,
  '8',0,
  '8',0,
  ' ',0,
  'H',0,
  'I',0,
  'D',0,
  ' ',0
};
/*产品序列号字符串*/
const uint8_t SerialDescriptor[12*2 + 2] = {
  (12*2 + 2),   /* bLength (12 Char + Type + lenght) */
  0x03,        /* bDescriptorType */
  'D',0,
  'E',0,
  'M',0,
  'O',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0
};
有了字符串描述符现在就是输入字符串描述符了。前面讲过主机是通过索引号来分别区分索要的字符串描述符,那么在__inline uint32_t USB_ReqGetDescriptor (void)函数的case STRING_DESCRIPTOR_TYPE:分支代码中也要进行相应的判断。代码如下:
case STRING_DESCRIPTOR_TYPE:                                        //字符串描述符
        #if PRINTF
                _DBG_("\r\n字符串描述符\r\n");
        #endif
        switch (SetupPacket.wValue.WB.L){
                case 0:
                        EP0Data.pData = (uint8_t *)languageID;                                        /*语言ID*/
                        len = sizeof(languageID);                                                       
                break;
                case 1:
                        EP0Data.pData = (uint8_t *)ManufacturStringDescriptor;        /*厂商字符串*/
                        len = sizeof(ManufacturStringDescriptor);                                                       
                break;
                case 2:
                        EP0Data.pData = (uint8_t *)ProductDescriptor;                        /*产品字符串*/
                        len = sizeof(ProductDescriptor);                                               
                break;
                case 3:
                        EP0Data.pData = (uint8_t *)SerialDescriptor;                                /*产品序列号字                                                                                                                                符串*/
                        len = sizeof(SerialDescriptor);                                                       
                break;
                default:
                        #if PRINTF
                                _DBG_("\r\未知字符串描述符\r\n");
                        #endif                                       
                break;
        }
break;
现在编译程序下载到开发板上实验现象如图4—4—1:

图4—4—1
如果不出意外会连续经过获取配置描述符,字符串描述符,设备描述符,几次获取后就会接收到一个设置配置的SETUP包。


第五篇;设置配置
设置配置就按照端点描述符的说明,去配置相应的端点:如下代码:
void USB_ConfigEP(uint32_t EPNum, uint32_t MaxPacketSize)
{
  uint32_t val;
  val = (EPNum & 0x0F) << 1;
  if (EPNum & 0x80) {
    val += 1;
  }
       
  LPC_USB->ReEp |= 0x00000080;                //使能物理端点
  LPC_USB->EpInd = 0x00000003;                //端点索引寄存器,也是物理端点的编号(0—31)
  LPC_USB->MaxPSize = MaxPacketSize;        //端点最大包长度

        LPC_USB->ReEp |= 0x00000040;//(1 << val);                //使能物理端点
  LPC_USB->EpInd = 0x00000002;                //端点索引寄存器,也是物理端点的编号(0—31)
  LPC_USB->MaxPSize = MaxPacketSize;        //端点最大包长度
       
  while ((LPC_USB->DevIntSt & 1<<8) == 0);        //等待端点被使能
  LPC_USB->DevIntClr = 1<<8;                                //清除DevIntSt寄存器端点使能中断
        WrCmdDat(CMD_SET_EP_STAT(val), DAT_WR_BYTE(0));                //设置端点状态
}
第六篇;报告描述符
USB HID 设备是通过报告来传输数据的,报告分输入报告和输出报告。
输入报告是USB设备发送给主机的,如USB鼠标移动和鼠标点击信息输入给主机,键盘把按键信息输入给主机。
输出报告是主机发送给USB设备的,如USB键盘上的数字小键盘锁定灯和大小写指示灯等等。
而报告描述符,是用来描述一个报告的结构和报告里面的数据用来干嘛的,主机通过报告描述符,判断出报告里的数据所表示的含义。报告描述符和其他描述符一样都是通过控制输入端点0传输给主机的。一个报告描述符可以描述多个报告,不同的报告用ID号来区分。
报告描述符和其他描述不一样,它没有表示描述符长度和描述符类型的域。而是由一个个条目组成。条目分为长条目和短条目,这里只说短条目。
短条目由1字节的前缀和可选的数据组成,可选的数据大小从0、1、2、4字节不等。表4—1—1是一个短条目的结构。
D7—D4位        D3—D2位        D1—D0位
表示条目的功能具体查看HID协议        表示条目类型:
    0—表示主条目
    1—表示全局条目
    2—表示局部条目
    3—保留值        表示条目后面的数据字节:
   0—表示0字节
   1—表示1字节
   2—表示2字节
   3—表示4字节
主条目共5个分别是Input(输入)、Output(输出)、Feature(特性)、Collection(集合)、EndCollection(关集合)。
/*HID报告描述符*/
const uint8_t HID_ReportDescriptor[26*2] = {
        0x05, 0x01,                        // 用途页(generic desktop)
        0x09, 0x02,                        // 用法索引(mouse)
        0xA1, 0x01,                        // 集合开始(collection,application)
        0x09, 0x01,                        // 用法索引(指针)
        0xA1, 0x00,                        // 集合开始
        0x05, 0x09,                        // 用法页
        0x19, 0x01,                        // 用法最小值
        0x29, 0x03,                        // 用法最大值
        0x15, 0x00,                        // 逻辑最小值
        0x25, 0x01,                        // 逻辑最大值                                          
        0x95, 0x03,                    // 报告计数(3):三键
0x75, 0x01,                        // 报告大小(1):按键值占用1Bit
        0x81, 0x02,                        // 输入(2)                                          
        0x95, 0x01,                        // 报告计数(1)
        0x75, 0x05,                        // 报告大小(5)
        0x81, 0x01,                        // 输入(1)                                          
        0x05, 0x01,                        // 使用页(通用桌面)
        0x09, 0x30,                        // 用法(X)
        0x09, 0x31,                        // 用法(Y)
        0x09, 0x38,                        // 用法(Z)
        0x15, 0x81,                        // 逻辑最小值(129)
        0x25, 0x7F,                        // 逻辑最大值(127)
        0x75, 0x08,                        // 报告大小(8):每轴的数值占用一个字节
        0x95, 0x03,                        // 报告计数(3):三轴
        0x81, 0x06,                        // 输入(6)
        0xC0,                                 // 集合结束
        0xC0                                // 集合结束
};
现在在__inline uint32_t USB_ReqGetDescriptor (void)函数里添加返回报告描述符的代码,代码如下:

case REQUEST_TO_INTERFACE:                                //接口
      switch (SetupPacket.wValue.WB.H) {
        case HID_REPORT_DESCRIPTOR_TYPE:        //报告描述符       
#if PRINTF
                                        _DBG_("\r\n写入报告描述符\r\n");
#endif
                  EP0Data.pData = (uint8_t *)HID_ReportDescriptor;
                        len = sizeof(HID_ReportDescriptor);       
          break;

        default:
          return (FALSE);
     }
编译下载到开发板上看看现象。

在设备管理器里就看到了有两个鼠标和两个USB输入设备!
第四章会说到USB设备的枚举过程。结合代码和数据手册可以更好的理解。不足之处还请多多指点。此帖会不断更新,以实现一个USB HID枚举的整个过程。附件有实现本章的代码!

开发环境:集成开发环境µVision4 IDE版本4.60.0.0。
主机系统:Microsoft Windows XP。
开发平台:旺宝悍马1788开发板。

USB例程4.rar

862 KB, 下载次数: 25

此帖出自NXP MCU论坛
 
 
 

回复

2

帖子

0

TA的资源

一粒金砂(初级)

8
 
什么时候再更新啊
此帖出自NXP MCU论坛
 
 
 

回复

218

帖子

0

TA的资源

一粒金砂(中级)

9
 
楼主的脚步太快,,,,第三章给跳过去了。。。
此帖出自NXP MCU论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表