1. 项目概述与核心价值
在工业自动化、运动控制或者任何对网络通信时序有苛刻要求的嵌入式场景里,工程师们常常面临一个核心挑战:如何让标准的以太网通信变得足够“快”和足够“确定”?这里的“快”不是指带宽,而是指从数据到达物理接口,到被处理器核心感知并做出反应的延迟必须极短且可预测。标准的中断驱动、操作系统调度的网络协议栈动辄引入数十甚至上百微秒的延迟,这在许多实时应用中是不可接受的。于是,像TI Sitara系列处理器中的PRU-ICSS(可编程实时单元与工业通信子系统)及其MII_RT(媒体独立接口-实时)子系统,就成了解决这类问题的利器。
简单来说,MII_RT不是一个全新的物理层标准,它依然是标准的MII接口,遵循相同的电气和时序规范。它的“魔法”在于,将MII数据流的控制权从复杂、不可预测的通用CPU和DMA手中,直接下放给了两个精简、确定性的PRU核心。PRU能以125MHz(8ns周期)甚至更高的频率运行,通过直接读写R30和R31这两个特殊寄存器,以及与RX L1 FIFO、TX L1 FIFO的紧密耦合,实现了对以太网帧的“线速”处理。你可以把它想象成一个高度定制化的、硬件加速的以太网数据泵,专门负责以最少的时钟周期开销搬运和预处理网络数据。
这篇文章,我们就来彻底拆解MII_RT的工作机制。我不会只停留在手册的寄存器描述层面,而是结合我多年在工业以太网协议(如EtherCAT、PROFINET IRT)开发中的实际踩坑经验,带你深入理解其数据帧的拆装过程、PRU寄存器的精妙用法、FIFO操作的时序陷阱,以及如何利用这些特性构建稳定可靠的实时通信链路。无论你是刚开始接触PRU的新手,还是希望优化现有实时网络性能的老手,相信这些从实践中总结出的细节和“坑点”都能给你带来直接的帮助。
2. MII_RT数据帧结构与硬件流水线
要驾驭MII_RT,首先必须理解数据在物理线上是如何被组织,又是如何被PRU“看见”的。这关乎到你后续编写的每一行PRU汇编或C代码是否正确理解了数据的边界和含义。
2.1 标准MII帧到PRU视角的转换
一个标准的以太网帧在MII接口上传输时,其结构如手册所述,包含:帧间隙(Inter-frame Gap)、前导码(Preamble, 7字节0x55)、帧起始定界符(SFD, 1字节0xD5)、数据载荷(Data)和帧校验序列(CRC32)。MII接口以半字节(Nibble, 4比特)为单位,在RX_CLK的上升沿同步传输数据。
这里第一个关键点来了:PRU并不直接“看到”比特流,它看到的是经过MII_RT硬件逻辑组装后的字节。如图7-68所示,MII_RT接收逻辑会等待两个连续的半字节(例如第一个半字节的D3-D0,第二个半字节的D7-D4)到来后,将它们组合成一个完整的字节(D7-D0),然后才放入RX L1 FIFO,并最终呈现给PRU的R31寄存器。这意味着对于PRU固件而言,数据访问的最小单位是字节,这简化了处理逻辑。
注意:字节序问题。虽然图中显示MSB(D7)先到达,但被放在了Nibble的LSB侧,这描述的是比特在物理线上的串行顺序。对于PRU程序员来说,你从R31读到的
BYTE0和BYTE1,其每个字节内的比特顺序(位序)是符合常规理解的(即BYTE0[7]是最高位)。你通常无需关心物理层的比特串行顺序,除非你在做极底层的信号调试。
2.2 数据就绪标志与POP操作的精确定时
PRU如何知道R31寄存器里的数据是新的、有效的?这依赖于R31中的几个状态位:DATA_RDY、BYTE_RDY、WORD_RDY。手册里轻描淡写的一句话“有2个时钟周期的延迟”,在实际编程中却是个大坑。
场景还原:假设你配置为按字(Word, 16位)读取。当RX FIFO中有新数据时,DATA_RDY会置位。PRU执行一条POP16命令(通过写R31的相应位)来消费当前数据并让FIFO指针前进。关键来了:在你执行POP16命令后的至少2个PRU时钟周期内,BYTE_RDY/WORD_RDY和DATA_RDY的状态是未定义的、正在更新的。如果你在这2个周期内就去读取这些状态位来判断是否有下一组数据,你可能会读到陈旧的值,导致程序逻辑错误,比如误判帧结束或陷入死循环。
实操心得:
- 保守策略:在
POP8/POP16操作后,插入至少2条NOP指令,或者执行一些与数据读取无关的本地计算,然后再去检查DATA_RDY位。这是最安全、最易理解的方式。 - 激进策略:通过精细的指令排布,让
POP操作与下一次状态检查之间自然间隔2条其他指令(如从本地存储器加载地址、做一次加法等)。这需要你对PRU指令流水线有很深的理解,并经过严格测试。 - 错误示范:
正确的做法是在; 错误代码:POP后立即检查 LBBO &r0, r31, 0, 2 ; 读取R31的当前数据(低16位在r0) SET r30, r30, 4 ; 假设bit4是POP16命令位 SBBO &r30, r31, 0, 4 ; 执行POP16(写R31命令接口) QBBS DATA_READY, r31, 16 ; 立即检查DATA_RDY位(bit16)--> 可能读到旧状态!SBBO(写命令)和QBBS(检查状态)之间加入延迟。
2.3 CRC校验的“提前”与“滞后”
MII_RT会在硬件中为每个接收到的帧计算CRC32,并与帧尾自带的CRC进行比较。这个比较结果ERROR_CRC会作为一个状态位提供给PRU。手册中特别强调,ERROR_CRC、RX_SOF、RX_SFD、RX_EOF、ERROR_NIBBLE这些状态位是“早期状态”(early status)。这意味着它们是在数据进入RX L1 FIFO之前就计算好的。
这带来了一个极其重要的编程影响:你可以在帧数据还未完全被PRU读取之前,就提前知道这个帧是否有CRC错误,或者是否是一个“半字节错误帧”(帧长度不是整字节)。这为实现高效的实时过滤和快速错误响应提供了可能。例如,在EtherCAT这样的协议中,一旦检测到CRC错误,从站可以立即丢弃该帧并准备发送错误应答,而不需要等到整个帧的数据都搬移到内存后再做软件校验,节省了宝贵的微秒级时间。
对应的“坑点”:ERROR_CRC位仅在RX_EOF置位时才有效。也就是说,你必须等到帧结束标志到来,才能去查询CRC是否正确。但它又是个“早期状态”,所以一旦RX_EOF置位,ERROR_CRC就已经是稳定可读的了,不需要等待数据全部读出。
3. PRU核心寄存器:R30与R31的深度操作指南
R30和R31是PRU与MII_RT世界交互的窗口。理解它们每一位的精确含义和操作时序,是写出稳定PRU固件的基石。
3.1 R31:多功能复合状态与控制寄存器
R31可能是PRU-ICSS中最复杂也最强大的寄存器之一。它是一个多功能复用寄存器,其含义完全取决于你是读它还是写它,以及当前PRU的GPIOMODE配置。
3.1.1 读模式:接收数据与状态捕获当PRU读取R31时,它获取的是接收路径的信息。其位域定义如表7-84所示,我们可以将其分为三大部分:
- 数据域(Bit 0-15):
BYTE0和BYTE1。这就是从RX L1 FIFO头部直接映射过来的两个字节数据。是否有效,由状态域决定。 - 核心状态域(Bit 16-20):
DATA_RDY/TX_EOF、BYTE_RDY、WORD_RDY、RX_EOF、RX_ERROR。这是驱动接收状态机的核心。DATA_RDY:这是接收数据流的“总开关”。为1表示有数据可读;执行POP操作后,如果FIFO已空,它会在延迟后变0���BYTE_RDY/WORD_RDY:指示当前R31中有一个字节还是一个字的数据是有效的。同样受POP操作延迟影响。RX_EOF:帧结束标志。这是最重要的信号之一。它置位表示一个完整的帧已经接收完毕(RX_DV变低)。此时,ERROR_CRC、ERROR_NIBBLE等状态位才具有参考意义。许多处理循环都以RX_EOF作为跳出或进行帧处理的判断条件。RX_ERROR:一个聚合错误标志,只要发生了帧长超限、前导码超限或物理层RX_ERR中的任何一种,它就会置位。
- 详细状态与事件域(Bit 21-29):
RX_SFD、RX_SOF、ERROR_NIBBLE、ERROR_CRC、RX_ERR、RX_MAX_PRE_CNT_ERR、RX_EOF_ERROR、RX_MAX_FRM_CNT_ERR、RX_MIN_FRM_CNT_ERR。这些位提供了更精细的错误诊断和帧事件信息,对于调试和实现高级协议功能(如时间戳插入)至关重要。
3.1.2 写模式:命令发送当PRU写入R31时,它不是在向接收路径写数据,而是在向MII_RT模块发送命令。这是控制数据流的关键。
- 接收侧命令:主要是
RX_POP8和RX_POP16。如前所述,它们告诉MII_RT:“我已经处理完当前R31中的数据,请将FIFO指针前移,把下一个数据(或字)加载到R31。” 还有RX_RESET,用于在FIFO溢出等错误后复位接收逻辑。 - 发送侧命令:包括
TX_PUSH8、TX_PUSH16(将R30中的数据推入TX L1 FIFO)、TX_EOF(指示当前写入的是帧的最后一个字节)、TX_CRC_HIGH/TX_CRC_LOW(控制CRC生成和插入,后文详述)等。
关键陷阱:R31的读值和写值是完全独立的物理电路。你写入的命令位不会影响你下一秒读出的状态位(除了由这些命令触发的状态变化,如POP后DATA_RDY变化)。在汇编中,你需要用不同的指令(LBBO读,SBBO写)和不同的字节偏移量来访问它们。混淆读写操作是新手最常见的错误之一。
3.2 R30:发送数据寄存器
相对于R31,R30的角色单纯很多:它主要是一个发送数据寄存器。当PRU需要发送一个帧时,它会将待发送的字节或字写入R30的相应位置(通常是低16位),然后通过写R31命令接口发出TX_PUSH命令,将R30中的数据压入TX L1 FIFO。
一个重要细节:R30也可以被读取,但在MII_RT上下文中,读R30通常获取的是其他子系统(如eCAP, ePWM)映射过来的输入信号状态,与MII发送无关。在纯粹的MII发送任务中,你通常只写R30。
操作流程示例(发送一个字节):
; 假设要发送的数据在寄存器 r2 的低8位 MOV r30, r2 ; 将数据移动到R30的低字节 SET r31, r31, 3 ; 假设bit3是TX_PUSH8命令位 SBBO &r30, r31, 0, 4 ; 关键:这个“写R31”操作,同时完成了两件事: ; 1. 将R30当前值(即要发送的数据)锁存到发送路径 ; 2. 将R31中对应的命令位(bit3)置位,触发PUSH操作注意,上述代码中SBBO &r30, r31, 0, 4这条指令非常精妙。它一次内存写入操作,同时更新了R30的数据锁存器和R31的命令寄存器。这是PRU-ICSS设计上的一个高效特性。
4. FIFO操作:数据流的核心缓冲与管理
RX L1 FIFO和TX L1 FIFO是MII_RT数据流中的关键缓冲器,理解它们的深度、指针行为以及溢出/下溢机制,是避免数据丢失的保证。
4.1 RX L1 FIFO:接收侧的32字节滑窗
这是一个32字节深的FIFO。它的工作模式可以理解为“滑动窗口”:
- 从MII接口接收到的字节,经过组装后,被填入这个FIFO。
- FIFO的头部第一个字节会直接映射到PRU的R31寄存器(
BYTE0)中,供PRU直接读取。 - 当PRU执行
POP操作时,并不是把整个FIFO里的数据“弹”出来,而是让FIFO的读指针前进1或2个位置。于是,新的字节成为“头部”,并立即出现在R31中。 - 这种设计使得PRU能以极低的延迟(通常就一两条指令的间隔)持续处理流入的数据,实现“线速”处理。
溢出(Overflow)处理实战: 手册提到,如果PRU处理速度跟不上数据流入速度,FIFO会溢出,数据被丢弃,并产生PRU<n>_RX_OVERFLOW系统事件。处理这个事件不是可选项,而是必须项。
- 原因:溢出意味着帧不完整,后续所有基于该帧数据的处理都无意义。
- 处理流程:
- 在PRU中断服务程序中:检测到溢出事件后,应立即通过写R31命令接口发送
RX_RESET命令。这个命令会清空RX L1 FIFO,并重置接收状态机,使其准备好接收下一个帧。 - 在应用层:需要记录这个错误,并可能触发更高层的重传或报警机制。在EtherCAT中,这可能意味着从站需要进入“安全状态”。
- 在PRU中断服务程序中:检测到溢出事件后,应立即通过写R31命令接口发送
- 预防措施:
- 优化PRU代码,确保
POP和数据处理指令的总周期数小于最坏情况下字节到达的时间间隔(对于100Mbps MII,一个字节是80ns,即10个PRU周期@125MHz)。 - 如果单帧数据量很大,考虑使用RX L2 Buffer模式,它提供了更大的缓冲空间。
- 优化PRU代码,确保
4.2 TX L1 FIFO:发送侧的40字节队列
这是一个40字节深的FIFO,用于缓存PRU准备发送的数据。发送逻辑会从这个FIFO中取出数据,加上前导码、SFD和硬件计算的CRC,然后通过MII TX端口发送出去。
下溢(Underflow)与发送使能条件: 下溢发生在TX_EN(发送使能)信号需要激活以开始发送一个帧时,但TX L1 FIFO是空的。这是一个严重错误,会导致发送出一个不完整的、损坏的帧。MII_RT会将此事件映射到INTC。
更关键的是TX_EN的激活条件,它依赖于四个计时器,这直接决定了帧间间隔(IPG)的精确控制:
- IPG定时器:确保帧与帧之间有最小间隔(对于以太网是96比特时间)。
- RX_DV to TX_EN定时器:在某些半双工或特定转发模式下,从接收到发送的切换时间。
- TX_EN比较定时器:用于精确控制TX_EN的激活时机。
- FIFO非空:这是最基本条件。
发送流程中的关键命令——TX_EOF: 当PRU将一帧的最后一个数据字节写入FIFO后,必须在同一个TX_PUSH命令中,同时置位TX_EOF位(R31 bit 29)。这个信号告诉MII_RT发送逻辑:“这是最后一字节数据,你可以在发送完它之后,开始计算并附加CRC,然后结束本帧。” 如果忘记设置TX_EOF,发送逻辑会一直等待更多数据,导致帧无法正常结束,或者CRC计算错误。
4.3 RX L2 Buffer:高性能双缓冲模式
当简单的RX L1 FIFO到PRU的路径无法满足需求时(例如,需要处理突发的大数据帧,或者PRU需要同时处理其他任务),可以启用RX L2 Buffer模式。这是一个64字节(两个32字节Bank)的“乒乓缓冲器”。
工作模式:
- 数据从MII接口进入RX L1 FIFO后,会被自动搬运到RX L2 Buffer的当前写Bank中。
- PRU不再通过R31直接读取数据,而是通过XFR(扩展寄存器文件)读指令,将整个Bank的数据(最多32字节)一次性加载到其寄存器文件(R2-R9)中。状态信息则加载到R10-R13。
- 当当前写Bank满(或帧结束),硬件会自动切换到另一个Bank继续写入,实现了无间断的数据接收。
- PRU可以���过读取R18寄存器中的写指针,来判断哪个Bank有有效数据,以及数据写到了哪个位置。
优势与挑战:
- 优势:大大减少了PRU因频繁执行
POP指令而产生的中断开销,允许PRU以“块”为单位处理数据,效率更高。也为PRU在数据搬运期间执行其他计算任务提供了时间窗口。 - 挑战:引入了更复杂的同步机制。PRU必须及时读取已满的Bank,否则会被新数据覆盖。这通常需要配合中断使用:当硬件完成一个Bank的写入或一帧结束时,产生一个事件中断PRU,PRU在中断服务程序中启动XFR读取。
实操心得:Bank切换与指针管理在L2模式下,最易出错的是对R18写指针的理解和Bank边界的处理。R18的低6位指示当前写入位置(0-63)。0-31对应Bank0的R2.R3...R9,32-63对应Bank1。你的PRU固件需要根据这个指针,计算出当前有效数据的长度。一个常见的策略是,在RX_EOF事件中断中,直接读取整个当前Bank,然后根据帧状态寄存器中的信息,判断实际有效数据长度。
5. CRC计算与高级发送控制
CRC校验是保证数据完整性的基石,MII_RT在发送和接收侧都提供了硬件CRC32计算,但发送侧的CRC控制尤为灵活和复杂。
5.1 接收CRC:自动校验与错误标记
如前所述,接收CRC是自动完成的,结果通过ERROR_CRC标志位提供。对于开发者而言,主要任务是在RX_EOF置位后检查该位,并采取相应行动(如丢弃帧、记录错误计数)。
5.2 发送CRC:三种编程模型详解
发送CRC的生成和插入方式,给了开发者很大的控制权。表7-83中的三种选项,对应着不同的应用场景和性能需求。
选项1:标准单命令模式cmdR31 [TX_CRC_HIGH + TX_CRC_LOW + TX_EOF]这是最常用、最简单的模式。当PRU写入帧的最后一个数据字节时,在同一个命令中同时置位TX_CRC_HIGH、TX_CRC_LOW和TX_EOF。MII_RT硬件会在发送完该字节后,自动计算整个帧的CRC,并将其附加在帧尾发出。
- 适用场景:绝大多数常规帧发送。
- 注意事项:确保在发送该命令时,TX L1 FIFO中有足够的空间(至少4字节)来存放即将计算出的CRC值,否则会导致FIFO溢出。
选项2:分步CRC插入模式步骤:1.cmdR31 [TX_CRC_HIGH]-> 2. 等待 >6 PRU周期 -> 3.cmdR31 [TX_CRC_LOW + TX_EOF]这个模式将CRC插入过程分成了两步。第一步TX_CRC_HIGH启动CRC计算,在等待至少6个周期后,第二步TX_CRC_LOW才真正将CRC值插入帧尾。这6个周期的窗口期,为PRU做最后一刻的修改(例如,基于实时计算更新CRC)提供了可能。
- 适用场景:需要动态生成或修改CRC的特定协议。注意:此模式仅在TX L2 Buffer禁用时才有效。
- “>6时钟”的玄机:这6个周期是CRC计算电路完成32位CRC计算所需的最短时间。少于这个周期,CRC值可能还未就绪,导致插入错误的数据。
选项3:完全软件CRC覆盖模式步骤:1.cmdR31 [TX_CRC_HIGH]-> 2. 等待 >6周期 -> 3. 读取TX_CRC0和TX_CRC1寄存器 -> 4. 修改CRC值 -> 5.cmdR31 [TX_PUSH16 + TX_EOF + TX_ERROR_NIBBLE]这是最复杂的模式,赋予了软件对CRC的完全控制权。PRU可以读取硬件计算出的CRC中间值,对其进行修改,然后将自己计算或修改后的CRC值,作为一个普通的16位数据(通过TX_PUSH16)推入FIFO,并同时标记帧结束和可能的错误半字节。
- 适用场景:实现非标准的校验算法,或在CRC字段中携带特殊信息(某些工业协议可能这样做)。同样,需要TX L2禁用。
- 关键操作:
TX_ERROR_NIBBLE位的使用。当软件自行提供CRC时,需要此位来指示帧结束边界。
5.3 分片帧的CRC处理
手册中关于分片帧(fragmented frames)的CRC描述是一个高级主题。它指的是将一个逻辑上的长帧分成多个物理片段发送。在这种情况下,每个片段都有自己的CRC,且后一片段的CRC计算依赖于前一片段的数据(运行总和)。TX_CRC_HIGH位在除最后一个片段外的所有片段中会被反转。
实际应用:这在一些专有的、追求极致确定性的实时协议中可能会用到,通过分片来减少单个帧的发送时间,从而降低链路延迟。对于标准以太网帧,通常不需要关心此模式。如果你的应用涉及此功能,务必仔细设计状态机来管理TX_CRC_HIGH标志的置位与反转。
6. 错误检测、诊断与系统集成
可靠的系统离不开完善的错误处理。MII_RT提供了多层次、细粒度的错误检测机制。
6.1 错误类型全景图
- 物理层错误(RX_ERR):由PHY芯片在
RX_DV有效期间通过RX_ER信号线报告。MII_RT会丢弃错误半字节及其后直到帧尾的所有数据,并置位RX_ERR标志。重要限制:此功能仅适用于MII模式,RGMII和SGMII模式不支持。 - CRC校验错误(ERROR_CRC):硬件计算CRC与帧尾CRC不匹配。这是最常见的数据完整性错误。
- 帧格式错误:
ERROR_NIBBLE:帧长度不是整字节,结束在半字节边界。RX_MIN_FRM_CNT_ERR/RX_MAX_FRM_CNT_ERR:帧长度小于或大于预设的阈值。RX_MAX_PRE_CNT_ERR:前导码(0x55)的个数超过限制。
- FIFO错误:
- RX Overflow:RX L1 FIFO溢出。
- TX Underflow:TX L1 FIFO下溢。
- 连续错误事件(RX_ERR32):这是一个高级安全特性。MII_RT会统计10μs时间窗口内发生的
RX_ERR事件。如果累计达到或超过32次,会触发一个中断(RX_ERR32)。这可用于检测持续的物理层故障(如电缆损坏、连接器松动),从而触发系统级的保护动作。
6.2 错误处理框架设计建议
在PRU固件中,一个健壮的错误处理框架应包括:
- 实时响应:在帧处理循环中,一旦检测到
RX_ERROR或RX_EOF伴随ERROR_CRC,应立即终止当前帧的处理,跳转到错误清理例程(如执行RX_RESET)。 - 错误分类与统计:在PRU的本地数据存储器或共享内存中,为不同类型的错误设立计数器。这有助于后期网络质量分析和故障诊断。
- 中断与主循环分工:将FIFO溢出(Overflow/Underflow)和连续错误(RX_ERR32)这类相对不频繁但严重的事件,配置为PRU系统事件,并映射到PRU中断。在中断服务程序(ISR)中进行紧急处理(如复位FIFO)。而CRC错误、格式错误等可以在主接收循环中同步处理。
- 状态位清理:许多错误状态位(如
RX_ERROR、各种*_CNT_ERR)需要通过写RX_ERROR_CLR命令来清除。务必在错误处理完毕、准备接收新帧前执行清理,避免残留错误状态影响下一帧的判断。
6.3 与PRU-ICSS INTC的集成
几乎所有重要的状态和错误事件(RX_SOF,RX_SFD,RX_EOF,ERROR_CRC,RX_ERR, 各种溢出错误等)都可以被配置为触发PRU-ICSS内部的中断控制器(INTC)事件。这意味着你可以用中断驱动的范式来编写PRU程序,而不是一味地轮询。
个人经验之谈:对于高吞吐量、低延迟的应用,我倾向于混合模式。对于数据流本身,采用轮询方式,因为PRU处理一个字节的时间极短,轮询效率最高。而对于帧开始(RX_SOF)、帧结束(RX_EOF)和错误事件,则启用中断。这样,PRU可以在没有数据时进入低功耗状态或执行其他后台任务,一旦帧开始或结束,能立即被中断唤醒进入处理状态,兼顾了效率和响应性。配置INTC的事件映射和通道是另一项细致的工作,需要参考PRU-ICSS的INTC章节,确保事件正确映射到PRU的系统事件,并在PRU中使能相应的中断。
7. 从理论到实践:一个��单的MII_RT回环示例
为了将以上所有概念串联起来,我们设想一个最简单的应用:PRU通过MII_RT接收一个以太网帧,不解析其内容,直接将其原样发送回去(回环,Loopback)。这个例子涵盖了完整的接收和发送流程。
步骤1:初始化配置
- 配置PRU的GPIOMODE为MII_RT模式。
- 配置MII_RT的RXCFG0/1寄存器,例如选择是否保留前导码和SFD。
- 使能所需的PRU系统事件(如
RX_EOF)到INTC的映射,并配置PRU中断(如果需要)。 - 清空所有FIFO(
RX_RESET,TX_RESET)。
步骤2:接收状态机(轮询方式)
// 伪代码,展示逻辑流程 while(1) { // 读取R31状态 status = read_R31_status(); if (status.DATA_RDY) { // 有数据可读 data_byte = read_R31_byte0(); // 读取数据 // 将数据存入临时缓冲区(用于后续发送) buffer[write_ptr++] = data_byte; // 发出POP命令,准备读取下一个数据 if (status.WORD_RDY) { issue_RX_POP16(); // 可以再读取一个字节 data_byte1 = read_R31_byte1(); // buffer[write_ptr++] = data_byte1; } else { issue_RX_POP8(); } // 检查是否帧结束 if (status.RX_EOF) { frame_length = write_ptr; // 检查错误 if (status.ERROR_CRC || status.RX_ERROR) { // 错误处理:丢弃缓冲区数据,执行RX_RESET write_ptr = 0; issue_RX_RESET(); continue; } // 帧接收完成,跳出接收循环,进入发送流程 break; } } else { // 无数据,可执行短暂等待或执行其他任务 delay_cycles(1); } }步骤3:发送状态机
// 伪代码,继续上述流程 // 首先,如果需要,插入前导码和SFD到发送缓冲区(或由硬件自动添加) // 然后,将接收到的数据推入TX FIFO for (i = 0; i < frame_length; i++) { write_R30(buffer[i]); // 将数据写入R30 if (i == frame_length - 1) { // 如果是最后一个字节,发送包含TX_EOF的命令 issue_TX_PUSH8_with_EOF(); } else { // 普通数据字节 issue_TX_PUSH8(); } // 注意:这里可能需要检查TX FIFO是否满,但PRU通常比发送速度快。 // 更稳健的做法是,在每次PUSH前,检查TX状态(如果可用)。 } // 发送完成后,复位写指针,准备下一帧 write_ptr = 0; // 可选:等待TX_EOF状态位确认发送完成步骤4:错误与边界情况处理
- 缓冲区管理:确保临时缓冲区足够大,能容纳最大帧。
- FIFO复位:在每次回环开始前,或发生错误后,执行
RX_RESET和TX_RESET。 - 中断处理:如果使用了
RX_EOF中断,上述接收循环会被中断触发,状态机设计需相应调整。
这个简单的例子忽略了IPG、CRC自动插入(使用选项1)、分片等复杂情况,但它清晰地展示了数据如何通过R31流入,通过R30流出,以及状态位如何驱动整个流程。在实际工业协议中,状态机会复杂得多,需要解析帧头、校验地址、执行逻辑处理等,但底层对MII_RT的操作原理是相通的。
深入理解MII_RT的每一个细节,从数据帧的比特流开始,到PRU寄存器中每一个状态位的含义,再到FIFO和CRC硬件的协同工作,是构建高性能、高可靠性嵌入式网络应用的基石。它要求开发者兼具硬件思维和软件精度,而这正是嵌入式实时编程的魅力所在。希望这篇结合了手册原理与实战经验的解析,能帮助你在下一次面对PRU和MII_RT时,更加游刃有余。