资讯动态

手写MDIO控制器:协议详解、Verilog/VHDL实现与调试实战

发布时间:2026/10/5 5:50:10 来源:尧图企业网站定制
1. 从一次网口死活不通的调试经历说起为什么要自己写MDIO控制器第一次和MDIO打交道完全是被一块板子逼的。板子做完回板光口和电口都起不来PHY芯片没有完成初始化协议栈报了各式各样的链路故障。手上没有预置配置的EEPROMPHY必须靠CPU通过MDIO总线写寄存器才能把工作模式、速率、自协商开关这些参数设置好。排查到最后问题根本不在这颗PHY芯片本身而在MDIO时序上。用逻辑分析仪一抓MDC波形方方正正可MDIO数据线上读回来的却是一大串0xFFFF。那一瞬间我意识到想高效定位这类问题不能只会点仿真器看一下表面现象得真正把MDIO控制器的内部逻辑吃透最好能亲手写一个。这篇文章要做的就三件事第一把MDIO协议从帧格式到时序参数完整拆开讲清楚第二带你从零开始手写一个MDIO控制器Verilog版本逐段精讲每一段代码为什么这么写都说明白第三给一份VHDL对照实现用同一个状态机设计思路让你亲眼看到两种硬件描述语言在解决同一问题时的各自写法。适合正在做FPGA以太网接口、用到RTL8211这类MDIO接口PHY的工程师也适合刚学完Verilog语法、想上手一个真实总线协议的学习者。如果你已经能调MII/RGMII接口但每次配置PHY寄存器还是靠厂商驱动库和现成脚本那这篇文章能帮你把总线底层控制权拿回自己手里以后遇到寄存器操作不响应、读写超时、地址漂移这类疑难杂症也不至于毫无头绪。2. 和PHY对话的规矩MDIO帧格式与寄存器访问规则2.1 一条MDIO操作到底长什么样MDIOManagement Data Input/Output是IEEE 802.3 Clause 22定义的两线管理接口一线是MDC时钟一线是MDIO双向数据。它做的事情很简单让MAC侧的处理器读或写PHY芯片内部的寄存器。但简单归简单帧格式里的每一个字段都有严格的先后顺序和位宽规定少一个周期或者多一拍整条命令就会作废。一次完整的MDIO操作帧是64位不含IDLE空闲周期划分如下字段位宽内容说明Preamble32位连续32个逻辑1表示一帧开始ST2位固定为01表示Clause 22帧OP2位10表示读01表示写PHYAD5位PHY芯片地址范围0x00-0x1FREGAD5位寄存器地址范围0x00-0x1FTA2位读操作是Z0写操作是10DATA16位读回或写入的数据眼尖的朋友可能已经发现64位刚好比一次协议操作多出1位因为DATA的16位里有一位是没有用的不是的把32 2 2 5 5 2 16加起来是64刚好是一帧完整长度。这是MDIO帧格式最基本也最关键的记忆点。在空闲状态下MDIO线处于高阻态靠外部上拉电阻保持高电平。所以如果你用逻辑分析仪抓一段空闲波形看到的是一条被拉高的平线。Preamble从IDLE进入第一个传输周期时MDIO被主机驱动输出低电平不对Preamble是32个1所以第一个位实际上是高电平但从高阻到主动驱动高电平逻辑分析仪上看起来是连续的高这时就要靠下降沿来区分了。这里其实牵扯一个非常重要的时序细节MDC上升沿发送数据、下降沿采样数据主机和PHY都是按这个约定来的。2.2 寄存器地址空间与MII标准寄存器MDIO可访问的寄存器地址是5位也就是0到31。IEEE 802.3在Clause 22里规定了0到15号寄存器的标准含义16到31留给厂商自定义。这些寄存器里0号控制寄存器BMCR和1号状态寄存器BMSR是几乎所有PHY都必须支持的初始化时最常用的也是它们。以RTL8211系列为例0号寄存器里bit 13是速度选择bit 12是自协商使能bit 8是全双工模式bit 9是自协商重启。1号寄存器里bit 5是自协商完成标志bit 2是链路建立状态。这个链路状态位也是网口调试时最常读的寄存器之一很多厂商的link led不亮第一反应就是去读BMSR寄存器确认物理链路到底有没有建立。实际项目中厂商还会在16到31号寄存器里放很多控制项比如RTL8211的绿色以太网节能功能、交叉检测配置、延迟补偿等这些都要通过MDIO读写。所以MDIO控制器不能只做一个读或一个写必须双向完整。2.3 读操作和写操作在TA周期的关键区别TATurnaround周期是MDIO协议里最需要小心的地方读和写在这里的处理方式完全不同。写操作时主机是总线的唯一驱动者TA周期的两个位都主动输出10。第一个位是高电平第二个位是低电平PHY在这两拍里不做任何动作纯粹是让总线保持连贯的转换时间。读操作时TA周期第一拍主机仍然驱动低电平也有的控制器写0x0或0x1严格标准是主机输出高阻或0但最常见的是输出0第二拍主机必须释放总线把MDIO线改为高阻输入由PHY接管并驱动第一个数据位。如果主机在TA第二拍没有释放总线和PHY的输出就会发生总线冲突轻则读到错误数据重则损坏引脚。这就是为什么很多MDIO控制器设计里读操作和写操作的状态机分支在TA阶段必须分开处理不能图省事共用一套输出逻辑。3. 架构先行模块划分、状态机设计与时钟分频3.1 为什么控制器必须用三段式状态机MDIO控制器本质上是一个串行协议引擎最自然的实现方式就是状态机。我在设计初期纠结过一个事状态机的写法到底用一段式、二段式还是三段式最终选择了三段式原因是在MDIO这种一边发控制位、一边采数据的场景里三段式的优势太明显了。一段式把所有逻辑揉在一个always块里代码短但时序难懂而且输出容易产生毛刺。二段式把时序和组合分开已经能解决大部分问题但输出逻辑仍混在状态判断里。三段式把状态跳转、次态判断、输出逻辑分成三部分每部分职责单一尤其在MDIO这种需要在不同状态下驱动不同位宽数据、还要在三态门上做切换的场景能避免很多隐藏的竞争问题。举个例子PRE状态要连续输出32个1OP状态要输出2位操作码PHYAD状态要输出5位地址DATA状态要输出16位数据。如果用一段式这些输出直接写在状态判断里状态切换的瞬间输出可能产生毛刺。三段式用mealy型输出逻辑把输出看作当前状态和输入信号的函数可以在状态转换的同一拍就更新输出时序更干净。3.2 控制器端口规划在设计端口之前先想清楚这个控制器要服务谁。最典型的使用场景是片上处理器软核或硬核或外部CPU通过总线接口向控制器发出读PHY寄存器、写PHY寄存器的请求。因此控制器对外应该提供一组类似寄存器的简单接口而不是直接暴露时序信号。我设计的端口如下信号方向位宽作用clkin1系统时钟rst_nin1异步复位低有效mdcout1管理时钟输出mdioinout1管理数据线reqin1请求发起一次操作wr_enin11写0读phy_addrin5PHY地址reg_addrin5寄存器地址wr_datain16写数据busyout1忙标志rd_dataout16读返回数据rd_data_validout1读数据有效标志上层模块只要拉高req一个周期输入好地址和数据读操作时wr_data不用管控制器就会自动完成整个时序完成后拉低busy读数据的时候rd_data_valid给一个单周期脉冲。这样上层逻辑完全不用关心MDC分频、位计数、三态切换这些底层细节耦合度很低拿到任何项目里都能直接复用。3.3 状态序列设计与转移条件状态设计的核心原则是每个MDC周期对应一个状态或者一组状态状态转移节奏与MDC同步。我定义的状态如下IDLE空闲不驱动MDCMDIO高阻PRE输出32位Preamble用计数器循环OP输出2位操作码PHYAD输出5位PHY地址REGAD输出5位寄存器地址TA2位turnaround周期读和写在这里分支DATA16位数据收发DONE操作完成回到IDLE在PRE状态下用一个6位计数器从0数到31每个MDC周期加1数满32个就跳到OP。OP状态下数2拍后跳转PHYAD和REGAD各数5拍TA数2拍DATA数16拍。整体节奏是线性的几乎不存在分支唯一的分支在TA状态写操作时两个位都主动输出读操作时第二拍释放总线并开始采样。MDC时钟不是每一拍都要输出。在IDLE和DONE状态下MDC保持低电平MDIO保持高阻这样可以省掉一些不必要的翻转功耗也让总线在空闲时更稳定。PRE状态开始后MDC才按照分频比例产生方波。3.4 MDC分频计算与时钟树设计IEEE 802.3标准规定MDC最高频率是2.5MHz但现在很多PHY实际能跑到更高比如RTL8211系列手册里标注最大25MHz实际上有些PHY能支持更高频率。不过稳妥起见设计时分频目标还是控制在2.5MHz以内特别是系统时钟比较高的FPGA里这个分频系数很容易算。假设系统时钟是100MHz目标MDC是2.5MHz那分频系数就是100 / 2.5 40即每40个系统时钟周期产生一个完整的MDC周期。具体实现可以用一个计数器在0到19之间翻转前20个周期MDC输出高后20个周期输出低。需要注意的是MDIO数据在MDC上升沿附近变化在MDC下降沿附近采样。所以控制器的内部逻辑应该以系统时钟为基准分频产生MDC同时在MDC下降沿的前一个系统时钟周期采样MDIO输入这样能保证采样点落在数据稳定区域内。如果直接用always (negedge mdc)去采样由于MDC是分频产生的它和系统时钟之间有相位关系并不好约束容易出现亚稳态。稳妥做法是用计数器判断当前处于MDC周期的什么位置在下降沿位置附近采样系统时钟同步后的MDIO信号。4. Verilog实现逐段精讲从状态跳转到三态门4.1 模块端口与参数定义先给出完整模块框架这部分不复杂但参数定义里有两个值得注意的坑。module mdio_controller #( parameter CLK_DIV 20, // 系统时钟/MDC/2等比分频系数 parameter PREAMBLE_LEN 32 // 前导码长度 )( input wire clk, input wire rst_n, output reg mdc, inout wire mdio, input wire req, input wire wr_en, input wire [4:0] phy_addr, input wire [4:0] reg_addr, input wire [15:0] wr_data, output reg busy, output reg [15:0] rd_data, output reg rd_data_valid );CLK_DIV这个参数我用的是半个MDC周期对应的系统时钟拍数。系统时钟100MHz目标MDC 2.5MHz完整周期是40个系统时钟半个周期就是20所以CLK_DIV填20。这样mdc信号的翻转判断就变成counter CLK_DIV-1时翻转逻辑上更直接。PREAMBLE_LEN默认32但有些PHY可能不要求完整32位前导码也能识别有些调试模式下甚至不需要前导码。保留这个参数可以让你在需要兼容特殊PHY时灵活调整不用改状态机逻辑。4.2 分频计数与MDC生成MDC生成和状态机的时序配合是很多初学者最容易写乱的地方。我采用的方式是用一个计数器div_cnt在0到2CLK_DIV-1之间循环当div_cnt等于CLK_DIV时MDC置高等于2CLK_DIV-1时置低并完成一个周期。但这个计数器不是一直跑的只在状态机离开IDLE后才工作回到IDLE时清零。reg [5:0] div_cnt; wire mdc_div_en (state ! IDLE) (state ! DONE); always (posedge clk or negedge rst_n) begin if (!rst_n) begin div_cnt 0; mdc 1b0; end else if (mdc_div_en) begin if (div_cnt (CLK_DIV * 2 - 1)) begin div_cnt 0; end else begin div_cnt div_cnt 1b1; end if (div_cnt CLK_DIV) begin mdc 1b1; end else begin mdc 1b0; end end else begin div_cnt 0; mdc 1b0; end end这段代码里div_cnt小于CLK_DIV时MDC为高大于等于CLK_DIV时MDC为低正好形成一个占空比50%的方波。要注意的是IDLE状态下mdc_div_en为假所以mdc被强制拉低这个电平状态对逻辑分析仪判断总线空闲很有帮助。4.3 三段式状态机的第一段状态寄存器的次态跳转状态寄存器用枚举类型定义清晰直观。localparam IDLE 3d0, PRE 3d1, OP 3d2, PHYAD 3d3, REGAD 3d4, TA 3d5, DATA 3d6, DONE 3d7; reg [2:0] state, next_state;第一段是纯粹的状态寄存器更新时序逻辑always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else state next_state; end4.4 第二段组合逻辑判断转移条件组合逻辑部分需要有明确的跳变条件。我用一个bit_cnt计数器配合每个状态的分支判断reg [5:0] bit_cnt; // 当前状态内已经处理的bit数 always (*) begin next_state state; case (state) IDLE: begin if (req) next_state PRE; end PRE: begin if (bit_cnt PREAMBLE_LEN - 1) next_state OP; end OP: begin if (bit_cnt 1) next_state PHYAD; end PHYAD: begin if (bit_cnt 4) next_state REGAD; end REGAD: begin if (bit_cnt 4) next_state TA; end TA: begin if (bit_cnt 1) next_state DATA; end DATA: begin if (bit_cnt 15) next_state DONE; end DONE: begin next_state IDLE; end default: next_state IDLE; endcase endbit_cnt的更新逻辑放在状态寄存器更新的时序块里或者单独一个always块。每个状态内部数完需要的拍数就跳走这个设计等价于把状态图展开成了一维计数跳转优点是逻辑非常简单不容易出现在多个状态间来回跳的bug。4.5 第三段输出逻辑与三态门处理输出逻辑是MDIO控制器的灵魂也是最容易写错的地方。先定义内部输出寄存器和三态使能信号reg mdio_out; reg mdio_en; // 1驱动MDIO0释放总线 wire mdio_in; assign mdio mdio_en ? mdio_out : 1bz; assign mdio_in mdio;三态门的关键在于mdio_en。在IDLE、读操作TA第二拍、DONE这几个时刻mdio_en必须是0让总线处于高阻态。其他状态下mdio_en为1控制器主动驱动数据线。输出逻辑按照状态机bit_cnt来确定当前要发送的位。这里我用一个移位寄存器来组织数据流比在case里逐位判断要优雅得多reg [15:0] cmd_shift; reg [15:0] data_shift; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cmd_shift 0; data_shift 0; end else if (state IDLE req) begin // 组装指令序列Preamble ST OP PHYAD REGAD TA DATA cmd_shift {32hFFFFFFFF, 2b01, (wr_en ? 2b01 : 2b10), phy_addr, reg_addr, (wr_en ? 2b10 : 2b00)}; data_shift wr_data; end else if (mdc_falling_edge) begin // 每个MDC周期移出一位 cmd_shift {cmd_shift[14:0], 1b0}; data_shift {data_shift[14:0], 1b0}; end end等等这个移位寄存器方案其实有个问题Preamble需要32位而cmd_shift只有16位塞不下。要处理32位前导码要么把cmd_shift扩展到64位要么在PRE状态单独用一个计数器循环发送1。我实际采用的方案是后者PRE状态下mdio_out固定为1直到bit_cnt数满PREAMBLE_LEN。这样既不需要大位宽移位寄存器也方便在调试时通过参数调整前导码长度。核心输出逻辑如下always (*) begin mdio_out 1b1; mdio_en 1b0; case (state) IDLE, DONE: begin mdio_en 1b0; end PRE: begin mdio_en 1b1; mdio_out 1b1; end OP: begin mdio_en 1b1; mdio_out (bit_cnt 0) ? (wr_en ? 1b0 : 1b1) : (wr_en ? 1b1 : 1b0); end PHYAD: begin mdio_en 1b1; mdio_out phy_addr[4 - bit_cnt]; end REGAD: begin mdio_en 1b1; mdio_out reg_addr[4 - bit_cnt]; end TA: begin if (wr_en) begin mdio_en 1b1; mdio_out 1b0; // 写操作TA第二个位是0 end else begin if (bit_cnt 0) mdio_en 1b1; // 第一拍仍驱动 else mdio_en 1b0; // 第二拍释放总线 mdio_out 1b0; end end DATA: begin if (wr_en) begin mdio_en 1b1; mdio_out wr_data[15 - bit_cnt]; end else begin mdio_en 1b0; // 读模式 // 在bit_cnt等于0..15时都采样 end end endcase end在DATA状态读模式下需要在每个MDC周期的下降沿附近采样mdio_inalways (posedge clk or negedge rst_n) begin if (!rst_n) begin rd_data 0; rd_data_valid 0; end else if (state DATA mdc_falling_edge) begin rd_data[15 - bit_cnt] mdio_in; if (bit_cnt 15) rd_data_valid 1b1; else rd_data_valid 1b0; end else begin rd_data_valid 1b0; end endmdc_falling_edge信号的产生很简单组合逻辑判断div_cnt等于CLK_DIV-1即半个周期结束MDC即将从高变低。有了这个脉冲所有在下降沿采样的逻辑都能统一对齐。5. VHDL对照实现同一套状态机的第二种写法5.1 枚举类型让状态定义更严格VHDL版本最直观的差异就是状态机的定义方式。Verilog用localparam定义位宽和常量本质上还是整数编译时不会对非法状态值做严格检查。VHDL用枚举类型直接定义状态编译器会强制要求状态集合合法不小心写错状态名在综合时就能报错这比Verilog在仿真时才暴露问题要提前一步。type mdio_state_type is (IDLE, PRE, OP, PHYAD, REGAD, TA, DATA, DONE); signal state : mdio_state_type; signal next_state : mdio_state_type;这种写法在代码可读性上完胜Verilog。综合工具也能根据枚举值自动做编码优化默认是二进制编码也可以指定one-hot编码。5.2 VHDL主控模块核心代码VHDL里没有inout wire的统一三态写法而是用std_logic类型的Z状态表示高阻。端口定义如下entity mdio_controller is generic ( CLK_DIV : integer : 20; PREAMBLE_LEN : integer : 32 ); port ( clk : in std_logic; rst_n : in std_logic; mdc : out std_logic; mdio : inout std_logic; req : in std_logic; wr_en : in std_logic; phy_addr : in std_logic_vector(4 downto 0); reg_addr : in std_logic_vector(4 downto 0); wr_data : in std_logic_vector(15 downto 0); busy : out std_logic; rd_data : out std_logic_vector(15 downto 0); rd_data_valid : out std_logic ); end mdio_controller;架构体里核心状态机与Verilog版本一一对应只是语法变成了processarchitecture rtl of mdio_controller is type mdio_state_type is (IDLE, PRE, OP, PHYAD, REGAD, TA, DATA, DONE); signal state, next_state : mdio_state_type; signal div_cnt : integer range 0 to 2 * CLK_DIV - 1; signal bit_cnt : integer range 0 to 31; signal mdio_out : std_logic; signal mdio_en : std_logic; signal rd_data_reg : std_logic_vector(15 downto 0); begin mdio mdio_out when mdio_en 1 else Z; state_proc : process(clk, rst_n) begin if rst_n 0 then state IDLE; elsif rising_edge(clk) then state next_state; end if; end process;VHDL的signal赋值是延时赋值这和Verilog的非阻塞赋值类似所以在同一个process里给信号赋值最终值要到process结束才生效。这点和Verilog的非阻塞赋值是一个原理理解了Verilog的时序逻辑习惯就能很自然迁移过来。5.3 Verilog与VHDL的关键差异对照表对比维度VerilogVHDL状态定义localparam regtype枚举类型 signal时序逻辑always (posedge clk)process(clk) rising_edge(clk)组合逻辑always (*) 或 assignprocess(敏感信号) 或 when-else信号赋值非阻塞 / 阻塞signal / variable :数据类型wire/reg宽松隐式转换std_logic/std_logic_vector强类型必须显式转换位宽裁剪直接截断自动适配必须用 resize 或显式截取否则编译报错case语句case (x) 分支以begin end包裹case x is when 分支以when结束三态门assign mdio en ? out : 1bzmdio out when en1 else Z第一眼看VHDL很多人会被强类型系统烦到。比如Verilog里可以直接写rd_data[15 - bit_cnt]VHDL里bit_cnt是integer索引语法没问题但如果你想把integer赋值给std_logic_vector就必须用std_logic_vector(to_unsigned(phy_addr_i, 5))这样的转换多写很多代码。不过好处是类型错误在编译期就暴露不像Verilog写错了位宽可能只在特定输入下才出错排错成本很高。5.4 从Verilog迁移到VHDL时最容易踩的三个坑第一个坑是process的完整敏感列表。Verilog的always (*)会自动推断敏感列表VHDL的process必须显式列出所有敏感信号少写一个信号综合工具可能仍然能推断出来但仿真结果会和综合结果不一致。这种仿真对、上板不对的问题在VHDL里比Verilog更常见排查起来也很费劲。第二个坑是std_logic_vector的索引方向。如果定义signal phy_addr : std_logic_vector(4 downto 0)那phy_addr(4)是最高位和Verilog里[4:0]一致。但一旦你哪天用了to方向索引规则就全反了。建议整个项目统一downto别混用。第三个坑是整数和向量的边界处理。VHDL里integer类型默认是32位有符号整数如果你用integer做计数器然后直接给端口赋值综合工具会警告宽度不匹配。我在工程里统一用unsigned类型做计数最后用to_stdlogicvector转换能减少大量宽度警告。6. 逻辑分析仪抓到的问题实测中的坑与调试思路6.1 功能仿真正常上板却读回0xFFFF这是MDIO调试里最常见的疑难杂症。仿真里明明协议时序完全正确Preamble、ST、OP、地址都对但一上板读BMSR永远是0xFFFF。用逻辑分析仪抓真实波形才发现TA周期第二拍的主机输出和PHY驱动输出发生了某个微妙的重叠或者根本就是MDIO引脚在释放总线后电平被拉高但PHY在采样窗口里采到的是外部干扰。排查这类问题时一定要按以下顺序确认MDIO有没有外部上拉电阻标准要求4.7kΩ到10kΩ上拉到IO电压。没有上拉读操作时总线浮空PHY即使驱动低电平采样端也可能采到不确定值。采样窗口是否落在MDC下降沿MDC下降沿后PHY输出数据主机应该在这个时刻之后采样。建议采样点放在下降沿过后的1/4周期处而不是恰好在下降沿。逻辑分析仪探头的容性负载是否过大MDIO是双向低速线探头电容大了会影响边沿我用示波器测过长探头的边沿明显变缓。6.2 MDC频率和时钟约束的坑很多PHY手册标称MDC最高2.5MHz但工程上为了让时序更稳我一般把目标频率压到1MHz到1.25MHz之间。分频系数大了控制器状态机里的位周期就长采样窗口也宽抗干扰能力更强。还有一个容易被忽略的点MDC和MDIO之间的约束关系。MDC是分频产生的如果系统时钟100MHz目标MDC 2.5MHzMDC边沿误差就在一个系统时钟周期内这对MDIO这种低速协议完全够用。但如果你用了PLL生成MDC而没有做I/O约束布线工具可能把MDC和MDIO的延迟差拉大。稳妥做法是在XDC或SDC里对MDC和MDIO加set_max_delay约束确保两者路径延迟差在几个纳秒以内。6.3 多PHY地址冲突与硬件引脚上拉MDIO总线上可以挂多个PHY每个PHY通过芯片的PHYAD[4:0]引脚设置地址。设计时如果多个PHY的地址引脚都接到了固定的高或低电平就会导致总线冲突。RTL8211这类芯片的PHYAD引脚一般内部有下拉外部可以通过上拉电阻改变地址。排查方法很简单上电后用逻辑分析仪抓一下MDIO上电初期的PHY回应或者读一遍0x02寄存器看能否读到厂商ID。读不到就检查PHYAD引脚电平确认没有两个PHY共用一个地址。多个PHY挂同一条MDIO总线时读操作的TA第二拍需要格外小心因为所有PHY都会在前导码后看到同一个地址和寄存器地址只有地址匹配的那个PHY才会接管TA周期并驱动数据。不匹配的PHY在这个阶段保持高阻只要地址不冲突总线不会受影响。6.4 MDIO与I2C的边界问题调试时经常被问到为什么有的PHY模块用I2C配置有的用MDIO其实两者定位不同。MDIO是IEEE 802.3定义的PHY管理接口专门用来读写PHY寄存器。而很多高速光模块或者电口模块里的状态监控功能比如温度、电压、光功率走的是I2C接口。原因很简单MDIO寄存器空间只有32个主要用于PHY的控制和状态塞不下完整的DMI诊断信息而I2C的地址空间更大、速率也更灵活适合承载这些监控数据。RTL8211GC这类芯片本身是MDIO接口但板级设计里常常旁边挂一片小的EEPROM用MDIO去加载初始配置同时又有一个I2C接口去读温度和告警。搞混这两个接口的话很容易出现对着I2C地址发MDIO帧的情况总线自然没有响应。经过这一轮完整的实现和调试MDIO控制器在我这里已经成了标准IP库里的固定模块。现在遇到网口初始化失败的问题我不会再急着怀疑PHY芯片本身而是先用逻辑分析仪抓MDIO波形对照状态机里的每一个状态确认数据位是否正确再检查硬件上拉和采样时序。这种从协议到实现到硬件全链路排查的感觉比单纯调库函数靠猜要踏实太多。如果你也正在调MDIO相关的问题建议按这个顺序走一遍大概率能直接定位到根因。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑