资讯详情

资讯详情

STM32F103接W5500做TCP:从SPI驱动到断线重连全解析

简介STM32F103与W5500的TCP通信例程面向有嵌入式基础的开发者适用于小型物联网设备与工业数据采集等场景通过SPI驱动W5500硬协议栈芯片完成TCP客户端与服务端的连接建立、数据收发和超时重连。压缩包共183个文件体积约3.48MB主体为45个h头文件和43个c源文件覆盖STM32标准外设库与W5500驱动层另含Keil工程配置、编译脚本及hex烧录文件解压后可直接导入编译运行。目前已有371人浏览学习。代码包含SPI初始化、W5500寄存器配置、IP地址、子网掩码与网关设置、Socket管理和中断处理等关键实现并考虑同一网段的联通要求配合网络调试助手可完成收发验证目录结构便于快速定位TCP服务端和客户端源码适合直接移植到自有硬件平台继续扩展。1. stm32f103接W5500做TCP先弄懂谁打底再谈例程怎么改用STM32F103做网络传输绕不开一个选择软件协议栈还是硬件协议栈。W5500这类硬件TCP/IP芯片把TCP和UDP的活全包了MCU不碰IP分片、校验和、重传这些琐事只通过SPI读写寄存器这对资源紧张的F103来说非常友好。某开发者拿到一套stm32f103W5500的TCP例程目标很明确——让板子通过网线把传感器数据送到服务端。真正上手才发现“例程能跑通”和“例程能长期稳定跑”是两回事寄存器时序、Socket状态轮询、超时重连哪一环没盯住都会翻车。这篇文章把这份TCP例程完整拆开从引脚布局讲到收发状态机再落到实际踩坑照着改就能把demo变成能扛活的通信层。2. 硬件接线与SPI驱动先把W5500从沉默中叫醒2.1 引脚分配SPI1默认口加两个GPIO控制脚先看硬件。W5500对外只有SPI这一条数据通道外加RSTN复位脚和INT中断脚。STM32F103这边我习惯直接用SPI1接线非常固定SCK、MOSI、MISO三根线走硬件SPICS片选用普通GPIO控制不要挂在SPI的NSS上否则以后想换Socket或做多片级联会很痛苦。下表是一份常用的连接方案抄作业时先核对引脚有没有被其他外设占用信号W5500引脚STM32F103引脚说明SCKSCLKPA5SPI1时钟MOSISIPA7主机发从机收MISOSOPA6主机收从机发CSSCSPA4GPIO推挽输出低有效RSTN/RSTPB0复位低有效INT/INTPB1中断输入可不上拉的输入模式W5500是3.3V器件F103的IO也是3.3V信号电平不用转换但一定要共地。电源部分别省芯片VDD脚旁边放一个100nF陶瓷电容加一个10uF钽电容这能挡掉网口变压器耦合过来的大部分电源毛刺。RSTN不要悬空哪怕不用软件复位也要用一个IO或上拉电阻把它固定在高电平。2.2 SPI配置9MHz主模式别漏了NSSF103的SPI1最高能跑到18MHzW5500的极限是33.3MHz所以问题不在上限而在下限。我的习惯是配成9MHz左右留一倍余量给飞线干扰。时钟极性CPOL0、相位CPHA0这是W5500数据手册明确要求的模式0配错的话读回来的寄存器全是0xFF几乎没有例外。static void SPI1_Init(void) { GPIO_InitTypeDef gpio; SPI_InitTypeDef spi; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // SCK/PA5、MOSI/PA7 复用推挽MISO/PA6 浮空输入 gpio.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); gpio.GPIO_Pin GPIO_Pin_6; gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, gpio); // 片选脚 PA4 推挽输出默认高电平 gpio.GPIO_Pin GPIO_Pin_4; gpio.GPIO_Mode GPIO_Mode_Out_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); GPIO_SetBits(GPIOA, GPIO_Pin_4); spi.SPI_Direction SPI_Direction_2Lines_FullDuplex; spi.SPI_Mode SPI_Mode_Master; spi.SPI_DataSize SPI_DataSize_8b; spi.SPI_CPOL SPI_CPOL_Low; spi.SPI_CPHA SPI_CPHA_1Edge; spi.SPI_NSS SPI_NSS_Soft; spi.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; // 36MHz / 8 4.5MHz spi.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, spi); SPI_Cmd(SPI1, ENABLE); }这里把预分频设为8系统时钟36MHz时SPI是4.5MHz稳定优先。SPI_NSS选Soft是为了完全由软件控制CS脚避免硬件NSS在通信中途自己拉高导致帧错乱。只要CS由GPIO操作NSS软件模式就是最省心的选择。分频系数可以根据实际板子的走线质量调整条件好的板子上到18MHz也没问题但飞线调试阶段建议保守一点。2.3 寄存器读写封装一帧搞定地址、模式和数据W5500的SPI帧和普通Flash芯片不太一样每次传输先发两个字节地址再发一个控制字节然后才是数据。控制字节里的OM字段决定访问寄存器块还是数据缓冲区RWB位决定读写方向VDM位置1表示进入可变数据模式这样读多字节时地址会自动递增效率比固定数据模式高很多。static void W5500_CS_Low(void) { GPIO_ResetBits(GPIOA, GPIO_Pin_4); } static void W5500_CS_High(void) { GPIO_SetBits(GPIOA, GPIO_Pin_4); } static uint8_t W5500_ReadByte(void) { uint8_t dummy 0xFF; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, dummy); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); return SPI_I2S_ReceiveData(SPI1); } static void W5500_WriteByte(uint8_t data) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, data); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); SPI_I2S_ReceiveData(SPI1); } static void W5500_ReadFrame(uint16_t addr, uint8_t *buf, uint16_t len, uint8_t block) { uint16_t i; W5500_CS_Low(); W5500_WriteByte(addr 8); W5500_WriteByte(addr 0xFF); W5500_WriteByte(block | 0x04 | 0x01); // 读模式 VDM for (i 0; i len; i) { buf[i] W5500_ReadByte(); } W5500_CS_High(); } static void W5500_WriteFrame(uint16_t addr, const uint8_t *buf, uint16_t len, uint8_t block) { uint16_t i; W5500_CS_Low(); W5500_WriteByte(addr 8); W5500_WriteByte(addr 0xFF); W5500_WriteByte(block | 0x00 | 0x01); // 写模式 VDM for (i 0; i len; i) { W5500_WriteByte(buf[i]); } W5500_CS_High(); }这两个函数是整份驱动的地基。写一个字节也要走完整帧因为W5500只认CS低电平期间的完整地址帧。block参数对应控制字节里的OM字段访问寄存器时传0访问Socket数据缓冲区时传入对应Socket的块索引。很多例程把块索引直接合并到地址里计算逻辑上一样但拆开来写更容易看懂。3. TCP连接建立Socket先钻出来再连远端3.1 Socket打开流程Sn_MR、Sn_PORT、Sn_CR一个都不能少W5500一共有8个Socket每个Socket独立工作TCP例程一般只用其中一个。打开Socket的顺序是固定套路先写Socket模式寄存器Sn_MR为TCP模式再写本地端口号Sn_PORT最后往命令寄存器Sn_CR写OPEN命令。写完OPEN命令后不能立刻做下一步要等待Sn_CR自动清零同时轮询Sn_SR状态寄存器直到它从SOCK_CLOSED变成SOCK_INIT。#define SOCKET_REG_BASE(sock) (0x0400 (sock * 0x0100)) uint8_t W5500_Socket(uint8_t sock, uint8_t protocol, uint16_t port, uint8_t flag) { uint8_t tmp; W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x00, protocol, 1, 0); // Sn_MR tmp port 8; W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x04, tmp, 1, 0); tmp port 0xFF; W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x05, tmp, 1, 0); tmp 0x01; // OPEN W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x01, tmp, 1, 0); do { W5500_ReadFrame(SOCKET_REG_BASE(sock) 0x01, tmp, 1, 0); // 等Sn_CR清零 } while (tmp ! 0x00); W5500_ReadFrame(SOCKET_REG_BASE(sock) 0x03, tmp, 1, 0); // 读Sn_SR if (tmp ! 0x13) return 0; // 不是SOCK_INIT说明打开失败 return 1; }这里的几个偏移量对应W5500的Socket寄存器区偏移0x00是Sn_MR模式寄存器0x01是Sn_CR命令寄存器0x03是Sn_SR状态寄存器0x04和0x05合成16位本地端口号。协议参数protocol传0x01就是TCP。打开成功后Sn_SR会停在0x13也就是SOCK_INIT表示Socket已经准备好但还没开始主动连接。3.2 连接远端IP、端口、CONNECT指令与状态轮询Socket打开后要把目标服务器的IP和端口填进Sn_DIPR和Sn_DPORT然后下发CONNECT命令。这里有个新手常犯的错IP是4字节端口是2字节两者都要按大端序逐字节写入不能直接memcpy一个整形变量进去。F103是32位小端核直接把uint32_t的IP地址按字节写会反导致连到完全错误的地址上。uint8_t W5500_Connect(uint8_t sock, uint8_t *ip, uint16_t port) { uint8_t cmd; uint16_t wait_cnt 0; W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x0C, ip, 4, 0); // Sn_DIPR W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x10, (uint8_t *)port, 2, 0); cmd 0x04; // CONNECT W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x01, cmd, 1, 0); // 轮询Sn_SR限定超时时间防止死循环 while (wait_cnt 3000) { W5500_ReadFrame(SOCKET_REG_BASE(sock) 0x03, cmd, 1, 0); if (cmd 0x17) return 1; // SOCK_ESTABLISHED连接成功 if (cmd 0x00 || cmd 0x15) break; // CLOSED 或 SYNSENT 异常分支 wait_cnt; delay_ms(1); } return 0; }连接后状态会经历SOCK_SYNSENT也就是SYN已发出但还没收到ACK。如果一直停在0x15说明服务器IP或端口不可达。如果退回0x00说明连接过程被对端RST或者网络参数配置错误中断。连接阶段务必加超时计数很多例程直接用while(1)等状态一旦网络异常整个MCU就卡死在连接函数里后续所有任务全部停摆。3.3 超时与重传参数把硬件协议栈的脾气摸清W5500内部有TCP重传机制由两个寄存器控制RTR设置重传超时值单位是100usRMSR是重传超时乘数。默认RTR值是0x07D0换算过来是2000×100us200msRMSR默认是1。也就是说每次发SYN或数据后200ms没收到对端响应就重传一次直到超过重传次数上限才放弃。这套默认参数在局域网里能用但实话实说不太适合工控现场。我一般会把RTR改成0x1388也就是5000×100us500msRMSR保持1减少重传频率。这对跨路由器或经过交换机的场景更友好因为公网链路往返时间经常超过200ms。以下是初始化网络参数和重传参数的写法void W5500_NetworkInit(uint8_t *mac, uint8_t *ip, uint8_t *mask, uint8_t *gw) { uint8_t tmp; // 物理地址不能全零否则对端ARP不回应 W5500_WriteFrame(0x0009, mac, 6, 0); // SHAR // 本机IP、子网掩码、网关 W5500_WriteFrame(0x000F, ip, 4, 0); // SIPR W5500_WriteFrame(0x0005, mask, 4, 0); // SUBR W5500_WriteFrame(0x0001, gw, 4, 0); // GAR // 重传超时设500msRTR写入0x1388 tmp 0x13; W5500_WriteFrame(0x0017, tmp, 1, 0); tmp 0x88; W5500_WriteFrame(0x0018, tmp, 1, 0); // RMSR设为默认倍数1 tmp 0x01; W5500_WriteFrame(0x001B, tmp, 1, 0); }这里的0x0009、0x000F等偏移是W5500通用寄存器区地址例程源码里都有对应的宏定义。IP和网关的字节序都要和点分十进制一致举例说写192.168.1.50时就是四个字节0xC0、0xA8、0x01、0x32依次填入SIPR。MAC地址如果全零交换机不会丢弃帧但服务器发ARP请求时没有设备会回应现象就是TCP始终无法建立连接。4. 数据收发与主循环把例程从能连改成能长期跑4.1 发送函数写指针、SEND命令、等待完成TCP连接建立之后核心就变成数据收发。发送路径看起来绕但抓住三个关键点就能理清查发送缓冲剩余空间、把数据写入发送缓冲区、更新写指针并触发SEND命令。W5500的发送缓冲区大小由Sn_TXBUF_SIZE配置默认每个Socket是2KB所以单次发送超过剩余空间时必须分片。uint8_t W5500_SendTCP(uint8_t sock, const uint8_t *data, uint16_t len) { uint8_t tmp; uint16_t free_size, write_addr; // 1. 查剩余空间 W5500_ReadFrame(SOCKET_REG_BASE(sock) 0x20, (uint8_t *)free_size, 2, 0); free_size (free_size 8) | (free_size 8); // 大小端转换 if (free_size len) return 0; // 2. 读出当前写指针 W5500_ReadFrame(SOCKET_REG_BASE(sock) 0x24, (uint8_t *)write_addr, 2, 0); write_addr (write_addr 8) | (write_addr 8); // 3. 数据写入发送缓冲区块索引按Socket计算 W5500_WriteFrame(write_addr, data, len, 0x08 sock); // 4. 更新写指针 write_addr len; tmp write_addr 8; W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x24, tmp, 1, 0); tmp write_addr 0xFF; W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x25, tmp, 1, 0); // 5. 触发SEND tmp 0x20; W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x01, tmp, 1, 0); do { W5500_ReadFrame(SOCKET_REG_BASE(sock) 0x01, tmp, 1, 0); } while (tmp ! 0x00); return 1; }发送缓冲区访问的块索引从0x08开始是因为W5500把8个Socket的发送缓冲区排在同一地址窗口后面具体块号和寄存器基址的映射关系在例程头文件里有定义不同版本的例程可能写法不同但操作流程一致。free_size是从Sn_TX_FSR读出来的剩余字节数单位是字节。写指针累加后不用做边界取模缓冲区是环形设计芯片内部会自动折回但如果手动改了缓冲区大小这里就要同步调整。4.2 接收路径中断引脚与轮询队列接收路径比发送多一个选择用INT引脚中断通知还是主循环轮询Sn_RX_RSR。例程里常用轮询因为W5500的中断需要读SIR寄存器确认来源还要在每个Socket的Sn_IR里清标志处理不好会漏中断。轮询的代价是实时性差一点但对F103这种主频偏低的MCU来说100ms周期内轮询一次接收计数完全够用。uint16_t W5500_RecvTCP(uint8_t sock, uint8_t *buf, uint16_t max_len) { uint8_t tmp; uint16_t rx_size, read_addr, rd_len; // 1. 读接收数据大小 W5500_ReadFrame(SOCKET_REG_BASE(sock) 0x26, (uint8_t *)rx_size, 2, 0); rx_size (rx_size 8) | (rx_size 8); if (rx_size 0) return 0; rd_len (rx_size max_len) ? max_len : rx_size; // 2. 读当前读指针 W5500_ReadFrame(SOCKET_REG_BASE(sock) 0x28, (uint8_t *)read_addr, 2, 0); read_addr (read_addr 8) | (read_addr 8); // 3. 从接收缓冲区读数据块索引要加0x10 W5500_ReadFrame(read_addr, buf, rd_len, 0x10 sock); // 4. 更新读指针并下发RECV命令释放缓冲区 read_addr rd_len; tmp read_addr 8; W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x28, tmp, 1, 0); tmp read_addr 0xFF; W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x29, tmp, 1, 0); tmp 0x40; // RECV W5500_WriteFrame(SOCKET_REG_BASE(sock) 0x01, tmp, 1, 0); do { W5500_ReadFrame(SOCKET_REG_BASE(sock) 0x01, tmp, 1, 0); } while (tmp ! 0x00); return rd_len; }接收路径上最容易被忽略的是RECV命令。读完数据、更新读指针后必须下发RECV命令否则W5500认为这块缓冲区还没被消费接收计数器Sn_RX_RSR不会减少下一次轮询会读到同样的数据造成重复收包。这个命令和发送端的SEND命令一样写入Sn_CR后要等待它清零。4.3 主循环状态机断开重连的注意力都在这里很多例程的缺陷在于把连接逻辑平铺在while(1)里断了就重新执行一遍初始化。这样在服务器重启或网线拔插的场景下会出问题Socket还停在旧状态重新OPEN可能失败。我习惯把TCP生命周期整理成一个四态状态机每个循环只处理一个状态这样断线重连完全是顺理成章的事。typedef enum { TCP_STATE_IDLE, TCP_STATE_CONNECTING, TCP_STATE_CONNECTED, TCP_STATE_RECONNECT } tcp_state_t; void TCP_MainTask(tcp_state_t *state, uint8_t *server_ip, uint16_t server_port) { uint8_t tmp; switch (*state) { case TCP_STATE_IDLE: if (W5500_Socket(0, 0x01, 5000, 0)) { *state TCP_STATE_CONNECTING; } break; case TCP_STATE_CONNECTING: if (W5500_Connect(0, server_ip, server_port)) { *state TCP_STATE_CONNECTED; } else { *state TCP_STATE_RECONNECT; } break; case TCP_STATE_CONNECTED: W5500_ReadFrame(SOCKET_REG_BASE(0) 0x03, tmp, 1, 0); if (tmp 0x00 || tmp 0x1C) { // CLOSED 或 CLOSE_WAIT需要关闭重连 tmp 0x10; // CLOSE W5500_WriteFrame(SOCKET_REG_BASE(0) 0x01, tmp, 1, 0); *state TCP_STATE_RECONNECT; } break; case TCP_STATE_RECONNECT: // 延时后重新走IDLE流程 delay_ms(2000); *state TCP_STATE_IDLE; break; } }这个状态机的好处是把“连接建立”和“正常收发”解耦RECONNECT状态里可以放心加延时不用担心阻塞收数据。CLOSE_WAIT状态值得单独提一下服务器主动关闭连接时W5500会进入0x1C这时候如果只做重连而不先CLOSE旧Socket的资源不会释放反复几次就会把8个Socket全部占满。5. 避坑排查连不上、丢包、复位失效的五个现场5.1 现象一TCP连接一直停在SYNSENT现象调用CONNECT后Sn_SR长时间停在0x15服务器抓包看不到SYN到达LAN口指示灯偶尔闪一下。原因最常见的是网关地址填错或没填。W5500是硬件协议栈发SYN之前要查ARP表如果网关GAR全零它不知道把帧交给谁重传几次后放弃Sn_SR直接退回CLOSED。另一种情况是MAC地址和局域网里某台设备冲突交换机把帧丢弃。解决先用串口把GAR、SUBR、SIPR、SHAR四个寄存器值打印出来逐字节核对。GAR一定要填路由器LAN口地址不是填服务器地址。MAC冲突的话换一个不常用的地址段比如把最后一字节改成0xF7。改完寄存器后用网线直连电脑禁用电脑无线网卡再试避免无线和有线抢路由表。5.2 现象二SPI读回来的寄存器全是0xFF现象初始化时读Sn_SR返回0xFF甚至读MR寄存器都是0xFF芯片看起来完全没反应。原因这个现象九成是SPI模式配置错误。W5500只认CPOL0、CPHA0也就是模式0。如果例程里复用了其他SPI设备的初始化代码CPOL或CPHA不对第一个字节地址帧就花掉了后边数据全错位。也有一部分原因是CS脚没有彻底拉低帧还没发完CS就拉高了W5500直接丢弃整帧。解决先把SPI模式改成模式0确认三个引脚配置正确。然后用示波器或逻辑分析仪抓SCK和CS的时序看CS低电平期间SCK是否连续产生16个时钟的地址加控制字节。手动触发一次写寄存器抓SPI波形比任何调试打印都直观。5.3 现象三板子复位后W5500不响应现象程序下载完第一次联网正常按复位键后TCP死活连不上重新上电又好了。原因W5500的上电复位时序要求RSTN拉低至少500us后再释放但很多F103例程里把复位脚配置成普通GPIO后立刻拉高芯片内部还在复位态此时SPI初始化已经开始了寄存器写入全部丢失。或者反过来复位拉低时间太长代码已经跑到连接函数芯片还没准备好。解决复位时序单独写一个函数拉低RSTN后delay_ms(10)拉高后再delay_ms(50)然后才开始配置网络参数。另外如果RSTN脚悬空上电瞬间可能因为IO浮空产生毛刺导致芯片随机复位这也是必须用GPIO强驱动或加上拉电阻的原因。5.4 现象四收到数据错位第一个字节被吞现象服务器发来完整JSON数据F103收到的第一个字节丢失或者后面数据整体向前错一位。原因大概率是接收缓冲区跨边界问题。W5500的接收缓冲区是环形结构读指针累加后如果越过缓冲区末尾应该折回起点。部分例程里把写指针和读指针当成线性地址处理没做取模更新读指针后W5500内部解析地址错乱。解决发送函数和接收函数里的指针累加都要做边界处理缓冲区大小2KB对应地址折回点是0x0800。最稳妥的做法是不自己算地址直接读Sn_RX_RD和Sn_TX_WR寄存器芯片会给出当前有效地址。另外单次RECV命令下发前确认Sn_RX_RSR的值等于实际收到的数据长度如果不等说明上次RECV命令没执行完就去读新数据了。5.5 现象五长时间运行后Socket卡死现象设备运行几小时后TCP连接断开重连流程执行了但Sn_SR卡在SOCK_INIT不再继续。原因服务器侧NAT会话超时或对端静默断开W5500这边没收到FINSn_SR停留在ESTABLISHED应用层也没发数据双方都在等对方先说话。等到超时后进入CLOSED但重连时本地端口还绑定在旧会话上高层应用没处理好导致Socket冲突。解决TCP层加保活机制。W5500有SEND_KEEP命令向对端发送Keep-Alive包配合RTR重传超时设置我把保活周期控制在30秒一次连续三次无响应就主动CLOSE。在主循环里做一个计时器超过30秒没发送任何业务数据就触发一次SEND_KEEP这样NAT会话不会过期服务器也能感知设备在线状态。6. 进阶把TCP例程改成透传通道的实用技巧例程跑通只是第一步真正在项目里用还需要把这段代码从“能连”变成“好用”。我最推荐的做法是先改造一层透明的帧收发接口而不是东改一点西改一点把业务逻辑直接塞进TCP中断里。具体做法是在W5500_SendTCP和W5500_RecvTCP外面套一层协议缓冲层维护一个发送队列和接收队列。应用层的传感器数据只往队列里丢主循环的任务周期性把队列内容通过TCP发出收到数据固化成一条完整消息再交给解析函数。这样TCP状态机和业务逻辑完全隔离后续换成UDP或MQTT时只动驱动层不用碰业务代码。另外一个实用技巧是保存一份“最近一次连接参数”到F103内部Flash。设备掉电重启后先从Flash读回上次成功连接的服务器IP和端口跳过DNS和参数重新下发环节直接尝试重连。这个方法在无人值守的工业现场特别有用配合状态机里RECONNECT状态的延时重试基本能做到断电自愈。还有一点是关于DMA的。F103的SPI带DMA但W5500的SPI帧短、命令频繁DMA的启动开销反而可能拖慢小数据量传输。我的建议是单次传输超过32字节的场景用DMA短命令读写保持轮询方式。改造时先量一下当前一次收发占用的主循环时间如果超过中断周期再考虑DMA也不迟。最后说一个我自己的教训。最早拿到类似例程时我把所有代码都堆在一个while循环里连接、发送、接收、重连全搅在一起。后来现场服务器重启一次设备卡在重连循环里再没出来只能派人去断电。从那以后我每次跑通通信例程都强制先把状态机画出来再写功能代码连接和收发必须是独立模块而不是互相嵌套。希望这些拆解和踩坑记录能帮到你让你少走那几段我走过的弯路。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →