
1. 项目概述深入USB设备控制器的“握手”艺术搞嵌入式USB设备开发最让人头疼的往往不是协议栈本身而是如何与底层硬件控制器高效、稳定地“对话”。尤其是当数据流开始涌动主机Host和设备Device之间那看不见的“握手”信号直接决定了你的设备是稳定可靠还是间歇性“抽风”。今天我们就来彻底拆解USB设备控制器中非同步事务Non-Isochronous Transactions的处理机制特别是IN和OUT方向的核心流程。这不仅仅是阅读芯片手册更是理解如何让MPU微处理器与USB控制器协同工作的实战指南。无论你是正在调试一个USB HID键盘还是开发一个高速数据采集设备掌握这些底层握手逻辑和FIFO管理策略都能让你在遇到“数据丢了”、“设备无响应”这类问题时快速定位到是软件流程bug还是硬件配置疏忽。所谓非同步事务主要指的就是USB协议中的控制Control、批量Bulk和中断Interrupt传输。它们不像同步Isochronous传输那样“佛系”不保证送达没有握手而是有一套严格的应答机制来确保每个数据包的命运都被明确知晓。这个过程完全由设备控制器上的硬件状态机与MPU软件通过一系列寄存器标志位和中断紧密配合来完成。核心中的核心就是几个关键的状态标志位STAT_FLG和那个小小的FIFO_EN位。理解它们在不同事务阶段的变化就如同掌握了与硬件沟通的密码。2. 核心机制总览状态机、FIFO与中断的三角关系在深入IN和OUT事务的细节之前我们必须先建立起一个宏观的认知框架USB设备控制器、MPU软件和USB主机三者是如何通过一套精密的“信号灯”系统来协作的。2.1 核心寄存器与标志位解读设备控制器为每个端点Endpoint都维护着一组控制与状态寄存器。对于非同步事务以下几个位是灵魂所在STAT_FLG.FIFO_EN(FIFO Enable): 这是数据流控制的“总闸门”。对于OUT端点主机→设备该位为1表示“RX FIFO已清空并准备好接收新数据”为0则表示“RX FIFO尚有数据未处理暂不接收”。对于IN端点设备→主机该位为1表示“TX FIFO已填充好数据可以发送”为0则表示“TX FIFO为空或无有效数据无法发送”。MPU软件通过设置对应的CTRL.SET_FIFO_EN位来打开这个闸门而硬件在特定条件下会自动关闭它。STAT_FLG.EP_HALTED(Endpoint Halted): 端点停滞标志。当该位置1时无论FIFO_EN状态如何该端点所有事务都将以STALL握手响应宣告端点功能异常。通常由MPU主动设置如处理错误或由主机通过标准USB请求Set Feature来设置。STAT_FLG.ACK/NAK/STALL: 这三个位是上一次事务握手结果的“记录员”。在一次事务结束后硬件会根据结果设置其中一个位并通过中断通知MPU。MPU读取这些位就能知道刚才发生了什么数据成功收发ACK、设备暂时忙NAK还是端点出错STALL。SYSCON1.NAK_EN(NAK Interrupt Enable): 这是一个全局配置位决定了当设备发出NAK握手时是否要产生中断通知MPU。关闭它可以减少不必要的CPU中断开销但打开它能让软件更精确地感知主机的请求频率。2.2 事务处理的基本流程与中断角色一次完整的非同步事务处理可以概括为“硬件驱动软件响应”的循环硬件检测与响应USB控制器硬件检测到总线上的令牌包Token Packet识别出目标端点和方向IN/OUT。状态判断与握手硬件立即检查该端点的STAT_FLG.FIFO_EN和EP_HALTED等状态位在数据阶段或握手阶段给出相应的响应发送数据、ACK、NAK或STALL。中断触发事务完成后如果触发了中断条件例如成功完成、或NAK_EN开启下的NAK硬件会置起对应的端点中断位。软件服务MPU进入中断服务程序ISR首先读取EPN_STAT寄存器确定是哪个端点产生的中断并清除中断标志。然后通过EP_NUM寄存器“选中”该端点再读取STAT_FLG来了解事务结果。数据搬运与状态恢复根据事务结果进行后续操作。对于成功的OUT事务从RX FIFO读取数据对于成功的IN事务准备下一包数据填入TX FIFO。最后重新设置FIFO_EN等控制位为下一次事务做好准备。这个流程的顺畅与否完全取决于软件对硬件状态变化的及时、正确处理。任何一个环节的延迟或逻辑错误都可能导致数据丢失、主机重试超时甚至设备被重置。注意芯片手册中常强调“STAT_FLG寄存器是只读的其内容由硬件根据事务结果更新”。软件不能直接写入STAT_FLG来改变状态只能通过操作CTRL寄存器如SET_FIFO_EN,CLR_HALT来间接影响硬件从而改变下一次事务时STAT_FLG可能的值。3. 非同步OUT事务详解从主机接收数据OUT事务是主机向设备发送数据的过程。对于设备控制器来说核心任务是安全地接收数据包并将其存入正确的端点RX FIFO同时通过握手信号告知主机接收状态。3.1 正常握手流程与软件操作一次成功的OUT事务以ACK握手结束是最高频的场景。其硬件与软件的交互时序如下事务前提主机发起OUT令牌包。此时目标端点的STAT_FLG.FIFO_EN位必须为1表示RX FIFO空闲可接收。数据接收与ACK硬件将随后到来的数据包存入RX FIFO并在握手阶段向主机回ACK确认接收成功。中断与状态更新硬件自动将STAT_FLG.FIFO_EN位清零防止新数据覆盖未读数据并设置STAT_FLG.ACK1然后触发该端点的RX中断。MPU中断服务程序ISR响应确定中断源读取EPN_STAT寄存器找到置位的位并写1清除该中断标志。选中端点并读取状态设置EP_NUM.EP_NUM为端点号EP_NUM.EP_DIR0OUT方向EP_NUM.EP_SEL1。然后读取STAT_FLG寄存器确认ACK1且FIFO_EMPTY0。读取数据这是极易出错的一步。必须先读取RXFSTAT.RXF_COUNT寄存器获取本次事务实际接收到的数据字节数然后再从DATA寄存器中读取相应数量的字节。绝不能凭感觉或预设数据长度去读因为主机可能发送小于FIFO尺寸的数据包。重新使能接收数据读取完毕后软件必须设置CTRL.SET_FIFO_EN1将STAT_FLG.FIFO_EN位重新置1告知硬件“FIFO已清空可以接收下一个数据包了”。释放端点最后清除EP_NUM.EP_SEL位。这个操作会同时清除STAT_FLG.ACK位为记录下一次事务结果腾出空间。// 伪代码示例OUT端点中断服务例程ACK情况 void USB_EPx_OUT_ISR(void) { // 1. 确定并清除中断 uint32_t ep_stat READ_REG(EPN_STAT); int ep_num FIND_SET_BIT(ep_stat); // 找到触发中断的端点号 WRITE_REG(EPN_STAT, (1 ep_num)); // 写1清除中断 // 2. 选中端点读取状态 WRITE_REG(EP_NUM, (ep_num EP_NUM_SHIFT) | (0 EP_DIR_SHIFT) | (1 EP_SEL_SHIFT)); uint32_t stat_flg READ_REG(STAT_FLG); if (stat_flg ACK_MASK) { // 3. 成功接收读取数据 uint32_t rx_count READ_REG(RXFSTAT) RXF_COUNT_MASK; uint8_t data_buffer[rx_count]; for (int i 0; i rx_count; i) { data_buffer[i] READ_REG(DATA); } // 4. 处理数据... process_received_data(data_buffer, rx_count); // 5. 重新使能FIFO接收 WRITE_REG(CTRL, SET_FIFO_EN_MASK); } else if (stat_flg NAK_MASK) { // 处理NAK情况如果NAK_EN开启 // ... } else if (stat_flg STALL_MASK) { // 处理STALL情况 // ... } // 6. 释放端点选择 WRITE_REG(EP_NUM, 0); // 清除EP_SEL等位 }3.2 NAK握手流量控制的基石NAKNot Acknowledge是USB设备最重要的流量控制机制。当设备暂时无法处理数据时例如上次OUT数据还未被MPU取走即FIFO_EN0就会对主机的OUT事务回复NAK。主机收到NAK后会在稍后重试该事务。硬件行为当STAT_FLG.FIFO_EN0时硬件在握手阶段自动回复NAK不会触动RX FIFO也不会自动清除FIFO_EN位。中断行为取决于SYSCON1.NAK_EN位。若为0则不产生中断软件无感知若为1则硬件会设置STAT_FLG.NAK1并产生端点RX中断。软件处理如果使能了NAK中断在ISR中读到NAK1通常意味着上一次的数据还未处理完。此时软件不应该去读RX FIFO因为里面是旧数据且FIFO_EMPTY很可能为0而应该检查自身数据处理逻辑是否过慢并在处理完旧数据后尽快执行CTRL.SET_FIFO_EN1来重新开放接收。处理完状态后同样需要清除EP_SEL位来清除NAK标志。实操心得对于高速或大数据量的OUT端点建议关闭NAK中断NAK_EN0。因为NAK是正常的流量控制手段频率可能很高每个NAK都产生中断会极大增加CPU负担。正确的做法是依靠成功的ACK中断来驱动数据处理并保证在ACK中断服务程序中尽快取走数据并重新使能FIFO减少NAK发生的概率。3.3 错误条件处理STALL与包错误当端点出现无法自行恢复的错误时需要通知主机停止重试这就是STALL握手。STALL条件有两种情况会触发STALL响应软件设置MPU主动设置了STAT_FLG.EP_HALTED位通过CTRL.SET_HALT。控制请求错误仅针对控制端点0在处理主机发来的Setup包时出现协议错误。硬件行为一旦EP_HALTED1其优先级高于FIFO_EN。硬件会回复STALL并自动清除FIFO_EN位如果它为1的话同时清空RX FIFO。然后设置STAT_FLG.STALL1和EP_HALTED1并产生中断。软件处理在STALL中断的ISR中软件需要判断停滞原因。如果是自己主动设置的例如等待某个外部事件则在事件完成后通过CTRL.CLR_HALT清除停滞状态并重新SET_FIFO_EN。如果是主机请求导致的则需要根据USB协议进行相应的恢复操作通常通过控制传输完成。STALL是一种严重的错误状态主机通常会感知并上报给上层驱动需要妥善处理。包错误Packet Error指在令牌包或数据包阶段检测到CRC错误、PID校验错误等。此时硬件不回复任何握手信号也不产生中断RX FIFO保持不变。数据包被静默丢弃。这种错误通常由总线物理层干扰引起设备端软件通常无法也无须处理依赖主机端的重传机制。FIFO溢出错误如果主机试图发送的数据量超过了RX FIFO的预设大小会发生溢出。硬件行为与包错误类似无握手、无中断、不接收数据。这属于配置错误必须在设计时确保主机发送的最大数据包大小不超过端点配置的FIFO尺寸。4. 非同步IN事务详解向主机发送数据IN事务是设备向主机发送数据的过程。设备控制器需要根据自身准备情况决定是发送数据还是回复NAK/STALL。4.1 正常发送流程ACK握手这是设备主动上报数据的场景例如HID设备报告按键或批量传输发送采集的数据。数据准备阶段MPU主导MPU等待上一次IN事务完成即收到TX中断且STAT_FLG.ACK1此时TX FIFO已空。MPU通过设置EP_NUM寄存器选中目标IN端点。MPU将待发送数据写入端点的TX FIFO通过DATA寄存器。必须确保写入的数据量不超过EPn_TX_SIZE配置的FIFO大小。数据写入完成后MPU设置CTRL.SET_FIFO_EN1将STAT_FLG.FIFO_EN位置1表示“数据已就绪可以发送”。最后清除EP_SEL位。事务触发与响应硬件主导主机发起IN令牌包。硬件检查STAT_FLG.FIFO_EN。若为1则将TX FIFO中的数据打包发出。主机成功接收后回复ACK握手。中断与状态更新硬件收到ACK后自动将STAT_FLG.FIFO_EN清零因为数据已发出FIFO可被再次写入设置STAT_FLG.ACK1并触发该端点的TX中断。TX FIFO在此刻变为空。MPU中断服务程序ISR响应读取EPN_STAT清除中断。选中端点EP_DIR1表示IN读取STAT_FLG确认ACK1。此时MPU知道上一包数据已成功送达主机且TX FIFO已空。如果需要连续发送可以立即准备下一包数据重复“数据准备阶段”的流程。最后清除EP_SEL位清除ACK标志。4.2 NAK握手告知主机“我还没准备好”当主机发起IN请求但设备TX FIFO为空FIFO_EN0时设备需要回复NAK。这与OUT方向的NAK逻辑对称都是流量控制。硬件行为FIFO_EN0时硬件回复NAK。TX FIFO内容保持不变因为根本没发送。中断行为同样受SYSCON1.NAK_EN控制。若开启则产生中断并设置STAT_FLG.NAK1。软件考量频繁的NAK会浪费总线带宽并可能让主机驱动认为设备响应慢。对于需要实时响应的中断传输端点应尽量避免NAK。策略是在上一包数据发出后ACK中断中尽快准备下一包数据并设置FIFO_EN1让FIFO在大部分时间内处于“就绪”状态。对于批量传输可以适当利用NAK进行流量控制但也要注意主机可能有超时机制。4.3 IN事务的错误条件STALLed Transactions与OUT类似当EP_HALTED1时IN事务会回复STALL。硬件会清空TX FIFO设置STALL1并产生中断。软件处理方式同OUT方向的STALL。Packet Errors令牌包错误主机发来的IN令牌包本身出错硬件直接忽略该事务无任何后续动作无中断。数据包无握手主机未回复ACK这可能是因为设备发出的数据包在传输中损坏主机通过CRC校验发现后选择不回复任何握手包而非发送NAK。设备控制器会等待一个超时时间然后判定此次传输失败。此时数据仍然保留在TX FIFO中FIFO_EN位也保持为1。硬件不产生中断。下一次主机重试IN事务时设备会重新发送同一包数据。这是USB协议保证可靠传输的重要机制。软件过早清空FIFO手册中提到如果MPU在IN事务的数据包传输阶段清空了TX FIFO会强制产生一个位填充错误。这属于严重的软件逻辑错误必须避免。5. 同步Isochronous事务的对比与核心差异虽然本文重点是非同步事务但理解同步事务的差异能加深对整体架构的认识。同步传输用于音频、视频等实时性要求高、但允许偶发数据丢失的场景。5.1 核心差异无握手与双缓冲与非同步事务最根本的两个区别是无握手协议主机发送ISO OUT或接收ISO IN数据包后没有ACK/NAK/STALL的握手阶段。主机“尽力而为”地发送设备“尽力而为”地接收或发送不保证送达。基于SOFStart of Frame的中断驱动与双缓冲FIFO由于没有每包事务的中断设备如何知道何时去处理数据呢答案是基于1ms一次的SOF中断。USB控制器会在每个帧起始SOF包产生一个全局中断。为了匹配1ms帧的节奏并避免数据竞争同步端点采用了硬件双缓冲机制每个同步端点有两个物理FIFO前台ForegroundFIFO和后台BackgroundFIFO。对MPU可见、可操作的是前台FIFO。对USB总线侧硬件数据搬运引擎可见、可操作的是后台FIFO。在每个SOF时刻前后台FIFO的身份会发生交换。上一帧的后台FIFO变成新的前台供MPU访问上一帧的前台FIFO变成新的后台供USB硬件使用。5.2 ISO OUT事务处理流程硬件动作在1ms帧内的任意时刻主机发送ISO OUT数据包。USB控制器硬件将其存入当前后台的RX FIFO中。无握手无端点中断。软件动作在SOF中断服务程序中SOF中断发生前后台FIFO交换。MPU遍历所有同步OUT端点对于每个端点选中端点检查STAT_FLG.ISO_FIFO_EMPTY位。如果为0非空则读取RXFSTAT.RXF_COUNT然后从新的前台FIFO即上一帧主机写入数据的那个FIFO中读取所有数据。读取完成后操作结束。注意对于同步端点没有SET_FIFO_EN的概念双缓冲切换由硬件在SOF自动完成。5.3 ISO IN事务处理流程软件动作在SOF中断服务程序中SOF中断发生前后台FIFO交换。同时新的后台TX FIFO即MPU上一帧填充的那个FIFO会被硬件清空准备被USB硬件读取。MPU遍历所有同步IN端点对于每个端点选中端点。将本帧要发送的数据写入新的前台TX FIFO。写入完成后操作结束。同样没有SET_FIFO_EN操作。硬件动作在接下来1ms帧内的任意时刻主机发起ISO IN请求。USB控制器硬件从当前后台TX FIFO即MPU在上一帧SOF中断中填充的那个FIFO中取出数据发送给主机。5.4 同步事务的错误处理同步事务的错误标志也通过SOF中断后的STAT_FLG来查看ISO_ERR如果上一帧中主机发送给某个ISO OUT端点的数据包出现错误CRC等或数据量超过了FIFO大小导致溢出则该位置1。同时对应的RX FIFO为空。DATA_FLUSH如果MPU未能在一个帧时间内即两次SOF之间读完某个ISO OUT前台FIFO中的所有数据当SOF发生、该FIFO变为后台时其中的剩余数据会被硬件丢弃Flush并且DATA_FLUSH位置1持续一帧时间。避坑指南开发同步传输功能时最关键的时序约束就是MPU必须在1ms内完成对所有同步端点的数据搬运OUT方向的读IN方向的写。如果MPU的SOF中断服务程序执行时间过长或者被更高优先级中断阻塞就极有可能导致DATA_FLUSH或数据覆盖造成音视频流卡顿。务必优化ISR代码或使用DMA来辅助数据搬运。6. 实战经验与常见问题排查理解了原理最终要落到代码和调试上。以下是一些从实际项目中总结的经验和常见问题。6.1 FIFO大小配置与内存规划EPn_TX[14:12]或EPn_RX寄存器中的FIFO大小配置至关重要。配置时需考虑最大数据包大小必须大于等于USB描述符中定义的该端点最大包大小wMaxPacketSize。双缓冲需求对于同步端点实际需要的FIFO内存是配置值的两倍因为硬件双缓冲。例如配置为512字节的同步IN端点实际上会占用1024字节的USB RAM。总体RAM分配USB控制器的内置RAM是共享的。需要为所有使能的端点包括控制端点0合理分配FIFO大小总和不能超过总RAM。错误的配置会导致无法枚举或数据传输错乱。6.2 中断服务程序ISR编写要点快进快出ISR中只做最必要的状态读取、标志清除和数据指针记录。将耗时的数据处理如解析HID报告、组包放到主循环或任务中。特别是SOF中断必须在1ms内完成对所有同步端点的操作。状态读取顺序务必先读EPN_STAT确定中断源并清除再通过EP_NUM选中端点最后读STAT_FLG。顺序错乱可能导致状态信息不对应或丢失中断。端点选择位的清理在ISR退出前或完成对一个端点的操作后一定要记得清除EP_NUM.EP_SEL位。这不仅是为了清除状态标志ACK/NAK/STALL也是防止软件在非中断上下文中误操作其他端点的寄存器。使用DMA对于大数据量的批量传输或同步传输强烈建议使用USB控制器集成的DMA引擎。让DMA在后台自动搬运FIFO数据可以极大减轻CPU负担避免因处理不及时导致的NAK或缓冲区溢出。6.3 典型问题排查速查表现象可能原因排查方向设备枚举失败端点0控制端点配置或处理错误。1. 检查端点0的FIFO大小是否足够处理标准请求至少8字节建议16或32。2. 检查SETUP、IN、OUT事务的中断处理逻辑是否正确特别是FIFO_EN位的管理。3. 确保对主机请求的响应速度避免因NAK过多导致主机超时。OUT端点数据丢失MPU未及时取走数据导致后续数据被NAK或覆盖。1. 检查OUT端点ACK中断的优先级是否足够高。2. 在ACK中断ISR中是否及时读取了RXF_COUNT并取走数据3. 取走数据后是否立即执行了SET_FIFO_EN14. 主循环处理数据是否太慢导致中断频繁NAK考虑使用环形缓冲区。IN端点数据发送不出去主机未收到数据或设备一直回复NAK/STALL。1. 检查FIFO_EN位是否在数据写入FIFO后被正确置1。2. 检查TX中断是否被正确响应并在中断中为下一次发送准备好了数据。3. 检查EP_HALTED位是否被意外置位。4. 使用总线分析仪抓包看设备是否回复了NAK/STALL以及数据包内容是否正确。同步传输音视频卡顿数据流不连续出现断流。1.首要怀疑SOF中断处理超时用示波器或GPIO翻转测量SOF ISR的执行时间确保远小于1ms。2. 检查是否出现了DATA_FLUSH或ISO_ERR标志。3. 确认双缓冲机制理解正确MPU是在向“前台”FIFO写入数据。4. 考虑使用DMA进行数据搬运。批量传输速度远低于理论值软件处理瓶颈或NAK过多。1. 检查是否使能了批量端点的DMA。2. 调整端点FIFO大小使其能容纳多个最大数据包减少事务次数。3. 优化软件数据处理流程避免在中断中处理大量数据。4. 确认主机驱动是否使用了正确的传输策略如一次传输多个事务。调试USB设备一个USB协议分析仪如Beagle, Ellisys是无可替代的工具。它能让你直观地看到总线上的每一个包、每一个握手信号直接定位是主机没发还是设备没回或者是数据内容错了从而将问题范围从“系统级”迅速缩小到“硬件行为级”或“软件逻辑级”。说到底USB设备控制器的编程就是一场与硬件状态机共舞的精细操作。每一个标志位的设置与清除每一次中断的响应与处理都必须严格遵循硬件定义的时间序和逻辑序。这份手册章节解读和实战总结希望能帮你理清这场舞蹈的节拍让你在开发下一个USB设备时能够更加得心应手写出稳定高效的底层驱动。