STM32F4标准库实战:手把手教你用LAN8720A和LWIP实现TCP客户端(无操作系统)

张开发
2026/4/21 21:29:29 15 分钟阅读

分享文章

STM32F4标准库实战:手把手教你用LAN8720A和LWIP实现TCP客户端(无操作系统)
STM32F4标准库实战从零构建裸机TCP客户端LAN8720ALWIP1. 硬件准备与基础概念在开始之前我们需要明确几个关键点。STM32F4系列微控制器内置了以太网MAC控制器但需要外接PHY芯片才能实现完整的以太网功能。LAN8720A是一款常见的10/100Mbps以太网PHY芯片体积小且性价比高非常适合嵌入式应用。所需硬件清单STM32F407/STM32F429开发板需带RMII接口LAN8720A模块或集成该芯片的扩展板网线和水晶头示波器非必需但调试时很有用RMII接口关键信号线信号线方向描述REF_CLKPHY→MAC50MHz参考时钟CRS_DVPHY→MAC载波侦听/数据有效RXD[1:0]PHY→MAC接收数据线TX_ENMAC→PHY发送使能TXD[1:0]MAC→PHY发送数据线MDIO双向管理数据输入输出MDCMAC→PHY管理数据时钟注意LAN8720A的nINT/REFCLKO引脚需要正确配置它既可作为中断输出也可提供时钟输出2. 工程搭建与驱动移植首先创建一个基于STM32标准库的空工程然后需要获取以下几个关键文件LWIP源码建议使用1.4.1或2.1.2版本STM32F4x7以太网驱动从ST官网下载PHY芯片驱动需适配LAN8720A文件结构规划Project/ ├── Drivers/ │ ├── CMSIS/ │ ├── STM32F4xx_StdPeriph_Driver/ │ └── LAN8720A/ # 自定义PHY驱动 ├── Middlewares/ │ └── LWIP/ │ ├── src/ # LWIP核心源码 │ └── port/ # 移植层 └── Src/ ├── ethernetif.c # 网络接口适配层 └── lwip_init.c # LWIP初始化配置关键移植步骤修改stm32f4x7_eth_conf.h中的PHY地址LAN8720A通常为0或1在ethernetif.c中实现底层收发函数// 发送函数示例 static err_t low_level_output(struct netif *netif, struct pbuf *p) { struct pbuf *q; uint8_t *buffer (uint8_t *)ETH_TX_BUF; // 将pbuf数据拷贝到发送缓冲区 for(q p; q ! NULL; q q-next) { memcpy(buffer, q-payload, q-len); buffer q-len; } // 启动DMA发送 ETH_DMATxDescChain-Buffer1Addr (uint32_t)ETH_TX_BUF; ETH_DMATxDescChain-ControlBufferSize p-tot_len | ETH_DMATxDesc_TIC; ETH_DMATxDescChain-Status ETH_DMATxDesc_OWN; return ERR_OK; }配置时钟树确保RMII接口的50MHz时钟正确3. 关键配置详解3.1 LWIP选项配置lwipopts.h这是整个项目的核心配置文件需要根据裸机环境进行优化/* 内存配置 */ #define MEM_SIZE (16*1024) // 根据实际需求调整 #define PBUF_POOL_SIZE 16 // 接收缓冲区数量 #define PBUF_POOL_BUFSIZE 1524 // 每个缓冲区大小 /* 协议支持 */ #define LWIP_TCP 1 // 启用TCP #define LWIP_DHCP 0 // 本例使用静态IP #define TCP_MSS 1460 // 最大分段大小 /* 超时设置 */ #define TCP_TMR_INTERVAL 250 // TCP定时器间隔(ms) #define TCP_FAST_INTERVAL TCP_TMR_INTERVAL/43.2 网络接口初始化在netconf.c中完成LWIP的初始化和网络接口注册void LwIP_Init(void) { struct ip_addr ipaddr, netmask, gw; // 静态IP配置 IP4_ADDR(ipaddr, 192, 168, 1, 100); IP4_ADDR(netmask, 255, 255, 255, 0); IP4_ADDR(gw, 192, 168, 1, 1); // 初始化LWIP lwip_init(); // 添加网络接口 netif_add(gnetif, ipaddr, netmask, gw, NULL, ðernetif_init, ðernet_input); netif_set_default(gnetif); netif_set_up(gnetif); }4. TCP客户端实现4.1 连接管理创建一个tcp_client.c文件实现TCP客户端逻辑static struct tcp_pcb *tcp_client_pcb; void tcp_client_connect(const ip_addr_t *ipaddr, u16_t port) { err_t err; // 创建TCP控制块 tcp_client_pcb tcp_new(); if(!tcp_client_pcb) return; // 设置接收回调 tcp_recv(tcp_client_pcb, tcp_client_recv); tcp_err(tcp_client_pcb, tcp_client_error); // 发起连接 err tcp_connect(tcp_client_pcb, ipaddr, port, tcp_client_connected); if(err ! ERR_OK) { printf(Connect failed: %d\n, err); tcp_client_close(); } } static err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err) { if(err ! ERR_OK) { printf(Connection error: %d\n, err); return err; } printf(Connected to server\n); return tcp_client_send_data(Hello from STM32!\n); }4.2 数据收发处理实现数据的接收和发送逻辑static err_t tcp_client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { if(!p) { // 连接关闭 tcp_client_close(); return ERR_OK; } // 处理接收到的数据 if(p-tot_len 0) { printf(Received %d bytes: %.*s\n, p-tot_len, p-tot_len, (char*)p-payload); // 回显数据可选 tcp_write(tpcb, p-payload, p-tot_len, TCP_WRITE_FLAG_COPY); } pbuf_free(p); return ERR_OK; } err_t tcp_client_send_data(const char *data) { if(!tcp_client_pcb || !data) return ERR_ARG; u16_t len strlen(data); err_t err tcp_write(tcp_client_pcb, data, len, TCP_WRITE_FLAG_COPY); if(err ERR_OK) { tcp_output(tcp_client_pcb); printf(Sent %d bytes\n, len); } else { printf(Send failed: %d\n, err); } return err; }5. 调试技巧与常见问题5.1 硬件调试要点时钟检查使用示波器测量REF_CLK引脚应有50MHz方波检查HSE时钟是否正确配置通常需要25MHz晶振PHY初始化通过SMI接口读取PHY ID寄存器LAN8720A应为0x0007C0F1检查自动协商是否完成读取BASIC_STATUS寄存器网络连接状态uint32_t phyreg; ETH_ReadPHYRegister(PHY_ADDRESS, PHY_BSR, phyreg); if(phyreg PHY_Linked_Status) { printf(Link up\n); }5.2 常见问题解决问题1Ping不通检查硬件连接特别是CRS_DV和RXD信号线确认IP地址、子网掩码和网关配置正确检查防火墙设置是否阻止了ICMP包问题2TCP连接不稳定调整LWIP内存配置增加PBUF_POOL_SIZE检查TCP_WND和TCP_MSS参数是否合理确保主循环中定期调用sys_check_timeouts()问题3数据包丢失优化DMA缓冲区描述符配置增加接收缓冲区数量检查PHY的LED指示灯状态判断链路质量6. 进阶优化6.1 零拷贝发送优化修改发送逻辑减少内存拷贝err_t tcp_client_send_pbuf(struct pbuf *p) { if(!tcp_client_pcb || !p) return ERR_ARG; err_t err tcp_write(tcp_client_pcb, p-payload, p-len, 0); if(err ERR_OK) { tcp_output(tcp_client_pcb); pbuf_free(p); // 发送完成后释放pbuf } return err; }6.2 自定义协议头定义简单的应用层协议帧结构#pragma pack(1) typedef struct { uint8_t cmd; // 命令字 uint16_t len; // 数据长度 uint32_t seq; // 序列号 uint8_t data[]; // 可变长数据 } app_protocol_t; #pragma pack() // 封包函数示例 struct pbuf* build_app_packet(uint8_t cmd, const void *data, uint16_t len) { struct pbuf *p pbuf_alloc(PBUF_TRANSPORT, sizeof(app_protocol_t)len, PBUF_RAM); if(p) { app_protocol_t *hdr (app_protocol_t*)p-payload; hdr-cmd cmd; hdr-len len; hdr-seq get_next_seq(); memcpy(hdr-data, data, len); } return p; }6.3 性能监控添加统计信息监控网络性能void print_net_stats(void) { printf( Network Stats \n); printf(Mem in use: %d/%d\n, mem_get_used(), MEM_SIZE); printf(PBUF avail: %d/%d\n, pbuf_pool_avail(), PBUF_POOL_SIZE); printf(TCP active: %d\n, TCP_PCB_NUM()); printf(TCP recv window: %d\n, TCP_WND); }

更多文章