1. 项目背景与硬件准备最近在做一个工业控制项目选用了AT32F437这款国产MCU搭配LAN8720以太网PHY芯片。说实话第一次用这个组合时心里有点打鼓毕竟网上关于AT32的实战资料比STM32少得多。不过实测下来AT32F437的性能确实不错主频高达288MHz内置MAC控制器价格还比同性能的STM32便宜30%左右。LAN8720算是老面孔了10/100M自协商、RMII接口很多开发板都在用。硬件连接上要注意几个关键点时钟配置LAN8720需要外部25MHz晶振输出的50MHz时钟要接到MCU的RMII_REF_CLK引脚复位电路PHY芯片的复位引脚建议通过MCU控制方便软件复位地址配置LAN8720的PHY地址由RXER/CRS引脚决定我们板子上接地所以地址是0x002. 驱动移植踩坑实录2.1 寄存器定义缺失问题刚移植RT-Thread的LAN8720驱动时就遇到了当头一棒error: PHY_CONTROL_REG undeclared这个报错意味着工程里缺少LAN8720的寄存器定义。翻遍RT-Thread的drv_eth.c文件发现它默认只定义了DP83848的寄存器。解决方法其实很简单——手动添加寄存器定义// 添加到drv_emac.h文件头部 #define PHY_CONTROL_REG (0x00) // 基本控制寄存器 #define PHY_STATUS_REG (0x01) // 状态寄存器 #define PHY_AUTO_NEGOTIATION (0x1000)// 自协商使能位 #define PHY_RESET_BIT (0x8000)// 软件复位位这里有个细节要注意从网页复制代码时中文引号可能会变成乱码需要手动替换为英文符号。我就因为这个折腾了半小时编译一直报非法字符。2.2 硬件初始化配置在board.c中找到at32_msp_emac_init函数这是硬件初始化的关键。需要根据原理图修改以下内容void at32_msp_emac_init(void *instance) { // 1. 使能GPIO时钟 gpio_clock_enable(ETH_RMII_GPIO_CLK); // 2. 配置RMII接口引脚 gpio_init_type gpio_init_struct; gpio_default_para_init(gpio_init_struct); gpio_init_struct.gpio_drive_strength GPIO_DRIVE_STRENGTH_STRONGER; gpio_init_struct.gpio_out_type GPIO_OUTPUT_PUSH_PULL; gpio_init_struct.gpio_mode GPIO_MODE_MUX; gpio_init_struct.gpio_pull GPIO_PULL_NONE; // TXD0/TXD1 gpio_init_struct.gpio_pins ETH_TXD0_PIN | ETH_TXD1_PIN; gpio_init(ETH_TXD_GPIO_PORT, gpio_init_struct); // 其他引脚配置... }实测发现AT32的GPIO驱动强度需要特别设置否则网络通信不稳定。建议所有RMII接口引脚都配置为GPIO_DRIVE_STRENGTH_STRONGER。3. LwIP协议栈调优3.1 内存池配置在rtconfig.h中调整以下参数#define RT_LWIP_PBUF_NUM 16 // 原始值8容易丢包 #define RT_LWIP_TCP_PCB_NUM 5 // TCP连接数 #define RT_LWIP_TCP_SND_BUF 8192 // 发送缓冲区第一次压力测试时发现连续ping大包会丢包把PBUF_NUM从8增加到16后问题解决。这里有个技巧可以通过ifconfig命令查看当前内存使用情况msh /ifconfig network interface: e0 (Default) MTU: 1500 MAC: 00 04 a3 12 34 56 FLAGS: UP LINK_UP ETHARP BROADCAST ip address: 192.168.1.100 gw address: 192.168.1.1 net mask : 255.255.255.0 dns server #0: 8.8.8.8 dns server #1: 114.114.114.1143.2 中断优化AT32的ETH中断默认优先级可能太高会导致其他任务饿死。建议在初始化时调整nvic_priority_group_config(NVIC_PRIORITY_GROUP_4); nvic_irq_enable(ETH_IRQn, 10, 0); // 优先级设为10曾经遇到过电机控制线程跑着跑着就卡死最后发现是网络中断抢占导致的。把优先级从0调到10后系统稳定运行。4. 稳定性测试技巧4.1 热插拔检测LAN8720有个特别实用的功能——热插拔中断。在初始化时开启这个功能// 在phy_reset()函数后添加 uint16_t reg phy_read(0, 0x1F); phy_write(0, 0x1F, reg | 0x0100); // 开启中断然后在中断服务函数里处理链路状态变化void ETH_IRQHandler(void) { if(eth_flag_get(ETH_LINK_FLAG)) { uint16_t status phy_read(0, PHY_STATUS_REG); if(status PHY_LINKED_STATUS_BIT) { printf(网线已插入\n); // 重新协商速率... } } }4.2 长时间ping测试建议用不同包长做组合测试# 小包测试 ping -l 64 -t 192.168.1.100 # 大包测试 ping -l 1472 -t 192.168.1.100 # 随机包长测试 for i in {1..100}; do ping -l $((RANDOM%140064)) -n 1 192.168.1.100; done遇到过最诡异的问题是连续ping 800字节左右的包会导致PHY死机。后来发现是硬件设计问题在PHY的VDD脚并了个100nF10uF的电容后解决。5. 性能优化实战5.1 Zero-Copy驱动RT-Thread默认的emac驱动有内存拷贝开销我们可以改写收发函数// 在drv_eth.c中修改 struct pbuf *low_level_input(struct netif *netif) { // 直接使用DMA描述符中的缓冲区 return pbuf_alloced_custom(PBUF_RAW, len, PBUF_REF, ...); } err_t low_level_output(struct netif *netif, struct pbuf *p) { // 直接发送pbuf指针 eth_dma_transmit(p-payload, p-len); }实测这个改动让网络吞吐量提升了40%CPU占用率下降15%。不过要注意pbuf必须来自POOL类型否则会内存泄漏。5.2 硬件校验和AT32F437支持硬件CRC校验在eth_init时开启eth_config.checksum_offload ETH_CHECKSUM_OFFLOAD_ENABLE;这个功能对TCP性能提升特别明显iperf测试时吞吐量直接从60Mbps提升到92Mbps。有个坑要注意开启后需要修改LwIP配置#define CHECKSUM_GEN_TCP 0 // 禁用软件校验 #define CHECKSUM_GEN_UDP 0 #define CHECKSUM_GEN_IP 06. 常见问题排查指南6.1 Ping不通排查步骤查物理层用示波器看RMII_REF_CLK是否有50MHz时钟测量PHY的1.2V、3.3V电源是否稳定查链路层uint16_t phy_status phy_read(0, PHY_STATUS_REG); printf(Link Status: %s\n, (phy_status0x0004)?Up:Down);查网络层用Wireshark抓包看是否有ARP请求检查防火墙是否拦截ICMP6.2 断连问题定位遇到随机断连时可以添加调试代码// 在ethernetif.c的ethernetif_input中添加 static uint32_t last_drop; if(dma_rx_desc-status ETH_DMA_RX_ERR) { printf(RX Error: 0x%08X\n, dma_rx_desc-status); if(rt_tick_get()-last_drop 100) { phy_reset(); // 连续错误时复位PHY } last_drop rt_tick_get(); }曾经通过这个日志发现是电磁干扰导致CRC错误给网口加磁环后解决。7. 进阶技巧7.1 VLAN配置如果需要支持VLAN修改PHY初始化// 设置VLAN标签 phy_write(0, 0x1F, 0x8100); // 进入扩展页 phy_write(0, 0x10, 0x2001); // 开启VLAN识别 phy_write(0, 0x1F, 0x0000); // 返回标准页对应的LwIP配置#define LWIP_VLAN 1 #define ETH_VLAN_ID 1007.2 低功耗优化对于电池供电设备可以动态调整PHY模式void eth_sleep(int enable) { uint16_t reg phy_read(0, PHY_CONTROL_REG); if(enable) { phy_write(0, PHY_CONTROL_REG, reg | 0x0800); // 进入低功耗 } else { phy_write(0, PHY_CONTROL_REG, reg ~0x0800); rt_thread_mdelay(100); // 等待唤醒 } }实测休眠时PHY功耗从120mA降到3mA唤醒时间约200ms。注意唤醒后需要重新协商链路。