USB基础概念
[!NOTE]
在学习USB开发的时候,因为涉及到很多未接触过的概念以及本人存在的一些思维定势,导致在学习过程中遇到了很多问题。为了防止以后遗忘,也为后人学习提供一些参考思路,现将USB的一些基础概念整理成文。本文的核心目标是能够帮助初学者辅助理解USB的编程。
[!CAUTION]
因个人实际学习需要,本文内容主要关注USB 2.0,且对于OTG内容探讨较少,另外虽然会涉及物理层面的内容,但是在本文中不会过于详细探讨。此外因本人并非专业USB开发人员,个人水平有限,内容可能存在遗漏或错误,欢迎指正。
USB组织架构
系统物理架构
一个拥有USB功能的芯片中通常至少有一个USB控制器(USB Controller)。若该USB控制器可以运行在主机模式(HOST MODE),则其内部拥有一个根集线器(ROOT HUB),通过根集线器可以延伸出一个或多个下行端口(Downstream Port)。若该USB控制器只能运行在设备模式(DEVICE MODE),则其不存在根集线器,并仅提供一个上行端口(Upstream Port)。
每个端口(Port)至少有两根数据传输线D+/D-(以USB 2.0为例)。与I2C这类单端信号总线不同,**D+/D-**为差分数据线,它们传输大小相等、相位相反的信号,并且每个端口采用一对一连接,进行半双工通信。
部分芯片的USB控制器支持OTG(On-The-Go)功能,则该设备同时支持主机模式和设备模式,并可以在使用过程中进行切换。OTG接口通常还需要多一根ID引脚(ID Pin)来决定默认角色:当ID引脚接地时,设备默认作为主机(A-device);当ID引脚悬空时,设备默认作为从设备(B-device)。对于一个实际的USB物理端口,还应有VCC和GND两根供电线。
为了实现设备检测和传输速度识别,通过对数据传输线D+/D-的上下拉电阻来实现。USB主机的D+/D-两根线均使用15KΩ电阻下拉至GND。对于全速(Full Speed)或高速(High Speed)的USB设备,则使用1.5KΩ电阻将D+上拉至3.3V。对于低速(Low Speed)的USB设备,则使用1.5KΩ电阻将**D-上拉至3.3V。主机通过检测D+/D-**引脚的电平来判断是否有设备接入以及接入设备的初始工作速度。高速设备在连接初期会表现为全速设备,在与主机完成握手协商后,再切换到高速模式。
系统逻辑组织
USB(通用串行总线)的逻辑结构为一种分层星型结构,也称树形结构。
一个USB通信系统中有且仅有一个USB主机(USB HOST),系统中所有的通信均由主机发起和控制,它是这个树形结构的根节点。主机通过其内部的根集线器,在物理上会提供一个或多个下行端口(Downstream Port)。下行端口可用于连接USB 设备或USB 集线器。
一个USB通信系统中可以有多个USB设备(USB DEVICE)。设备不能主动与主机或其他设备通信,必须等待主机轮询,它们是这个树形结构的叶节点。
一个USB通信系统中可以有多个USB集线器(USB HUB)。一个USB集线器通常有一个上行端口(Upstream Port)和多个下行端口(Downstream Port)。其上行端口连接USB主机或上层集线器的下行端口,其下行端口可以连接USB设备或下层集线器。在通信过程中,集线器扮演着双重角色:对于上层而言,它是一个USB设备;对于下层而言,它扮演着简化版USB主机的角色(负责端口管理和信号中继)。尽管USB集线器可以层层级联,但USB协议规定这种拓扑的最大深度不能超过7层。USB集线器是树形结构的分支节点。

不过对于USB主机而言,在逻辑拓扑上所有的USB设备都是和USB主机直接相连的,通过访问不同的地址即可实现访问任意设备。
对于一个USB通信系统,包括USB集线器在内的所有设备不能超过127个,因为USB的寻址系统使用7位地址,最多可提供127个设备地址(地址0保留用于初始配置)。
单设备逻辑组织
对于某一个具体的USB设备而言,其在逻辑组织上存在一个四层组织,从上到下分别是:设备、配置、接口、端点,每个层次都有对应的描述符可用于发送给USB主机,在设备插入后进行发送用于设备的枚举。
[!CAUTION]
在下方的介绍中我会提供描述符的结构示例,但是并不会详细介绍每项具体是做什么的,事实上如果只是为了目前的初步学习,你也没必要认真看每一项,通常情况如果需要配置描述符的时候再去查就可以了。
设备(设备描述符):用于描述这是一个怎样的设备。里面包含了厂商ID(VID)、产品ID(PID),用于唯一标识设备型号,操作系统可以用它来寻找合适的驱动程序。除此之外,还可以在里面设置该设备的设备类 (Class)、子类 (SubClass)、协议 (Protocol),用于粗略指出设备的类型(如果是复合设备,即拥有多种功能的设备,则不建议在设备描述符里面明确这一部分)。以下是设备描述符的结构:
struct _DEVICE_DESCRIPTOR_STRUCT { BYTE bLength; //设备描述符的字节数大小,为0x12 BYTE bDescriptorType; //描述符类型编号,为0x01 WORD bcdUSB; //USB版本号,表示了本设备能适用于那种协议,例如 0x0200 代表 USB 2.0 BYTE bDeviceClass; //USB分配的设备类代码,0x01~0xfe为标准设备类,0xff为厂商自定义类型 BYTE bDeviceSubClass; //usb分配的子类代码,同上,值由USB规定和分配的 BYTE bDeviceProtocol; //USB分配的设备协议代码,同上 //上面三个字段的配置,如果设备类定义在接口描述符中,则设备描述符中的这些字段应设置为 0x00 BYTE bMaxPacketSize0; //端点0的最大包长 WORD idVendor; //厂商编号 WORD idProduct; //产品编号 WORD bcdDevice; //设备出厂编号 BYTE iManufacturer; //描述厂商字符串的索引 BYTE iProduct; //描述产品字符串的索引 BYTE iSerialNumber; //描述设备序列号字符串的索引 BYTE bNumConfigurations;//可能的配置数量 }配置(配置描述符):代表设备的一种工作模式,一个设备可以有一个或多个配置,但是同一时间只能有一个配置是激活的。配置描述符会指定设备的供电方式、最大功耗、接口数量。切换配置会改变整个设备的功耗、接口等特性。通常情况,一个设备有一个配置就够了,不过在部分需要实现设备高低功耗切换或使用不同传输模式的情况下,就可以设置多个配置。以下是配置描述符的结构:
struct _CONFIGURATION_DESCRIPTOR_STRUCT { BYTE bLength; //配置描述符的字节数大小,固定为9字节 BYTE bDescriptorType; //描述符类型编号,为0x02 WORD wTotalLength; //此配置下所有描述符(配置、接口、端点等)的总长度 BYTE bNumInterface; //此配置所支持的接口数量 BYTE bConfigurationVale; //Set_Configuration命令需要的参数值 BYTE iConfiguration; //描述该配置的字符串的索引值 BYTE bmAttribute; //供电模式的选择,bit 6 表示自供电(1)还是总线供电(0),bit 5 表示是否支持远程唤醒。这两个是最关键的位。 BYTE MaxPower; //设备从总线提取的最大电流,单位是 2mA。例如,100 代表最大电流为 200mA。 }CONFIGURATION_DESCRIPTOR_STRUCT接口(接口描述符):用于将设备在逻辑上划分为多个独立的功能部分,每个接口通常对应主机上的一个驱动程序。通常如果需要设置复合设备,就可以通过设置多个不同接口来实现。例如一个设备同时拥有键盘功能和SD卡读卡器功能,那么它就可以定义两个接口,并修改两个接口的接口描述符,并将这两个接口分别设置为HID和MSC。以下是接口描述符的结构:
struct _INTERFACE_DESCRIPTOR_STRUCT { BYTE bLength; //设备描述符的字节数大小,为0x09 BYTE bDescriptorType; //描述符类型编号,为0x04 BYTE bInterfaceNumber; //接口的编号 BYTE bAlternateSetting; //备用的接口描述符编号,一个接口可以有多个“备用设置”(例如,一个音频接口可以有不同带宽的设置),主机可以通过 SetInterface 请求在它们之间动态切换。 BYTE bNumEndpoints; //该接口使用端点数,不包括端点0 BYTE bInterfaceClass; //接口类型 BYTE bInterfaceSubClass;//接口子类型 BYTE bInterfaceProtocol;//接口所遵循的协议 BYTE iInterface; //描述该接口的字符串索引值 }INTERFACE_DESCRIPTOR_STRUCT不过随着复合设备越来越复杂,有时候需要将多个接口组合起来共同实现一个单一功能。比如一个视频会议摄像头,它拥有一个视频接口、一个音频接口、一个控制接口,但是它们共同实现的是“视频会议”这个单一的功能,需要加载一个共同的驱动,为此USB协议还引入了接口关联描述符(IAD)。可以在每个类要合并的接口描述符之前加一个接口关联描述符,将多个接口定义为一个类设备,从而让主机为这几个接口加载一个共同的驱动。以下是接口关联描述符的结构:
typedef struct _USB_INTERFACE_ASSOCIATION_DESCRIPTOR { UCHAR bLength; //长度为8 UCHAR bDescriptorType; //USB_INTERFACE_ASSOCIATION_DESCRIPTOR_TYPE,值为0x0b UCHAR bFirstInterface; //第一个接口编号 UCHAR bInterfaceCount; //接口总数量 UCHAR bFunctionClass; //视频接口类代码CC_VIDEO,值0x0E UCHAR bFunctionSubClass; //视频子类接口代码 SC_VIDEO_INTERFACE_COLLECTION,值为0x03 UCHAR bFunctionProtocol; //未用,必须为PC_PROTOCOL_UNDEFINED,值为0x00 UCHAR iFunction; //字符串描述符索引 } USB_INTERFACE_ASSOCIATION_DESCRIPTOR, *PUSB_INTERFACE_ASSOCIATION_DESCRIPTOR;端点(端点描述符):负责数据传输的硬件单元,它是主机与设备通信的最终目的地或源。端点可以被配置为USB规范的四种传输类型(控制传输、中断传输、同步传输、批量传输),每个端点只能有一个传输方向,一个设备最多可以有32个端点(16个IN端点和16个OUT端点),它们各自以0至15进行编号,其中端点0是一个特殊的双向端点,它可以是IN也可以是OUT,但共享一个端点号0。端点0通常仅用于枚举过程,且传输类型只能是控制传输。端点类似于一个缓冲区,因为程序并不能直接读写USB总线,且USB读写流程总是受主机控制,故主机发送给设备的数据,实际上是直接发送给一个OUT端点的,同理,当USB主机还未轮询到设备前,USB设备如果须发送数据,就应该把数据暂存在一个IN端点里面,当主机轮询时由设备的USB控制器将其发送至主机。需要注意的是IN和OUT是针对主机视角的。以下是端点描述符的结构:
struct _ENDPOIN_DESCRIPTOR_STRUCT { BYTE bLength; //设备描述符的字节数大小,为0x07 BYTE bDescriptorType; //描述符类型编号,为0x05 BYTE bEndpointAddress; //端点地址及输入输出属性,bit 7 表示方向(1=IN, 0=OUT),bit 3~0 表示端点号。 BYTE bmAttribute; //端点的传输类型属性, bits 1..0 表示传输类型(00=控制,01=同步,10=批量,11=中断)。对于同步端点,其他位有额外含义。 WORD wMaxPacketSize; //端点收、发的最大包的大小 BYTE bInterval; //主机查询端点的时间间隔 } ENDPOIN_DESCRIPTOR_STRUCT ;
通常情况,一个设备中可以有一个或多个配置(但每次只有一个配置是活动的),一个配置中可以有多个接口(当前配置下所有接口以及接口下的端点都是活动的),一个接口下可以有一个或多个端点。不过需要注意的是,在USB连接之后,只有端点0是可以通信的,只有当使用端点0枚举完成后,其它端点才可以开始通信。
USB数据传输
设计概念
USB在传输设计上和USART、SPI这类简单串行协议不同。为了保证传输速度、稳定性和准确性,USB采用了类似网络协议的分层设计,将所需传输的数据进行封装而非简单透传。为了更好理解这种设计,我们引用TCP/IP协议作为参考。
注意:USB协议规范本身并没有明确划分这些层级,以下的类比是为了理解方便而采用的逻辑模型。
应用层 (Application Layer)
TCP/IP:如HTTP、FTP、SMTP等,负责处理特定应用程序的数据语义。
USB:如HID(人机接口设备)、MSC(大容量存储设备)、CDC(通信设备类)等设备类协议。它们定义了设备的功能以及数据格式的含义。
传输层 (Transport Layer)
TCP/IP:由TCP和UDP组成。TCP提供可靠、面向连接的传输服务;UDP提供无连接的、不可靠的传输服务。
USB:对应USB架构中的传输(Transfer)。它由控制传输、中断传输、同步传输和批量传输四种传输类型组成。它们负责在主机和设备的某个端点之间提供可靠、有时序保证的端到端数据传递。
事务层 (Transaction Layer)
TCP/IP:对应网络层(如IP),负责寻址和路由数据包到目标主机。
USB:由事务(Transaction) 组成,例如IN、OUT、SETUP等。一次传输(Transfer) 通常需要分解为多个事务。事务是主机与设备特定端点之间单次数据交换(例如:主机询问、设备发送数据、设备回复状态)的基本可靠单元。全局的“寻址”通过包含在每个事务中的设备地址和端点号来实现。
数据链路层 (Data Link Layer)
TCP/IP:如以太网协议,将数据包封装成帧,处理帧同步、错误校验等。
USB:由包(Packet) 组成,例如令牌包、数据包、握手包。协议将事务信息封装成包,并添加CRC校验等,为在物理线上传输做好准备。
简单来讲,USB协议栈在处理数据时可以看作“应用 — 传输 — 事务 — 包”这样的逻辑结构。每次应用需要交换数据时,协议栈首先会根据类型(如批量)建立一次传输,然后将这次传输根据数据长度拆分成多个事务,而每个事务又由三个(有时是两个)包(令牌包、数据包、握手包)组成,以确保通信的时序和可靠性。
这种分层是有意义的,现在让我们从设计USB协议的视角去思考为什么要这样设计?
在明确我们只有D+/D-这样两根差分总线进行数据传输的物理设计,假设我们直接将所有数据进行透传,那么我们的一个USB就只能连接一个设备。但这与我们实际需求不同,我们需要更多的USB接口,但不可能为每个接口都添加一个USB控制器(这样成本太高了),为此我们需要对数据进行封,从而区分当前总线上传输的数据是属于哪一个设备的。一个简单的方法就是在数据的开头添加一个指向某一具体设备的地址信息,同时在结尾添加一个结束信号用于表示解除占用总线。
但是如果只是这样简单地在首位添加数据,就存在这样一个问题,假设一个设备是实时传输设备,比如摄像头,我们无法得知它会在什么时候停止使用,此时为了保证实时性,就无法添加结束信号,这个设备就会长时间占用整个总线,而其它设备就无法使用了。很显然,在串行总线上解决多设备并行运行问题最简单的思想就是时分复用,为此我们可以将传输数据进行拆分,并在拆分的每个部分的前后添加上我们的地址信息和结束信号,此外还可以添加一些校验信息、时钟信息等,来保证每次传输的稳定性,而这样一个新的微型数据就是包。
尽管我们有了包之后,已经拥有时分复用传输数据的基础了,但是整个总线仍然需要一个管理机制来确认每次总线该有哪个设备占用。因为USB上总是有一个主机和多个设备,并且这些设备都是服务于主机的,那么自然由主机来确定由谁来占用总线是最合理的。很显然最简单的方法就是让主机来决定,并且通过总线将这个决定告诉所有设备,即在最开始发送一个“现在我需要发送/接受设备X的数据”(也就是令牌包),通过这样的方式来提醒需要发送或接受数据的设备做好准备,而其他的设备应当停止发送数据。
尽管我们在物理设计的时候已经尽量保证总线的稳定性了,但是现实中难免会出现一些意外产生丢包的情况,为了防止这样的情况出现,我们需要添加一个保证包传输可靠性的功能。一个比较好的方法就是接收方在收到“所需要的数据所在的包”(也就是数据包)后,向发送方发送一个表示“我已经收到了的包”(也就是握手包),否则就不发。这样发送方就可以知晓数据是否成功传输到目标,从而决定是否需要重新再发送一次。
现在通过令牌包——数据包——握手包这样的过程,就可以保证一次通信的完整可靠性,这也就是事务的作用。
不过对于嵌入式、上位机、驱动开发者而言,这些底层的内容其实并不是那么重要,因为整个过程其实都是在USB协议栈和硬件中完成了设计和定义,尽管在部分情况下为了性能优化可能需要进行一些额外的配置,但是这个配置的过程对于需要开发的程序逻辑而言是不重要的,应用程序关心的是完整的数据,而不关心这个程序是通过多少次事务完成的。应用需要直接关注的是通过什么样的方式来管理这次通信,是需要保证即时通信(例如摄像头),还是需要保证定期检测的通信(例如键盘),或者需要收发大量数据(例如U盘),也可能是配置设备的某些功能(例如设置鼠标宏),因为这些功能对于总线带宽的占用频率和大小存在非常大的差异。为此我们设计了四种传输模式,来分配传输的频率和占用带宽大小。控制传输用于突发、小量数据的传输。批量传输用于突发、大量数据的传输。中断传输用于周期性、小量数据的传输。等时传输用于周期性、大量数据的传输。而主机会根据设备不同的传输模式,来控制传输的频率和带宽。
通过这样的控制设计之后,在应用层面,程序员只需专注于对数据的处理逻辑和算法即可,整个USB的功能也就完成了。
包
包是USB总线上实际传输的二进制数据序列,是物理信号层面上最基本的单元。一个包的传输分为四个部分,从开始到结束依次为:
SOP:用于告知包的开始。在物理层面是主机将差分信号线从空闲状态(J状态,对于全速/高速设备为D+高、D-低)跳变到K状态(D-高、D+低),通过这个跳变沿告知总线上所有接收器一个包即将开始。SYNC:用于完成时钟同步。在物理层面是发送了一串8位二进制序列00000001。因为USB在差分线上使用NRZI编码,传输这个序列会产生频繁的跳变,从而实现时钟同步,并让接收方确认之前的跳变是有效的SOP而非噪声干扰。Packet Content:这是一个包中实际需要传输的内容,根据包类型的不同,其数据内容也会有所差异,但核心内容分为以下部分:PID:包标识符,用于区分不同类型的包,USB。在数据层面是八个比特的数据,高四位的包类型标识符用于指明包的类型,低四位是高四位的补码,用于错误检测。
令牌包(
xx01):总是由主机发出,用于定义一个事务的开始,并设定该事务的地址和端点。PID 含义 说明 0001 令牌OUT 主机发送数据到USB设备 1001 令牌IN 主机接收从USB设备发出的数据 0101 令牌SOF 此时作为一个帧或者微帧的开始信息 1101 令牌SETUP 主机向USB设备发送配置信息 数据包(
xx11):用于携带实际的数据载荷。可以由主机或设备发出,其中DATA0和DATA1应交替发送,这个机制被称为数据切换同步(Data Toggle),是USB保证事务级可靠性的关键机制之一,用于检测和丢弃重复的数据包。PID 含义 说明 0011 数据DATA0 数据包偶数包 1011 数据DATA1 数据为奇数据包 0111 数据DATA2 此为作为一个高速同步事务的专用数据包 1111 数据MDATA 此时作为一个SPLIT事务的专用数据包。 握手包(
xx10):用于确认事务的状态,是最简单的包类型(通常没有数据载荷)。PID 含义 说明 0010 握手ACK 数据正确接收 1010 握手NAK 数据未正确接收 1110 握手STALL 使用的端点被挂起 0110 握手NYET 接收方没有响应 特殊包(
xx00):用于一些特殊用途,例如高速传输中的拆分事务(Split Transaction)等。PID 含义 说明 1000 特殊用途SPLIT 高速主使用事该SPLIT事务解决从高速模式到低速和全速模式的转换 0100 特殊用途PING 仅用于高速模式下主机使用该事务判断设备是否可以接收数据 1100 特殊用途ERR SPLIT事务中表示出现错误 1100 PRE令牌包 低速数据的先导包
地址:地址包含设备地址和端点地址,用于明确这个包是发到哪的。其中设备地址占1字节,端点地址占4位。
帧号:总共11位,主机每发出一个帧,帧号都会自动加1,帧号到达到7FFH时将归零重新开始计数。
数据域:USB传输的数据,对于不同的USB传输类型,数据域长度不同。
- 控制传输:高速和全速都是64字节。
- 批量传输:高速512字节,全速64字节。
- 中断传输:高速1024字节,全速64字节。
- 同步传输:高速1024字节,全速1023字节。
CRC校验域:分为令牌校验域和数据校验域。
- 令牌Token CRC校验域 计算地址域和帧号域的CRC:G(X) = X^5^+ X^2^ + 1
- Data CRC 计算数据域数据的CRC: G(X) = X^16^ + X^15^ + X^2^ + 1
EOP:用于告知包的结束。在物理层面是产生一个持续2位时间的SE0(Single-Ended Zero) 状态(D+和D-均被驱动为低),然后恢复1位时间的J状态,之后总线驱动被释放,进入空闲状态。
事务
尽管包是传输物理信号层面上的最小单元,但是单个包(如令牌包、握手包)并没有传输用户的实际数据,而是协议层面的控制信息。事务才是一次完整且有意义的通信流程的最小单元,它确保了定量数据的可靠交付。
一次事务通常由三个阶段的包组成:
- 令牌包 (Token Packet):由主机发起,指定目标设备地址、端点号和事务类型(IN, OUT, SETUP)。
- 数据包 (Data Packet) (可选):由数据发送方发出,承载实际的有效数据载荷。数据的长度不能超过对应端点的最大包长(
wMaxPacketSize)。 - 握手包 (Handshake Packet):由数据的接收方发出,用于向发送方反馈接收状态(如ACK, NAK, STALL),以保证传输的可靠性。某些事务可能没有数据过程,因此直接以握手包结束。
通常,一个大的数据传输会被拆分成多个事务进行。只有数据包中的有效载荷才会被缓存在端点的FIFO中,协议开销(包标识PID、地址、CRC等)由硬件处理,不占用软件可见的缓冲区空间。
对于USB 2.0,基本事务类型包括:
- SETUP事务:用于控制传输的建立阶段,传输主机发送给设备的请求命令。其数据包必须为DATA0,且会重置端点的数据触发位。
- OUT事务:主机向设备发送数据。
- IN事务:主机从设备读取数据。
- PING事务(仅高速模式):主机探询设备是否有空间接收OUT数据,以提升总线效率。
- SPLIT事务(仅高速枢纽使用):用于在高速总线上传输全速/低速流量。
IN和OUT事务使用DATA0和DATA1数据包交替发送的机制(数据切换同步)。每次事务成功完成,双方都会翻转数据触发位,用于检测数据包是否丢失或重复。事务失败则重试相同的数据包PID。
传输
因为单个事务传输数据大小有限,但是对于应用而言,只需要关注完整的数据即可,故将一次完整数据的通信过程称之为传输,并根据传输的频率和带宽占用划分为四种传输模式。
- 控制传输(Control Transfer) :用于突发的少量数据传输。例如USB设备在接入总线后进行的枚举过程,就是通过控制传输发送USB设备的描述符。
- 批量传输(Bulk Transfer):用于突发的大量数据传输。例如U盘、打印机等设备。
- 中断传输(Interrupt Transfer):用于周期的少量数据传输。例如鼠标、键盘、游戏手柄等。
- 等时传输(Isochronous Transfer):用于周期的大量数据传输。例如摄像头、麦克风、音箱等。
需要注意的是,控制传输是最可靠的传输方式,通常用于设备的配置控制。控制传输被分为三个阶段,分别为建立阶段——数据阶段——状态阶段:
- 在建立阶段,固定有1个SETUP事务,目的是为了传递一个8字节的标准化请求,该请求数据放在数据包DATA0中,且握手包必须回复ACK,否则主机会重试。
- 在数据阶段,根据请求类型,传输数据(如发送描述符),可能会有0个或多个IN或OUT事务,使用DATA0/DATA1交替传输。
- 在状态阶段,固定有1个事务,由数据阶段的接收方发送一个IN或OUT事务,数据包存在但载荷为0字节。
USB定义了8个字节的标准请求,通过这些请求,可以对设备的状态进行更改或对设备进行枚举,请求必须通过控制传输进行,关于USB的标准请求可以详见《USB 标准请求》。
其余三种传输都没有分阶段,而是直接由IN或OUT事务组成的单事务循环队列,且在一次传输中是单向通信的。
不过等时传输更特殊一些,它是可靠性最低但实时性最高的传输方式,它的IN或OUT事务中没有握手包,也就是说如果出现丢包的情况,主机和设备会直接无视这次错误而不会采取重传,以此来保证下一次事务的准时通信。
总结
我们目前已经基本了解了USB的基础概念,脑海中已经有了一定的USB通信过程的框架。通过学习,我们不难知道,一个USB设备的工作流程可以细化为以下步骤:
- 接入与上电 (Attachment and Power):
- 设备插入USB端口,从总线(VBus)获取电源。
- 设备通过其上拉电阻将D+或D-信号线拉高,主机检测到这一电平变化,确认有设备接入。
- 复位 (Reset):
- 主机向设备发送一个复位信号(持续10ms以上的SE0状态)。
- 设备收到复位信号后,进入默认状态,使用默认地址0进行通信,并准备好响应控制传输。
- 枚举与配置 (Enumeration and Configuration):
- 获取描述符:主机通过默认地址0,向设备的端点0发送各种标准请求(如
GET_DESCRIPTOR),逐步获取设备的设备描述符、配置描述符、接口描述符、端点描述符等。 - 设置地址:主机为设备分配一个唯一的地址(1-127),并发送
SET_ADDRESS请求。设备之后的所有通信都使用这个新地址。 - 获取配置信息:主机使用新地址,再次获取设备的详细配置描述符。
- 选择配置:主机根据设备的配置信息,发送
SET_CONFIGURATION请求,激活设备的某一个配置。设备至此进入配置状态(Configured State),其所有端点都准备就绪,可以正常工作。
- 获取描述符:主机通过默认地址0,向设备的端点0发送各种标准请求(如
- 驱动加载与通信 (Driver Loading and Communication):
- 主机根据枚举到的信息(如设备类、厂商ID、产品ID),加载对应的设备驱动程序。
- 驱动程序开始工作,通过主机控制器与设备进行数据通信。所有通信均由主机发起,通过之前配置好的各个端点(如批量传输端点、中断传输端点等),按照设备支持的传输类型进行数据交换。
- 移除与下电 (Removal and Power Down):
- 设备被拔出,主机检测到D+/D-信号线进入SE0状态持续一段时间,判断设备已移除。
- 主机通知操作系统和驱动程序,释放所有为该设备分配的资源(地址、带宽等)。