欢迎光临
我们一直在努力

以太网通信实战:STM32F407+LAN8720A+LwIP,TCP/IP协议栈应用

文章目录

    • 一、开发环境与硬件准备
      • 1.1 开发环境搭建
      • 1.2 硬件资源说明
      • 1.3 硬件接线表(RMII接口)
    • 二、开发流程总览
    • 三、STM32CubeMX配置步骤
      • 3.1 新建工程
      • 3.2 时钟配置
      • 3.3 引脚配置
      • 3.4 外设配置
      • 3.5 LwIP协议栈配置
      • 3.6 生成代码
    • 四、底层驱动编写
      • 4.1 LAN8720A复位驱动(文件名:lan8720a_reset.c)
      • 4.2 LAN8720A复位驱动头文件(文件名:lan8720a_reset.h)
      • 4.3 ETH底层驱动扩展(文件名:eth_driver_ext.c)
      • 4.4 ETH底层驱动扩展头文件(文件名:eth_driver_ext.h)
    • 五、LwIP协议栈移植与配置
      • 5.1 LwIP核心配置(文件名:lwipopts.h)
      • 5.2 LwIP初始化(文件名:lwip_init.c)
      • 5.3 LwIP初始化头文件(文件名:lwip_init.h)
    • 六、TCP应用层开发
      • 6.1 TCP服务器端(文件名:tcp_server.c)
      • 6.2 TCP服务器端头文件(文件名:tcp_server.h)
    • 七、主函数整合(文件名:main.c)
    • 八、编译下载与测试
      • 8.1 编译代码
      • 8.2 下载代码
      • 8.3 网络测试
    • 九、常见问题排查
      • 9.1 硬件问题
      • 9.2 软件问题
    • 总结

一、开发环境与硬件准备

1.1 开发环境搭建

在开始实战前,需完成以下环境配置,确保代码能正常编译、下载和调试:

  • 编译器:MDK-ARM (Keil uVision5) V5.38 及以上版本
  • 固件库:STM32F4xx Standard Peripheral Library (V1.8.0) 或 STM32CubeF4 (V1.27.0)
  • 调试器:J-Link / ST-Link V2
  • LwIP版本:LwIP 2.1.2(STM32CubeMX集成版本)
  • 驱动库:LAN8720A 底层驱动(无需单独下载,集成在LwIP适配层)

1.2 硬件资源说明

硬件模块核心参数连接关系
STM32F407ZGT6 主频168MHz,带ETH MAC控制器 主控芯片,连接LAN8720A和调试器
LAN8720A 10/100M自适应以太网物理层芯片 通过RMII接口与STM32F407连接
外部晶振 25MHz(ETH时钟源) 为LAN8720A提供时钟
电源 3.3V(LAN8720A)、5V(外设供电) 稳定供电保证通信可靠性

1.3 硬件接线表(RMII接口)

STM32F407引脚LAN8720A引脚功能说明
PA1 RMII_RXDV 接收数据有效信号
PA2 RMII_TXD0 发送数据0
PA3 RMII_TXD1 发送数据1
PA7 RMII_CRS_DV 载波侦听/接收有效
PB11 RMII_TX_EN 发送使能
PC4 RMII_RXD0 接收数据0
PC5 RMII_RXD1 接收数据1
PG11 RMII_MDC 管理数据时钟
PG13 RMII_MDIO 管理数据输入/输出
PG0 LAN8720A_RST LAN8720A复位引脚
25MHz晶振 XTAL_IN/OUT LAN8720A时钟输入

二、开发流程总览

以下流程图清晰展示从环境配置到最终通信测试的完整流程,深色底+白色字体保证可读性:

#mermaid-svg-LXeetI2yjtVBeFRC{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LXeetI2yjtVBeFRC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LXeetI2yjtVBeFRC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LXeetI2yjtVBeFRC .error-icon{fill:#552222;}#mermaid-svg-LXeetI2yjtVBeFRC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LXeetI2yjtVBeFRC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LXeetI2yjtVBeFRC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LXeetI2yjtVBeFRC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LXeetI2yjtVBeFRC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LXeetI2yjtVBeFRC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LXeetI2yjtVBeFRC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LXeetI2yjtVBeFRC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LXeetI2yjtVBeFRC .marker.cross{stroke:#333333;}#mermaid-svg-LXeetI2yjtVBeFRC svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LXeetI2yjtVBeFRC p{margin:0;}#mermaid-svg-LXeetI2yjtVBeFRC .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-LXeetI2yjtVBeFRC .cluster-label text{fill:#333;}#mermaid-svg-LXeetI2yjtVBeFRC .cluster-label span{color:#333;}#mermaid-svg-LXeetI2yjtVBeFRC .cluster-label span p{background-color:transparent;}#mermaid-svg-LXeetI2yjtVBeFRC .label text,#mermaid-svg-LXeetI2yjtVBeFRC span{fill:#333;color:#333;}#mermaid-svg-LXeetI2yjtVBeFRC .node rect,#mermaid-svg-LXeetI2yjtVBeFRC .node circle,#mermaid-svg-LXeetI2yjtVBeFRC .node ellipse,#mermaid-svg-LXeetI2yjtVBeFRC .node polygon,#mermaid-svg-LXeetI2yjtVBeFRC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LXeetI2yjtVBeFRC .rough-node .label text,#mermaid-svg-LXeetI2yjtVBeFRC .node .label text,#mermaid-svg-LXeetI2yjtVBeFRC .image-shape .label,#mermaid-svg-LXeetI2yjtVBeFRC .icon-shape .label{text-anchor:middle;}#mermaid-svg-LXeetI2yjtVBeFRC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LXeetI2yjtVBeFRC .rough-node .label,#mermaid-svg-LXeetI2yjtVBeFRC .node .label,#mermaid-svg-LXeetI2yjtVBeFRC .image-shape .label,#mermaid-svg-LXeetI2yjtVBeFRC .icon-shape .label{text-align:center;}#mermaid-svg-LXeetI2yjtVBeFRC .node.clickable{cursor:pointer;}#mermaid-svg-LXeetI2yjtVBeFRC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LXeetI2yjtVBeFRC .arrowheadPath{fill:#333333;}#mermaid-svg-LXeetI2yjtVBeFRC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LXeetI2yjtVBeFRC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LXeetI2yjtVBeFRC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LXeetI2yjtVBeFRC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LXeetI2yjtVBeFRC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LXeetI2yjtVBeFRC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LXeetI2yjtVBeFRC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LXeetI2yjtVBeFRC .cluster text{fill:#333;}#mermaid-svg-LXeetI2yjtVBeFRC .cluster span{color:#333;}#mermaid-svg-LXeetI2yjtVBeFRC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LXeetI2yjtVBeFRC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LXeetI2yjtVBeFRC rect.text{fill:none;stroke-width:0;}#mermaid-svg-LXeetI2yjtVBeFRC .icon-shape,#mermaid-svg-LXeetI2yjtVBeFRC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LXeetI2yjtVBeFRC .icon-shape p,#mermaid-svg-LXeetI2yjtVBeFRC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LXeetI2yjtVBeFRC .icon-shape rect,#mermaid-svg-LXeetI2yjtVBeFRC .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LXeetI2yjtVBeFRC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LXeetI2yjtVBeFRC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LXeetI2yjtVBeFRC :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-LXeetI2yjtVBeFRC .node>*{fill:#2c3e50!important;color:#ffffff!important;font-size:14px!important;font-family:Microsoft YaHei!important;padding:8px!important;stroke:#3498db!important;stroke-width:2px!important;}#mermaid-svg-LXeetI2yjtVBeFRC .node span{fill:#2c3e50!important;color:#ffffff!important;font-size:14px!important;font-family:Microsoft YaHei!important;padding:8px!important;stroke:#3498db!important;stroke-width:2px!important;}#mermaid-svg-LXeetI2yjtVBeFRC .node tspan{fill:#ffffff!important;}

环境搭建

STM32CubeMX配置

LAN8720A底层驱动编写

LwIP协议栈移植

TCP/IP应用层开发

代码编译下载

网络调试工具测试

通信成功?

排查硬件/代码问题

功能扩展/优化

三、STM32CubeMX配置步骤

3.1 新建工程

  • 打开STM32CubeMX,选择File -> New Project,搜索STM32F407ZGT6并选中
  • 选择Start Project进入配置界面
  • 3.2 时钟配置

  • 进入Clock Configuration,设置外部高速时钟(HSE)为25MHz
  • 配置系统时钟(SYSCLK)为168MHz,HCLK=168MHz,PCLK2=84MHz(ETH时钟依赖PCLK2)
  • ETH MAC时钟配置:ETH_CLK = PCLK2 (84MHz) / 2 = 42MHz(满足RMII接口时钟要求)
  • 3.3 引脚配置

  • 进入Pinout & Configuration,依次配置1.3节中所有RMII接口引脚,模式均为Alternate Function Mode
  • 配置LAN8720A复位引脚(PG0)为GPIO_Output模式
  • 为所有ETH相关引脚分配复用功能(AF11):
    • PA1/PA2/PA3/PA7: AF11
    • PB11: AF11
    • PC4/PC5: AF11
    • PG11/PG13: AF11
  • 3.4 外设配置

  • 启用ETH外设:Connectivity -> ETH,选择RMII模式
  • 关闭ETH_PTP(无需精确时间协议)
  • 配置NVIC:开启ETH全局中断,优先级设置为中等(如抢占优先级2,子优先级0)
  • 3.5 LwIP协议栈配置

  • 进入Middleware -> LwIP,启用LwIP协议栈
  • 选择TCP和UDP协议(按需选择,本文以TCP为例)
  • IP地址配置:
    • 静态IP:192.168.1.100
    • 子网掩码:255.255.255.0
    • 网关:192.168.1.1
  • 关闭DHCP(新手先使用静态IP,降低复杂度)
  • 配置LwIP内存池:MEM_SIZE=16384,MEMP_NUM_TCP_PCB=5
  • 3.6 生成代码

  • 进入Project Manager,设置工程名(如STM32F407_LAN8720A_LwIP),选择保存路径
  • 工具链选择MDK-ARM v5
  • 点击GENERATE CODE生成工程,完成后用Keil打开
  • 四、底层驱动编写

    4.1 LAN8720A复位驱动(文件名:lan8720a_reset.c)

    #include "lan8720a_reset.h"
    #include "gpio.h"
    #include "delay.h" // 需自行实现简单的延时函数

    // LAN8720A复位引脚定义(PG0)
    #define LAN8720A_RST_PIN GPIO_PIN_0
    #define LAN8720A_RST_PORT GPIOG

    /**
    * @brief LAN8720A硬件复位
    * @note 复位脉冲宽度需满足芯片手册要求(至少100us)
    * @param 无
    * @retval 无
    */

    void LAN8720A_Reset(void)
    {
    // 拉低复位引脚
    HAL_GPIO_WritePin(LAN8720A_RST_PORT, LAN8720A_RST_PIN, GPIO_PIN_RESET);
    // 延时1ms,确保复位完成
    HAL_Delay(1);
    // 释放复位
    HAL_GPIO_WritePin(LAN8720A_RST_PORT, LAN8720A_RST_PIN, GPIO_PIN_SET);
    // 等待芯片初始化(至少10ms)
    HAL_Delay(10);
    }

    /**
    * @brief LAN8720A初始化
    * @note 复位后检测芯片是否正常
    * @param 无
    * @retval 0:成功 其他:失败
    */

    uint8_t LAN8720A_Init(void)
    {
    // 复位LAN8720A
    LAN8720A_Reset();

    // 简单检测:读取LAN8720A的PHY ID(可选,新手可跳过)
    uint16_t phy_id = ETH_ReadPHYRegister(ETH_PHY_ADDRESS, PHY_ID1);
    if((phy_id & 0xFFF0) != 0x0070) // LAN8720A的PHY ID高位为0x0070
    {
    return 1; // 检测失败
    }

    return 0; // 初始化成功
    }

    4.2 LAN8720A复位驱动头文件(文件名:lan8720a_reset.h)

    #ifndef __LAN8720A_RESET_H
    #define __LAN8720A_RESET_H

    #include "stm32f4xx_hal.h"

    #ifdef __cplusplus
    extern "C" {
    #endif

    // LAN8720A PHY地址(默认0x00)
    #define ETH_PHY_ADDRESS 0x00

    // PHY寄存器地址定义
    #define PHY_ID1 0x02

    // 函数声明
    void LAN8720A_Reset(void);
    uint8_t LAN8720A_Init(void);

    #ifdef __cplusplus
    }
    #endif

    #endif /* __LAN8720A_RESET_H */

    4.3 ETH底层驱动扩展(文件名:eth_driver_ext.c)

    #include "eth_driver_ext.h"
    #include "lan8720a_reset.h"

    /**
    * @brief 读取PHY寄存器
    * @note 底层ETH驱动扩展函数,用于访问LAN8720A寄存器
    * @param phy_addr: PHY地址
    * @param reg_addr: 寄存器地址
    * @retval 寄存器值
    */

    uint16_t ETH_ReadPHYRegister(uint8_t phy_addr, uint8_t reg_addr)
    {
    uint16_t reg_value = 0;
    HAL_ETH_ReadPHYRegister(&heth, phy_addr, reg_addr, &reg_value);
    return reg_value;
    }

    /**
    * @brief 写入PHY寄存器
    * @param phy_addr: PHY地址
    * @param reg_addr: 寄存器地址
    * @param reg_value: 要写入的值
    * @retval 无
    */

    void ETH_WritePHYRegister(uint8_t phy_addr, uint8_t reg_addr, uint16_t reg_value)
    {
    HAL_ETH_WritePHYRegister(&heth, phy_addr, reg_addr, reg_value);
    }

    /**
    * @brief ETH外设初始化扩展
    * @note 包含LAN8720A初始化和ETH参数配置
    * @param 无
    * @retval HAL状态
    */

    HAL_StatusTypeDef ETH_Init_Ext(void)
    {
    // 初始化LAN8720A
    if(LAN8720A_Init() != 0)
    {
    return HAL_ERROR;
    }

    // 配置ETH工作模式(100M全双工)
    ETH_WritePHYRegister(ETH_PHY_ADDRESS, 0x00, 0x2100);

    return HAL_OK;
    }

    4.4 ETH底层驱动扩展头文件(文件名:eth_driver_ext.h)

    #ifndef __ETH_DRIVER_EXT_H
    #define __ETH_DRIVER_EXT_H

    #include "stm32f4xx_hal.h"
    #include "eth.h"

    #ifdef __cplusplus
    extern "C" {
    #endif

    // 函数声明
    uint16_t ETH_ReadPHYRegister(uint8_t phy_addr, uint8_t reg_addr);
    void ETH_WritePHYRegister(uint8_t phy_addr, uint8_t reg_addr, uint16_t reg_value);
    HAL_StatusTypeDef ETH_Init_Ext(void);

    #ifdef __cplusplus
    }
    #endif

    #endif /* __ETH_DRIVER_EXT_H */

    五、LwIP协议栈移植与配置

    5.1 LwIP核心配置(文件名:lwipopts.h)

    #ifndef __LWIPOPTS_H__
    #define __LWIPOPTS_H__

    // 1. 基础配置
    #define NO_SYS 0 // 使用操作系统(本文用裸机,0为不使用OS)
    #define LWIP_SOCKET 1 // 启用socket API
    #define LWIP_NETCONN 1 // 启用netconn API
    #define LWIP_TCP 1 // 启用TCP协议
    #define LWIP_UDP 1 // 启用UDP协议
    #define LWIP_IPV4 1 // 启用IPv4
    #define LWIP_DHCP 0 // 关闭DHCP,使用静态IP
    #define LWIP_ICMP 1 // 启用ICMP(ping功能)
    #define LWIP_RAW 1 // 启用RAW API

    // 2. 内存配置
    #define MEM_ALIGNMENT 4 // 内存对齐方式
    #define MEM_SIZE 16384 // 内存池大小(16KB)
    #define MEMP_NUM_TCP_PCB 5 // TCP连接控制块数量
    #define MEMP_NUM_UDP_PCB 5 // UDP控制块数量
    #define MEMP_NUM_NETBUF 10 // 网络缓冲区数量
    #define MEMP_NUM_NETCONN 10 // 网络连接数量

    // 3. TCP配置
    #define TCP_MSS 1460 // TCP最大分段大小
    #define TCP_WND 2048 // TCP窗口大小
    #define TCP_SND_BUF 2048 // TCP发送缓冲区大小
    #define TCP_SND_QUEUELEN 5 // TCP发送队列长度
    #define TCP_LISTEN_BACKLOG 5 // TCP监听队列长度

    // 4. 网络接口配置
    #define LWIP_NETIF_LINK_CALLBACK 1 // 启用链路状态回调
    #define LWIP_NETIF_HOSTNAME 1 // 启用主机名
    #define NETIF_HOSTNAME "STM32F407_LAN8720A" // 主机名

    // 5. 时钟配置(STM32F407主频168MHz)
    #define SYSTICK_PERIOD_MS 1 // 系统滴答定时器周期(1ms)
    #define LWIP_TIMERS 1 // 启用LwIP定时器

    // 6. 调试配置(新手可关闭)
    #define LWIP_DEBUG 0
    #define TCP_DEBUG LWIP_DBG_OFF
    #define UDP_DEBUG LWIP_DBG_OFF
    #define IP_DEBUG LWIP_DBG_OFF

    #endif /* __LWIPOPTS_H__ */

    5.2 LwIP初始化(文件名:lwip_init.c)

    #include "lwip_init.h"
    #include "lwip/opt.h"
    #include "lwip/arch.h"
    #include "lwip/api.h"
    #include "lwip/netif.h"
    #include "lwip/tcpip.h"
    #include "netif/etharp.h"
    #include "eth_driver_ext.h"
    #include "stm32f4xx_hal.h"

    // 网络接口结构体
    struct netif gnetif;

    // IP地址配置
    #define IP_ADDR0 192
    #define IP_ADDR1 168
    #define IP_ADDR2 1
    #define IP_ADDR3 100

    #define NETMASK0 255
    #define NETMASK1 255
    #define NETMASK2 255
    #define NETMASK3 0

    #define GW_ADDR0 192
    #define GW_ADDR1 168
    #define GW_ADDR2 1
    #define GW_ADDR3 1

    /**
    * @brief LwIP时钟处理函数
    * @note 需在SysTick中断中调用,周期1ms
    * @param 无
    * @retval 无
    */

    void LwIP_Time_Handler(void)
    {
    sys_check_timeouts();
    }

    /**
    * @brief 网络接口状态回调函数
    * @note 打印链路状态(可选)
    * @param netif: 网络接口指针
    * @retval 无
    */

    void netif_link_callback(struct netif *netif)
    {
    if(netif_is_link_up(netif))
    {
    // 链路已连接(可添加LED指示或串口打印)
    }
    else
    {
    // 链路断开(可添加LED指示或串口打印)
    }
    }

    /**
    * @brief LwIP初始化函数
    * @note 完成协议栈和网络接口初始化
    * @param 无
    * @retval 0:成功 其他:失败
    */

    uint8_t LwIP_Init(void)
    {
    ip4_addr_t ipaddr, netmask, gw;

    // 1. 初始化TCP/IP内核
    tcpip_init(NULL, NULL);

    // 2. 配置IP地址、子网掩码、网关
    IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
    IP4_ADDR(&netmask, NETMASK0, NETMASK1, NETMASK2, NETMASK3);
    IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);

    // 3. 添加网络接口
    netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);

    // 4. 设置默认网络接口
    netif_set_default(&gnetif);

    // 5. 启用链路状态回调
    netif_set_link_callback(&gnetif, netif_link_callback);

    // 6. 初始化ETH外设
    if(ETH_Init_Ext() != HAL_OK)
    {
    return 1;
    }

    // 7. 标记网络接口为UP状态
    netif_set_up(&gnetif);

    // 8. 标记链路为UP状态
    netif_set_link_up(&gnetif);

    return 0;
    }

    5.3 LwIP初始化头文件(文件名:lwip_init.h)

    #ifndef __LWIP_INIT_H
    #define __LWIP_INIT_H

    #include "lwip/netif.h"

    #ifdef __cplusplus
    extern "C" {
    #endif

    // 外部声明网络接口结构体
    extern struct netif gnetif;

    // 函数声明
    void LwIP_Time_Handler(void);
    uint8_t LwIP_Init(void);

    #ifdef __cplusplus
    }
    #endif

    #endif /* __LWIP_INIT_H */

    六、TCP应用层开发

    6.1 TCP服务器端(文件名:tcp_server.c)

    #include "tcp_server.h"
    #include "lwip/api.h"
    #include "lwip/sys.h"
    #include "stm32f4xx_hal.h"

    #define TCP_SERVER_PORT 8080 // TCP服务器端口
    #define TCP_BUF_SIZE 1024 // 数据缓冲区大小

    // TCP服务器线程函数(裸机下用轮询方式)
    static void tcp_server_thread(void)
    {
    struct netconn *conn, *newconn;
    err_t err, recv_err;
    struct netbuf *buf;
    void *data;
    u16_t len;
    ip_addr_t client_ip;
    u16_t client_port;

    // 1. 创建TCP连接控制块
    conn = netconn_new(NETCONN_TCP);
    if(conn == NULL)
    {
    return; // 创建失败
    }

    // 2. 绑定端口
    err = netconn_bind(conn, IP_ADDR_ANY, TCP_SERVER_PORT);
    if(err != ERR_OK)
    {
    netconn_delete(conn);
    return; // 绑定失败
    }

    // 3. 开始监听
    err = netconn_listen(conn);
    if(err != ERR_OK)
    {
    netconn_delete(conn);
    return; // 监听失败
    }

    // 4. 轮询等待客户端连接
    while(1)
    {
    // 等待新连接(阻塞式)
    err = netconn_accept(conn, &newconn);
    if(err == ERR_OK)
    {
    // 获取客户端IP和端口
    client_ip = newconn->pcb.tcp->remote_ip;
    client_port = newconn->pcb.tcp->remote_port;

    // 向客户端发送欢迎信息
    char welcome_msg[64];
    sprintf(welcome_msg, "Welcome! Client IP: %d.%d.%d.%d, Port: %d\\r\\n",
    ip4_addr1(&client_ip), ip4_addr2(&client_ip),
    ip4_addr3(&client_ip), ip4_addr4(&client_ip),
    client_port);
    netconn_write(newconn, welcome_msg, strlen(welcome_msg), NETCONN_COPY);

    // 循环接收客户端数据
    while(1)
    {
    // 接收数据
    recv_err = netconn_recv(newconn, &buf);
    if(recv_err == ERR_OK)
    {
    // 提取数据和长度
    netbuf_data(buf, &data, &len);

    // 回显数据给客户端(echo功能)
    netconn_write(newconn, data, len, NETCONN_COPY);

    // 释放缓冲区
    netbuf_delete(buf);
    }
    else
    {
    // 连接断开或出错
    break;
    }
    }

    // 关闭连接
    netconn_close(newconn);
    netconn_delete(newconn);
    }
    }
    }

    /**
    * @brief TCP服务器初始化
    * @note 启动TCP服务器(裸机下直接调用线程函数)
    * @param 无
    * @retval 无
    */

    void tcp_server_init(void)
    {
    // 裸机环境下直接调用轮询函数
    tcp_server_thread();
    }

    6.2 TCP服务器端头文件(文件名:tcp_server.h)

    #ifndef __TCP_SERVER_H
    #define __TCP_SERVER_H

    #ifdef __cplusplus
    extern "C" {
    #endif

    // 函数声明
    void tcp_server_init(void);

    #ifdef __cplusplus
    }
    #endif

    #endif /* __TCP_SERVER_H */

    七、主函数整合(文件名:main.c)

    #include "main.h"
    #include "gpio.h"
    #include "eth.h"
    #include "lwip_init.h"
    #include "tcp_server.h"
    #include "delay.h"

    /* Private function prototypes ———————————————–*/
    void SystemClock_Config(void);
    static void MX_GPIO_Init(void);
    static void MX_ETH_Init(void);

    int main(void)
    {
    /* MCU Configuration——————————————————–*/
    // 1. 复位所有外设,初始化Flash接口和SysTick
    HAL_Init();

    // 2. 配置系统时钟(168MHz)
    SystemClock_Config();

    // 3. 初始化所有配置的外设
    MX_GPIO_Init();
    MX_ETH_Init();

    // 4. 初始化LwIP协议栈
    if(LwIP_Init() != 0)
    {
    // 初始化失败,可点亮错误LED
    while(1)
    {
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // PA5为LED引脚示例
    HAL_Delay(500);
    }
    }

    // 5. 启动TCP服务器
    tcp_server_init();

    // 6. 主循环(LwIP时钟处理)
    while (1)
    {
    // 1ms调用一次LwIP时钟处理函数(需确保SysTick中断周期为1ms)
    LwIP_Time_Handler();
    HAL_Delay(1);
    }
    }

    /**
    * @brief System Clock Configuration
    * @retval None
    */

    void SystemClock_Config(void)
    {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    // 配置外部高速振荡器(HSE)
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 25;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 7;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
    Error_Handler();
    }

    // 配置系统时钟总线
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
    |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
    {
    Error_Handler();
    }
    }

    /**
    * @brief 错误处理函数
    * @retval None
    */

    void Error_Handler(void)
    {
    // 用户可添加自定义错误处理逻辑
    __disable_irq();
    while (1)
    {
    }
    }

    #ifdef USE_FULL_ASSERT
    /**
    * @brief 断言失败处理函数
    * @param file: 断言失败的文件名称
    * @param line: 断言失败的行号
    * @retval None
    */

    void assert_failed(uint8_t *file, uint32_t line)
    {
    // 用户可添加自定义打印信息
    }
    #endif /* USE_FULL_ASSERT */

    八、编译下载与测试

    8.1 编译代码

  • 在Keil中打开生成的工程,添加上述所有代码文件到对应目录
  • 点击Build或Rebuild编译工程,确保无错误和警告
    • 常见错误:缺少delay.c/delay.h,需自行实现简单的延时函数
    • 常见警告:未使用的变量,可忽略或注释掉
  • 8.2 下载代码

  • 连接J-Link/ST-Link到STM32F407开发板
  • 在Keil中配置调试器:Options for Target -> Debug,选择对应调试器
  • 点击Download下载代码到开发板
  • 8.3 网络测试

  • 硬件连接:用网线将开发板连接到路由器/电脑网口
  • 电脑IP配置:将电脑IP设置为192.168.1.xxx(如192.168.1.200),子网掩码255.255.255.0
  • 测试工具:使用网络调试助手(如NetAssist)
    • 选择TCP客户端,IP地址填写192.168.1.100,端口8080
    • 点击连接,成功后会收到STM32发送的欢迎信息
    • 在发送框输入任意字符,点击发送,会收到STM32回显的相同字符
  • Ping测试:在电脑CMD中输入ping 192.168.1.100,能收到回复说明网络通
  • 九、常见问题排查

    9.1 硬件问题

  • 网线未插好或损坏:更换网线测试
  • LAN8720A复位引脚未配置:检查PG0引脚电平是否正确
  • RMII接口接线错误:对照1.3节重新检查接线
  • 电源不稳定:确保3.3V供电稳定,纹波小于100mV
  • 9.2 软件问题

  • 时钟配置错误:检查PCLK2是否为84MHz
  • LwIP配置错误:检查lwipopts.h中关键参数(如MEM_SIZE)
  • IP地址冲突:确保192.168.1.100未被其他设备占用
  • 端口被占用:更换TCP服务器端口(如8081)测试
  • 总结

  • 本次实战基于STM32F407+LAN8720A+LwIP实现了TCP服务器功能,核心步骤包括硬件接线、CubeMX配置、底层驱动编写、LwIP移植和应用层开发,所有代码可直接复用。
  • 关键要点:RMII接口的正确配置、LAN8720A的复位时序、LwIP协议栈的内存参数配置、TCP连接的轮询处理。
  • 测试环节需先确保硬件链路通(Ping测试),再测试TCP通信,排查问题时优先检查硬件接线和时钟配置。
  • 通过以上完整步骤,零基础小白也能一步步实现STM32的以太网通信功能,在此基础上可扩展HTTP服务器、UDP通信、MQTT协议等更多功能。

    赞(0)
    未经允许不得转载:171主机测评 » 以太网通信实战:STM32F407+LAN8720A+LwIP,TCP/IP协议栈应用
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址