Guia rápido do driver PHY Ethernet personalizado ESP-IDF

Nov 29 2022
“Gostaria de iniciar um novo design de produto com meu chip favorito, mas está esgotado! Oh não! Terei que projetar um novo layout e desenvolver novos drivers para ele!” Todo designer conhece muito bem esse sentimento hoje em dia... A boa notícia é que você não precisa mais se preocupar com isso, pelo menos em termos de suporte ao driver ESP-IDF Ethernet PHY. Neste artigo, vou demonstrar como é simples criar um novo driver Ethernet PHY.

“Gostaria de iniciar um novo design de produto com meu chip favorito, mas está esgotado! Oh não! Terei que projetar um novo layout e desenvolver novos drivers para ele!” Todo designer conhece muito bem esse sentimento hoje em dia…

A boa notícia é que você não precisa mais se preocupar com isso, pelo menos em termos de suporte ao driver ESP-IDF Ethernet PHY. Neste artigo, vou demonstrar como é simples criar um novo driver Ethernet PHY.

Em primeiro lugar, você precisa encontrar um substituto para o seu chip PHY atual. Tenho um amigo que trabalha para uma empresa de distribuição de semicondutores, então peguei meu telefone e pedi alguns conselhos. Ele recomendou ADIN1200. É um chip industrial robusto com um amplo conjunto de recursos. Por outro lado, é mais caro e também pode ficar mais difícil de ser encontrado em estoque se você não tiver sorte. De qualquer forma, é o candidato ideal para fins de demonstração, pois existe uma placa de avaliação, o chip é de um fornecedor que atualmente não é suportado pelo ESP-IDF (ou seja, limitamos a possibilidade de bloqueio do fornecedor para nossos clientes) e o chip é IEEE 802.3 reclamação. Este último fato é o que mais nos ajudará a reduzir o esforço necessário para a criação de um novo driver, pois uma interface de gerenciamento entre EMAC e PHY é padronizada e o ESP-IDFv5.0 tira proveito disso. O driver Ethernet ESP-IDF consiste basicamente em três camadas:

  • Os próprios objetos Ethernet, que são a API pública e que envolve as camadas MAC e PHY em uma unidade funcional.
  • A camada MAC, que controla o comportamento do Media Access Controller e fornece interface de dados para o aplicativo do driver.
  • A camada PHY, que controla as propriedades da camada física e reúne o status do link.
  • Indicação de status do link que é quase sempre específica do chip.
  • Inicialização do chip. Esta parte não é estritamente necessária, pois é mais comum. É bom ser adicionado para garantir que o chip esperado seja usado.
  • Configuração de recursos específicos do chip (como Wake on LAN, Ethernet com eficiência energética, vários recursos de diagnóstico, etc.).

Em termos de hardware, é mais fácil começar com a placa de avaliação EVAL-ADIN1200. Esta placa de avaliação requer apenas algumas modificações para ser conectada ao ESP32 via RMII. (Você pode, é claro, começar com um novo projeto de PCB se achar mais apropriado.)

Passos para preparar o hardware:

1) Estude os materiais na página do produto ADIN1200 e familiarize-se com o chip e a placa de avaliação.

2) Uma vez familiarizado com a banca de avaliação, são necessárias as seguintes modificações:

  • O ADIN1200 requer um RMII REF_CLK externo de 50 MHz, portanto dessolde o oscilador Y1 e seus capacitores de acoplamento associados.
  • Solda R120 com resistor 0R
  • Solde pull-ups de 10K em RX_CTL(High) e RX_CLK (High) para configurar o ADIN1200 no modo RMII.
  • Para opções de configuração adicionais, consulte “ Tabela 5. Definição de configuração do EVAL-ADIN1200FMCZ” no Guia do usuário do EVAL-ADIN1200FMCZ.

Observe que RMII REF_CLK precisa ser gerado externamente para ADIN1200 por um oscilador externo de 50 MHz ou por ESP32. É mais simples usar o ESP32 para fins de demonstração, então usei essa solução, mas lembre-se de que o módulo ESP32 WROOM é necessário.

╔════════════════════╦═════════════════╦═════════════╗
║ RMII Interface Pin ║  EVAL-ADIN1200  ║ ESP32 WROOM ║
╠════════════════════╬═════════════════╬═════════════╣
║ TX_EN              ║ R80             ║ GPIO21      ║
║ TXD[0]             ║ R90             ║ GPIO19      ║
║ TXD[1]             ║ R88             ║ GPIO22      ║
║ RXD[0]             ║ R99             ║ GPIO25      ║
║ RXD[1]             ║ R86             ║ GPIO26      ║
║ CRS_DV             ║ R81             ║ GPIO27      ║
║ RESET              ║ R213            ║ GPIO5       ║
║ REF_CLK            ║ C92             ║ GPIO17      ║
║ MDIO               ║ MDIO Test Point ║ GPIO18      ║
║ MDC                ║ MDC Test Point  ║ GPIO23      ║
╚════════════════════╩═════════════════╩═════════════╝

EVAL-ADIN1200 com ESP32 WROOM

Do ponto de vista do software, é ainda mais simples.

Etapas para criar o novo driver Ethernet PHY:

1) Crie uma cópia de esp_eth_phy_ip101.c ou qualquer outro arquivo de origem do chip PHY compatível com IEEE 802.3 da pasta ESP-IDF /components/esp_eth/src/ para uma nova pasta.

2) Renomeie todas as ocorrências de “ip101” para “adin1200”.

3) Atualize a seção de código “Registros Específicos do Fornecedor” com os registros ADIN1200 que você planeja usar. Eu apenas atualizei o “Registro PHY Status 1” já que não estou planejando usar nenhum dos recursos avançados ainda.

/***************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)

/* 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);

...    
    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;
            }
...

… e o novo driver PHY está pronto e pronto para ser usado!

Agora, basta criar os objetos MAC e PHY como estamos acostumados e inicializar o driver ESP-IDF Ethernet em nossa aplicação.

#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, &eth_handle) == ESP_OK, NULL,
                    err, TAG, "Ethernet driver install failed");