做嵌入式开发,只要碰过以太网功能,十有八九会遇到这么一种情况:板子焊好了,PHY芯片装上了,RJ45也压好了,结果Linux起来网口就是起不来;或者Link灯一亮一灭,终于稳定Link了,一跑吞吐量又疯狂掉包。很多人第一反应就是查驱动、查DMA描述符、查内核配置,折腾半天还不知道问题出在硬件还是软件。我自己的体会是,与其在代码里大海捞针,不如先把以太网这条数据通路上MAC、PHY、MII这几个角色的分工彻底吃透。硬件上谁负责组帧、谁负责编码,数据走过哪些引脚,软件能看到哪一层、看不到哪一层,把这些串起来之后,绝大多数问题都能很快定位到具体模块。
这篇内容适合三类人:一是刚开始碰嵌入式以太网的开发者,想弄明白STM32或FPGA外接PHY芯片时,那个MII/RMII接口到底在干什么;二是做硬件或驱动的人,想知道为什么有的板子引MII、有的引RGMII,接口选择跟速率、引脚、PCB成本到底有什么关系;三是对以太网帧结构有好奇心,想搞清楚Wireshark里看到的MAC地址、长度字段是在哪一层被处理的。今天这一篇先把MAC、PHY和MII家族接口这条主干道讲明白,属于整个以太网系列的第一篇。
1. 一条以太网链路里到底有几个角色在干活:先厘清MAC与PHY的边界
1.1 从OSI分层到实际芯片:MAC是逻辑,PHY是物理
大多数人谈网络协议都从OSI七层模型开始,但落到实际硬件上,我们真正关心的其实就是二层和一层。数据链路层的下半部分叫MAC子层,物理层则由PHY芯片实现。平时大家说的“网卡”其实是一个很笼统的概念,在一些板卡上它可能只是一个PCIe接口的独立控制器,内部同时集成了MAC和PHY;但在单片机、FPGA这类嵌入式系统里,MAC往往以内置IP核或外设形式存在于主控芯片内部,PHY则是外面一颗独立的小芯片。这两颗芯片各管一段,中间的连接接口就是MII家族。
MAC负责的是逻辑层面的事:把上层IP包封装成以太网帧,填好目的MAC、源MAC、类型字段,算好FCS校验码,再通过发送FIFO交给PHY。PHY负责的是物理层面的事:把MAC送来的并行数据变成能在网线上传输的电平信号,同时从网线接收到的模拟信号中恢复出时钟和数据,再交还给MAC。如果一定要打个比方,MAC像快递公司里负责贴面单、分拣包裹的人,PHY则像驾驶员,负责把包裹从仓库送到另一个城市;MII接口就是仓库门口那条装货通道。
搞清这个边界对调试非常关键。很多驱动问题被误判为“PHY不工作”,实际上MAC侧DMA配置根本就没配对;也有很多硬件问题被误判为“驱动bug”,实际上PHY的参考时钟根本没起来。把分层边界画出来,你才知道该拿示波器量哪里、该拿工具读哪里。
1.2 数据帧的接力赛:从DMA到RJ45,每一棒都在忙什么
我自己排查问题时习惯把一条完整的发送链路拆成几棒,每一棒都单独验证。以最典型的MCU+外置PHY为例,一个帧从CPU到网线的实际路径是这样的:
- CPU把要发送的数据放在内存,DMA描述符告诉MAC控制器“数据在哪、多长”。
- MAC从内存取出数据,计算CRC32,插入前导码、SFD、源MAC、EtherType等字段,按MAC层的规则组装成完整帧,放进发送FIFO。
- MAC通过MII/RMII/GMII/RGMII这些并行接口,把帧数据按位宽和时钟节拍送给PHY。
- PHY收到并行数据后,先做物理层编码(10M的曼彻斯特、100M的4B/5B、千兆的PAM5之类),再做电平/线缆驱动,最终从差分对送出。
- 信号经过隔离变压器、RJ45、网线,到达对端设备的PHY,然后反向走一遍:解码、去掉前导码、恢复并行数据、按接口协议交给对端MAC。
接收路径正好反过来。这里有一个重要概念:驱动能看到的只有MAC这一侧,比如DMA描述符里的状态、MAC接收错误计数、中断标志;而PHY内部的链路状态、自协商结果、信号质量,需要通过MDIO/MDC管理接口去读寄存器。所以“软件能管到哪一层”决定了你的排查手段。
1.3 “媒介无关接口”这个名字,本身就是答案
MII的全称是Media Independent Interface,直译就是“媒介无关接口”。这个词不是随便起的,它道出了MAC和PHY分离设计的核心动机:MAC只需要跟PHY通过一个标准接口打交道,至于PHY后面接的是双绞线、光纤、还是车载的单对线,MAC根本不需要关心。同样一个MAC IP核,今天配百兆铜口PHY,明天配千兆光模块PHY,只要接口符合约定义务,MAC侧不用大改。
这也是为什么车载以太网、工业以太网、普通以太网在MAC层几乎可以复用同一套软件栈,差异都集中在PHY一侧。“媒介无关”是整套设计的灵魂,理解了它,你就理解了为什么MII家族会演化出那么多版本:接口标准越统一,产业链解耦越彻底,做SoC的和做PHY芯片的厂商才能各自发挥。
2. MAC层到底在忙什么:帧结构、地址过滤与差错检测的交接逻辑
2.1 以太网帧的完整构造:抓包工具不会显示的字段
以太网帧格式很多人背过,但真到调试时往往发现和抓包软件看到的对不上,原因在于抓包软件通常不会把前导码和帧间隙显示出来。一个标准的802.3以太网帧从介质上看其实长这样:
| 字段 | 长度 | 作用 |
|---|---|---|
| 前导码Preamble | 7字节 | 用于接收端时钟同步,每个字节固定0x55 |
| SFD帧起始定界符 | 1字节 | 标记帧内容正式开始 |
| 目的MAC地址 | 6字节 | 接收方地址,广播就是全FF |
| 源MAC地址 | 6字节 | 发送方地址 |
| EtherType/Length | 2字节 | 大于或等于0x0600时是类型字段,如0x0800表示IPv4 |
| Payload数据 | 46-1500字节 | 上层协议数据,不足需补零 |
| FCS帧校验 | 4字节 | 对目的MAC到Payload做CRC32的结果 |
之所以有46字节的最小数据限制,是为了保证整个帧从目的MAC到FCS至少有64字节。在经典半双工以太网里,这个最小帧长度和碰撞检测窗口直接相关:发送端必须在发出64字节之前就能判断是否发生了碰撞,否则一个短帧发完了都不知道自己有没有和别的设备撞车。千兆以太网在半双工模式下为了维持这个机制,还额外引入了载波扩展和帧突发,把发送窗口拉长。
2.2 单播、广播、组播与混杂模式:MAC怎么决定“这包是不是我的”
MAC地址一共48位,前24位是厂商OUI(组织唯一标识符),后24位由厂商分配。第一个字节的最低位是I/G位:0表示单播地址,1表示组播或多播地址;第二个低位是U/L位,0表示全球唯一,1表示本地管理地址。这也是为什么组播MAC地址通常以01开头,而广播地址FF:FF:FF:FF:FF:FF其实就是“所有位全1”的特殊组播。
接收方向,MAC控制器会做一层很硬的过滤逻辑:单播帧必须和本机MAC地址完全一致才接收;广播帧无条件接收;组播帧则看有没有使能对应组播组,很多硬件用哈希表实现,不一定精确匹配;如果驱动把网卡设成混杂模式,那么所有帧都收下来,Wireshark抓包就是靠这个实现的。日常有人问“MAC地址怎么查”,Windows用ipconfig /all,Linux用ip link show或ethtool -P eth0,但在批量产品上更该注意的其实是:不要每台设备都用默认随机MAC,最好在bootloader阶段把唯一MAC烧进Flash,再写入MAC控制器的地址寄存器,否则同一局域网里两台设备就可能互相打架。
2.3 CRC32与CSMA/CD:今天仍需理解的老机制
FCS字段用的CRC32多项式是0x04C11DB7,接收端重新算一遍,结果不匹配就把整个帧丢掉,驱动里表现成rx_crc_errors一类的统计计数在涨。这个计数是我排查“能通但丢包”时必看的指标:如果它涨得厉害,要么线上干扰,要么PHY和MAC之间的数据线采样有问题,要么晶体频率偏差太大。
CSMA/CD是另一个绕不开的历史包袱。半双工模式下,发送前要监听信道,发送中要检测碰撞,碰撞后按退避算法重传。现在的主干网络基本都是交换机和全双工,每个端口一个冲突域,CSMA/CD已经没有实际作用,对应的MII接口信号CRS、COL在全双工下也基本闲置。但你仍要理解这套机制,因为工业现场、老旧设备、某些串口服务器还会用半双工,而且自协商出错时,设备可能会从全双工掉到半双工,出现“能通但速度极慢”的怪现象。
3. PHY层不只是收发器:编码、自协商与MDIO寄存器
3.1 从曼彻斯特到PAM5:以太网编码到底在解决什么问题
很多人觉得PHY就是“把数字信号转成模拟信号发出去”,实际上PHY内部特别忙,编码就是它最核心的活之一。为什么要编码?因为接收端要从数据流里恢复时钟,不能允许长时间出现连续的0或1;同时信号经过变压器和长线缆时还要考虑直流平衡、EMI和抗干扰能力。
不同的以太网速率用了完全不同的编码方式:
| 速率/标准 | 编码/线路码 | 备注 |
|---|---|---|
| 10BASE-T | 曼彻斯特编码 | 每个bit中间必然有跳变,时钟恢复简单,带宽开销大 |
| 100BASE-TX | 4B/5B + MLT-3 | 4bit扩成5bit,再用三电平调制成形,降低高频分量 |
| 1000BASE-T | PAM5 | 4对线同时双向传输,每对250Mbps,五电平编码 |
| 1000BASE-X / SGMII | 8B/10B | 8bit扩成10bit,用于光口和串行PHY接口 |
我见过不少新工程师误以为所有千兆PHY都用8B/10B,实际上1000BASE-T铜口走的是PAM5,8B/10B更多出现在光模块和SGMII这类串行链路上。编码这件事决定了同样一段网线在100M和1000M下对噪声的敏感程度不同,也决定了你在PCB布线、变压器选型时的要求不同。
3.2 自协商:网线两端是怎么“谈判”的
PHY上电之后,并不会一上来就按最高速率跑,而是先和链路对端“谈判”一轮。10BASE-T时代PHY会定期发Normal Link Pulse表示自己在线,100BASE-TX升级成FLP快速链路脉冲,里面携带了本端支持的能力信息:速率、全/半双工、流控等。两端PHY交换能力后,按优先顺序选一个双方都支持的最高组合,通常优先级是:千兆全双工 > 千兆半双工 > 百兆全双工 > 百兆半双工 > 10M全双工 > 10M半双工。
这里有个非常经典的坑:现场为了“稳定”,把交换机某个口强制成100M全双工,但设备端没有关闭自协商。结果对端发来的FLP里没有可协商的“100M全双工”对应项时,本端可能会落到100M半双工。链路看起来是通的,Ping也通,但只要流量一上来,半双工侧的碰撞和冲突会直接让吞吐量崩掉。所以检查“能通但慢”的故障时,一定先确认两端的速率和双工模式是不是一致,不要想当然。
3.3 用MDIO“体检”PHY:寄存器1的bit2为什么最重要
PHY的管理接口就是MDC(管理时钟)和MDIO(管理数据)两根线。MAC通过MDIO读写PHY内部的寄存器,标准寄存器有32个,前6个有统一规范:寄存器0是控制寄存器,bit0是软复位,bit12是自协商使能,bit13和bit8分别控制速度和双工;寄存器1是状态寄存器,bit2是Link Status,bit5是自协商完成标志。这个bit2基本是排错第一站:链路通不通,读它就知道了。
在带Linux系统的主板上可以这样快速看:
ethtool eth0 mii-tool -v eth0如果系统还没起来,在U-Boot里也能操作MDIO:
mdio list mdio read eth 1 1这里的“1”是PHY地址,后面“1”是寄存器号。PHY地址不是软件分配的,而是由芯片外面的PHYAD引脚在复位时被上下拉决定的,常见默认地址有0x00、0x01、0x04等。遇到“设备树里配置了但找不到PHY”,第一件事不是改代码,而是拿万用表量PHYAD引脚,看strap到底拉成了几。
4. MII家族接口逐个拆解:MII、RMII、GMII、RGMII与SGMII
4.1 接口演进主线:从四车道到双车道,从并行到串行
MII家族的名字看着多,其实演进逻辑非常清楚。第一个标准MII是IEEE 802.3为10/100M以太网定义的并行接口,数据线4位发送、4位接收,跑25MHz时钟刚好100Mbps。到了千兆,直接把位宽翻倍变成8位发送、8位接收,时钟升到125MHz,这就是GMII。但GMII引脚太多,SoC和PHY芯片都很占地方,于是出现了RMII和RGMII这类“缩线”版本。
RMII把发送和接收各砍成2位,参考时钟固定50MHz,用“速率换位宽”的思路在百兆下刚好满足100Mbps。RGMII则把千兆的8位数据拆成4位,用125MHz时钟双沿采样,在上升沿和下降沿各送4bit,等效8bit,这样既保住了千兆吞吐,又把管脚从二十几根压到十几根。越往后走,接口越窄、时钟越快、时序越苛刻,这就是整个MII家族的主线。
4.2 一张表看全MII家族:位宽、时钟、线速与管脚
我把常见接口的要素整理成一张表,方便对照:
| 接口 | 数据位宽 | 参考时钟 | 最高线速 | 管脚数量 | 适用速率 |
|---|---|---|---|---|---|
| MII | TXD/RXD各4bit | 25MHz(100M) / 2.5MHz(10M) | 100Mbps | 约16根 | 10/100M |
| RMII | TXD/RXD各2bit | REF_CLK固定50MHz | 100Mbps | 约10根 | 10/100M |
| GMII | TXD/RXD各8bit | GTX_CLK 125MHz | 1000Mbps | 约24根 | 10/100/1000M |
| RGMII | TXD/RXD各4bit | TXC 125MHz DDR | 1000Mbps | 约12根 | 10/100/1000M |
| SGMII | 差分串行TX/RX各一对 | 1.25Gbps SerDes | 1000Mbps | 不到10根 | 10/100/1000M |
以RMII为例,100Mbps = 2bit × 50MHz,正好;而到了10M模式,REF_CLK仍然是50MHz,但数据不是每个周期都有效,而是大约每10个周期里传2位有效数据,所以等价速率降到10Mbps。再看RGMII,千兆模式 = 4bit × 2(双沿) × 125MHz,同样算得刚刚好。每次看接口,先算一遍“位宽×有效边沿×时钟频率”,心里就有底了。
4.3 时钟方向与“同源”问题:这些坑最隐蔽
接口位宽搞对了,时钟方向是最容易翻车的地方。标准MII里TX_CLK和RX_CLK都由PHY给MAC,因为PHY知道当前介质上的线速是多少。GMII比较特殊:千兆模式下GTX_CLK由MAC提供给PHY,因为发送时钟必须跟MAC的125MHz一致;但10/100M模式下又退回MII式的PHY送时钟。RGMII在千兆模式下通常也是MAC侧输出TXC,而且很多PHY要求TXC和TXD之间有约2ns的延迟关系,有的要开PHY内部delay,有的要开FPGA内部的IODELAY,搞反的结果就是“能Link但数据全是错帧”。
RMII还有一个著名的“同源”问题:REF_CLK必须是同一个时钟源送到MAC和PHY,不能你用一个晶振、我用一个晶振,否则时钟频率偏差会在长帧传输时把采样点完全拉开,表现出来的就是偶发掉包。很多STM32板卡上,RMII参考时钟会通过PHY的REF_CLK引脚回送给MCU,或者由MCU的MCO输出50MHz给PHY,两种方案都能用,但一定要保证源头唯一、电平摆幅够。示波器量一下频率和幅值,往往比读半天手册更直接。
4.4 SGMII与“MAC模式/PHY模式”的配置选择
SGMII是串行接口里的代表,一对差分发送、一对差分接收,线速1.25Gbps,因为内部编码是8B/10B,有效数据速率正好1Gbps。它最大的好处是把并行接口从几十根线压到几根,而且SerDes天生适合跨板传输、抗干扰能力强,所以在交换芯片、FPGA网卡、车载以太网主控上非常常见。
但SGMII有一个特别容易被忽略的配置项:当FPGA的SGMII IP核和外部PHY芯片一起使用时,IP核必须配置成MAC模式,而不是PHY模式。这个“应配置成MAC模式”的细节,网上相关的问题一搜一大把。原因是SGMII在链路建立时也会跑一套自己的自协商和带内状态字,IP核得知道对面是PHY还是另一个MAC:对面是PHY时,IP核要当MAC;对面是MAC时,IP核才当PHY。选错模式,经常表现为接口层Link正常,但对端始终收不到有效报文。换一个PHY型号,或者改一种背靠背互联方式,第一件事就是把SGMII的角色方向确认清楚。
5. 接口在真实项目里的落点:STM32、FPGA三速以太网与车载以太网
5.1 STM32内置MAC外接PHY:为什么RMII方案居多
STM32F4/F7/H7系列芯片内部继承了以太网MAC外设,但PHY需要外挂。这类MCU引脚本来就紧张,所以资源受限的板子绝大多数走RMII,只用两组2位数据线和一根50MHz参考时钟;只有引脚富余或者要求扫描兼容MII特性时才会选标准MII。典型接法就是PHY芯片承担介质接口,RMII的REF_CLK由外部50MHz晶振或者主控MCO提供,MDIO/MDC用来配置PHY。
我之前调过一块STM32F407的板子,现象是偶尔能获取到IP,但ping包时通时不通。最后量波形发现RMII REF_CLK上升沿过缓,幅值只有2V多,MCU和PHY对时钟边沿判断不一致,导致采样错位。把晶振的负载电容重新匹配之后,问题立刻消失。这类情况软件怎么查都查不出来,因为你看到的是驱动层表现“丢包”,根因却在时钟信号质量。
5.2 FPGA三速以太网IP:GMII、RGMII、SGMII怎么选
FPGA里做以太网,常见的路线是用三速以太网MAC IP(10/100/1000自适应)。这种IP对外通常会出GMII、RGMII、SGMII三种类型的引脚,看实际需求选。资源充足、速率要求高、时序约束好做,可以考虑GMII;板级走线紧张、要减少输出引脚,就用RGMII;若FPGA内部有硬核SerDes、PHY距离远,就上SGMII。
RGMII在FPGA项目里特别考验时序约束。千兆125MHz DDR模式下,数据在时钟上升沿和下降沿各采样一次,PCB走线长度、FPGA IODELAY、PHY内部delay三者必须协调好。我碰到过一个例子:同一块FPGA板卡换了一个牌子的PHY之后,原来能正常工作的RGMII接口开始偶发CRC错误,后来查下来发现新PHY默认不使能内部TXC delay,FPGA侧又没有补这个延迟,需要在PHY寄存器里把发射延迟打开才解决。所以FPGA调RGMII,不能只改代码,还要把PHY的delay相关寄存器一个一个对照手册确认。
5.3 车载以太网与国产PHY:一样的MAC,不一样的介质
车载以太网这两年讨论很多,热搜里也一直有“车载以太网测试”“100BASE-T1”这类词。车载以太网和传统以太网最大的差异在物理层:100BASE-T1用一对非屏蔽双绞线,编码方式也变成PAM3,链路线缆更少、EMC要求更苛刻。但MAC层和MII/RMII/SGMII这类接口概念完全没变,所以你的协议栈、上层服务、网络安全机制都可以无缝复用到车上,这就是“媒介无关”在工程上的巨大优势。
国产PHY芯片这些年也越来越多。百兆PHY、千兆PHY,甚至车载PHY都有国产厂商在做,对项目来说,可选项变多了,供货和成本压力会小很多。但换国产PHY时不要以为驱动可以原样照搬,虽然IEEE标准寄存器一样,但不少厂商的扩展寄存器(用于延时配置、LED控制、低功耗模式)各不相同,初始化序列经常需要重新适配。
6. 调试链路时我的排错顺序:从Link灯到PHY寄存器
6.1 先看Link状态,再谈驱动问题
我自己的习惯是:拿到一块网卡不工作的板子,第一件事不是翻驱动,而是读PHY寄存器1的bit2。这一步只需要一条命令,或者一片几百微秒的MDIO访问,但能直接把问题分成两半——Link都不通,问题在物理链路、PHY配置、变压器、RJ45这一侧;Link通但收发异常,问题才可能在MAC、DMA、驱动协议栈。很多工程师一上来就查驱动,把链路层的问题绕了一个大圈。
如果Link不通,按这个顺序查:先看PHY的供电、晶体、复位时序;再看PHYAD strap到底配了几号地址,设备树和驱动里写的和实际是否一致;然后用网线测试仪确认线序,特别是交叉线和直连线在带自适应功能的PHY上一般能自动翻转,但老设备不一定支持。这些基础都排除后,再用示波器量MDIO有没有波形,如果MAC根本没去访问PHY,那问题可能又回到了MAC侧初始化。
6.2 能Link但收发异常时,优先怀疑接口时钟与采样时序
Link灯亮了只能说明物理层“握手”成功,不代表MAC和PHY之间的数据接口没问题。遇到能Link但收不到、或者疯狂CRC错误的情况,先看RTOS或Linux里的统计计数:
ifconfig eth0 ethtool -S eth0 | grep -i crc如果rx_crc_errors、rx_errors在涨,重点查三个方向:一是MII/RMII/RGMII的数据线、时钟线布线是不是等长、有没有跨分割;二是时钟方向是否和PHY手册一致,尤其是RGMII的TXC delay和RMII的REF_CLK同源;三是PHY的工作模式有没有被配置成百兆、千兆不一致的状态。很多时候这比反复改软件参数有效得多。
这里有个很实用的隔离手段:先用PHY的数字环回(Digital Loopback),让MAC发出的数据在PHY芯片内部直接绕回MAC。如果环回模式下收发正常,说明MAC和接口没问题,故障点在PHY的模拟收发链路或网线上;如果环回模式本身都不通,那就要回到MAC侧和接口时序去找。
6.3 速率/双工不匹配导致的“能通但慢”怎么定位
有一种极常见的故障表现:网络能通,Ping能通,但传文件、跑带宽测速时吞吐量掉到正常值的几分之一。这大概率不是CPU算力问题,而是对端或PHY的速率/双工协商结果不一致。用ethtool一眼就能看到:
ethtool eth0重点关注Speed和Duplex两行。如果本端显示100Mbps全双工,对端交换机口却显示100Mbps半双工,链路就已经处于双工不匹配状态。半双工端会产生大量冲突,表现就是高延迟、高重传、吞吐暴跌。这种问题通常是有人手动把交换机端口强制成了半双工,或者设备端关闭了自协商而交换机还开着自协商,两边协商到了不同的能力。解决办法就是两端统一策略:要么全部自协商,要么全部强制到同一组速率/双工参数。
我个人的习惯是,拿到新板子之后,第一件事不是急着跑网络demo,而是先用MDIO把PHY的寄存器0、1、4、5全部读一遍,把复位状态、能力、协商结果、Link状态记录下来。很多疑难杂症其实在环境一致、记录完整的条件下都会自己浮出水面。这篇先把MAC、PHY和MII家族接口的基本盘理清了,下一篇我打算把MDIO寄存器逐位过一遍,再单独聊RGMII的时序约束和PHY芯片的调试手法,那个更贴近实战,遇到问题了可以直接拿来对照。