USB设备响应主机的枚举中端点0的控制传输

前言 

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

     我们主机对设备完成复位以后,就要开始获取设备的各种描述符了,获取设备的描述符是由SETUP,OUT,IN事务构成的控制传输完成的,一开始收到SETUP事务(通知设备将要开始控制传输了),使得USB_EP0R寄存器的CTR_RX位置1,因为USB_CNTR寄存器的CTRM位使能了,将进入中断USB_Istr()函数中,每次传输都会使USB_ISTR中断状态寄存器中的CTR位置为1,所以接下来进入CTR_LP()函数中,

void CTR_LP(void)

85268d5c397045af8861f0a78ec92a8a.png

进入CTR_LP函数里,使用wIstr全局变量 用来保存USB_ISTR寄存器的值,然后确认CTR位是否置为1,如果确认为1就表示事务传输完成,退出这个循环,从wIstr中获取事务针对的端点号,我们这个时候是总线枚举时刻,所以我们获得的端点肯定是0,直接进入if语句中,然后又是老套路,先获取端点0寄存器(USB_EP0R)的值,将其中的发送和接收状态保存起来,因为我们接下来要设置端口的接收和发送均为NAK模式表示设备现在在忙,不之前保存下来后面恢复不了,在就接下来就是通过wIstr来判断USB_ISTR中的DIR是多少,如果是0那么是IN事务进入 In0_Process();如果是1那么有可能是OUT也有可能是SETUP事务,这时候就要进一步判断SETUP位,如果是1,就表示是SETUP事务,进入 Setup0_Process(),如果是0就表示OUT事务,进入 Out0_Process()函数

 

我们大概捋一下,控制传输的事务序列,主机获取设备描述符的第一步是在 建立时期 使用SETUP事务发送标准请求,然后在数据时期通过IN事务返回设备描述符数据,最后在状态时期通过OUT事务进行状态返回(状态报告方向总是设备到主机),Setup0_Process()这个函数就是进行建立时期的处理操作,首先就是把SETUP事务中标准请求数据解析出来,然后根据具体的请求进行处理,SETUP事务都是被放在端点0对应的接收包缓冲区中,所以我们需要先复制出来

复制出来以后就是将设备状态设置为SETTING_UP,表示我现在正式进入了控制传输的建立时期,接下来就是判断该SETUP事务是否有数据时期(wLength是否为零),

由于我们获取的设备描述符请求是有数据时期的,所以我们进入Data_Setup0()函数,

cd8e2396453d4c6e8a1678ab2c0b306a.png

这个函数里,就是定义个变量Request_No保存 DEVICE_INFO 设备信息里的请求代码bReqeust

然后根据下面这个表

a0ac37c7803a4105a6e22796519d93ce.png

一个一个判断是什么请求,然后做不同的操作

有个注意的地方就是我们进入Data_Setup0()这个函数中第一步就是声明了一个函数指针 

uint8_t *(*CopyRoutine)(uint16_t);

我们到时候每个不同请求中的函数指针都会赋值给他执行

回调函数的执行结果会修改pProerpy指向结构体中的Ctrl_Info结构体变量,它包含了将要发送(或接收)数据源的长度(Usb_wLengh),偏移地址(Usb_wOffset),最大数据包传输字节长度(PacketSize)及函数指针CopyData(也指向刚刚执行的回调函数,以供下次正式发送数据时调用),后续会根据设置好的参数进行数据发送(或接收)。简单地说,第一次以形参“0”调用CopyRoutine的目的就是把需要发送(或接收)数据的首地址与字节长度进行定位。

如果对请求数据进行一系列的判断还无法确定,还有可能为特定类请求,那么就会调用这个函数指针Class_Data_Setup  实际上就是调用了Joystick_Data_Setup()函数,

如果确实是不支持主机发送的请求,或者需要返回的数据为0(绝对不可能为0的,因为所有的请求都是需要获取设备的信息,都是要有数据返回的),那就把状态设置为STALL表示出现了错误,然后直接返回

如果执行到了最后一个if语句说明请求数据解析没有出错,根据下表判断一下bRequestType的最高位是为多少,如果为1,说明数据的传输方向是从设备到主机,然后进一步判断将要发送的数据字节长度是否大于主机请求的数据字节长度,要限制在主机请求的数据字节长度之内,相等或者小于主机要请求的数据字节长度, 

如果bRequestType的最高位为0那么表示数据的传输方向是从主机到设备,就会把控制管道状态设置为OUT_)DATA,之后进入控制写序列的数据时期,也就是再次从USB_Istr函数重新执行流程.

其实这里要讲一下描述符了,后面马上来讲一下

42acaa0b8ead473d8c7c363b19439dc7.png

void DataStageIn(void) //控制读序列的数据时期处理函数
 

前面已经将设备需要获取的设备描述符数据通过函数指针uint8_t *(*CopyRoutine)(uint16_t);定位好了,接下来我们就是进入DataStageIn()函数中,将定位到的数据复制到发送包缓冲区中,

3117667ba5394fa5a2e57a2523065ce9.png

  首先就是判断控制管道状态是否是最后一个数据包状态(LAST_IN_DATA)和设备需要发送的数据包字节长度是否为0,如果都是都是肯定的,那就再判断Data_Mul_MaxPacketSize是否都是为TRUE还是FALSE,如果为TURE,但是我们的数据已经发送完毕了,这时候就再发送一个0数据(设备可能在数据十七多次响应IN事务 而进入这个DataStageIn()函数中),并把Data_Mul_MaxPacketSize设置为FALSE下次就可以退出了,如果为FALSE那么就表示现在数据全部发送完毕,设置当前状态为WAIT_STATUS_OUT,表示没有数据发送,进入状态时期,.

  如果还有数据需要发送,则首先就会判断要发送的数据包长度是否小于或者等于控制管道支持的最大数据包长度,如果是,就把状态设置为最后一个数据包状态(LAST_IN_DATA),如果不是就设置为IN_DATA,表示下次还要发送,后面就把传输长度设置为实际传输长度

执行请求回调函数 ,传参就是实际传输长度,用DataBuffer指针保存要传输的数据的地址,然后使用  UserToPMABufferCopy(DataBuffer, GetEPTxAddr(ENDP0), Length);这个函数将数据(此处是描述符数据数据)复制到端点0对应的包缓冲去中

每次完成数据的复制以后,就会设置发送端点为有效状态,然后会将设备需要发送的数据长度减去已经发送的数据长度,源数据的偏移地址则增加已经发送数据包字节长度

UserToPMABufferCopy(void)

这里说一下,就是包缓冲区地址wPMABufAddr乘以2,是因为这部分存储区是按照2字节访问的,即每存放1个字节的数据要占据2个字节的空间(只利用其中一个字节,另一个空闲在那)。

7ae0d6873c684bb1852d8f84822ae71a.png

8c460ba604324f5dba3951bae7f1a5f1.png

上面还只是完成了控制传输的建立时期,也就是说主机需要设备发送数据,则在控制传输时期解析了SETUP事务中的请求数据并执行(也就是将需要的数据复制到包缓冲区中),但数据并没有到达主机

只有主机接下来在数据时期发送IN事务才会进入到CTR_LP函数进入In0_Process函数

先是判断一下当前状态是否在IN_DATA或者LAST_IN_DATA,如果在说明还在控制传输的数据时期,那就继续进入DataStageIn函数中,将剩下的数据包复制到发送缓冲区中,如果当前状态是WAIT_STATUS_IN,就有两种可能,一个是在控制写序列中使用了IN事务向主机报告状态,最后总会将控制管道状态设置为STALLED。如果是其他状态说明出错了,也会设置为STALLED。

这里设置为STALLED并不是代表端点这时候出问题,设备收到不支持或者无效请求,而是因为对STALL握手包,有两种情况前面的是属于功能类,如果是功能类,那此时主机收到STALL握手包就会通过控制端点0来实现端点重启,但是在控制传输的数据时期和状态时期返回的STALL握手包是协议性的,并不是代表请求发生了问题,设备会对每个IN事务或OUT事务都返回STALL握手包,直到一个新的SETUP事务收到为止,也就是说主机收到STALL握手包应该重新开启一次控制传输,

from : STM32 USB Device应用中的包缓冲话题 - STM32/8

 In0_Process();

7fd7bb44a3b9416dbc62e9e17a3af5dc.png

Out0_Process()

 

现在数据时期完成了,所以主机应该要发送OUT事务来要求设备返回状态,处理方法和In0_Process()函数差不多只不过就是一个把缓冲区的数据复制出来,如果是WAIT_STATUS_OUT,就说明出错了,要通过IN事务向主机返回状态。

931a0f92d5a4433aab703d62c9121fbe.png

 Setup0_Process()

7875621bc937483699a864f51e0ddcad.png以上就是我们获取设备描述符的具体执行过程,同样的其他描述符也适用,后面进入总线枚举的设置设备地址和选择配置阶段他们的wLength都是0,这就意味没有数据阶段进入了void NoData_Setup0(void)  //无数据时期控制序列的建立时期处理函数

和有数据的阶段差不多,一个套路,就不多说了,处理完成后管道状态设置为WAIT_STATUS_IN

 

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值