ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MII模式下CRS信号处理实战:以LAT1595为例的完整指南

2026/8/29 13:13:40 拓冰建站 浏览量
MII模式下CRS信号处理实战:以LAT1595为例的完整指南 1. 为什么MII模式下的CRS信号成了“烫手山芋”做嵌入式网络开发的朋友应该都有过这种经历硬件原理图上明明把PHY的CRS、COL引脚连到了MAC软件里配的也是标准的MII模式可跑起来就是不对——要么收包错乱要么系统偶尔卡死最头疼的是问题还不好复现。我刚开始调LAT1595这颗芯片的Ethernet接口时就在这个坑里蹲了差不多一周最后翻到datasheet里一句不起眼的注释才恍然大悟MII模式下CRS信号的处理并不是“按照标准连上就行”那么简单。先说清楚一个基本事实MIIMedia Independent Interface是IEEE 802.3定义的一种MAC与PHY之间的标准接口数据通路是4位宽时钟25MHz整体速率100Mbps。它区别于RMIIReduced MII的地方在于MII保留了完整的信号集合包括TX_ER、RX_ER、CRS和COL而RMII为了省引脚把这些状态信号砍掉了大半只留了一个CRS_DV复用信号。正因如此很多从RMII转过来做MII的工程师会对CRS这种“在RMII里根本没单独出现过”的信号感到陌生不知道它到底该怎么接、怎么配、怎么用。MII信号组里CRSCarrier Sense载波侦听的作用是告诉MAC介质上有载波活动也就是线路正处于“忙”的状态。只要TX或RX方向上有活动CRS就会被PHY拉高。在10Mbps模式下CRS在帧传输期间保持有效在100Mbps模式下CRS在帧前导码和帧数据期间有效。从协议层面看CRS是CSMA/CD载波侦听多路访问/冲突检测机制的一部分用于避免冲突但在全双工模式下这条信号的作用就很微妙了——因为全双工本身不依赖载波侦听来判断能否发送它只关心对端是否在发数据。所以问题来了在全双工MII模式下CRS到底怎么处理是忽略、是拉高、还是照常接这恰恰是LAT1595这类芯片手册里说得最含糊、而实际工程中最容易出错的地方。这篇文章我就以LAT1595的Ethernet接口为例把我调MII模式时摸出来的CRS信号处理思路、Kconfig/设备树配置方式、以及那些“手册没写但实测会翻车”的细节一起整理出来。适合正在调LAT1595、或者用其他带MII接口的MAC芯片且被CRS/COL信号困扰的开发者参考。2. 先弄懂CRS在MII里的角色全双工与半双工的天壤之别2.1 从CSMA/CD机制说起要理解CRS为什么重要得先回到Ethernet最原始的机制。早期的Ethernet是共享介质网络所有节点挂在同一条同轴电缆上谁想发数据得先“听”一下线路上有没有人在发。这个“听”的动作就是载波侦听。CRS就是PHY把这个“听到的结果”上报给MAC的物理信号。在100Mbps半双工模式下MAC发送前必须确认CRS为低才认为线路空闲可以开始发送如果发送过程中CRS突然变高对端也在发就说明发生了冲突这时COL信号会被拉高MAC需要执行退避算法等待随机时间后重发。这套流程就是CSMA/CD。而在全双工模式下链路两端使用独立的收发通道MAC发送数据时不需要管线路上有没有其他节点在发送因为对端的发送走的是另外一对线。这样CRS就没有存在的必要了。IEEE 802.3规范里也说明了在全双工模式下PHY可以不产生CRS或者MAC不需要响应CRS。但问题在于很多PHY芯片并不清楚自己连接的上游MAC到底工作在什么模式。CRS是PHY根据物理线路状态主动拉高/拉低的它不知道MAC是全双工还是半双工。所以MAC一侧必须在逻辑上正确处理这个信号要么根据双工模式主动忽略它要么在硬件上把它处理成不造成影响的状态。2.2 为什么CRS处理不当会“卡死”系统如果MAC内部逻辑没有正确屏蔽CRS在配置为全双工时仍然把CRS当作“忙”信号来阻塞发送队列就会出现一个很隐蔽的现象PHY在收到远端数据时拉高CRSMAC看到CRS高就推迟自己的发送于是发送延迟变得不稳定时延抖动很大极端情况下如果PHY在链路空闲时因为噪声或者其他原因误触发CRS这在某些PHY上是存在的MAC就会被“锁死”在等待状态表现就是整个网络接口看起来瘫了但寄存器和中断都是正常的。这还不是最糟的。更常见的一个问题是CRS和COL信号在MAC芯片内部如果被送到中断控制器或者DMA引擎一旦PHY在上电初始化或link down/up切换瞬间产生毛刺就可能触发MAC的异常中断干扰驱动的正常运行。LAT1595这类集成MAC的FPGA芯片如果信号没有在FPGA内部做同步和过滤毛刺很容易被采集到进而影响状态机的正常工作。2.3 半双工场景下CRS是真的要用的与全双工相反半双工模式下CRS是必须使用的。虽然现在绝大多数网络设备默认跑全双工但在一些工业现场老旧的集线器Hub仍然存在此时链路协商结果会是半双工。如果MAC在硬件层面直接把CRS屏蔽掉半双工模式下的冲突检测就彻底失效了两个节点同时发送时不会退避数据大量冲突网络退化到几乎不可用。所以在设计阶段就要想清楚产品是只支持全双工还是需要兼容半双工如果需要兼容那CRS必须被正确引入MAC逻辑而不能简单地“接个下拉电阻了事”。3. LAT1595的MII接口信号全览除了CRS还有哪些容易忽略的引脚3.1 标准MII信号分组先列出完整的MII接口信号方便对照检查硬件连接。MII总共需要16根信号线不包含管理接口MDIO/MDC分为发送、接收、状态和管理四类信号组信号名方向MAC视角说明发送数据TXD[3:0]输出4位并行发送数据发送控制TX_EN输出发送使能高有效发送错误TX_ER输出发送错误指示发送时钟TX_CLK输入25MHz100M/ 2.5MHz10M接收数据RXD[3:0]输入4位并行接收数据接收控制RX_DV输入接收数据有效接收错误RX_ER输入接收错误指示接收时钟RX_CLK输入25MHz100M/ 2.5MHz10M状态CRS输入载波侦听状态COL输入冲突检测管理MDC输出管理时钟管理MDIO双向管理数据注意TX_CLK和RX_CLK的方向是相对于MAC的输入。这两个时钟分别由PHY根据自身链路速率产生MAC不能反向提供时钟给PHY那是RMII模式下的做法。这一点在FPGA工程里特别容易被混淆尤其是之前做过RMII设计的人——RMII模式下50MHz参考时钟由MAC侧的时钟源提供而MII模式下MAC必须被动接收PHY送来的TX_CLK和RX_CLK。如果LAT1595的FPGA逻辑里把TX_CLK当输出引脚去驱动硬件上必然会打架。3.2 CRS和COL的内部逻辑位置在LAT1595内部MII接口通常挂在一个EMACEthernet MAC软核或硬核上。CRS和COL信号进入MAC核之后会被送到一个叫“flow control / duplex control”的逻辑块。全双工模式下该逻辑块会忽略CRS和COL半双工模式下CRS用于载波侦听COL用于冲突检测驱动退避算法的状态机。这里有一个可能在FPGA工程里遇到的实际问题CRS和COL在MAC核内部如果被当作异步信号直接使用而它们来自PHY且与RX_CLK25MHz同步那么如果MAC核内部用TX_CLK域去采样就会产生跨时钟域问题。LAT1595的MAC核通常已经做好了同步处理但当我们在FPGA里自己扩展逻辑时比如想用CRS做链路空闲判断、流量统计等就一定要自己加两级同步器否则大概率出现亚稳态导致的偶发逻辑错误。3.3 PHY侧与MAC侧的连接映射关系从PHY角度看CRS和COL是输出信号MAC侧是输入信号。PHY内部根据RXD、RX_DV、TX_EN、TXD等信号综合出CRS只要RX_DV有效或者TX_EN有效CRS就置高。COL则是当PHY的接收和发送同时发生时半双工模式下被拉高。所以CRS的时序其实和RX_DV、TX_EN是严格相关的它不是一个独立的随机信号而是由收发活动“组合”出来的。这个特性意味着如果PHY芯片的CRS引脚在硬件设计时被悬空PHY内部逻辑可能仍然能正常工作但CRS引脚会因为浮空而随机跳动MAC侧如果读了它就可能得到错误的“忙”状态。反过来如果CRS被接到一个电平固定的引脚比如直接接GND那PHY输出的实际CRS信号就被外部电路强制拉低了半双工模式下MAC无法侦听载波网络行为异常。所以CRS在硬件上不能随意绑死要根据模式需求处理。4. 全双工MII模式下CRS的典型处理方案与取舍4.1 方案一在PHY侧配置寄存器让CRS不再产生不少PHY芯片如Marvell、Broadcom、Realtek的常见千兆/百兆PHY提供寄存器位来控制CRS功能。最典型的是在100M全双工模式下通过PHY的特定寄存器例如Marvell的Copper Control Register、Broadcom的Shadow Register等把“Carrier Sense”功能关掉或者把CRS引脚设置为强制低电平输出。这样做的好处是MAC侧完全不用关心CRS硬件上把引脚接到固定的高/低电平即可。缺点也很明显PHY寄存器配置需要额外的初始化代码而且不同厂商的PHY寄存器映射不一样驱动代码的通用性会下降。此外一旦以后需要切换半双工模式比如现场被强制到10M半双工这套配置就会导致载波侦听失效。从实际项目角度看如果产品定义明确“只做千兆/百兆全双工永不支持半双工”那么在PHY寄存器里关掉CRS是可行且干净的。但一旦产品需要兼容工业现场的老旧交换机或Hub建议不要这么做。4.2 方案二在MAC侧忽略CRS只保留内部逻辑上的“虚拟载波侦听”这是目前绝大多数主流MAC/驱动采用的方案。MAC核在全双工模式下内部逻辑自动忽略外部CRS信号发送行为完全由内部FIFO状态和流控机制控制不等待CRS。与此同时MAC仍然向驱动软件上报链路状态link up/down这个状态来自PHY的寄存器通过MDIO读取而不是来自CRS引脚。这样做的好处是PHY不需要做特殊配置硬件连接保持标准MII引脚定义即可驱动代码可移植性最好。缺点是不能完全“物理上不管”——引脚还是得接好因为PHY在链路刚建立、自协商过程中可能会让CRS出现跳变如果MAC核没有对CRS做去抖处理这些跳变可能被上层误判为链路抖动。在LAT1595的FPGA设计中通常做法是MAC核实例化时把duplex mode配置为全双工并确认MAC核内部已经把CRS、COL的输入在逻辑上忽略。如果你用的是软核MAC比如Lattice的Ethernet MAC IP或第三方IP需要查看IP核的端口说明有些IP核干脆不把CRS/COL引出来而是在IP内部直接接地有些IP核则保留这两个信号需要用户自己处理。我自己用LAT1595的MAC IP核时发现该IP在全双工模式下默认把CRS和COL输入当作“dont care”但综合工具仍然要求这两个信号有驱动源不能悬空。所以在FPGA顶层我把CRS和COL的FPGA引脚约束为输入然后在RTL里统一连接到MAC核的对应端口同时在模块内部加了一级同步器将PHY来的CRS同步到RX_CLK域后再送进MAC核。同步器输出的信号即便没有被MAC核使用也保证不会有亚稳态问题传导到其他逻辑。4.3 方案三硬件上通过下拉/上拉电阻固定电平在电路板设计层面有些人会直接把CRS和COL引脚通过10kΩ电阻下拉到地或者上拉到高电平认为这样“既满足了引脚不能悬空的要求又不会影响正常工作”。实际上这个做法需要谨慎。如果下拉到地CRS恒为低PHY侧正常工作在半双工模式时MAC将永远认为线路空闲冲突检测失效。如果上拉到高CRS恒为高MAC在半双工模式下会一直认为线路忙永远不发送数据。这两个结果在全双工模式下来看问题不大因为MAC忽略CRS但一旦链路协商成半双工行为就是灾难性的。所以如果你采用固定电平方案前提必须是产品软件层强制指定全双工模式并且自协商被关闭或只允许全双工。否则我建议还是老老实实把CRS接入MAC让协议栈自己处理。4.4 我的推荐全双工为主、兼容半双工的折中做法结合LAT1595的实际场景我推荐的做法是硬件上完整接入CRS和COL不悬空、不完全固定电平。MAC核运行在全双工模式软件配置强制全双工关闭自协商中的半双工能力通过设置PHY的advertised abilities寄存器。PHY侧不主动关闭CRS保持默认行为。FPGA内部对CRS、COL添加同步器和可选的去抖动滤波防止link切换瞬间的毛刺被MAC状态机误采。驱动层不依赖CRS判断链路状态链路状态只从PHY的Link Status寄存器读取。这个方案的好处是正常工作时CRS完全不影响数据通路全双工的吞吐和延迟都正常万一以后需要兼容半双工场景更换PHY、或修改PHY寄存器只需要把MAC核配置改成半双工硬件无需改动FPGA的同步器和信号通路仍然有效。灵活性最高改造代价最小。5. 踩坑实录LAT1595上CRS处理不当的真实报错与排查过程5.1 现象描述系统偶尔“整个网络接口消失”我最初调试LAT1595时遇到的问题很诡异FPGA加载完成后网络接口能正常起来ping也通但运行十几分钟到几十分钟不等网络接口会突然“消失”——ifconfig里看不到网卡或者网卡还在但ping不通。而且这种现象在低温环境下更容易复现温度低的时候基本十几分钟必现。第一次遇到时我第一反应是PHY芯片不稳定换了PHY芯片、换了晶振问题依旧。又怀疑是电源纹波加了电容滤波也没有本质改善。排查了两三天整个人都是懵的。5.2 排查链路从软件栈逐步逼到硬件信号我的排查顺序是这样的查看内核日志确认网卡驱动是否报错。结果没有任何error级别的消息只是在故障瞬间出现过一次“tx timeout”的警告。查看PHY状态通过MDIO读取PHY寄存器发现故障时PHY的Link Status从“up”变成了“down”但随即又恢复up。也就是说PHY认为链路短暂断开了一下。用示波器抓PHY的链路状态引脚如果有的话发现这个引脚并没有变化说明PHY的物理链路其实没问题但寄存器里的Link Status位发生了一次翻转。检查MDIO总线上是否有干扰发现MDIO在故障瞬间出现了一串异常波形。继续追发现是MAC侧主动发起了一笔MDIO读操作读的恰好是PHY的Link Status寄存器。继续向上追为什么MAC会突然读Link Status因为MAC收到了一个link change事件。再追发现link change事件来自MAC核的“link status change”中断而这个中断的触发条件是——CRS信号出现了一个由高到低的跳变。到这里真相大白了PHY在正常运行过程中CRS本身会因为远端偶尔发来的广播包、ARP包而拉高再拉低这个拉高再拉低的动作被MAC核当作“链路状态变化”上报给了中断控制器于是驱动就误以为链路down了触发了一连串的PHY寄存器读取和重新协商流程。虽然链路最终恢复了但在这期间数据通路会被暂时挂起表现就是网络接口“消失”了一段时间。5.3 根因分析MAC核把CRS当作链路状态信号了为什么MAC核会把CRS当作链路状态变化呢查了LAT1595的MAC IP核说明文档发现它在“链路状态检测”部分有一个设计选择当CRS信号从高变低时MAC核会触发一次“carrier lost”事件。这个事件在IP核内部被映射到了中断控制器用于表示“载波丢失”。在半双工模式下这个事件确实有意义——载波丢失意味着冲突退避结束或者链路空闲。但在全双工模式下CRS的跳变只是正常收发活动的体现不应该被视为“链路故障”。问题就在于IP核的默认配置里这个事件在两种模式下都被上报了导致全双工模式下会出现误报。5.4 解决在FPGA内部滤除CRS在正常收发时的跳变定位到根因后解决方案就比较清晰了在全双工模式下不能让CRS的每一个下降沿都直达MAC核的链路状态检测逻辑需要做“语义翻译”——把“CRS跳变”转换成MAC核真正关心的“链路物理断开”。链路物理断开的特征是RX_CLK消失、RX_DV长时间无活动、或者PHY的Link状态寄存器翻转。这几种情况与CRS的正常跳变有本质区别。于是我做了两件事第一把FPGA输入的CRS信号做了一级同步和滤波。用一个几十微秒的窗口对CRS做“确认”只有当CRS保持低电平超过这个窗口才认为载波真的丢失如果只是短暂跳变就过滤掉。这个窗口时间可以根据实际PHY的行为调整太小了滤不掉毛刺太大了会延迟链路down事件的检测。第二在驱动层面把MAC核的事件掩码寄存器重新配置在确认模式为全双工时屏蔽掉“carrier lost”事件上报只保留真正的link down中断由MDIO状态寄存器变化触发。改完之后跑了一个星期的长时间压力测试和低温测试问题再也没有复现。这里也提醒一下如果你的MAC核提供了中断掩码寄存器一定要去查datasheet里每个中断位的触发条件不要只看名字想当然。6. 软件配置要点设备树、驱动与PHY寄存器的配合6.1 设备树中MII模式的描述方式在Linux系统下使用LAT1595的Ethernet MAC时设备树里需要明确指定phy-mode为“mii”。设备树节点大致长这样emac0 { status okay; phy-mode mii; phy-handle phy0; phy0: ethernet-phy0 { reg 0; /* PHY地址为0按实际硬件配置 */ }; };phy-mode的值直接决定了MAC核的接口配置。如果这里误写成“rmii”MAC核会按RMII的引脚定义去采样届时TX_CLK/RX_CLK的相位关系完全不对网络接口根本起不来。6.2 驱动里关于双工模式的处理在驱动初始化时需要读取PHY的协商结果并据此配置MAC核的双工模式。这里有一个容易踩的坑有些驱动只在link up时配置一次双工模式但如果链路在运行中被重新协商比如对端重启双工模式可能发生变化而MAC核没有同步更新就会导致收发行为异常。所以建议在驱动的adjust_link回调函数里每次链路状态变化时都重新把双工模式写入MAC核寄存器同时根据双工模式动态决定是否处理CRS/COL中断。伪代码如下static void adjust_link(struct net_device *ndev) { struct phy_device *phydev ndev-phydev; int duplex; if (phydev-link) { duplex phydev-duplex; /* 配置MAC核双工模式 */ mac_write_duplex(ndev, duplex); /* 全双工时屏蔽carrier lost中断半双工时打开 */ if (duplex DUPLEX_FULL) mac_enable_carrier_lost_irq(ndev, false); else mac_enable_carrier_lost_irq(ndev, true); } }这里最关键的一点是不要假设链路永远不会从全双工切到半双工。工业现场的对端交换机可能在调试中被重启、换配置链路重新协商后可能改变双工模式。如果你在驱动里写死了“只匹配全双工”那么一旦对端变成半双工你的MAC核还是按全双工逻辑运行CRS被忽略冲突检测失效网络性能会断崖式下降。6.3 PHY寄存器侧的关键配置PHY初始化时需要把本端支持的速率/双工能力设置好。以最常见的百兆PHY为例需要操作的是PHY的ANARAuto-Negotiation Advertisement Register地址4和BMCRBasic Mode Control Register地址0寄存器。如果你想关闭半双工能力让链路只能协商到全双工可以这样操作/* 读取ANAR寄存器 */ u16 anar phy_read(phydev, MII_ADVERTISE); /* 清除100Base-TX Half和10Base-T Half位保留Full位 */ anar ~(ADVERTISE_100HALF | ADVERTISE_10HALF); phy_write(phydev, MII_ADVERTISE, anar); /* 重启自协商 */ u16 bmcr phy_read(phydev, MII_BMCR); bmcr | BMCR_ANRESTART; phy_write(phydev, MII_BMCR, bmcr);这样配置之后对端即使只支持半双工两端协商不出双方都支持的half模式就只能放弃协商或者协商到全双工如果对端也支持全双工。如果对端是只能半双工的老旧设备那么链路协商会失败网络接口无法起来——这其实是好事因为半双工和全双工强制错配导致的网络瘫痪比link down更可怕。6.4 用mdio-tools手动验证PHY行为在调试过程中我强烈建议在Linux用户态使用mdio-tools或老版本的mii-tool来手动读写PHY寄存器快速验证硬件连接和PHY配置是否生效。例如# 查看PHY基本状态 mii-tool eth0 # 读取PHY的ANAR寄存器地址4 mdio-tools eth0 reg read 4 # 读取PHY的基本模式控制寄存器地址0 mdio-tools eth0 reg read 0如果你发现mii-tool的输出显示“negotiated 100baseTx-HD”或“100baseTx-FD”但驱动里配置的模式与之不符那问题基本就锁定在MAC核模式配置上。这种软件工具定位问题的方法比反复猜硬件原因要高效得多。7. 关于CRS信号处理的最终建议清单调试MII接口的CRS信号我总结下来其实就几条原则但每条背后都有真实的“学费”。整理成清单放在这里方便你做硬件设计评审或写代码时对照自查。硬件设计层面CRS和COL引脚必须接入MAC不能悬空。如果MAC核确实用不到也要接上下拉电阻固定到确定电平避免浮空输入带来的不确定电流消耗和状态抖动。如果决定使用固定电平方案务必确保软件强制全双工且关闭自协商中的半双工能力否则链路协商到半双工时行为会异常。PHY的CRS、COL与MAC之间不需要串联电阻或滤波电容除非datasheet有特殊要求正常走线即可。但要注意信号完整性CRS/COL属于慢速状态信号对走线长度要求不高不过尽量远离TX_CLK/RX_CLK这类高速时钟线避免耦合噪声。FPGA逻辑层面进入MAC核的CRS信号建议先做两级同步器同步到RX_CLK域再做可选的脉冲滤波。不要直接把来自PHY引脚的电平信号接入MAC核内部逻辑。如果MAC核提供了中断掩码检查每个中断位的触发条件尤其是与CRS/COL相关的事件在全双工模式下要评估是否需要屏蔽。不要在多个时钟域里直接使用CRS做判断。CRS本身与RX_DV/TX_EN同步产生但它跨越了RX和TX两个时钟域所以任何基于CRS的逻辑都要明确自己处在哪个时钟域避免跨域直采。软件驱动层面链路状态判断一律以PHY的Link Status寄存器为准不要依赖CRS。CRS反映了介质活动但不等同于链路物理连接状态。在adjust_link回调里动态更新MAC核的双工模式并动态使能/屏蔽CRS相关中断。不要只配置一次。通过设备树明确配置phy-mode为“mii”不要靠“默认值”蒙混过关。默认值往往是“不工作”的同义词。测试验证层面全双工压力测试要包含多种帧长混合流量观察是否有“praticle lost”或tx timeout异常。半双工测试建议使用一个真正的Hub不是交换机来连接两个节点制造一个真实的冲突环境验证退避算法是否正常工作。低温环境测试对信号毛刺问题很有帮助很多偶发故障在低温下更容易暴露如果你的产品有温度范围要求一定要做高低温摸底。8. 附LAT1595以外这个方法对谁同样有效这篇文章虽然一直以LAT1595为背景但CRS信号处理的原则并不仅限于这一颗芯片。任何带MII接口的MAC无论是FPGA软核Lattice、Xilinx、Altera的Ethernet MAC IP还是MCU内嵌的MACSTM32、NXP、Microchip等在处理CRS时面临的本质问题是一样的全双工模式下这个信号是否会被误用、半双工模式下是否还能正确参与冲突检测。如果你用的是带MII接口的MCU处理方式可能更简单一些因为MCU内部MAC核的寄存器已经把CRS/COL行为封装好了驱动代码通常也是厂商提供的你需要做的只是在初始化时正确配置双工模式并确认中断屏蔽位。但原理是一样的。而FPGA方案因为有灵活性反而更容易踩坑——因为一切都暴露在RTL层稍不留神就会把信号处理错。最后说一点个人体会调试这种“看起来很简单但实际很玄学”的信号最重要的是建立一套可靠的观测手段。我后来在FPGA里加了一个调试探针把CRS、COL、RX_DV、TX_EN这几个信号实时映射到GPIO上用逻辑分析仪去抓。一旦网络出现异常先看这几个信号的状态基本能立刻定位是PHY侧行为异常还是MAC侧逻辑处理有问题省去了大量猜测的时间。如果你也在调类似的接口建议从一开始就把观测手段预留好别等问题出现了再临时加。