USB枚举过程中的各种获取描述符的标准请求

本文详细介绍了STM32中USB2.0设备处理GET_DESCRIPTOR请求的过程,包括判断请求类型、识别设备描述符、配置描述符和字符串描述符,以及如何响应GET_STATUS请求,包括设备状态、接口状态和端点状态的获取。此外,还涉及了清除特征请求用于修复端点问题。
摘要由CSDN通过智能技术生成

前言 

     最近学习了USB2.0的一些知识,这里就当笔记,回顾记录一下,参考的资料是B站的刘凯老师STM32 培训教程和龙虎老师的USB应用精粹分析,如果有哪些错误的理解,也希望大佬们指点一二,谢谢!

回到Data_Setup0()函数,如果主机发出“获取描述符” GET_DESCRIPTOR的请求。

8d72c1d72d3a412897b05740ff94a16d.png

那么我们就要判断是 这个 Type_Recipient,他是一个宏定义,取出了USBbmRequestType的低七位,其中包含了请求种类与接收方。

3a8d81d455b54ba7a466281753d71c11.png

如果是标准请求(STANDARD_REQUEST 0x00)且接收方是设备(DEVICE_RECIPIENT0x00)

188b578d00f4487098e4fc76812ad64f.png

接下来需要进一步判断USBwValue1是获取设备,配置还是字符串描述符,然后将相应的函数指针赋给CopyRoutine

a6e476a9912d4ced8ce059a4f5aab66c.png

获取设备描述符的函数实际是uint8_t *Joystick_GetDeviceDescriptor(uint16_t Length)

其中实际执行的是Standard_GetDescriptorData(Length, &Device_Descriptor);

2af1add452e24bb0b5336d6895302943.png

第一个形参是长度(length)

我们第一次调用Standard_GetDescriptorData(Length, &Device_Descriptor)函数时,

Length传参的是0,

d3120700588f4d638f6c49786792f431.png

第二次Length传参 传的是发送数据包的字节长度(在DataStagein函数中),

b461ba5e7e994108963ddec4e7344ff6.png

第二个形参是 传的是Device_Descriptor的首地址

5703d43368824520aa943e2ce396b401.png

Device_Descriptor 其实就是 ONE_DESCRIPTOR这个结构体 它包含了描述符数据数组的首地址与长度信息,

d0b7134cdddd428ea9affcd898a1a1bb.png​我们的Standard_GetDescriptorData函数就是根据这两个参数(Length,&Device_Descriptor),来确定“将要发送给主机的描述数组首地址与字节长度”

在Standard_GetDescriptorData函数中,首先就是把Usb_wOffset(数据偏移地址)取出来,这个在第一次执行CopyRoutine函数指针之前已经设置为0了,由于第一次传入的长度为0,那么就是将Usb_wLength设置为Descriptor_Size,也就是设备描述符的长度,

之后就是进入DataStageIn()函数中,因为这次长度不为0,那我们就不进如if语句中,将发送数据的偏移地址进行了调整,也就是描述符的数据首地址进行偏移。

e950c6d0ae3640e9909ac84eb323315c.png

Usb_wOffset 这个变量是调整下次需要发送数据的偏移地址,Length就是要传输的长度

8f815ce79cf44b14baab846add33a2fe.png

我们再回到Data_Setup()函数,根据总线枚举过程,接下来是获取配置描述符

相应的回调函数是这个 uint8_t *Joystick_GetConfigDescriptor(uint16_t Length)

他和配置描述符差不多就不多说了。

ae79e7c7457942429792d4dcf4a5392c.png

接下来就是获取字符串描述符请求,相应的回调函数为

uint8_t *Joystick_GetStringDescriptor(uint16_t Length)

我们从以下的程序中能发现定义的字符串描述符有4个,

37c206560cdc490eacf1326bfe891569.png

 

主机究竟想要获取哪一个呢,这就的由数据中的wValue字段来决定,wValue字段的高字节代表描述符的类型,而低字节就是表示具体的索引值(仅仅对配置与字符串描述符有效),

索引值被定义在String_Descriptor数组中,也就是说wValue0不会大于三,所以在这个原厂固件中的比较索引值不应该是4而不是3。

aded45c5e19e451fb10548d8308e4a18.png

2c034da419554e24919fe3d42a989218.png

 

以上就是主机获取到了所需要的所有描述符信息,接下来就要给设备分配一个地址,相应的请求代码是SET_ADDRESS,这个请求是没有数据时期的,所以我们来到NoData_Setup0()这个函数

第一步先判断设备地址是否有效(不能大于127),然后就是wIndex都为0,同时判断Current_Configuration不为0(因为配置完成后Current_Configuration是一个不为0的值)不然就会认为请求是错误的,就会将控制管道状态设置为STALLED直接返回。

7d627663251f4ce3af98614c449a3143.png

在NoData_Setup0()这个函数中,我们没有确切的分配地址,但是在确定接收的“设置地址”请求有效之后,设备会将控制管道状态设置为WAIT_STATUS_IN,而分配地址的具体操作就是在状态时期操作的,也就是在 In0_Process()函数中被执行了,其中将主机发送过来的设备地址(wValue0)传入SetDeviceAddress()函数中这个函数做了两件事情,一个是将主机发送的设备地址保存在USB_DADDR寄存器的低7位中,并且将EF位置1(如果此位为0,USB控制器将停止工作,不响应任何USB总线通信)。第二就是通过for语句把设备所有使用过的端点寄存器都写入端点号(USB_RPOR的最低4位写0,USB_RP1R的最低4位写1,依次内推)

9c4cf2bbbe0340afb6da217e5a97dbb4.png

在这个官方例程中我们使用了两个端点,Device_Table结构体变量中包含了有使用的端点数量和配置值,如果要增加更多的端点,就要修改EP_NUM值,官方固件库的SetDeviceAddress()函数有个限制:必须使用连续的端点号,

707f7a2861ae497390544938776c5bdc.png

但是官方固件库里给用户提供了一次自定义改变端点号的机会,就是SetDeviceAddress()函数使用以后还有个User_SetDeviceAddress()函数,它实际执行的是

void Joystick_SetDeviceAddress (void)函数,这里面只是将bDeviceState设置为已设置好已编址状态,我们可以在这里加其他操作。

ff834d8ab6b7492887f7ac645bdc2a28.png

51f426e8239d4a319428d6a06721704e.png

分配好地址后,主机就是会给设备选择一个配置,相应的请求代码是SET_CONFIGURATION

我们这个官方例程是游戏操作杆,只有一个配置,所以主机发送的配置值应该是1,将这个

应用程序将其保存在指针 pInformation指向的结构体 DEVICE_INFO中的 Current_Configuration

57a2d2b4e95e46a3a986006eb2560497.png

接下来执行回调函数User_SetConfiguration,实际上就是void Joystick_SetConfiguration(void)

将设备设置为已配置状态

8a97248d2c4f4ae9a800f1f2ba556b96.png

对于游戏操纵杆设备,选择完配置后我们还要获取报告描述符,在Data_Setup0()函数中

调用Class_Data_Setup这个函数指针,实际是Joystick_Data_Setup();

dc77c27a2e6d4b4ea3eb0eaaa056654f.png

88da8e9d333e4d0ab356ab757b788adc.png

Joystick_Data_Setup()该函数执行过程和处理标准请求类似,也是通过最终调用Standard_GetDescriptorData()这个函数来解决的

6fcadd7d69694a0b97c82657c7274e3a.png

还有几个标准请求

一个就是Data_Setup0()函数中“获取状态(GET_STATUS)”请求的。

获取状态可以针对设备,接口,端点。这主要取决于bmRequestType的值。如果请求对象为设备,那么wIndex就应该为0,如果请求的对象是接口或者端点,那么wIndex字段应该是相应的接口号或端点号,相应的格式,就如下所示

e5d8e11aafb14ea9ab621d03d958cbdd.png

“获取状态”请求最终返回的数据都是2字节,根据针对的请求对象,返回的状态数据也是不同的。

如下图所示:

379b4d50ea764573ac915a920d783582.png

从上面两个图,可以看到,主机发送“获取状态”请求中,wIndex字段的高字节与wValue字段总是为0,wLength总是为2(表示两个字节)

所以呀,我们在这个Data_Setup0(void)函数中,当判断当前代码为GET_STATUS时,首先就会据此进一步判断是否为“获取状态”请求。如果确实是获取状态请求,就紧接着判断是获取设备,接口还是端点状态。如果是获取设备状态请求,则wIndex总是为0.如果是获取接口状态请求,则通过调用Class_Get_Interface_Setting(Joystick_Get_Interface_Setting函数)确认接口号是否正确。

如果是获取端点状态请求,则取出wIndex低字节的端点号,,然后根据传输方向来判断USB控制器中是否已经对该端点进行了正常初始化,无论端点的传输方向如何,最终都会将Standard_GetStatus函数指针赋给CopyRoutine,第一次就是传入0,设置发送数据长度为2字节,

而在  DataStageIn();  还会执行第二次,然后根据需求返回StatusInfo这个全局变量,也就是需要返回的状态数据

66e9dd5676284e0182289a58b181cf9a.png

//获取状态标准请求
uint8_t *Standard_GetStatus(uint16_t Length)

首先我们判断是不是设备,如果是的话,我们就应该返回“支持唤醒与供电模式与否”的状态数据,所以我们首先取出Current_Feature,这个变量在Joystick_Reset函数中已经设置为了配置描述符数据Joystick_ConfigDescriptor索引值为7的数据,他保存的恰好就是“设备是否支持远程唤醒与自供电模式”那个字节(bmAttributes字段),所以接下来根据Current_Feature来逐步设置数据,最后返回

如果是获取状态请求针对接口,则返回的数据总是0,所以直接返回已经被清零的StatusInfo就好了

如果是获取状态请求针对端点号,则它返回端点的停止(Halt)状态,为此首先从USBwIndex0获取主机的请求是哪个端点状态,如果状态是STALL,则将StatusInfo0的最低位置置1,需要返回的数据就创建完成了。

ad325de86ad441d39beffe389a28c65b.png

8f2cd0f17781462b80521f413d01669b.png

f515d967cb374409a190f1281c4b5dce.png

除了设置配置与设备地址外,我们基本都是讲如何获取信息的请求,其实也还可以设置其他请求,只不过我们对我们设备而言,并不是必须的,除了下面我框框的那两个

2e99e23fdb6d474ba93dc0b028566f93.png

其他都被宏定义成了如下这个,要想使用注释掉宏定义,找到对应的回调函数修改就好了。

eae198ad0a0b4d2ab19858c9ed9025fb.png

 

 

前面还说到,当我们流管道返回STALL握手包的时候,说明现在端点有问题,应该使用“清除特征CLEAR_FEATURE”请求重启端点,它是在NoData_Setup0()函数中执行的,与处理设置特征的请求类似。

如果接收方是设备的话,就直接清除D5位(远程唤醒支持位)返回,如果接收方是端点,就需要判断wValue与wIndex字段的值是否符合规范定义要求,即wValue字段必须是ENDPOINT_STALL

对应如下的ENDPOINT_HALT,且wIndex的高字节必须为0,

接下来就是判断端点是否有效,如果设备没有被配置,或端点状态为禁止,或者端点号大于设备使用端点号 则请求不支持,

如果是输入端点则直接调用ClearDTOG_TX函数重置发送数据时序切换位,然后将该端点的状态设置为VALID,即清除了STALL状态。如果是输出端点,则同样判断是否处于STALL状态,

然后如果是控制端点0则设置接收数据字节长度为最大数据包长度,并设置接收状态有效,如果是非0端点,则执行ClearDTOG_RX函数,重置了接收数据时序切换位,并同样设置接收状态有效,最后执行一下函数指针User_ClearFeature(实际是Joystick_ClearFeature函数)才返回

23be647bb1844d0e88bed830312e622d.png

a50bdca654364f2992d70f55168bad80.png

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值