1、概述
本文通过NXP官方SDK讲解如何移植ENET的驱动,面向刚入门的小白。
官方IDE:MCUXpresso,SDK:MIMXRT1170-EVK。
2、SDK工程导入
详细步骤参照文章:<RT1176系列3>LPSPI入门级应用和基础API解析-CSDN博客
这次导入ENET_1G的例程做代码分析:
3、MDIO/MDC
3.1 定义
MDIO(Management Data Input/Output,管理数据输入 / 输出)是一种用于以太网物理层(PHY)和媒体访问控制层(MAC)之间进行管理通信的标准接口,广泛应用于嵌入式系统和网络设备中。它定义了一套简单的总线协议,让 MAC 能够读取和配置 PHY 的寄存器,从而实现对 PHY 工作状态的监控、模式设置(如速率、双工模式)、故障诊断等功能。
3.2 MDIO 的核心组成
MDIO 接口由两条信号线组成,二者共同构成管理总线:
- MDC(Management Data Clock,管理数据时钟):由 MAC 提供的时钟信号,用于同步 MDIO 总线上的数据传输。时钟频率通常在 10MHz 以下(常见为 2.5MHz 或 5MHz,具体需参考 PHY 芯片手册)。
- MDIO(Management Data Input/Output,管理数据输入 / 输出):双向数据线,用于 MAC 和 PHY 之间传输管理数据(寄存器地址、读写命令、数据值等)。
3.3 MDIO 的通信协议
MDIO 采用主从式通信模式(MAC 为主机,PHY 为从机),通过一套固定的帧格式实现数据传输。每次通信由 MAC 发起,帧结构包含以下关键部分(共 32 位):
位段 | 功能描述 |
---|---|
前导码(2 位) | 固定为 “01”,表示帧的开始(Start of Frame,SOF)。 |
操作码(2 位) | 定义操作类型:“10” 表示读操作,“01” 表示写操作。 |
PHY 地址(5 位) | 标识目标 PHY 的地址(0-31),由 PHY 的硬件引脚(如 PHYAD0~PHYAD4)配置,用于在多 PHY 系统中区分不同设备。 |
寄存器地址(5 位) | 表示要访问的 PHY 内部寄存器编号(0-31),不同 PHY 的寄存器功能不同(需参考手册)。 |
turnaround(2 位) | 用于切换 MDIO 总线的方向(从 MAC 输出到 PHY 输入,或反之),避免总线冲突。读操作时为 “10”,写操作时为 “01”。 |
数据(16 位) | 读操作时,PHY 返回的寄存器值;写操作时,MAC 发送的要写入寄存器的数据。 |
3.4 MDIO 使用注意事项
1)PHY 地址唯一性:多 PHY 系统中,每个 PHY 的地址必须不同(通过硬件引脚设置),否则会导致 MDIO 通信冲突。
2)时钟频率限制:MDC 时钟频率需符合 PHY 芯片要求(通常≤10MHz),过高会导致数据传输错误。
3)总线空闲状态:MDIO 线需通过上拉电阻(通常 10kΩ)保持空闲时为高电平,避免噪声干扰。
4)操作时序:需严格遵循协议规定的时序(如建立时间、保持时间),尤其是在高速通信时。
3.5 时序图
4、PHY部分代码解析
4.1 RESET PHY
上电复位PHY,优点是可以保证PHY每次上电工作正常,缺点是热复位时复位phy会导致以太网断一下。 IOMUXC_GPR_GPR5_ENET1G_RGMII_EN_MASK,配置GPR5的RGMII_EN位,使得可以使用千兆网。
IOMUXC_GPR->GPR5 |= IOMUXC_GPR_GPR5_ENET1G_RGMII_EN_MASK; /* bit1:iomuxc_gpr_enet_clk_dir
bit0:GPR_ENET_TX_CLK_SEL(internal or OSC) */
GPIO_PinInit(GPIO11, 14, &gpio_config);
/* For a complete PHY reset of RTL8211FDI-CG, this pin must be asserted low for at least 10ms. And
* wait for a further 30ms(for internal circuits settling time) before accessing the PHY register */
SDK_DelayAtLeastUs(10000, CLOCK_GetFreq(kCLOCK_CpuClk));
GPIO_WritePinOutput(GPIO11, 14, 1);
SDK_DelayAtLeastUs(30000, CLOCK_GetFreq(kCLOCK_CpuClk));
4.2 配置phy句柄
通过定义phy_ops获取相关phy操作的接口,这种写法非常规范,代码直接分割的很清晰。
extern phy_rtl8211f_resource_t g_phy_resource;
#define EXAMPLE_ENET ENET_1G
#define EXAMPLE_PHY_ADDRESS 0x01U
#define EXAMPLE_PHY_OPS &phyrtl8211f_ops
#define EXAMPLE_PHY_RESOURCE &g_phy_resource
typedef struct _phy_rtl8211f_resource
{
mdioWrite write;
mdioRead read;
} phy_rtl8211f_resource_t;
extern phy_rtl8211f_resource_t g_phy_resource;
#define EXAMPLE_ENET ENET_1G
#define EXAMPLE_PHY_ADDRESS 0x01U
#define EXAMPLE_PHY_OPS &phyrtl8211f_ops
#define EXAMPLE_PHY_RESOURCE &g_phy_resource
const phy_operations_t phyrtl8211f_ops = {.phyInit = PHY_RTL8211F_Init,
.phyWrite = PHY_RTL8211F_Write,
.phyRead = PHY_RTL8211F_Read,
.getAutoNegoStatus = PHY_RTL8211F_GetAutoNegotiationStatus,
.getLinkStatus = PHY_RTL8211F_GetLinkStatus,
.getLinkSpeedDuplex = PHY_RTL8211F_GetLinkSpeedDuplex,
.setLinkSpeedDuplex = PHY_RTL8211F_SetLinkSpeedDuplex,
.enableLoopback = PHY_RTL8211F_EnableLoopback,
.enableLinkInterrupt = PHY_RTL8211F_EnableLinkInterrupt,
.clearInterrupt = PHY_RTL8211F_ClearInterrupt};
/* Set SMI to get PHY link status. */
phyConfig.phyAddr = EXAMPLE_PHY_ADDRESS;
phyConfig.ops = EXAMPLE_PHY_OPS;
phyConfig.resource = EXAMPLE_PHY_RESOURCE;
phyConfig.autoNeg = true;
4.3 初始化phy直到link和自适应上或者超时
-
Link(链路):指 PHY 与对端设备通过网线建立的物理连接状态,link只表示物理上连接。
- 有效(Up):双方信号同步、物理参数兼容,可传输数据;
- 无效(Down):可能因网线问题、信号衰减或参数不兼容导致。
可通过 PHY 状态寄存器的 “链路状态位” 查看。
-
自适应(Auto-negotiation):PHY 与对端自动协商最优通信参数的功能。
- 协商内容:传输速率(10/100/1000Mbps 等)、双工模式(全双工 / 半双工)等;
- 作用:自动匹配双方都支持的最高性能模式,无需手动配置;
- 控制:通过 PHY 控制寄存器的 “自适应使能位” 开启 / 关闭,关闭时需手动配置参数且需与对端一致。
do
{
result = PHY_Init(&phyHandle, &phyConfig);
if (result == kStatus_Success)
{
PRINTF("Wait for PHY link up...\r\n");
/* Wait for auto-negotiation success and link up */
count = PHY_AUTONEGO_TIMEOUT_COUNT;
do
{
PHY_GetAutoNegotiationStatus(&phyHandle, &autonego);
PHY_GetLinkStatus(&phyHandle, &link);
if (autonego && link)
{
break;
}
} while (--count);
if (!autonego)
{
PRINTF("PHY Auto-negotiation failed. Please check the cable connection and link partner setting.\r\n");
}
}
} while (!(link && autonego));
5、通用配置phy的步骤
总结一般配置phy的过程:
1)复位PHY,通过芯片上的RESET管脚拉低拉高给复位信号;
2)MDIO初始化,保证后续通信读与写;
3)PHY初始化,调用PHY_INIT接口,一般在里面配置相应的状态寄存器,并且回读PHY ID确保与PHY之间通信正常;
4)INIT完成后获取PHY的LINK状态、自适应状态以及通信速率和全双工半双工等信息用于后面ENET的配置和初始化;
6、phy配置接口解析(phyrtl8211f)
6.1 PHY_RTL8211F_Init
INIT中进行了检查PHY ID、重置PHY寄存器、配置RGMII延迟、配置中断引脚、清除中断、设置自动协商模式以及启用链路中断的操作。
status_t PHY_RTL8211F_Init(phy_handle_t *handle, const phy_config_t *config)
6.2 PHY_RTL8211F_Write
底层是通过MDIO/MDC进行写操作。
status_t PHY_RTL8211F_Write(phy_handle_t *handle, uint8_t phyReg, uint16_t data)
6.3 PHY_RTL8211F_Read
底层是通过MDIO/MDC进行读操作。
status_t PHY_RTL8211F_Read(phy_handle_t *handle, uint8_t phyReg, uint16_t *pData)
6.4 PHY_RTL8211F_GetAutoNegotiationStatus
读取相关寄存器配置返回自适应状态。
status_t PHY_RTL8211F_GetAutoNegotiationStatus(phy_handle_t *handle, bool *status)
6.5 PHY_RTL8211F_GetLinkStatus
读取相关寄存器配置返回link状态。
status_t PHY_RTL8211F_GetLinkStatus(phy_handle_t *handle, bool *status)
6.6 PHY_RTL8211F_GetLinkSpeedDuplex
读取相关寄存器配置返回通信速率以及全双工/半双工状态。
status_t PHY_RTL8211F_GetLinkSpeedDuplex(phy_handle_t *handle, phy_speed_t *speed, phy_duplex_t *duplex)
6.7 PHY_RTL8211F_SetLinkSpeedDuplex
写入相关寄存器配置通信速率以及全双工/半双工状态。
status_t PHY_RTL8211F_SetLinkSpeedDuplex(phy_handle_t *handle, phy_speed_t speed, phy_duplex_t duplex)
6.8 PHY_RTL8211F_EnableLoopback
启用/禁用回环测试,回环测试用于测试和诊断链路完整性。
status_t PHY_RTL8211F_EnableLoopback(phy_handle_t *handle, phy_loop_t mode, phy_speed_t speed, bool enable)
6.9 PHY_RTL8211F_EnableLinkInterrupt
启用链路中断变化。
“链路中断变化” 指的是 PHY 与对端设备之间的物理连接状态(即链路状态)发生的从 “正常连接(Link Up)” 到 “断开(Link Down)” 或从 “断开(Link Down)” 到 “正常连接(Link Up)” 的切换事件。
status_t PHY_RTL8211F_EnableLinkInterrupt(phy_handle_t *handle, phy_interrupt_type_t type)
6.10 PHY_RTL8211F_ClearInterrupt
清除链路变化中断。
status_t PHY_RTL8211F_ClearInterrupt(phy_handle_t *handle)