1. 项目概述
如果你正在基于TI的TMS320DM35x系列芯片开发嵌入式产品,并且需要实现一个高速、稳定的USB数据通道——无论是作为海量存储设备、视频采集卡,还是作为带主机功能的便携设备——那么你大概率绕不开对片上USB控制器的深度编程。这个控制器远不止是一个简单的串行接口,它是一个集成了USB 2.0 PHY、可编程端点FIFO和高效CPPI DMA引擎的复杂子系统。手册里上百页的寄存器描述和流程图,初次接触时确实让人望而生畏。我花了相当长的时间,在调试各种USB传输超时、DMA描述符丢失、FIFO配置错误的过程中,才逐渐摸清了它的“脾气”。这篇文章,就是把我踩过的坑、验证过的配置流程和核心原理,系统地梳理出来,希望能帮你快速上手,避开那些让我熬夜的陷阱。
TMS320DM35x的USB控制器是一个双角色(DRD)控制器,意味着它既可作为USB设备(Peripheral Mode)被电脑枚举,也可作为USB主机(Host Mode)去连接U盘、鼠标等外设。其核心优势在于集成了TI特有的CPPI(Chaos Packet Peripheral Interface)DMA引擎,支持4个独立的TX和RX DMA通道,能够将CPU从繁重的数据搬运工作中解放出来,实现接近理论带宽的数据吞吐。理解它的工作模式、FIFO管理机制和DMA描述符链,是发挥其性能的关键。
2. 控制器架构与核心模块解析
2.1 整体功能框图与数据流
要驾驭这个控制器,首先得在脑子里建立起它的数据通路模型。抛开手册里复杂的方框图,我们可以把它想象成一个高效的物流中心。
核心模块包括:
- USB 2.0 PHY:这是物理层的收发器,负责将数字信号转换成差分信号(USB_DP/USB_DM)在线缆上传输,并处理高速(480Mbps)、全速(12Mbps)、低速(1.5Mbps)的识别与切换。PHY的初始化是第一步,通常通过配置特定的系统控制寄存器(如
USB_PHY_CTRL)来完成,包括上电、使能时钟和解除复位。 - 端点FIFO RAM:这是一块4KB大小的共享内存,被灵活划分给各个端点使用。你可以把它看作一组并行的快递分拣缓冲区。每个端点(EP0-EP4)的TX(发送)和RX(接收)方向都可以独立配置FIFO的起始地址和大小(从8字节到4096字节,以2的幂次递增)。双缓冲配置是提升吞吐量的关键技巧,它允许CPU或DMA在填充一个缓冲区时,USB核心可以同时发送另一个缓冲区中的数据,几乎消除了总线等待时间。
- CPPI DMA引擎:这是控制器的“智能搬运工”。与传统DMA需要CPU频繁配置每次传输不同,CPPI采用描述符链(Descriptor Chain)机制。CPU只需要预先准备好一串描述符(每个描述符指向一个数据缓冲区并定义了其属性和下一个描述符的位置),然后启动DMA。DMA引擎会自动按链抓取数据、进行传输,并在完成后通过中断通知CPU。它独立服务于4个TX和4个RX通道,与端点1-4一一对应。
- 寄存器组与控制逻辑:这是CPU与USB控制器沟通的“控制面板”。包括全局控制寄存器(
CTRLR)、端点控制和状态寄存器(如PERI_TXCSR、HOST_RXCSR)、中断管理寄存器簇(INTSRCR,INTMSKR等),以及专门用于配置DMA的CPPI相关寄存器。所有对传输流程的调度、状态查询和错误处理,都通过对这些寄存器的读写来完成。
数据流示例(设备模式,Bulk OUT传输):
- 主机发送一个OUT数据包。
- USB PHY接收并解码数据,将其写入对应端点的RX FIFO。
- 如果该端点的DMA已使能,CPPI RX DMA引擎会检测到FIFO中有数据,自动根据当前描述符将数据搬运到系统内存的指定缓冲区。
- 数据搬运完成(达到一个数据包长度或遇到短包),DMA引擎更新状态,并可产生中断。
- CPU在中断服务程序中,检查描述符的状态,确认数据接收成功,然后回收或准备下一个描述符,以供后续传输使用。整个过程,CPU仅在链首尾进行干预。
2.2 关键硬件特性与配置要点
时钟控制:USB控制器需要一个精确的时钟源,通常由外部晶体振荡器提供,内部PLL产生核心工作时钟。确保时钟稳定是USB通信的基础,任何频偏都可能导致高速模式握手失败。
动态FIFO sizing:这是DM35x USB控制器的一个亮点。你不需要在硬件设计时就固定死每个端点的缓冲区大小。通过TXFIFOSZ和RXFIFOSZ寄存器,软件可以在初始化时动态分配那4KB RAM。例如,对于一个需要大吞吐量的视频流端点(如Bulk EP2),你可以分配1024字节的FIFO;而对于传输少量状态信息的中断端点(Interrupt EP1),分配64字节可能就足够了。分配策略直接影响性能:FIFO太小会导致频繁的DMA请求或NAK握手,降低效率;FIFO太大则浪费内存,可能挤压其他端点的空间。我的经验法则是,对于高速Bulk传输,FIFO大小至少设置为最大数据包长度(通常为512字节)的2倍以上,以充分利用双缓冲优势。
VBUS电源管理:作为主机时,控制器需要提供+5V的VBUS电源。DM35x通常通过一个外部电源开关芯片(由I2C或GPIO控制)来管理VBUS。代码中需要在主机初始化阶段打开VBUS,并在设备拔出或系统休眠时关闭VBUS,以实现节能和安全的插拔检测。
索引与非索引寄存器:为了减少寄存器地址空间占用,端点1-4的控制状态寄存器(如TXMAXP,PERI_TXCSR)是索引访问的。即,你先向INDEX寄存器写入目标端点号(1-4),然后后续对TXMAXP等寄存器的读写操作就会映射到对应端点上。务必注意:在操作多个端点时,每次切换端点前都要重新设置INDEX寄存器,这是一个常见的疏忽点,会导致配置错乱。而EP0和全局寄存器(如FADDR,POWER)则是固定地址的非索引寄存器。
3. 设备模式操作深度剖析
当DM35x作为USB设备(如U盘、摄像头)连接至电脑时,它工作在设备模式。这是最常用的模式,其核心是正确响应主机的枚举请求和处理数据传输。
3.1 端点0:控制传输的生命线
端点0(EP0)是每个USB设备必须存在的控制端点,且总是双向的(包含TX和RX)。它用于处理所有标准的设备请求,如获取描述符、设置地址、设置配置等。EP0的传输由一系列预设的状态机驱动,CPU主要通过查询或中断方式响应。
EP0事务处理流程:
- SETUP阶段:主机发送一个8字节的SETUP包到EP0。USB控制器会将其存入FIFO0,并置位
PERI_CSR0寄存器中的SetupEnd或RxPktRdy位(取决于配置),同时产生中断。 - CPU响应:在中断服务程序(ISR)中,CPU从
FIFO0寄存器读取这8字节SETUP数据,解析bmRequestType,bRequest等字段。 - 数据阶段(可选):如果是
GET_DESCRIPTOR等需要返回数据的请求,CPU将数据准备好,写入FIFO0,然后设置PERI_CSR0的TxPktRdy位,控制器会自动发送数据。如果是SET_ADDRESS等无数据阶段请求,则跳过此步。 - 状态阶段:最后,主机发送一个IN或OUT包(长度为0)来确认整个控制传输的完成。对于IN请求的状态阶段,设备需要发出一个0长度的OUT包应答(即让RX就绪);对于OUT请求,则需要发出一个0长度的IN包应答(即让TX就绪)。完成后,
PERI_CSR0的StatusPkt位会被置位。
实操心得:EP0的“ServicedRxPktRdy”与“ServicedSetupEnd”这是两个极易混淆的位。简单来说,
ServicedRxPktRdy用于清除由数据OUT包(非SETUP包)触发的RxPktRdy状态。而ServicedSetupEnd专门用于清除由SETUP包触发的SetupEnd状态。关键区别:当你读取一个SETUP包后,必须写ServicedSetupEnd来清除中断和状态,为接收下一个SETUP包做准备。如果你错误地写了ServicedRxPktRdy,控制器可能无法正确进入下一个SETUP阶段,导致枚举失败。我的调试血泪史是,在枚举阶段反复收到SetupEnd中断却无法前进,最终发现就是这两个位操作反了。
3.2 批量与中断端点传输编程
端点1-4可配置为Bulk或Interrupt传输端点。它们的编程模型类似,主要通过对PERI_TXCSR(发送)和PERI_RXCSR(接收)寄存器的操作来完成。
Bulk IN 传输(设备发送数据给主机)流程:
- 配置端点:通过
INDEX选择端点,设置TXMAXP(最大包长),TXFIFOSZ和TXFIFOADDR。 - 准备数据:CPU或DMA将待发送数据写入该端点的TX FIFO。
- 启动传输:设置
PERI_TXCSR寄存器的TxPktRdy位为1。控制器检测到该位,便会开始将FIFO中的数据发送出去。 - 传输完成:当数据包成功发送后,控制器会清除
TxPktRdy位,并可能产生中断(如果使能)。如果是DMA模式,DMA引擎会在搬空FIFO后自动设置TxPktRdy。 - 处理短包与ZLP:USB协议使用短包(数据长度小于
TXMAXP)或零长度包(ZLP)来标识一个传输阶段的结束。在代码中,当你需要发送一个逻辑上结束的数据块时,必须确保最后一个数据包是短包或ZLP。对于DMA,需要在最后一个缓冲区的描述符中设置EOP(End of Packet)标志。
Bulk OUT 传输(设备从主机接收数据)流程:
- 配置端点:设置
RXMAXP,RXFIFOSZ和RXFIFOADDR。 - 就绪接收:设置
PERI_RXCSR的RxPktRdy位为0(表示FIFO空闲可接收),或由DMA自动管理。 - 数据到达:主机发送数据包,控制器将其写入RX FIFO,并设置
RxPktRdy为1。 - 读取数据:CPU轮询到
RxPktRdy为1,或DMA被触发,从FIFO中读取数据。 - 确认接收:数据读取完毕后,必须清除
RxPktRdy位(写0),以告知控制器FIFO已空,可以接收下一个包。在DMA模式下,DMA引擎在完成搬运后会自动清除此位。
中断传输与批量传输在编程上几乎相同,区别在于协议层:主机会以固定的时间间隔(由端点描述符中的bInterval指定)来查询(Poll)中断端点。因此,设备端需要确保在每次查询到来时,数据已经准备就绪(对于IN方向),或者FIFO是空的可以接收新数据(对于OUT方向)。在PERI_TXCSR/PERI_RXCSR中,也有相应的位(如Iso位应设为0)来区分。
3.3 同步端点与双缓冲配置
同步(Isochronous)传输用于对时间敏感但容错性高的数据流,如音频、视频。其特点是没有握手包(No ACK/NAK),传输失败也不会重试。
配置关键点:
- 在
PERI_TXCSR或PERI_RXCSR中,必须设置ISO位为1。 - 由于没有重试,确保FIFO大小和DMA带宽能够跟上数据流速率至关重要。通常需要更大的FIFO来平滑数据波动。
- 双缓冲(Double Buffering)对于同步和高速批量传输几乎是必选项。启用方法是在配置
TXFIFOSZ或RXFIFOSZ时,将bit 4(DPB位)设置为1。启用后,硬件会为这个端点分配两倍于所设大小的FIFO空间。工作时,一个缓冲区用于CPU/DMA访问,另一个缓冲区用于USB核心的发送/接收,两者自动切换,极大地减少了总线冲突和等待时间。
一个典型的设备模式初始化代码框架(补充细节后):
void usb_peripheral_endpoint_init(int ep_num, int dir, int type, int max_packet_size) { // 保存当前INDEX,防止影响其他端点操作 Uint32 old_index = USBREGS->INDEX; // 1. 设置目标端点索引 (EP1-4) USBREGS->INDEX = ep_num; // 2. 配置FIFO大小和地址(示例:分配512字节,启用双缓冲) int fifo_size_code = 6; // 2^6 * 8 = 512 bytes int fifo_start_addr = calculate_fifo_start_addr(ep_num, dir); // 需实现地址计算函数 if(dir == USB_DIR_TX) { USBREGS->TXFIFOSZ = fifo_size_code | (1 << 4); // bit4=1 启用双缓冲 USBREGS->TXFIFOADDR = fifo_start_addr; USBREGS->TXMAXP = max_packet_size; } else { USBREGS->RXFIFOSZ = fifo_size_code | (1 << 4); USBREGS->RXFIFOADDR = fifo_start_addr; USBREGS->RXMAXP = max_packet_size; } // 3. 根据传输类型配置控制状态寄存器 if(dir == USB_DIR_TX) { USBREGS->PERI_TXCSR = 0; // 先清零 if(type == USB_TRANSFER_TYPE_ISOCHRONOUS) { USBREGS->PERI_TXCSR |= (1 << 2); // 设置ISO位 } // 其他配置:如自动清TxPktRdy、DMA使能等 // USBREGS->PERI_TXCSR |= (1 << 3); // 例如,使能DMA请求 } else { USBREGS->PERI_RXCSR = 0; if(type == USB_TRANSFER_TYPE_ISOCHRONOUS) { USBREGS->PERI_RXCSR |= (1 << 2); // 设置ISO位 } // 例如,使能数据触发位(Data Toggle)自动清零 // USBREGS->PERI_RXCSR |= (1 << 1); } // 4. 恢复INDEX寄存器 USBREGS->INDEX = old_index; }4. 主机模式操作与事务调度
当DM35x需要读取U盘或连接USB键盘时,它需要扮演主机角色。主机模式的核心是事务调度——由主机主动发起所有通信。
4.1 主机模式初始化与设备枚举
作为主机,首先要提供VBUS电源(通过外部电路),然后执行端口复位(向DEVCTL寄存器写特定序列),强制下游端口进入复位状态至少10ms。复位结束后,连接的低速或全速设备会通过上拉电阻表明自身速度,高速设备则会先以全速连接,再通过“Chirp”握手过程切换到高速模式。控制器硬件会自动检测并记录设备速度。
枚举流程的软件实现要点:
- 控制传输发起:所有枚举请求(如
GET_DESCRIPTOR,SET_ADDRESS)都是通过EP0发起的控制传输。在主机模式下,你需要手动构建SETUP包的数据(8字节),将其写入FIFO0,然后设置HOST_CSR0的SetupPkt位为1,控制器便会发起SETUP事务。 - 数据与状态阶段管理:对于
GET_DESCRIPTOR,在SETUP阶段后是IN数据阶段。你需要先设置HOST_CSR0的RxPktRdy位为0(表示FIFO空,准备接收),然后设置ReqPkt位为1来请求数据包。控制器发起IN事务,将设备返回的数据存入FIFO,并设置RxPktRdy为1。你读取数据后,再清除RxPktRdy。最后的状态阶段是一个OUT(对于IN数据阶段)或IN(对于OUT数据阶段)的0长度包,你需要相应地设置TxPktRdy或RxPktRdy/ReqPkt。 - 错误与超时处理:主机必须处理设备可能返回的NAK(否定应答)或STALL(错误停止)握手包。控制器会在
HOST_CSR0中设置相应的状态位(如NAKTimeout)。你的代码需要根据这些状态决定重试(对于NAK)还是报告错误(对于STALL)。HOST_NAKLIMIT0寄存器可以设置NAK超时的帧数(对于控制端点0)。
4.2 批量与中断传输的主机端编程
对于非0端点,主机模式的编程与设备模式有对称性,但发起方是主机。
主机发起 Bulk IN 传输(从设备读取数据):
- 配置目标端点的
HOST_TXTYPE(对于IN事务,实际是主机接收,但寄存器名是TXTYPE,注意区分)、HOST_TXINTERVAL(对于中断/同步端点)等。 - 设置
HOST_RXCSR寄存器的ReqPkt位为1。这会使得主机在下一个合适的调度时间点,向设备发送一个IN令牌包。 - 设备响应数据包,主机控制器将其接收并存入RX FIFO,然后设置
RxPktRdy位。 - CPU或DMA从FIFO读取数据。
- 读取完成后,如果传输尚未结束(例如,数据量大于一个包),需要再次设置
ReqPkt位,请求下一个数据包。直到收到一个短包或ZLP,表示传输结束。
主机发起 Bulk OUT 传输(向设备发送数据):
- 配置目标端点的
HOST_TXTYPE、HOST_TXINTERVAL等。 - 将数据写入TX FIFO。
- 设置
HOST_TXCSR寄存器的TxPktRdy位为1。主机控制器会发起一个OUT令牌包,后跟数据包。 - 设备返回ACK握手包后,控制器清除
TxPktRdy位,表示本次事务完成。 - 如果还有后续数据,重复步骤2-4。
关键寄存器HOST_TXCSR/HOST_RXCSR的配置差异:与设备模式的PERI_*CSR相比,主机模式的CSR寄存器多了ReqPkt(请求数据包)、Error(传输错误)、NAKTimeout等控制位。你需要根据传输方向(IN/OUT)正确操作这些位。一个常见的错误是在IN传输中误操作了TxPktRdy位,导致主机错误地发送了数据包。
4.3 主机模式下的NAK超时与轮询间隔
- NAK超时(
NAKLIMIT):当设备暂时无法响应(返回NAK)时,主机会持续重试。NAKLIMIT寄存器定义了在放弃并产生超时错误前,主机连续接收NAK的最大次数(以帧/微帧为单位)。对于控制端点,通常设置为一个较小的值(如2-4),以便快速失败。对于批量端点,可以设置得大一些,给设备更长的准备时间。 - 轮询间隔(
TXINTERVAL/RXINTERVAL):仅对中断和同步端点有效。它定义了主机发起两次事务之间的时间间隔(以帧/微帧为单位)。这个值必须与设备端点描述符中的bInterval相匹配。如果设置得过小,会增加总线负担;设置得过大,可能导致数据丢失或不及时。
5. CPPI DMA引擎详解与编程实战
CPPI DMA是DM35x USB性能的基石。它通过描述符链(Descriptor Chain)实现“一次配置,多次传输”的自动化数据搬运。
5.1 DMA描述符结构解析
描述符是一组4个连续的32位字(16字节),在内存中按16字节对齐。TX和RX描述符结构类似但略有不同。
TX DMA 描述符(用于发送数据):
- Word 0: Next Descriptor Pointer:指向下一个描述符的物理地址。如果这是链的最后一个描述符,可以设置为0或一个特殊值(如自身地址)。
- Word 1: Buffer Pointer:指向待发送数据缓冲区的物理地址。
- Word 2: Buffer Offset & Length:
[31:16]: Buffer Offset。通常为0。[15:0]: Buffer Length。本次传输的数据字节数(0-65535)。
- Word 3: Packet Info & Control:
[31] SOP (Start of Packet): 设置为1表示这是一个数据流的开始。[30] EOP (End of Packet): 设置为1表示这是数据流的结束。对于USB传输,必须在最后一个数据包的描述符中设置EOP,以触发短包或ZLP的发送逻辑。[29] OWNER: 1表示描述符由DMA引擎所有(即就绪),0表示由CPU所有(即已完成或未就绪)。CPU准备好描述符后,必须将此位置1,DMA才会处理它。DMA完成后,会将其清0。[28] EOQ (End of Queue): 1表示这是描述符队列的末尾。当DMA处理完一个EOQ=1的描述符后,会停止并产生一个队列完成中断。[23] ZERO_BYTE: 当Buffer Length为0时,必须将此位置1,同时将Buffer Length字段设为1。这用于发送零长度包(ZLP)。[19] RX_ABORT(仅RX描述符有效): 在RX描述符中用于中止接收。[15:0] Packet Length: 应等于Word 2中的Buffer Length。用于硬件校验。
RX DMA 描述符(用于接收数据):结构基本与TX相同,但Word 3中的SOP/EOP位由硬件根据实际接收到的数据包情况自动设置和清除。CPU在准备接收缓冲区时,只需要设置好Buffer Pointer和Buffer Length,并将OWNER位置1,然后将其加入队列即可。
5.2 DMA通道初始化与启动流程
- 全局DMA控制寄存器:首先,通过
TCPPICR(TX控制)和RCPPICR(RX控制)寄存器禁用所有DMA通道。 - 通道状态初始化:对每个要使用的通道(0-3),将其对应的
TCPPIDMASTATEW0和RCPPIDMASTATEW0寄存器清零。这个寄存器存放的是DMA队列的头指针(Head Pointer),清零表示队列为空。 - 构建描述符链:在系统内存(通常是DDR)中分配一段对齐的缓存,用于存放描述符数组。为每个通道初始化一个描述符链表。例如,为通道1的TX准备N个描述符,让每个描述符的Word 0指向下一个描述符的地址,最后一个描述符的Word 0指向NULL或自身,并设置
EOQ=1。 - 填充数据缓冲区指针:将每个描述符的Word 1指向实际的数据缓冲区(用于TX)或预留的接收缓冲区(用于RX)。
- 激活描述符:将描述符链中第一个描述符的
OWNER位置1。对于TX,还需要设置好SOP和EOP。 - 启动DMA引擎:
- 将第一个描述符的地址写入对应通道的
TCPPIDMASTATEW0(TX)或RCPPIDMASTATEW0(RX)寄存器。这相当于将队列头指针指向链表的开始。 - 设置对应端点的控制寄存器(
PERI_TXCSR或PERI_RXCSR)中的DMAEN(DMA使能)位为1。 - 最后,使能全局DMA控制器:
USBREGS->TCPPICR = 1;(使能TX DMA)。
- 将第一个描述符的地址写入对应通道的
5.3 DMA传输完成与中断处理
DMA引擎在以下情况会产生中断:
- 队列完成:当处理到一个
EOQ=1的描述符时。 - 描述符完成:每个描述符处理完成后(可选,通过中断使能寄存器配置)。
- 错误:如缓冲区溢出、描述符错误等。
中断服务程序(ISR)关键操作:
- 读取中断状态:检查
TCPPIMSKSR(TX屏蔽状态)或RCPPIMSKSR(RX屏蔽状态)寄存器,确定是哪个通道的什么事件触发了中断。 - 处理完成的数据:
- 对于TX:检查已完成描述符(
OWNER位被DMA清0)的EOP位,确认一个完整的USB传输是否结束。然后可以回收该描述符和对应的数据缓冲区,用于下一次传输。 - 对于RX:检查已完成描述符的
EOP位和Packet Length字段(Word 3的低16位),获取实际接收到的数据长度。然后从描述符指向的缓冲区中读取数据。非常重要:在回收RX描述符并重新将其加入队列前,必须确保已经读取了数据,并且将OWNER位重新置1,同时将Buffer Length字段重置为缓冲区的最大容量。
- 对于TX:检查已完成描述符(
- 更新队列指针:通常,DMA状态寄存器
TCPPIDMASTATEW0/RCPPIDMASTATEW0会自动更新为下一个待处理的描述符地址。但如果队列已空(头指针为0),你需要将新的描述符链的头部地址再次写入该寄存器,以重启DMA。 - 清除中断:向
TCPPIEOIR(TX)或RCPPIEOIR(RX)寄存器写入任意值,以清除当前中断。同时,也需要清除USB通用中断寄存器(EOIR)中的相应位。
避坑指南:DMA描述符的“Ownership”陷阱
OWNER位是CPU和DMA引擎之间的“信号旗”。CPU置1,DMA开始处理;DMA处理完清0。最常见的死锁或数据丢失问题就出在这里:场景一:CPU还没把数据填入缓冲区,就匆忙将描述符OWNER置1并提交给DMA。DMA立即开始发送旧数据或乱码。场景二:RX传输中,DMA完成一次接收后清除了OWNER位,但CPU没有及时读取数据并重新置1OWNER位,导致DMA队列变空,后续的数据包因无处存放而被硬件丢弃。最佳实践:维护两个链表——“空闲描述符链表”和“DMA工作链表”。CPU只操作空闲链表,将准备好的描述符挂到工作链表尾部;ISR只从工作链表头部回收已完成的描述符,放回空闲链表。用这种生产者-消费者模型能有效避免竞争。
6. 中断系统与电源管理
6.1 多层次中断处理
DM35x USB控制器的中断系统分为两层:
- USB核心中断:由
INTRUSB,INTRTX,INTRRX寄存器组管理。它们反映USB协议层事件,如复位、挂起、恢复、传输完成、NAK超时等。你需要通过INTRUSBE,INTRTXE,INTRRXE寄存器使能关心的中断源。 - CPPI DMA中断:由
TCPPIMSKSR/RCPPIMSKSR等寄存器管理。反映DMA传输层事件,如描述符完成、队列完成、错误等。需要通过TCPPIIENSETR/RCPPIENSETR来使能。
中断服务程序(ISR)设计建议:
- 保持简短:ISR中只做最必要的状态读取、标志清除和数据指针移动。将复杂的数据处理(如协议解析)放到主循环或任务中。
- 分层处理:先读取
INTVECTR(中断向量寄存器)或检查INTMASKEDR(被屏蔽的中断源寄存器)快速定位中断源。然后进入对应的子处理程序。 - 及时清除:处理完中断后,务必按照正确的顺序清除中断标志位,通常是先清除具体的事件标志,再向
EOIR寄存器写入结束指令。
6.2 电源管理与唤醒
USB控制器支持挂起(Suspend)和恢复(Resume)状态,这对于电池供电的便携设备至关重要。
- 进入挂起:当总线上无活动超过3ms,硬件会自动进入挂起状态,并产生
SUSPEND中断。在此中断中,软件可以进一步关闭PHY时钟或降低芯片功耗。 - 远程唤醒:作为设备,可以通过软件设置
PERI_CSR0中的Resume位,驱动总线产生一个“K”状态(恢复信号),将主机从挂起中唤醒。 - 会话请求协议:作为OTG设备,可以通过设置
DEVCTL寄存器的HOSTREQ位,请求成为主机。当对方设备进入挂起后,硬件会自动执行HNP(主机协商协议)序列。
电源管理编程注意:在进入低功耗模式前,除了处理挂起,还应妥善保存所有关键的USB寄存器状态(特别是端点FIFO指针和DMA状态),并在唤醒后恢复。同时,要确保VBUS供电的控制逻辑与软件状态机同步,避免在不应供电时提供VBUS。
7. 常见问题排查与调试技巧
7.1 枚举失败问题排查
设备无反应:
- 检查VBUS和接地:用万用表测量物理连接。
- 检查PHY初始化:确认
USB_PHY_CTRL寄存器配置正确,PHY已上电且脱离复位。 - 检查时钟:确认USB参考时钟是否稳定且频率正确。
- 监听总线:使用USB协议分析仪(如Beagle, Ellisys)查看是否有任何数据包。如果看不到任何主机发出的SETUP包,问题可能出在主机端口或物理层。
收到SETUP包但无法回复:
- 检查EP0 FIFO配置:EP0的FIFO是固定的,无需通过
TXFIFOADDR/RXFIFOADDR配置,但需确保其他端点的FIFO分配没有覆盖EP0的默认区域(通常是前64字节)。 - 检查
PERI_CSR0操作:重点检查ServicedSetupEnd和ServicedRxPktRdy的清除操作是否正确,以及TxPktRdy在数据阶段是否及时设置。 - 检查数据触发位:控制传输的数据触发位(Data Toggle)应该由硬件自动管理。但如果操作不当,可能导致主机和设备状态不同步。确保没有手动干预
DATA0/1位。
- 检查EP0 FIFO配置:EP0的FIFO是固定的,无需通过
7.2 DMA传输数据错误或丢失
数据错位或重复:
- 检查描述符对齐:描述符的起始地址必须是16字节对齐的。不对齐会导致不可预知的行为。
- 检查缓冲区指针:确保描述符中
Buffer Pointer指向的物理地址是CPU和DMA控制器都能访问的有效内存(通常是DDR)。虚拟地址需要先转换为物理地址。 - 检查缓存一致性:如果CPU和DMA共享的数据缓冲区位于带缓存(Cache)的内存区域,必须在CPU写入数据后、DMA读取前,执行缓存写回(Cache Writeback);在DMA写入数据后、CPU读取前,执行缓存无效(Cache Invalidate)。忘记缓存操作是导致数据不同步的最常见原因。
DMA中途停止,无法完成传输:
- 检查描述符链:确保链中最后一个描述符的
EOQ位被正确设置,且Next Descriptor Pointer不为悬空指针。 - 检查
OWNER位状态机:使用调试器查看描述符内存,确认DMA是否已正确处理(OWNER清0)以及CPU是否已重新准备(OWNER置1)。 - 检查中断是否被屏蔽:确认DMA中断和USB核心中断已在全局中断控制器和USB本地中断屏蔽寄存器中正确使能。
- 检查描述符链:确保链中最后一个描述符的
7.3 性能优化建议
- FIFO大小与双缓冲:对于高速批量传输,使用双缓冲并设置足够大的FIFO(如1024字节)可以显著提升吞吐量,减少NAK导致的等待。
- 描述符队列深度:不要只准备一个描述符。维护一个包含多个描述符的队列(例如8-16个),让DMA可以持续工作,避免因CPU来不及准备下一个描述符而产生的流水线断流。
- 使用
RNDIS模式:如果传输的是类似网络包的可变长度数据,可以考虑使用RNDIS模式。在此模式下,DMA引擎可以识别短包作为数据帧的结束,自动生成正确的USB短包或ZLP,简化了软件处理逻辑。通过设置RNDISR寄存器来启用。 - 合理使用NAK超时:在主机模式下,对于响应较慢的设备(如某些U盘),适当增加批量端点的
NAKLIMIT值,可以减少不必要的超时错误。
调试这类复杂的片上外设,除了仔细阅读手册,最有效的工具就是逻辑分析仪(抓取总线信号)和芯片的寄存器查看窗口。养成在关键状态切换(如设置TxPktRdy, 进入中断)时打印或记录相关寄存器值的习惯,能帮你快速定位问题所在。DM35x的USB控制器功能强大,一旦调通,其稳定的高性能数据传输能力会让前期的所有投入都变得值得。