1. 项目概述
搞嵌入式开发,尤其是工业控制、电机驱动这些领域,TI的C2000系列DSP是绕不开的经典平台。最近在做一个基于TMS320F2807x的项目,需要实现一个USB主机功能,用来连接和读取一些标准的人机接口设备(HID),比如键盘、条码枪。本以为有现成的库函数调用就完事了,结果发现官方驱动库在某些复杂事务处理上封装得比较深,出了问题不好排查。于是,我不得不一头扎进技术参考手册,把USB主机控制器的寄存器级操作逻辑彻底捋了一遍。
这个过程让我意识到,很多关于USB主机开发的资料要么太理论,只讲协议;要么太浅显,只给API。真正从芯片寄存器视角,把“主机如何发起一次IN请求”、“如何应对设备的NAK响应”、“如何调度不同端点的传输”这些细节讲透的内容并不多。而这恰恰是我们在调试USB通信不稳定、数据丢包、设备枚举失败等问题时最需要的知识。因此,我决定结合TMS320F2807x的USB模块,写一篇关于USB主机控制器原理与寄存器配置的详解。目标很明确:不谈空泛的协议,只聚焦于如何通过配置那一堆名字相似的寄存器,让这个微控制器稳稳地扮演好“主机”的角色,搞定数据收发。无论你是刚开始接触C2000的USB,还是在调试中遇到了棘手的通信问题,希望这篇基于实战的梳理能给你带来实实在在的帮助。
2. USB主机控制器核心原理与TMS320F2807x架构
2.1 USB通信的基本模型:端点与管道
在开始摆弄寄存器之前,我们必须统一语言,理解USB通信的核心模型。你可以把USB设备(比如一个U盘)想象成一个有很多“信箱”(端点)的公寓楼。每个端点(Endpoint)都有一个唯一的地址和方向,比如端点1-IN、端点2-OUT。主机(我们的F2807x)则是邮差,它通过“管道”(Pipe)与这些特定的信箱建立连接,进行数据投递或收取。
TMS320F2807x的USB控制器支持最多16个双向端点(Endpoint 0 - 15)。Endpoint 0是特殊的控制端点,用于设备枚举、配置等标准请求,其FIFO大小固定为64字节。其他端点(1-15)则可以灵活配置为控制、中断或批量传输类型,其FIFO区域需要我们从总共4KB的USB FIFO RAM中手动划分。理解这一点至关重要,因为后续所有对USBTXMAXPn(最大发送数据包大小)和USBRXMAXPn(最大接收数据包大小)寄存器的配置,以及FIFO地址的分配,都基于这个“端点即信箱”的模型。
2.2 TMS320F2807x USB模块的“双模式”与总线视角
F2807x的USB控制器是一个双角色控制器(DRD),既能作设备(Device),也能作主机(Host)。我们本文聚焦于主机模式。当它作为主机时,它就成为了USB总线上的“老大”,负责发起所有通信、提供总线时钟(SOF包)、管理电源(VBUS),并协调多个设备(通过集线器)的访问。
从硬件连接上看,控制器通过USB0DP和USB0DP信号线连接到物理接口。但这里有一个关键点:USB主机需要为下游设备提供5V电源(VBUS)。F2807x芯片本身并不直接输出VBUS,而是通过一个GPIO引脚(USB0EPEN)来控制外部电源电路的通断。同时,另一个引脚(USB0PFLT)用于监测电源故障。在硬件设计上,务必确保上电初期USB0EPEN为无效状态,避免在控制器未初始化时就向总线供电,这可能损坏设备或控制器。
2.3 关键寄存器组概览:控制、状态与配置
面对手册里长达数页的寄存器列表(从USBFADDR到USBRXINTERVAL15),新手很容易发懵。我们可以将其分为几大类来理解:
- 全局控制与状态寄存器:例如
USBPOWER(电源管理)、USBDEVCTL(设备控制,用于主机模式下的会话管理)、USBIS/USBIE(中断汇总)。它们是控制整个USB模块开关、复位、中断的“总闸”。 - 端点控制与状态寄存器:这是核心中的核心,数量最多。每个端点都对应一组寄存器,例如对于端点n:
USBTXCSRLn/USBTXCSRHn:发送控制与状态(低/高字节)。USBRXCSRLn/USBRXCSRHn:接收控制与状态(低/高字节)。USBTXMAXPn/USBRXMAXPn:设置该端点发送/接收的最大数据包大小。USBTXTYPEn/USBRXTYPEn:配置该端点的传输类型(控制、中断、批量)和目标设备速度、地址。USBTXINTERVALn/USBRXINTERVALn:配置中断端点的轮询间隔。
- FIFO访问寄存器:
USBFIFO0-USBFIFO15。这些寄存器实际上是一个窗口,通过它们读写对应端点的FIFO缓冲区数据。这里有一个重要陷阱:F2807x的CPU是16位总线,而USB控制器内部是8位内存空间。通过__byte()intrinsic进行8位访问是安全的,但如果你在CCS的Memory Browser里以16位或32位视图查看这些地址,会看到数据被重复显示,这属于地址/数据总线桥接带来的视图假象,并非错误,编程时需以8位访问为准。 - DMA与辅助寄存器:如
USBRQPKTCOUNTn(块传输请求包计数)、USBDMASEL等。需要注意的是,F2807x的USB模块不支持硬件DMA触发信号,但你可以通过软件触发DMA控制器来搬运FIFO数据,这能极大减轻CPU负担。
3. 主机模式下的核心事务处理流程与寄存器配置
理解了架构,我们进入最实战的部分:主机如何发起并完成一次数据交易(Transaction)。这是所有USB数据通信的基石。
3.1 IN事务:主机从设备读取数据
IN事务,即主机向设备索要数据。流程和关键寄存器操作如下:
发起请求:主机软件需要先设置目标接收端点(比如EP1-IN)对应的
USBCSRL0寄存器(对于端点0)或USBRXCSRLn寄存器中的REQPKT位。这个动作相当于告诉USB控制器的“事务调度器”:“我需要在端点n上发起一个IN请求”。对于非零端点,通常使用USBRXCSRLn寄存器。调度与令牌发送:事务调度器看到
REQPKT被置位,会在合适的时机(遵循USB帧结构和调度规则,见后文)在总线上发出一个IN令牌包(Token Packet),其中包含目标设备的地址和端点号。数据接收与就绪:目标设备收到IN令牌后,如果数据准备就绪,会发回一个数据包。USB控制器将此数据包存入对应端点的接收FIFO,然后自动置位
USBRXCSRLn寄存器中的RXRDY位,并可能产生接收端点中断。读取数据与确认:主机软件检测到
RXRDY位为1,就知道FIFO里有数据了,于是通过读取USBFIFOn寄存器来卸载(Unload)数据。数据读取完毕后,必须手动清除RXRDY位,以告知控制器本包数据处理完毕。此时,控制器会自动向设备发送一个ACK握手包,完成本次事务。
关键技巧:自动控制位的妙用手册中提到了两个非常实用的位:
AUTOCL和AUTORQ(位于USBRXCSRHn寄存器)。
AUTOCL(Auto Clear):如果设置此位,当从FIFO中卸载的数据包长度等于USBRXMAXPn中设置的最大包长时,控制器会自动清除RXRDY位。这适用于数据量固定且已知的情况,可以省去一次手动清除操作。AUTORQ(Auto Request):如果设置此位,当RXRDY位被清除时,控制器会自动重新置位REQPKT位,从而发起下一次IN请求。这在连续流数据传输(如从摄像头读取视频流)时极其有用,可以实现“乒乓”操作,只要FIFO一空,下一个请求立即发出,最大化总线利用率。
- 块传输计数:对于需要传输大量数据的批量(Bulk)传输,你可以使用
USBRQPKTCOUNTn寄存器。在发起传输前,将需要传输的数据包总数写入该寄存器。控制器每完成一次IN事务并清除RXRDY后,会自动将该计数值减1。当计数值减到0时,控制器会自动清除AUTORQ位,停止自动请求。如果传输长度未知,则应将USBRQPKTCOUNTn清零,并依靠接收到的“短包”(数据长度小于最大包长)来判断传输结束,此时AUTORQ位也会被清除。
3.2 OUT事务:主机向设备发送数据
OUT事务,即主机向设备发送数据。其逻辑与IN事务对称:
- 数据加载:主机软件将待发送的数据通过
USBFIFOn寄存器写入发送FIFO。 - 就绪标志:每加载完一个数据包到FIFO,必须手动置位
USBTXCSRLn寄存器中的TXRDY位。这告诉事务调度器:“端点n的发送FIFO已就绪,可以发起OUT事务了”。 - 自动设置:同样,可以设置
USBTXCSRHn寄存器中的AUTOSET位。当写入FIFO的数据量达到USBTXMAXPn设置的最大包长时,控制器会自动置位TXRDY,简化编程。 - 调度与发送:事务调度器在总线空闲时,发出OUT令牌包,紧接着将FIFO中的数据包发出。
- 设备响应:设备收到数据后,会回复一个握手包(ACK表示成功接收,NAK表示暂时无法接收,STALL表示端点故障)。
- 完成处理:主机收到ACK后,会清除
TXRDY位,表示本次事务完成,FIFO空间可被复用。如果使能了AUTOSET,在下一个最大长度数据包写入后,TXRDY会自动再次置位。
3.3 错误处理与重试机制
USB通信并非总是坦途,主机必须能妥善处理设备的异常响应。
- NAK处理:NAK(Negative Acknowledge)是设备说“我忙,现在没空处理”。主机控制器在收到NAK后,不会立即认为失败,而是会根据
USBNAKLMT寄存器中设定的NAK重试限制,在后续的调度周期中不断重试该事务。这对于中断和批量传输是标准行为,确保了在设备临时繁忙时的数据可靠性。 - STALL处理:STALL是设备报告一个功能或协议错误,通常是端点被挂起(Halted)。主机控制器一旦收到STALL握手包,便会停止重试该事务,并设置
USBCSRL0(对于控制端点)或USBTXCSRLn/USBRXCSRLn中的STALLED位,同时产生中断。软件必须介入,分析STALL原因(通过标准请求清除端点STALL),并在解决问题后手动清除STALLED位和重新配置端点,才能恢复通信。 - 超时与错误:如果设备在预定时间内无响应,或数据包出现CRC校验、位填充错误,主机控制器会重试该事务。通常,在连续3次尝试失败后,控制器会放弃,清除
REQPKT或TXRDY位,并设置ERROR位,产生错误中断。软件需要在此中断服务程序中处理超时,例如重置端点或上报错误。
4. 事务调度、电源管理与连接枚举
4.1 事务调度器:总线交通警察
USB主机控制器内部有一个智能的“事务调度器”,它自动管理不同端点、不同类型事务的总线访问顺序,开发者无需手动干预令牌发送时机。但其调度策略需要我们理解,以便合理配置。
- 帧与微帧:全速USB以1ms为单位划分成“帧”(Frame),每帧起始由主机发送一个SOF(Start of Frame)包。调度器在每个帧内安排事务。
- 调度优先级:
- 周期性传输(中断、同步):在每帧开始时,调度器首先处理所有已配置的中断端点事务。
USBTXINTERVALn/USBRXINTERVALn寄存器决定了该端点每N帧被轮询一次(1-255)。这是保证中断传输实时性的关键。 - 非周期性传输(控制、批量):处理完周期性事务后,调度器才处理控制和批量事务。控制传输有最高优先级,但通常只在枚举和配置阶段使用。批量传输则利用帧内的剩余时间进行。
- 周期性传输(中断、同步):在每帧开始时,调度器首先处理所有已配置的中断端点事务。
- 公平性与防饿死:调度器采用“轮询”机制。如果一个批量端点因设备频繁回复NAK而需要重试,调度器不会一直卡在这个端点上,而是会先检查并服务其他端点的待处理事务,然后再回来重试,这防止了某个慢速设备阻塞整个总线。
4.2 电源、复位与挂起管理
作为主机,F2807x需要管理总线的电源状态。
- 启动与供电:在初始化完成后,需要设置
USBPOWER寄存器中的相应位来使能USB PHY,并通过GPIO控制USB0EPEN引脚为高,激活外部5V电源电路,为VBUS供电。 - 复位设备:在检测到设备连接后(通过连接中断),主机必须发起总线复位。通过设置
USBPOWER寄存器的RESET位至少20ms,然后在清除该位,即可产生标准的USB复位信号,使设备进入默认状态,准备开始枚举。 - 挂起与恢复:为了节能,当总线空闲时,主机可以进入挂起(SUSPEND)状态。设置
USBPOWER寄存器的SUSPEND位,控制器会在完成当前事务后停止调度器和帧计数器,不再发送SOF包。要恢复,需设置RESUME位并清除SUSPEND位,控制器会发出20ms的恢复信号(K状态),然后恢复正常操作。设备也可以通过远程唤醒信号来请求主机退出挂起。
4.3 设备连接、枚举与端点动态绑定
主机模式的初始化和设备枚举是一个标准但繁琐的过程。
- 开始会话:设置
USBDEVCTL寄存器的SESSION位,使控制器开始检测设备连接。 - 检测连接:当设备插入,DP/DM线上电平变化会被检测到,产生连接中断。此时,可以读取
USBDEVCTL寄存器的FSDEV或LSDEV位来判断连接的是全速还是低速设备。 - 总线复位:如4.2所述,对设备进行复位。
- 枚举与配置:这是最复杂的部分,需要通过控制传输(默认使用端点0)与设备进行一系列标准请求(Descriptor请求、Set Address、Set Configuration等)的交互。这个过程需要软件实现一个完整的控制传输状态机。
- 端点配置与绑定:枚举过程中,主机会获取设备的描述符,得知设备有哪些接口和端点。然后,主机软件需要动态地将F2807x内部的端点资源(EP1-EP15)与设备上的物理端点进行绑定。这是通过配置
USBTXTYPEn/USBRXTYPEn寄存器实现的,你需要设置目标设备的地址、端点号、传输类型和速度。对于通过USB Hub连接的设备,还需要配置USBTXHUBADDRn/USBTXHUBPORTn(或接收对应寄存器)来指定Hub地址和端口号。
重要心得:端点配置的时机务必在任何进行中的事务完成之后,才能修改一个端点的类型、地址或Hub关联寄存器。否则可能导致总线出现不可预知的行为。一个稳妥的做法是,在更改配置前,先检查对应端点的
TXRDY或RXRDY位是否已清除,并确保没有未完成的中断。
5. 关键寄存器配置详解与编程示例
理论讲完了,我们来看具体怎么配。以下配置均假设使用CPU1,USB模块基地址为0x0004_0000。
5.1 基础初始化与时钟配置
任何操作前,必须开启模块时钟并配置引脚。
// 1. 使能USB外设时钟 (假设使用PCLKCR11) EALLOW; SysCtrlRegs.PCLKCR11.bit.USB0_A_ENCLK = 1; // 使能USB模块时钟 EDIS; // 2. 配置USB引脚功能 (GPIO42/USB0DM, GPIO43/USB0DP) EALLOW; GpioCtrlRegs.GPBPUD.bit.GPIO42 = 0; // 使能上拉 GpioCtrlRegs.GPBPUD.bit.GPIO43 = 0; GpioCtrlRegs.GPBAMSEL.bit.GPIO42 = 1; // 将GPIO42/43配置为USB0功能 GpioCtrlRegs.GPBAMSEL.bit.GPIO43 = 1; GpioCtrlRegs.GPBMUX1.bit.GPIO42 = 3; // 选择USB0DM功能 GpioCtrlRegs.GPBMUX1.bit.GPIO43 = 3; // 选择USB0DP功能 EDIS; // 3. 配置辅助PLL,为USB模块提供60MHz时钟 (具体配置依赖系统时钟设置,此处略) // 通常需要配置PLL、分频器等,请参考系统控制章节。5.2 配置一个批量OUT端点(以EP1为例)
假设我们要通过EP1向一个地址为1的设备发送批量数据,设备端点是EP1-OUT,最大包长64字节。
#include “DSP280x_Device.h” // 包含寄存器定义头文件 // 定义USB寄存器结构体指针(通常TI的DriverLib已提供,此处为示例) volatile struct USB_REGS *UsbRegs = (volatile struct USB_REGS *)0x00040000; void ConfigBulkOutEndpoint1(void) { // 1. 选择要配置的端点索引 (EP1) UsbRegs->USBEPIDX.bit.EPIDX = 1; // 2. 配置端点类型:设备地址=1,端点号=1,传输类型=批量(Bulk),目标速度=全速(Full) // USBTXTYPE1 寄存器 // BIT[6:4]: SPEED (0=保留, 1=全速, 2=低速) // BIT[3]: PROTO (0=控制, 1=其他) // BIT[2:0]: TEP (目标端点号) UsbRegs->USBTXTYPE1.bit.SPEED = 1; // 全速 UsbRegs->USBTXTYPE1.bit.PROTO = 1; // 非控制端点 UsbRegs->USBTXTYPE1.bit.TEP = 1; // 目标设备端点号 EP1 // 注意:设备地址(1)通常不在TXTYPE寄存器设置,可能在FADDR或功能地址寄存器,此处简化。 // 3. 配置最大数据包大小 (64字节) UsbRegs->USBTXMAXP1.bit.MAXLOAD = 64; // 4. 配置发送控制寄存器 (USBTXCSRL1) // 先确保端点未被挂起或出错 UsbRegs->USBTXCSRL1.bit.CLRDT = 1; // 清除数据触发位 (用于控制传输同步,批量传输通常也需管理) UsbRegs->USBTXCSRL1.bit.FLUSH = 0; // 非必要不刷新FIFO // 使能自动设置TXRDY (当写入最大包长数据时自动置位) UsbRegs->USBTXCSRH1.bit.AUTOSET = 1; // 5. (可选)配置FIFO起始地址和大小 // 需要根据整体4KB FIFO RAM进行规划。假设为EP1-OUT分配128字节缓冲区。 // USBTXFIFOSZ 和 USBTXFIFOADD 寄存器的配置通常在初始化所有端点时统一计算设置。 }5.3 执行一次批量OUT传输
配置好后,如何发送一包数据?
void SendBulkDataToEP1(uint16_t *data, uint16_t length) { volatile uint8_t *fifo_ptr; uint16_t i; // 1. 确保端点索引正确 UsbRegs->USBEPIDX.bit.EPIDX = 1; // 2. 检查FIFO是否就绪 (TXRDY应为0,表示FIFO空闲可写) while(UsbRegs->USBTXCSRL1.bit.TXRDY == 1) { // 等待上一包数据发送完成或超时处理 } // 3. 将数据写入FIFO (必须使用8位访问) fifo_ptr = (volatile uint8_t *)&(UsbRegs->USBFIFO1); for(i = 0; i < length; i++) { // 注意:data是uint16_t数组,但USB是8位传输,需要分高低字节写入 // 这里假设data数组里已经是按顺序排列的字节流 __byte((int *)fifo_ptr, 0) = ((uint8_t*)data)[i]; // 使用__byte intrinsic进行8位写 } // 4. 如果写入的数据长度等于USBTXMAXP1设置的最大包长(64), // 且AUTOSET=1,则TXRDY会被硬件自动置位,事务会自动发起。 // 如果写入的数据小于最大包长(短包,表示传输结束),则需要手动置位TXRDY if (length > 0) { if (length < 64) { // 发送短包,手动启动 UsbRegs->USBTXCSRL1.bit.TXRDY = 1; } // 如果length==64,AUTOSET已自动处理 } }5.4 配置一个批量IN端点并处理数据(以EP2为例)
从设备读取数据,我们配置EP2-IN。
void ConfigBulkInEndpoint2(void) { UsbRegs->USBEPIDX.bit.EPIDX = 2; // 配置接收类型寄存器 (USBRXTYPE2) UsbRegs->USBRXTYPE2.bit.SPEED = 1; // 全速 UsbRegs->USBRXTYPE2.bit.PROTO = 1; // 非控制端点 UsbRegs->USBRXTYPE2.bit.TEP = 2; // 目标设备端点号 EP2-IN // 配置最大接收包大小 UsbRegs->USBRXMAXP2.bit.MAXLOAD = 64; // 配置接收控制寄存器 (USBRXCSRL2, USBRXCSRH2) // 使能自动清除RXRDY (当读取最大包长数据后自动清除) UsbRegs->USBRXCSRH2.bit.AUTOCL = 1; // 使能自动请求下一包 (当RXRDY清除后自动置位REQPKT) UsbRegs->USBRXCSRH2.bit.AUTORQ = 1; // 对于已知长度的多包传输,可以设置USBRQPKTCOUNT2 // UsbRegs->USBRQPKTCOUNT2 = total_packet_count; // 发起第一次数据请求 UsbRegs->USBRXCSRL2.bit.REQPKT = 1; }在中断服务程序或主循环轮询中处理接收到的数据:
void HandleBulkInDataEP2(void) { uint16_t byte_count, i; volatile uint8_t *fifo_ptr; uint8_t buffer[64]; UsbRegs->USBEPIDX.bit.EPIDX = 2; // 检查是否有数据就绪 if (UsbRegs->USBRXCSRL2.bit.RXRDY) { // 1. 读取本包数据的字节数 byte_count = UsbRegs->USBRXCOUNT2.bit.RXCOUNT; // 2. 从FIFO读取数据 fifo_ptr = (volatile uint8_t *)&(UsbRegs->USBFIFO2); for(i = 0; i < byte_count; i++) { buffer[i] = __byte((int *)fifo_ptr, 0); // 使用__byte intrinsic进行8位读 } // 3. 数据读取完毕后,RXRDY位会被自动清除(因为AUTOCL=1且包长为64) // 同时,由于AUTORQ=1,REQPKT会被自动置位,请求下一包数据。 // 如果收到的是短包(byte_count < 64),AUTORQ会被硬件清除,传输结束。 // 4. 处理接收到的数据 buffer[0..byte_count-1] ProcessReceivedData(buffer, byte_count); // 5. 检查是否出错 if (UsbRegs->USBRXCSRL2.bit.ERROR) { // 处理错误:超时、CRC错误等 UsbRegs->USBRXCSRL2.bit.ERROR = 1; // 写1清除错误标志 // 可能需要重新初始化该端点 } if (UsbRegs->USBRXCSRL2.bit.STALLED) { // 端点被设备挂起,需要清除STALL UsbRegs->USBRXCSRL2.bit.STALLED = 1; // 写1清除STALL标志 // 通常需要发送标准请求 CLEAR_FEATURE(ENDPOINT_HALT) 到设备 } } }6. 常见问题排查与调试心得
在实际调试中,你会遇到各种各样的问题。以下是我踩过的一些坑和总结的排查思路。
6.1 设备无法枚举或连接不稳定
- 检查电源和复位:这是最常见的问题。用示波器测量VBUS电压是否稳定在5V,并在设备插入时观察DP/DM线是否有复位信号(SE0状态持续20ms)。确保
USB0EPEN引脚控制逻辑正确。 - 确认速度匹配:在
USBTYPE0/USBTXTYPEn/USBRXTYPEn寄存器中配置的设备速度(全速/低速)必须与实际连接的设备一致。低速设备需要额外的上拉电阻配置。 - 检查描述符请求:使用USB分析仪(如Beagle USB Protocol Analyzer)或软件工具,捕获枚举过程中的控制传输。查看主机发出的Get Descriptor请求,以及设备的回复是否正确。很多问题出在设备描述符、配置描述符的内容或长度不符合规范。
- 端点0配置:Endpoint 0是控制端点,其FIFO固定为64字节。确保在枚举阶段,你的控制传输状态机能够正确处理Setup、Data、Status三个阶段,并正确管理
USBCSRL0中的SETUPEND、DATAEND、RXSTALL等状态位。
6.2 数据传输丢包或错误
- FIFO溢出或下溢:这是数据丢失的主要原因。确保你的软件处理数据的速度跟得上总线速度。对于IN传输,要及时读取FIFO数据并清除
RXRDY;对于OUT传输,要在下一次数据准备好之前及时填充FIFO并设置TXRDY。使用双缓冲(Double Buffering)可以显著改善性能,通过设置USBTXCSRHn.bit.DMAEN和USBRXCSRHn.bit.DMAEN(虽然无硬件触发,但模式可用)并结��软件DMA搬运。 - NAK超时:如果设备长时间回复NAK,主机会不断重试。检查
USBNAKLMT寄存器的设置,避免重试次数过多导致通信卡死。对于实时性要求不高的批量传输,可以设置一个较大的值或0(无限重试);对于需要及时响应的,可以设置较小值并在超时后报告错误。 - 总线Babble错误:如果总线上在帧结束时仍有活动,控制器会产生Babble中断。这通常意味着设备端出现了严重故障,持续驱动总线。检查设备端的固件或硬件。
6.3 中断不触发或处理不当
- 中断使能未打开:除了使能每个端点的发送/接收中断(
USBTXIEn,USBRXIEn),还必须使能USB全局中断(USBIE寄存器),并在PIE向量表中正确配置USB中断服务例程。 - 中断标志未清除:在中断服务程序(ISR)中,必须读取并清除相应的中断标志位。对于端点中断,是清除
USBTXCSRLn或USBRXCSRLn中的特定位(如TXRDY,RXRDY);对于全局中断,需要读取USBIS寄存器并清除对应的位。标志位不清除会导致中断持续触发,系统卡死。 - 中断服务程序过长:USB中断可能比较频繁,尤其是全速中断传输(1ms间隔)。ISR应尽可能短小,只做必要的标志处理和数据搬运,将复杂的处理放到主循环中。避免在ISR内进行大量计算或阻塞操作。
6.4 关于DMA使用的特别说明
手册明确提到,F2807x的USB模块不支持硬件DMA触发信号。这意味着你无法像使用其他外设(如SPI、ADC)那样,配置DMA在USB FIFO就绪时自动触发传输。但是,你仍然可以使用DMA控制器,只是触发方式需要改为“软件触发”或“周期触发”。一种常见的做法是:
- 在USB接收中断中,仅设置一个标志位并清除中断。
- 在主循环或一个高优先级的后台任务中,检查该标志位。
- 当标志位有效时,手动启动一次DMA传输,将数据从USB FIFO搬运到指定的内存区域。这虽然不如硬件触发高效,但依然能大幅降低CPU在数据搬运上的开销。
调试USB主机是一个需要耐心和细致观察的过程。善用开发板的LED、串口打印状态信息,结合逻辑分析仪或专用的USB协议分析仪观察总线上的实际数据流,是定位问题的终极武器。从最基本的端点0枚举成功,到稳定高效的数据传输,每一步都需要你对寄存器行为和USB协议有清晰的理解。希望这篇结合了TMS320F2807x具体寄存器操作的解析,能成为你攻克嵌入式USB主机开发难题的一块坚实垫脚石。