1. 项目概述
在嵌入式系统开发中,尤其是涉及音视频处理、数据采集或设备互联的场景,高速、可靠的数据传输通道是项目成败的关键。TMS320DM644x作为一款经典的数字媒体片上系统(DMSoC),其集成的USB 2.0控制器是连接外部世界的重要桥梁。然而,面对动辄数百页的官方用户指南,很多开发者,尤其是初次接触该平台的工程师,往往会感到无从下手:寄存器位域含义晦涩,DMA描述符结构复杂,主机与外设模式下的编程模型差异巨大。我曾在一个视频采集盒项目中,因为对USB控制器的CPPI DMA机制理解不透彻,导致传输过程中频繁丢帧,调试过程苦不堪言。本文将结合我踩过的坑和最终调优的经验,为你系统性地拆解TMS320DM644x USB控制器的架构、两种核心操作模式(主机与外设)的配置流程,以及最核心也最易出错的CPPI DMA编程细节。无论你是希望实现一个高速U盘功能,还是构建一个连接摄像头的视频流主机,理解本文的内容都将帮你绕过那些官方文档中语焉不详的“暗礁”,直接构建稳定高效的USB数据链路。
2. 控制器架构与核心模块解析
TMS320DM644x的USB控制器并非一个简单的串行接口,而是一个集成了协议处理、数据缓冲和直接内存访问的复杂子系统。其设计目标是高效卸载CPU负担,实现接近理论带宽的数据吞吐。
2.1 整体功能框图与数据流
从功能框图上看,控制器核心包含几个关键部分:USB 2.0 PHY、FIFO RAM、包编码/解码引擎、寄存器与中断控制逻辑,以及CPPI DMA引擎。数据流路径清晰:来自USB电缆的差分信号经过PHY转换为并行数据,进入包处理引擎进行CRC校验、PID识别等操作,然后根据传输方向存入或取出对应的端点FIFO。CPU或DMA引擎则通过内部总线与FIFO交互,完成应用层数据的搬移。
这里需要明确一个关键概念:端点(Endpoint)。USB通信是基于管道的,而管道的终点就是端点。DM644x的控制器支持最多4个同时活动的双向端点(EP1-EP4),外加一个必须的控制端点EP0。每个端点都独立拥有自己的FIFO缓冲区、控制状态寄存器(CSR)和DMA通道(如果使能)。这种设计允许开发者并行处理多个数据流,例如,用EP1发送视频数据,用EP2接收控制命令。
2.2 时钟与电源管理
控制器的时钟来源于外部24MHz晶振,内部PLL会将其倍频至工作所需频率。POWER寄存器(地址偏移0x01)的SOFTCONN位至关重要,它控制内部模拟收发器的上电。很多新手会忽略这一步,导致USB设备根本无法被主机识别。正确的上电序列是:先通过外部电路(通常由I2C控制的GPIO)提供VBUS(如果需要),然后向POWER寄存器的SOFTCONN位写1,最后再进行其他配置。
另一个容易出问题的是动态FIFO sizing。控制器内部有一块4KB的共享RAM,作为所有端点的FIFO存储区。其分配是灵活的,通过TXFIFOSZ/RXFIFOSZ和TXFIFOADDR/RXFIFOADDR寄存器来配置每个端点FIFO的大小和起始地址。分配原则是:必须为EP0预留至少64字节的空间(通常从地址0开始),然后其他端点的FIFO地址必须按大小对齐,且不能重叠。一个实用的技巧是,在项目初期规划好各端点所需的最大包尺寸,然后从EP0之后开始,像堆内存一样依次分配,并记录下每个端点的基址和大小,避免后期调整时混乱。
2.3 索引寄存器(INDEX)的作用
这是理解寄存器访问的关键。USB控制器有很多寄存器是端点相关的,例如TXMAXP、PERI_TXCSR等。为了节省地址空间,这些寄存器被设计为“窗口”式访问。你需要先向**INDEX寄存器**(地址偏移0x0E)写入目标端点的编号(0-4),然后后续对端点相关寄存器的读写操作,就会自动映射到该端点对应的上下文上。忘记设置INDEX是导致配置错误的最常见原因之一。我的习惯是,在编写任何端点配置函数时,第一行就是保存旧的INDEX值,中间配置目标端点,最后一行恢复旧的INDEX值,这是一个很好的防御性编程实践。
void configure_endpoint_tx(int ep_num, Uint16 max_packet_size) { Uint8 old_index = usbRegs->INDEX; // 保存当前索引 usbRegs->INDEX = ep_num; // 指向目标端点 usbRegs->TXMAXP = max_packet_size; // 配置该端点的最大包大小 // ... 其他配置 usbRegs->INDEX = old_index; // 恢复索引 }3. 外设模式(Peripheral Mode)操作精解
当DM644x作为USB设备(如U盘、摄像头)连接至PC或其他主机时,它工作在外设模式。此模式下的编程核心是正确响应主机的标准请求,并管理好各端点的数据收发。
3.1 端点0的控制传输处理
端点0是默认的控制端点,用于处理枚举、配置等标准USB请求。其状态由PERI_CSR0寄存器控制。处理控制传输是一个状态机,通常包含SETUP、DATA(可选)、STATUS三个阶段。
SETUP阶段:当主机发起设置请求时,控制器会将8字节的SETUP包数据放入EP0的FIFO,并置位PERI_CSR0的RxPktRdy位。你的中断服务程序(ISR)必须立即读取FIFO数据,解析bmRequestType、bRequest等字段,并决定如何响应。例如,对于GET_DESCRIPTOR请求,你需要将描述符数据准备好。
DATA阶段:如果是IN方向(设备到主机),你需要将数据写入EP0 FIFO,然后设置PERI_CSR0的TxPktRdy位。如果是OUT方向,你需要等待RxPktRdy置位,然后从FIFO读取主机发来的数据。
STATUS阶段:这是最后一个握手包。对于IN事务的状态阶段,主机发一个OUT包(长度为0),你需要确保EP0处于接收就绪状态(RxPktRdy已清除),并在收到这个零长度包后,正确完成事务。一个常见的坑是,在状态阶段后没有正确清理端点状态,导致后续事务失败。
注意:处理EP0事务时,务必遵循USB协议规定的时序。特别是在状态阶段,过早或过晚地操作
TxPktRdy/RxPktRdy都可能导致主机认为设备无响应而重置连接。建议参考官方流程图,严格按步骤编程。
3.2 批量(Bulk)传输端点的配置与使用
对于大数据量传输,如文件读写,我们使用批量端点。以EP1作为批量IN端点(发送数据给主机)为例,配置步骤如下:
- 设置索引:
usbRegs->INDEX = 1; - 配置FIFO:设置
TXFIFOSZ和TXFIFOADDR,分配缓冲区。 - 设置最大包大小:
usbRegs->TXMAXP = 512;// 高速批量端点最大为512字节 - 配置控制状态寄存器:
usbRegs->PERI_TXCSR。关键位:DMAEN:使能DMA。MODE:设为0,表示非同步传输。FRCDATATOG:通常设为0,让硬件自动管理数据切换(DATA0/DATA1)。仅在需要强制同步时使用。FIFONotEmpty和TxPktRdy:由硬件自动管理,在DMA模式下一般无需软件直接操作。
数据发送流程(非DMA模式):
- 检查
PERI_TXCSR的TxPktRdy位是否为0(FIFO空闲)。 - 将数据写入
FIFO1寄存器(它是一个对准FIFO内存的窗口)。 - 如果写入的数据量达到
TXMAXP,或者这是最后一包数据(短包),则设置PERI_TXCSR的TxPktRdy位为1,通知硬件发送此包。 - 硬件发送完成后,会产生中断,此时可以准备下一包数据。
外设模式批量IN传输的PERI_TXCSR关键位操作流程表:
| 操作阶段 | TxPktRdy | FIFONotEmpty | UNDRN(Underrun) | 软件动作 |
|---|---|---|---|---|
| 初始/空闲 | 0 | 0 | 0 | 等待主机IN令牌 |
| 填充FIFO | 0 | 1 | 0 | 向FIFOx写入数据 |
| 数据就绪 | 1 | 1 | 0 | 写TxPktRdy=1,启动发送 |
| 发送完成 | 0 | 0 | 0 | 硬件清零TxPktRdy,产生中断,软件准备下一包 |
| 错误处理 | X | X | 1 | FIFO被主机IN令牌清空但无数据。需清除UNDRN位,重新填充数据。 |
3.3 同步(Isochronous)传输的特殊处理
同步传输用于对时序有严格要求但容错性较高的场景,如音频流。其最大特点是没有握手包,数据发送或接收后,无论对错,都不会重传。
配置同步端点与批量端点类似,但需要设置PERI_TXCSR或PERI_RXCSR的ISO位为1。对于IN端点,一旦设置TxPktRdy,数据将在下一个合适的微帧(对于高速USB)或帧(对于全速)被发送出去,不管主机是否准备好接收。这意味着你必须严格按节奏提供数据,否则会出现欠载(Underrun)或溢出(Overrun)。
一个关键技巧:对于同步IN传输,可以利用双缓冲(Double Buffer)FIFO。在TXFIFOSZ寄存器中设置双缓冲模式后,硬件会为端点分配两倍大小的FIFO空间,并自动在两个缓冲区之间切换。这样,你可以在硬件发送一个缓冲区数据的同时,向另一个缓冲区填充下一帧数据,从而有效避免欠载。
4. 主机模式(Host Mode)操作精解
当DM644x需要连接U盘、鼠标或其他USB设备时,它需要工作在主机模式。主机模式的编程更为复杂,因为你需要扮演“管理者”的角色,发起所有事务,管理设备枚举、驱动加载和数据传输调度。
4.1 主机模式下的端点配置
主机模式下,端点的配置寄存器换成了HOST_前缀的系列,如HOST_TXCSR、HOST_RXCSR、HOST_TXTYPE等。配置一个主机端点的核心是正确设置HOST_TXTYPE或HOST_RXTYPE寄存器。这个寄存器编码了目标设备的速度、端点类型和端点号。
例如,要配置一个与高速、端点1、批量IN端点通信的主机通道,你需要:
usbRegs->INDEX = 1; // 假设使用通道1对应设备端点1 // 设置类型寄存器:速度(HS=01),类型(Bulk=10),端点号(1) // 位[7:6]: 速度 (00=LS, 01=FS, 10=HS, 11=保留) // 位[5:4]: 类型 (00=Control, 01=Iso, 10=Bulk, 11=Int) // 位[3:0]: 端点号 usbRegs->HOST_TXTYPE = (0x01 << 6) | (0x02 << 4) | 0x01;同时,还需要通过TXFUNCADDR寄存器设置目标设备的地址(在枚举成功后获得)。
4.2 控制传输作为主机
主机发起控制传输(用于枚举)的流程,是理解主机编程的绝佳范例。它同样分为SETUP、DATA、STATUS三个阶段,但所有事务的发起方变成了主机。
SETUP阶段:主机向目标设备的EP0发送一个SETUP令牌包,紧跟一个DATA0包(包含8字节请求),然后期望设备返回ACK。
- 软件操作:将SETUP数据包写入
FIFO0,设置HOST_CSR0的SetupPkt位和TxPktRdy位。 - 硬件完成后,会清除
TxPktRdy并产生中断。
- 软件操作:将SETUP数据包写入
DATA阶段(可选):如果是IN方向(设备返回数据,如描述符),主机发送IN令牌。
- 软件操作:等待
HOST_CSR0的RxPktRdy置位,然后从FIFO0读取数据。 - 如果是OUT方向,主机发送OUT令牌和DATAx包。
- 软件操作:将数据写入
FIFO0,设置TxPktRdy。
- 软件操作:等待
STATUS阶段:主机发送一个相反方向的零长度包来确认整个事务。
- 例如,如果DATA阶段是IN,则STATUS阶段主机发OUT(零长度);反之亦然。
- 软件需要根据方向,相应地等待
RxPktRdy或设置TxPktRdy(对于零长度包)。
主机模式控制传输状态机要点:HOST_CSR0的ReqPkt位非常有用。在SETUP阶段结束后,如果你设置了ReqPkt,硬件会自动根据HOST_CSR0的DATAEND位状态,发起DATA或STATUS阶段的事务,简化了编程。你需要仔细规划每个阶段后DATAEND位的设置。
4.3 处理NAK和超时
在主机模式下,设备可能因为忙而无法立即响应数据阶段(IN或OUT),此时它会返回NAK握手包。主机控制器会自动重试。HOST_NAKLIMIT0寄存器(对于控制端点)或HOST_TXINTERVAL/HOST_RXINTERVAL(对于其他端点)定义了在放弃并产生错误中断前,主机连续接收NAK的次数(或帧数)。
对于批量传输,通常设置为一个较大的值(如0x10),给予设备充分的响应时间。对于中断或同步传输,这个值定义了轮询间隔。设置不当会导致性能下降(轮询太慢)或不必要的错误中断(超时太快)。
5. CPPI DMA引擎深度剖析与编程实战
这是提升USB吞吐性能、降低CPU负载的核心。CPPI(Communications Port Programming Interface)是TI的一种DMA描述符协议。USB控制器内的CPPI DMA引擎可以自动从系统内存中读取描述符链表,并根据描述符内容搬运数据到/从端点FIFO,整个过程无需CPU干预。
5.1 DMA描述符结构解析
CPPI描述符是一个由4个32位字(Word)组成的结构体,在内存中必须16字节对齐。这是硬性规定,不对齐会导致不可预知的行为。
发送(TX)描述符(Word 0 - Word 3):
- Word 0: Next Descriptor Pointer (NDP):指向下一个描述符的物理地址。如果这是链表中的最后一个描述符,则设置为
NULL(0)。 - Word 1: Buffer Pointer:指向待发送数据缓冲区的物理地址。
- Word 2: Buffer Offset & Length:
[31:16]: Buffer Offset。通常为0。[15:0]: Buffer Length。本次传输的数据字节数(0-65535)。
- Word 3: Packet Length & Flags:
[31] SOP (Start of Packet): 置1表示这是一个数据包的开始。[30] EOP (End of Packet): 置1表示这是一个数据包的结束。对于USB传输,一个描述符通常既是SOP也是EOP,除非你手动分割大包。[29] OWNER: 必须由软件置1,表示描述符由DMA引擎所有。DMA完成后会清零。[28] EOQ (End of Queue): 置1表示这是描述符队列的末尾。通常与EOP一起设置。[23] ZERO_BYTE: 如果Buffer Length为0但需要发送一个零长度包(ZLP),则将此位置1,并将[15:0]置为1。[19] RX_ABORT: 仅用于接收描述符。[15:0] Packet Length: 应与Word 2的Buffer Length一致。
接收(RX)描述符:结构与TX类似,但Word 3的Flags用法不同。OWNER位同样重要,Buffer Length字段定义了为接收数据预留的缓冲区大小。
5.2 DMA通道的初始化与启动流程
以配置EP1为批量IN端点并使用DMA发送为例:
全局DMA控制寄存器初始化:
usbRegs->TCPPICR = 0; // 先禁用TX DMA usbRegs->RCPPICR = 0; // 先禁用RX DMA // 清零所有通道的DMA状态字,这是一个重要的复位操作 for(int i=0; i<4; i++) { usbRegs->CHANNEL[i].TCPPIDMASTATEW0 = 0; // ... 清零其他状态字 }构建描述符链表:在内存中(如
DATA_SECTION指定的区域)创建描述符数组。例如,要发送一个1024字节的数据,可以创建一个描述符,其Buffer Pointer指向数据区,Buffer Length为1024,并设置SOP、EOP、OWNER、EOQ标志。启动DMA传输:
- 将第一个描述符的地址写入对应通道的
TCPPIDMASTATEW0寄存器(对于TX)或RCPPIDMASTATEW1寄存器(对于RX)。注意:TX是STATEW0,RX是STATEW1,这个差异很容易搞错。 - 对于RX DMA,还需要向
RBUFCNTx寄存器(x为通道号)写入描述符链表中描述符的数量。这个寄存器是累加的,每次添加新的描述符链表都需要增加计数值。 - 设置端点控制寄存器的DMA使能位(
PERI_TXCSR.DMAEN或PERI_RXCSR.DMAEN)。 - 最后,置位
TCPPICR或RCPPICR的使能位,启动DMA引擎。
- 将第一个描述符的地址写入对应通道的
传输完成与回收:DMA引擎处理完一个描述符后,会清除其OWNER位,并可能产生中断。在中断服务程序中,软件可以检查
TCPPICOMPPTR或RCPPICOMPPTR寄存器,它指向最后一个已完成的描述符。然后,软件可以回收这些描述符及其关联的数据缓冲区,用于下一次传输。务必在回收后,将描述符的OWNER位置1,并重新将其加入DMA队列,否则DMA会在处理完当前队列后停止。
5.3 RNDIS模式与零长度包处理
RNDIS是微软的远程网络驱动接口规范,常用于USB网络适配器。其特点是协议数据单元(PDU)可能非常小,并且用一个短包(包括零长度包)来指示一个PDU的结束。
CPPI DMA的RNDIS模式就是为优化这种场景设计的。当使能RNDIS模式(通过RNDISR寄存器)后,对于TX方向,DMA引擎在遇到一个长度小于端点最大包大小(TXMAXP)的包时,会自动将其标记为短包发送,并可能提前结束当前DMA描述符的处理。这简化了协议栈的实现,无需软件判断何时发送短包。
零长度包(ZLP)的处理:在USB批量传输中,当数据长度恰好是端点最大包大小的整数倍时,主机需要一个ZLP来标识传输结束。在CPPI DMA中,发送ZLP需要特殊处理:将描述符的Buffer Length设为0,同时将Word 3的ZERO_BYTE位置1,并将Packet Length字段设为1。这是一个硬件要求的特殊约定。
6. 中断与错误处理实战指南
USB控制器有丰富的中断源,合理配置中断是构建稳定驱动的基础。中断主要分为三类:USB核心中断(INTRUSB)、发送端点中断(INTRTX)和接收端点中断(INTRRX)。
6.1 中断寄存器层次与使能
中断处理采用“源-掩码-状态”三层模型:
- 中断源寄存器(
INTSRCR):只读,显示所有原始中断事件。 - 中断掩码寄存器(
INTMSKR):可读写,用于使能或禁用特定的中断源。 - 已屏蔽中断状态寄存器(
INTMASKEDR):只读,显示被当前掩码允许的中断状态。通常ISR读取此寄存器来判断中断原因。
此外,每个端点(0-4)在INTRTX和INTRRX寄存器中也有自己的中断位,用于指示端点特定事件,如发送完成、接收就绪等。这些端点中断也有对应的使能寄存器(INTRTXE,INTRRXE)。
初始化中断的典型步骤:
// 1. 清除所有未决中断 usbRegs->INTCLRR = usbRegs->INTSRCR; // 2. 配置中断掩码,例如使能USB复位、挂起、DMA完成等中断 usbRegs->INTMSKR = USB_INT_RESET | USB_INT_SUSPEND | USB_INT_DMA_CMPLT; // 3. 使能端点中断,例如使能EP1的发送完成中断 usbRegs->INDEX = 1; usbRegs->INTRTXE |= USB_INTRTX_EP1_TX; // 假设USB_INTRTX_EP1_TX是EP1 TX中断的位掩码 // 4. 在系统级使能USB控制器的中断向量6.2 常见中断处理场景
- USB Reset中断:主机发起了总线复位。这是设备枚举的开始。ISR中需要重置所有端点的状态(特别是
FADDR寄存器要清零),重新配置FIFO,并准备好响应SETUP包。 - Suspend/Resume中断:总线进入或退出挂起状态。在挂起时,设备应进入低功耗模式;恢复时,需重新同步内部状态。
- DMA完成中断:检查
TCPPIMSKSR或RCPPIMSKSR寄存器,确定是哪个通道的DMA完成。然后读取TCPPICOMPPTR或RCPPICOMPPTR,遍历完成描述符链表,进行缓冲区回收和后续处理。 - 端点发送完成中断(
INTRTX):对于非DMA模式,表示一包数据已成功发送,可以准备下一包。对于DMA模式,通常依赖DMA完成中断,但端点中断可能用于指示FIFO空等状态。 - 端点接收就绪中断(
INTRRX):表示有数据包到达FIFO,需要软件或DMA去读取。
6.3 错误排查与调试技巧
USB调试往往伴随着“设备无法识别”、“传输卡住”、“数据错误”等问题。以下是我总结的排查清单:
- 物理层检查:确保VBUS供电正常(如果是设备),DP/DM线连接正确,并测量是否有正确的差分信号。使用USB协议分析仪是终极手段。
- 寄存器状态检查:
POWER寄存器的SOFTCONN位是否置1?FADDR在枚举后是否被正确设置?- 目标端点的
TXMAXP/RXMAXP是否与主机/设备端匹配? PERI_TXCSR/HOST_TXCSR中的错误位(如UNDRN,OVRN,NAKTO,STALL)是否被置位?一旦置位,需要软件写1清除,并重新初始化端点状态。
- FIFO配置检查:计算分配的FIFO地址是否重叠?EP0是否预留了空间?双缓冲模式下,总FIFO大小是否超出4KB?
- DMA描述符检查:
- 描述符地址是否16字节对齐?
OWNER位在提交给DMA前是否置1?Buffer Pointer指向的物理地址是否有效?缓存一致性是否处理(对于带Cache的系统,需确保数据已写回内存或使用非缓存内存)?- 对于TX,
SOP/EOP/EOQ标志是否正确设置?
- 中断处理检查:是否所有必要的中断都已使能?ISR是否及时清除了中断源(通过写
INTCLRR或EOIR)?是否发生了中断嵌套或丢失? - 时序问题:在主机模式下,发起事务的间隔是否符合USB规范?在外设模式下,响应主机请求是否超时?对于同步传输,数据供给速率是否稳定?
一个实用的调试方法:在项目初期,可以先用轮询(Polling)模式而非中断模式实现最基本的控制传输(如获取设备描述符)。这能排除中断服务程序复杂性的干扰,确认底层寄存器操作和FIFO访问是正确的。待基础通信稳定后,再切换到中断和DMA模式以提升性能。
7. 性能优化与高级配置
当基本功能实现后,下一步就是榨干USB 2.0 High-Speed的480 Mbps带宽。这需要多方面的精细调优。
7.1 FIFO大小与双缓冲策略
FIFO是数据在系统内存和USB串行链路之间的缓冲。FIFO大小直接影响突发传输能力和对DMA延迟的容忍度。
- 批量传输:通常使用较大的FIFO(如512或1024字节),以便DMA能一次性搬运更多数据,减少总线仲裁和DMA启动开销。对于高速批量端点,最大包是512字节,因此FIFO大小至少应为512字节,设置为1024字节(双包缓冲)效果更佳。
- 同步传输:必须使用双缓冲。FIFO大小应至少能容纳一帧音频/视频数据。计算依据是:采样率 × 位深 × 通道数 × (1秒/帧率)。例如,48kHz, 16-bit立体声音频,一毫秒的数据量是
48000 * 2 * 2 / 1000 = 192字节。那么分配一个256字节的双缓冲FIFO是合适的。 - 中断传输:数据量小,但对延迟敏感。FIFO可以设置得小一些(如64字节),但同样推荐使用双缓冲以确保不会因为软件延迟而丢失数据。
7.2 DMA描述符队列深度与分散/聚集
CPPI DMA支持描述符链表,这意味着你可以提前构建一个包含多个缓冲区的描述符队列并提交给DMA。增加队列深度可以更好地隐藏软件处理延迟,实现持续的流水线操作。
- 对于TX(发送):可以预先构建好多个描述符,每个指向一帧或一块数据。当DMA在处理当前描述符时,软件可以准备后续的数据并链接新的描述符。关键在于管理好
EOQ标志和TCPPIDMASTATEW0(队列头指针)的更新时机,避免DMA引擎跑到链表末尾空转。 - 对于RX(接收):更为重要。你必须始终为DMA准备好足够多的空接收描述符。如果RX描述符队列耗尽,DMA会停止,导致后续数据丢失。一个稳健的做法是:在RX DMA完成中断中,不仅回收已满的描述符,还立即补充新的空描述符到队列尾部,并更新
RBUFCNTx寄存器。
分散/聚集(Scatter/Gather):CPPI描述符天然支持此功能。你可以构建一个描述符,其Next Descriptor Pointer指向另一个描述符,而这两个描述符的Buffer Pointer可以指向物理上不连续的内存块。这对于处理分散的音频/视频帧缓冲区或网络数据包非常有用。
7.3 总线利用率与NAK限流
在主机模式下,当设备返回NAK时,主机会不断重试。HOST_NAKLIMIT0(对控制端点)或HOST_TXINTERVAL/HOST_RXINTERVAL(对其他端点)控制着重试次数。设置过大,会在设备真正故障时浪费总线时间;设置过小,可能因设备临时繁忙而误判为错误。
- 批量传输:可以设置较大的NAK限制(如16-32),因为批量传输重在可靠性,允许一定的等待。
- 中断/同步传输:其间隔(
TXINTERVAL/RXINTERVAL)是以微帧(125us)或帧(1ms)为单位的。这个值必须根据端点描述符中bInterval字段来设置。设置不正确会导致主机轮询过快或过慢,影响实时性或浪费带宽。
7.4 缓存一致性问题
在带有数据缓存(Cache)的系统中(如DM644x的ARM核),CPU和DMA对同一块内存的访问必须考虑缓存一致性。如果CPU写了数据到缓存但未写回内存(Write-Back),DMA从内存读取的就是旧数据。反之,如果DMA将数据写入内存,CPU缓存中的可能是旧数据。
解决方案:
- 使用非缓存内存:最简单可靠的方法。在链接器命令文件(.cmd)中定义一段非缓存的内存区域(如
NCACHE),将DMA描述符和数据缓冲区都放在这个区域。#pragma DATA_SECTION(dma_buffer, ".NCACHE") Uint8 dma_buffer[BUFFER_SIZE]; - 手动维护缓存一致性:如果必须使用缓存内存,则在启动DMA传输前,对于TX,需要调用
Cache_wbInv或Cache_wb函数将数据从缓存写回内存;对于RX,在DMA完成后,需要调用Cache_inv函数使缓存中对应区域失效,迫使CPU从内存重新读取DMA写入的新数据。TI的CSL库或RTSC提供了相应的API。
忽略缓存一致性是导致DMA传输数据错误的最隐蔽原因之一,现象往往是数据时对时错,极难排查。