Guia rápido do driver PHY Ethernet personalizado ESP-IDF
“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 ║
╚════════════════════╩═════════════════╩═════════════╝

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