Introducción
En esta guía se describe el proceso de integración del chip PHY YT8512C con el stack TCP/IP LwIP sobre un microcontrolador STM32, empleando STM32CubeMX para la generación del código base. Se utiliza la placa de desarrollo Explorer V3.4 de Alientek junto con las herramientas CLion, arm-none-eabi-gcc y OpenOCD. El código del controlador PHY se basa en un repositorio disponible en Gitee.
Creación del proyecto en STM32CubeMX
Activación del reloj y la interfaz de depuración
Configurar adecuado del oscilador HSE y la interfaz SWD/JTAG en la pestaña Pinout & Configuration.
Habilitación del periférico Ethernet (ETH)
Seleccionar el modo RMII para la interfaz Media Independent Interface reducida dentro de la configuración del periférico ETH.
Configuración del pin de reinicio del PHY
Designar un GPIO de salida para controlar la línea de hardware reset del chip YT8512C.
Activación del middleware LwIP
En la sección Middleware → LWIP, habilitar el stack y configurar los parámetros de red requeridos (dirección IP, máscara, puerta de enlace).
Generación del código fuente
Seleccionar como toolchain "Makefile" o "STM32CubeIDE" según el flujo de trabajo deseado y generar el proyecto.
Apertura del proyecto en CLion
Incorporación del controlador YT8512C
Archivo de cabecera
/* Evitar inclusión múltiple */
#ifndef PHY_YT8512C_H
#define PHY_YT8512C_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "stm32f4xx_hal.h"
/* Direcciones de los registros del PHY */
#define PHY_REG_BASIC_CTRL ((uint16_t)0x0000U)
#define PHY_REG_BASIC_STATUS ((uint16_t)0x0001U)
#define PHY_REG_ID_MSB ((uint16_t)0x0002U)
#define PHY_REG_ID_LSB ((uint16_t)0x0003U)
/* Bits del registro de control básico (BCR) */
#define PHY_BCR_SW_RESET ((uint16_t)0x8000U)
#define PHY_BCR_LOOPBACK_EN ((uint16_t)0x4000U)
#define PHY_BCR_SPEED_SEL ((uint16_t)0x2000U)
#define PHY_BCR_ANEG_ENABLE ((uint16_t)0x1000U)
#define PHY_BCR_POWER_DOWN ((uint16_t)0x0800U)
#define PHY_BCR_ISOLATE ((uint16_t)0x0400U)
#define PHY_BCR_RESTART_ANEG ((uint16_t)0x0200U)
#define PHY_BCR_DUPLEX ((uint16_t)0x0100U)
/* Bits del registro de estado básico (BSR) */
#define PHY_BSR_100BASE_T4 ((uint16_t)0x8000U)
#define PHY_BSR_100TX_FULL ((uint16_t)0x4000U)
#define PHY_BSR_100TX_HALF ((uint16_t)0x2000U)
#define PHY_BSR_10T_FULL ((uint16_t)0x1000U)
#define PHY_BSR_10T_HALF ((uint16_t)0x0800U)
#define PHY_BSR_100T2_FULL ((uint16_t)0x0400U)
#define PHY_BSR_100T2_HALF ((uint16_t)0x0200U)
#define PHY_BSR_EXT_STATUS ((uint16_t)0x0100U)
#define PHY_BSR_ANEG_COMPLETE ((uint16_t)0x0020U)
#define PHY_BSR_REMOTE_FAULT ((uint16_t)0x0010U)
#define PHY_BSR_ANEG_ABILITY ((uint16_t)0x0008U)
#define PHY_BSR_LINK_UP ((uint16_t)0x0004U)
#define PHY_BSR_JABBER_DET ((uint16_t)0x0002U)
#define PHY_BSR_EXT_CAPABILITY ((uint16_t)0x0001U)
/* Códigos de resultado de las operaciones */
#define PHY_RESULT_READ_FAIL ((int32_t)-5)
#define PHY_RESULT_WRITE_FAIL ((int32_t)-4)
#define PHY_RESULT_ADDR_INVALID ((int32_t)-3)
#define PHY_RESULT_RESET_TIMEOUT ((int32_t)-2)
#define PHY_RESULT_FAIL ((int32_t)-1)
#define PHY_RESULT_SUCCESS ((int32_t) 0)
#define PHY_RESULT_LINK_DOWN ((int32_t) 1)
#define PHY_RESULT_100M_FULL ((int32_t) 2)
#define PHY_RESULT_100M_HALF ((int32_t) 3)
#define PHY_RESULT_10M_FULL ((int32_t) 4)
#define PHY_RESULT_10M_HALF ((int32_t) 5)
#define PHY_RESULT_ANEG_PENDING ((int32_t) 6)
/* Dirección PHY configurable por el usuario */
#define PHY_DEVICE_ADDR ((uint16_t)0x0000U)
#define PHY_ADDR_MASK ((uint16_t)0x001FU)
/* Registro especial de estado del transceptor */
#define PHY_XCVR_STATUS_REG ((uint16_t)0x11U)
#define PHY_SPEED_BIT ((uint16_t)0x4010U)
#define PHY_DUPLEX_BIT ((uint16_t)0x2000U)
/* Tipos de función para la capa de E/S */
typedef int32_t (*phy_io_init_fn)(void);
typedef int32_t (*phy_io_deinit_fn)(void);
typedef int32_t (*phy_io_read_fn)(uint32_t dev, uint32_t reg, uint32_t *val);
typedef int32_t (*phy_io_write_fn)(uint32_t dev, uint32_t reg, uint32_t val);
typedef int32_t (*phy_io_tick_fn)(void);
/* Estructura que agrupa los callbacks de E/S */
typedef struct {
phy_io_init_fn on_init;
phy_io_deinit_fn on_deinit;
phy_io_write_fn on_write;
phy_io_read_fn on_read;
phy_io_tick_fn on_tick;
} phy_bus_interface_t;
/* Descriptor principal del dispositivo PHY */
typedef struct {
uint32_t address;
uint32_t initialized;
phy_bus_interface_t bus;
void *user_data;
} phy_device_t;
/* Prototipos de la API pública */
int32_t phy_attach_bus(phy_device_t *dev, phy_bus_interface_t *iface);
int32_t phy_startup(phy_device_t *dev);
int32_t phy_shutdown(phy_device_t *dev);
int32_t phy_wake(phy_device_t *dev);
int32_t phy_sleep(phy_device_t *dev);
int32_t phy_begin_autoneg(phy_device_t *dev);
int32_t phy_read_link_status(phy_device_t *dev);
int32_t phy_force_link_mode(phy_device_t *dev, uint32_t mode);
int32_t phy_loopback_on(phy_device_t *dev);
int32_t phy_loopback_off(phy_device_t *dev);
#ifdef __cplusplus
}
#endif
#endif /* PHY_YT8512C_H */
Archivo de implementación
#include "phy_yt8512c.h"
#define SW_RESET_WAIT_MS ((uint32_t)500U)
#define STARTUP_SETTLE_MS ((uint32_t)2000U)
#define MAX_PHY_ADDR ((uint32_t)31U)
/**
* @brief Registra las funciones de bus (E/S) en el descriptor del PHY.
*/
int32_t phy_attach_bus(phy_device_t *dev, phy_bus_interface_t *iface)
{
if (!dev || !iface->on_read || !iface->on_write || !iface->on_tick)
{
return PHY_RESULT_FAIL;
}
dev->bus.on_init = iface->on_init;
dev->bus.on_deinit = iface->on_deinit;
dev->bus.on_read = iface->on_read;
dev->bus.on_write = iface->on_write;
dev->bus.on_tick = iface->on_tick;
return PHY_RESULT_SUCCESS;
}
/**
* @brief Realiza la inicialización completa del PHY YT8512C.
* Detecta la dirección PHY y ejecuta un reset por software.
*/
int32_t phy_startup(phy_device_t *dev)
{
uint32_t stamp, reg_val, probe_addr;
int32_t rc = PHY_RESULT_SUCCESS;
if (dev->initialized)
{
return PHY_RESULT_SUCCESS;
}
if (dev->bus.on_init)
{
dev->bus.on_init();
}
/* Escaneo de direcciones para localizar el PHY */
dev->address = MAX_PHY_ADDR + 1;
for (probe_addr = 0; probe_addr <= MAX_PHY_ADDR; probe_addr++)
{
if (dev->bus.on_read(probe_addr, PHY_XCVR_STATUS_REG, ®_val) < 0)
{
rc = PHY_RESULT_READ_FAIL;
continue;
}
if ((reg_val & PHY_ADDR_MASK) == probe_addr)
{
dev->address = probe_addr;
rc = PHY_RESULT_SUCCESS;
break;
}
}
if (dev->address > MAX_PHY_ADDR)
{
return PHY_RESULT_ADDR_INVALID;
}
/* Iniciar reset por software */
if (dev->bus.on_write(dev->address, PHY_REG_BASIC_CTRL, PHY_BCR_SW_RESET) < 0)
{
return PHY_RESULT_WRITE_FAIL;
}
if (dev->bus.on_read(dev->address, PHY_REG_BASIC_CTRL, ®_val) < 0)
{
return PHY_RESULT_READ_FAIL;
}
stamp = dev->bus.on_tick();
while (reg_val & PHY_BCR_SW_RESET)
{
if ((dev->bus.on_tick() - stamp) > SW_RESET_WAIT_MS)
{
return PHY_RESULT_RESET_TIMEOUT;
}
if (dev->bus.on_read(dev->address, PHY_REG_BASIC_CTRL, ®_val) < 0)
{
return PHY_RESULT_READ_FAIL;
}
}
/* Periodo de estabilización tras el reinicio */
stamp = dev->bus.on_tick();
while ((dev->bus.on_tick() - stamp) < STARTUP_SETTLE_MS)
{
/* Espera activa */
}
dev->initialized = 1;
return PHY_RESULT_SUCCESS;
}
/**
* @brief Libia los recursos asociados al PHY.
*/
int32_t phy_shutdown(phy_device_t *dev)
{
if (!dev->initialized)
{
return PHY_RESULT_SUCCESS;
}
if (dev->bus.on_deinit && dev->bus.on_deinit() < 0)
{
return PHY_RESULT_FAIL;
}
dev->initialized = 0;
return PHY_RESULT_SUCCESS;
}
/**
* @brief Desactiva el modo de bajo consumo (power-down).
*/
int32_t phy_wake(phy_device_t *dev)
{
uint32_t val;
if (dev->bus.on_read(dev->address, PHY_REG_BASIC_CTRL, &val) < 0)
{
return PHY_RESULT_READ_FAIL;
}
val &= ~PHY_BCR_POWER_DOWN;
if (dev->bus.on_write(dev->address, PHY_REG_BASIC_CTRL, val) < 0)
{
return PHY_RESULT_WRITE_FAIL;
}
return PHY_RESULT_SUCCESS;
}
/**
* @brief Activa el modo de bajo consumo.
*/
int32_t phy_sleep(phy_device_t *dev)
{
uint32_t val;
if (dev->bus.on_read(dev->address, PHY_REG_BASIC_CTRL, &val) < 0)
{
return PHY_RESULT_READ_FAIL;
}
val |= PHY_BCR_POWER_DOWN;
if (dev->bus.on_write(dev->address, PHY_REG_BASIC_CTRL, val) < 0)
{
return PHY_RESULT_WRITE_FAIL;
}
return PHY_RESULT_SUCCESS;
}
/**
* @brief Inicia el proceso de negociación automática.
*/
int32_t phy_begin_autoneg(phy_device_t *dev)
{
uint32_t val;
if (dev->bus.on_read(dev->address, PHY_REG_BASIC_CTRL, &val) < 0)
{
return PHY_RESULT_READ_FAIL;
}
val |= PHY_BCR_ANEG_ENABLE;
if (dev->bus.on_write(dev->address, PHY_REG_BASIC_CTRL, val) < 0)
{
return PHY_RESULT_WRITE_FAIL;
}
return PHY_RESULT_SUCCESS;
}
/**
* @brief Consulta el registro de estado del transceptor para determinar
* velocidad y modo dúplex del enlace actual.
*/
int32_t phy_read_link_status(phy_device_t *dev)
{
uint32_t reg;
if (dev->bus.on_read(dev->address, PHY_XCVR_STATUS_REG, ®) < 0)
{
return PHY_RESULT_READ_FAIL;
}
int speed_is_100 = ((reg & PHY_SPEED_BIT) != PHY_SPEED_BIT);
int is_full_duplex = ((reg & PHY_DUPLEX_BIT) != 0);
if (speed_is_100 && is_full_duplex)
{
return PHY_RESULT_100M_FULL;
}
else if (speed_is_100)
{
return PHY_RESULT_100M_HALF;
}
else if (is_full_duplex)
{
return PHY_RESULT_10M_FULL;
}
else
{
return PHY_RESULT_10M_HALF;
}
}
/**
* @brief Fuerza un modo de enlace específico deshabilitando la negociación automática.
*/
int32_t phy_force_link_mode(phy_device_t *dev, uint32_t mode)
{
uint32_t ctrl;
int32_t rc = PHY_RESULT_SUCCESS;
if (dev->bus.on_read(dev->address, PHY_REG_BASIC_CTRL, &ctrl) < 0)
{
return PHY_RESULT_READ_FAIL;
}
/* Limpiar campos de negociación, velocidad y dúplex */
ctrl &= ~(PHY_BCR_ANEG_ENABLE | PHY_BCR_SPEED_SEL | PHY_BCR_DUPLEX);
switch (mode)
{
case PHY_RESULT_100M_FULL:
ctrl |= (PHY_BCR_SPEED_SEL | PHY_BCR_DUPLEX);
break;
case PHY_RESULT_100M_HALF:
ctrl |= PHY_BCR_SPEED_SEL;
break;
case PHY_RESULT_10M_FULL:
ctrl |= PHY_BCR_DUPLEX;
break;
default:
return PHY_RESULT_FAIL;
}
if (dev->bus.on_write(dev->address, PHY_REG_BASIC_CTRL, ctrl) < 0)
{
rc = PHY_RESULT_WRITE_FAIL;
}
return rc;
}
/**
* @brief Habilita el modo loopback para pruebas.
*/
int32_t phy_loopback_on(phy_device_t *dev)
{
uint32_t val;
if (dev->bus.on_read(dev->address, PHY_REG_BASIC_CTRL, &val) < 0)
{
return PHY_RESULT_READ_FAIL;
}
val |= PHY_BCR_LOOPBACK_EN;
if (dev->bus.on_write(dev->address, PHY_REG_BASIC_CTRL, val) < 0)
{
return PHY_RESULT_WRITE_FAIL;
}
return PHY_RESULT_SUCCESS;
}
/**
* @brief Deshabilita el modo loopback.
*/
int32_t phy_loopback_off(phy_device_t *dev)
{
uint32_t val;
if (dev->bus.on_read(dev->address, PHY_REG_BASIC_CTRL, &val) < 0)
{
return PHY_RESULT_READ_FAIL;
}
val &= ~PHY_BCR_LOOPBACK_EN;
if (dev->bus.on_write(dev->address, PHY_REG_BASIC_CTRL, val) < 0)
{
return PHY_RESULT_WRITE_FAIL;
}
return PHY_RESULT_SUCCESS;
}
Archivo de interfaz Ethernet (ethernetif.c)
Es necesario crear o adaptar el archivo ethernetif.c que conecta el HAL de Ethernet con el stack LwIP. A continuación se muestra una versión modificada que integra el controlador YT8512C:
#include "main.h"
#include "lwip/opt.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/timeouts.h"
#include "netif/ethernet.h"
#include "netif/etharp.h"
#include "lwip/ethip6.h"
#include "ethernetif.h"
#include "phy_yt8512c.h"
#include <string.h>
#define NET_NAME_0 's'
#define NET_NAME_1 't'
#define TX_DMA_TIMEOUT (20U)
typedef struct {
struct pbuf_custom cp;
uint8_t payload_buffer[(ETH_RX_BUF_SIZE + 31) & ~31] __ALIGNED(32);
} rx_pool_entry_t;
#define RX_POOL_ENTRIES 12U
LWIP_MEMPOOL_DECLARE(RX_MEM_POOL, RX_POOL_ENTRIES, sizeof(rx_pool_entry_t), "Zero-copy RX pool");
static ETH_DMADescTypeDef rx_desc_table[ETH_RX_DESC_CNT];
static ETH_DMADescTypeDef tx_desc_table[ETH_TX_DESC_CNT];
static uint8_t rx_alloc_failed;
ETH_HandleTypeDef heth_handle;
ETH_TxPacketConfig tx_packet_cfg;
/* Instancia del PHY y su interfaz de bus */
static phy_device_t phy_dev;
static phy_bus_interface_t phy_io = {
.on_init = ETH_PHY_IO_Init,
.on_deinit = ETH_PHY_IO_DeInit,
.on_write = ETH_PHY_IO_WriteReg,
.on_read = ETH_PHY_IO_ReadReg,
.on_tick = ETH_PHY_IO_GetTick
};
/* Prototipos de funciones de E/S del PHY */
int32_t ETH_PHY_IO_Init(void);
int32_t ETH_PHY_IO_DeInit(void);
int32_t ETH_PHY_IO_ReadReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t *pRegVal);
int32_t ETH_PHY_IO_WriteReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t RegVal);
int32_t ETH_PHY_IO_GetTick(void);
static void ethernet_low_init(struct netif *netif)
{
HAL_StatusTypeDef hal_sts;
uint8_t mac_bytes[6] = {0x00, 0x80, 0xE1, 0x00, 0x00, 0x00};
heth_handle.Instance = ETH;
heth_handle.Init.MACAddr = &mac_bytes[0];
heth_handle.Init.MediaInterface = HAL_ETH_RMII_MODE;
heth_handle.Init.TxDesc = tx_desc_table;
heth_handle.Init.RxDesc = rx_desc_table;
heth_handle.Init.RxBuffLen = 1536;
/* Pulsar la línea de reset del PHY */
HAL_GPIO_WritePin(ETH_RESET_GPIO_Port, ETH_RESET_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(ETH_RESET_GPIO_Port, ETH_RESET_Pin, GPIO_PIN_SET);
HAL_Delay(100);
hal_sts = HAL_ETH_Init(&heth_handle);
memset(&tx_packet_cfg, 0, sizeof(tx_packet_cfg));
tx_packet_cfg.Attributes = ETH_TX_PACKETS_FEATURES_CSUM | ETH_TX_PACKETS_FEATURES_CRCPAD;
tx_packet_cfg.ChecksumCtrl = ETH_CHECKSUM_IPHDR_PAYLOAD_INSERT_PHDR_CALC;
tx_packet_cfg.CRCPadCtrl = ETH_CRC_PAD_INSERT;
LWIP_MEMPOOL_INIT(RX_MEM_POOL);
netif->hwaddr_len = ETH_HWADDR_LEN;
memcpy(netif->hwaddr, mac_bytes, 6);
netif->mtu = ETH_MAX_PAYLOAD;
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
/* Vincular el PHY con su capa de bus e inicializar */
phy_attach_bus(&phy_dev, &phy_io);
if (phy_startup(&phy_dev) != PHY_RESULT_SUCCESS)
{
netif_set_link_down(netif);
netif_set_down(netif);
return;
}
phy_begin_autoneg(&phy_dev);
if (hal_sts == HAL_OK)
{
ethernet_link_check_state(netif);
}
else
{
Error_Handler();
}
}
static err_t ethernet_low_output(struct netif *netif, struct pbuf *p)
{
ETH_BufferTypeDef buf_chain[ETH_TX_DESC_CNT];
struct pbuf *q;
uint32_t idx = 0;
memset(buf_chain, 0, sizeof(buf_chain));
for (q = p; q != NULL; q = q->next)
{
if (idx >= ETH_TX_DESC_CNT)
{
return ERR_IF;
}
buf_chain[idx].buffer = q->payload;
buf_chain[idx].len = q->len;
if (idx > 0)
{
buf_chain[idx - 1].next = &buf_chain[idx];
}
if (q->next == NULL)
{
buf_chain[idx].next = NULL;
}
idx++;
}
tx_packet_cfg.Length = p->tot_len;
tx_packet_cfg.TxBuffer = buf_chain;
tx_packet_cfg.pData = p;
HAL_ETH_Transmit(&heth_handle, &tx_packet_cfg, TX_DMA_TIMEOUT);
return ERR_OK;
}
static struct pbuf *ethernet_low_input(struct netif *netif)
{
struct pbuf *result = NULL;
if (rx_alloc_failed == 0)
{
HAL_ETH_ReadData(&heth_handle, (void **)&result);
}
return result;
}
void ethernetif_input(struct netif *netif)
{
struct pbuf *pkt;
do {
pkt = ethernet_low_input(netif);
if (pkt != NULL)
{
if (netif->input(pkt, netif) != ERR_OK)
{
pbuf_free(pkt);
}
}
} while (pkt != NULL);
}
err_t ethernetif_init(struct netif *netif)
{
LWIP_ASSERT("netif != NULL", (netif != NULL));
netif->name[0] = NET_NAME_0;
netif->name[1] = NET_NAME_1;
#if LWIP_IPV4
netif->output = etharp_output;
#endif
#if LWIP_IPV6
netif->output_ip6 = ethip6_output;
#endif
netif->linkoutput = ethernet_low_output;
ethernet_low_init(netif);
return ERR_OK;
}
void pbuf_free_custom(struct pbuf *p)
{
struct pbuf_custom *cp = (struct pbuf_custom *)p;
LWIP_MEMPOOL_FREE(RX_MEM_POOL, cp);
if (rx_alloc_failed)
{
rx_alloc_failed = 0;
}
}
u32_t sys_now(void)
{
return HAL_GetTick();
}
void HAL_ETH_MspInit(ETH_HandleTypeDef *handle)
{
GPIO_InitTypeDef gpio_cfg = {0};
if (handle->Instance == ETH)
{
__HAL_RCC_ETH_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
gpio_cfg.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5;
gpio_cfg.Mode = GPIO_MODE_AF_PP;
gpio_cfg.Pull = GPIO_NOPULL;
gpio_cfg.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
gpio_cfg.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOC, &gpio_cfg);
gpio_cfg.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7;
HAL_GPIO_Init(GPIOA, &gpio_cfg);
gpio_cfg.Pin = GPIO_PIN_11 | GPIO_PIN_13 | GPIO_PIN_14;
HAL_GPIO_Init(GPIOG, &gpio_cfg);
}
}
void HAL_ETH_MspDeInit(ETH_HandleTypeDef *handle)
{
if (handle->Instance == ETH)
{
__HAL_RCC_ETH_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5);
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7);
HAL_GPIO_DeInit(GPIOG, GPIO_PIN_11 | GPIO_PIN_13 | GPIO_PIN_14);
}
}
int32_t ETH_PHY_IO_Init(void)
{
HAL_ETH_SetMDIOClockRange(&heth_handle);
return 0;
}
int32_t ETH_PHY_IO_DeInit(void)
{
return 0;
}
int32_t ETH_PHY_IO_ReadReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t *pRegVal)
{
return (HAL_ETH_ReadPHYRegister(&heth_handle, DevAddr, RegAddr, pRegVal) == HAL_OK) ? 0 : -1;
}
int32_t ETH_PHY_IO_WriteReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t RegVal)
{
return (HAL_ETH_WritePHYRegister(&heth_handle, DevAddr, RegAddr, RegVal) == HAL_OK) ? 0 : -1;
}
int32_t ETH_PHY_IO_GetTick(void)
{
return (int32_t)HAL_GetTick();
}
void ethernet_link_check_state(struct netif *netif)
{
ETH_MACConfigTypeDef mac_cfg = {0};
int32_t link_state;
uint32_t speed_val, duplex_val;
link_state = phy_read_link_status(&phy_dev);
if (netif_is_link_up(netif) && link_state == PHY_RESULT_LINK_DOWN)
{
HAL_ETH_Stop(&heth_handle);
netif_set_down(netif);
netif_set_link_down(netif);
return;
}
if (!netif_is_link_up(netif) && link_state > PHY_RESULT_LINK_DOWN)
{
switch (link_state)
{
case PHY_RESULT_100M_FULL:
duplex_val = ETH_FULLDUPLEX_MODE;
speed_val = ETH_SPEED_100M;
break;
case PHY_RESULT_100M_HALF:
duplex_val = ETH_HALFDUPLEX_MODE;
speed_val = ETH_SPEED_100M;
break;
case PHY_RESULT_10M_FULL:
duplex_val = ETH_FULLDUPLEX_MODE;
speed_val = ETH_SPEED_10M;
break;
case PHY_RESULT_10M_HALF:
duplex_val = ETH_HALFDUPLEX_MODE;
speed_val = ETH_SPEED_10M;
break;
default:
return;
}
HAL_ETH_GetMACConfig(&heth_handle, &mac_cfg);
mac_cfg.DuplexMode = duplex_val;
mac_cfg.Speed = speed_val;
HAL_ETH_SetMACConfig(&heth_handle, &mac_cfg);
HAL_ETH_Start(&heth_handle);
netif_set_up(netif);
netif_set_link_up(netif);
}
}
void HAL_ETH_RxAllocateCallback(uint8_t **buff)
{
struct pbuf_custom *p = LWIP_MEMPOOL_ALLOC(RX_MEM_POOL);
if (p)
{
*buff = (uint8_t *)p + offsetof(rx_pool_entry_t, payload_buffer);
p->custom_free_function = pbuf_free_custom;
pbuf_alloced_custom(PBUF_RAW, 0, PBUF_REF, p, *buff, ETH_RX_BUF_SIZE);
}
else
{
rx_alloc_failed = 1;
*buff = NULL;
}
}
void HAL_ETH_RxLinkCallback(void **pStart, void **pEnd, uint8_t *buff, uint16_t Length)
{
struct pbuf **head = (struct pbuf **)pStart;
struct pbuf **tail = (struct pbuf **)pEnd;
struct pbuf *node = (struct pbuf *)(buff - offsetof(rx_pool_entry_t, payload_buffer));
node->next = NULL;
node->tot_len = 0;
node->len = Length;
if (*head == NULL)
{
*head = node;
}
else
{
(*tail)->next = node;
}
*tail = node;
for (struct pbuf *iter = *head; iter != NULL; iter = iter->next)
{
iter->tot_len += Length;
}
}
void HAL_ETH_TxFreeCallback(uint32_t *buff)
{
pbuf_free((struct pbuf *)buff);
}
Inclusión de los archivos en el proyecto
Agregar tanto phy_yt8512c.h como phy_yt8512c.c al directorio de fuentes del proyecto. Si se trabaja con Makefile, asegurarse de incluir la ruta del directorio en C_INCLUDES y el archivo .c en la lista C_SOURCES.
Lógica de procesamiento LwIP en main.c
En la función main, tras la inicialización de periféricos y del stack LwIP mediante MX_LWIP_Init(), se debe invocar periódicamente la función MX_LWIP_Process() (o ethernetif_input() y los temporizadores de LwIP) dentro del bucle principal para procesar tramas entrantes y eventos de red.
Compilación, descarga y verificación
Tras compilar el proyecto y transferir el firmware al microcontrolador mediante OpenOCD, se puede validar el funcionamiento ejecutando comandos de red básicos desde una termianl. Si la configuración es correcta, el PHY establece el enlace y el dispositivo responde a pings o peticiones de la aplicación implementada.