USB 略解

来源:互联网 发布:c语言杨辉直角三角形 编辑:程序博客网 时间:2024/06/05 06:08
  1. USB协议分析
    1. USB概述
    2. USB描述符
    3. USB数据传输
    4. USB枚举
  2. USB_IP
    1. 深入解析STM32_USB-FS-Device_Lib库函数
    2. 描述符获取过程
    3. 库函数USB_Istr 分析

1、USB入门:

1.1 USB概述:

USB是通用串行总线(Universal Serial Bus)

连接模型:

所有的数据传输都由主机主动发起,而设备只是被动的负责应答。例如,在读数据时,USB先发出读命令,设备收到该命令后,才返回数据。


USB的拓扑结构为金字塔型:

USB数据传输路径如下:USB主控制器发出数据包,通过根集线器,再通过下面的集线器(如果有的话),再发给USB设备;设备返回数据,交给它上层的集线器,上层的集线器再交给更上层的集线器,直到USB主控制器为止。而USB主控制器就可以跟CPU打交道了。

USB电气特性:

标准的USB使用4根线:5V电源线(Vbus),差分数据线负(D-),差分数据线正(D+),地(Gnd)。在USB OTG中,又增加了一种mini接口,使用的是5根线,比标准的USB多了一根身份识别(ID)线。USB使用的是差分传输模式,有两根数据线,分别是D+和D-。在USB的低速和全速模式中,采用的是电压传输模式。而在高速模式下,则是电流传输模式。关于具体的高低电平门限值,请参看USB协议。为了防止出现长时间的0或者1(这样不利于时钟信号的提取),在发送数据前要经过位填充处理。然后再将数据串行化,发送到数据线上,由两根数据线的差分值来表示0或者1。而在接收端,则刚好是相反的过程。接收端采样数据线,将数据并行化,并同时去掉未填充,然后解析数据。通常,我们使用现成的USB芯片,像位填充,串行化这些芯片内部的硬件已经帮我们做好了,因此通常我们并不用关心这些细节。


USB接收处理:

在设备接收数据时,芯片的串行接口引擎(SIE)会接收属于自己地址的数据,并根据相应的端口号,放到相应的缓冲区内,并返回ACK给主机进行确认,然后产生中断请求,通知我们的程序,已经收到数据包了。在我们还未处理完缓冲区的数据之前,如果再收到对该端点的输出请求,USB芯片将会使用NAK返回,告诉主机端点现在忙,主机检测到NAK后,过段时间会重试输出数据,直到超时为止;发送数据时,用户将数据写入USB芯片的缓冲区,并通知USB芯片缓冲区内数据可用,然后USB芯片检测到主机请求对应的端点输入时,它就会将数据返回,数据发送完毕并收到主机的ACK确认之后,产生中断请求通知应用程序数据已经发送完毕。如果USB芯片已经收到了输入请求,但是用户程序还未填充好缓冲区,它也会用NAK返回,告诉主机数据还未准备好。主机收到NAK后,过段时间会重试,直到超时为止。

USB设备的插入检测机制:

USB主机是如何检测到设备的插入的呢?首先,在USB集线器的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻。对于全速和高速设备, 上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到高速模式的。在高速模式下,是电流传输模式,这时将D+上的 上拉电阻断开。

USB设备的枚举过程:

USB主机在检测到USB设备插入后,就要对设备进行枚举了。为什么要枚举呢?枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序。调试USB设备,很重要的一点就是USB的枚举过程,只要枚举成功了,那么就已经成功大半了。

USB设备枚举过程主要可分为8个部分:

  1. 获取设备描述符
  2. 复位
  3. 设置地址
  4. 再次获取设备描述符
  5. 获取配置描述符
  6. 获取接口、端点描述符
  7. 获取字符串描述符
  8. 选择设备配置

USB枚举过程详解:

USB协议定义了设备的6种状态,仅在枚举过程中,设备就经历了4个状态的迁移:上电状态(Powered),默认状态(Default),地址状态(Address)和配置状态(Configured)(其他两种是连接状态和挂起状态(Suspend))。

(1)用户把USB设备插入USB端口或系统启动时给设备上电

这里的USB端口指的是主机下的根hub或主机下行端口上的hub端口。Hub给端口供电(hub.c:usb_hub_power_on()),连接着的设备处于上电状态。此时,USB设备处于加电状态,它所连接的端口是无效的。

(2) Hub监测它各个端口数据线上(D+/D-)的电压

在hub端,数据线D+和D-都有一个阻值在14.25k到24.8k的下拉电阻Rpd,而在设备端,D+(全速,高速)和D-(低速)上有一个1.5k的上拉电阻Rpu。当设备插入到hub端口时,有上拉电阻的一根数据线被拉高到幅值的90%的电压(大致是3V)。hub检测到它的一根数据线是高电平,就认为是有设备插入,并能根据是D+还是D-被拉高来判断到底是什么设备(全速/低速)插入端口。检测到设备后,hub继续给设备供电,但并不急于与设备进行USB传输。

(3)Host了解连接的设备

每个hub利用它自己的中断端点向主机报告它的各个端口的状态(hub.c:usb_hub_events()),报告的内容只是hub端口的设备连接/断开的事件。如果有连接/断开事件发生(hub.c:usb_hub_port_connect_change()),那么host会发送一个 usb_hub_port_status请求给hub以了解此次状态改变的确切含义。usb_hub_port_status等请求属于所有hub都要求支持的hub类标准请求(standard hub-class requests)。

(4) Hub检测所插入的设备是高速还是低速设备

hub通过检测USB总线空闲(Idle)时差分线的高低电压来判断所连接设备的速度类型,当host发来usb_hub_port_status请求时,hub就可以将此设备的速度类型信息回复给host。

(5) hub复位设备

主机一旦得知新设备已连上以后,它至少等待100ms以使得插入操作的完成以及设备电源稳定工作。然后主机控制器就向hub发出一个 usb_set_port_feature请求让hub复位其管理的端口(刚才设备插上的端口)。hub通过驱动数据线到复位状态(D+和D-全为低电平 ),并持续至少10ms。当然,hub不会把这样的复位信号发送给其他已有设备连接的端口,所以其他连在该hub上的设备自然看不到复位信号,不受影响。

(6) Host检测所连接的全速设备是否是支持高速模式

对于USB1.1协议,host不进行高速检测,设备将一直以全速工作。如果是USB 2.0协议,高速(High Speed)设备在初始时是默认全速(Full Speed )状态运行,所以对于一个支持USB 2.0的高速hub,当它发现它的端口连接的是一个全速设备时,会进行高速检测,看看目前这个设备是否还支持高速传输,如果是,那就切到高速信号模式,否则就一直在全速状态下工作。
同样的,从设备的角度来看,如果是一个高速设备,在刚连接bub或上电时只能用全速信号模式运行(根据USB 2.0协议,高速设备必须向下兼容USB 1.1的全速模式)。随后hub会进行高速检测,之后这个设备才会切换到高速模式下工作。

(7) Hub建立设备和主机之间的信息通道

主机通过usb_hub_port_wait_reset不停地向hub发送usb_hub_port_status请求,以查询设备是否复位成功。Hub返回的报告信息中有专门的一位用来标志设备的复位状态。
当hub撤销了复位信号,设备就处于默认/空闲状态(Default state),准备接收主机发来的请求。设备和主机之间的通信通过控制传输,默认地址0,端点号0进行。此时,设备能从总线上得到的最大电流是100mA。(所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信。)

(8) 主机发送usb_get_device_descriptor请求获取默认管道的最大包长度

默认管道(Default Pipe)在设备一端来看就是端点0。主机此时发送的请求是默认地址0,端点0,虽然所有未分配地址的设备都是通过地址0来获取主机发来的请求,但由于枚举过程不是多个设备并行处理,而是一次枚举一个设备的方式进行,所以不会发生多个设备同时响应主机发来的请求。
设备描述符(附1)的第8字节(bMaxPacketSize)代表设备端点0的最大包大小。虽然说设备所返回的设备描述符(Device Descriptor)长度只有18字节,但系统也不在乎,此时,描述符的长度信息对它来说是最重要的。当完成第一次的控制传输后,也就是完成控制传输的状态阶段,系统会要求hub对设备进行再一次的复位操作(USB规范里面可没这要求)。再次复位的目的是使设备进入一个确定的状态。

(9) 主机给设备分配一个地址

主机控制器通过usb_set_address请求向设备分配一个唯一的地址。在完成这次传输之后,设备进入地址状态(Address state),之后就启用新地址继续与主机通信。这个地址对于设备来说是终生制的,设备在,地址在;设备消失(被拔出,复位,系统重启),地址被收回。同一个设备当再次被枚举后得到的地址不一定是上次那个了。

(10) 主机获取设备的信息

主机发送 usb_get_device_descriptor请求到新地址读取设备描述符,这次主机发送usb_get_device_descriptor会认真解析设备描述符的内容。设备描述符内信息包括端点0的最大包长度,设备所支持的配置(Configuration)个数,设备类型,VID(Vendor ID,由USB-IF分配), PID((Product ID,由厂商自己定制)等信息。Get_Descriptor请求(Device type)和设备描述符(已抹去VID,PID等信息)
之后主机发送usb_get_configuration请求,读取配置描述符(附2),字符串等,逐一了解设备更详细的信息。事实上,对于配置描述符的标准请求中,有时bLength(附2)一项会大于实际配置描述符的长度(9字节),比如255。这样的效果便是:主机发送了一个usb_get_configuration 的请求,设备会把接口描述符,端点描述符等后续描述符一并回给主机,主机则根据描述符头部的标志判断送上来的具体是何种描述符。
接下来,主机就会获取配置描述符。配置描述符总共为9字节。主机在获取到配置描述符后,根据里面的配置集合总长度,再获取配置集合。配置集合包括配置描述符,接口描述符,端点描符等等。
如果有字符串描述符的话,还要获取字符串描述符。另外HID设备还有HID描述符等。

(11) 主机给设备挂载驱动

主机通过解析描述符后对设备有了足够的了解,会通过usb_find_drivers选择一个最合适的驱动给设备。然后tell the world(announce_device)说明设备已经找到了,最后调用设备模型提供的接口usbdevfs_add_device将设备添加到 usb 总线的设备列表里,然后 usb总线会遍历驱动列表usb_driver_list里的每个驱动,调用自己的 usb_match_id 函数看它们和当前连接的设备或接口是否匹配,匹配的话就将控制权交到相应的设备驱动了。
对于复合设备,通常应该是不同的接口(Interface)配置给不同的驱动,因此,需要等到当设备被配置并把接口使能后才可以把驱动挂载上去。

(12) 设备驱动选择一个配置

驱动(注意,这里是驱动,之后的事情都是由驱动来接管负责与设备的通信)根据前面设备回复的信息,发送usb_set_configuration请求来正式确定选择设备的哪个配置(Configuration)作为工作配置(对于大多数设备来说,一般只有一个配置被定义)。至此,设备处于配置状态(Configured),当然,设备也应该使能它的各个接口(Interface)。
对于复合设备,主机会在这个时候根据设备接口信息,给它们挂载驱动。至此,USB枚举过程结束,设备可以正常使用了。


USB总线协议:

所有总线操作都可以归结为三种包的传输 任何操作都是从主机开始的 主机以预先排好的时序 发出一个描述操作类型 方向 外设地址以及端点号(这将在以下部分给予解释)的包 我们称之为令牌包(Token Packet) 然后在令牌中指定的数据发送者发出一个数据包或者指出它没有数据可以传输 而数据的目的地一般要以一个确认包(Handshake Packet)作出响应以表明传输是否成功

1.令牌(token)包
在USB系统中,只有主机才能发出令牌包。令牌包定义了数据传输的类型,它是事务处理的第一阶段。令牌包中较为重要的是SETUP、IN和OUT这三个令牌包。它们用来在根集线器和设备端点之间建立数据传输。一个IN包用来建立一个从设备到根集线器的数据传送,一个OUT包用来建立从根集线器到设备的数据传输。令牌包格式如下:

2.数据(data)包
数据封包含有4个域:SYNC、PID、DATA与CRC16。DATA数据域的位值是根据USB设备的传输速度及传输类型而定,且须以8字节为基本单位。也就是,若传输的数据不足8字节,或传输到最后所剩余的也不足8字节,仍须传输8字节的数据域。格式如下:

  1. 握手(Handshake)包
    握手信息包是最简单的信息包类型。在这个握手信息包中仅包含一个PID数据域而已,它的格式如下所列:

包(Packet)是USB系统中信息传输的基本单元,所有数据都是经过打包后在总线上传输的。USB包由五部分组成,即同步字段(SYNC)、包标识符字段(PID)、数据字段、循环冗余校验字段(CRC)和包结尾字段(EOP),包的基本格式如下图:

1、SYNC字段:由8位组成,作为每个数据封包的前导,用来产生同步作用,使USB设备与总线的包传输率同步,它的数值固定为00000001。

2、PID字段:用来表示数据封包的类型。包标识符中的校验字段是通过对类型字段的每个位求反码产生的, PID字段如下图所示:

3、数据字段:是用来携带主机与设备之间要传递的信息,其内容和长度根据包标识符、传输类型的不同而各不相同。在USB包中,数据字段可以包含设备地址、端点号、帧序列号以及数据等内容。在总线传输中,总是首先传输字节的最低位,最后传输字节的最高位。

(1) 设备地址(ADDR)数据域
ADDR数据域由7位组成,可用来寻址多达127个外围设备。

(2) 端点(ENDP)数据域
ENDP数据域由4位组成。通过这4个位最多可寻址出32个端点。这个ENDP数据域仅用在IN、OUT与SETUP令牌信息包中。对于慢速设备可支持端点0以及端点1作为中断传输模式,而全速设备则可以拥有16个输入端点(IN)与16个输出端点(OUT)共32个端点。

(3) 帧序列号
当USB令牌包的PID为SOF时,其数据字段必须为11位的帧序列号。帧序列号由主机产生,且每个数据帧自动加一,最大数值为0x7FF。当帧序列号达到最大数时将自动从0开始循环。

(4) 数据
它仅存于DATA信息包内,根据不同的传输类型,拥有不同的字节大小,从0到1023字节(实时传输)。

4、循环冗余码CRC字段由不同数目的位所组成。根据不同的信息包类型,CRC数据域由不同数目的位所组成。其中重要的数据信息包采用CRC16的数据域(16个位),而其余的信息包类型则采用CRC5的数据域(5个位)。其中的循环冗余码校验CRC,是一种错误检测技术。由于数据在传输时,有时候会发生错误,因此CRC可根据数据算出一个校验值,然后依此判断数据的正确性

5、包结尾字段即发送方在包的结尾发出包结尾信号。USB主机根据EOP判断数据包的结束。

事务:

1.输入(IN)事务处理

2.输出(OUT)事务处理

3.设置(SETUP)事务处理


USB的描述及各种描述符之间的关系:

USB是个通用的总线,端口都是统一的。但是USB设备却各种各样,例如USB鼠标,USB键盘,U盘等等,那么USB主机是如何识别出不同的设备的呢?这就要依赖于描述符了。

一个USB设备有一个设备描述符,设备描述符里面决定了该设备有多少种配置,每种配置描述符对应着配置描述符;而在配置描述符中又定义了该配置里面有多少个接口,每个接口有对应的接口描述符;在接口描述符里面又定义了该接口有多少个端点,每个端点对应一个端点描述符;端点描述符定义了端点的大小,类型等等。由此我们可以看出,USB的描述符之间的关系是一层一层的,最上一层是设备描述符,下面是配置描述符,再下面是接口描述符,再下面是端点描述符。在获取描述符时,先获取设备描述符,然后再获取配置描述符,根据配置描述符中的配置集合长度,一次将配置描述符、接口描述符、端点描述符一起一次读回。其中可能还会有获取设备序列号,厂商字符串,产品字符串等。

每种描述符都有自己独立的编号,如下:

#define DEVICE_DESCRIPTOR   0x01  //设备描述符#define CONFIGURATION_DESCRIPTOR0x02  //配置描述符#define STRING_DESCRIPTOR   0x03  //字符串描述符#define INTERFACE_DESCRIPTOR0x04  //接口描述符#define ENDPOINT_DESCRIPTOR 0x05  //端点描述符

#

定义标准的设备描述符结构


typedef struct _DEVICE_DCESCRIPTOR_STRUCT

{BYTE blength;   //设备描述符的字节数大小BYTE bDescriptorType;  //设备描述符类型编号WORD bcdUSB;//USB版本号BYTE bDeviceClass;  //USB分配的设备类代码BYTE bDeviceSubClass;   //USB分配的子类代码BYTE bDeviceProtocol;   //USB分配的设备协议代码BYTE bMaxPacketSize0;   //端点0的最大包大小WORD idVendor;  //厂商编号WORD idProduct; //产品编号WORD bcdDevice; //设备出厂编号BYTE iManufacturer; //设备厂商字符串的索引BYTE iProduct;  //描述产品字符串的索引BYTE iSerialNumber; //描述设备序列号字符串的索引BYTE bNumConfigurations;//可能的配置数量}

#

实际的设备描述符示例

code DEVICE_DESCRIPTOR_STRUCT device_descriptor= //设备描述符

{sizeof(DEVICE_DESCRIPTOR_STRUCT),   //设备描述符的字节数大小,这里是18字节DEVICE_DESCRIPTOR,  //设备描述符类型编号,设备描述符是010x1001,   //USB版本号,这里是USB01.10,即USB1.1。由于51是大端模式,所以高低字节交换0x00, //USB分配的设备类代码,0表示类型在接口描述符中定义0x00, //USB分配的子类代码,上面一项为0时,本项也要设置为00x00,//USB分配的设备协议代码,上面一项为0时,本项也要设置为00x10,//端点0的最大包大小,这里为16字节0x7104, //厂商编号,这个是需要跟USB组织申请的ID号,表示厂商代号。0xf0ff,//该产品的编号,跟厂商编号一起配合使用,让主机注册该设备并加载相应的驱动程序0x0100,   //设备出厂编号0x01,//设备厂商字符串的索引,在获取字符串描述符时,使用该索引号来识别不同的字符串0x02,//描述产品字符串的索引,同上0x03, //描述设备序列号字符串的索引,同上0x01//可能的配置数为1,即该设备只有一个配置};

#

2.配置描述符


typedef struct _CONFIGURATION_DESCRIPTOR_STRUCT

{BYTE bLength;   //配置描述符的字节数大小BYTE bDescriptorType;   //配置描述符类型编号WORD wTotalLength;  //此配置返回的所有数据大小BYTE bNumInterfaces;//此配置所支持的接口数量BYTE bConfigurationValue;   //Set_Configuration命令所需要的参数值BYTE iConfiguration;//描述该配置的字符串的索引值BYTE bmAttributes;  //供电模式的选择BYTE MaxPower;  //设备从总线提取的最大电流}CONFIGURATION_DESCRIPTOR_STRUCT, * pCONFIGURATION_DESCRIPTOR_STRUCT;

#
3.接口描述符

     ![](https://i.imgur.com/kZq3jaw.png)  typedef struct _INTERFACE_DESCRIPTOR_STRUCT{BYTE bLength;   //接口描述符的字节数大小BYTE bDescriptorType;   //接口描述符的类型编号BYTE bInterfaceNumber;  //该接口的编号BYTE bAlternateSetting; //备用的接口描述符编号BYTE bNumEndpoints; //该接口使用的端点数,不包括端点0BYTE bInterfaceClass;   //接口类型BYTE bInterfaceSubClass;//接口子类型BYTE bInterfaceProtocol;//接口遵循的协议BYTE iInterface;//描述该接口的字符串索引值}INTERFACE_DESCRIPTOR_STRUCT,*pINTERFACE_DESCRIPTOR_STRUCT;

#

4.端点描述符

typedef struct _ENDPOINT_DESCRIPTOR_STRUCT

{BYTE bLegth;//端点描述符字节数大小BYTE bDescriptorType;   //端点描述符类型编号BYTE bEndpointAddress;  //端点地址及输入输出属性BYTE bmAttributes;  //端点的传输类型属性WORD wMaxPacketSize;//端点收、发的最大包大小BYTE bInterval; //主机查询端点的时间间隔}ENDPOINT_DESCRIPTOR_STRUCT, * pENDPOINT_DESCRIPTOR_STRUCT;

#

其中关于端点的类型定义如下

//定义的端点类型#define ENDPOINT_TYPE_CONTROL   0x00  //控制传输#define ENDPOINT_TYPE_ISOCHRONOUS   0x01  //同步传输#define ENDPOINT_TYPE_BULK  0x02  //批量传输#define ENDPOINT_TYPE_INTERRUPT 0x03  //中断传输

#


2、 USB_IP:

2.1 深入解析STM32_USB-FS-Device_Lib库函数:

2.2库函数 USB_Istr 分析:

2.3枚举前的准备工作

  1. 首先系统执行USB中断设置:USB_Interrupts_Config();//设置中断向量表,设置优先级
  2. 然后执行USB时钟设置:Set_USBClock();//时钟设置,USB使用48M时钟,
  3. 然后执行USB初始化设置:USB_Init();//这个函数比较重要
  4. 在执行USB初始化设置主要进行结构体,与函数指针的配置

#
设置中断向量表

void USB_Interrupts_Config(void){ NVIC_InitTypeDef NVIC_InitStructure;  NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN_RX0_IRQChannel; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);   // enable timer2 NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQChannel; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);// }

设置时间

void Set_USBClock(void){  // Select USBCLK source  RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);  // 72MHZ sysclk  //  RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_Div1);  /* Enable USB clock */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);}

USB初始化

void USB_Init(void){ pInformation = &Device_Info;//USB 信息 pInformation->ControlState = 2; pProperty = &Device_Property;//设备本身的属性和方法 pUser_Standard_Requests = &User_Standard_Requests;//主机请求的实现方法   //Initialize devices one by one pProperty->Init();//初始化}

#

1、USB信息及设备属性和方法结构体 的填充:

pProperty = &Device_Property

结构体定义:

typedef struct _DEVICE_PROP{  void (*Init)(void);     void (*Reset)(void);      void (*Process_Status_IN)(void);     void (*Process_Status_OUT)(void);      RESULT (*Class_Data_Setup)(u8 RequestNo);        RESULT (*Class_NoData_Setup)(u8 RequestNo);      RESULT  (*Class_Get_Interface_Setting)(u8 Interface, u8 AlternateSetting);      u8* (*GetDeviceDescriptor)(u16 Length);  u8* (*GetConfigDescriptor)(u16 Length);  u8* (*GetStringDescriptor)(u16 Length);  u8* RxEP_buffer;  u8 MaxPacketSize;    }DEVICE_PROP;

#

填充:

DEVICE_PROP Device_Property =  {CustomHID_init,CustomHID_Reset,CustomHID_Status_In,CustomHID_Status_Out,CustomHID_Data_Setup,CustomHID_NoData_Setup,CustomHID_Get_Interface_Setting,CustomHID_GetDeviceDescriptor,CustomHID_GetConfigDescriptor,CustomHID_GetStringDescriptor,0,0x40 /*MAX PACKET SIZE*/  };

#

初始化

pProperty->Init();//初始化

void CustomHID_init(void){  /* Update the serial number string descriptor with the data from the unique   ID*/ Get_SerialNum();//获取设备序列号 pInformation->Current_Configuration = 0;  /* Connect the device */ PowerOn();  /* USB interrupts initialization */ _SetISTR(0);   /* clear pending interrupts */ wInterrupt_Mask = IMR_MSK;_SetCNTR(wInterrupt_Mask); /* set interrupts mask */ bDeviceState = UNCONNECTED;}

#

初始化中首先Get_SerialNum();获取STM32内部唯一标识码。然后赋给CustomHID_StringSerial,不过这个东西好像并没有什么用

init中接下来执行 PowerOn()

这个函数首先把D+的上拉电阻上电;这样电脑就可以检测到设备了。(集线器报告设备连接状态,并收到主机指令后,会复位 USB总线,这需要一定的时间(这段时间内设备应该准备好处理复位指令)。但是现在设备初始化程序将继续往下进行,因为它还没有使能复位中断)。
#

连接USB,实质上是让主机检测到*******************************************************************************/RESULT PowerOn(void){  u16 wRegVal;  /*** cable plugged-in ? ***/  USB_Cable_Config(ENABLE);//上电连接,实质上就是将USB控制线置为低电平,便于主机检测  /*** CNTR_PWDN = 0 ***/ //USB 收发器内部参照电压  wRegVal = CNTR_FRES;  _SetCNTR(wRegVal);//强制复位,使能了USB电源,需要过段时间,同时中断不允许,不会中断  /*** CNTR_FRES = 0 ***/  wInterrupt_Mask = 0;  _SetCNTR(wInterrupt_Mask);//清除复位  /*** Clear pending interrupts ***/  _SetISTR(0);  /*** Set interrupt mask ***/  wInterrupt_Mask = CNTR_RESETM | CNTR_SUSPM | CNTR_WKUPM;//复位、挂起、唤醒中断使能  _SetCNTR(wInterrupt_Mask);  return USB_SUCCESS;}

执行中断:

程序直接进入了USB_LP_CAN1_RX0_IRQHandler的中断口,执行 USB_Istr();通过匹配发现中断源为复位中断,程序运行到复位中断部分,开始执行复位程序(这里提一下,判断发生复位中断后,首先清中断位。在其它几个中断源也是先清中断位,但是CTR_LP();函数之前却没有清中断位是为什么呢?在STM32参考手册中是这样说的:端点在成功完成一次传输后, CTR位会被硬件置起,如果USB_CNTR上的相应位也被设置的话,就会产生中断。与端点相关的中断标志和USB_CNTR寄存器的CTRM位无关。这两个中断标志位将一直保持有效,直到应用程序清除了USB_EpnR寄存器中的相关中断挂起位(CTR位是个只读位)。)

/******************************************************************************** Function Name  : USB_LP_CAN_RX0_IRQHandler* Description: This function handles USB Low Priority or CAN RX0 interrupts *  requests.* Input  : None* Output : None* Return : None*主机进入控制传输的第一阶段:建立事务,发setup命令包,发请求数据包、设备发ACK包*主机对端点0发setup令牌包,端点0寄存器的11位  setup 置位,标明收到setup*此时端点0数据接收有效,接下来主机的请求数据包保存到端点0描述附表的RXADDR里面,收到的字节数保存到RXcount里*端点0的CTR-RX 置位,ISTR的CTR置位 DIR=1 EP_ID=0,表示端点0接收到主机来的请求数据,此时设备ACK主机,进入正确传输中断*******************************************************************************/void USB_LP_CAN_RX0_IRQHandler(void){USB_Istr();}

#

首次连接主机,主机会下发从机复位

void CustomHID_Reset(void){  /* Set Joystick_DEVICE as not configured */ pInformation->Current_Configuration = 0;//当前配置为0 pInformation->Current_Interface = 0;  /*the default Interface 当前接口0*/  /* Current Feature initialization */ pInformation->Current_Feature = CustomHID_ConfigDescriptor[7];//需要总线供电 SetBTABLE(BTABLE_ADDRESS);//设置缓冲包地址  /* Initialize Endpoint 0 */ SetEPType(ENDP0, EP_CONTROL);//端点0为控制端点 SetEPTxStatus(ENDP0, EP_TX_STALL);//主机IN令牌到,会送STALL SetEPRxAddr(ENDP0, ENDP0_RXADDR);//设置端点0描述表:包括接收缓冲区地址、最大允许接收的字节数、发送缓冲区地址 SetEPTxAddr(ENDP0, ENDP0_TXADDR);//发送缓冲区地址 Clear_Status_Out(ENDP0);//清楚Status_out,如果该位被设置,在控制模式下,只对0字节响应,其余都响应stall,主要用于控制传输的状态 SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);//接收缓冲区支持64字节 SetEPRxValid(ENDP0);//使能端点0的接收  /* Initialize Endpoint 1 */ SetEPType(ENDP1, EP_INTERRUPT);//端点1为中断端点 SetEPRxAddr(ENDP1, ENDP1_RXADDR);//端点1接收缓冲区地址 SetEPRxCount(ENDP1, nReportCnt);//端点1接收长度 SetEPRxStatus(ENDP1, EP_RX_VALID);//端点1接收允许 //SetEPTxStatus(ENDP1, EP_TX_DIS);  /* Initialize Endpoint 2 */ SetEPType(ENDP2, EP_INTERRUPT);//端点2为中断端点 SetEPTxAddr(ENDP2, ENDP2_TXADDR);//端点2发送缓冲区 SetEPTxCount(ENDP2, nReportCnt);//端点2发送长度// SetEPTxStatus(ENDP2, EP_TX_DIS); SetEPTxStatus(ENDP2, EP_TX_NAK);//现在不允许端点2发送数据 bDeviceState = ATTACHED;//连接状态为已连接  /* Set this device to response on default address */ SetDeviceAddress(0);//地址默认为0 }

#

枚举 :

1、获取设备描述符

设备描述符号:

 const u8 CustomHID_DeviceDescriptor[CUSTOMHID_SIZ_DEVICE_DESC] =  {0x12,   /*bLength长度,18字节 */USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType类型:01*/0x00,   /*bcdUSB  USB 版本:2.0 */0x02,0x00,   /*bDeviceClass设备使用的类代码*/0x00,   /*bDeviceSubClass设备使用的子类代码*/0x00,   /*bDeviceProtocol设备使用的协议*/0x40,   /*bMaxPacketSize40:最大包长度 */0x83,   /*idVendor (0x0483):厂商ID代码*/0x04,0x50,   /*idProduct = 0x5750产品ID代码*/0x57,0x00,   /*bcdDevice rel. 2.00设备版本号*/0x02,1,  /*Index of string descriptor describing  manufacturer 厂商字符串索引*/2,  /*Index of string descriptor describing product产品字符串索引*/3,  /*Index of string descriptor describing the device serial number 设备字符串序列号索引*/0x01/*bNumConfigurations设备配置种类*/  }  ; /* CustomHID_DeviceDescriptor */

#

#
DEVICE_PROP Device_Property =
{
CustomHID_init,
CustomHID_Reset,
CustomHID_Status_In,
CustomHID_Status_Out,
CustomHID_Data_Setup,
CustomHID_NoData_Setup,
CustomHID_Get_Interface_Setting,
CustomHID_GetDeviceDescriptor,
CustomHID_GetConfigDescriptor,
CustomHID_GetStringDescriptor,
0,
0x40 /MAX PACKET SIZE/
};

#

追:

u8 *CustomHID_GetDeviceDescriptor(u16 Length){  return Standard_GetDescriptorData(Length, &Device_Descriptor);}

#

结构体包装:

ONE_DESCRIPTOR Device_Descriptor =  {(u8*)CustomHID_DeviceDescriptor,CUSTOMHID_SIZ_DEVICE_DESC  };

#

追: 通过偏移来取出描述符号

u8 *Standard_GetDescriptorData(u16 Length, ONE_DESCRIPTOR *pDesc){  u32  wOffset;  wOffset = pInformation->Ctrl_Info.Usb_wOffset;  if (Length == 0)  {pInformation->Ctrl_Info.Usb_wLength = pDesc->Descriptor_Size - wOffset;return 0;  }  return pDesc->Descriptor + wOffset;  //通过偏移来取出}

#

USB_Istr

void CTR_LP(void)

Setup0_Process

void Data_Setup0(void)

DataStageIn

2、主机发送复位命令

3、设置地址

4、从新地址获取设备描述符

5、获取配置描述符

6、获取字符串描述符。

7、主机再次获取设备描述符和配置描述符集合

8、主机设置配置

9、主机获取报告描述符

#

#

喜欢我的博客,可以加我一起交流哦。

原创粉丝点击