“我想用我最喜欢的芯片开始新的产品设计,但它断货了!哦,不!我必须设计一个新的 PCB,并重新开发驱动程序!”如今,每个设计师都非常清楚这种感觉…
好消息是,至少在 ESP-IDF 以太网 PHY 驱动程序支持方面,您再也不用担心这种事情了。在本文中,我将向您演示创建一个新的以太网 PHY 驱动程序是多么简单。
首先,您需要找到当前 PHY 芯片的替代品。我刚好有个朋友在一家半导体经销公司工作,所以我直接拿起电话向他请教。这位朋友推荐了 ADIN1200。这是一款具有广泛功能的强大工业级芯片,对于我们的演示来说,是一个理想的候选产品,原因包括:
有现成的评估板;
该芯片来自目前 ESP-IDF 未支持的供应商(也就是说,我们有机会帮助客户解锁更多供应商),
该芯片符合 IEEE 802.3 标准。
正因如此,创建新驱动程序所需的工作量大大降低了,EMAC 和 PHY 之间的管理接口是标准化的,并且 ESP-IDF v5.0 也利用了这一点。ESP-IDF 以太网驱动程序ADIN1200 产品页面基本上由三层组成:
以太网对象本身是公共 API,已经将 MAC 和 PHY 层封装为一个功能单元。
MAC 层控制媒体访问控制器的行为,并为驱动程序应用程序提供数据接口。
PHY 层控制物理层属性,并收集链路状态。
IEEE 802.3 标准的“22.2.4管理功能”章节定义了所谓的“MII 管理接口”,可控制 PHY 并通过 MAC 从PHY 收集状态;定义了一组管理寄存器,ESP-IDF 以太网驱动程序将此基本管理功能作为一组通用功能进行处理。因此,在准备新的 PHY 驱动程序时,您只需要关注芯片的特定功能,如:
链路状态指示,这点几乎每款芯片都有不同。
芯片初始化。这部分非常常见,不作严格要求。建议增加该部分,确保使用正确的芯片。
芯片特定功能配置(如 LAN 唤醒、节能以太网、各种诊断功能等)。
接下来,让我们演示创建驱动程序的具体步骤
硬件方面建议可以先使用 EVAL-ADIN1200 评估板,您仅需进行少量修改即可通过 RMII 连接到ESP32。(当然,您也可以根据需求,选择从 PCB 设计从头开始。)
硬件准备:
查看 ADIN1200 产品页面,熟悉芯片和评估板的基本情况和特性。
在熟悉了评估版之后,需要进行以下修改:
ADIN1200 需外接一个 50 MHz RMI REF_CLK,因此需要拆焊 Y1 振荡器及其相关耦合电容器。
R120 处焊接 0R 电阻。
在 RX_CTL (高)和 RX_CLK (高) 处焊接 10K 上拉电阻,配置 ADIN1200 进入 RMII 模式。
有关其他配置选项,请见《EVAL-ADIN1200FMCZ 用户手册》中的“表5. EVAL-ADIN1200FMCZ 配置设置”。
3. 连接 ADIN1200 RMII 接口到 ESP32。我将电线焊接至暴露的 0R 电阻上(如下表所示)。请使电线尽可能短,并且长度相同。
注意,RMII REF_CLK 需要由外部 50 MHz 振荡器或 ESP32 在 ADIN1200 外部生成。在进行演示时,我使用了更简单的 ESP32,但实际上您需要使用 ESP32-WROOM 模组。
EVAL-ADIN1200 评估板的硬件修改结果见下图。
搭载 ESP32-WROOM 的 EVAL-ADIN1200
软件准备则更简单。
创建新的以太网 PHY 驱动程序的步骤:
前往 ESP-IDF 的 /components/esp_eth/src/ 文件夹,复制 esp_eth_phy_ip101.c 或任何其他 IEEE 802.3 兼容的 phy 芯片源文件至一个新的文件夹。
将所有 “ip101” 均替换为 “adin1200”。
前往“供应商特定寄存器”代码部分,将相关寄存器修改为 ADIN1200 的对应寄存器。由于我不准备使用任何高级功能,因此我这里只更新了“PHY Status 1 寄存器”。
/***************Vendor Specific Register***************/
/**
* @brief PHY Status 1 Register
*
*/
typedef union {
struct {
uint32_t lp_apause_adv : 1; /* The link partner has advertised asymmetric pause */
uint32_t lp_pause_adv : 1; /* The link partner has advertised pause */
uint32_t autoneg_sup : 1; /* Local and remote PHYs support autonegotiation */
uint32_t col_stat : 1; /* Indicates that collision is asserted */
uint32_t rx_dv_stat : 1; /* Indication that receive data valid (RX_DV) is asserted. */
uint32_t tx_en_stat : 1; /* Indication that transmit enable (TX_EN) is asserted */
uint32_t link_stat : 1; /* Link status */
uint32_t hcd_tech : 3; /* Indication of the resolved technology after the link is established */
uint32_t b_10_pol_inv : 1; /* polarity of the 10BASE-T signal inversion */
uint32_t pair_01_swap : 1; /* Pair 0 and Pair 1 swap */
uint32_t autoneg_stat : 1; /* Autonegotiation Status Bit */
uint32_t par_det_flt_stat: 1; /* Parallel Detection Fault Status Bit */
uint32_t reserverd : 1; /* Reserved */
uint32_t phy_in_stndby : 1; /* PHY is in standby state and does not attempt to bring up links */
};
uint32_t val;
} ps1r_reg_t;
#define ETH_PHY_PS1R_REG_ADDR (0x1A)
根据技术规格书中定义的“PHY Identifier ½ 寄存器”,更新 adin1200_init() 函数中的 oui 和 model。
/* Check PHY ID */
uint32_t oui;
uint8_t model;
ESP_GOTO_ON_ERROR(esp_eth_phy_802_3_read_oui(phy_802_3, &oui), err, TAG, "read OUI failed");
ESP_GOTO_ON_ERROR(esp_eth_phy_802_3_read_manufac_info(phy_802_3, &model, NULL), err, TAG, "read manufacturer's info failed");
ESP_GOTO_ON_FALSE(oui == 0xa0ef && model == 0x02, ESP_FAIL, err, TAG, "wrong chip ID (read oui=0x%" PRIx32 ", model=0x%" PRIx8 ")", oui, model);
5. 修改 update_link_duplex_speed() 函数以读取实际协商结果。该信息在 IEEE 802.3 中并未进行标准化,因此所有 PHY 芯片都不同。ADIN1200 使用 “PHY Status 1 寄存器”存储该数据。
...
ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, addr, ETH_PHY_ANLPAR_REG_ADDR, &(anlpar.val)), err, TAG, "read ANLPAR failed");
ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, addr, ETH_PHY_BMSR_REG_ADDR, &(bmsr.val)), err, TAG, "read BMSR failed");
eth_link_t link = bmsr.link_status ? ETH_LINK_UP : ETH_LINK_DOWN;
/* check if link status changed */
if (adin1200->phy_802_3.link_status != link) {
/* when link up, read negotiation result */
if (link == ETH_LINK_UP) {
ps1r_reg_t ps1r;
ESP_GOTO_ON_ERROR(eth->phy_reg_read(eth, addr, ETH_PHY_PS1R_REG_ADDR, &(ps1r.val)), err, TAG, "read PS1R failed");
switch (ps1r.hcd_tech) {
case 0: //10Base-T half-duplex
speed = ETH_SPEED_10M;
duplex = ETH_DUPLEX_HALF;
break;
case 1: //10Base-T full-duplex
speed = ETH_SPEED_10M;
duplex = ETH_DUPLEX_FULL;
break;
case 2: //100Base-TX half-duplex
speed = ETH_SPEED_100M;
duplex = ETH_DUPLEX_HALF;
break;
case 3: //100Base-TX full-duplex
speed = ETH_SPEED_100M;
duplex = ETH_DUPLEX_FULL;
break;
default:
break;
}
...
6. 使用esp_eth_phy_new_adin1200() 函数创建一个新的头文件。
…至此,新的 PHY 驱动程序已准备完成,并可以使用了!
现在,我们只需按照之前的方式创建 MAC 和 PHY 对象,并在我们的应用程序中初始化 ESP-IDF 以太网驱动程序。
#include "esp_eth_phy_adin1200.h"
// Init common MAC and PHY configs to default
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
// Update PHY config based on board specific configuration
phy_config.phy_addr = CONFIG_EXAMPLE_ETH_PHY_ADDR;
phy_config.reset_gpio_num = CONFIG_EXAMPLE_ETH_PHY_RST_GPIO;
// Init vendor specific MAC config to default
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
// Update vendor specific MAC config based on board configuration
esp32_emac_config.smi_mdc_gpio_num = CONFIG_EXAMPLE_ETH_MDC_GPIO;
esp32_emac_config.smi_mdio_gpio_num = CONFIG_EXAMPLE_ETH_MDIO_GPIO;
// Create new ESP32 Ethernet MAC instance
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
// Create new PHY instance
esp_eth_phy_t *phy = esp_eth_phy_new_adin1200(&phy_config);
// Init Ethernet driver to default and install it
esp_eth_handle_t eth_handle = NULL;
esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
ESP_GOTO_ON_FALSE(esp_eth_driver_install(&config, ð_handle) == ESP_OK, NULL,
err, TAG, "Ethernet driver install failed");
ADIN1200 以太网 PHY 驱动程序的完整版本可见 ESP-IDF附加以太网驱动程序仓库或通过 IDF 组件管理器。