上一节通过STM32Cubemx生成了一个usb例程它的工程目录如下:
工程目录让我们一目了然,应用层(User),中间层(Middlewares),和驱动层(Drivers)
首先为了阅读代码的方便先说一下整个库文件的结构 Low Layer USB Driver(LL层)提供设备的API函数,每个模式支持内核初始化。 The Peripheral controllerdriver (PCD 层) HAL是(Hardware abstraction layer)硬件抽象层,里面全部是底层相关的函数 st现在做的底层库移植起来很容易,对新手来说理解起来更简单一下,但是对与使用过以前版本库的同学来说,这个版本的库用起来比较讨厌啊,完全是两个版本的函数库,我个人认为可能是为了匹配stm32cubemx做的,好处就是,应用的时候基本上不用考虑底层是怎么做的,只要调用函数就行,可是说实话,简单的应用还可以,但是如果是想要深入了解的话就比较麻烦了。 首先说说它的命名规则,比如stm32f4xx_hal_flash.c 包含了flash的基本操作,stm32f4xx_hal_flash_ex.c是flash的一些扩展操作。 对于usb来说,底层我们只关心stm32f4xx_ll_usb.c, stm32f4xx_hal_pcd.c和stm32f4xx_hal_pcd_ex.c其中后两个文件是USB的外设控制层(ThePeripheral controller driver PCD 层)
下面介绍一下主机是如何识别USB设备的: 一. 在这之前还要介绍一下usb设备的描述符: 1. 设备描述符(device Descriptor):
是设备在连接主机时,主机第一个请求的描述符。设备描述符提供了关于设备的、设备的配置以及设备的类别。
对应到我们程序中的就是usbd_desc.c中的USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] 数组,其中bcdUSB就是版本号,我们所说的usb1.0,usb2.0设备就是在这里定义的
bDeviceClass为那些设备层定义功能的的设备类 2. Device_qualifier 描述符
即支持全速状态也支持高速状态的设备,必须含有这个描述符,当设备转换速度时,设备描述符中某些字段的取值可能改变。 厂商ID,产品ID、设备版本号、制作商字符串、产品字符串以及序列号字符串都不会因为速度的改变而改变,因此device_qualifier描述中不包括这些字段 3. 配置描述符
如果主机取得了设备描述符,主机便可在取得设备的配置、接口和端点描述符 wTotalLength 等于配置描述符以及它所有附属描述符的总字节数(包括后面的接口描述符,端点描述符等)
bMaxPower 规定设备要求多少的总线电流,对于USB2.0,bMaxPower以2mA为单位。 4. 接口描述符
5. 端点描述符 6. 字符串描述符 以上描述符是通用的,usb设备根据协议不同,增加了不同的描述符,比如我用的例程是hid协议,在描述符中就增加了hid描述符,使用其他协议的话,还有其他描述符。 以下是HID描述符
配置描述符对应的我们的程序中的usbd_hid.c文件中的USBD_HID_CfgDesc[USB_HID_CONFIG_DESC_SIZ]数组
/* USB HID device Configuration Descriptor*/ __ALIGN_BEGIN static uint8_tUSBD_HID_CfgDesc[USB_HID_CONFIG_DESC_SIZ] __ALIGN_END = { /*********配置描述符*************/ 0x09, /* bLength: Configuration Descriptor size */ USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ USB_HID_CONFIG_DESC_SIZ, /*wTotalLength: Bytes returned */ 0x00, 0x01, /*bNumInterfaces: 1interface*/ 0x01, /*bConfigurationValue: Configuration value*/ 0x00, /*iConfiguration:Index of string descriptor describing theconfiguration*/ 0xE0, /*bmAttributes: buspowered and Support Remote Wake-up */ 0x32, /*MaxPower 100 mA:this current is used for detecting Vbus*/ /***********接口描述符*************/ /*09 */ 0x09, /*bLength: InterfaceDescriptor size*/ USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/ 0x00, /*bInterfaceNumber:Number of Interface*/ 0x00, /*bAlternateSetting:Alternate setting*/ 0x01, /*bNumEndpoints*/ 0x03, /*bInterfaceClass:HID*/ 0x01, /*bInterfaceSubClass: 1=BOOT, 0=no boot*/ 0x02, /*nInterfaceProtocol: 0=none, 1=keyboard, 2=mouse*/ 0, /*iInterface: Indexof string descriptor*/ /*******由于是HID设备,增加了一个HID描述符***********/ /*18 */ 0x09, /*bLength: HIDDescriptor size*/ HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ 0x11, /*bcdHID: HID ClassSpec release number*/ 0x01, 0x00, /*bCountryCode:Hardware target country*/ 0x01, /*bNumDescriptors:Number of HID class descriptors to follow*/ 0x22, /*bDescriptorType*/ HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/ 0x00, /******端点描述符****/ /*27 */ 0x07, /*bLength: EndpointDescriptor size*/ USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ HID_EPIN_ADDR, /*bEndpointAddress:Endpoint Address (IN)*/ 0x03, /*bmAttributes:Interrupt endpoint*/ HID_EPIN_SIZE, /*wMaxPacketSize: 4 Byte max */ 0x00, HID_FS_BINTERVAL, /*bInterval: Polling Interval (10 ms)*/ /*34 */ } ; 描述符介绍完了,那就看看描述符在usb运行的时候所起的作用。 二. 继续说说usb设备的枚举过程: 枚举过程中,设备会先后经历开机,缺省,地址和配置,连接,挂起 状态。
典型USB2.0枚举顺序 Windows枚举过程 1.主机集线器提供端口电源,设备处于开机状态(powered state)设备可从总线获取100mA的电流 2.集线器检测设备,集线器监视每个端口的信号线(D+或D-)电压,设备在插入到集线器是,设备上的上拉电阻会将信号线上的电位抬高,使集线器检测到设备。 3.主机获悉新设备。每个集线器会使用它的中断端点报告集线器上的事件,报告只表明是集线器还是端口经历了事件。 当获悉事件时,主机将发送一个Get Port Status 请求 以了解更多信息。 4.集线器检测设备是低速还是高速。在复位设备之前,集线器通过检测两根信号线上的电压来确定是低速还是全速。 然后集线器将给主机发送信息,响应下一个get port status 请求。 5.集线器复位设备。当主机获悉一新设备时,主机将向集线器发送一个Set Port Feature 请求。此信号要求集线器复位端口。 集线器只对新设备发送复位,总线上的其他集线器和设备都不会看到此复位信号。 6.主机了解全速设备是否支持高速状态。 7.集线器在设备和总线之间建立一条信号路径。主机会通过发送Get Port Status 请求证明设备已经暴脱复位状态。返回数据中的一个数据位将用于表明设备是否仍处于复位状态,若有必要主机还会重复此请求知道设备离开复位状态。 /***********************************以上看看就行了*********************************/ 8.主机发送Get Descriptor 请求,了解缺省管道的最大信息包尺寸,主机向设备地址00h,端口0发送此请求。由于主机每次只枚举一个设备,故也只有一个设备会响应00h通信。 设备描述符的第8个字节含有端点0所支持的最大信息包尺寸。收到信息包后,主机便会开启传输的状态阶段,状态阶段完成时,Windows主机会请求集线器复位设备,如同第5步。 9.主机指定一个地址。复位完成时,主机控制器通过发送Set Address请求,为设备指定唯一地址。设备将以缺省地址完成请求状态阶段,之后才开始执行新地址。设备会一直处于地址状态(address state)。从这个端口所发出的所有通信会指向新的地址,地址会一直有效,直到设备断开,集线器复位端口或者系统重启。下次枚举时,主机可能会分配给设备一个不同的地址。 10.主机了解设备能力。 主机向新地址发送Get Descriptor 请求,读取设备描述符,这一次主机会取回整个描述符,描述符含有端点0的最大信息包尺寸、设备支持的配置数量,以及其他基本信息。 主机会通过请求一个或者多个指定在设备描述符内的配置描述符,继续了解设备。windosws主机一开始只请求配置描述符9个字节,这些字节包括了配置描述符的总长度以及它的附属描述符,然后Windows主机会再次请求配置描述符,而且使用所得的总长度来请求字节数。设备会通过发送配置描述符来做响应。其后会跟全部的配置的附属描述符。 /***************************以上比较重要****************************/ 11.主机指定并加载设备驱动程序。根据描述符了解完成设备信息后,主机会寻找最匹配的驱动程序,来管理与设备通信。Windows主机使用inf文件确认最优匹配。 对于之前已经枚举过的设备,windows会使用已经存储的信息,而不再找寻INF文件。 在操作系统指定并加载完驱动程序后,驱动程序可能会向设备请求重新发送描述符和其他类型的专属描述符, 12.主机的设备驱动程序会选择配置。从描述符那里了解完设备信息后,设备驱动程序会通过发送带有配置号的请求,来请求配置。设备接到请求之后,设备就实现那个被请求的配置。至此设备就进入了已配置状态,设备接口也就被使能了。
实际上根据上面的步骤,其实usb枚举过程可以简单的看成 主机获取usb设备描述符 主机再次获取usb设备描述符 主机获取一部分配置描述符,以获取整个配置描述符的长度
主机获取全部配置描述符
|