--- 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. 实现以下底层函数: ```c 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`** ```c /* * @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`** ```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`** ```c /* * @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 // 初始化 void ETH_Init(void); #endif ``` **文件:`stm32/09_ethernet_webserver_register/Interface/Ethernet/eth.c`** ```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`** ```c /* * @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 // 初始化Web服务器 void WebServer_Init(void); // 运行Web服务器 void WebServer_Start(void); #endif ``` **文件:`stm32/09_ethernet_webserver_register/App/Web/web_server.c`** ```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[] = "\n" "\n" "\n" " \n" " \n" " \n" " 尚硅谷嵌入式课程\n" "\n" " \n" "\n" "\n" "\n" "\n" "\n" "\n" ""; // 初始化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); } } ``` --- ## 实验项目详解 所有项目共享 `Hardware/SPI/spi.h/.c`(SPI 驱动,已在上面展示)和 `Interface/Ethernet/eth.h/.c`(W5500 网络初始化)。 ### 05 — 基础以太网通信测试(寄存器版) **项目路径**:`stm32/05_ethernet_test_register` 仅验证 SPI + W5500 初始化是否成功,不做任何网络通信。 **文件:`stm32/05_ethernet_test_register/User/main.c`** ```c #include "usart.h" #include "eth.h" int main(void) { USART_Init(); printf("尚硅谷以太网实验:初始化硬件建立\n"); ETH_Init(); printf("\n以太网初始化完成!\n"); while (1) {} } ``` `eth.c` 执行:SPI 初始化 → 注册 W5500 回调 → 复位 W5500 → 配置 MAC/IP/掩码/网关。与 `09_ethernet_webserver_register` 中的 `eth.c` 完全一致。 --- ### 06 — TCP Server(寄存器版) **项目路径**:`stm32/06_ethernet_tcp_server_register` W5500 作为 TCP 服务器,监听端口 8080,接收客户端数据并原样返回(Echo Server)。 **文件:`stm32/06_ethernet_tcp_server_register/App/TCP/tcp.h`** ```c #ifndef __TCP_H #define __TCP_H #include "eth.h" #include "socket.h" #define SN 0 #define CLIENT 0 #define SERVER 1 #define ROLE SERVER void TCP_ServerStart(void); void TCP_RecvData(uint8_t buff[], uint16_t *len); void TCP_SendData(uint8_t data[], uint16_t len); #endif ``` **文件:`stm32/06_ethernet_tcp_server_register/App/TCP/tcp.c`** — 状态机驱动 ```c #include "tcp.h" uint8_t clientIP[4]; uint16_t clientPort; void TCP_ServerStart(void) { uint8_t status = getSn_SR(SN); if (status == SOCK_CLOSED) { // 关闭状态 → 创建 TCP socket,端口 8080 int8_t n = socket(SN, Sn_MR_TCP, 8080, SF_TCP_NODELAY); if (n == SN) printf("socket %d 打开成功!\n", SN); else printf("socket %d 开失败,返回码:%d\n", SN, n); } else if (status == SOCK_INIT) { // INIT 状态 → 开始监听 int8_t res = listen(SN); if (res == SOCK_OK) printf("socket %d 监听成功!\n", SN); else printf("socket %d 监听失败,返回码:%d\n", SN, res); } else if (status == SOCK_ESTABLISHED) { // 已建立连接 → 提取客户端 IP/端口 if (getSn_IR(SN) & Sn_IR_CON) { getSn_DIPR(SN, clientIP); clientPort = getSn_DPORT(SN); printf("客户端连接成功!IP: %d.%d.%d.%d, Port: %d\n", clientIP[0], clientIP[1], clientIP[2], clientIP[3], clientPort); setSn_IR(SN, Sn_IR_CON); } } else if (status == SOCK_CLOSE_WAIT) { // 客户端断开 → 关闭 socket 等待重连 printf("失去客户端的连接,准备关闭socket重新打开...\n"); close(SN); } } void TCP_RecvData(uint8_t buff[], uint16_t *len) { uint8_t status = getSn_SR(SN); if (status == SOCK_ESTABLISHED && (getSn_IR(SN) & Sn_IR_RECV)) { setSn_IR(SN, Sn_IR_RECV); *len = getSn_RX_RSR(SN); recv(SN, buff, *len); } } void TCP_SendData(uint8_t data[], uint16_t len) { uint8_t status = getSn_SR(SN); if (status == SOCK_ESTABLISHED) { send(SN, data, len); } } ``` > W5500 Socket API 是状态机模式。`socket()` → `listen()` → 等待 `SOCK_ESTABLISHED` → `recv()`/`send()`。每个函数执行一步状态迁移,主循环中反复调用。 **文件:`stm32/06_ethernet_tcp_server_register/User/main.c`** — 主循环 ```c #include "usart.h" #include "eth.h" #include "tcp.h" uint8_t rxBuff[1024]; uint16_t rxLen; int main(void) { USART_Init(); printf("尚硅谷以太网实验:TCP Server\n"); ETH_Init(); while (1) { TCP_ServerStart(); TCP_RecvData(rxBuff, &rxLen); if (rxLen > 0) { printf("收到数据:%.*s\n", rxLen, rxBuff); TCP_SendData(rxBuff, rxLen); // 原样返回 rxLen = 0; } } } ``` --- ### 07 — TCP Client(寄存器版) **项目路径**:`stm32/07_ethernet_tcp_client_register` W5500 作为 TCP 客户端,主动连接服务器(192.168.44.53:8888),发送初始消息后收发数据。 **文件:`stm32/07_ethernet_tcp_client_register/App/TCP/tcp.h`** — 新增 `TCP_ClientStart` 声明 ```c #define CLIENT 0 #define SERVER 1 #define ROLE CLIENT // 角色切换为客户端 void TCP_ServerStart(void); void TCP_ClientStart(void); // 新增 void TCP_RecvData(uint8_t buff[], uint16_t *len); void TCP_SendData(uint8_t data[], uint16_t len); ``` **文件:`stm32/07_ethernet_tcp_client_register/App/TCP/tcp.c`** — 新增 `TCP_ClientStart` ```c // 目标服务器 IP 和端口 uint8_t serverIP[4] = {192, 168, 44, 53}; uint16_t serverPort = 8888; void TCP_ClientStart(void) { uint8_t status = getSn_SR(SN); if (status == SOCK_CLOSED) { // 创建 TCP socket,本地端口 9999 int8_t n = socket(SN, Sn_MR_TCP, 9999, SF_TCP_NODELAY); if (n == SN) printf("socket %d 打开成功!\n", SN); else printf("socket %d 开失败,返回码:%d\n", SN, n); } else if (status == SOCK_INIT) { // INIT 状态 → 连接服务器 int8_t res = connect(SN, serverIP, serverPort); if (res == SOCK_OK) { printf("客户端连接服务器成功!\n"); TCP_SendData("Hello, this is STM32 TCP Client!", 32); } else { printf("客户端连接服务器失败,返回码: %d\n", res); } } else if (status == SOCK_CLOSE_WAIT) { printf("失去服务端的连接,准备关闭socket重新打开...\n"); close(SN); } } ``` > 客户端状态机:`socket()` → `connect(serverIP, serverPort)` → 等待 `SOCK_ESTABLISHED`。`TCP_RecvData()`/`TCP_SendData()` 与服务器版完全相同。 **文件:`stm32/07_ethernet_tcp_client_register/User/main.c`** — 主循环调用 `TCP_ClientStart` ```c #include "usart.h" #include "eth.h" #include "tcp.h" uint8_t rxBuff[1024]; uint16_t rxLen; int main(void) { USART_Init(); printf("尚硅谷以太网实验:TCP Client\n"); ETH_Init(); while (1) { TCP_ClientStart(); // 调用 Client 而非 Server TCP_RecvData(rxBuff, &rxLen); if (rxLen > 0) { printf("收到数据:%.*s\n", rxLen, rxBuff); TCP_SendData(rxBuff, rxLen); rxLen = 0; } } } ``` --- ### 08 — UDP 通信(寄存器版) **项目路径**:`stm32/08_ethernet_udp_register` UDP 无连接通信,W5500 Socket 0 绑定端口 9999,收发数据时不维护连接状态。 **文件:`stm32/08_ethernet_udp_register/App/UDP/udp.h`** ```c #ifndef __UDP_H #define __UDP_H #include "eth.h" #include "socket.h" #define SN 0 void UDP_Start(void); void UDP_RecvData(uint8_t buff[], uint16_t *len, uint8_t *srcIP, uint16_t *srcPort); void UDP_SendData(uint8_t data[], uint16_t len, uint8_t *dstIP, uint16_t dstPort); #endif ``` **文件:`stm32/08_ethernet_udp_register/App/UDP/udp.c`** ```c #include "udp.h" void UDP_Start(void) { uint8_t status = getSn_SR(SN); if (status == SOCK_CLOSED) { // 创建 UDP socket,协议 Sn_MR_UDP int8_t n = socket(SN, Sn_MR_UDP, 9999, 0); if (n == SN) printf("socket %d 打开成功!\n", SN); else printf("socket %d 开失败,返回码:%d\n", SN, n); } } void UDP_RecvData(uint8_t buff[], uint16_t *len, uint8_t *srcIP, uint16_t *srcPort) { uint8_t status = getSn_SR(SN); if (status == SOCK_UDP && (getSn_IR(SN) & Sn_IR_RECV)) { setSn_IR(SN, Sn_IR_RECV); uint16_t tmp = getSn_RX_RSR(SN); if (tmp > 8) { // > 8 字节说明有实际数据(前 8 字节为 UDP 头) *len = tmp - 8; recvfrom(SN, buff, *len, srcIP, srcPort); // 获取数据 + 来源 IP/端口 } } } void UDP_SendData(uint8_t data[], uint16_t len, uint8_t *dstIP, uint16_t dstPort) { uint8_t status = getSn_SR(SN); if (status == SOCK_UDP) { sendto(SN, data, len, dstIP, dstPort); // 指定目标 IP/端口发送 printf("发送完毕,数据: %.*s\n", len, data); } } ``` > UDP 与 TCP 的关键差异:`recvfrom()` 会返回发送者的 IP 和端口(用于应答);`sendto()` 需指定目标 IP/端口;协议常量用 `Sn_MR_UDP`。 **文件:`stm32/08_ethernet_udp_register/User/main.c`** ```c #include "usart.h" #include "eth.h" #include "udp.h" uint8_t rxBuff[1024]; uint16_t rxLen; uint8_t srcIP[4]; uint16_t srcPort; int main(void) { USART_Init(); printf("尚硅谷以太网实验:UDP通信\n"); ETH_Init(); while (1) { UDP_Start(); UDP_RecvData(rxBuff, &rxLen, srcIP, &srcPort); if (rxLen > 0) { printf("收到数据:%.*s\n", rxLen, rxBuff); UDP_SendData(rxBuff, rxLen, srcIP, srcPort); rxLen = 0; } } } ``` --- ### 09 — HTTP Web Server(寄存器版) **项目路径**:`stm32/09_ethernet_webserver_register` Web Server 代码(`App/Web/web_server.h/.c`、`Interface/Ethernet/eth.h/.c`)已在之前的"实验:W5500 TCP Server"小节完整展示。此处仅展示主函数。 **文件:`stm32/09_ethernet_webserver_register/User/main.c`** ```c #include "usart.h" #include "eth.h" #include "web_server.h" int main(void) { USART_Init(); printf("尚硅谷以太网实验:Web Server\n"); ETH_Init(); WebServer_Init(); // 注册 HTML 页面 + LED 初始化 while (1) { WebServer_Start(); // 轮询所有 8 个 socket 的 HTTP 请求 } } ``` > `WebServer_Init()` 中注册的 HTML 页面包含"开灯""关灯""翻转"三个按钮,通过 URL 参数 `?action=1/2/3` 控制开发板上的 LED。这是典型的嵌入式 Web 控制面板模式。 --- ## 核心函数速查表(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/Deselect` 和 `SPI_ReadWrite`,并注册到库中 5. **Socket 资源不足** → W5500 最多 8 个 Socket,每个需要分配 Tx/Rx 缓冲区,注意 32KB 的总量分配 6. **HTTP 响应只发一次** → HTTP/1.1 默认为 keep-alive,如需短连接在响应头加 `Connection: close`