大熊FPGA 阅读(29) 评论(0)

  大家好,你们的大熊又回来了。本篇文章我们来重点了解一下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的关注!