公司动态
USB主机控制器开发实战:从寄存器操作到DMA优化详解
1. USB控制器主机模式从寄存器操作到数据流搞嵌入式开发尤其是涉及到USB主机功能比如自己做个数据采集器、定制化HID设备或者音频接口总免不了要和USB控制器的底层寄存器打交道。很多人一看到TI、NXP这些厂商动辄几百页的控制器手册就头大里面满是缩写和状态机。我当年第一次调USB主机对着TXPKTRDY、REQPKT这些比特位也是一头雾水没少踩坑。其实一旦你理解了USB通信的基本“对话”逻辑再看这些寄存器操作就会觉得清晰很多。USB通信的本质是主机发起的一次次“事务”而控制器硬件就是帮你封装好了这些事务的底层细节你通过配置几个关键的寄存器位来“指挥”它干活。简单来说USB主机控制器就像一个邮局系统。你软件是发件人控制器是邮局和邮递员USB设备是收件人。你要寄包裹数据需要填写正确的地址设备地址、端点号、选择快递类型控制、批量、同步然后把包裹放进邮局的待发区FIFO最后按下“发货”按钮设置TXPKTRDY位。邮局控制器会负责打包、贴上运单生成USB令牌包、派出邮递员发送总线信号、并等待收件人的签收回执ACK/NAK/STALL。整个过程里你不需要关心信号电平怎么转换、CRC怎么计算你只需要关注那几个关键的“按钮”和“状态指示灯”寄存器位以及处理“无人签收”、“拒收”这些异常情况。这篇文章我就以TI的USB控制器为例掰开揉碎了讲讲在主机模式下如何操作控制器来完成控制传输、批量传输和同步传输。我会重点放在“为什么”要这么设置以及实际编程中那些手册里不会明说但能让你少熬几个通宵的细节和坑。无论你是正在调试USB主机驱动的工程师还是想深入理解USB协议如何硬件实现的学习者希望这篇近万字的详解能成为你手边实用的参考。2. 控制传输详解设备枚举与命令的基石控制传输是USB通信的“管理通道”所有USB设备都必须支持端点0上的控制传输。它用于设备枚举、配置以及发送一些标准或厂商特定的命令。一次完整的控制传输由三个阶段构成SETUP阶段、可选的数据阶段IN或OUT和状态阶段与数据阶段方向相反。手册里给出的流程图Figure 11, 12, 13描述的就是数据阶段和状态阶段的状态机我们结合寄存器操作来理解。2.1 OUT数据阶段主机发送配置或数据当SETUP阶段指定了后续要发送数据例如在设置设备地址时数据阶段是OUT发送一个包含新地址的数据包主机就进入OUT数据阶段。这个过程的核心是HOST_CSR0寄存器控制端点0的状态控制寄存器。2.1.1 操作流程与寄存器位解析第一步你作为软件驱动需要把要发送的数据加载到端点0的发送FIFO中。这个FIFO是控制器内部的一块内存专门用于临时存放待发送或已接收的数据。第二步设置TXPKTRDY位HOST_CSR0的位1。这个操作就像按下了“发射”按钮。一旦设置控制器硬件状态机立即启动它自动生成一个OUT令牌包包含设备地址和端点号紧接着将FIFO里的数据打包成DATA0或DATA1包数据交替位由控制器自动管理发送到总线上。如果一切顺利目标设备会回复一个ACK握手包。注意这里有个关键细节。TXPKTRDY位必须在数据已经完整写入FIFO之后才能设置。在写入FIFO和设置TXPKTRDY之间不应该有其他的端点0寄存器访问操作以免产生竞争条件。有些控制器对FIFO的访问有特殊时序要求需要查阅具体的数据手册。第三步等待中断。控制器完成一次发送尝试无论成功与否后都会产生一个端点0中断。你的中断服务程序需要读取HOST_CSR0寄存器检查以下几个关键状态位以决定下一步动作RXSTALL(位2)如果置位表示目标设备返回了STALL握手包。这意味着设备端点处于“停止”状态通常是因为它不理解这个请求比如无效的命令或者遇到了无法恢复的错误。这是设备端的错误指示主机软件通常需要报告错误并可能重置该管道。ERROR(位4)如果置位表示控制器尝试发送OUT令牌和DATA包总共三次都没有收到任何响应既不是ACK也不是NAK或STALL。这通常意味着物理连接问题比如设备被拔除或者设备没有正确响应地址。NAK_TIMEOUT(位7)如果置位表示设备持续回复NAK无数据可用或忙并且NAK的持续时间超过了你在HOST_NAKLIMIT0寄存器中设置的超时值。NAK是设备说“我现在忙等会儿再试”的临时性响应。处理NAK超时有两种策略继续重试简单地清除NAK_TIMEOUT位控制器会从零开始重新计数NAK直到再次超时。这适用于你愿意等待设备就绪的场景。中止事务先执行一次FIFO刷新操作通常通过设置某个刷新位然后再清除NAK_TIMEOUT位。这会放弃当前FIFO中的数据终止本次传输。这适用于上层应用有超时机制需要放弃本次请求的情况。如果以上三个错误位都没有置位那么恭喜你数据被设备正确接收并回复了ACK本次OUT数据包传输成功。第四步循环。如果控制传输的数据阶段包含多个数据包由SETUP阶段中的wLength字段指定你需要为每个数据包重复步骤1到3。直到所有数据包发送完毕才能进入状态阶段。2.1.2 实操心得错误处理的艺术手册里的流程图是理想路径实际调试中大部分时间都在和错误状态打交道。我的经验是在开发初期一定要把中断服务程序里的状态位检查做得非常详尽并且把每种错误状态都通过调试接口如串口打印出来。NAK_TIMEOUT是最常见的尤其是在设备枚举初期设备可能还没准备好。这时合理的HOST_NAKLIMIT0超时值设置就很重要。设得太短可能设备刚上电初始化慢一点就被误判为故障设得太长又会拖慢整个枚举过程。对于全速设备一个帧是1ms通常可以将NAK超时设置为几十到几百个帧的时间例如50-100ms给设备足够的响应时间。2.2 IN状态阶段确认命令执行结果在SETUP阶段或OUT数据阶段之后主机需要发起一个IN状态阶段来获取设备对刚刚执行的命令的最终确认。这是一个零长度的数据包事务。操作上你需要同时设置HOST_CSR0寄存器的STATUSPKT位6和REQPKT位5。STATUSPKT标志告诉控制器这是一个状态阶段事务REQPKT则是请求发送IN令牌。设置后控制器发送IN令牌。设备在状态阶段的响应至关重要如果设备返回一个DATA1包内容长度为零并紧跟一个ACK表示命令被成功接受和执行。如果设备返回STALL表示命令执行失败或不被支持。如果设备返回NAK理论上在状态阶段不应该发生如果发生通常按错误处理。在中断服务程序中除了检查RXSTALL、ERROR、NAK_TIMEOUT位还需要检查RXPKTRDY位0。如果它被置位说明收到了一个数据包应该是零长度的DATA1你需要读取FIFO虽然没数据并清除该位以完成事务。2.3 OUT状态阶段确认数据接收完成在IN数据阶段主机从设备读取数据之后主机需要发起一个OUT状态阶段向设备确认数据已成功接收。这也是一个零长度的数据包事务。操作上你需要同时设置HOST_CSR0寄存器的STATUSPKT位6和TXPKTRDY位1。注意这里虽然TXPKTRDY被设置但FIFO里并没有数据或者是一个预置的空包。控制器会发送一个OUT令牌后跟一个零长度的DATA1包。设备应回复ACK。主机在中断中检查RXSTALL、ERROR、NAK_TIMEOUT位如果都未置位则状态阶段成功整个控制传输完成。重要提示控制传输的这三个阶段SETUP DATA STATUS必须完整且顺序正确。任何一个阶段失败如收到STALL通常意味着整个控制传输失败主机软件需要根据USB规范进行错误恢复比如尝试重置设备或重新枚举。状态阶段的方向必须与数据阶段相反这是USB协议规定的用于实现可靠的握手。3. 批量传输大块非实时数据的可靠通道批量传输用于传输大量、非周期性的数据对传输时间没有保证但保证数据的正确性。U盘、打印机、扫描仪等设备主要使用批量传输。它使用错误检测和重传机制ACK/NAK确保数据无误。在主机模式下批量传输分为Bulk IN主机读和Bulk OUT主机写它们使用独立的端点寄存器组例如HOST_RXCSR用于INHOST_TXCSR用于OUT而不再是端点0的HOST_CSR0。3.1 批量IN传输从设备读取数据3.1.1 初始化配置为通信铺路在发起任何批量IN事务之前必须正确配置对应的接收端点例如EP1-IN。这个过程比控制传输复杂因为你需要告诉控制器更多关于目标设备的信息。设置目标地址在RXFUNCADDR寄存器中写入你要通信的USB设备的地址。这个地址是在枚举阶段由主机分配的。配置端点类型编程HOST_RXTYPE寄存器。这是关键一步SPEED字段位7:6设置目标设备的速度低速、全速、高速。PROT字段位5:4对于批量传输必须设置为二进制10。RENDPN字段写入目标设备IN端点的端点号。这个号码来自设备描述符。设置最大包大小将RXMAXP寄存器设置为目标端点描述符中的wMaxPacketSize值。控制器会用这个值来判断一个数据包是否接收完整以及进行流量控制。设置NAK超时间隔HOST_RXINTERVAL寄存器用于设置NAK超时值2-32768个帧/微帧。如果设备持续回复NAK超过这个时间控制器将产生DATAERR_NAKTIMEOUT中断。如果不需要此功能可写0。启用中断在INTRRXE寄存器中设置对应端点的中断使能位这样当数据到达或发生错误时你才能收到通知。配置HOST_RXCSR控制状态寄存器DMAEN(位13)如果希望使用DMA自动搬运FIFO数据到内存则置1。DISNYET(位12)对于高速传输的PING流控通常清0以启用正常流程。DMAMODE(位11)必须清0。初始化数据交替位和FIFO首次配置端点时必须确保数据交替位Data Toggle从0开始。可以通过设置CLRDATATOG位位7来强制清零。同时检查RXPKTRDY位如果被置位表示FIFO中有残留数据必须通过设置FLUSHFIFO位位4来清空FIFO。注意如果启用了双缓冲DPB可能需要连续执行两次FIFO刷新操作才能确保清空。3.1.2 启动传输与数据接收配置完成后要启动一次批量IN传输你只需简单地设置对应端点的HOST_RXCSR寄存器中的REQPKT位位5。控制器便会自动向目标设备发送IN令牌包。随后控制器会处理响应设备返回数据包控制器接收数据存入FIFO并设置RXPKTRDY位然后产生中断。设备返回STALL设置RXSTALL位产生中断。表示该端点永久停止需要主机干预。设备返回NAK控制器会自动重发IN令牌直到成功或达到HOST_RXINTERVAL设置的NAK超时限制。超时后设置DATAERR_NAKTIMEOUT位并产生中断。设备无响应控制器尝试三次后设置ERROR位并产生中断。在你的中断服务程序中你需要读取HOST_RXCSR来判断状态。如果RXPKTRDY置位就从FIFO中读取数据包然后清除RXPKTRDY位。如果启用了AutoRequest且DMA使能清除RXPKTRDY位会自动设置REQPKT位从而连续请求下一个数据包实现流式传输。3.1.3 性能优化双缓冲与DMA批量传输常常用于传输大文件因此性能至关重要。这里有两个关键特性双缓冲Double Packet Buffering通过设置RXFIFOSZ寄存器的DPB位启用。它允许FIFO同时存储两个数据包。当CPU或DMA正在读取第一个包时控制器可以同时接收第二个包几乎消除了包之间的总线空闲时间显著提升了连续读取的吞吐量。DMA直接内存访问这是解放CPU、提升效率的利器。使能DMA后每当FIFO中有数据包RXPKTRDY置位DMA控制器会自动将数据搬运到你在内存中预设的缓冲区无需CPU介入。对于大数据量传输这能极大降低CPU负载。配合AutoRequest功能可以实现“无人值守”的连续数据流传输DMA搬走一个包AutoRequest自动发起下一个请求直到整个传输完成才产生一个中断通知CPU。3.2 批量OUT传输向设备发送数据批量OUT传输的流程与IN对称但方向相反使用的是HOST_TXCSR等寄存器。3.2.1 初始化配置设置目标地址配置TXFUNCADDR。配置端点类型编程HOST_TXTYPEPROT字段同样设为10TENDPN字段填目标设备的OUT端点号。设置最大包大小配置TXMAXP。设置NAK超时间隔配置HOST_TXINTERVAL。启用中断配置INTRTXE。配置HOST_TXCSRMODE(位13)如果FIFO与Rx端点共享需置1以确保FIFO使能。DMAEN(位12)需要DMA时置1。FRCDATATOG(位11)通常清0允许正常数据交替。DMAMODE(位10)当DMA使能且不希望每个包发送完成都产生中断时置1。这在流式传输时非常有用。初始化数据交替位和FIFO同样使用CLRDATATOG和FLUSHFIFO位进行初始化。3.2.2 启动传输与数据发送要发送数据首先将数据写入端点的发送FIFO。如果启用了双缓冲可以连续写入两个包的数据。然后设置HOST_TXCSR寄存器的TXPKTRDY位位0。控制器随即发送OUT令牌包后跟FIFO中的数据。设备可能回复ACK、NAK或STALL处理逻辑与批量IN类似。如果收到ACK控制器会自动清除TXPKTRDY位表示FIFO空间已空可以加载下一个数据包。如果发生NAK超时或ERROR处理方式也类似控制传输。3.2.3 错误处理与流程控制批量传输的错误处理相对直接。STALL意味着端点永久错误主机应停止使用该管道并报告错误。NAK是流控的一部分设备通过它告诉主机“我现在的缓冲区满了请稍后再发”。控制器的自动重试和NAK超时机制使得上层软件无需关心暂时的忙状态。ERROR位通常指示更严重的通信问题如设备断开。踩坑记录在调试批量OUT时最容易出错的地方是数据包边界和FIFO管理。务必确保你写入FIFO的数据长度不超过TXMAXP设置的值。如果一次要发送的数据大于最大包大小你需要手动将其分割成多个包并逐个加载到FIFO、设置TXPKTRDY。在DMA模式下则需要正确设置DMA描述符中的包长度。另一个坑是在发送完最后一个数据包后如果这个包的长度恰好等于最大包大小设备可能会期待另一个零长度的包作为“短包”来标识传输结束。这在USB大容量存储类协议中很常见需要根据具体的设备类协议来正确处理。4. 同步传输为实时流媒体设计的通道同步传输用于传输对时间敏感、但允许一定数据错误的实时数据如音频流、视频流。它不进行错误重传没有ACK/NAK/STALL握手但保证固定的带宽和传输间隔。因此它的错误处理和控制逻辑比批量传输简单但时序要求严格。4.1 同步IN传输接收音频/视频流同步IN的配置与批量IN非常相似主要区别在于端点类型在HOST_RXTYPE寄存器中PROT字段需设置为二进制01表示同步传输。轮询间隔HOST_RXINTERVAL寄存器设置的是事务间隔例如每帧/微帧一次而不是NAK超时。对于全速同步端点通常每帧1ms进行一次事务。错误处理同步传输没有STALL或NAK。主要的错误是数据错误如CRC校验失败或位填充错误。当发生这种错误时控制器仍然会将可能损坏的数据存入FIFO但会设置HOST_RXCSR中的DATAERR位在有些控制器中与NAKTIMEOUT共用位3。软件需要检查这个位并决定是丢弃错误数据还是尝试使用例如音频中的静音补偿。操作上设置REQPKT位启动传输后控制器会根据设定的间隔自动发送IN令牌。数据到达后产生中断软件读取FIFO并清除RXPKTRDY位。4.1.1 时序挑战与SOF同步同步传输最大的挑战是保持稳定的数据流。中断的触发时间可能在帧内的任何时刻这可能导致软件读取FIFO的节奏不稳定。如果下游处理模块如音频DAC需要稳定的数据速率这种不稳定性会成为问题。一个经典的解决方案是利用帧起始SOF信号。USB主机每1ms全速或125us高速会发送一个SOF令牌包。许多USB控制器会提供一个SOF_PULSE中断或信号。你可以这样做仍然使能端点中断在中断服务程序中仅清除RXPKTRDY位可选地也可以读取数据到二级缓冲区。同时使能SOF_PULSE中断。在SOF中断服务程序中从二级缓冲区或直接如果支持向你的音频处理模块提交恰好一帧时间所需的数据量。这样无论USB数据包在帧内何时到达你向音频模块提供数据的节奏都被严格锁定在1ms的SOF周期上消除了抖动实现了“等时”播放。4.2 同步OUT传输发送音频/视频流同步OUT传输的配置与批量OUT类似PROT字段设为01HOST_TXINTERVAL设置发送间隔。操作流程是软件将一帧数据写入FIFO设置TXPKTRDY位。控制器在预定的时间点发送OUT令牌和数据。由于没有握手包发送后即认为完成。4.2.1 保证连续性的缓冲策略与同步IN类似同步OUT也需要稳定的数据供给。如果软件填充FIFO的时机不固定可能导致某些帧没有数据可发产生断流。同样可以利用SOF_PULSE信号。在SOF中断服务程序中将下一帧要发送的数据预先加载到FIFO中。这样无论你的数据源如音频ADC采样何时提供数据向USB FIFO填充数据的动作都被同步到SOF周期确保了数据流的连续性。这里双缓冲特性同样能提供帮助允许你提前准备下一包数据。经验之谈调试同步传输时逻辑分析仪或USB协议分析仪是必不可少的工具。你需要亲眼看到IN/OUT令牌是否严格按照设定的间隔例如每帧一个出现在总线上。数据流的连续性是否得到保证。对于音频应用一个常见的测试方法是发送或接收一个固定频率的正弦波数据然后在接收端用示波器或软件观察波形是否连续、有无毛刺或断裂这能最直观地反映同步传输的质量。5. DMA高级应用解放CPU实现高效数据搬运当数据传输量很大时如果每个USB数据包都触发CPU中断由CPU来搬运FIFO中的数据CPU负载会非常高系统效率低下。DMA直接内存访问正是为了解决这个问题而生它允许外设如USB控制器直接与内存交换数据无需CPU参与。5.1 DMA工作原理与核心概念TI的USB控制器通常集成一个多通道的DMA控制器。它通过“缓冲区描述符”Buffer Descriptor这种数据结构来管理数据传输。你可以把它想象成一份“送货单”链表。5.1.1 缓冲区描述符Buffer Descriptor每个要传输的数据块缓冲区都对应一个描述符描述符包含以下关键信息下一个描述符指针构成一个链表让DMA知道当前缓冲区传输完后下一个要处理的是谁。缓冲区指针数据在内存中的实际地址。缓冲区长度有效数据的字节数。缓冲区偏移缓冲区起始处需要跳过的字节数用于对齐或协议头处理。SOP/EOP包起始/包结束标志标识一个DMA数据包的开始和结束。一个DMA包可以由多个物理上不连续的内存缓冲区链接而成。EOQ队列结束标志标识这是DMA队列中的最后一个描述符。这些描述符本身需要放在内存中并且地址必须16字节对齐。CPU负责创建和初始化这些描述符链然后告诉DMA控制器“第一份送货单在哪里”。5.1.2 传输模式透明模式与RNDIS模式透明模式Transparent Mode这是最基本模式。每个USB数据包的传输完成或错误都会产生一个DMA中断给CPU。CPU需要处理每个包例如检查状态、更新描述符指针到下一个。这减轻了CPU搬运数据的负担但中断频率依然很高。RNDIS模式这是一种更高效的聚合模式。它得名于微软的远程网络驱动接口规范但思想可以泛化。在此模式下DMA可以处理多个USB数据包后才产生一个中断通知CPU。例如DMA可以连续传输完一个包含10个USB包的完整网络帧或音频帧后再产生一次中断。这极大地降低了中断频率特别适合高速流式数据传输。启用RNDIS模式通常需要设置DMAMODE位以及配置相关的包计数寄存器。5.2 DMA传输设置与流程以批量OUT的DMA传输为例设置流程如下内存中准备数据缓冲区在系统内存中开辟一块或多块连续区域存放要发送的数据。创建并初始化TX缓冲区描述符链为每个数据缓冲区创建一个描述符。填写Buffer Pointer缓冲区地址和Buffer Length。设置SOP和EOP位来界定一个完整的DMA数据包可能对应一个或多个USB数据包。通过Next Descriptor Pointer将描述符链接起来。最后一个描述符的该指针设为NULL或设置EOQ位。配置DMA通道寄存器将第一个描述符的地址写入DMA通道的DMA头指针寄存器。配置传输模式透明/RNDIS、突发长度等参数。配置USB端点并启用DMA如前所述配置HOST_TXTYPE、TXMAXP等。在HOST_TXCSR寄存器中设置DMAEN1。如果希望使用RNDIS模式以减少中断还需设置DMAMODE1。启动传输将第一个数据包或通过DMA描述符指定的数据写入FIFO可能仍需要软件触发或者在某些自动模式下使能DMA后传输会自动开始。设置TXPKTRDY位。DMA自动工作USB控制器需要发送数据时会向DMA控制器发起请求。DMA控制器根据当前描述符自动从内存中读取数据填充到USB控制器的FIFO中。一个缓冲区用完后自动跳转到下一个描述符。中断处理传输完成或出错时产生中断。在中断服务程序中检查DMA通道状态寄存器确认是正常完成还是错误。如果使用透明模式可能需要为每个包更新状态如果使用RNDIS模式则可能是在传输完一个完整的数据块后才处理。可以准备下一批数据的描述符链并重新启动DMA传输。5.2.1 双缓冲与DMA的协同双缓冲和DMA是绝配。对于IN传输DMA可以在CPU处理前一个包的数据时自动将刚到达FIFO的第二个包搬走最大化利用了总线带宽。对于OUT传输DMA可以提前将下一个包的数据加载到空闲的FIFO缓冲区中实现无缝流水线。避坑指南DMA描述符的内存对齐和缓存一致性是两个大坑。描述符本身必须按要求的对齐方式如16字节分配否则DMA控制器可能无法正确读取。更大的问题是缓存如果CPU和DMA共享同一块内存数据缓冲区或描述符区而CPU有缓存那么CPU写入的数据可能还在缓存里没有刷到主存DMA读到的是旧数据同样DMA写入的数据在主存CPU可能从缓存读到旧数据。解决方法通常是使用非缓存Non-cacheable的内存区域或者在进行DMA操作前后手动执行缓存清洗flush和无效invalidate操作。这是嵌入式系统开发中一个非常常见且棘手的问题务必仔细处理。