瑞萨RA8D2 USBHS中断与FIFO管理实战解析
1. USBHS中断与FIFO管理:从硬件信号到软件响应的全链路解析
搞嵌入式USB设备开发,尤其是用像瑞萨RA8D2这类带USBHS(USB 2.0 High-Speed Module)模块的MCU,最让人头疼又必须吃透的就是中断和FIFO管理。手册上那些NRDY、BEMP、BRDY中断,还有一堆状态标志位,初看简直像天书。但说白了,它们就是硬件在替你“喊话”:数据来了、缓冲区满了、设备没准备好……你的软件能不能及时、正确地“接话”,决定了整个USB通信的成败。今天我就结合手册和实际踩坑经验,把USBHS的中断机制和FIFO缓冲区管理掰开揉碎了讲,重点聚焦在设备控制器模式下,那些手册里一笔带过但实际开发中频频出错的细节。
为什么非得搞懂这些?因为USB通信是“主从式”的,主机(比如你的电脑)永远掌握发起通信的主动权,设备只能被动响应。中断就是设备向MCU内核“求救”或“汇报”的快速通道。而FIFO缓冲区,则是数据在高速USB总线和相对低速的MCU内存之间关键的“中转站”。管理不好,轻则数据丢失、传输卡顿,重则直接导致枚举失败、设备无法识别。下面,我们就从最核心的中断事件开始。
2. 核心中断机制深度剖析与实战应对
USBHS模块的中断类型不少,但最核心、与数据传输直接相关的就是NRDY(Not Ready)和BEMP(Buffer Empty)中断。理解它们的触发条件、硬件行为以及软件该如何响应,是编写稳定USB设备驱动的基础。
2.1 NRDY中断:设备“未就绪”的精准报告
NRDY中断,顾名思义,是设备告诉主机“我现在还没准备好收发数据”。这在USB通信中是一种流控机制,避免主机在设备忙不过来时盲目发送数据导致丢失。
2.1.1 NRDY中断的触发条件与场景
根据手册,NRDY中断主要在以下几种情况下由硬件自动产生:
- 对于同步传输管道:在一个帧间隔内没有成功接收到令牌包。当SOF帧起始包到达时,USBHS会生成NRDY中断请求,并将
NRDYSTS.PIPENRDY标志位置1。同步传输没有握手包,所以用NRDY来及时通知主机设备侧缓冲区的状态。 - 对于批量传输和中断传输管道:当管道被设置为
BUF响应模式(PID[1:0]=01b),且FIFO缓冲区状态不满足数据传输条件时。- 发送方向(IN事务):主机发来IN令牌,请求设备发送数据,但设备的FIFO缓冲区里没有数据可发(缓冲区空)。此时设备会先回复NAK握手包,同时硬件可能产生NRDY中断,通知软件“该喂数据了”。
- 接收方向(OUT事务):主机发来OUT令牌和数据包,但设备的FIFO缓冲区已满,没有空间接收新数据。设备回复NAK,并可能触发NRDY中断,通知软件“快把数据读走”。
手册中的图37.6清晰地展示了设备控制器模式下NRDY中断产生的时序。关键在于,NRDY中断的产生与PIPENRDY标志位置1是同步的,但该标志位仅在管道响应PID设置为BUF时才有效。这意味着,如果你将管道配置为NAK或STALL响应,即使硬件条件满足,也不会置位这个标志和触发中断。
2.1.2 软件处理NRDY中断的实战流程
当你的中断服务程序检测到INTSTS0.NRDY标志位为1时,应该按以下步骤处理:
- 查询中断源:立即读取
NRDYSTS寄存器,检查是哪个管道(PIPENRDY[n])触发了中断。一个NRDY中断可能对应多个管道标志位置位。 - 分析原因并采取行动:
- 如果是发送管道:说明缓冲区空了,软件需要尽快准备下一包数据,并通过FIFO端口写入到对应的管道缓冲区中。写入完成后,缓冲区的“未就绪”状态解除,硬件会在下次主机发起IN令牌时正常发送数据。
- 如果是接收管道:说明缓冲区已满或没有有效空间。软件需要从FIFO端口读取数据,释放缓冲区空间。对于双缓冲模式,需要检查是哪个缓冲区满了。
- 清除中断标志:向
NRDYSTS.PIPENRDY[n]位写入1来清除该管道的NRDY状态标志。务必注意:要先处理中断原因(如读写数据),再清除标志位,否则可能立即再次进入中断。最后清除INTSTS0.NRDY主中断标志。
踩坑记录:NRDY中断风暴早期调试时,我曾遇到系统被NRDY中断卡死的问题。原因是我的发送任务优先级较低,当NRDY中断产生后,软件未能及时向FIFO填充数据。主机持续发送IN令牌,设备持续回复NAK并触发NRDY中断,形成了“中断风暴”。解决方案是:在中断服务程序中,仅设置一个“数据待发送”的软件标志,然后快速退出中断。在更低优先级的主循环或任务中,检查这个标志并执行耗时的数据准备和FIFO写入操作。这保证了中断响应速度,也避免了在中断内进行复杂操作。
2.2 BEMP中断:缓冲区“空”的状态通告
BEMP中断与NRDY有些关联但侧重点不同。BEMP更直接地告诉你:一个管道的数据发送动作已经完成,缓冲区变空了。
2.2.1 BEMP中断的触发条件与细微差别
手册对BEMP中断的描述分发送和接收方向:
- 对于发送管道:当一次传输完成(包括零长度包传输),且该管道的FIFO缓冲区变为空时,会产生一个内部的BEMP中断请求。在单缓冲模式下,这个中断请求与BRDY中断是同时产生的。但是,在以下情况下不会产生BEMP中断:
- 在双缓冲模式下,当CPU或DMA已经开始向另一个缓冲区写入数据时。
- 当通过设置
PIPEnCTR.ACLRM或CFIFOCTR.BCLR位来手动清空缓冲区时。 - 在设备控制器模式下,控制传输的状态阶段执行IN传输(发送零长度包)时。
- 对于接收管道:当成功接收到的数据包大小超过了该管道设定的最大包大小时,USBHS会产生BEMP中断,并将
BEMPSTS.PIPEBEMP标志位置1。这是一个错误情况!硬件会丢弃接收到的数据,并将该管道的响应PID设置为STALL。在设备模式下,会向主机返回STALL握手包。
2.2.2 BEMP中断的应用场景与处理
对于发送管道,BEMP中断是一个非常有用的“发送完成”通知。特别是当你需要精确控制数据包的发送节奏,或者需要在发送完一包数据后立即准备下一包时。处理流程如下:
- 中断服务程序检查
BEMPSTS寄存器,确定是哪个发送管道触发了中断。 - 这通常意味着上一包数据已经成功发送并被主机确认。你可以选择:
- 如果采用“乒乓缓冲”策略,可以开始向刚刚变空的缓冲区填充下一包数据。
- 如果发送任务结束,可以关闭管道或切换其响应模式。
- 清除对应的
BEMPSTS.PIPEBEMP标志位和INTSTS0.BEMP标志位。
对于接收管道因包超长触发的BEMP中断,这属于错误处理流程。你需要:
- 识别到这是接收管道的BEMP中断(结合管道方向判断)。
- 将管道PID设置为
NAK或进行其他错误恢复操作(可能需要主机重新枚举)。 - 同样,清除中断标志。
经验之谈:BEMP vs BRDY很多新手会混淆BEMP和BRDY。简单来说:
- BRDY (Buffer Ready):缓冲区就绪。对于发送,意味着你可以向FIFO写数据了(缓冲区有空位);对于接收,意味着有数据可读了(缓冲区有数据)。它标志着数据传输动作可以开始。
- BEMP (Buffer Empty):缓冲区空。对于发送,意味着数据已经全部送走,缓冲区完全空闲;对于接收(异常情况),意味着发生了超长包错误。它标志着一次数据传输动作的彻底结束。 在单缓冲发送模式下,数据写入FIFO(可能触发BRDY处理),然后数据被发送,发送完成后缓冲区变空,此时BEMP和BRDY中断会同时产生。在双缓冲模式下,你可以利用BRDY中断来填充第二个缓冲区,而利用BEMP中断来知晓第一个缓冲区的数据已完全发送完毕,从而实现流水线操作,最大化总线利用率。
2.3 其他关键中断速览
除了NRDY和BEMP,USBHS还有其他一些关键中断,它们共同构成了完整的状态监控网络:
- 设备状态转换中断 (DVST):在设备控制器模式下,当设备状态(如上电、默认、地址、配置、挂起)发生变化时触发。通过
DVSTCTR0.DVST标志位和PL1CTRL.DVSQ状态位,软件可以精确知道设备当前处于USB规范的哪个状态,这对于实现标准的设备枚举流程至关重要。 - 控制传输阶段转换中断 (CTRT):仅在设备控制器模式下有效。它标志着控制传输(Setup、Data、Status阶段)的推进。通过
INTSTS0.CTSQ可以查询当前处于哪个阶段。这对于实现USB设备请求(如获取描述符、设置地址、设置配置)的解析和处理是核心机制。手册中特别提到了各种控制传输序列错误的条件,例如在数据阶段收到了不该有的令牌包,这些都会触发CTRT中断且CTSQ进入错误状态(110b)。 - 帧起始包中断 (SOFR):在设备模式下,每收到一个SOF包(全速模式下每1ms一次)就会触发。它可以用于实现基于帧的时间同步,例如在同步传输中安排数据。
- 连接检测中断 (ATTCH/DTCH/BCHG):
ATTCH用于主机模式检测设备连接,DTCH用于检测设备断开,BCHG用于检测总线状态变化(如远程唤醒)。这些是实现即插即用功能的基础。 - VBUS与过流中断 (VBUS/OVRCR):用于检测电源状态和过流保护,对于自供电设备或需要电源管理的场景很重要。
3. FIFO缓冲区:数据吞吐的咽喉要道
如果说中断是神经系统,那FIFO缓冲区就是消化系统。所有进出USB总线的数据都要经过这里。USBHS的FIFO是一个共享的存储区域,通过精密的硬件逻辑映射到各个管道。
3.1 缓冲区配置与核心寄存器
每个管道(Pipe)的FIFO行为都由一组寄存器控制,理解它们是进行高效数据管理的前提:
| 寄存器名 | 核心位域 | 功能描述 | 配置要点与避坑指南 |
|---|---|---|---|
| PIPECFG | TYPE[1:0] | 设置管道传输类型:控制、批量、中断、同步。 | DCP固定为控制传输。Pipe1-2可设为批量或同步,Pipe3-5为批量,Pipe6-9为中断。类型决定了管道的特性和可配置项,例如只有Pipe1-2支持双缓冲和同步传输。 |
DIR | 传输方向:0为OUT(设备接收),1为IN(设备发送)。 | 必须与端点描述符中的方向一致。一个端点地址(EPNUM)+方向(DIR)的组合必须是唯一的。 | |
EPNUM[3:0] | 端点号(1-15)。DCP固定为0。 | 对应USB端点描述符中的端点地址(不包括方向位)。 | |
DBLB | 双缓冲选择。 | 性能关键配置。仅Pipe1-5可用。开启后,硬件自动管理两个缓冲区,实现“乒乓”操作,可隐藏软件处理延迟,显著提升连续传输吞吐量。 | |
BFRE | BRDY中断模式选择。 | 如果开启,每当缓冲区就绪(可读或可写)时都会产生BRDY中断。否则,需要软件查询缓冲区状态。高吞吐量场景建议开启。 | |
SHTNAK | 传输结束时自动禁用管道(设为NAK)。 | 仅用于批量传输。当收到短包或达到事务计数时,硬件自动将PID设为NAK,停止响应主机。适用于需要明确传输边界的情况。 | |
| PIPEBUF | BUFSIZE | 分配给该管道的FIFO缓冲区大小。 | DCP固定256字节,Pipe1-5最大可设2KB,Pipe6-9固定64字节。大小必须是最大包大小的整数倍,且总分配量不能超过USBHS模块的总FIFO内存。 |
BUFNMB | 分配的缓冲区起始编号。 | 硬件自动或手动分配,用于在共享FIFO内存中划定该管道的专属区域。需要仔细规划,避免冲突。 | |
| PIPEMAXP | MXPS | 该管道支持的最大数据包大小。 | 必须严格匹配USB描述符中定义的wMaxPacketSize。高速批量传输通常为512字节,全速中断传输为64字节。设置错误会导致通信失败或BEMP错误中断。 |
| PIPEnCTR | PID[1:0] | 管道响应PID:00b=NAK,01b=BUF,1xb=STALL。 | 软件控制管道状态的开关。初始化为NAK,准备就绪后设为BUF开始传输,出错时可设为STALL。手册强调,修改管道配置前,必须先将PID设为NAK。 |
ACLRM | 自动缓冲区清除模式。 | 设为1时,硬件自动丢弃所有接收到的数据包(但仍回复ACK)。用于快速清空接收缓冲区,仅在接收方向有效。 | |
SQCLR/SQSET | 清除/设置数据PID序列位(DATA0/DATA1)。 | 用于手动控制数据同步。在控制传输的状态阶段或处理ClearFeature请求后,可能需要软件干预序列位。 | |
BSTS | 缓冲区状态(CPU侧)。 | 0=忙(不可访问),1=就绪(可访问)。是软件判断能否对FIFO进行读写的主要依据。 | |
INBUFM | IN缓冲区监控(SIE侧,仅发送管道有效)。 | 0=传输完成,无数据待发;1=FIFO端口已写入数据,有待发数据。在双缓冲模式下,结合BSTS和INBUFM可以精确掌握两个缓冲区的状态。 |
3.2 双缓冲模式:提升吞吐量的利器
对于Pipe1-5,可以启用双缓冲模式(PIPECFG.DBLB=1)。这是实现高性能USB传输的关键。
3.2.1 双缓冲工作原理硬件为管道分配两个物理缓冲区:Buffer A和Buffer B。当SIE(串行接口引擎)正在使用Buffer A与USB总线进行数据交换时,CPU可以同时向Buffer B读写数据,反之亦然。这样就实现了数据传输和软件处理的并行化。
3.2.2 双缓冲下的中断与状态管理在双缓冲模式下,中断和状态位的解读变得稍微复杂:
- 发送方向(IN):
BSTS位反映的是CPU侧的缓冲区状态。当CPU写完一个缓冲区(如Buffer A)后,BSTS可能变为1(就绪),但这不代表数据已发送。数据要等到SIE取走并发送。INBUFM位反映的是SIE侧的缓冲区状态。为1表示SIE侧有数据等待发送。- BEMP中断:在双缓冲模式下,当一个缓冲区完全变空(数据已发送)时就会触发。这意味着你可能在
INBUFM=1(另一个缓冲区有数据)时收到BEMP中断,通知你刚刚有一个缓冲区空闲出来了。
- 接收方向(OUT):
- 主机数据先被SIE接收到一个缓冲区(如Buffer A)。
- 当Buffer A满或收到短包时,产生BRDY中断,通知CPU来读取。同时,SIE可以开始将后续数据接收到Buffer B。
- CPU读取Buffer A的数据,并清除中断。如果Buffer B也满了,会再次产生BRDY中断。
3.2.3 双缓冲配置实战步骤
- 规划缓冲区大小:
BUFSIZE设置的是单个缓冲区的大小。例如,对于高速批量传输(最大包512字节),如果你希望每个缓冲区能存4个包,就设置为2048字节。但要确保总FIFO内存够用。 - 启用双缓冲:设置
PIPECFG.DBLB=1。 - 处理中断:在BRDY中断中,读取已满的缓冲区数据;在BEMP中断中,得知一个发送缓冲区已空,可以填充新数据。
- 状态查询:在复杂的流控中,除了依赖中断,主循环中也可以查询
BSTS和INBUFM来辅助决策。
避坑指南:双缓冲下的“饥饿”与“溢出”
- 发送饥饿:如果CPU填充数据的速度跟不上SIE发送的速度,两个缓冲区会先后变空,导致主机频繁收到NAK,传输速率下降。需要优化数据准备逻辑,或使用DMA来加速数据搬运。
- 接收溢出:如果CPU处理数据的速度跟不上SIE接收的速度,两个缓冲区会先后被填满。此时后续的OUT事务会触发NRDY(缓冲区满)或更糟,如果主机不顾NAK持续发送,可能引发未定义行为。解决方案是提升CPU处理优先级,或者使用更大的缓冲区来增加时间裕量,但最根本的是保证消费速率大于生产速率。
3.3 FIFO端口访问与缓冲区清除
CPU通过特定的FIFO端口寄存器(如CFIFO,D0FIFO,D1FIFO)来读写数据。访问前,必须通过CFIFOSEL,D0FIFOSEL,D1FIFOSEL寄存器选择要操作的管道。
3.3.1 写入数据(发送)
- 通过
CFIFOSEL.ISEL或DnFIFOSEL选择目标发送管道。 - 等待该管道的
BSTS位变为1(表示CPU侧缓冲区可写)。 - 向
CFIFO或DnFIFO寄存器连续写入数据。写入的数据量可以小于最大包大小。 - 关键一步:写入完成后,必须设置
CFIFOCTR.BVAL=1或DnFIFOCTR.BVAL=1。这个操作被称为“定界”,它告诉USBHS:我这包数据写完了,可以发送了。即使你写的数据量达到了最大包大小,也建议显式设置BVAL,这是一个好习惯。 - 如果要发送零长度包(ZLP),则不需要写数据,直接设置
BCLR=1清空缓冲区,然后立即设置BVAL=1即可。
3.3.2 读取数据(接收)
- 发生BRDY中断后,在中断服务程序中或根据查询,通过
DnFIFOSEL选择触发中断的接收管道。 - 读取
DnFIFOCTR.DTLN寄存器,获取本次接收到的数据长度。 - 如果
DTLN > 0,从DnFIFO寄存器连续读取DTLN字节的数据。 - 读取完成后,硬件会自动准备接收下一包数据。如果
DTLN == 0,表示收到了一个零长度包。此时不能进行读操作,必须通过设置DnFIFOCTR.BCLR=1来清除缓冲区状态。
3.3.3 缓冲区清除的三种方式
- 手动清除 (BCLR):向
CFIFOCTR.BCLR或DnFIFOCTR.BCLR写1。这是最直接的清除方式,会立即清空当前选定管道的FIFO缓冲区。常用于处理零长度包或错误恢复。 - 自动清除模式 (DCLRM):设置
DnFIFOSEL.DCLRM=1。在此模式下,当软件从FIFO端口读取完指定管道的数据后,硬件会自动清除该缓冲区的状态。简化了软件流程,但要注意它只在上一次读操作完成后生效。 - 自动缓冲区清除模式 (ACLRM):设置
PIPEnCTR.ACLRM=1。这是一个强大的功能,但也是陷阱。在此模式下,硬件会自动丢弃所有发送给该管道的数据包,但同时仍会向主机回复ACK握手包。这相当于一个“数据黑洞”。它只在接收方向有效,常用于需要快速清空接收队列而不处理数据的场景。重要警告:手册指出,将ACLRM位先置1再清0,会无条件清除该管道的FIFO缓冲区,且这之间需要至少100ns的访问间隔以供硬件处理。
4. 管道控制与高级功能解析
管道是USBHS进行数据传输的逻辑通道,其控制是驱动程序的中心。
4.1 管道配置与状态切换流程
修改管道配置(如传输类型、端点号、缓冲区大小)不是随时都能进行的。手册37.3.7.1节给出了严格的流程,违反会导致不可预知的行为。
正确的管道信息修改流程(当USB通信已启用,即PID=BUF时):
- 请求变更:软件决定要修改某个管道的配置。
- 设为NAK:将该管道的
PIPEnCTR.PID[1:0]设置为00b(NAK响应)。这告诉硬件,暂停这个管道上的事务。 - 等待空闲:
- (仅主机模式)等待该管道的
PIPEnCTR.CSSTS位变为0。 - 等待该管道的
PIPEnCTR.PBUSY位变为0。PBUSY=1表示硬件正在处理该管道的事务。
- (仅主机模式)等待该管道的
- 执行变更:此时,可以安全地修改
PIPECFG,PIPEBUF,PIPEMAXP,PIPEPERI等配置寄存器。 - 恢复通信:重新将
PID[1:0]设置为01b(BUF响应),启用管道。
绝对禁止在PID=BUF时修改的寄存器:包括上述配置寄存器,以及SQCLR/SQSET、ATREPM、ACLRM等控制位。此外,在CFIFOSEL.CURPIPE等寄存器指向某个管道时,也不能修改该管道的配置。
4.2 事务计数器与SHTNAK功能
对于Pipe1-5的接收方向,USBHS提供了硬件事务计数器功能,由PIPEnTRN(设定值)和一个内部当前计数器实现。
4.2.1 工作原理
- 软件在
PIPEnTRN中设置期望接收的事务次数(比如3次OUT事务)。 - 每成功完成一次OUT事务(收到数据并回复ACK),内部当前计数器加1。
- 当内部计数器值达到
PIPEnTRN的设定值时,如果PIPECFG.SHTNAK=1,则硬件自动将该管道的PID设置为NAK,停止接收更多数据。 - 软件可以通过读取
PIPEnTRN(当TRENB=0时)获取设定值,或(当TRENB=1时)获取当前计数值。 - 通过设置
PIPEnTRE.TRCLR=1可以清零内部当前计数器,重新开始计数。
4.2.2 应用场景与限制这个功能非常适用于需要接收固定数量数据包的场景,例如通过批量传输接收一个已知长度的文件。SHTNAK功能确保在收到足够数据后,管道自动“关闭”,避免主机发送多余数据。限制:
- 仅适用于批量传输。
- 仅适用于接收方向。
- 在事务计数进行中(
PID=BUF)或缓冲区仍有数据时,不能清除当前计数器(TRCLR操作无效)。
4.3 自动响应模式
这是针对批量传输管道(1-5)的一个特殊模式,通过设置PIPEnCTR.ATREPM=1启用。它分为两种子模式:
- OUT-NAK模式:针对批量OUT管道。启用后,设备会对所有OUT令牌自动回复NAK,并产生NRDY中断。这有什么用?想象一个场景:设备正在忙于处理之前接收到的海量数据,无法及时清空FIFO接收新数据。与其让主机不断重试(浪费总线时间),不如直接进入OUT-NAK模式,告诉主机“别发了,我正忙”。等设备处理完,再退出此模式,恢复正常接收。切换此模式必须在管道
PID=NAK时进行。 - 空自动响应模式:针对批量IN管道。启用后,设备会持续向主机发送零长度包。这又有什么用?在某些协议中,设备可能需要通过持续发送“空包”来维持连接或表示某种状态,而无需CPU干预。同样,切换此模式前必须确保缓冲区为空(
INBUFM=0),且切换过程中不能向FIFO写数据。
5. 开发实战:从零构建一个USB批量传输驱动
理论说了这么多,我们来看一个具体的例子:如何在RA8D2上实现一个高速批量OUT端点(接收数据)。
5.1 硬件与软件初始化
- 时钟与引脚配置:使能USBHS模块的时钟,配置相关的USB_DP, USB_DM引脚功能为USBHS。
- 模块全局初始化:设置
SYSCFG相关寄存器,选择设备控制器模式,使能USBHS。 - 等待电源稳定:监控
INTSTS0.VBSTS标志,等待VBUS有效。
5.2 管道配置(以Pipe1为例)
假设我们要配置一个高速批量OUT端点,端点地址为0x01(方向OUT),最大包大小为512字节。
// 1. 首先确保管道禁用 USBHS.PIPE1CTR.PID = 0; // 设为NAK // 2. 等待管道空闲 (PBUSY=0) while(USBHS.PIPE1CTR.PBUSY != 0); // 3. 配置管道参数 USBHS.PIPE1CFG.BIT.TYPE = 1; // 批量传输 USBHS.PIPE1CFG.BIT.DIR = 0; // OUT方向 USBHS.PIPE1CFG.BIT.EPNUM = 1; // 端点号1 USBHS.PIPE1CFG.BIT.DBLB = 1; // 启用双缓冲,提升性能 USBHS.PIPE1CFG.BIT.BFRE = 1; // 启用BRDY中断 USBHS.PIPE1CFG.BIT.SHTNAK = 0; // 我们先不启用自动NAK // 4. 配置缓冲区:分配1024字节(2*512),使用缓冲区区域0x8起始 USBHS.PIPE1BUF.BIT.BUFSIZE = 4; // 1024字节对应的编码,需查手册 USBHS.PIPE1BUF.BIT.BUFNMB = 0x08; // 缓冲区编号 // 5. 配置最大包大小 USBHS.PIPE1MAXP.BIT.MXPS = 512; // 高速批量传输最大512字节 // 6. 使能管道中断 USBHS.BRDYENB.BIT.PIPE1B = 1; // 使能Pipe1的BRDY中断 USBHS.NRDYENB.BIT.PIPE1B = 1; // 使能Pipe1的NRDY中断 // BEMP中断对于OUT管道通常用于错误处理,可根据需要开启 // 7. 最后,激活管道 USBHS.PIPE1CTR.PID = 1; // 设为BUF,准备接收数据5.3 中断服务程序处理
在USBHS的全局中断服务程序中,需要先读取INTSTS0判断中断源,然后分发处理。
void USBHS_IRQHandler(void) { uint16_t intsts0 = USBHS.INTSTS0.WORD; // 处理BRDY中断 if(intsts0 & USBHS_INTSTS0_BRDY_MASK) { uint16_t brdysts = USBHS.BRDYSTS.WORD; USBHS.BRDYSTS.WORD = brdysts; // 写1清标志 if(brdysts & USBHS_BRDYSTS_PIPE1B_MASK) { // Pipe1的BRDY中断,表示有数据可读 handle_pipe1_out_data(); } // 处理其他管道的BRDY... USBHS.INTSTS0.BIT.BRDY = 0; // 清除主中断标志 } // 处理NRDY中断 if(intsts0 & USBHS_INTSTS0_NRDY_MASK) { uint16_t nrdysts = USBHS.NRDYSTS.WORD; USBHS.NRDYSTS.WORD = nrdysts; // 写1清标志 // 通常NRDY表示缓冲区满,需要检查数据读取是否及时 // 可以设置一个标志,在主循环中加快处理速度 USBHS.INTSTS0.BIT.NRDY = 0; } // 处理其他中断... }5.4 数据接收处理函数
handle_pipe1_out_data函数是核心:
static uint8_t g_rx_buf[2][512]; // 双缓冲对应的软件缓冲区 static int g_current_buf_idx = 0; void handle_pipe1_out_data(void) { // 1. 选择要读取的管道和FIFO端口(例如D0FIFO) USBHS.D0FIFOSEL.WORD = (1 << USBHS_D0FIFOSEL_CURPIPE_POS); // 选择Pipe1 // 2. 等待选择生效(硬件要求) while(USBHS.D0FIFOSEL.BIT.CURPIPE != 1); // 3. 获取接收数据长度 uint16_t data_len = USBHS.D0FIFOCTR.BIT.DTLN; if(data_len > 0 && data_len <= 512) { // 4. 从FIFO端口读取数据 volatile uint32_t *fifo_reg = (volatile uint32_t*)&USBHS.D0FIFO.WORD; uint32_t *p_buf = (uint32_t*)g_rx_buf[g_current_buf_idx]; int len_words = (data_len + 3) / 4; // 按32位字读取 for(int i = 0; i < len_words; i++) { p_buf[i] = *fifo_reg; } // 5. 数据读取完成后,硬件会自动准备下一次接收。 // 切换软件缓冲区索引,用于下一次接收 g_current_buf_idx ^= 1; // 6. 通知应用程序有新数据到达(例如置位标志、发送消息队列等) g_new_data_flag = 1; g_current_data_len = data_len; } else if(data_len == 0) { // 收到零长度包,需要清除缓冲区 USBHS.D0FIFOCTR.BIT.BCLR = 1; } else { // 数据长度异常,错误处理 USBHS.PIPE1CTR.PID = 2; // 设为STALL // ... 其他错误恢复逻辑 } // 7. 取消FIFO端口选择(可选,但建议) USBHS.D0FIFOSEL.WORD = 0; }5.5 常见问题排查速查表
在实际开发中,你会遇到各种各样的问题。下面这个表格总结了一些典型症状和排查思路:
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 设备无法枚举 | 1. 端点0(DCP)配置错误。 2. 描述符不正确。 3. 电源/VBUS问题。 4. 时钟未正确配置。 | 1. 检查DCP的MXPS(高速应为64)。2. 使用USB分析仪抓取总线数据,对比描述符。 3. 检查 INTSTS0.VBSTS和VBUS中断。4. 确认USBHS时钟源和分频设置正确。 |
| 批量传输中途卡住,主机报错 | 1. 数据PID序列错误。 2. 未及时响应BRDY/NRDY中断,导致缓冲区溢出/欠载。 3. 双缓冲管理逻辑错误。 | 1. 检查SQMON位,确认DATA0/DATA1交替。在控制传输状态阶段后或错误时,用SQSET/SQCLR手动同步。2. 优化中断处理,确保及时读写FIFO。检查中断是否被屏蔽或优先级过低。 3. 在双缓冲模式下,仔细跟踪 BSTS和INBUFM,确保读写的是正确的缓冲区。 |
| 只能传输一次数据,后续失败 | 1. 传输完成后管道PID被错误地设为NAK或STALL。 2. SHTNAK功能被误启用,在短包或事务计数结束后自动禁用了管道。3. 数据未正确“定界”(未设置 BVAL)。 | 1. 在传输回调函数中,确认完成处理后管道PID仍为BUF。 2. 检查 PIPECFG.SHTNAK位,如果不需自动停止,应设为0。3. 发送数据后,确认设置了 BVAL位。 |
| 传输速度远低于理论值 | 1. 未使用双缓冲。 2. 中断处理或数据搬运耗时太长。 3. 主机端驱动或应用程序限制。 | 1. 对Pipe1-5的批量传输,务必启用DBLB。2. 使用DMA进行FIFO数据搬运,大幅降低CPU开销。将耗时的数据处理移出中断服务程序。 3. 在主机端使用合适的API(如libusb的异步传输)和较大的缓冲区。 |
| 收到超长包错误(BEMP中断) | 1. 主机发送的数据包大于端点声明的wMaxPacketSize。2. PIPEMAXP.MXPS寄存器设置与描述符不一致。 | 1. 检查主机端应用程序,确保发送数据包不超过最大包大小。 2. 核对 PIPEMAXP寄存器设置与USB设备描述符中的wMaxPacketSize完全一致。 |
| 系统进入异常中断循环 | 1. 中断标志未正确清除。 2. 在中断服务程序中进行了耗时操作,导致错过其他中断或响应不及时。 | 1.严格按照“读-处理-写1清除”的顺序操作中断状态寄存器。先读取值,再根据值处理,最后将读取的值写回以清除标志位。 2. 遵循“快进快出”原则,在ISR中只做最紧急的操作(如设置标志、复制寄存器值),复杂处理放到主循环或任务中。 |
调试USB这类复杂外设,一个USB协议分析仪是必不可少的。它能让您看到总线上的每一个令牌、数据包和握手包,是定位问题最直接的工具。结合MCU的调试器,单步跟踪中断触发、寄存器变化和FIFO状态,就能逐步将复杂的USB通信行为梳理清晰。
最后一点体会,USBHS模块的复杂性在于其状态机众多且相互关联。编写驱动时,一定要有清晰的状态管理思路。为每个管道维护一个软件状态(如IDLE,BUSY,WAITING,ERROR),在中断和主循环中根据硬件标志位来驱动状态转移。这样写出的代码结构清晰,也更容易调试和维护。
