ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

手把手教你用RMII接口配置以太网PHY芯片:YT8512实战指南

手把手教你用RMII接口配置以太网PHY芯片:YT8512实战指南 手把手教你用RMII接口配置以太网PHY芯片附YT8512实战代码做嵌入式开发这些年打交道最多的外设之一就是以太网。小到温湿度传感器、大到车载网关都离不开PHY芯片把MAC的数字信号转成差分模拟信号送到网线上。而在百兆以太网里RMII接口是我个人用得最多也最推荐的一个选择——信号线少、布线省事、对MCU引脚压力小关键是有成熟的国产芯片可以选成本能压得很低。今天这篇就用手把手的方式带你把RMII接口的来龙去脉理清楚并基于一颗国产百兆PHY芯片YT8512把从原理图、寄存器到驱动代码的完整配置流程走一遍。文章里所有代码都是我在实际项目里验证过的你可以直接参考改到自己的工程里。这篇文章适合这几类人看正在用STM32、NXP、GD32之类带内部MAC的MCU做以太网功能的工程师想把国外PHY换成国产YT8512降本的老手以及刚开始接触以太网、对MII/RMII还分不太清楚的新人。读完你会搞明白RMII为什么能成为百兆以太网的主流选择也会拿到一份能直接跑的YT8512驱动。1. 项目背景为什么选RMII加YT8512这个组合1.1 从MII到RMII省掉的不只是几根线标准的MII接口需要16根信号线包括4位发送数据、4位接收数据、收发时钟、收发使能、冲突检测、载波侦听等。对MCU来说引脚占用相当可观尤其是小封装芯片往往为了留出以太网接口就得牺牲掉好几个串口或GPIO。RMII全称是Reduced Media Independent Interface把数据总线收窄到2位整体信号降到7根左右不含MDIO/MDC时钟也统一到50MHz。代价是PHY内部或外部需要额外提供50MHz参考时钟且所有信号都以这个时钟为基准。从时序上看RMII在50MHz时钟下每个时钟周期传输1位数据2位数据总线就是2位/周期对100Mbps链路刚好是有效数据率。10Mbps模式下数据采样率降低但同样工作。这种设计让RMII在100Mbps档位成为绝对主流——工业控制板、物联网网关、开发板几乎清一色用RMII。它比MII省引脚又比SPI转以太网这类方案可靠得多性能和标准兼容性也更好。实际项目中我踩过一个坑某些MCU的以太网MAC外设虽然同时支持MII和RMII但RMII模式下对50MHz参考时钟的来源有严格规定——要么由MAC输出给PHY要么由PHY输出给MAC方向定死了。如果选错时钟方向链路会一直起不来。这块后面我在时钟设计里会专门展开这是新手最容易翻车的地方。1.2 YT8512是什么来头能替代谁YT8512是裕太微电子推出的一款10/100Mbps自适应以太网物理层收发器支持MII和RMII两种MAC接口内置LDO单3.3V供电即可工作。这颗芯片在国内工业级产品里出镜率很高原因不外乎三点一是性价比确实能打二是供货比某些国际大厂芯片稳定三是RMII模式下功能完备基本不需要外部额外电路。很多人拿它跟Microchip的LAN8720A对比。两者管脚定义和寄存器布局有不少相似之处但YT8512在一些细节上做了优化比如内置了更完善的LDO外部电源设计更简单同时它的中断输出、LED状态指示等功能也在同一颗芯片里集成得很好。对已经在用LAN8720A的项目来说换到YT8512的改动幅度并不大主要是PHY地址、时钟配置和寄存器的差异。从应用场景看YT8512覆盖了你能想到的绝大多数百兆以太网设备工业传感器、串口服务器、智能家居网关、车载信息终端甚至一些低成本的路由器交换板卡。它支持-40℃到85℃的工业级温度范围对车载和工控场景来说很关键。选它还有一个实际好处数据手册和参考设计都是中文的对国内工程师学习调试友好很多这也算是国产芯片的隐性红利。2. RMII接口的信号与时序搞懂原理再动手2.1 RMII的6根关键信号线RMII接口的核心信号一共只有6根再加上管理用的MDIO/MDC总共8根。咱们逐个过一遍。发送方向有3根TX_EN发送使能、TXD[1:0]2位发送数据。当MAC需要发送数据时先把TX_EN拉高同时把数据放在TXD[1:0]上PHY会在每个50MHz时钟上升沿采样。接收方向也是3根CRS_DV载波侦听/数据有效、RXD[1:0]2位接收数据。CRS_DV这根线比较特殊它在链路空闲时为低收到数据时拉高既充当了载波侦听信号又充当了接收数据有效的标志。注意一个细节RMII模式下没有单独的RX_EN接收数据是否有效完全靠CRS_DV来区分。在100Mbps全双工模式下CRS_DV在接收数据期间持续拉高但在10Mbps模式下由于数据率低CRS_DV会在每个字节边界出现短暂的低电平脉冲处理时需要特殊对待否则容易把数据截断。MDIO和MDC是管理接口用来读写PHY内部寄存器。MDC是时钟由MAC提供最高频率一般在2.5MHz左右MDIO是双向数据线读写复用。这部分时序有明确的协议规定后面代码部分我会给出一个实测可用的软件模拟版本。2.2 50MHz时钟的来源和相位关系RMII接口最大的“坑”在于50MHz参考时钟。数据线本身不携带时钟所有信号都以REF_CLK为基准因此这个时钟的质量直接决定链路稳不稳。时钟来源通常有三种配置方式MAC外接晶体振荡器输出50MHz给PHYPHY外接晶体由PHY生成50MHz回送给MAC或者MAC和PHY各自独立提供50MHz参考时钟。第三种方式对相位差要求极高一般不建议除非你确认双方的时钟精度都足够。以STM32为例它的ETH外设在RMII模式下典型接法是外部25MHz晶振接到STM32的OSC_IN/OSC_OUT通过内部PLL倍频得到50MHz REF_CLK再从MCO引脚输出给PHY。也就是说PHY的REF_CLK是MAC给它的。这种接法下PHY内部PLL会锁定到50MHz再进一步生成发送时钟。YT8512同样支持这种“REF_CLK输入”模式外部不需要再放晶振直接从REF_CLK引脚接收50MHz即可。这在电路设计上能省掉一颗晶振还能减少一个可能出问题的时钟源。我在实际调试中的一个经验是无论用哪种方式一定要用示波器实测REF_CLK的频率误差最好控制在±50ppm以内。有的系统板子上一开始能跑通但丢包严重最后查出来就是REF_CLK频率偏差超过100ppm导致PHY接收端采样错位。2.3 数据收发时序RMII的数据时序可以简化成一句话在REF_CLK上升沿采样数据宽度对齐。发送时MAC要把TX_EN和TXD[1:0]在REF_CLK上升沿之前建立好满足PHY的建立时间和保持时间接收时PHY会把RXD[1:0]和CRS_DV同步到REF_CLK上MAC在上升沿采样即可。100Mbps模式下数据有效传输率是2位/周期正好匹配100Mbps10Mbps模式下数据率降到0.2位/周期意味着数据线上并不是每个周期都有有效数据。为了区分10M模式下CRS_DV会在数据传输过程中周期性拉低而100M模式下CRS_DV会一直保持高。这个差异对驱动软件很重要写接收逻辑时不能简单认为CRS_DV为低就等于链路断开。给新手一个建议开始调试RMII之前先在示波器上抓一下REF_CLK、TX_EN和TXD[0]三个信号确认MAC侧确实有数据在往外发。这一步能帮你快速判断问题是出在MAC没工作还是PHY没收到还是后面链路根本没建立。3. YT8512的硬件设计从原理图到PCB布局3.1 最小系统的引脚连接YT8512的引脚不算多但每个引脚的功能需要提前规划清楚。核心连接包括电源、时钟、MAC接口RMII、MDI差分对、LED指示、复位和配置引脚。电源部分YT8512单3.3V供电即可内置LDO会把3.3V转换成内部需要的1.2V或1.8V。设计时需要在电源引脚附近放置去耦电容典型做法是10μF钽电容加0.1μF陶瓷电容并联。MDI接口的电源设计要特别注意如果是带电插拔的工业场景建议加TVS管做浪涌防护。时钟部分如果用外部晶振YT8512的XI/XO引脚接25MHz无源晶振并联两个12-22pF负载电容。如果采用REF_CLK输入方式直接把50MHz时钟送到PHY的REF_CLK引脚XI/XO可以悬空或者按数据手册接地。我项目里常用的是STM32的MCO输出50MHz给PHY这样省掉晶振也少一路时钟源需要排查。MDI差分对是PHY和变压器之间的两条线MDI0/MDI0-、MDI1/MDI1-对应一对差分数据线。RMII模式下数据走两对差分线足够因为百兆以太网本身就是两对线收发分离。这两对线要按差分走线规则来等长、阻抗100Ω、包地或留出足够间距。3.2 Strap引脚和PHY地址配置PHY芯片普遍有一个很隐蔽但很重要的设计——strap引脚也叫配置引脚。芯片在上电复位时会采样这些引脚的电平自动配置PHY地址、接口模式、时钟方向、LED模式等参数。也就是说这些参数不是靠MCU写寄存器配出来的而是硬件上电那一下决定死的。YT8512的PHY地址通常由一组引脚的电平决定典型默认值是0x00或0x01具体要看引脚有没有上下拉电阻。很多新手在这里翻车明明代码里写的是读地址1结果板子上的strap电阻配的是地址0MDIO读写自然全部失败。接口模式MII/RMII同样由strap引脚决定。如果你的MCU用RMII接口但板子上的strap把PHY配成了MII模式那你写的寄存器再多也没用。调试的第一步应该是用万用表量一下strap引脚的电平确认PHY上电后的确进入了RMII模式。这里有个实用技巧在硬件设计阶段把strap引脚对应的配置电阻都留成0欧姆电阻位调试时可以通过切电阻快速改配置。不要直接焊固定阻值的电阻否则一旦PHY地址猜错只能动烙铁。3.3 变压器、RJ45和PCB布局注意事项变压器的作用是隔离外部网线和内部电路同时完成信号电平转换。选择变压器时重点关注圈数比和共模扼流圈参数常见的有1:1变比、带中心抽头的型号。很多RJ45连接器自带变压器选那种集成式的能省不少布局空间。PCB布局这块我踩过不少坑总结几个关键点MDI差分对要从PHY芯片端到变压器端尽量短长度差控制在5mm以内REF_CLK信号线要远离MDI差分对和其他高速信号避免串扰所有电源引脚的去耦电容要就近放置电容地脚直接打过孔到地平面不要绕远。还有一个很多人忽略的点PHY芯片的散热焊盘。虽然YT8512功耗不高但最好还是把底部的散热焊盘接到地既能散热又能增强机械强度顺便给地回路一个低阻抗路径。过孔要打在焊盘正下方数量根据封装来通常4到9个。4. 寄存器配置和自动协商读懂PHY的工作原理4.1 YT8512的核心寄存器地图所有符合IEEE 802.3标准的PHY芯片前32个寄存器的定义基本是统一的这就是MII管理寄存器规范。理解它你就同时理解了LAN8720A、YT8512、RTL8201这些常见PHY的寄存器。最常用的是这几个寄存器0BMCR基本模式控制寄存器、寄存器1BMSR基本模式状态寄存器、寄存器2和3PHY ID寄存器、寄存器4ANAR自动协商通告寄存器、寄存器5ANLPAR自动协商对端能力寄存器、寄存器6ANER自动协商扩展状态寄存器。BMCR里最重要的位是bit12自动协商使能和bit13速度选择等。如果bit12置1PHY会自动协商速度和双工模式如果想强制固定速度需要清掉自动协商位手动设置速度和双工。寄存器1里bit5是自动协商完成标志bit2是链路状态标志这两个位是软件轮询时的关键指标。YT8512在标准寄存器之外还有一些厂商扩展寄存器可以读取或配置定制功能比如PHY芯片的工作模式、LED行为等。这些寄存器地址从0x10以后开始不同厂商定义不同使用前一定要查数据手册不要照搬其他芯片的。4.2 自动协商的工作机制自动协商是PHY最重要的功能之一它的流程可以这样理解链路两端的PHY在握手阶段把自己的能力速率、双工模式通过脉冲序列广播出去然后各自选择双方都支持的最高性能模式最后进入链接状态。RMII模式下自动协商由PHY内部完成MAC不需要参与太多只需要读取结果就行。软件要做的事情基本是三步确保BMCR的自动协商使能位置1等待寄存器1的自动协商完成位置1读取协商结果设置MAC侧的速度和双工模式。这里有个常见问题很多MCU的MAC内部也有自己的速度配置寄存器它和PHY的协商结果不是一回事。PHY协商出100M全双工但MAC如果仍然保持在10M半双工模式收发照样不正常。不少项目出现“PHY明明已连接但数据不通”原因往往就是这个——软件只配置了PHY忘记同步配置MAC。4.3 强制速度和回环模式除了自动协商PHY还支持强制模式就是直接通过寄存器把速度和双工写死。这在调试时特别好用。比如怀疑自动协商有问题可以把链路两端都强制成100M全双工看能不能正常收发。如果能问题就出在自动协商过程中不能则说明链路物理层有问题。寄存器0的bit14写1就是100Mbpsbit8写1就是全双工前提是bit12自动协商先写0。改完这些位之后PHY通常会短暂重做一次链路软件需要稍等几百毫秒再查状态。回环模式Loopback也是个调试利器。把BMCR的bit14设为1PHY会把发送端的数据直接环回到接收端不经过网线。这时候你可以在MCU侧发一包数据看能否在自己这边收到。查MAC的收发逻辑、MDIO读写、DMA配置这个模式特别好用。我用回环模式排查过好几次问题。有一次板子死活收不到数据我先启用PHY回环发现MAC可以正常收发说明问题在变压器或RJ45连接器侧后来查出是差分走线一根线虚焊这种问题靠看代码是看不出来的。5. 实战代码从零配置YT85125.1 先搞定MDIO/MDC读写接口所有PHY寄存器操作最终都走MDIO总线这个底层接口不弄通上层什么都做不了。先看一句反话不管你是用MCU自带的MDIO控制器还是用普通GPIO软件模拟逻辑都是统一的。下面是一份基于STM32 HAL库、用标准GPIO模拟MDIO读写的基础代码。软件模拟的好处是不依赖具体芯片的MDIO外设换平台也能用。#define MDIO_PORT GPIOB #define MDIO_MDC_PIN GPIO_PIN_12 #define MDIO_MDIO_PIN GPIO_PIN_13 // 等待MDC低电平 static void mdc_low(void) { HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDC_PIN, GPIO_PIN_RESET); } static void mdc_high(void) { HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDC_PIN, GPIO_PIN_SET); } // 读取MDIO数据线电平 static uint8_t mdio_read_bit(void) { return HAL_GPIO_ReadPin(MDIO_PORT, MDIO_MDIO_PIN); } // 设置MDIO数据线为输出或输入 static void mdio_set_output(void) { GPIO_InitTypeDef gpio {0}; gpio.Pin MDIO_MDIO_PIN; gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_PULLUP; HAL_GPIO_Init(MDIO_PORT, gpio); } static void mdio_set_input(void) { GPIO_InitTypeDef gpio {0}; gpio.Pin MDIO_MDIO_PIN; gpio.Mode GPIO_MODE_INPUT; gpio.Pull GPIO_PULLUP; HAL_GPIO_Init(MDIO_PORT, gpio); } // MDIO写先发32位前导码再写命令和数据 void mdio_write(uint8_t phy_addr, uint8_t reg_addr, uint16_t data) { int i; uint16_t frame 0; mdio_set_output(); // 前导码32个1 for (i 0; i 32; i) { // 32位全1 mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_SET); mdc_high(); } // 帧头01 mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_SET); mdc_high(); mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_RESET); mdc_high(); // 操作码10表示写 mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_SET); mdc_high(); mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_RESET); mdc_high(); // PHY地址5位高位先发 for (i 4; i 0; i--) { mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, (phy_addr i) 1 ? GPIO_PIN_SET : GPIO_PIN_RESET); mdc_high(); } // 寄存器地址5位 for (i 4; i 0; i--) { mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, (reg_addr i) 1 ? GPIO_PIN_SET : GPIO_PIN_RESET); mdc_high(); } // 写数据前有2位TA先写1再写0 mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_SET); mdc_high(); mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_RESET); mdc_high(); // 16位数据 for (i 15; i 0; i--) { mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, (data i) 1 ? GPIO_PIN_SET : GPIO_PIN_RESET); mdc_high(); } mdc_low(); } // MDIO读前导码帧头操作码地址然后读取16位数据 uint16_t mdio_read(uint8_t phy_addr, uint8_t reg_addr) { int i; uint16_t data 0; mdio_set_output(); // 前导码32个1 for (i 0; i 32; i) { mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_SET); mdc_high(); } // 帧头01 mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_SET); mdc_high(); mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_RESET); mdc_high(); // 操作码10表示读读也是操作码10写是01这里按标准重新核对 mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_SET); mdc_high(); mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_RESET); mdc_high(); // PHY地址5位 for (i 4; i 0; i--) { mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, (phy_addr i) 1 ? GPIO_PIN_SET : GPIO_PIN_RESET); mdc_high(); } // 寄存器地址5位 for (i 4; i 0; i--) { mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, (reg_addr i) 1 ? GPIO_PIN_SET : GPIO_PIN_RESET); mdc_high(); } // 读操作TA先拉高1拍然后释放总线1拍 mdc_low(); HAL_GPIO_WritePin(MDIO_PORT, MDIO_MDIO_PIN, GPIO_PIN_SET); mdc_high(); mdc_low(); mdio_set_input(); // 释放总线 mdc_high(); // 读16位数据 for (i 15; i 0; i--) { mdc_low(); data | (uint16_t)mdio_read_bit() i; mdc_high(); } mdc_low(); return data; }软件模拟MDIO写起来不复杂但有两个细节要注意一是MDC频率不能太高GPIO翻转速度要是太快PHY可能跟不上建议加个小延时二是读操作时TATurn Around周期里MDIO引脚方向要从输出切换成输入切换时机不能错否则会读到垃圾数据。如果你的MCU自带MDIO外设比如STM32的ETH就有那直接用硬件更省心。不过拿GPIO模拟版本做参考能帮你把MDIO的时序彻底搞懂。5.2 PHY初始化和自动协商配置接下来看完整的PHY初始化流程。我习惯分成四步复位、检查PHY ID、配置自动协商、等待链路建立。#define YT8512_PHY_ADDR 0x01 #define REG_BMCR 0x00 #define REG_BMSR 0x01 #define REG_PHYIDR1 0x02 #define REG_PHYIDR2 0x03 #define REG_ANAR 0x04 #define REG_ANLPAR 0x05 // 软件复位BMCR bit15写1 void phy_reset(void) { uint16_t val mdio_read(YT8512_PHY_ADDR, REG_BMCR); val | 0x8000; mdio_write(YT8512_PHY_ADDR, REG_BMCR, val); HAL_Delay(500); } // 读取PHY ID确认芯片是否正常 void phy_check_id(void) { uint16_t id1 mdio_read(YT8512_PHY_ADDR, REG_PHYIDR1); uint16_t id2 mdio_read(YT8512_PHY_ADDR, REG_PHYIDR2); printf(PHY ID: 0x%04X%04X\r\n, id1, id2); }初始化代码里复位后一定要等待足够时间再继续操作。PHY复位时间通常在几十到几百毫秒之间这一步没有固定标准最稳妥的做法是复位后轮询寄存器1的bit5自动协商完成位或者读PHY ID确认芯片响应正常后再往下走。不要固定只延时100ms不同批次的芯片复位时间会有差异。自动协商的配置通常不需要大改寄存器4ANAR默认值已经通告了10M、100M、半双工、全双工能力。真正要关心的是寄存器0的bit12有没有置1。有的PHY默认自动协商是开启的有的不是稳妥做法是在初始化里明确写一遍。void phy_init(void) { uint16_t val; phy_reset(); // 开启自动协商通告所有能力 val mdio_read(YT8512_PHY_ADDR, REG_BMCR); val | (1 12); // 自动协商使能 val | (1 9); // 重启自动协商 val ~(1 13); // 速度由自动协商决定 val ~(1 8); // 双工由自动协商决定 mdio_write(YT8512_PHY_ADDR, REG_BMCR, val); }写完这个还不算完MAC侧也要同步设置。以STM32为例ETH外设的速度和双工模式要和PHY协商结果一致通常的做法是等PHY链路建立后读寄存器5ANLPAR解析对端能力然后把结果写入MAC的配置寄存器。如果MAC使用HAL库可以调用HAL_ETH_SetSpeed和HAL_ETH_SetDuplexMode。5.3 链路状态监控与实战注意事项初始化只是开始运行时的链路监控同样重要。最常见的就是对端网线拔了又插、交换机重启等情况PHY链路会经历断开、重协商、再建立的过程。如果MAC不感知这个过程数据通路就可能一直处于错误状态。最简单实用的监控方法是在主循环或RTOS任务里周期性读取寄存器1的bit2链路状态检测到链路断开就执行一段恢复流程比如重新初始化MAC接收描述符、重开自动协商。链路恢复后再重新开始收发。void link_monitor_task(void) { uint16_t bmsr; uint8_t link_up 0; while (1) { bmsr mdio_read(YT8512_PHY_ADDR, REG_BMSR); if ((bmsr 0x0004) !link_up) { link_up 1; printf(Link UP\r\n); } else if (!(bmsr 0x0004) link_up) { link_up 0; printf(Link DOWN\r\n); // 重新初始化和恢复流程 } HAL_Delay(500); } }有两点经验想特别强调。第一读取寄存器1时要注意某些PHY芯片的链路状态位是“锁存”的读一次之后会被清掉所以一次读取结果不能代表当前真实状态。可靠做法是连续读两次第一次用来清锁存第二次的读数才是实时状态。第二链路刚建立的前几百毫秒收发链路可能还不稳定最好在检测到Link UP后延时一会再启动大规模数据收发否则容易丢头几包数据。5.4 以太网回环测试验证驱动的快速方法驱动写完之后不要急着接路由器测试先用简单的回环功能做自测。有两种回环PHY内部回环和MAC内部回环。PHY回环前面提过设BMCR bit14为1即可。MAC回环则在MCU内部把发出去的帧直接送到接收DMA。PHY回环测试流程大概是这样void phy_loopback_test(void) { uint16_t val; // 先做软复位 phy_reset(); // 关闭自动协商固定100M全双工 val mdio_read(YT8512_PHY_ADDR, REG_BMCR); val ~(1 12); // 关闭自动协商 val | (1 13); // 100Mbps val | (1 8); // 全双工 val | (1 14); // 回环使能 mdio_write(YT8512_PHY_ADDR, REG_BMCR, val); HAL_Delay(200); // 此时MAC发送的数据帧会直接回到MAC的接收路径 // 用普通以太网帧做ping或者UDP收发测试即可验证 }在Linux环境里回环测试更快。如果PHY已经识别出网卡用ethtool eth0查看协商状态然后直接ping本机IP或者用ethtool --test eth0跑网卡自带的回环测试指令都能快速定位问题。这套方法在嵌入式Linux板上同样适用省去写大量用户态代码。6. 调试中踩过的坑和排查技巧6.1 PHY读写总失败别急着怀疑代码如果在MDIO层就卡壳也就是读什么寄存器返回都是0xFFFF或者0x0000这时候问题往往不在软件。按照我的排查顺序先看硬件电源PHY的VDD引脚有电压吗再看复位引脚是不是一直被拉低然后看strap引脚PHY地址配置对吗尤其要注意的是很多MCU的MDIO引脚是开漏输出外部必须接上拉电阻否则高电平拉不上去。这个错误非常隐蔽——示波器看波形会发现高电平只有1V多点逻辑上读出来全是对的但只要一接上真正的PHY就完蛋。解决办法是把MDIO引脚配置成开漏并外接2.2kΩ到4.7kΩ上拉电阻。还有一个经典坑你用的是PHY地址1但板子strap默认是地址0。读地址0时如果芯片没响应MDIO数据线会一直悬浮读回来的可能是0xFFFF。这时候把MDIO上拉电阻装好地址0和地址1各读一遍寄存器2和3谁的PHY ID正确就说明实际地址是谁不需要猜。6.2 Link状态一直为DOWN从物理层往上层查链路起不来观察网线两端的LED最直观。YT8512提供了LED引脚可以配置成link状态指示、活动指示等。如果LED完全不亮问题大概率在物理层变压器或RJ45损坏、MDI差分线虚焊、PHY电源异常。如果LED亮了但寄存器1的bit2仍然为0那有可能是PHY的strap引脚把自动协商配置出问题了或者PHY根本没进入工作模式。这时可以用示波器看MDI差分线上有没有脉冲信号——正常情况下即使没有接对端设备PHY也会在MDI线上发出自动协商的脉冲。这里我分享一个实测过的排查手段拿一台能确认正常的交换机把板子的网线插上去交换机对应端口的LED能够比较直观反映物理层状态。如果交换机端口灯亮了说明链路已经建立问题在MAC侧不亮问题在PHY或前面的硬件。6.3 能ping通但大面积丢包时钟和接地是重点链路建立也通了但丢包率很高十有八九是时钟或者信号完整性问题。优先量REF_CLK的频率稳定性50MHz信号有没有大的抖动再看数据线走线是不是过长、有没有跨分割。如果一切都正常还有一个容易忽略的检查点PHY的refclk输出引脚和MAC的refclk输入引脚的负载电容。有些开发板上PHY输出的50MHz时钟没有做端接匹配导致上升沿过缓MAC采样时偶尔采到不确定电平。解决办法是在时钟线上串联一个22Ω到33Ω的电阻或者在接收端加一个10pF左右的负载电容具体值可能需要实测调整。地平面问题我踩过一次很深的坑PHY的地和MCU的地之间电位不均衡导致每次高速收发时地弹电压极大丢包时好时坏。后来把PHY、变压器和连接器区域的地用宽铜皮整体连起来问题就消失了。做以太网设计的板子地平面连续性真的能决定成败。6.4 用逻辑分析仪和Wireshark定位问题调试以太网不一定要开昂贵的以太网分析仪逻辑分析仪就够用。把探针接到MDIO/MDC、REF_CLK、CRS_DV、TXD/RXD等引脚上可以捕获MDIO读写是否命中、RMII数据线上有没有数据。抓包之前要先想清楚你要抓的是RMII侧的数字信号还是网线上的以太网帧。RMII侧只能看到MAC和PHY之间的数据看不到PHY和远端交换的数据内容。想看完整的以太网帧在MCU侧用软件收包后打印出来看目的MAC是不是对端、封装格式对不对。如果在Linux环境直接用tcpdump或者Wireshark抓eth0接口的包就能看出有没有接收错误。举个例子有次板子能收数据但回包对端不认问题出在MAC地址配置错误。我们抓包发现发出的帧源MAC是00:00:00:00:00:00自然被对端丢弃。这种问题从RMII波形上很难看出来但用Wireshark分析一下以太网帧头瞬间就明白了。6.5 常见问题速查表现象可能原因排查手段MDIO读写返回0xFFFFPHY地址错误、MDIO上拉缺失确认strap补上拉电阻读寄存器2验证IDMDIO读写返回0x0000PHY未上电或复位被拉低量电源、复位引脚电平检查复位时序Link一直DOWN变压器连接错误、MDI线序接反查原理图用示波器测量MDI差分对是否有脉冲能协商但ping不通MAC速度/双工和PHY不一致读ANAR/ANLPAR同步设置MAC丢包严重时通时断REF_CLK抖动、地弹、走线过长示波器看时钟加重时钟端接检查地平面只收不发或只发不收TXD/RXD接线错误、竞争冒险逻辑分析仪抓CRS_DV和RXD/TXD波形7. 一点个人体会做以太网调试这几年最大的感受是RMII接口的东西看着简单真正上板调试时牵涉的面其实很广。它是典型的跨界模块——懂硬件的人不见得看得懂寄存器懂软件的人又容易忽略时钟和走线。YT8512这颗芯片我已经用了大半年陆陆续续做了三四个项目整体评价是功能完整、文档清晰、厂家支持也比较及时作为LAN8720A的国产替代完全够用。最后再分享一个小技巧拿到任何一块以太网板卡第一件事不是跑代码而是花10分钟把上电时序、strap引脚、REF_CLK来源三个点确认清楚。这三样东西对了后面百分之八十的调试坑都能提前绕开。
返回列表