14-以太网通信与W5500.md 15 KB


tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(高级篇)V2.0.1 — 第2章:以太网 + 配套代码05~09" author: "尚硅谷研究院" date: 2026-07-15

created: 2026-07-15

以太网通信与W5500

用生活理解:W5500 就像一个自带"同声传译"的网卡芯片——你只需要用 SPI 告诉它"我要连哪台机器、哪个端口",它自己就把 TCP/IP 协议栈这些复杂工作全干了。你的单片机完全不用关心三次握手、重传、分片这些事,只管发/收数据就好。


互联网基础

OSI 7 层模型 vs TCP/IP 4 层模型

OSI 7 层 TCP/IP 4 层 代表协议 功能
应用层 应用层 HTTP, FTP, MQTT, DNS 为用户提供网络应用服务
表示层 SSL/TLS 数据加密/解密、压缩
会话层 SOCKS 建立/管理/终止会话
传输层 传输层 TCP, UDP 端到端通信、可靠传输
网络层 网络层 IP, ICMP, ARP 路由寻址、分组转发
数据链路层 网络接口层 Ethernet, MAC 帧封装、介质访问
物理层 以太网物理层 比特流传输、信号编码

TCP vs UDP

特性 TCP UDP
连接 面向连接(三次握手) 无连接
可靠性 可靠(确认重传) 不可靠(最大努力交付)
顺序 按序交付 不保证顺序
速度 较慢 快速
适用场景 Web、文件传输、邮件 视频流、DNS、IoT 传感器数据

常见协议

协议 端口 功能
HTTP 80 Web 网页访问
HTTPS 443 加密 Web 访问
DNS 53 域名→IP 解析
DHCP 67/68 自动分配 IP 地址
MQTT 1883 IoT 轻量级消息协议

W5500 芯片详解

W5500 是韩国 WIZnet 公司生产的全硬件 TCP/IP 协议栈芯片。不需要在单片机端运行 TCP/IP 协议栈——W5500 内部硬件实现。

特性
接口 SPI(最高 80MHz,可变长数据帧模式)
协议 TCP, UDP, ICMP, IPv4, ARP, IGMP, PPPoE
Socket 8 个独立 Socket(同时支持 8 个连接)
内部 RAM 32KB(Tx/Rx 可分配给各 Socket)
MAC + PHY 内置(10/100Mbps 自适应)
封装 LQFP48

参考:W5500 数据手册(WIZnet 官网)

W5500 接入框图

STM32F103ZET6                           W5500
┌──────────────┐                      ┌──────────┐    ┌──────┐
│ SPI2_SCK     ├────── PB13(SCK) ────→│ SCLK     │    │RJ45 │
│ SPI2_MOSI    ├────── PB15(MOSI) ───→│ MOSI     ├────┤带有 │
│ SPI2_MISO    ├────── PB14(MISO) ←──┤ MISO     │    │网络 │
│ PD3(CS)      ├────────────────────→│ nSCS     │    │变压 │
│ PG6(INT)     │←────────────────────┤ INTn     │    │器  │
│ PG7(RST)     ├────────────────────→│ RSTn     │    └──────┘
│              │                      │          │
│ SPI2         │                      │内置MAC+PHY│
└──────────────┘                      └──────────┘

W5500 内部架构

SPI 接口 ←→ 寄存器管理单元 ←→ 通用寄存器(模式/MAC/IP等)
                          ←→ Socket 寄存器(8个Socket,各512字节)
                          ←→ Tx/Rx 存储器(32KB 可分配)
                          ←→ TCP/IP 内核(硬件实现)
                                  ↓
                               MAC + PHY ←→ 以太网口

W5500 SPI 可变长数据帧模式

W5500 的 SPI 数据帧与普通 SPI Flash 不同,分为控制字节 + 数据

帧格式: [地址偏移(1B)] [控制字节(1B)] [数据(N字节)]

控制字节:
  [7:5] = 块选择 (000=通用寄存器, 001=Socket寄存器, 010=Tx缓冲区, 011=Rx缓冲区)
  [4:2] = 读写模式
  [1]   = 可变长模式 1=VDM (推荐)
  [0]   = 1=读, 0=写

W5500 官方库移植步骤

  1. 从 WIZnet 官网下载 W5500 官方库
  2. 复制 Ethernet/ 目录到项目
  3. 修改 wizchip_conf.h 中的 SPI 接口函数
  4. 实现以下底层函数:

    void SPI_CS_Select(void);      // 芯片选择
    void SPI_CS_Deselect(void);    // 取消片选
    uint8_t SPI_ReadWrite(uint8_t byte); // SPI 读写1字节
    
  5. 调用 wizchip_init()wizchip_setnetinfo() 初始化


实验:W5500 TCP Server

软件设计

项目路径stm32/05_ethernet_test_register ~ stm32/09_ethernet_webserver_register

文件:stm32/05_ethernet_test_register/Hardware/SPI/spi.h

/*
 * @Author: wushengran
 * @Date: 2024-10-09 16:17:42
 * @Description: 
 * 
 * Copyright (c) 2024 by atguigu, All Rights Reserved. 
 */
#ifndef __SPI_H
#define __SPI_H

#include "stm32f10x.h"

// 宏定义操作总线的引脚
// CS - PD3
#define CS_HIGH (GPIOD->ODR |= GPIO_ODR_ODR3)
#define CS_LOW (GPIOD->ODR &= ~GPIO_ODR_ODR3)

// 初始化
void SPI_Init(void);

// SPI通信的开启和关闭
void SPI_Start(void);
void SPI_Stop(void);

// 一个时钟周期内,交换一个字节数据
uint8_t SPI_SwapByte(uint8_t byte);

#endif

文件:stm32/05_ethernet_test_register/Hardware/SPI/spi.c

/*
 * @Author: wushengran
 * @Date: 2024-10-09 16:17:30
 * @Description: 
 * 
 * Copyright (c) 2024 by atguigu, All Rights Reserved. 
 */
#include "spi.h"

// 初始化
void SPI_Init(void)
{
    // 1. 开启时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;
    RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;

    // 2. GPIO配置模式
    // 2.1 CS - PD3:通用推挽输出,CNF = 00,MODE = 11
    GPIOD->CRL |= GPIO_CRL_MODE3;
    GPIOD->CRL &= ~GPIO_CRL_CNF3;

    // 2.2 SCK - PB13:复用推挽输出,CNF = 10,MODE = 11
    GPIOB->CRH |= GPIO_CRH_MODE13;
    GPIOB->CRH |= GPIO_CRH_CNF13_1;
    GPIOB->CRH &= ~GPIO_CRH_CNF13_0;

    // 2.3 MOSI - PB15:复用推挽输出,CNF = 10,MODE = 11
    GPIOB->CRH |= GPIO_CRH_MODE15;
    GPIOB->CRH |= GPIO_CRH_CNF15_1;
    GPIOB->CRH &= ~GPIO_CRH_CNF15_0;

    // 2.4 MISO - PB14:浮空输入,CNF = 01,MODE = 00
    GPIOB->CRH &= ~GPIO_CRH_MODE14;
    GPIOB->CRH &= ~GPIO_CRH_CNF14_1;
    GPIOB->CRH |= GPIO_CRH_CNF14_0;

    // 3. SPI模块配置
    // 3.1 配置模式参数
    // 3.1.1 设为主模式
    SPI2->CR1 |= SPI_CR1_MSTR;

    // 3.1.2 选择软件控制片选,SSI置为高电平
    SPI2->CR1 |= SPI_CR1_SSM;
    SPI2->CR1 |= SPI_CR1_SSI;

    // 3.1.2 SPI通信模式设为模式0:CPOL = 0,CPHA = 0
    SPI2->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA);

    // 3.2 设置波特率:时钟分频系数,000 - 2分频,18MHz
    SPI2->CR1 &= ~SPI_CR1_BR;
    // SPI2->CR1 |= SPI_CR1_BR_0;

    // 3.3 配置帧格式参数
    // 3.3.1 数据帧长度:0 - 8位
    SPI2->CR1 &= ~SPI_CR1_DFF;

    // 3.3.2 数据传输顺序:0 - MSB先发
    SPI2->CR1 &= ~SPI_CR1_LSBFIRST;

    // 3.4 使能SPI
    SPI2->CR1 |= SPI_CR1_SPE;
}

// SPI通信的开启和关闭,通过片选信号控制
void SPI_Start(void)
{
    CS_LOW;
}
void SPI_Stop(void)
{
    CS_HIGH;
}

// 一个时钟周期内,交换一个字节数据
uint8_t SPI_SwapByte(uint8_t byte)
{
    // 1. 将要发送的数据写入发送缓冲区
    // 1.1 等待发送缓冲区为空(TXE = 1)
    while ((SPI2->SR & SPI_SR_TXE) == 0 )
    {}
    
    // 1.2 将数据写入DR
    SPI2->DR = byte;

    // 2. 获取接收到的数据并返回
    // 2.1 等待接收缓冲区非空(RXNE = 1)
    while ( (SPI2->SR & SPI_SR_RXNE) == 0 )
    {}
    
    // 2.2 将DR中数据返回
    return (uint8_t)(SPI2->DR & 0xff);
}

文件:stm32/09_ethernet_webserver_register/Interface/Ethernet/eth.h

/*
 * @Author: wushengran
 * @Date: 2024-12-24 14:38:01
 * @Description: 
 * 
 * Copyright (c) 2024 by atguigu, All Rights Reserved. 
 */
#ifndef __ETH_H
#define __ETH_H

#include "w5500.h"
#include <stdio.h>

// 初始化
void ETH_Init(void);

#endif

文件:stm32/09_ethernet_webserver_register/Interface/Ethernet/eth.c

/*
 * @Author: wushengran
 * @Date: 2024-12-24 14:37:53
 * @Description:
 *
 * Copyright (c) 2024 by atguigu, All Rights Reserved.
 */
#include "eth.h"
#include "delay.h"

// 定义W5500的IP地址、MAC地址、子网掩码和网关地址
uint8_t ip[4] = {192, 168, 44, 222};
uint8_t mac[6] = {110, 120, 130, 140, 150, 160};
uint8_t submask[4] = {255, 255, 255, 0};
uint8_t gateway[4] = {192, 168, 44, 1};

// 复位W5500
static void ETH_Reset(void);
// 配置 MAC 地址
static void ETH_SetMac(void);
// 配置 IP 地址、子网掩码和网关
static void ETH_SetIP(void);

// 初始化
void ETH_Init(void)
{
    // 0. SPI 初始化
    SPI_Init();

    // 1. 注册自定义回调函数
    user_register_function();

    // 2. 复位W5500
    ETH_Reset();

    // 3. 配置 MAC 地址
    ETH_SetMac();

    // 4. 配置 IP 地址、子网掩码和网关
    ETH_SetIP();
}

// 复位W5500
static void ETH_Reset(void)
{
    // 1. 配置RST引脚-PG7
    // 1.1 开启时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPGEN;

    // 1.2 配置工作模式:通用推挽输出,MODE - 11,CNF - 00
    GPIOG->CRL |= GPIO_CRL_MODE7;
    GPIOG->CRL &= ~GPIO_CRL_CNF7;

    // 2. 拉低RST引脚,等待500us以上
    GPIOG->ODR &= ~GPIO_ODR_ODR7;

    Delay_us(800);

    GPIOG->ODR |= GPIO_ODR_ODR7;

    printf("W5500 复位完成!\n");
}

// 配置 MAC 地址
static void ETH_SetMac(void)
{
    printf("开始配置 MAC 地址:\n");
    setSHAR(mac);
    printf("MAC 地址配置完成:%X-%X-%X-%X-%X-%X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

// 配置 IP 地址、子网掩码和网关
static void ETH_SetIP(void)
{
    printf("开始配置 IP 地址:\n");

    // 配置IP
    setSIPR(ip);
    // 配置子网掩码
    setSUBR(submask);
    // 配置网关地址
    setGAR(gateway);

    printf("IP 地址配置完成:%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
}

文件:stm32/09_ethernet_webserver_register/App/Web/web_server.h

/*
 * @Author: wushengran
 * @Date: 2024-12-25 16:35:51
 * @Description: 
 * 
 * Copyright (c) 2024 by atguigu, All Rights Reserved. 
 */
#ifndef __WEB_SERVER_H
#define __WEB_SERVER_H

#include "httpServer.h"
#include "led.h"
#include <string.h>

// 初始化Web服务器
void WebServer_Init(void);

// 运行Web服务器
void WebServer_Start(void);

#endif

文件:stm32/09_ethernet_webserver_register/App/Web/web_server.c

/*
 * @Author: wushengran
 * @Date: 2024-12-25 16:35:46
 * @Description: 
 * 
 * Copyright (c) 2024 by atguigu, All Rights Reserved. 
 */
#include "web_server.h"

// 全局变量定义,初始化Web服务器需要的参数
uint8_t txBuff[2048] = {0};
uint8_t rxBuff[2048] = {0};
uint8_t socketCount = 8;
uint8_t socketList[] = {0,1,2,3,4,5,6,7};

uint8_t *contentName = "index.html";

uint8_t content[] = "<!doctype html>\n"
                             "<html lang=\"en\">\n"
                             "<head>\n"
                             "    <meta charset=\"GBK\">\n"
                             "    <meta name=\"viewport\"\n"
                             "          content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\">\n"
                             "    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n"
                             "    <title>尚硅谷嵌入式课程</title>\n"
                             "\n"
                             "    <style type=\"text/css\">\n"
                             "        #open_red{\n"
                             "            color: red;\n"
                             "            width: 100px;\n"
                             "            height: 40px;\n"
                             "\n"
                             "\n"
                             "        }\n"
                             "        #close_red{\n"
                             "            color: black;\n"
                             "            width: 100px;\n"
                             "            height: 40px;\n"
                             "        }\n"
                             "    </style>\n"
                             "</head>\n"
                             "<body>\n"
                             "<a href=\"/index.html?action=1\"><button id=\"open_red\" >开灯</button></a>\n"
                             "<a href=\"/index.html?action=2\"><button id=\"close_red\" >关灯</button></a>\n"
                             "<a href=\"/index.html?action=3\"><button id=\"close_red\" >翻转</button></a>\n"
                             "</body>\n"
                             "</html>";


// 初始化Web服务器
void WebServer_Init(void)
{
    // 1. 初始化LED
    LED_Init();

    // 2. 初始化http服务器
    httpServer_init(txBuff, rxBuff, socketCount, socketList);

    // 3. 注册html页面,告诉Web服务器需要响应的数据,即页面
    reg_httpServer_webContent(contentName, content);
}

// 运行Web服务器
void WebServer_Start(void)
{
    for (uint8_t i = 0; i < sizeof(socketList); i++)
    {
        httpServer_run(i);
    }
}

// 内部函数
// 解析URL中的action
static uint8_t parse_url_action(uint8_t *url);

// 根据action执行LED控制
static void do_led_action(uint8_t action);

void handler_user_function(uint8_t *url)
{
    // 1. 从URL解析出action的值
    uint8_t action = parse_url_action(url);

    // 2. 根据action的值,执行相应的LED控制
    do_led_action(action);
}

// 解析URL中的action
static uint8_t parse_url_action(uint8_t *url)
{
    // 查找action=字符串的位置
    uint8_t *pAction = (uint8_t *)strstr((char *)url, "action=");

    if (pAction == NULL)
    {
        return '0';
    }
    else
    {
        return *(pAction + 7);
    }
    
}

// 根据action执行LED控制
static void do_led_action(uint8_t action)
{
    if (action == '1')
    {
        LED_On(LED_2);
    }
    else if (action == '2')
    {
        LED_Off(LED_2);
    }
    else if (action == '3')
    {
        LED_Toggle(LED_2);
    } 
}

核心函数速查表(W5500 Socket API)

函数 说明
socket(sn, protocol, port, flag) 创建 Socket,返回句柄(-1 失败)
close(sn) 关闭 Socket
listen(sn) TCP Server 开始监听
connect(sn, ip, port) TCP Client 连接远端
accept(sn) 接受连接(返回新 Socket 句柄)
send(sn, data, len) 发送数据
recv(sn, buffer) 接收数据(返回实际接收长度)
setsockopt(sn, option, value) 设置 Socket 选项
getsockopt(sn, option) 获取 Socket 状态

常见问题与避坑

  1. W5500 不工作 → 确认 SPI 通信(示波器看 SCK/MOSI)、检查晶振(25MHz)是否起振
  2. Ping 不通 → 检查 IP/子网掩码/网关配置、检查以太网物理层(RJ45 链接灯亮?)
  3. TCP 连接频繁断开 → W5500 在无数据时可能进入节能模式,需定期发送心跳包
  4. W5500 官方库移植注意 → 必须实现 SPI_CS_Select/DeselectSPI_ReadWrite,并注册到库中
  5. Socket 资源不足 → W5500 最多 8 个 Socket,每个需要分配 Tx/Rx 缓冲区,注意 32KB 的总量分配
  6. HTTP 响应只发一次 → HTTP/1.1 默认为 keep-alive,如需短连接在响应头加 Connection: close