简介:一份面向FPGA开发者的基于RGMII接口以太网MAC实现工程资料,适合网络通信方向学生与硬件工程师参考。内容覆盖RGMII协议(25MHz时钟、4路RX/TX数据线全双工)、MAC层数据帧组装与解组装、VHDL/Verilog逻辑设计、时序约束、PHY芯片(DP83848/LAN8720)对接、IP核集成、仿真验证以及驱动/DMA协同等完整技术链路。资源共728个文件,压缩包约22.91MB,主体为Quartus工程文件(.qpf/.qsf/.sdc)、Verilog/VHDL源码(.v/.vhd)、ModelSim仿真波形(.wlf)及PDF/docx说明文档,另含MDIO接口配置、PHY百兆配置等关键子模块代码。已有2938人学习下载。通过该工程可掌握RGMII时序控制、MAC状态机、CRC校验、错误检测与MDIO配置等设计要点,并结合仿真记录快速梳理排错思路,是一份兼顾原理与实操的FPGA网络接口设计参考。
1. 基于RGMII的以太网MAC:FPGA实现到底卡在哪
把以太网MAC跑在FPGA上,说简单也简单,说玄学也真玄学。RGMII这个接口看起来就12根线,但真正上板调通、跑到线速、不丢包,坑全藏在时序细节里。这份资源给的是完整的MAC层实现,从RGMII收发器、MAC核状态机到CRC校验、FIFO缓存,再到跨时钟域处理,每一段都有对应代码。适合两类人:一是刚接触以太网、想把UDP/IP协议栈往FPGA上搬的开发者;二是已经在用别人IP核、但出问题不知从何下手、想自己掌握每一行逻辑的工程师。我拆完这套工程后最深的感受是:MAC从来不是瓶颈,RGMII的时序约束和跨时钟域才是决定能不能跑到千兆的关键。这篇文章就把整个实现链路拆开,参数怎么设、代码怎么写、板上怎么调、哪些坑必须躲,一并讲清。
2. RGMII接口:先搞懂为什么是它,再谈怎么实现
2.1 三选一:GMII、RGMII、SGMII为何选RGMII
以太网MAC和PHY之间的接口,工程上最常碰到的就是GMII、RGMII、SGMII三种。GMII是8位并行数据接口,发射时钟125MHz,意味着每一拍要并行走8位数据,在FPGA的引脚资源上开销很大——数据线、控制线加起来超过20根,而且高速翻转带来的PCB布线压力也不小。SGMII则是串行接口,用1.25Gbps的SerDes差分对走数据,引脚省但需要在FPGA里例化高速收发器,资源占用和复杂度并不低,而且很多中小规模FPGA芯片根本没有SerDes模块。
RGMII是Reduced GMII的缩写,它的核心思路是把数据线从8位砍到4位,用DDR双沿采样来弥补带宽。发射时钟125MHz,上升沿和下降沿各送4位,等效8位吞吐;接收方向同理。12根线就能跑满千兆,引脚占用小、FPGA内部逻辑简单、普通IO就能满足要求,这也是绝大多数开发板、SoC的MAC-PHY接口选择RGMII的原因。资源包里的实现就是基于这个接口做的,完整覆盖了MAC侧收发的全部逻辑。
接口选型这件事直接决定后续工作量。选GMII,FPGA引脚多出一倍,PCB绕线难度加大,但逻辑简单;选SGMII就得查芯片手册看有没有GTP/GTX,没有就白搭。RGMII对于绝大多数项目来说是最平衡的那个点:引脚少、逻辑适中、时序可控。要注意的是,RGMII有两种模式——引脚上下拉配置决定是3.3V还是2.5V电平标准,这个错了,上板直接不亮。
2.2 RGMII时序模型:TSU、HOLD和时钟偏移怎么算
RGMII的时序理解是整个实现能否成功的分水岭。发射方向上,MAC在125MHz时钟驱动下,把TXD[3:0]在时钟上升沿送低4位、下降沿送高4位,TXC则是一个和TXD对齐的125MHz时钟。PHY在TXC的上下边沿分别采样。接收方向反过来,PHY把RXC和RXD发给MAC,MAC需要在RXC的双沿采样RXD。
这里就引出一个关键概念:TXC和TXD之间是有相位关系的。RGMII规范要求TXC相对TXD有大约2ns的延迟,这个延迟称为TSU/HOLD窗口。在FPGA实现中,ODDR原语会自动把时钟和数据的相位关系处理好,但前提是你得正确使用原语,而不是用普通的组合逻辑去拼。
我们看一组典型参数:
| 参数 | 典型值 | 说明 |
|---|---|---|
| 时钟频率 | 125 MHz | 周期8ns,双沿等效250M采样 |
| TSU | 1 ns | 数据在时钟沿前需稳定的时间 |
| HOLD | 1.2 ns | 数据在时钟沿后需保持的时间 |
| 数据线延迟 | 0.2-1.5 ns | 受PCB走线和IOB延迟影响 |
| TXC-TXD偏移 | 1.5-2.5 ns | RGMII规范建议约2ns |
这段时序信息直接决定你后面写的约束文件长什么样,也决定上板后能不能稳定跑在千兆速率而不随机丢包。RGMII的调节依赖于PHY侧的延时配置,比如某些PHY芯片支持通过寄存器调整内部延时,但如果MAC侧不做任何约束、直接把时钟和数据一起发出去,PCB走线差异就会吃掉整个时序裕量。
2.3 核心代码:SDR转DDR是第一步
PL端实现RGMII接口,第一步做的是把内部的单沿数据转成双沿输出。用Xilinx的ODDR原语一句话就能解决:
// SDR转DDR:在时钟上升沿送低4位,下降沿送高4位 ODDR #( .DDR_CLK_EDGE ("SAME_EDGE"), // 双沿同一时刻输出 .INIT (1'b0), .SRTYPE ("SYNC") ) oddr_txd_inst ( .Q (rgmii_txd[3:0]), // 输出到PHY的数据线 .C (clk_125m), // 发射时钟125MHz .CE (1'b1), .D1 (txd_low[3:0]), // 上升沿送低4位 .D2 (txd_high[3:0]), // 下降沿送高4位 .R (1'b0), .S (1'b0) );这段代码的逻辑不复杂:ODDR在时钟上升沿把D1输出到Q,下降沿把D2输出到Q。关键在SAME_EDGE模式——它让D1和D2在同一个时钟沿被锁存,避免D2数据提前半个周期到达导致毛刺。实际工程里很多翻车就翻在这里:用了OPPOSITE_EDGE模式,结果D2数据需要提前送,在跨时钟域场景下极易出亚稳态。
接收方向用IDDR。注意IDDR的SAME_EDGE模式会在一个时钟周期后同时输出上下沿采到的两路数据,这意味着接收数据天然有1拍延迟,在MAC层做FIFO写入时序时,要把这个延迟算进去,否则首字节就错位。
// DDR转SDR:接收数据上下沿分别锁存 IDDR #( .DDR_CLK_EDGE ("SAME_EDGE"), .INIT (1'b0) ) iddr_rxd_inst ( .Q1 (rxd_low[3:0]), // 上升沿采到的数据 .Q2 (rxd_high[3:0]), // 下降沿采到的数据 .C (clk_125m), .CE (1'b1), .D (rgmii_rxd[3:0]), .R (1'b0), .S (1'b0) );还有一个细节:RGMII的数据线都是4位宽,但每个DDR时钟周期实际搬运了8位数据。所以内部数据总线宽度不是4位而是8位,在FIFO写入端要以8位为单位组装,否则帧数据错位后CRC必然报错。
2.4 输入输出约束:没有约束的RGMII就是裸奔
代码写对了,约束没写对,照样跑不起来。RGMII约束的核心是set_input_delay和set_output_delay。以发射方向为例,FPGA输出的TXD相对TXC的关系需要约束:
# 发射方向:TXC相对TXD延时2ns,约束输出延迟范围 set_output_delay -clock [get_clocks clk_125m] -max 2.5 [get_ports {rgmii_txd[*]}] set_output_delay -clock [get_clocks clk_125m] -min 0.5 [get_ports {rgmii_txd[*]}] set_output_delay -clock [get_clocks clk_125m] -clock_fall -max 2.5 [get_ports {rgmii_txd[*]}] set_output_delay -clock [get_clocks clk_125m] -clock_fall -min 0.5 [get_ports {rgmii_txd[*]}]接收方向的约束,前提是PHY送给MAC的RXC和RXD关系。多数PHY的RXC相对RXD也是有延时的,要对PHY手册确认是中心对齐还是边沿对齐,再对应写输入延迟:
# 接收方向:假设PHY输出数据相对时钟中心对齐 set_input_delay -clock [get_clocks clk_rx_125m] -max 1.5 [get_ports {rgmii_rxd[*]}] set_input_delay -clock [get_clocks clk_rx_125m] -min 0.5 [get_ports {rgmii_rxd[*]}]这里有个工程上常见的模糊地带——很多PHY(比如某款国产PHY)要求MAC侧把RXC相对RXD延时1.5-2ns,否则采样点落在数据跳变沿上,会出现间歇性错包。这个可以通过PHY的寄存器配置实现,也可以通过FPGA的IO延时原语(IDELAYE2)来微调。资源包里给了两种方案,用寄存器方案更省逻辑,但不够灵活;用IDELAYE2可以逐拍扫描延迟值找最佳采样点,调试的时候非常管用。
注意约束里的时钟名要和你代码里的时钟网络完全一致。Vivado里很容易出现get_clocks返回空集合导致约束静默失效的情况,跑完综合后一定要看时序报告,确认这些约束被真正解析到了。
3. MAC收发通路:从状态机到CRC,逐模块拆解
3.1 MAC帧格式和IPG:先立规矩再写代码
MAC层收发的对象是以太网帧。标准以太网帧结构从目的MAC地址开始,到FCS(帧校验序列)结束,最小帧长64字节,最大1518字节(不含前导码和SFD)。工程上有几个预谋已久的坑点:一是前导码(7字节0x55)和SFD(1字节0xD5)在接收侧需要判断并剥离,但发送侧需要自动生成;二是帧间隙IPG(Inter-Packet Gap)是12字节,千兆速率下就是96ns,不能少。如果IPG不够,PHY或对端交换机可能把连续两帧当成一帧接收,或者因为缓冲溢出而丢帧。
MAC实现里还有一个容易忽略的点:全双工模式下不需要CSMA/CD,但半双工模式下存在碰撞退避逻辑。现在绝大多数场景都是全双工,RGMII接口基本只跑全双工。资源包里默认做成全双工模式,简化了状态机,也正好契合当前交换机环境的实际需求。
MAC发送状态机的核心状态如下:
| 状态 | 动作 | 退出条件 |
|---|---|---|
| IDLE | 等待上层数据有效 | 检测到发送请求 |
| PREAMBLE | 输出前导码和SFD | 8字节发送完成 |
| DATA | 逐字节搬移数据 | 帧数据已发完 |
| PAD | 不足64字节时补0 | 已凑够60字节数据 |
| FCS | 输出CRC32校验值 | 4字节校验码发完 |
| IPG | 等待帧间隙 | 12字节间隙计时结束 |
3.2 发送状态机代码:带自动补零和CRC插入
发送状态机是整个MAC发送通路的中枢,我拆到的核心代码长这样:
// 发送状态机:从IDLE到IPG的完整流转 always @(posedge clk_125m or negedge rst_n) begin if (!rst_n) begin state <= IDLE; tx_byte_cnt <= 0; crc_en <= 0; end else begin case (state) IDLE: begin if (tx_req) begin // 上层请求发送 state <= PREAMBLE; tx_byte_cnt <= 0; end end PREAMBLE: begin if (tx_byte_cnt == 8) begin // 前导码+SFD共8字节 state <= DATA; tx_byte_cnt <= 0; crc_en <= 1; // 打开CRC计算 end else begin tx_byte_cnt <= tx_byte_cnt + 1; end end DATA: begin if (tx_valid && tx_last) begin // 整帧数据送完 if (tx_byte_cnt < 60) begin state <= PAD; // 不够最小帧长补零 end else begin state <= FCS; end end end PAD: begin if (tx_byte_cnt == 60) state <= FCS; // 数据+填充凑满60字节 else tx_byte_cnt <= tx_byte_cnt + 1; end FCS: begin if (tx_byte_cnt == 4) begin // CRC共4字节 state <= IPG; crc_en <= 0; end end IPG: begin if (tx_byte_cnt >= 12) state <= IDLE; // 12字节帧间隙 else tx_byte_cnt <= tx_byte_cnt + 1; end endcase end end这段代码有几个值得留意的参数点。第一,PAD和DATA边界计数器的关系——标准要求帧长不足64字节时要补零到64字节,也就是数据域加填充至少60字节(不算前导、SFD和FCS)。这里计数从0开始,DATA阶段每发一个字节加一,FCS阶段也要占4拍,加起来刚好满足最小帧长。第二,CRC使能信号只在DATA和PAD阶段打开,前导码和SFD不参与计算,FCS本身也不参与。这个细节错一个,CRC就是错的。第三,IPG的长度是用周期数计数的,125MHz下12字节等于96ns,所以计数到12就能保证PHY侧的IPG不小于规范要求。
3.3 CRC32的两种实现:并行查表最省逻辑
CRC32在以太网里的多项式是0x04C11DB7,实现方式有串行LFSR和并行查表两种。串行LFSR最省资源但每bit一拍,千兆速率下需要处理125M*8bit/s的数据流,根本来不及。并行实现则要求每个时钟周期计算8bit数据的CRC,组合逻辑深、时序压力大。资源包用的是查表法——预先算出256项的CRC表,每拍查一次表,再做异或和移位,逻辑级数可控,时序收敛容易。
// CRC32并行查表实现,数据位宽8bit function [31:0] crc32_8bit; input [7:0] data; input [31:0] crc; reg [31:0] next_crc; begin next_crc = crc ^ {24'd0, data}; crc32_8bit = 32'hFFFFFFFF; // 占位,真实代码查表 // 查表逻辑:先对低8位查crc_table,再移位异或 end endfunction实际工程里更常见的做法是直接用IP核,但用IP核会黑盒化,出问题难排查。手写CRC的好处是你能在仿真里把每一拍的CRC值和标准工具(比如在线CRC计算器)比对,定位到底是数据拼错还是计算错。这里给一个经验:CRC计算时数据字节序和位序都要和以太网规范对齐,帧内容从左往右发送,但CRC计算是按位从LSB开始,一旦字节序反了,CRC永远对不上。
3.4 接收通路:前导码检测和FIFO写入时机
接收状态机的复杂度比发送高,原因在于发送的数据是自产的,什么时候发什么完全可控;接收的数据来自PHY,什么时候开始、什么时候结束都由外部决定。前导码检测逻辑要能容忍PHY在前导码阶段可能出现的一两个bit错误,但SFD字节必须严格匹配0xD5,否则就丢弃并回到IDLE。
接收到完整帧后,MAC把数据写入FIFO。这里的关键是FIFO的写时序和IDDR的延迟对齐——IDDR SAME_EDGE模式输出的数据比线上数据晚一拍,所以写使能信号也要相应延后一拍。如果直接拿RXC去当写时钟、用接收状态机的valid信号当写使能,大概率会出现首字节缺失,或者整帧错位。
// 接收侧FIFO写入:数据对齐和写使能的时序关系 always @(posedge rx_clk or negedge rst_n) begin if (!rst_n) begin rx_d_valid_dly <= 0; end else begin rx_d_valid_dly <= rx_d_valid; // 写使能打一拍对齐数据 end end // 写FIFO:数据是IDDR输出的延后版本,使能也要延后 fifo_wr_en = rx_d_valid_dly && (rx_state != IDLE); fifo_wr_data = {rxd_high[3:0], rxd_low[3:0]}; // 高4位是下降沿数据注意这里数据拼位法:在SAME_EDGE模式下,Q1是上升沿采到的低4位,Q2是下降沿采到的高4位,拼成一个字节时高4位在前、低4位在后,顺序错了整个帧的字节序就乱了。
3.5 跨时钟域:从125MHz进FIFO到用户时钟
MAC的收发时钟是独立的125MHz,而FPGA内部用户逻辑通常跑在更低的时钟频率(比如100MHz、150MHz)或者完全异步的时钟域。两个时钟域之间的数据搬运只能靠异步FIFO。资源包里在收发两端各放了一个异步FIFO,读端连接到用户逻辑时钟域。
用异步FIFO最怕的是FIFO本身的空满信号跨时钟域出问题。标准的做法是使用格雷码指针同步,这在Xilinx的FIFO IP里已经封装好了,不需要自己写。但有一个坑:FIFO的almost_full和almost_empty信号在跨时钟域后会有最多3个周期的延迟,如果你的MAC状态机直接拿almost_full当反压信号,在最极限的连续帧场景下可能溢出。工程上稳妥的做法是留出至少4个以太网帧的缓冲深度,并把almost_full阈值设为深度减16,给同步延迟留余量。
资源包的FIFO深度默认是1024x8bit,最大帧1518字节大概需要1518个存储单元,1024深度其实是装不下一整帧的。所以如果要做大帧传输,深度至少要2048,否则接收侧会溢出丢帧。这个参数是拿到工程后第一件要改的事。
4. 时序收敛与上板验证:从仿真通过到真正跑起来
4.1 仿真和上板的温差:为什么仿真通过还会翻车
仿真通过不代表上板能跑,这是硬件工程师的共识,但RGMII尤其如此。仿真环境里时钟和数据的关系是理想化的,没有PCB走线延迟,没有IOB的Tsu/Th要求,没有ODDR的时序限制,也没有供电噪声。到了板上,任何一个时序裕量不足的路径都可能变成随机丢包的来源。
我常用的验证策略是三级递进:第一级是纯RTL仿真,验证MAC状态机和CRC逻辑正确;第二级是加入IOB模型的仿真,验证ODDR/IDDR的时序行为;第三级是上板用内部逻辑分析仪抓RGMII引脚上的实际波形。工程包里提供了一个简单的以太网帧生成器,可以在仿真时注入已知数据帧,再在接收端比对。
4.2 约束文件实战:一组能直接用的XDC
以下约束适用于常见的Artix-7系列,引脚位置请替换成自己板卡实际原理图:
# RGMII引脚约束,电平标准3.3V,注意PCB上是否有串联电阻 set_property PACKAGE_PIN AB12 [get_ports {rgmii_txd[0]}] set_property PACKAGE_PIN AA12 [get_ports {rgmii_txd[1]}] set_property PACKAGE_PIN AB11 [get_ports {rgmii_txd[2]}] set_property PACKAGE_PIN AA11 [get_ports {rgmii_txd[3]}] set_property PACKAGE_PIN AB10 [get_ports {rgmii_tx_ctl}] set_property PACKAGE_PIN AC12 [get_ports {rgmii_txc}] set_property IOSTANDARD LVCMOS33 [get_ports {rgmii_*}] # 时钟约束:TXC是125M时钟输出 create_clock -name clk_125m -period 8.000 [get_ports {rgmii_txc}] # 发射方向的输出延迟约束(关键!) set_output_delay -clock [get_clocks clk_125m] -max 2.0 [get_ports {rgmii_txd[*]}] set_output_delay -clock [get_clocks clk_125m] -min 0.5 [get_ports {rgmii_txd[*]}] set_output_delay -clock [get_clocks clk_125m] -clock_fall -max 2.0 [get_ports {rgmii_txd[*]}] set_output_delay -clock [get_clocks clk_125m] -clock_fall -min 0.5 [get_ports {rgmii_txd[*]}]注意create_clock加在rgmii_txc端口上,是为了让工具知道这个输出时钟的周期,从而计算ODDR输出路径的时序关系。很多人一开始不建这个时钟,导致ODDR路径没有时序约束,工具随机布局布线,上板后时序裕量可能是负的。
4.3 ILA抓波形:怎么判断数据到底错在哪
上板后第一件事是用ILA抓RGMII接收引脚。抓之前要明确一个前提——ILA本身需要额外的时钟,最常见的做法是用RXC作为ILA的采样时钟。但RXC在无帧时是125MHz连续时钟,有帧时也是连续时钟,作为采样时钟没问题。不过ILA的存储深度有限,抓长时间数据不现实,建议用触发条件匹配SFD字节0xD5来捕获帧头,然后看后续数据流。
抓到的波形如果发现RXD上的数据和期望不一致,先不要怀疑MAC逻辑,先确认RXC和RXD的相位关系——把两个信号一起抓到,看数据变化沿和时钟沿的相对位置。如果数据在时钟沿附近变化,说明采样点不在数据眼图中央,就要调整PHY的延时配置或IDELAYE2的tap值。MAC逻辑反而很少出错,因为它只跟RXC打交道。
5. 避坑与调试:RGMII以太网MAC的五个血泪踩坑记录
5.1 现象:上板后收发都不通,示波器看TXC有波形但PHY不识别
原因:RGMII的TX_CTL和TXD共用DDR机制,TX_CTL在上升沿表示TX_EN,下降沿表示TX_ER。如果ODDR的D1和D2接反了,TX_EN被当成错误标志,PHY直接丢弃所有帧。很多人把TX_CTL的D1接成了TX_ER,D2接成了TX_EN,结果控制信号整个反转。
解决:检查自己的ODDR实例,确保D1是TX_EN、D2是TX_ER。仿真里加一段断言:在发送数据期间,TX_CTL的高电平必须持续整个帧时间,而不是只在某一半周期出现。
5.2 现象:能用但每传几个大帧就丢一帧,速率越高丢得越狠
原因:FIFO深度不够。默认1024深度装不下一整帧1518字节,大帧进来直接溢出。而且almost_full跨时钟域同步延迟3个周期,在连续帧场景下MAC读侧根本来不及响应。
解决:把FIFO深度改为2048或4096,almost_full阈值设为深度减16。这个改动只要改参数,不需要动逻辑。改完后再压测大帧长时间传输,丢帧率归零。
5.3 现象:接收方向偶尔错字节,错误位置不固定
原因:要么是RXC和RXD的相位偏了,采样点不在数据眼图中央;要么是跨时钟域处理不当,IDDR的Q1/Q2在低4位和高4位拼字节时顺序搞反。最容易出现在高速布局布线后,PCB走线差异导致RXD相对RXC的延迟变化。
解决:用IDELAYE2对RXD做可调延迟,在调试时把tap值从0到31扫一遍,找出丢包率为0的区间,固定到中间值。扫描方法:写一个小的调试逻辑,每换一个tap值就发1000帧测试包,统计错包数,画出延迟-错包率曲线,选中间的稳定区间。
5.4 现象:CRC校验总是报错,但数据看起来是对的
原因:字节序和位序问题。以太网CRC计算是从帧的第一个字节的LSB开始,而很多人的数据总线是MSB-first;或者MAC把FCS当作有效载荷的一部分送给了上层,上层再校验时把CRC也算进数据里了。
解决:先确认MAC收数据时是否剥离了FCS。如果上层软件用的是标准socket,内核会自动处理FCS,MAC层直接把带FCS的全帧交上去就会重复校验失败。再确认CRC计算模块的输入字节序和以太网规范一致。调试时可以抓一个已知帧,用Python的binascii.crc32手工算一遍,和MAC算出的值对比。
5.5 现象:TX方向单独测试正常,PC端ping不通,但RX方向能收到数据
原因:环路测试能过说明数据通路基本正常,ping不通大概率不是MAC的问题,而是ARP协议没处理。MAC层只负责收发以太网帧,ARP请求/响应需要上层协议栈处理。如果FPGA工程里只实现了MAC而没有对应的ARP应答模块,PC的ping自然不通。
解决:确认自己的工程是否包含ARP模块。很多MAC资源包不带协议栈,只到MAC层为止。ping需要ARP能通,TCP/UDP又依赖IP层,IP层又需要ARP。如果只是做MAC验证,用FPGA发已知UDP帧到PC,用Wireshark抓包看帧格式对不对就够了,不必纠结ping不通。
6. 用已有工程模板快速改出自己的千兆MAC:最小改动清单
拿到这套资源后,最快把它变成自己工程的方法不是从头理解每一行,而是按这份清单做定向修改。第一,替换引脚约束——找到XDC文件里所有的PACKAGE_PIN,换成自己板卡原理图上的实际引脚,IOSTANDARD按板卡电平改,3.3V或2.5V必须和原理图一致。第二,确认时钟——RGMII的TXC是MAC输出的,但这个时钟的来源有两种:一是MAC自己生成125MHz,二是由PHY反馈时钟。资源包的默认设计是用PLL生成125MHz驱动TXC,如果你的板卡是PHY反馈时钟模式,要改时钟拓扑,否则收发时钟频率不一致会丢帧。第三,调整FIFO深度——之前说过默认1024装不下一整帧,改成2048起步,如果要做巨型帧jumbo frame(9000字节),直接上8192。第四,对照PHY芯片手册检查RX延迟配置——不同PHY对RXC和RXD的相位关系定义不同,有的要求MAC侧额外加延时,有的要求不加,要根据实际PHY型号查寄存器手册。
改完这四个地方,整个工程基本就能跑起来。如果收到数据但CRC错,按调试章节的字节序检查法过一遍,大概率20分钟内定位。从那以后我每次接手新板卡,都会先做一遍最小改动清单再上板,避免从头理解所有代码浪费一整天。这套方法帮我跳过不少坑,希望帮到你。
本文还有配套的精品资源,点击获取