三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

ESP32自定义以太网PHY驱动开发指南:以ADIN1200为例

ESP32自定义以太网PHY驱动开发指南:以ADIN1200为例

1. 项目概述:为什么需要自定义PHY驱动?

在ESP32系列芯片上搞以太网开发,尤其是当你手头的板子用的不是乐鑫官方SDK里已经内置支持的那几款PHY芯片时,你大概率会卡在第一步:网络初始化失败,提示“PHY not found”。我最近就遇到了这个情况,项目里用了一颗ADI的ADIN1200,官方ESP-IDF的eth_phy组件里压根没它的身影。这时候,你就得自己动手,丰衣足食——编写一个自定义的以太网PHY驱动程序。

这听起来有点硬核,但说白了,PHY驱动就是一个“翻译官”。ESP32内部的以太网MAC(媒体访问控制器)只会说一种标准的“语言”(比如通过SMI/MII/RMII接口发送特定的寄存器读写命令),而不同的PHY芯片(如ADIN1200、LAN8720、IP101等)各有各的“方言”(寄存器地址、功能位定义不同)。自定义驱动的核心任务,就是为你的特定PHY芯片实现这套“方言”的翻译规则,让MAC能够正确地配置它、读取它的状态(比如链接是否建立、速度是10M还是100M、双工模式等),从而建立起可靠的物理层连接。

网上很多教程只讲如何配置官方已支持的PHY,一旦遇到非常规芯片,资料就寥寥无几。这篇指南就是来填这个坑的。我会以ADIN1200为例,手把手带你走通在ESP-IDF框架下,从零开始编写、集成并调试一个自定义PHY驱动的全过程。无论你用的是ADIN1200,还是其他类似的需要通过SMI(串行管理接口)管理的PHY,这套思路和方法都是通用的。

2. 核心思路与驱动框架解析

在动手写代码之前,我们必须先吃透ESP-IDF的以太网驱动框架。它采用了典型的分层设计,理解这个结构,才能知道我们的自定义驱动应该插在哪个位置,以及需要实现哪些功能。

2.1 ESP-IDF以太网驱动分层模型

ESP-IDF的以太网驱动可以粗略分为三层:

  1. 硬件抽象层(HAL):最底层,直接操作ESP32的MAC和SMI控制器寄存器。这部分通常由乐鑫的esp_eth组件提供,我们一般不直接触碰。
  2. PHY驱动层:中间层,也是我们自定义的核心。它通过标准的SMI接口与PHY芯片通信,实现PHY的初始化、配置、状态查询、中断处理等。这一层需要针对具体的PHY芯片型号进行实现。
  3. 网络接口层:最上层,基于LwIP协议栈,提供标准的Socket API。驱动配置好后,这一层会自动工作。

当我们调用esp_eth_driver_install()来安装以太网驱动时,函数需要一个eth_mac_config_t和一个eth_phy_config_t结构体作为参数。其中,eth_phy_config_t里有一个关键成员:phy_addr(PHY的SMI地址)和reset_timeout_ms等。但最重要的是,我们需要提供一个eth_phy_t类型的驱动实例。

eth_phy_t是什么?它是一个结构体,定义了一组函数指针,这就是PHY驱动的“接口契约”或“操作集”。我们的自定义驱动,本质上就是实现这个结构体里定义的所有函数,并创建一个它的实例。

2.2 自定义驱动的实现蓝图

因此,我们的工作就非常明确了:

  1. 实现驱动函数:根据目标PHY芯片的数据手册,编写一组C函数,分别完成init,deinit,set_mediator,reset,negotiate,get_link,power_control,get_addr,del等操作。这些函数签名必须与eth_phy_t中定义的指针类型完全一致。
  2. 封装驱动实例:创建一个eth_phy_t类型的全局变量(例如phy_adin1200),将上一步实现的函数地址赋值给这个结构体的对应成员。
  3. 集成到项目:在应用程序的以太网初始化代码中,不再使用默认的esp_eth_phy_new_xxx(),而是直接使用我们自定义的&phy_adin1200实例。

这个过程中,最耗时和最需要谨慎对待的,就是第一步中的函数实现,它们需要严格遵循芯片数据手册的时序和寄存器定义。

注意:在开始编码前,请务必、反复、仔细阅读你的PHY芯片数据手册(Datasheet)中关于“SMI Management Interface”和“Register Set”的章节。这是你唯一且最重要的参考资料,一切操作都以手册为准。

3. 以ADIN1200为例的驱动实现详解

接下来,我们进入实战环节。我手头的ADIN1200是一颗支持10/100 Mbps的工业级单端口以太网PHY。我们假设它通过RMII接口连接ESP32,并通过SMI管理(最常用的方式)。

3.1 搭建驱动文件结构

首先在项目内创建一个新的组件或目录来存放驱动代码。例如,在components目录下创建phy_adin1200文件夹,并包含以下文件:

your_project/ ├── main/ │ └── app_main.c └── components/ └── phy_adin1200/ ├── CMakeLists.txt ├── include/ │ └── phy_adin1200.h └── phy_adin1200.c

CMakeLists.txt内容用于注册这个组件:

idf_component_register(SRCS “phy_adin1200.c" INCLUDE_DIRS “include”)

3.2 定义PHY寄存器与关键函数

phy_adin1200.h中,我们定义芯片特定的寄存器地址。ADIN1200的寄存器地址空间与标准的IEEE 802.3 Clause 22 MII兼容,但有很多扩展功能寄存器。

// phy_adin1200.h #pragma once #include “esp_eth_phy.h” #include “esp_err.h” #ifdef __cplusplus extern “C” { #endif // 基础控制与状态寄存器(Clause 22标准) #define ADIN1200_REG_BMCR 0x00 // 基本模式控制寄存器 #define ADIN1200_REG_BMSR 0x01 // 基本模式状态寄存器 #define ADIN1200_REG_PHYIDR1 0x02 // PHY标识符1 #define ADIN1200_REG_PHYIDR2 0x03 // PHY标识符2 #define ADIN1200_REG_ANAR 0x04 // 自协商通告寄存器 #define ADIN1200_REG_ANLPAR 0x05 // 自协商链路伙伴能力寄存器 #define ADIN1200_REG_ANER 0x06 // 自协商扩展寄存器 #define ADIN1200_REG_ANNPTR 0x07 // 自协商下一页发送 // ADIN1200特定功能寄存器(需查阅手册) #define ADIN1200_REG_PHY_SCR 0x10 // 状态与控制寄存器,用于获取链接状态、速度等 #define ADIN1200_REG_RX_ERR_CNT 0x1A // 接收错误计数器,用于调试 // 常用寄存器位定义(以BMCR为例) #define BMCR_RESET_BIT (1 << 15) #define BMCR_LOOPBACK_BIT (1 << 14) #define BMCR_AN_ENABLE_BIT (1 << 12) #define BMCR_POWER_DOWN_BIT (1 << 11) #define BMCR_ISOLATE_BIT (1 << 10) #define BMCR_RESTART_AN_BIT (1 << 9) #define BMCR_DUPLEX_MODE_BIT (1 << 8) #define BMCR_SPEED_SELECT_BIT (1 << 13) // 0 = 10Mbps, 1 = 100Mbps // 在状态寄存器(或PHY_SCR)中获取链接状态的位 #define PHY_SCR_LINK_STATUS_BIT (1 << 2) #define PHY_SCR_SPEED_BIT (1 << 1) // 假设此位表示速度,具体看手册 #define PHY_SCR_DUPLEX_BIT (1 << 0) // 假设此位表示双工,具体看手册 /** * @brief 创建并初始化一个ADIN1200 PHY驱动实例 * * @param[in] config PHY配置参数(如SMI地址、复位超时等) * @return eth_phy_t* 成功返回驱动实例句柄,失败返回NULL */ eth_phy_t *esp_eth_phy_new_adin1200(const eth_phy_config_t *config); #ifdef __cplusplus } #endif

3.3 核心驱动函数实现(phy_adin1200.c)

这是最核心的部分。我们需要实现eth_phy_t定义的所有接口函数。这里我挑几个最关键的函数进行详细拆解。

首先,定义一个私有数据结构体,用于保存驱动状态:

// phy_adin1200.c #include <string.h> #include “esp_log.h” #include “esp_eth.h” #include “phy_adin1200.h” static const char *TAG = “adin1200_phy”; typedef struct { esp_eth_mediator_t *mediator; // 中介对象,用于访问SMI读写函数 uint32_t addr; // PHY的SMI地址 bool link_status; // 当前链接状态缓存 int reset_timeout_ms; // 复位超时时间 } phy_adin1200_t;

1.phy_adin1200_init- 初始化函数这个函数在驱动安装时被调用。它的核心任务是硬件复位PHY芯片

static esp_err_t phy_adin1200_init(esp_eth_phy_t *phy) { phy_adin1200_t *adin1200 = __containerof(phy, phy_adin1200_t, parent); esp_err_t ret = ESP_OK; // 1. 执行硬件复位:向BMCR寄存器的复位位写1 ret = adin1200->mediator->phy_reg_write(adin1200->mediator, adin1200->addr, ADIN1200_REG_BMCR, BMCR_RESET_BIT); if (ret != ESP_OK) { ESP_LOGE(TAG, “write BMCR reset failed”); return ret; } // 2. 等待复位完成:复位位会自清除 uint32_t timeout_ms = adin1200->reset_timeout_ms; uint16_t reg_val = 0; while (timeout_ms--) { vTaskDelay(pdMS_TO_TICKS(10)); // 每10ms检查一次 ret = adin1200->mediator->phy_reg_read(adin1200->mediator, adin1200->addr, ADIN1200_REG_BMCR, ®_val); if (ret == ESP_OK && !(reg_val & BMCR_RESET_BIT)) { ESP_LOGI(TAG, “PHY reset completed”); break; } } if (reg_val & BMCR_RESET_BIT) { ESP_LOGE(TAG, “PHY reset timeout”); return ESP_ERR_TIMEOUT; } // 3. (可选)验证PHY ID,确保通信正常 uint16_t id1, id2; adin1200->mediator->phy_reg_read(adin1200->mediator, adin1200->addr, ADIN1200_REG_PHYIDR1, &id1); adin1200->mediator->phy_reg_read(adin1200->mediator, adin1200->addr, ADIN1200_REG_PHYIDR2, &id2); ESP_LOGI(TAG, “PHY ID: 0x%04X%04X”, id1, id2); // 可以在这里检查ID是否与ADIN1200的预期值匹配(查阅手册) // 4. 配置基础工作模式:例如使能自协商 uint16_t bmcr_val = BMCR_AN_ENABLE_BIT; // 使能自协商 // bmcr_val |= BMCR_SPEED_SELECT_BIT; // 如果强制100M,取消注释 // bmcr_val |= BMCR_DUPLEX_MODE_BIT; // 如果强制全双工,取消注释 ret = adin1200->mediator->phy_reg_write(adin1200->mediator, adin1200->addr, ADIN1200_REG_BMCR, bmcr_val); if (ret != ESP_OK) { ESP_LOGE(TAG, “configure BMCR failed”); } return ret; }

实操心得:复位后的等待循环是必须的。超时时间adin1200->reset_timeout_ms建议配置为500ms以上。有些PHY复位较慢,时间不够会导致后续所有操作失败。

2.phy_adin1200_negotiate- 启动自协商如果配置为自协商模式,这个函数需要触发自协商过程。

static esp_err_t phy_adin1200_negotiate(esp_eth_phy_t *phy) { phy_adin1200_t *adin1200 = __containerof(phy, phy_adin1200_t, parent); // 对于自协商,通常在上一步init中配置BMCR时已经使能。 // 如果需要重新协商,可以向BMCR写入 RESTART_AN 位。 uint16_t bmcr_val; esp_err_t ret = adin1200->mediator->phy_reg_read(adin1200->mediator, adin1200->addr, ADIN1200_REG_BMCR, &bmcr_val); if (ret != ESP_OK) { return ret; } bmcr_val |= BMCR_RESTART_AN_BIT; ret = adin1200->mediator->phy_reg_write(adin1200->mediator, adin1200->addr, ADIN1200_REG_BMCR, bmcr_val); ESP_LOGI(TAG, “Auto-negotiation restarted”); return ret; }

3.phy_adin1200_get_link- 获取链接状态这是驱动中最常被调用的函数之一(由网络栈定期轮询),用于判断网线是否插好、链接是否建立。

static esp_err_t phy_adin1200_get_link(esp_eth_phy_t *phy) { phy_adin1200_t *adin1200 = __containerof(phy, phy_adin1200_t, parent); uint16_t status_reg; esp_err_t ret; // 读取状态寄存器(这里以ADIN1200_REG_PHY_SCR为例,具体寄存器需查手册) ret = adin1200->mediator->phy_reg_read(adin1200->mediator, adin1200->addr, ADIN1200_REG_PHY_SCR, &status_reg); if (ret != ESP_OK) { return ret; } bool link_up = (status_reg & PHY_SCR_LINK_STATUS_BIT) ? true : false; // 链接状态发生变化时,通知上层(MAC驱动) if (adin1200->link_status != link_up) { adin1200->link_status = link_up; if (link_up) { uint16_t speed = (status_reg & PHY_SCR_SPEED_BIT) ? ETH_SPEED_100M : ETH_SPEED_10M; uint16_t duplex = (status_reg & PHY_SCR_DUPLEX_BIT) ? ETH_DUPLEX_FULL : ETH_DUPLEX_HALF; ESP_LOGI(TAG, “Link Up, Speed: %s, Duplex: %s”, speed == ETH_SPEED_100M ? “100M” : “10M”, duplex == ETH_DUPLEX_FULL ? “Full” : “Half”); // 通过mediator回调函数通知MAC驱动链接状态变化 adin1200->mediator->on_state_changed(adin1200->mediator, ETH_STATE_SPEED, (void *)speed); adin1200->mediator->on_state_changed(adin1200->mediator, ETH_STATE_DUPLEX, (void *)duplex); } else { ESP_LOGW(TAG, “Link Down”); } adin1200->mediator->on_state_changed(adin1200->mediator, ETH_STATE_LINK, (void *)link_up); } return ESP_OK; }

注意事项get_link函数会被频繁调用,因此其实现应尽可能高效。避免在每次调用时都进行复杂的寄存器读取或打印大量日志,否则可能会影响系统性能。通常只读取一个关键的状态寄存器即可。

4.phy_adin1200_reset- 软件复位除了上电硬件复位,有时需要通过软件复位PHY。

static esp_err_t phy_adin1200_reset(esp_eth_phy_t *phy) { // 软件复位通常就是再次触发BMCR的复位位 // 实现逻辑与init函数开头的复位部分类似,但通常不需要再等待那么久,或者复用init的复位流程。 // 一种简单的实现是直接调用 phy_adin1200_deinit 再调用 phy_adin1200_init。 // 这里为清晰起见,我们实现一个独立的软复位: phy_adin1200_t *adin1200 = __containerof(phy, phy_adin1200_t, parent); esp_err_t ret = adin1200->mediator->phy_reg_write(adin1200->mediator, adin1200->addr, ADIN1200_REG_BMCR, BMCR_RESET_BIT); if (ret != ESP_OK) { return ret; } vTaskDelay(pdMS_TO_TICKS(50)); // 等待一个较短的时间,通常软复位较快 ESP_LOGI(TAG, “Software reset performed”); return ESP_OK; }

5. 其他必需函数set_mediator,get_addr,power_control,del等函数实现通常比较模板化。

static esp_err_t phy_adin1200_set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *mediator) { phy_adin1200_t *adin1200 = __containerof(phy, phy_adin1200_t, parent); if (mediator == NULL) { return ESP_ERR_INVALID_ARG; } adin1200->mediator = mediator; return ESP_OK; } static esp_err_t phy_adin1200_power_control(esp_eth_phy_t *phy, bool enable) { phy_adin1200_t *adin1200 = __containerof(phy, phy_adin1200_t, parent); uint16_t bmcr_val; esp_err_t ret = adin1200->mediator->phy_reg_read(adin1200->mediator, adin1200->addr, ADIN1200_REG_BMCR, &bmcr_val); if (ret != ESP_OK) { return ret; } if (enable) { bmcr_val &= ~BMCR_POWER_DOWN_BIT; // 清除掉电位 } else { bmcr_val |= BMCR_POWER_DOWN_BIT; // 置位掉电位 } return adin1200->mediator->phy_reg_write(adin1200->mediator, adin1200->addr, ADIN1200_REG_BMCR, bmcr_val); } static esp_err_t phy_adin1200_get_addr(esp_eth_phy_t *phy, uint32_t *addr) { phy_adin1200_t *adin1200 = __containerof(phy, phy_adin1200_t, parent); *addr = adin1200->addr; return ESP_OK; } static esp_err_t phy_adin1200_del(esp_eth_phy_t *phy) { phy_adin1200_t *adin1200 = __containerof(phy, phy_adin1200_t, parent); free(adin1200); return ESP_OK; }

6. 驱动实例的创建函数这是对外暴露的API,用于生成一个eth_phy_t实例。

eth_phy_t *esp_eth_phy_new_adin1200(const eth_phy_config_t *config) { if (config == NULL) { ESP_LOGE(TAG, “config can‘t be null”); return NULL; } phy_adin1200_t *adin1200 = calloc(1, sizeof(phy_adin1200_t)); if (adin1200 == NULL) { ESP_LOGE(TAG, “calloc adin1200 object failed”); return NULL; } // 填充父类 eth_phy_t 的函数指针 adin1200->parent.reset = phy_adin1200_reset; adin1200->parent.init = phy_adin1200_init; adin1200->parent.deinit = NULL; // 如果不需特殊处理,可设为NULL adin1200->parent.set_mediator = phy_adin1200_set_mediator; adin1200->parent.negotiate = phy_adin1200_negotiate; adin1200->parent.get_link = phy_adin1200_get_link; adin1200->parent.power_control = phy_adin1200_power_control; adin1200->parent.get_addr = phy_adin1200_get_addr; adin1200->parent.del = phy_adin1200_del; // 根据ESP-IDF版本,可能还有 get_caps, set_duplex, set_speed 等函数,需要查阅对应版本的 esp_eth_phy.h 并实现 // 保存配置 adin1200->addr = config->phy_addr; adin1200->reset_timeout_ms = config->reset_timeout_ms; adin1200->link_status = false; return &(adin1200->parent); }

4. 在应用程序中集成与使用自定义驱动

驱动编写完成后,我们需要在应用程序中用它替换掉默认的PHY驱动。

4.1 修改主程序以太网初始化代码

在你的app_main.c或网络初始化文件中,包含头文件并修改配置。

#include “esp_eth.h” #include “esp_netif.h” #include “phy_adin1200.h” // 包含我们自定义驱动的头文件 void eth_init(void) { // 1. 创建网络接口 esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH(); esp_netif_t *eth_netif = esp_netif_new(&netif_cfg); // 2. 配置MAC和PHY eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); // 根据你的板子调整引脚 eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); // 这里会设置phy_addr=1, reset_timeout_ms=100等默认值 // 非常重要:根据你的硬件连接,设置正确的PHY地址(SMI地址) // ADIN1200的地址通常由硬件引脚决定,常见为1或0 phy_config.phy_addr = 1; phy_config.reset_timeout_ms = 500; // 给复位留足时间 phy_config.reset_gpio_num = -1; // 如果使用硬件复位引脚,在这里指定GPIO号。我们示例中使用软件复位,设为-1。 // 3. 创建MAC和PHY实例 // MAC使用ESP-IDF内置的EMAC驱动 esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); if (mac == NULL) { ESP_LOGE(TAG, “create MAC failed”); return; } // 关键步骤:使用我们自定义的ADIN1200 PHY驱动,而不是 esp_eth_phy_new_xxx() esp_eth_phy_t *phy = esp_eth_phy_new_adin1200(&phy_config); // 调用我们写的创建函数 if (phy == NULL) { ESP_LOGE(TAG, “create PHY (ADIN1200) failed”); free(mac); return; } // 4. 创建以太网驱动句柄并安装 esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); esp_eth_handle_t eth_handle = NULL; ESP_ERROR_CHECK(esp_eth_driver_install(&eth_config, ð_handle)); // 5. 将驱动句柄附加到网络接口,并设置事件处理器 ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle))); // 6. 注册以太网事件回调(处理链接、断开等) ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL)); // 7. 启动以太网驱动 ESP_ERROR_CHECK(esp_eth_start(eth_handle)); ESP_LOGI(TAG, “Ethernet driver (with custom ADIN1200 PHY) installed and started”); }

4.2 配置项目依赖

确保你的CMakeLists.txtcomponent.mk正确包含了自定义驱动组件。 在主项目的CMakeLists.txt中:

# 将自定义驱动组件添加到依赖中 set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/components $ENV{IDF_PATH}/components/esp_wifi $ENV{IDF_PATH}/components/esp_eth ./components) # 或者使用 idf_component_register 的 REQUIRES 参数

5. 调试与问题排查实录

即使代码逻辑正确,第一次调试也几乎不可能一帆风顺。以下是我在调试ADIN1200驱动时遇到的一些典型问题及解决方法。

5.1 常见问题速查表

问题现象可能原因排查步骤与解决方案
esp_eth_driver_install失败,返回ESP_ERR_TIMEOUT1. PHY硬件复位失败或超时。
2. SMI通信不成功(PHY地址错误、时钟极性不对、GPIO配置错误)。
3. 电源或晶振未正常工作。
1.检查复位时序:在phy_adin1200_init的复位等待循环中加入日志,确认复位位是否被清除。适当增加reset_timeout_ms(我试过加到1000ms)。
2.验证SMI通信:在init函数读取PHY ID寄存器。如果读出来全是0xFF或0x00,说明SMI通信失败。检查eth_mac_config_t中的smi_mdc_gpio_numsmi_mdio_gpio_num是否与硬件连接一致。用逻辑分析仪抓取MDC/MDIO波形是最直接的方法。
3.检查硬件:测量PHY芯片供电电压(通常3.3V或2.5V)、复位引脚电平、25MHz晶振是否起振。
驱动安装成功,但始终Link Down1. 网线未连接或对端设备问题。
2. PHY自协商未成功。
3.get_link函数读取的状态寄存器或状态位错误。
1.换网线、换交换机端口测试,排除物理层问题。
2.检查自协商配置:确认BMCR寄存器是否正确使能了自协商(BMCR_AN_ENABLE_BIT)。可以尝试强制模式(禁用自协商,强制设置速度和双工)来测试。
3.仔细核对数据手册:确认用于判断链接状态的寄存器地址和位定义绝对正确。ADIN1200的链接状态可能在BMSR寄存器(标准位)或PHY_SCR寄存器(芯片特定位)。我最初就错用了寄存器,导致永远读不到链接。
链接状态不稳定,时通时断1. 电磁干扰或信号完整性问题。
2. 电源噪声。
3. PHY的某些特定配置(如节能模式)导致。
1.检查PCB布局:RMII数据线、时钟线是否等长?是否有包地?远离噪声源。
2.增加电源去耦电容:在PHY的VDD引脚附近增加100nF和10uF电容。
3.查阅PHY手册的“Application Notes”:有些PHY需要配置额外的寄存器来优化性能或关闭某些测试模式。例如,ADIN1200可能需要配置REG_CRSM_SFD等寄存器来适应特定环境。
可以Ping通网关但无法访问外网或速度极慢1. MAC层配置问题(如RMII时钟源选择错误)。
2. 网络协议栈(LwIP)配置问题。
3. 驱动中速度/双工模式上报错误。
1.确认RMII时钟:ESP32作为MAC,其RMII参考时钟(通常50MHz)必须由外部提供或内部生成,配置在eth_mac_config_tclock_config成员中。这是最容易出错的地方之一,务必对照开发板原理图确认。
2.检查LwIP配置:在menuconfig中 (Component config -> LWIP) 确认相关功能已开启,如Enable IP forwarding,Enable SO_REUSE等。
3.get_link函数中打印日志:确认上报给MAC的速度和双工模式是否与交换机显示的一致。

5.2 高级调试技巧:使用SMI读写进行寄存器探查

当驱动不工作时,最有效的调试手段是直接通过SMI接口读写PHY寄存器,验证硬件和底层通信是否正常。你可以在驱动初始化之前或之外,写一个简单的测试函数。

#include “driver/gpio.h” #include “esp_eth.h” void test_phy_smi(uint32_t phy_addr) { // 临时创建一个MAC实例来获取mediator(仅用于调试) eth_mac_config_t mac_cfg = ETH_MAC_DEFAULT_CONFIG(); esp_eth_mac_t *test_mac = esp_eth_mac_new_esp32(&mac_cfg); if (test_mac == NULL) return; // 获取mediator(它提供了phy_reg_read/write函数) esp_eth_mediator_t *mediator = test_mac->get_mediator(test_mac); uint16_t val; esp_err_t ret; // 尝试读取PHY ID寄存器 ret = mediator->phy_reg_read(mediator, phy_addr, ADIN1200_REG_PHYIDR1, &val); ESP_LOGI(“TEST”, “Read PHYIDR1 (0x%02X) = 0x%04X, ret=%d”, ADIN1200_REG_PHYIDR1, val, ret); ret = mediator->phy_reg_read(mediator, phy_addr, ADIN1200_REG_PHYIDR2, &val); ESP_LOGI(“TEST”, “Read PHYIDR2 (0x%02X) = 0x%04X, ret=%d”, ADIN1200_REG_PHYIDR2, val, ret); // 读取状态寄存器 ret = mediator->phy_reg_read(mediator, phy_addr, ADIN1200_REG_BMSR, &val); ESP_LOGI(“TEST”, “Read BMSR (0x%02X) = 0x%04X, ret=%d”, ADIN1200_REG_BMSR, val, ret); // 记得删除MAC实例 test_mac->del(test_mac); }

app_main中早期调用这个函数(在以太网驱动安装前),可以快速判断SMI总线是否通畅、PHY地址是否正确、芯片是否响应。如果读出的ID是0xFFFF或0x0000,基本可以断定是硬件连接或SMI配置问题。

5.3 利用ESP-IDF内置的调试日志

确保在menuconfig中打开以太网组件的调试日志:

Component config -> Ethernet -> Enable Ethernet debug log (Verbose)

这样,ESP-IDF内部的以太网驱动会打印更详细的信息,包括SMI读写操作、MAC状态变化等,对于定位问题非常有帮助。

编写自定义PHY驱动是一个需要耐心和细致的过程,它紧密依赖于硬件和数据手册。成功的标志不仅仅是驱动能编译通过,更是网络能够稳定地连接和通信。当你看到 “Link Up, Speed: 100M, Duplex: Full” 的日志,并且能够成功Ping通网关时,那种成就感是非常实在的。希望这篇基于ADIN1200的详细指南,能为你实现其他PHY芯片的驱动提供清晰的路径和实用的排错方法。

← 返回列表