您的位置:首页 > 其它

USB之基本协议和数据波形1

2019-05-30 20:27 666 查看

 

=============  本系列参考  =============

《圈圈教你玩USB》、《Linux那些事儿之我是USB》

协议文档:https://www.usb.org/document-library/usb-20-specification  usb_20_20190524/usb_20.pdf

====================================

前言:

  我们先不一上来讲USB大而全的协议规范文档, 会让人退而却步, 只要有协议, 在数据传输上波形就有规律可循, 翻译成数据, 也先不管USB1.1/2.0等版本, 因为最终的传输单元是一样的

 

一. 最基本传输单位 --包(packet)

1.电气信号:

  a. 采用D+/D-差分信号传输, LSB在前, NRZI编码也就是0反转, 1不反转, 遇到连续6个1强插一个0

 

  b. 低速Lowspeed 1.5Mb/s, 全速Fullspeed 12Mb/s, 高速Highspeed 480Mb/s, USB1.1支持L/F  USB2.0支持L/F/H USB3.0也支持L/F/H 同时支持OTG功能

  c. OTG(on the go) 就是多了根ID线, 用于判断主控器作Host还是device

  d. L/F S采用电压传输(3.3v), HS采用电流传输(等效电阻后示波器显示400mv)

  e. 传输方向以Host为准, 即IN表示device数据到Host, OUT表示Host数据到device

  f. 插入上电波形分析(下面单独抽出来分析)

2. packet格式

      SYNC同步域  +  PID域  +  数据域  +  CRC  +  EOP

  a. 同步域: L/FS 固定00000001, HS前面31个0后一个1

  b. PID占一个字节,高4bit是低4bit的反码, 用于校验PID本身, 而PID[3:0] 表示该packet的类型(协议文档8.3.1):

    

    打*表示USB1.1不支持的, 而USB2.0全支持, 这里要特别注意令牌包, 任何事务传输, 必须先发个令牌包说明意图, 至于后面是否需要数据包还是握手吧取决事务类型(下面会说)

  c. 数据域是可选的, 取决PID是不是数据包(DATA0/1/2/M) 

  d. CRC也是可选的, PID自校验,所以只对数据域校验,  若数据域没有那CRC也没有, 令牌包的数据域采用CRC5校验, 数据包的数据域采用CRC16校验

  e. EOP结束包, 对于L/FS是两个数据位宽的SE0信号(D+/D-都是0),  对于HS使用故意位填充表示(待具体解释)

  f. 空闲状态, 在SYNC同步域前和EOP后 总线上处于空闲状态, L/FS是一根高电平一根低电平(也就是J或K状态, 后面会讲), HS是SE0表示空闲状态

  g. SYNC同步域、EOP、CRC是硬件发射器自动添加和硬件接收器自动解析的, 软件看到的只有PID域和数据域

    

2. packet类型

  根据PID[3:0]可以将包的类型分成4类

  a. 令牌包: 一次USB传输必须首发令牌包, 告知意图, 同时后面数据表示跟哪个设备及端点通信, 这点很重要, 设想一下一个Host接了很多外设, Host发出的信号会到达所有hub和普通外设, 如何避免串扰呢?

        那就是总线某一时刻只有一个外设与Host通信, 外设硬件接口只响应令牌包, 因为令牌包的数据域表示设备的地址和端点地址, 外设可以解析是否和自己匹配, 如果是则响应(使能硬件接收数据), 以及后续的数据包交互, 如果不是

        就不响应, 当然后续的数据包也会被外设硬件屏蔽, 不理会总线信号, 除非一段时间后又检测到令牌包, 再次进行地址匹配, 符合才使能硬件接收总线上的信号

   IN OUT SETUP 包的数据域包含7bit设备地址和4bit端点地址, 所以一个Host能够最多接127个设备(0是外设刚插入时的默认地址, 握手后必须赋值非0, 不然下一个设备也是0就冲突了),  一个设备端点最多只能16个(端点0是必须的, 所以其他最多15个)

       

 

 

   SOF(帧起始包) 相当于心跳包, 让所有外设知道Host还在活动(哪怕Host不是跟该设备通信但起码知道跟其他设备通信), L/FS每隔1ms发一次, 每发一次11bit帧号加1, HS把1ms分割8份即每隔125us发一次, 但这8份里面的11bit帧号是相同的

  

    这个心跳包主要用于休眠唤醒用的, 当Host没有发SOF超过3ms时(一般是Host自己进入休眠或者想外设休眠), 外设设置自己进入低功耗状态(如果支持), 然后进入监听模式如果检测到总线有信号变化(只要跟睡眠前不一样)立即唤醒,

  可能是Host要召唤设备了, 当然设备也可以唤醒Host, Host进入休眠也会设置监听总线状态,外设被人为唤醒改变总线信号接着唤醒Host

 

  b. 数据包: 这没啥好说的就是PID表明自己是数据包(DATA0还是DATA1主要用于 确保对方收到), 后面就是字节数据了, 这里需要注意就是没有告知这个数据包到底多少个数据, 所以我猜想外设接收PID域后, 每接收一个字节counter计数器加1

        直到EOP, 然后减2 CRC16校验值就是数据量, 接着对FIFO数据CRC16和最后两个字节对比, 不一致就产生数据错误中断, 一致就产生数据成功中断并将数据量填充RX counter寄存器

     

  c. 握手包: 告知对方状态, 比如Host发送IN令牌包, 接着设备发送数据包, 然后Host接收完发送ACK握手包告知设备成功接收

     不用数据域!

  d. 特殊包主要用于高速, 比如上面Host发完IN令牌包后, 设备应该要发数据包的, 但设备还没准备好数据, 导致Host等待超时, Host可以再次发IN包让设备进入发送数据, Host切换等待接收数据状态, 

    这里有两个小问题, 一是设备数据未准备好, 却没有有效方式告知Host, 只能啥都不做靠超时告知, 浪费Host时间, 二是IN包让设备进入发送数据模式, 设备有数据早发了还等你吹, 还让外设进入发送模式影响准备数据

    而PING特殊包就是当第一次超时后, Host不发IN包改发PING包询问设备准备好没, 设备若准备好了回复ACK握手包, 接着Host再发IN包, 如果还没准备好就发NAK告知, Host就知道设备还没准备好而不用死等超时,

    其他几个读者可自行查阅

 

   总结: 总线是一个一个packet传输的, 且信号达到所有外设, 当发送SYNC域所有外设接收并调整时钟采样点做好同步, 接着解析PID域,  如果是非令牌包就不理会(只有已被选中的外设才理会), 如果是令牌包就解析后面地址是否和自己匹配,

      不匹配继续不会理, 匹配的使能硬件接收数据功能, 并根据PID是IN OUT SETUP SOF再细分, 如果是OUT,产生OUT中断, 软件应该清空使能FIFO准备接收数据, 如果是IN, 产生IN中断, 软件要填充好即将发的数据然后使能端点发送,

      如果是SETUP包(Host会接着发DATA0数据包数据域包含8个字节的标准请求), 设备要清空特殊FIFO并做好接受下一个数据, 接受完才产生SETUP中断, 软件就解析FIFO里的8byte标准请求, 然后准备数据, 比如是获取设备描述符请求

      那软件得准备好设备描述符缓存并ACK(必须ACK不能NAK)回复, 然后Host会发IN包, 接着设备IN中断将刚才准备好的设备描述符缓存丢到端点0发出去!

      如果是SOF包, 设备会重置时间计数器, 当3ms内没有新的SOF包, 就会产生中断, 设备知道总线现在是空闲状态, 可以自行决定是否休眠

 

 

 二、 事务--四种传输类型

  一个个packet只是一盘散沙, 通过组织起来作为一个有效传输我们称之为事务, 所以一个事务起码包含:

  一个令牌包, 通过地址选中具体外设

  可选的数据包, 如果是IN/OUT/SETUP包那后续有数据包, 如果是 SOF则数据包和握手吧都没有

  可选的握手包, 像视频聊天这种实时传输不需要ACK应该, 丢了就丢了, 省下带宽不如用来发数据

  因此, 根据具体的使用场景, 事务可以分成四种传输类型:

1. 批量传输(Bulk transfers )

  一个批量事务包含三个阶段, 令牌包阶段 + 数据包阶段 + 握手包阶段, 其中数据包阶段可以发一个或多个数据包

               

 

  以Beagle USB 480 逻辑分析仪抓U盘上电时序时为例, 期间Host(PC机)会读取U盘数据(bluk传输), 我们可以猜测应该发一个读取U盘根目录命令, 然后读取扇区信息, 如下:    

                   

  一个读取扇区信息命令分别为 Command + Data + Status, Command是一个写操作, 往设备发送数据告知想干嘛, 然后就是读数据, 最后检查状态, 可以看到这些操作都由三个packet构成 IN/OUT令牌包 + 数据包 + 握手包

  因为U盘每次操作只能512byte/block, 所以想读取多个扇区只能分多次IN操作(传输最大字节数端点描述符有说明)

 

2. 中断传输(Interrupt transfers )

   一个中断事务跟批量事务类似, 不同在于传输量比较少, 且希望Host每隔一段时间来访问设备(不是靠硬件中断告知系统, 而是端点描述符有个时间间隔变量, 告知Host最好小于这个时间间隔来访问设备), 像鼠标键盘都是这类传输模式, 

   以Beagle USB 480 逻辑分析仪抓键盘为例:

  

  这里可以看出三点, 一是Host每间隔x时间就发起一次读取键盘数据操作(还是老样子 IN包 + DATA0包 + ACK包); 二是如果我没敲键盘, 则设备NAK告知Host没有数据; 三是间隔时间约 72/10  344/44 = 8ms

  查看键盘端点描述符bInterval=1, 根据datasheet代表1ms, 即键盘希望Host每隔1ms读取一次数据, 但采不采纳在于Host端

           

 

 3. 等时传输(Isochronous transfers )

  等时事务跟前两种也差不多, 不同在于对时间敏感, 对数据准确性不关心, 所以不需要握手包, 主要用于音频、视频类设备

 

4. 控制传输(Control transfers )

   控制传输稍微复杂一点, 上面三个一个传输就是一个事务, 但控制传输有三个状态, 每个状态对应一个事务, 所以需要三次事务

三次过程分别为:

  建立过程:SETUP令牌包 + DATA0数据包(标准请求就在这) + ACK握手包(设备必须返回ACK, 不能NAK 如果设备连这个都不能保证的话就别玩了)

  数据过程: 可选, 如上面是获取设备描述符这里就是 IN令牌包 + DATA1数据包 + ACK握手包; 如果是设置地址请求, 地址在请求内部了, 不需要数据过程 

  状态过程: 上面的数据过程必须是同一个方向的, 如果方向改变, 则就是状态过程, 如果没有数据过程, 则这个数据包就是状态过程不管哪个方向

      

  以Beagle USB 480 逻辑分析仪抓U盘为例:

      

  从捕捉的数据可看到, 建立过程的数据包包含着标准请求 80 06 00 01 00 00 12 00 (小端排序) , 前面的C3是PID, 后面E0 F4 是CRC16, 可以通过http://www.ip33.com/crc.html 验证

80 06 0100 0000 0012
struct usb_ctrlrequest {
__u8 bRequestType; //0x80
__u8 bRequest;    //0x06
__le16 wValue;     //0x100
__le16 wIndex;    //0
__le16 wLength;  //0x12
} __attribute__ ((packed));
具体请参考协议文档9-4

 

  上面log还有个有趣的现象: 状态过程发送1字节0x00数据包,  U盘竟然返回NAK, 不知为何,  由于是高速模式下, 所以Host接下来会发PING包探测U盘是否ready, 直到U盘回复ACK才再次发送OUT包,如果是L/FS则继续发OUT包直到接收ACK

 

剩余数据的解析将在下一篇博文讲解!

 

 

 

 

 

 

 

 

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: