1. 项目概述与核心价值
在工业自动化、运动控制这些对时间要求极其苛刻的领域,毫秒甚至微秒级的延迟都可能导致生产线停机或设备损坏。传统的基于通用处理器和操作系统的网络通信,由于任务调度、中断响应等不确定性,很难满足这种硬实时需求。这时,就需要像TI Sitara系列处理器中的PRU-ICSS(可编程实时单元与工业通信子系统)这样的“特种兵”上场。它的核心价值,就在于将网络数据包的收发和处理,从“软件调度”的范畴,剥离到“硬件直通”的层面,从而实现确定性的、极低延迟的通信。
PRU-ICSS实现这一目标的关键,在于其与物理层(PHY)直接对话的MII(介质无关接口)。而PRU核心与这个MII接口交互的“双手”,就是R30和R31这两个特殊功能寄存器。你可以把它们想象成PRU这个“特种兵”与外部网络世界进行数据交换的两个专用窗口:一个只负责往外递东西(发送,R30),另一个负责接东西和接收指令(接收与命令,R31)。整个数据帧,从PHY芯片传来的比特流,到被PRU处理成有意义的字节,再到被转发或响应,其生命周期的每一个环节,都离不开对R30/R31寄存器的精准操控。
理解R30/R31的工作机制,不仅仅是读懂几个比特位的含义,更是掌握如何在PRU上编写高效、可靠的工业以太网协议栈(如EtherCAT从站、PROFINET设备)的基石。很多开发者初次接触PRU编程时,往往卡在数据收发的时序、FIFO的溢出处理、CRC的插入时机这些细节上,其根源就是对这两个寄存器以及背后MII接口的状态机理解不够透彻。本文将深入解析PRU-ICSS中MII接口的数据通路,聚焦R30/R31寄存器在数据帧处理中的核心作用,并结合实际编程中的坑点,为你呈现一幅清晰的硬件级实时通信实现蓝图。
2. MII接口与数据帧结构解析
在深入寄存器之前,我们必须先理解PRU-ICSS所面对的“原始数据”是什么样子的。MII接口是IEEE 802.3标准定义的一种并行接口,用于连接MAC(媒体访问控制)层和PHY(物理层)芯片。PRU-ICSS内部的MII_RT(MII实时)模块,本质上扮演了一个简化版、但高度可定制的MAC角色。
2.1 标准以太网帧在MII上的呈现
一个标准的以太网数据帧,在MII接口上并非以完整的、连续的字节流形式出现。它被拆解成更底层的信号和半字节(Nibble,4比特)流。下图展示了一个帧从PHY到PRU的“变形记”:
MII_RXD[3:0] (4-bit Nibble Stream from PHY): 时钟周期0: 0101 (0x5) <- 前导码(Preamble)的一部分 时钟周期1: 0101 (0x5) ... 时钟周期6: 0101 (0x5) 时钟周期7: 1101 (0xD) <- 帧起始定界符(SFD) 时钟周期8: [数据字节0的高4位] (例如: 0xE) 时钟周期9: [数据字节0的低4位] (例如: 0xA) -> 组合成字节0xEA 时钟周期10:[数据字节1的高4位] ...注意:MII接口的数据线是4位宽的(
RXD[3:0]),每个时钟周期传输一个半字节。因此,一个字节的数据需要两个时钟周期才能传输完毕。并且,高位半字节(MSN)先传输,低位半字节(LSN)后传输。这是理解后续数据组装逻辑的关键。
PRU-ICSS的MII_RT模块硬件会自动识别前导码(连续7个以上的0x5)和SFD(0x5D),并在此之后,开始将每两个连续的半字节组装成一个完整的字节。这个组装好的字节,才会被放入接收FIFO,供PRU读取。
2.2 数据帧的完整结构
一个完整的、待处理的数据帧,在PRU-ICSS的视角里,结构如下表所示。MII_RT硬件会处理其中一部分,而另一部分则需要PRU固件参与处理。
| 组成部分 | 长度 | 说明 | 处理方 |
|---|---|---|---|
| 前导码 (Preamble) | 7+字节 | 由0x55(二进制01010101)重复组成,用于时钟同步。 | MII_RT硬件自动检测并可选移除。 |
| 帧起始定界符 (SFD) | 1字节 | 固定为0xD5(二进制11010101),标识帧开始。 | MII_RT硬件自动检测并产生RX_SFD事件。 |
| 目的MAC地址 | 6字节 | 帧的目标设备地址。 | PRU固件从FIFO中读取并解析。 |
| 源MAC地址 | 6字节 | 帧的发送设备地址。 | PRU固件从FIFO中读取并解析。 |
| 长度/类型字段 | 2字节 | 指示数据域长度或上层协议类型。 | PRU固件解析。 |
| 数据与填充 (Payload & Pad) | 46-1500字节 | 实际传输的有效数据。 | PRU固件处理的核心内容。 |
| 帧校验序列 (FCS/CRC) | 4字节 | 基于前面所有字段计算的32位CRC校验码。 | 接收时:MII_RT硬件计算并比对,通过ERROR_CRC标志位告知PRU。发送时:PRU通过命令控制,由MII_RT硬件自动计算并附加。 |
这个结构是PRU处理任何以太网帧(无论是标准TCP/IP帧还是工业以太网帧)的基础。工业以太网协议通常会在“数据与填充”字段内定义自己的报文头和应用数据。
3. R31寄存器:数据接收与流程控制的枢纽
R31寄存器是PRU与MII接收路径交互的核心。它身兼三职:状态指示器、数据读取窗口和命令控制台。理解它的三重身份是避免编程错误的第一步。
3.1 R31的三重角色与访问模式
R31的访问模式取决于你是在“读”它还是在“写”它,这是两个完全不同的逻辑接口:
- 读模式 (R31 as Input):当PRU执行
LBBO(加载字节/字)指令读取R31时,它获取的是接收数据和状态标志。此时,R31的低16位(BYTE0和BYTE1)是来自RX L1 FIFO的数据,而高16位则是一系列反映当前接收状态和错误的标志位(如DATA_RDY,RX_EOF,ERROR_CRC等)。 - 写模式 (R31 as Output/Command):当PRU执行
SBBO(存储字节/字)指令向R31的特定比特位写入1时,它是在向MII_RT硬件发送控制命令。例如,写入RX_POP8位,就是命令硬件从RX L1 FIFO中弹出一个字节,从而更新R31读取窗口中的数据。非常重要的一点是:向R31写入命令,并不会影响你读取R31时得到的数据和状态值,这两个通路是独立的。
这种设计非常精妙,它允许PRU在单条指令中同时完成“读取当前数据”和“下达处理下一个数据的命令”,这对于实现单周期级别的实时响应至关重要。
3.2 接收数据路径与R31的交互流程
数据从MII引脚到PRU寄存器,主要经历两条路径。我们重点看最常用、延迟最低的直连路径:RX MII Port -> RX L1 FIFO -> PRU R31。
- 数据抵达与就绪:当MII_RT硬件组装好一个或两个字节(取决于配置)后,会将其压入32字节深的RX L1 FIFO。一旦FIFO非空,
DATA_RDY状态位就会被置位。同时,最新的数据字节会自动出现在R31寄存器的BYTE0(和BYTE1,如果WORD_RDY置位)位置。 - PRU读取数据:PRU固件通过轮询
DATA_RDY位(或利用中断)得知数据可用。然后直接读取R31的BYTE0/1即可获得数据。这里有一个关键细节:此时数据仍然停留在FIFO中,只是被“镜像”到了R31。 - PRU下达弹出命令:PRU处理完当前数据后,必须通过向R31命令接口写入
RX_POP8(弹出1字节)或RX_POP16(弹出2字节)来通知硬件。这个“弹出”操作,才会真正将数据从RX L1 FIFO中移除,并将FIFO中的下一个数据(如果有)移动到R31的镜像位置。 - 状态更新延迟:在你写入弹出命令后,
DATA_RDY、BYTE_RDY、WORD_RDY这些状态位需要约2个PRU时钟周期来更新。因此,固件在发出弹出命令后,必须等待至少2个周期再��检查这些状态位,否则会读到旧的状态,导致逻辑错误。这是一个非常常见的坑点。
// 示例:PRU C代码片段,演示读取一个字节的流程 while (!(__R31 & (1 << 16))) { // 等待 DATA_RDY 位(第16位)变为1 // 在实际应用中,这里可能需要加入超时或错误处理 } // DATA_RDY 为1,读取数据 received_byte = __R31 & 0xFF; // 读取 BYTE0 // ... 处理 received_byte ... // 发出弹出命令,准备读取下一个字节 __R31 = (1 << 14); // 假设 RX_POP8 命令对应 R31 的第14位(具体位需查手册) // !!!重要:等待2个周期让状态更新 !!! __delay_cycles(2); // 使用PRU内置延时3.3 关键状态位与错误处理详解
R31的高位包含了丰富的状态信息,是编写健壮接收程序的关键。下表列出了最核心的几个状态/错误位及其处理要点:
| 位 | 名称 | 触发条件 | 对PRU固件的意义与操作 |
|---|---|---|---|
| 16 | DATA_RDY | RX L1 FIFO中有数据可用。 | 最重要的轮询标志。为1时可安全读取BYTE0/1。弹出操作后需等待2周期再检查。 |
| 17 | BYTE_RDY | R31的BYTE0(低字节)包含有效数据。 | 通常与DATA_RDY一同判断。用于8位数据模式。 |
| 18 | WORD_RDY | R31的BYTE0和BYTE1(一个字)都包含有效数据。 | 用于16位数据模式,可提高吞吐量。同样需注意2周期延迟。 |
| 20 | RX_EOF | 一个完整的帧接收结束(RX_DV信号变低)。 | 帧边界标志。收到此标志后,应读取CRC错误位并处理帧尾。需要写1清除。 |
| 21 | RX_SFD | 检测到帧起始定界符(0xD5)。 | 可用于精确的时间戳记录,是帧开始的精确时刻。需要写1清除。 |
| 24 | ERROR_CRC | 硬件计算的CRC与帧尾的CRC不匹配。 | 严重错误。此位仅在RX_EOF有效时才有效。应丢弃该帧。通过RX_ERROR_CLR命令清除。 |
| 23 | ERROR_NIBBLE | 帧在奇数个半字节处结束(非字节对齐)。 | 违反以太网规范,通常意味着物理层错误。应丢弃该帧。 |
| 25 | RX_ERR | PHY通过RX_ER信号报告接收错误。 | 物理层错误,如链路断开、冲突等。应立即停止处理当前帧。 |
实操心得:状态位的“早期”与“同步”:手册中多次提到
RX_SFD、RX_EOF、ERROR_CRC等是“早期状态”,意味着它们在数据进入RX L1 FIFO之前就已计算好。这给了PRU一个极短的提前量去做一些预处理(比如记录精确的帧到达时间戳)。而DATA_RDY这类位是与数据同步的。理解这个差异有助于优化高精度时序应用。
错误处理流程建议:
- 始终在检测到
RX_EOF后检查ERROR_CRC和ERROR_NIBBLE。 - 如果发现任何错误,应通过
RX_ERROR_CLR命令清除错误状态位,并可选地执行RX_RESET来清空FIFO,确保从错误状态中恢复。 - 对于
RX_ERR,一旦发生,当前帧后续的所有数据都会被硬件丢弃,直到RX_DV变低。PRU应忽略此帧的所有后续数据。
4. R30寄存器与数据发送机制
如果说R31是“耳”和“令”,那么R30就是“口”。PRU通过R30寄存器将需要发送的数据递交给MII_RT硬件,由硬件完成字节到半字节流的拆分、前导码/SFD的添加以及CRC的计算与附加。
4.1 R30的数据与掩码(TXMASK)机制
R30寄存器在发送时的结构比读取时更丰富:
- 低16位 (
TXDATA[15:0]):这是PRU准备发送的数据。可以是一次写8位(使用低字节TXDATA[7:0])或16位。 - 高16位 (
TXMASK[15:0]):这是一个比特掩码,用于实现一个强大的功能——数据透传或修改。它的存在使得PRU可以在极小的延迟内,实现类似网络交换机或EtherCAT从站“转发并修改”帧头的能力。
掩码的工作原理: 发送到TX L1 FIFO的最终数据,由以下公式决定:最终发送数据 = (R30中的数据 AND TXMASK) OR (从RX L1 FIFO刚读出的数据 AND (NOT TXMASK))
这意味着:
- 如果
TXMASK的某个比特为1,则最终数据对应位来自R30。 - 如果
TXMASK的某个比特为0,则最终数据对应位来自刚刚从RX L1 FIFO读出的数据(即R31的BYTE0/1)。
应用场景:在EtherCAT从站中,报文在网络中逐站传递。每个从站需要读取报文中的指令,并将自己的状态数据写回报文的特定位置。使用TXMASK,PRU可以在接收到帧的某个字节后,在同一个或极短的周期内,将其转发出去(掩码位为0的部分),同时将自己的数据插入(掩码位为1的部分),而无需先将整个帧存储到内存再修改。这是实现亚微秒级转发延迟的关键硬件加速特性。
// 示例:假设需要转发接收到的数据,但将接收数据的第二个字节(原数据)替换为0xAB // 假设已从R31读取到2字节数据在变量 rx_data 中 uint32_t tx_word = (0xAB << 8) | (rx_data & 0xFF); // 低字节用接收的,高字节用0xAB uint32_t tx_mask = 0xFF00; // 高字节掩码为1(用R30),低字节掩码为0(用RX数据) __R30 = (tx_mask << 16) | tx_word; // 组合掩码和数据写入R30 // 然后通过R31命令接口执行 TX_PUSH16 操作4.2 发送数据路径与命令控制
数据发送的典型路径是:PRU R30 -> TX L1 FIFO -> TX MII Port。
- 数据准备:PRU将待发送数据(和掩码)写入R30寄存器。
- 推入FIFO:PRU通过向R31命令接口写入
TX_PUSH8或TX_PUSH16命令,将R30中的数据推入64字节深的TX L1 FIFO。可以连续推入多个字节/字,构成一个完整的帧。 - 硬件自动发送:当TX L1 FIFO非空,且满足一系列发送条件(如帧间间隔定时器到期、
RX_DV到TX_EN的定时器到期等)后,MII_RT硬件会自动开始发送过程:首先发送前导码和SFD,然后依次将FIFO中的数据以半字节流的形式发送到MII TX线上。 - 指示帧结束与CRC插入:当PRU将帧的最后一个数据字节推入FIFO后,它必须在同一个
TX_PUSH命令中,同时置位TX_EOF位(通过R31命令接口)。这个TX_EOF命令是硬件开始计算并附加CRC32校验码的触发器。 - CRC处理模式:如手册所述,CRC的插入有三种编程模式(Option 1/2/3)。最常用的是Option 1:在推送最后一个数据字节的命令中,同时置位
TX_CRC_HIGH、TX_CRC_LOW和TX_EOF。硬件会自动计算整个帧的CRC,并将其附加在帧尾发送出去。
关键陷阱:手册中特别用NOTE警告:如果使能了“自动生成前导码”选项(通常如此),那么第一个推送到TX FIFO的操作必须是
TX_PUSH8(推送一个字节)。如果你第一个操作就使用TX_PUSH16(推送一个字),会导致CRC计算错误,整个帧的校验码失效。这个坑非常隐蔽,一旦出错,对端设备会因CRC错误而丢弃整个帧。
4.3 发送流程示例与超时管理
一个完整的发送函数需要考虑FIFO管理、EOF标记和潜在的溢出。
// 示例:发送一个以太网帧的简化流程 void send_ethernet_frame(const uint8_t *frame_data, uint16_t length) { uint16_t i; uint32_t r31_cmd; // 1. 可选:重置TX FIFO,确保起点干净 // __R31 = (1 << TX_RESET_BIT); __delay_cycles(10); // 2. 发送数据 (假设使用字节推送) for (i = 0; i < length; i++) { // 准备数据,掩码设为0xFFFF(全部使用R30数据) __R30 = (0xFFFF << 16) | frame_data[i]; // 判断是否为最后一个字节 r31_cmd = (1 << TX_PUSH8_BIT); if (i == length - 1) { // 最后一个字节:���加EOF和CRC插入命令 r31_cmd |= (1 << TX_EOF_BIT) | (1 << TX_CRC_HIGH_BIT) | (1 << TX_CRC_LOW_BIT); } // 执行推送命令 __R31 = r31_cmd; // 3. !!!重要:简单的FIFO防溢出等待 !!! // TX L1 FIFO只有64字节。如果PRU推送太快,而MII发送较慢,会溢出。 // 一种简单策略:每推送若干字节后,延迟一段时间。更精确的做法是利用PRU循环计数器估算。 if ((i % 8) == 7) { // 每发送8字节后延迟 __delay_cycles(100); // 延迟周期数需根据实际波特率计算 } } // 4. 等待发送完成(可选,可通过查询或中断) // 硬件发送完成后,会有相应状态或中断事件。 }FIFO溢出管理:这是发送侧最常见的难题。TX L1 FIFO仅64字节,在100Mbps以太网下,填满它只需要5.12微秒。如果PRU固件在一个循环中快速写入大量数据,而MII接口正在发送前一帧,溢出就会发生。一旦溢出,必须通过TX_RESET命令复位FIFO才能恢复。可靠的固件必须实现FIFO水位管理,例如:
- 基于定时器:估算发送一个字节所需的时间(如100Mbps下为80ns),在每次
TX_PUSH后等待相应时间。 - 基于循环计数器:使用PRU的
CYCLE寄存器进行高精度延时。 - 保守设计:对于固定长度的周期性帧,可以预先计算好整个帧的发送时间,并在此时间内完成所有
TX_PUSH操作,确保不会超过FIFO容量。
5. FIFO深度管理与数据流控制实战
PRU-ICSS中的FIFO是数据流顺畅与否的“咽喉要道”。理解它们的工作原理和限制,是避免数据丢失、实现稳定通信的关键。
5.1 RX L1 FIFO:32字节的接收缓冲区
RX L1 FIFO只有32字节深度,这意味着在最坏情况下,它只能存储4个标准以太网帧(假设最小帧84字节)的一小部分。实际上,它的设计目的不是缓存整个帧,而是作为一个流水线寄存器,实现数据从MII时钟域到PRU时钟域的低延迟传递。
- 溢出与处理:如果PRU没有及时通过
RX_POP命令消费数据,而MII接口持续接收,FIFO就会溢出。溢出时,硬件会丢弃新数据,并产生PRU_RX_OVERFLOW系统事件(中断)。处理溢出没有优雅的办法:一旦发生,当前正在接收的帧就已经损坏(字节丢失)。固件必须:- 检测到溢出事件(通过轮询或中断)。
- 立即通过
RX_RESET命令复位接收路径。 - 丢弃当前不完整的帧,等待下一个帧的开始。
- 性能考量:为了避免溢出,PRU处理接收数据的循环必须足够快。在100Mbps下,一个字节到达的间隔是80ns。PRU的时钟通常是200MHz或更高(周期5ns),理论上一条指令的时间就够处理一个字节。但固件中还有其他逻辑,因此需要精心优化接收中断服务例程或轮询循环的代码路径。
5.2 TX L1 FIFO:64字节的发送缓冲区
如前所述,TX L1 FIFO的深度是64字节,略大于RX L1,但同样需要小心管理。除了溢出,还要注意下溢。
- 下溢:当硬件
TX_EN信号激活,开始从TX L1 FIFO读取数据发送时,如果FIFO为空,就会发生下溢。这会导致发送一个不完整的、错误的帧。硬件会报告发送下溢错误事件。 - 填充策略:一个稳健的发送策略是“预填充”。对于周期性发送的帧,不要在发送时刻到来时才匆忙填充FIFO。可以在一个周期开始时或空闲时,提前将整个帧的数据推入FIFO。只要确保在
TX_EN条件满足时,FIFO中已有数据即可。
5.3 RX L2 Buffer:高性能双缓冲模式
当直接路径的吞吐量或延迟无法满足需求时,可以启用RX L2 Buffer。这是一个64字节的缓冲区,被组织成两个32字节的Bank(Bank0和Bank1),以Ping-Pong方式工作。
- 工作原理:MII数据先进入RX L1 FIFO,然后被快速转储到RX L2的当前写Bank。PRU则通过高效的
XFR(外部传输)读指令,从另一个(读)Bank中一次性读取大块数据(最多32字节)。 - 优势:
- 减少中断/轮询开销:PRU可以等一个Bank快满或一帧结束时,再一次性读取大量数据,而不是每字节都处理。
- 避免溢出:双缓冲给了PRU更多的响应时间。即使PRU暂时忙于其他任务,只要能在第二个Bank被填满前处理完第一个Bank的数据,就不会丢失数据。
- 适合大数据量处理:对于需要解析整个帧头或负载的协议,批量读取效率更高。
- 使用复杂度:需要管理读/写指针(通过R18寄存器),并处理Bank切换逻辑。固件需要知道当前硬件正在写入哪个Bank,并从另一个Bank读取。这增加了软件复杂性,但换来了性能提升。
选择指南:
- 对延迟极端敏感,帧处理简单(如EtherCAT分布式时钟同步报文):使用RX L1 -> PRU直连模式,追求单字节处理的最低延迟。
- 数据吞吐量大,或帧处理逻辑较复杂:启用RX L2 Buffer,用稍高的初始延迟换取更高的整体吞吐量和更宽松的处理时限。
6. 常见问题排查与调试技巧实录
在实际开发和调试PRU-ICSS MII通信程序时,会遇到各种棘手的问题。以下是我从多个项目中总结出的常见问题清单和排查思路。
6.1 数据收发完全失败
- 症状:PRU无法收到任何数据,或发送的数据对方收不到。
- 排查步骤:
- 检查物理连接与PHY配置:确认MDIO/MDC是否正确配置了PHY芯片,PHY的链路是否已建立(Link Up)。这是最常见也是最容易被忽略的底层问题。
- 确认时钟与复位:检查PRU-ICSS模块的时钟是否使能,相关引脚复用配置是否正确,模块是否已解除复位。
- 验证MII_RT配置寄存器:仔细检查
RXCFG0/1,TXCFG等寄存器。例如,是否错误地配置为移除了前导码/SFD?TX和RX路径是否使能? - 检查PRU程序是否成功加载并运行:通过调试器或
PRU_ICSS_CTRL.CYCLE寄存器查看PRU核心是否在运行。检查程序计数器(PC)。 - 使用逻辑分析仪或示波器:这是最直接的手段。抓取MII接口的
TX_CLK,TX_EN,TX_DATA,RX_CLK,RX_DV,RX_DATA信号,看是否有波形。如果发送侧有波形而接收侧没有,问题可能在PHY或对端设备。
6.2 能收到数据但帧不完整或错位
- 症状:PRU能触发
DATA_RDY,但读取到的数据不是预期的以太网帧内容,比如目的MAC地址错误。 - 排查步骤:
- 检查半字节序和字节序:确认你理解MII是先传高位半字节。如果你在调试窗口看到R31的
BYTE0是0xEA,而逻辑分析仪上看到的前两个半字节是0xE和0xA,那就是正确的。如果反了,可能是你对数据的解释有误。 - 检查
RX_POP操作时机:是否在读取数据后忘记了执行RX_POP?这会导致R31中的数据永远不变。或者是否在RX_POP后没有等待2个周期就读取状态位,导致误判? - 检查FIFO溢出:在R31状态位中查找溢出迹象,或使能
PRU_RX_OVERFLOW中断。如果频繁溢出,说明你的PRU处理循环太慢。 - 核对帧结构:将PRU收到的原始字节流打印出来,与你在网络抓包工具(如Wireshark)中看到的同一帧的原始hex数据进行逐字节对比。这能迅速定位是哪个环节出现了错位。
- 检查半字节序和字节序:确认你理解MII是先传高位半字节。如果你在调试窗口看到R31的
6.3 发送的帧CRC校验错误
- 症状:PRU发送的帧,对端设备报告CRC错误。
- 排查步骤:
- 确认CRC插入模式:你使用的是Option 1, 2还是3?最常用且不易出错的是Option 1(在最后一个
TX_PUSH命令中同时置位TX_CRC_HIGH,TX_CRC_LOW,TX_EOF)。 - 检查“自动前导码”陷阱:这是最高频的原因!请务必确认:你的第一个
TX_PUSH操作是TX_PUSH8吗?即使你想发送一个字,也必须先发一个TX_PUSH8,后续再用TX_PUSH16。或者,在初始化配置中禁用自动前导码生成,由PRU自己发送前导码和SFD。 - 检查帧长度:CRC计算涵盖从目的MAC地址到数据域的所有字节。确保你
TX_PUSH的字节数与你声明的或协议要求的帧长度一致。多一个或少一个填充字节都会导致CRC错误。 - 手动计算校验:作为一个调试方法,可以暂时在PRU程序中禁用硬件CRC(通过配置寄存器),由软件计算CRC并作为数据的一部分推入FIFO。如果这样发送的帧CRC正确,那问题就出在硬件CRC插入逻辑上。
- 确认CRC插入模式:你使用的是Option 1, 2还是3?最常用且不易出错的是Option 1(在最后一个
6.4 实时性不达标或偶尔丢帧
- 症状:系统大部分时间正常,但在高负载或特定时序下出现延迟增大或丢帧。
- 排查思路:
- 量化性能指标:使用PRU的
CYCLE寄存器,在RX_SFD中断服务程序开始和结束处打点,精确测量从帧到达处理完毕的延迟。分析瓶颈在哪里。 - 检查共享资源竞争:PRU是否与ARM核心或其他PRU核心共享某些内存或外设?是否存在总线仲裁导致的延迟?考虑使用PRU的局部内存(Data RAM)而非共享内存。
- 优化代码路径:PRU汇编或C代码是否高效?循环是否紧凑?避免在关键路径上进行复杂的数学运算或内存访问。使用寄存器变量。
- 分析中断负载:如果使用了PRU系统事件(中断),评估中断频率是否过高。高频中断本身会带来开销。对于极高速数据流,轮询
DATA_RDY可能比中断更高效。 - 压力测试与边界条件:构造最坏情况下的数据流(如背靠背最小帧)进行测试,看系统能否持续处理。
- 量化性能指标:使用PRU的
调试PRU程序,尤其是涉及精确时序的MII通信,系统性的日志和性能计数至关重要。可以在PRU的Data RAM中开辟一个区域作为调试日志缓冲区,记录关键事件(如RX_SFD、RX_EOF、错误标志等)及其对应的循环计数器值。然后由ARM核心定期读取并分析,这比单步调试更能反映真实运行时的状态。