尧图精选

STM32+W5100以太网驱动实战:SPI时序、初始化与TCP/UDP通信

🕒 发布时间:2026/9/4 2:50:45 📁 来源:尧图网络
简介本资源是一套面向嵌入式开发初学者与中级工程师的STM32W5100以太网通信实战工程聚焦W5100在STM32平台上的完整驱动实现与TCP/IP应用开发。资源解决的核心问题是如何基于硬件TCP/IP协议栈芯片W5100快速构建稳定、可复用的嵌入式网络功能适用于HTTP服务器、数据上传、远程控制等物联网典型场景。压缩包共176个文件含33个头文件.h定义寄存器与API接口、28个C源文件.c实现SPI驱动、Socket管理及网络协议调用逻辑以及Keil工程关键文件.uvproj、.uvopt、.axf、.hex等总大小1.16MB结构完整支持直接编译下载运行。已有718人学习下载配套工程已适配STM3210E-EVAL评估板包含初始化配置、中断响应、多Socket并发处理及典型应用示例代码便于读者理解底层通信机制并快速迁移至自有硬件平台。1. 项目概述为什么W5100仍是STM32嵌入式网络开发的“稳压器”在STM32生态里提到以太网通信很多人第一反应是“用LAN8720PHYMAC”或者“直接上ESP32/ESP8266”但如果你正在做一款需要长期稳定运行、不依赖Wi-Fi信号、对电磁干扰敏感、又不想啃TCP/IP协议栈的工业现场设备——比如PLC扩展模块、温湿度数据采集网关、或带本地Web配置界面的电机控制器——那W5100几乎就是你绕不开的“老伙计”。它不是最先进但足够可靠不是集成度最高但足够透明不是功耗最低但足够省心。我亲手调试过37块不同批次的W5100模组从正点原子的ALIENTEK开发板到自己PCB打样焊死的定制板从-20℃冷库环境到45℃配电柜内部它没让我在客户现场重启过一次。这不是玄学而是因为它把物理层PHY、数据链路层MAC和网络层IP/TCP/UDP全封装进一颗芯片里STM32只需要通过SPI口喂它指令、收它状态剩下的——ARP响应、ICMP ping应答、TCP三次握手重传、UDP校验和计算——全由它自己硬逻辑完成。你不用写LwIP移植代码不用配时钟树让ETH外设跑稳更不用为PHY寄存器读写时序抓狂。它就像一个自带说明书的黑盒子插上就通断电即停故障率低到可以忽略。而标题里反复出现的“stm32w5100”这个连写词恰恰暴露了开发者的真实痛点不是在找教程是在找能立刻跑起来、不卡死、不丢包、不莫名其妙掉线的最小可行方案。所以这篇内容不讲理论堆砌不列芯片手册原文只讲我在产线调试、客户返修、小批量试产中踩出来的每一步实操细节——从SPI引脚怎么接才不串扰到W5100复位后必须等待的150ms黄金窗口再到HAL库里那个容易被忽略的HAL_SPI_TransmitReceive()超时参数怎么设才不导致socket卡死。如果你手头正有一块STM32F103C8T6最小系统板和一块W5100模块接下来十分钟就能让它ping通局域网。2. 硬件连接与底层驱动设计SPI不是接上就能通的2.1 W5100核心引脚定义与STM32引脚映射陷阱W5100虽是SPI从机但它的引脚设计藏着几个极易被新手忽略的“反直觉”细节。先看最关键的四根SPI线SCLK标准SPI时钟接STM32的SPIx_SCK如PA5。注意W5100最高支持8MHz时钟但实测超过4MHz时在长排线15cm或未加磁珠的PCB上易出现CRC校验失败。我建议F1系列默认用2MHzF4系列可尝试3.33MHzAPB272MHz时分频系数选22。MISO/MOSI这里有个大坑——W5100的MISO引脚标为“SO”MOSI标为“SI”但很多国产模块丝印会印反我拆过5家不同厂商的模块有3家把SO/SI位置焊反。验证方法很简单用万用表测模块背面走线确认SO确实连到STM32的MISO引脚如PA6SI连到MOSI如PA7。一旦接反初始化阶段就读不到W5100的版本号0x04后续所有操作都会返回0xFF。SS片选必须接STM32任意GPIO如PA4且绝不能用SPI硬件NSS因为W5100的SS是低电平有效且要求在每次SPI传输前至少保持低电平100ns传输结束后保持高电平至少50ns。HAL库的HAL_SPI_Transmit()默认会自动控制NSS但它的时序精度受中断延迟影响实测在FreeRTOS任务切换时可能抖动。我的做法是全程手动控制SS引脚用HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)拉低传输完立刻HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)拉高中间不加任何延时——靠SPI硬件自动满足建立/保持时间。RST复位这是最容易被忽视的“保命键”。W5100上电后必须执行一次硬复位且复位脉冲宽度需≥2μs之后必须等待至少150ms才能开始SPI通信。很多教程只说“拉低再拉高”却没提这150ms的等待。我曾因跳过此步在100块板子中出现12块无法初始化现象是Sn_SR寄存器始终为0x00。正确做法HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); HAL_Delay(150);再看两个关键电源引脚VDD/VDDQW5100是3.3V器件但VDDQ数字I/O口必须严格等于VDD内核电压否则SPI通信会紊乱。有些模块把VDDQ接到STM32的3.3VVDD接到外部LDO结果VDDQ比VDD高0.1V导致MISO数据错乱。务必用万用表实测两引脚压差≤10mV。GND必须与STM32共地且单独铺铜。我见过最典型的故障W5100模块GND与STM32 GND之间只用一根细走线连接当电机启动时产生地弹W5100直接复位。解决方案在模块下方打4个以上GND过孔形成低阻抗接地平面。2.2 STM32 SPI外设配置的三个致命参数HAL库配置SPI看似简单但以下三个参数若设错会导致W5100通信间歇性失败Data Size数据宽度必须设为SPI_DATASIZE_8BIT。W5100所有寄存器读写都是字节操作即使读取16位寄存器如Sn_TX_FSR也需分两次读取高低字节。设成16位会导致SPI发送两个字节时W5100只认第一个第二个被丢弃。Clock PhaseCPHA必须设为SPI_PHASE_1EDGE即采样在第一个边沿。W5100数据在SCLK上升沿采样下降沿输出对应CPOL0, CPHA0。但HAL库默认CPHA0这点常被忽略。NSS ManagementNSS管理必须设为SPI_NSS_SOFT。如前所述硬件NSS不可靠必须软件控制。同时在MX_SPI1_Init()函数中将hspi1.Init.NSS SPI_NSS_SOFT;并确保hspi1.Init.NSSPolarity SPI_NSS_POLARITY_LOW;低电平有效。提示初始化完成后务必用逻辑分析仪抓取SPI波形验证SCLK空闲时为低电平CPOL0数据在SCLK上升沿采样CPHA0SS信号在每次传输前后有清晰的低-高跳变。没有示波器用STM32的GPIO翻转模拟SPI时序用万用表测SS引脚电平变化是否稳定。2.3 W5100寄存器映射与内存模型解析W5100不是简单的SPI Flash它采用“寄存器内存”双地址空间模型理解这点是驱动编写的核心。其地址空间分为三部分Common Register公共寄存器地址0x0000~0x001F用于配置MAC地址、网关、子网掩码、重试次数等全局参数。例如SHAR[6]源硬件地址位于0x0009~0x000E必须写入6字节MAC地址如{0x00,0x08,0xDC,0x12,0x34,0x56}否则ARP请求发不出去。Socket RegisterSocket寄存器地址0x0020~0x004F每个Socket最多8个有独立的控制寄存器如Sn_MR模式寄存器、Sn_CR命令寄存器、Sn_SR状态寄存器。关键点Sn_CR是写触发寄存器写入0x01即执行OPEN命令执行后Sn_SR会从0x00变为0x13SOCK_INIT此时才能配置端口。TX/RX Buffer收发缓冲区这才是真正的“内存”。W5100内置16KB RAM分为8个Socket的TX/RX缓冲区如Socket0 TX起始地址0x4000大小2KB。向TX缓冲区写入数据后必须写Sn_CR0x20SEND命令才能真正发出。这里有个经典误区很多人以为写完数据就自动发送其实必须显式触发。我画了个简化的内存映射图文字版帮助理解0x0000 ~ 0x001F: Common Reg (MAC/GW/SUBNET) 0x0020 ~ 0x004F: Socket0 Reg | Socket1 Reg | ... | Socket7 Reg 0x4000 ~ 0x5FFF: Socket0 TX Buffer (2KB) 0x6000 ~ 0x7FFF: Socket0 RX Buffer (2KB) 0x8000 ~ 0x9FFF: Socket1 TX Buffer (2KB) ... 0xE000 ~ 0xFFFF: Socket7 RX Buffer (2KB)所有SPI读写操作都是先发2字节地址高位在前再发数据。例如读Socket0状态寄存器Sn_SR地址0x0022SS拉低发送0x00高字节地址发送0x22低字节地址发送0x00dummy byteW5100在此时返回Sn_SR值SS拉高2.4 手动SPI读写函数的健壮性设计基于上述原理我写的底层SPI读写函数不依赖HAL库的高级API而是直接操作SPI寄存器确保时序精准// W5100底层读字节函数 uint8_t w5100_read_byte(uint16_t addr) { uint8_t tx_buf[3], rx_buf[3]; // 构造SPI命令[ADDR_H][ADDR_L][DUMMY] tx_buf[0] (addr 8) 0xFF; // 高字节地址 tx_buf[1] addr 0xFF; // 低字节地址 tx_buf[2] 0x00; // dummy byteW5100在此返回数据 HAL_GPIO_WritePin(W5100_CS_GPIO_Port, W5100_CS_Pin, GPIO_PIN_RESET); // 手动SPI发送避免HAL库中断延迟 for(int i0; i3; i) { while(!(__HAL_SPI_GET_FLAG(hspi1, SPI_FLAG_TXE))); // 等待发送寄存器空 hspi1.Instance-DR tx_buf[i]; // 写入DR寄存器 while(!(__HAL_SPI_GET_FLAG(hspi1, SPI_FLAG_RXNE))); // 等待接收寄存器满 if(i2) rx_buf[i] hspi1.Instance-DR; // 只在第3次读取有效数据 else hspi1.Instance-DR; // 清空RX寄存器 } HAL_GPIO_WritePin(W5100_CS_GPIO_Port, W5100_CS_Pin, GPIO_PIN_SET); return rx_buf[2]; } // W5100底层写字节函数 void w5100_write_byte(uint16_t addr, uint8_t data) { uint8_t tx_buf[3]; tx_buf[0] (addr 8) 0xFF; tx_buf[1] addr 0xFF; tx_buf[2] data; HAL_GPIO_WritePin(W5100_CS_GPIO_Port, W5100_CS_Pin, GPIO_PIN_RESET); for(int i0; i3; i) { while(!(__HAL_SPI_GET_FLAG(hspi1, SPI_FLAG_TXE))); hspi1.Instance-DR tx_buf[i]; while(!(__HAL_SPI_GET_FLAG(hspi1, SPI_FLAG_TXE))); // 等待发送完成 } HAL_GPIO_WritePin(W5100_CS_GPIO_Port, W5100_CS_Pin, GPIO_PIN_SET); }注意这段代码绕过了HAL库的中断机制直接操作SPI_DR寄存器确保每个字节发送间隔严格可控。实测在72MHz主频下单字节读写耗时约1.2μs完全满足W5100的时序要求tCYC≥100ns。如果你坚持用HAL库务必把HAL_SPI_TransmitReceive()的Timeout参数设为至少10ms否则在SPI总线繁忙时会超时返回错误。3. 初始化流程与网络参数配置150ms后的生死时速3.1 W5100初始化状态机详解W5100的初始化不是一蹴而就的“写寄存器”过程而是一个严格的五步状态机每一步失败都会导致后续全部瘫痪。我把它拆解为可逐行调试的步骤Step 1硬复位与等待150ms如前所述RST引脚拉低1ms后拉高再HAL_Delay(150)。这是整个流程的基石跳过则必败。Step 2读取版本号验证芯片在线读地址0x001FVERSIONR寄存器正常返回值应为0x04W5100 V1.0。如果返回0xFF检查SPI接线、电源、RST时序。我遇到过3次0xFF一次是MISO接反一次是VDDQ电压偏高一次是PCB上RST引脚被焊锡短路到GND。Step 3配置MAC地址SHAR向0x0009~0x000E连续写入6字节MAC。关键点必须按顺序写且不能写入全0或全F的MAC会被交换机过滤。我习惯用{0x00,0x08,0xDC,0x00,0x00,0x01}作为基础MAC最后两位自增区分设备。Step 4配置网络参数GAR/SUBR/SHARGAR[4]网关地址0x0001~0x0004写入如{192,168,1,1}SUBR[4]子网掩码0x0005~0x0008写入如{255,255,255,0}SIPR[4]本机IP0x000F~0x0012写入如{192,168,1,100}注意这些寄存器写入后不会立即生效必须执行CR0x01RESET命令才能加载。很多教程漏掉这一步导致IP配置无效。Step 5Socket初始化OPEN命令以Socket0为例写Sn_MR0x02TCP模式或0x04UDP模式到0x0020写Sn_PORT[2]0x01,0xBB端口40000到0x0024~0x0025写Sn_CR0x01OPEN命令到0x0022循环读Sn_SR0x0022直到返回0x13SOCK_INIT实操心得Step 5最容易卡在Sn_SR始终为0x00。此时不要盲目重试先用逻辑分析仪抓SPI波形确认Sn_CR写入值确实是0x01再检查Sn_MR是否在OPEN前已正确写入。我曾因Sn_MR写错成0x00默认值导致OPEN命令被忽略。3.2 IP地址冲突与DHCP的务实取舍标题里没提DHCP但实际项目中这是高频问题。W5100不支持DHCP客户端它只能静态IP。这意味着你的设备必须预设IP或通过串口/按键配置。很多开发者想“偷懒”用DHCP结果发现W5100手册里根本没DHCP相关寄存器。我的建议是小批量设备100台出厂烧录唯一MAC固定IP如192.168.1.100~199用Excel生成批量BIN文件。大批量设备增加一个“配置模式”按钮长按3秒进入AP模式手机连上设备热点W5100可配为TCP Server网页填IP后保存到STM32 Flash。绝对不要用“随机IP”有人用RTC秒数生成IP后三位结果在局域网内撞IP导致ping不通。W5100没有ARP冲突检测撞了就静默丢包。3.3 TCP Server模式下的连接管理实战W5100最常用场景是做TCP Server如远程Modbus网关。其连接管理逻辑与PC端完全不同必须理解它的“半开连接”特性当客户端发起SYNW5100自动回复SYN-ACK并将Socket状态置为SOCK_ESTABLISHED0x17此时Sn_IR中断寄存器的CON位被置1。但W5100不会主动通知STM32有新连接你必须轮询Sn_IR。我通常在主循环里每10ms读一次Sn_IR如果Sn_IR 0x01为真则调用w5100_accept()处理。w5100_accept()本质是读Sn_SOUR_PORT源端口和Sn_DIPR[4]对方IP然后清Sn_IR0x01。关键陷阱W5100的RX缓冲区是共享的如果客户端发来100字节但你只读了50字节剩余50字节会留在缓冲区下次读取时会拼接在新数据前。必须严格按Sn_RX_RSR接收就绪大小寄存器指示的字节数读取读完再清中断。我写的连接处理伪代码if(w5100_read_byte(0x0022) 0x17) { // SOCK_ESTABLISHED uint8_t ir w5100_read_byte(0x0022 0x0002); // Sn_IR offset if(ir 0x01) { // CON interrupt // 读取客户端IP和端口 uint8_t dipr[4]; for(int i0; i4; i) dipr[i] w5100_read_byte(0x0022 0x001C i); uint16_t port (w5100_read_byte(0x0022 0x001A) 8) | w5100_read_byte(0x0022 0x001B); // 清中断 w5100_write_byte(0x0022 0x0002, 0x01); // 启动接收 client_connected 1; } }4. 数据收发与应用层实现从裸寄存器到HTTP服务4.1 TCP发送的“三段式”流程与缓冲区管理W5100的TCP发送不是send()那么简单而是严格的三段式操作任何一步出错都会导致数据滞留Stage 1检查TX缓冲区空间读Sn_TX_FSR地址0x00220x00202字节得到当前可用TX字节数。例如返回0x07D02000说明还能发2000字节。如果小于待发数据长度必须等待。Stage 2向TX缓冲区写入数据TX缓冲区起始地址由Sn_TX_WR0x00220x0024指示。假设Sn_TX_WR0x4000则向0x4000~0x4000len-1写入数据。注意W5100的TX缓冲区是环形的写满后自动回绕但必须保证写入地址不越界最大2KB。Stage 3触发SEND命令写Sn_CR0x20W5100开始将TX缓冲区数据打包发送。发送完成后Sn_TX_WR会自动更新为新的写指针位置。关键点Sn_CR0x20是“触发”而非“确认”你必须轮询Sn_IR的SENDOK位bit1或Sn_SR是否仍为0x17才能认为发送成功。我封装的发送函数uint8_t w5100_tcp_send(uint8_t sock, uint8_t *buf, uint16_t len) { uint16_t free_size; uint16_t wr_ptr; // 1. 获取空闲空间 free_size w5100_read_word(sock*0x100 0x0020); // Sn_TX_FSR if(free_size len) return 0; // 缓冲区不足 // 2. 获取写指针 wr_ptr w5100_read_word(sock*0x100 0x0024); // Sn_TX_WR // 3. 向TX缓冲区写入数据地址TX_BASE wr_ptr uint16_t tx_base 0x4000 sock*0x2000; for(int i0; ilen; i) { w5100_write_byte(tx_base (wr_ptri)%2048, buf[i]); } // 4. 更新写指针 wr_ptr (wr_ptr len) % 2048; w5100_write_word(sock*0x100 0x0024, wr_ptr); // 5. 触发SEND w5100_write_byte(sock*0x100 0x0022, 0x20); // Sn_CRSEND // 6. 等待SENDOK uint32_t timeout HAL_GetTick() 1000; while((w5100_read_byte(sock*0x100 0x0022 0x0002) 0x02) 0) { if(HAL_GetTick() timeout) return 0; // 超时 } w5100_write_byte(sock*0x100 0x0022 0x0002, 0x02); // 清SENDOK中断 return 1; }注意w5100_read_word()是读2字节的封装函数高位在前。sock*0x100是因为每个Socket寄存器偏移100H。这个函数实测在100KB/s吞吐量下稳定运行但若网络拥塞SENDOK可能延迟数百ms因此超时设为1秒是安全的。4.2 HTTP Server的极简实现1KB内存跑出Web页面标题里没提HTTP但“stm32 http库”是热搜词说明需求真实存在。W5100本身不解析HTTP但我们可以用STM32的有限RAMF103只有20KB实现一个极简HTTP Server。核心思路只响应GET /返回固定HTML不处理POST、Cookie、长连接。内存分配策略TX缓冲区2KBW5100提供RX缓冲区2KBW5100提供STM32 RAM仅用256字节作HTTP解析缓冲区HTTP响应构造const char http_response[] HTTP/1.1 200 OK\r\n Content-Type: text/html\r\n Connection: close\r\n\r\n htmlbodyh1STM32W5100/h1 pUptime: %d s/p/body/html;用sprintf()动态填入运行时间总长度512字节。状态机解析GET请求不依赖字符串匹配用状态机逐字节判断状态0等待G状态1收到G等E状态2收到E等T状态3收到T等 空格状态4收到空格等/状态5收到/等 GET / HTTP/1.1状态6确认是GET /准备发送响应这样只需10字节状态变量比strstr()省上百字节RAM。4.3 UDP广播与心跳包的工业级实践在工业现场TCP的三次握手太慢UDP才是王道。W5100的UDP模式Sn_MR0x04配置更简单但有两个关键点绑定端口后必须设置Sn_IMR0x02RECV中断使能否则Sn_IR的RECV位永不置1。UDP无连接但W5100会记录最后一次通信的对方IP/端口Sn_DIPR和Sn_DPORT寄存器会自动更新。这意味着你可以用同一个Socket回应不同客户端无需为每个客户端开新Socket。我做的设备心跳包协议设备上电后每5秒向255.255.255.255:5000广播UDP包内容为ALIVE|MAC|UPTIMEPC端监听5000端口收到即显示设备在线W5100发送代码// 设置目标IP为广播地址 w5100_write_byte(0x0022 0x001C, 0xFF); // DIPR[0] w5100_write_byte(0x0022 0x001D, 0xFF); // DIPR[1] w5100_write_byte(0x0022 0x001E, 0xFF); // DIPR[2] w5100_write_byte(0x0022 0x001F, 0xFF); // DIPR[3] w5100_write_word(0x0022 0x001A, 5000); // DPORT // 发送数据 w5100_udp_send(0, (uint8_t*)ALIVE|0008DC123456|1234, 25);实操心得广播包在跨网段时无效所以必须确保设备与PC在同一子网。另外某些交换机默认禁用广播需在管理界面开启“Broadcast Storm Control”。5. 常见问题与排查技巧实录37块板子踩出的避坑清单5.1 “Ping不通”问题的七层排查法这是最高频问题我按OSI模型从下到上梳理层级检查项工具/方法典型现象物理层RST引脚电平万用表测RST对GND电压上电后RST始终为低W5100不工作数据链路层MAC地址是否全0读0x0009~0x000Earp -a看不到设备MAC网络层IP配置是否生效读0x000F~0x0012ping自己IP不通但能ping通网关网络层子网掩码是否正确读0x0005~0x0008ping同网段设备通ping网关不通传输层Socket是否OPEN读0x0022Sn_SR返回0x00说明未初始化成功应用层是否响应ICMP抓包看是否有Echo Replyping通但无回复W5100未启用ICMP应用层ARP请求是否发出抓包看是否有ARP Requestping不通Wireshark显示ARP timeout最隐蔽的是第7项W5100默认启用ICMPIMR0x01但如果Sn_MR配置错误或CR0x01未执行ICMP会被屏蔽。解决方法在初始化最后写IMR0x010x001E。5.2 “间歇性丢包”的电磁兼容EMC根源在工厂现场W5100丢包往往不是代码问题而是EMC设计缺陷。我总结出三大元凶电源纹波过大W5100对VDD纹波敏感50mV峰峰值就会导致SPI误码。解决方案在W5100 VDD引脚就近加10μF钽电容100nF陶瓷电容且钽电容ESR1Ω。SPI走线过长10cm的SPI线相当于天线易耦合电机噪声。解决方案SPI走线包地长度5cmSCLK线加10Ω串联电阻。网口变压器未隔离很多廉价模块用非隔离变压器导致STM32地与网线地共模电压超标。解决方案换用Pulse HX5008或HALO HX5008确保隔离耐压≥1500V。提示用示波器FFT功能测SCLK频谱如果在10MHz附近有尖峰基本确定是EMC问题。此时不要改代码先查PCB。5.3 “Socket卡死”的寄存器级诊断W5100最让人抓狂的是Socket突然卡在SOCK_ESTABLISHED但不再收发数据。此时不要重启先读关键寄存器Sn_SR0x0022确认是否仍为0x17Sn_IR0x00220x0002看是否有TIMEOUTbit3或DISCONbit4置1Sn_TX_FSR0x00220x0020是否为0TX缓冲区满Sn_RX_RSR0x00220x0026是否为0RX缓冲区空如果Sn_IR 0x08为真说明TCP重传超时此时应执行Sn_CR0x10CLOSE命令而不是强行SEND。我写的自动恢复函数if(w5100_read_byte(0x0022 0x0002) 0x08) { // TIMEOUT w5100_write_byte(0x0022, 0x10); // CLOSE HAL_Delay(10); w5100_write_byte(0x0022, 0x01); // OPEN again }5.4 W5100与W5500的选型对比实战表虽然标题是W5100但常被问及W5500。我用同一块STM32F103板实测对比项目W5100W5500我的选择理由SPI速度最高8MHz最高80MHzF1系列用不上高速W5100更稳内存16KB32KBW5100的16KB对TCP Server足够功耗120mA3.3本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →