tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(高级篇)V2.0.1 — 第2章:以太网 + 配套代码05~09" author: "尚硅谷研究院" date: 2026-07-15
用生活理解:W5500 就像一个自带"同声传译"的网卡芯片——你只需要用 SPI 告诉它"我要连哪台机器、哪个端口",它自己就把 TCP/IP 协议栈这些复杂工作全干了。你的单片机完全不用关心三次握手、重传、分片这些事,只管发/收数据就好。
| OSI 7 层 | TCP/IP 4 层 | 代表协议 | 功能 |
|---|---|---|---|
| 应用层 | 应用层 | HTTP, FTP, MQTT, DNS | 为用户提供网络应用服务 |
| 表示层 | ↓ | SSL/TLS | 数据加密/解密、压缩 |
| 会话层 | ↓ | SOCKS | 建立/管理/终止会话 |
| 传输层 | 传输层 | TCP, UDP | 端到端通信、可靠传输 |
| 网络层 | 网络层 | IP, ICMP, ARP | 路由寻址、分组转发 |
| 数据链路层 | 网络接口层 | Ethernet, MAC | 帧封装、介质访问 |
| 物理层 | ↓ | 以太网物理层 | 比特流传输、信号编码 |
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(确认重传) | 不可靠(最大努力交付) |
| 顺序 | 按序交付 | 不保证顺序 |
| 速度 | 较慢 | 快速 |
| 适用场景 | Web、文件传输、邮件 | 视频流、DNS、IoT 传感器数据 |
| 协议 | 端口 | 功能 |
|---|---|---|
| HTTP | 80 | Web 网页访问 |
| HTTPS | 443 | 加密 Web 访问 |
| DNS | 53 | 域名→IP 解析 |
| DHCP | 67/68 | 自动分配 IP 地址 |
| MQTT | 1883 | IoT 轻量级消息协议 |
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 官网)
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│
└──────────────┘ └──────────┘
SPI 接口 ←→ 寄存器管理单元 ←→ 通用寄存器(模式/MAC/IP等)
←→ Socket 寄存器(8个Socket,各512字节)
←→ Tx/Rx 存储器(32KB 可分配)
←→ TCP/IP 内核(硬件实现)
↓
MAC + PHY ←→ 以太网口
W5500 的 SPI 数据帧与普通 SPI Flash 不同,分为控制字节 + 数据:
帧格式: [地址偏移(1B)] [控制字节(1B)] [数据(N字节)]
控制字节:
[7:5] = 块选择 (000=通用寄存器, 001=Socket寄存器, 010=Tx缓冲区, 011=Rx缓冲区)
[4:2] = 读写模式
[1] = 可变长模式 1=VDM (推荐)
[0] = 1=读, 0=写
Ethernet/ 目录到项目wizchip_conf.h 中的 SPI 接口函数实现以下底层函数:
void SPI_CS_Select(void); // 芯片选择
void SPI_CS_Deselect(void); // 取消片选
uint8_t SPI_ReadWrite(uint8_t byte); // SPI 读写1字节
调用 wizchip_init() 和 wizchip_setnetinfo() 初始化
项目路径: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);
}
}
所有项目共享 Hardware/SPI/spi.h/.c(SPI 驱动,已在上面展示)和 Interface/Ethernet/eth.h/.c(W5500 网络初始化)。
项目路径:stm32/05_ethernet_test_register
仅验证 SPI + W5500 初始化是否成功,不做任何网络通信。
文件:stm32/05_ethernet_test_register/User/main.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 完全一致。
项目路径:stm32/06_ethernet_tcp_server_register
W5500 作为 TCP 服务器,监听端口 8080,接收客户端数据并原样返回(Echo Server)。
文件:stm32/06_ethernet_tcp_server_register/App/TCP/tcp.h
#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 — 状态机驱动
#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 — 主循环
#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;
}
}
}
项目路径: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 声明
#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
// 目标服务器 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
#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;
}
}
}
项目路径:stm32/08_ethernet_udp_register
UDP 无连接通信,W5500 Socket 0 绑定端口 9999,收发数据时不维护连接状态。
文件:stm32/08_ethernet_udp_register/App/UDP/udp.h
#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
#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
#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;
}
}
}
项目路径: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
#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 控制面板模式。
| 函数 | 说明 |
|---|---|
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 状态 |
SPI_CS_Select/Deselect 和 SPI_ReadWrite,并注册到库中Connection: close