
1. 硬件准备与电路设计第一次接触STM32F4和LAN8720A以太网通信时我对着开发板上的PHY芯片发呆了半小时——这个比指甲盖还小的芯片真的能跑百兆网络后来实测发现硬件设计才是稳定通信的基础。先说几个容易踩坑的点RMII接口布线是首要关注项。LAN8720A通过RMII精简介质独立接口与STM32F4通信需要严格保证时钟信号质量。我的经验是使用50Ω阻抗匹配的PCB走线REF_CLK信号长度控制在10cm以内避免与高频信号线平行走线在RMII_TXD[1:0]和RMII_RXD[1:0]信号线上串联22Ω电阻具体到STM32F407的引脚分配参考我的实际工程配置/* ETH_RMII模式引脚定义 */ #define ETH_REF_CLK PA1 // 必须外接25MHz晶振 #define ETH_MDIO PA2 #define ETH_MDC PC1 #define ETH_RMII_CRS PA7 #define ETH_RMII_TXD0 PC4 #define ETH_RMII_TXD1 PC5 #define ETH_RMII_TX_EN PB11 #define ETH_RMII_RXD0 PC4 #define ETH_RMII_RXD1 PC5 #define ETH_RMII_RX_ER PB10 #define PHY_RESET_PIN PJ12 // 硬件复位引脚注意不同STM32F4系列芯片的ETH引脚可能不同务必查阅对应型号的参考手册LAN8720A的硬件设计也有讲究电源部分要加0.1μF和10μF去耦电容25MHz晶振要选择±50ppm精度nINT/REFCLKO引脚需上拉4.7k电阻LED指示灯建议保留便于调试时观察链路状态2. CubeMX工程配置详解打开CubeMX时新手常被各种选项搞得晕头转向。这里分享我的标准配置流程2.1 ETH基础配置在Connectivity选项卡启用ETH外设选择RMII接口模式Auto Negotiation设为EnableChecksum Offload保持默认在Advanced Parameters中Rx Mode选DMATx Mode选DMA将PHY Address设为0LAN8720A默认地址2.2 LWIP协议栈配置转到Middleware选项卡配置LWIP在Key Options中MEM_SIZE建议设为16KBPBUF_POOL_SIZE设为16TCP_WND保持默认8760勾选Use DHCP调试阶段建议关闭在Platform Settings中勾选Use FreeRTOS选择CMSIS_V1接口2.3 FreeRTOS适配由于LWIP需要操作系统支持FreeRTOS配置很关键在Tasks and Queues中添加一个TCP任务建议栈空间1024字节设置合理的优先级通常低于默认任务在Config parameters中将TICK_RATE_HZ设为1000启用软件定时器生成代码前务必检查时钟树配置确保HCLK不超过168MHzETH时钟必须来自25MHz外部晶振PLL_Q分频系数设为7生成48MHz USB时钟3. 双模式TCP通信实现实际项目中经常需要设备在TCP服务器和客户端间切换。我的方案是通过宏定义实现模式切换核心代码如下// tcpecho.h #define TCP_MODE 2 // 2服务器模式 3客户端模式 #if TCP_MODE 2 #define LOCAL_IP 192.168.1.100 #define REMOTE_IP 192.168.1.200 #define LOCAL_PORT 8080 #else #define LOCAL_IP 192.168.1.200 #define REMOTE_IP 192.168.1.100 #define REMOTE_PORT 8080 #endif3.1 服务器模式实现服务器需要绑定端口并监听连接void tcp_server_thread(void *arg) { int sock, client; struct sockaddr_in server, client_addr; // 创建socket sock socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 server.sin_family AF_INET; server.sin_port htons(LOCAL_PORT); server.sin_addr.s_addr inet_addr(LOCAL_IP); // 绑定并监听 bind(sock, (struct sockaddr*)server, sizeof(server)); listen(sock, 1); // 接受客户端连接 socklen_t len sizeof(client_addr); client accept(sock, (struct sockaddr*)client_addr, len); // 数据收发处理 while(1) { char buf[1024]; int len recv(client, buf, sizeof(buf), 0); if(len 0) { // 数据处理逻辑 send(client, buf, len, 0); } } }3.2 客户端模式实现客户端需要主动连接服务器void tcp_client_thread(void *arg) { int sock; struct sockaddr_in server; // 创建socket sock socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 server.sin_family AF_INET; server.sin_port htons(REMOTE_PORT); server.sin_addr.s_addr inet_addr(REMOTE_IP); // 连接服务器 connect(sock, (struct sockaddr*)server, sizeof(server)); // 数据收发处理 while(1) { char buf[] Hello Server!; send(sock, buf, strlen(buf), 0); int len recv(sock, buf, sizeof(buf), 0); if(len 0) { // 处理接收数据 } osDelay(1000); } }4. 调试技巧与性能优化调通TCP通信后我发现这些优化手段能显著提升稳定性4.1 网络状态监测在lwipopts.h中添加#define LWIP_STATS 1 #define LWIP_STATS_DISPLAY 1通过下面函数打印网络状态void print_net_stats() { printf(TCP RX: %d packets\n, lwip_stats.tcp.recv); printf(TCP TX: %d packets\n, lwip_stats.tcp.sent); printf(TCP Err: %d\n, lwip_stats.tcp.err); printf(MEM Used: %d/%d\n, lwip_stats.mem.used, lwip_stats.mem.max); }4.2 内存优化配置修改lwipopts.h中的关键参数#define MEM_SIZE (16*1024) #define TCP_WND (4*TCP_MSS) #define TCP_SND_BUF (2*TCP_MSS) #define PBUF_POOL_SIZE 16 #define PBUF_POOL_BUFSIZE 5124.3 常见问题排查Ping不通检查PHY芯片是否正常复位用示波器测量REF_CLK是否有25MHz时钟确认IP地址和子网掩码设置正确TCP连接不稳定增大TCP_WND和TCP_SND_BUF检查FreeRTOS任务栈是否足够启用LWIP调试输出#define LWIP_DEBUG 1数据传输丢包降低发送频率或增大发送缓冲区使用Wireshark抓包分析检查DMA缓冲区对齐情况最后分享一个实用技巧在PHY芯片附近放置一个测试点用跳线连接nINT信号到GPIO可以实时监测链路状态变化。当网络异常时这个信号会最先反应物理层问题。