大家好,你们的大熊又回来了。本篇文章我们来重点了解一下USB设备的四大传输方式之一——控制传输。不同于其他三种传输方式,控制传输有其独特的作用和功能,是一个USB设备必须支持的传输方式。控制传输对带宽没有什么要求,但是数据传输的准确性是最有保障的,因此特别适合配置、命令、状态之类的通信,不适合传输大批量数据。之所以控制传输是USB设备不可或缺的,是因为枚举过程就是通过控制传输实现的,而一个USB设备只有经历过枚举过程才有可能被主机识别为“USB设备”。控制传输能传输的数据量太少了,一次控制传输携带的单个数据包能加载的最大数据数目,对于全速设备可以是8、16、32或64字节,对于高速设备是64字节,对于低速设备是8字节。此外,控制传输还有一项其他传输不具有的特点——支持真正的双向传输。尽管其他的传输方式也可以是双向,但是必须事先给端点定义好方向,而在实际工作中只能有一个传输方向,除非重新进行配置。
USB协议规定控制传输分为三个阶段(Stage),分别是Setup Stage、Data Stage、Status Stage。接下来我就以Cy7c68013A为例介绍这三阶段,图1比较具体地描绘了整个控制传输的过程。
✦1 控制传输三阶段
Setup Stage只有Setup事务,Setup事务由三个数据包构成。SETUP包是控制传输特有的,它指示了一次控制传输的开始。DATA0数据包含有8字节的数据,这8字节的数据对应8字节的请求(如图2),在这8字节中指明了该请求的请求类型、方向、接收者、对应的请求码等等,USB设备在进行请求的响应时需要根据这8字节信息来确定如何做出相应的应答行为。ACK包用来握手,并且告诉主机本阶段正确完成了,如果USB设备发现数据有损坏,则丢弃当前数据并且不进行握手。对于Cy7c68013A来说,在Setup Stage阶段会产生两个中断,当接收到SETUP包时触发SUTOK中断,与此同时EP0CS的HSNAK位会置1,当接收完DATA0数据包时会产生SUDAV中断,固件程序可以在中断服务程序中完成相应的任务。需要注意的是,在上一次的控制传输未完成前,不能让主机再发送Setup包,否则会开启新的传输,而之前的传输就会被打断。除非之前的控制传输遇到了错误,这时才能发送新的Setup包。
✦2 8字节请求的结构
Data Stage是可选的,当Setup Stage的8字节仍不够我们传输数据时,可以使用数据阶段来传输其余的数据。Data Stage的数据包的方向必须是一致的,该阶段需要传输的数据的个数和方向由Setup Stage决定。如果数据的个数超出了最大包长,则必须分成多个IN或OUT事务来完成。Data Stage的结束发生在两种情况下,其一是接收完了Setup Stage指定数目的数据,其二是当前数据包未达到最大包长。在Data Stage过程中如果传输的数据发现有错误,可以进行重传,如果传输的数据数目超出了Setup Stage指定的数目,那么该端点就会中止。
Status Stage是控制传输的最后一个阶段,在Data Stage或者Setup Stage完成后就会进入该阶段,该阶段要完成的是本次传输的状态反馈,因此Status Stage的传输方向与Data Stage(也有可能是Setup Stage)相反。当控制传输一切都传输正常的时候,Status Stage产生ACK应答,如果遇到了错误,则产生STALL应答,如果数据接收方正忙,则产生NAK应答。Status Stage遇到错误也可以重传。对于Cy7c68013a,在没有完成数据的处理之前会一直NAK应答,直到固件程序将HSNAK位写1清0,Status State才能正确应答。
以下我们结合代码来理解,从代码A中我们可以看到,这里使用了厂商请求的方式来完成控制传输。之所以使用厂商请求是因为其他的请求都被USB协议规定的标准请求“霸占”了,剩下的厂商请求(也可以是类请求)我们才能使用(具体的哪些是标准请求哪些是厂商请求可以参考EZ-USB技术手册)。
1 BOOL DR_VendorCmnd(void) 2 { 3 switch (SETUPDAT[1]) 4 { 5 case 0xa1: //读请求 6 if (SETUPDAT[7] != 0 || SETUPDAT[6] != 0) 7 { 8 EP0BCH = SETUPDAT[7]; 9 SYNCDELAY; 10 EP0BCL = SETUPDAT[6]; 11 SYNCDELAY; 12 while(EP0CS & bmEPBUSY); 13 //从EP0BUF中读取数据 14 } 15 16 break; 17 case 0xa2: //写请求 18 if (SETUPDAT[7] != 0 || SETUPDAT[6] != 0) 19 { 20 //往EP0BUF中写入数据 21 EP0BCH = 0; 22 SYNCDELAY; 23 EP0BCL = cnt; 24 SYNCDELAY; 25 while(EP0CS & bmEPBUSY); 26 } 27 break; 28 default: 29 return(TRUE); 30 } 31 return(FALSE); 32 }
✦A 控制传输代码
从代码A中可以看到,在Setup Stage完成之后,作为“攻城狮”的我们还有一项任务要做,判断8字节数据(存放在SETUPDAT中)中的wLengthL与wLengthH是否为0,如果是0,则意味着后面的数据阶段是没有的(接下来直接进入Status Stage),此时什么都不用做,如果不是0,那就说明还有数据需要传输,此时我们的任务就是往EP0BCH:L寄存器内加载数据,这样才能开启数据阶段。对于IN传输,我们需要先往EP0BUF中写入数据,再往EP0BCH:L加载数据(指示需要传输数据的个数),等待数据阶段完成(判断EP0CS寄存器的BUSY位)。对于OUT传输,我们需要先往EP0BCH:L加载数据,接下来就是等待数据阶段完成(判断EP0CS寄存器的BUSY位),最后从EP0BUF读取数据。光看文字描述可能会有点晕乎,现在我用一张图来总结CPU、BUSY位、EP0BUF、EP0BCH:L寄存器之间的关系。
✦3 控制传输转换流程
感谢大家对大熊FPGA的关注!