深入解析USB主机控制器IN/OUT事务处理与寄存器配置
1. USB主机控制器:从硬件视角看数据交换的基石
如果你在嵌入式领域摸爬滚打过几年,尤其是在工业控制、数据采集或者需要连接各种外设(比如U盘、鼠标键盘、自定义HID设备)的项目里,USB主机功能大概率是你绕不开的一个坎。很多人觉得USB复杂,是因为它涉及协议栈、描述符、枚举等一系列软件概念,但在我看来,最核心、最底层的挑战,往往在于如何让微控制器(MCU)里的那个USB主机控制器硬件,按照你的意愿精准地“动”起来。今天,我们不谈那些高层的协议,就聚焦在德州仪器(TI)TMS320F2837xS这类微控制器内置的USB主机控制器上,掰开揉碎了讲讲它的心脏——IN/OUT事务处理流程,以及那些决定其行为的寄存器配置。这就像开车,你不需要成为汽车工程师,但必须清楚油门、刹车和方向盘怎么用,才能把车开稳、开快。理解这些寄存器,就是你精准操控USB数据流的“驾驶手册”。
USB通信的本质是事务(Transaction)。一个完整的数据传输(比如从U盘读取一个扇区)由无数个微小的IN或OUT事务组成。主机控制器就是这些事务的发起者和调度者。它的核心职责可以概括为三点:发起请求(发送IN/OUT令牌包)、管理数据缓冲(通过FIFO)、以及处理设备响应(ACK/NAK/STALL)。TMS320F2837xS的USB控制器将这三项职责,巧妙地映射到了一组功能明确的寄存器上。通过配置这些寄存器,你实际上是在给硬件下达精确的指令:什么时候发数据、发多少、数据放哪里、遇到错误怎么办。这对于需要确定性和实时性的嵌入式应用至关重要,一个配置失误就可能导致数据丢失、设备无响应,或者更隐蔽的间歇性通信故障。
2. 核心事务处理机制:IN与OUT的硬件舞蹈
要驾驭USB主机控制器,你必须先理解IN和OUT事务在硬件层面是如何“跳完一支舞”的。这个过程完全由硬件自动执行,但每一步的节奏和动作,都由我们通过寄存器来设定。
2.1 IN事务:主机如何从设备“要”数据
IN事务,即主机从设备读取数据。这个过程听起来简单——“主机发个请求,设备把数据送来”,但硬件内部却经历了一系列精密的连锁反应。
第一步:发起请求(Setting the Stage)当你的应用程序需要从某个设备的端点(Endpoint)读取数据时,你并不能直接去读。你需要先“预约”一次数据传输。这是通过设置目标端点对应的控制状态寄存器中的REQPKT(Request Packet)位来完成的。以端点0的控制状态寄存器低字节USBCSRL0为例,向其REQPKT位写1,就像按下了门铃。这个动作告诉控制器内部的事务调度器:“这个端点有活跃的事务请求了,请安排处理。”
注意:
REQPKT位是主机模式下接收端点(RX Endpoint)专用的“启动键”。对于发送端点(TX Endpoint),对应的“就绪”标志是TXRDY。千万别搞混了方向。
第二步:调度与令牌发送(The Scheduler Takes Over)事务调度器在后台持续运行。它一旦检测到某个端点的REQPKT位被置位,就会在合适的总线时间(遵循USB的帧/微帧结构和带宽分配规则)向目标设备发送一个IN令牌包。这个包包含了设备地址和端点号,明确告诉设备:“请把指定端点的数据发过来。”
第三步:数据接收与就绪通知(Data Arrival)设备收到IN令牌后,如果数据准备就绪,会发回一个数据包。USB控制器硬件会自动将这个数据包存入为该端点分配的接收FIFO中。数据安全存入后,硬件会自动将USBCSRL0寄存器中的RXRDY(Receive Ready)位置1,并产生相应的端点接收中断。这就像快递员把包裹放进了你家门口的收件箱,然后按响了门铃。RXRDY=1就是你听到的门铃声,它明确告诉你:“FIFO里有数据了,快来取!”
第四步:数据卸载与流控(Unloading and Flow Control)你的中断服务程序(ISR)检测到RXRDY中断后,需要从FIFO中读取数据(卸载数据包)。数据读取完成后,你必须手动将RXRDY位清零。这是关键一步,它向硬件确认:“数据我取走了,收件箱清空了。” 如果你忘了清零,硬件会认为FIFO仍是满的,不会处理后续发往该端点的数据,导致通信卡死。
为了方便流控,硬件提供了两个非常实用的自动控制位,位于USBRXCSRHn(n为端点号)寄存器中:
AUTOCL(Auto Clear): 如果使能,当从FIFO中卸载的数据包长度等于该端点设定的最大包长(USBRXMAXPn寄存器值)时,硬件会自动清除RXRDY位。这适用于已知长度的批量传输,可以节省一次手动操作。AUTORQ(Auto Request): 如果使能,当RXRDY位被清除(无论是手动还是通过AUTOCL)时,硬件会自动重新置位REQPKT位,从而发起下一次IN事务请求。这为实现连续、自动的数据流提供了极大便利,特别适合大块数据的传输。
第五步:多包传输与结束判断(Multi-Packet and Termination)对于需要传输大量数据的场景(如从大容量存储设备读取文件),一次IN事务可能不够。硬件通过USBRQPKTCOUNTn寄存器来辅助管理。
- 已知长度传输:如果你事先知道要接收多少个数据包(例如,读取一个512字节的扇区,每个包64字节,共需8个包),你需要在传输开始前,将包数量写入
USBRQPKTCOUNTn寄存器。硬件每完成一次IN请求(即每清除一次RXRDY),就会将该寄存器的值减1。当值减到0时,硬件会自动清除AUTORQ位,停止自动发起新的请求,标志着传输结束。 - 未知长度传输:对于长度未知的传输(如中断传输或批量传输直到收到短包),你需要将
USBRQPKTCOUNTn寄存器清零。此时,AUTORQ位会一直保持有效,硬件会持续自动请求数据,直到它收到一个短包(即数据长度小于USBRXMAXPn设定的最大值)。短包是USB协议中标识传输结束的标准方式,硬件在收到短包后会自动清除AUTORQ位。
2.2 OUT事务:主机如何向设备“送”数据
OUT事务,即主机向设备发送数据。其流程与IN事务对称,但方向相反。
核心步骤:数据装载与发送
- 数据就绪:你的应用程序将待发送的数据写入目标端点对应的发送FIFO。
- 置位TXRDY:数据写入FIFO后,你必须手动设置
USBTXCSRLn寄存器中的TXRDY(Transmit Ready)位为1。这个操作告诉事务调度器:“这个端点的发送FIFO里有数据了,可以安排发送。” - 自动发送优化:与接收端类似,发送端也有一个
AUTOSET位(位于USBTXCSRHn)。如果使能,当向FIFO写入一个达到最大包长(USBTXMAXPn)的数据包时,硬件会自动置位TXRDY,无需软件干预。 - 调度与发送:事务调度器检测到
TXRDY置位,会在适当时机向设备发送一个OUT令牌包,紧接着将FIFO中的数据包发出。 - 设备响应处理:设备收到数据后,会回复一个握手包(ACK/NAK/STALL)。控制器硬件会根据响应更新状态寄存器,并可能产生中断通知CPU。
2.3 错误与异常处理:NAK、STALL与超时
在实际通信中,设备并不总是能立刻响应。USB主机控制器内置了完善的错误处理机制,这是保证通信鲁棒性的关键。
- NAK (Not Acknowledge):设备暂时无法接收或发送数据(例如,它的FIFO满了或空了)。这是USB流控的正常部分。当主机收到NAK响应时,控制器会自动重试该事务。你可以通过
USBNAKLMT寄存器为每个端点设置一个“NAK限制”。当连续收到NAK的次数达到此限制时,控制器会停止重试并产生超时错误。这防止了一个“忙”的设备长时间阻塞总线。 - STALL:设备端点发生了功能错误或遇到了它无法处理的请求(例如,收到了不支持的指令)。STALL是一个错误状态。当主机收到STALL响应时,控制器不会重试,而是会设置相应端点状态寄存器中的
STALLED位并产生中断。软件必须介入处理(通常需要清除该端点的STALL状态)。 - 无响应/总线错误:如果设备在指定时间内没有响应IN/OUT令牌,或者接收到的数据包存在CRC校验或位填充错误,控制器也会自动重试。通常,在连续3次尝试失败后,控制器会认为事务失败。对于IN事务,它会清除
REQPKT位并设置ERROR位;对于OUT事务,它会清空发送FIFO并设置ERROR位,同时产生错误中断。
实操心得:调试USB通信时,第一个要查的就是这些错误状态位(
ERROR,STALLED)。很多通信失败的根本原因就在这里。例如,设备枚举成功后却无法传输数据,很可能是某个端点的STALLED位被意外置位了,而你的软件没有去检测和清除它。
3. 事务调度与带宽管理:总线的交通指挥官
USB是一个共享总线。主机控制器内部有一个事务调度器,它的角色就像十字路口的交通信号灯,确保不同设备、不同端点的数据包有序、公平地在总线上传输,不会发生碰撞,并满足不同传输类型的时序要求。
3.1 调度策略:中断、批量与控制的区别对待
调度器根据端点类型采用不同的策略:
- 中断传输 (Interrupt Transfer):用于对延迟有要求、但数据量小的设备(如鼠标、键盘)。调度器保证在设定的时间间隔内,至少为它安排一次事务。这个间隔由
USBTXINTERVALn或USBRXINTERVALn寄存器配置,范围是1到255个帧(对于全速USB,1帧=1ms)。例如,一个全速鼠标通常将轮询间隔设置为10ms(即INTERVAL=10),调度器会保证每10帧(10ms)为它调度一次IN事务。 - 批量传输 (Bulk Transfer):用于大数据量、对实时性要求不高的传输(如U盘、打印机)。它没有固定的调度间隔,采用“尽力而为”的策略。调度器在每帧的剩余时间内,循环检查所有配置为批量传输的端点是否有活跃事务(
REQPKT=1或TXRDY=1),并安排发送。如果一个批量端点因设备返回NAK而需要重试,调度器会先“跳过”它,去检查并服务其他端点的请求,然后再回来重试这个端点。这确保了单个慢速设备不会独占总线带宽。 - 控制传输 (Control Transfer):用于设备枚举和配置。它享有最高的优先级,通常被安排在帧的开始部分。
3.2 帧管理与SOF
USB全速总线以1ms为周期划分为“帧”,低速设备则依赖主机发送的“保持激活”信号。调度器的运作与帧计数器紧密相关:
- 帧开始 (Start of Frame, SOF):在每个1ms帧的开始,如果连接的是全速设备,控制器硬件会自动发送一个SOF令牌包,并递增帧计数器。这个SOF包是所有全速设备的时间基准。
- 调度循环:发送完SOF后,调度器开始在本帧的剩余时间内,循环遍历所有已配置的端点,寻找那些有“活跃事务”的端点(即
REQPKT=1的接收端点,或TXRDY=1的发送端点),并依次安排它们的事务。 - 时间预算:调度器非常“守时”。在发起一个事务前,它会估算完成这个事务(包括令牌、数据、握手包以及包间间隔)所需的时间。如果当前帧剩余的时间不足以完成该事务,它会推迟到下一帧再执行,以避免事务跨越帧边界,这被称为“Babble”防护机制的一部分。
3.3 Babble与挂起/恢复机制
- Babble保护:如果总线在帧结束时仍然处于活动状态(即上一个数据包还没传完),控制器会认为连接的设备发生了故障(“说个没完”),从而暂停所有事务并产生一个Babble中断。这防止了故障设备破坏整个总线的时序。
- 挂起 (SUSPEND):为了节能,主机可以进入挂起模式。通过设置
USBPOWER寄存器中的SUSPEND位,主机在完成当前事务后,会停止调度器和帧计数器,不再发送SOF包,总线进入空闲状态。 - 恢复 (RESUME):要退出挂起,需要先清除
SUSPEND位,然后设置RESUME位。控制器会在总线上产生持续20ms的恢复信号(K状态),唤醒所有挂起的设备。20ms后,软件应清除RESUME位,调度器和帧计数器重新开始工作。
4. 关键寄存器配置详解:从理论到代码
理解了流程,我们来看看如何用代码配置这些寄存器。这里以TMS320F2837xS的C2000平台为例,但原理通用。
4.1 端点基础配置
在开始任何数据传输前,必须正确配置端点。这包括设置端点类型、最大包大小、分配FIFO空间等。
// 假设我们要配置端点1为批量输入(IN)端点,最大包长64字节 void ConfigureEndpoint1_IN(void) { // 1. 选择要配置的端点索引 HWREGH(USB0_BASE + USB_O_EPIDX) = 1; // 指向端点1的寄存器组 // 2. 配置端点类型和速度(主机模式下,需指定设备地址、端点和速度) // USBRXTYPE1: [7:6]=00(控制),01(等时),10(批量),11(中断); [5:4]=00(低速),01(全速); [3:0]=端点号 // 假设设备地址为1,全速,端点1为批量输入 HWREGH(USB0_BASE + USB_O_RXTYPE1) = (0x2 << 6) | (0x1 << 4) | 0x1; // 批量传输,全速,端点1 // 3. 配置最大接收包大小 HWREGH(USB0_BASE + USB_O_RXMAXP1) = 64; // 最大包长64字节 // 4. 配置接收控制状态高位寄存器 (USBRXCSRH1) uint16_t csrh = 0; csrh |= USB_RXCSRH1_AUTORQ; // 使能自动请求下一包 csrh |= USB_RXCSRH1_AUTOCL; // 当收到最大长度包时自动清除RXRDY // csrh |= USB_RXCSRH1_DMAEN; // 如果使用DMA则使能,此处为轮询示例 HWREGH(USB0_BASE + USB_O_RXCSRH1) = csrh; // 5. 配置轮询间隔(对于批量传输,此寄存器通常忽略或设为0) HWREGH(USB0_BASE + USB_O_RXINTERVAL1) = 0; // 6. 分配FIFO空间(此操作通常在全局初始化时进行) // USB控制器共有4KB FIFO RAM,前64字节固定给端点0。 // 需要计算并设置USBRXFIFOADD寄存器,为端点1分配缓冲区起始地址。 // 例如,为端点1分配128字节缓冲区,起始地址为64(紧接端点0之后) // HWREGH(USB0_BASE + USB_O_RXFIFOADD) = 64 >> 3; // 地址以8字节为单位 }4.2 发起与处理一次IN传输
配置完成后,就可以发起数据传输了。以下是一个处理端点1批量IN传输的简化流程:
// 发起一次IN传输请求 void StartBulkINTransfer(uint16_t endpoint, uint16_t packetCount) { HWREGH(USB0_BASE + USB_O_EPIDX) = endpoint; // 选择端点 // 如果知道要接收的包总数,设置USBRQPKTCOUNTn if(packetCount > 0) { // USBRQPKTCOUNTn寄存器地址偏移为 0x304 + (endpoint-1)*4 uint32_t regAddr = USB0_BASE + 0x304 + ((endpoint - 1) * 4); HWREG(regAddr) = packetCount; } else { // 未知长度传输,确保USBRQPKTCOUNTn为0 uint32_t regAddr = USB0_BASE + 0x304 + ((endpoint - 1) * 4); HWREG(regAddr) = 0; } // 置位REQPKT,启动传输 uint16_t csrl = HWREGH(USB0_BASE + USB_O_RXCSRL + (endpoint * 2)); // 计算端点CSRL地址偏移 csrl |= USB_RXCSRL1_REQPKT; HWREGH(USB0_BASE + USB_O_RXCSRL + (endpoint * 2)) = csrl; } // 在USB接收中断服务程序(ISR)中处理数据到达 __interrupt void USB0_RX_ISR(void) { // 1. 读取USB接收中断状态寄存器,判断是哪个端点产生了中断 uint16_t rxIs = HWREGH(USB0_BASE + USB_O_RXIS); // 2. 检查端点1的中断 if(rxIs & (1 << 1)) { // 假设端点1对应位为BIT1 HWREGH(USB0_BASE + USB_O_EPIDX) = 1; // 切换到端点1寄存器组 // 3. 读取端点1的接收控制状态低字节 uint16_t csrl = HWREGH(USB0_BASE + USB_O_RXCSRL1); // 4. 检查RXRDY位,表示有数据可读 if(csrl & USB_RXCSRL1_RXRDY) { // 5. 读取本次接收的字节数 uint16_t byteCount = HWREGH(USB0_BASE + USB_O_RXCOUNT1); // 6. 从端点1的FIFO中读取数据 uint8_t dataBuffer[64]; uint16_t *fifoAddr = (uint16_t*)(USB0_BASE + USB_O_FIFO1); // FIFO1的访问地址 for(int i = 0; i < (byteCount + 1)/2; i++) { // 按16位读取,注意字节数对齐 ((uint16_t*)dataBuffer)[i] = *fifoAddr; } // 7. 数据处理 (应用程序逻辑) ProcessReceivedData(dataBuffer, byteCount); // 8. 清除RXRDY位(如果AUTOCL未使能,必须手动清除) // 注意:读取USBRXCSRL1会清除某些状态位,最好先保存再操作 csrl &= ~USB_RXCSRL1_RXRDY; HWREGH(USB0_BASE + USB_O_RXCSRL1) = csrl; // 9. 检查是否收到短包(传输结束) if(byteCount < 64) { // 假设最大包长为64 // 收到短包,传输结束。如果需要,可以在这里清除AUTORQ或进行其他清理。 uint16_t csrh = HWREGH(USB0_BASE + USB_O_RXCSRH1); if(csrh & USB_RXCSRH1_AUTORQ) { // 收到短包后,硬件应已自动清除了AUTORQ,此处可做确认。 } } } // 10. 检查错误状态 if(csrl & USB_RXCSRL1_ERROR) { // 处理错误(如超时) HandleUSBError(1, csrl); // 清除错误位 csrl &= ~USB_RXCSRL1_ERROR; HWREGH(USB0_BASE + USB_O_RXCSRL1) = csrl; } if(csrl & USB_RXCSRL1_STALL) { // 端点被设备STALL,需要软件干预(通常发送清除特性请求) HandleStallEndpoint(1); // 清除STALL位 csrl &= ~USB_RXCSRL1_STALL; HWREGH(USB0_BASE + USB_O_RXCSRL1) = csrl; } } // ... 处理其他端点中断 // 清除全局中断标志 HWREGH(USB0_BASE + USB_O_IS) = 0xFFFF; // 写1清除 }4.3 地址/数据总线桥接的注意事项
TMS320F2837xS的USB控制器最初是为ARM的AHB总线设计的,后适配到C28x的16位总线。这带来一个关键差异:USB控制器的寄存器是8位宽的,而C28x的默认访问是16位的。这会导致在Code Composer Studio (CCS)的内存窗口中查看寄存器时,看到的数据与实际寄存器内容不对应。
核心规则:
- 32位和16位访问:对于大多数寄存器,使用16位 (
HWREGH) 或32位 (HWREG) 读写操作是透明的,编译器/硬件会处理对齐,你无需担心。这是访问寄存器的推荐方式。 - 8位访问:当你必须进行8位访问时(例如,访问那些严格按字节对齐的寄存器),需要使用C28x编译器提供的
__byte内在函数。 - CCS内存视图:在CCS中查看USB内存空间时要注意:
- 在32位或16位视图下,你看到的偶数地址数据实际上是重复的(因为16位读取了2个8位寄存器)。忽略所有奇数地址的显示值。
- 在8位视图下,控制器内的偶数地址数据会被复制显示到视图的奇数地址上,而控制器内奇数地址的数据根本不会显示。
避坑指南:永远不要通过CCS的内存窗口直接修改USB寄存器的值来调试!因为视图显示不真实。正确的做法是使用调试器的“表达式”窗口,调用你的寄存器读写函数(如
HWREGH)来查看和修改寄存器值,或者直接查看你代码中的变量。直接修改内存窗口很可能导致写入错误地址,造成难以排查的故障。
5. 实战配置流程与初始化清单
让我们从一个上电复位开始,梳理一遍USB主机控制器的完整初始化流程。这个过程就像给一台精密仪器上电自检和校准。
5.1 初始化步骤详解
时钟与电源使能:
// 使能USB外设时钟(具体寄存器名可能因型号而异,参考TRM) EALLOW; ClkCfgRegs.PCLKCR11.bit.USB0 = 1; // 假设在PCLKCR11寄存器 EDIS; // 配置辅助PLL,为USB模块提供精确的60MHz时钟(USB PHY要求) InitAuxPLLTo60MHz(); // 此函数需根据具体时钟树实现GPIO引脚复用配置:
// 将USB0DM和USB0DP信号复用到对应的GPIO引脚(例如GPIO42, GPIO43) EALLOW; GpioCtrlRegs.GPBPUD.bit.GPIO42 = 0; // 使能上拉 GpioCtrlRegs.GPBPUD.bit.GPIO43 = 0; GpioCtrlRegs.GPBQSEL1.bit.GPIO42 = 3; // 异步输入,无同步 GpioCtrlRegs.GPBQSEL1.bit.GPIO43 = 3; GpioCtrlRegs.GPBMUX1.bit.GPIO42 = 1; // 配置为USB0DM功能 GpioCtrlRegs.GPBMUX1.bit.GPIO43 = 1; // 配置为USB0DP功能 // 注意:USB0EPEN(外部电源使能)和USB0PFLT(电源故障)是软件模拟信号,需自行用GPIO实现 EDIS;USB控制器基础模式设置:
// 写入USB基地址(通常由头文件定义,如USB0_BASE) // 设置为主机模式(具体位取决于寄存器定义,通常在USBDEVCTL或USBPOWER中) // 例如,清除USBDEVCTL中的DEV位进入主机模式 HWREGH(USB0_BASE + USB_O_DEVCTL) &= ~USB_DEVCTL_DEV; // 使能USB PHY HWREGH(USB0_BASE + USB_O_POWER) |= USB_POWER_HSENAB; // 使能高速PHY(如果支持)FIFO内存分配: USB控制器有4KB的共享FIFO RAM。你需要像管理堆内存一样,为每个使能的端点分配缓冲区。
// 假设我们使用端点0(控制端点,64字节)和端点1(批量IN,128字节),端点2(批量OUT,128字节) uint16_t fifoStartAddr = 0; // 端点0固定使用前64字节 (0-63) // 配置端点1接收FIFO起始地址 (64字节处开始) HWREGH(USB0_BASE + USB_O_RXFIFOADD) = 64 >> 3; // 地址右移3位(除以8) // 配置端点2发送FIFO起始地址 (64+128=192字节处开始) HWREGH(USB0_BASE + USB_O_TXFIFOADD) = 192 >> 3; // 注意:需要根据所有端点的最大包长和是否双缓冲来仔细计算,避免重叠。端点配置: 如第4.1节所示,为每个要使用的端点配置类型、最大包长、地址等。
连接设备与枚举:
// 1. 开启会话(提供VBUS电源) // 通过你配置的GPIO(模拟USB0EPEN)使能外部5V电源。 EnableVBUSPower(); // 设置USBDEVCTL寄存器的SESSION位,开始检测设备连接 HWREGH(USB0_BASE + USB_O_DEVCTL) |= USB_DEVCTL_SESSION; // 2. 等待连接中断 // 在中断服务程序中,检测到连接后,读取USBDEVCTL的FSDEV/LSDEV位判断设备速度。 // 3. 复位设备 HWREGH(USB0_BASE + USB_O_POWER) |= USB_POWER_RESET; DELAY_US(20000); // 保持复位至少20ms HWREGH(USB0_BASE + USB_O_POWER) &= ~USB_POWER_RESET; // 4. 开始标准USB枚举过程��通过控制端点0发送标准请求) // 这是一个复杂的状态机,包括获取描述符、设置地址、设置配置等。 // 此部分需要实现完整的控制传输状态机,超出本文寄存器详解范围。
5.2 配置检查清单
在调试USB主机功能时,如果通信失败,可以按以下清单逐项排查:
| 检查项 | 可能的问题 | 验证方法 |
|---|---|---|
| 时钟与电源 | USB模块无时钟;VBUS未供电 | 检查PCLKCR寄存器;测量USB接口的VBUS引脚是否有5V电压。 |
| GPIO复用 | USB数据线未连接到控制器 | 检查GPIO的MUX和PUD寄存器配置是否正确。 |
| 工作模式 | 控制器仍处于设备模式 | 检查USBDEVCTL寄存器的模式位是否已设为主机。 |
| FIFO分配 | 端点FIFO地址重叠或溢出 | 计算并确认每个端点的*FIFOADD寄存器值,确保缓冲区不重叠且有足够空间。 |
| 端点类型/地址 | 主机与设备端点类型/地址不匹配 | 确认USBTXTYPEn/USBRXTYPEn寄存器配置的设备地址、端点号、速度、传输类型与设备描述符一致。 |
| 最大包长 | 包长设置大于设备端点能力 | 对照设备描述符中的wMaxPacketSize设置USBTXMAXPn/USBRXMAXPn。 |
| 控制位 | AUTORQ,AUTOCL,AUTOSET使能错误 | 根据传输需求(单次/连续,已知/未知长度)检查这些位的设置。 |
| 中断使能 | 未使能全局或端点中断 | 检查USBIE(全局中断使能)和USBTXIEn/USBRXIEn(端点中断使能)寄存器。 |
| NAK限制 | 设备忙导致无限重试或过早超时 | 根据设备特性调整USBNAKLMT寄存器值,批量传输可设大些(如255),中断传输可设小些。 |
| 总线状态 | 设备未连接、断开或处于异常状态 | 检查USBDEVCTL寄存器的连接状态位;监测是否有Babble、连接断开中断。 |
6. 高级话题与性能优化
掌握了基础配置后,你可以通过一些高级技巧来优化USB主机的性能和可靠性。
6.1 双缓冲FIFO的使用
对于高速数据流,为了避免CPU来不及处理FIFO数据而导致数据丢失,可以使用双缓冲(Double Buffering)。这需要你在配置FIFO大小时,分配两倍于最大包长的空间。硬件会自动在两个缓冲区之间切换:当CPU正在读取缓冲区A的数据时,硬件可以将下一个数据包存入缓冲区B。通过设置USBRXDPKTBUFDIS/USBTXDPKTBUFDIS寄存器可以禁用双缓冲,但对于高性能端点,建议使能。
6.2 DMA集成
虽然TMS320F2837xS的USB模块不直接支持DMA触发信号,但你仍然可以使用CPU的DMA控制器来搬运FIFO数据,从而解放CPU。你需要将DMA的源/目标地址指向USB端点的FIFO地址(如USBFIFO1),并使用软件触发或基于RXRDY/TXRDY状态位变化的事件来启动DMA传输。这能极大提高大数据量吞吐量,并降低CPU中断负载。
6.3 连接USB集线器
当通过USB 2.0集线器连接低速或全速设备时,需要额外配置USBRXHUBADDRn、USBRXHUBPORTn、USBTXHUBADDRn、USBTXHUBPORTn寄存器。这些寄存器记录了目标设备所在的集线器地址和端口号。USB主机控制器支持动态更新这些地址,以最大化支持设备数量。关键点是:必须在影响端点上所有正在进行的事务完成后,才能更改这些分配。
6.4 电源管理与唤醒
在电池供电应用中,合理使用挂起 (SUSPEND) 和远程唤醒功能至关重要。当总线空闲时,置位SUSPEND位可以显著降低功耗。设备可以通过发送恢复信号来唤醒主机,主机需要能检测并处理USBDEVCTL中的远程唤醒事件。正确处理这些状态转换,是实现低功耗USB主机的关键。
调试USB主机,尤其是底层寄存器,最有效的工具是逻辑分析仪配合USB协议分析软件。它能让你直观地看到总线上的令牌、数据和握手包,直接验证你的寄存器配置是否产生了正确的总线活动。当遇到问题时,首先检查中断状态寄存器 (USBIS,USBTXIS,USBRXIS) 和端点控制状态寄存器 (USBCSRL0,USBTXCSRLn,USBRXCSRLn) 中的错误位和状态位。90%的问题都能通过这里找到线索——是NAK超时了,还是收到了STALL,或者是FIFO溢出?寄存器状态就是硬件对你最直接的“告白”。