news 2026/9/12 11:56:17

STC单片机模拟SPI驱动W5500:TCP服务端实现与调试全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STC单片机模拟SPI驱动W5500:TCP服务端实现与调试全攻略

简介:STC单片机+W5500模块服务端模式例程,面向嵌入式开发者,展示如何在STC平台上通过W5500以太网控制器实现TCP/UDP服务端程序,并采用模拟SPI方式解决硬件SPI资源不足的问题。例程包内含12个文件,主要包括C源码、头文件、Keil工程配置文件及备份文件,压缩包大小仅44KB,结构紧凑便于移植学习,已有630人学习/下载。包内提供了完整的服务端初始化流程,包括IP配置、端口监听、中断处理和数据收发函数;同时,模拟SPI实现可帮助开发者理解在缺少SPI外设时如何通过GPIO模拟时序完成与W5500的可靠通信。对于希望将STC单片机接入以太网、实现HTTP或FTP服务器的项目,这份例程提供了可参考的工程框架与排错思路,适合具备一定单片机基础的开发者借鉴。

1. 把 STC 的普通 IO 拉成 W5500 要的 SPI:什么时候必须上模拟 SPI

一块 W5500 模块,一片 STC 单片机,两者之间不接硬件 SPI 引脚,而是用四根普通 GPIO 一位一位把时钟和数据“捅”出来——这不是偷懒,是很多 STC 项目里唯一现实的接法。STC 这一系里带硬件 SPI 的型号不算少,但引脚往往和下载口、外部中断、串口复用,真正留给以太网模块的常常只剩几个 IO。W5500 的 SPI 从机接口偏偏对时序非常宽容,SCK 不连续、频率低都能正常工作,于是软件模拟 SPI 反而成为稳定且通用的方案。这篇文章把 STC 单片机 + W5500 模块的服务端模式例程拆开讲:模拟 SPI 的帧结构、Socket 状态机、最小代码骨架、调试路径,以及从例程搬到真实工程时要处理的细节。目标是让一块 STC89C52 或者 STC15 系列单片机,接上裸 W5500 模块,就能被局域网内的 PC 直接 TCP 连接上来回收发数据。

2. W5500 服务端模式的底层准备:帧结构、寄存器与 Socket 状态机

2.1 W5500 的 SPI 帧:2 字节地址、1 字节控制,然后是数据

W5500 的 SPI 访问方式和 W25Qxx 这类 SPI Flash 不一样。它每一帧都由固定的三段组成:16 位地址、8 位控制字节、N 字节数据。地址不是某个寄存器内部的位偏移,而是整个芯片统一编址的空间,靠控制字节里的“块选择”字段决定这次访问落在通用寄存器、某个 Socket 的寄存器、还是 TX/RX 缓冲区。

控制字节是理解 W5500 的关键。bit7~bit5 选块,bit4 决定读写方向,bit3~bit2 是数据长度模式。工程里最常用的是可变数据长度模式,也就是 CS 拉低多久,数据段就传多久,收完最后一字节再把 CS 拉高,一帧结束。

bit7~bit5:块选择bit4:读写bit3~bit2:长度模式bit1~bit0
000=通用寄存器,001=Socket0 寄存器,010=Socket1 寄存器,011=Socket2,100=Socket3,101=TX 缓冲,110=RX 缓冲1=读,0=写00=可变数据长度(CS 控制帧长),01=固定数据长度(数据前带 2 字节长度)保留,写 0

组装帧头的代码在模拟 SPI 里就三行:

void w5500_frame_head(unsigned char block, unsigned int addr, unsigned char rw) { spi_write_byte((unsigned char)(addr >> 8)); // 地址高 8 位先发 spi_write_byte((unsigned char)(addr & 0xFF)); // 再发低 8 位 spi_write_byte((block << 5) | (rw << 4)); // 控制字节:块 + 读写方向 }

这里rw传 1 表示读,传 0 表示写。W5500 是 MSB first,地址高位在前,控制字节里的块选择也要左移到位。固定数据长度模式在控制字节后还要再跟 2 字节长度,例程用不上,不需要管。

2.2 服务端模式的 Socket 状态机:OPEN、LISTEN、ESTABLISHED

服务端模式的本质,是把 W5500 的某个 Socket 配成 TCP,然后执行 OPEN 打开端口,LISTEN 进入监听,等待客户端的三次握手完成,状态跳到 ESTABLISHED 之后才能收发数据。这些动作不是调库函数,而是往 Sn_CR 写命令码,再读 Sn_SR 确认状态。

Sn_CR 的每个命令执行完后会被硬件自动清零,所以标准写法是写完命令后轮询它回到 0,再继续下一步。Sn_SR 是只读状态,不会自动跳变,要用程序轮询。

Sn_CR 命令码命令Sn_SR 状态值状态含义
0x01OPEN0x00SOCK_CLOSED
0x02LISTEN0x13SOCK_INIT
0x08DISCON0x14SOCK_LISTEN
0x10CLOSE0x17SOCK_ESTABLISHED
0x20SEND0x1CSOCK_CLOSE_WAIT
0x40RECV

很多第一次写服务端的人,会在客户端断开后卡住,就是因为只处理了 ESTABLISHED 一个状态。客户端正常断开时 Socket 会先进 CLOSE_WAIT,必须对 W5500 执行 DISCON 回到 INIT,再 CLOSE 回到 CLOSED,最后重新 OPEN + LISTEN,否则第二次连接永远建立不起来。

2.3 模拟 SPI 的时序取舍与 STC 引脚处理

模拟 SPI 在代码层面只有四件事:拉高 SCK、拉低 SCK、置 MOSI、读 MISO。W5500 支持 SPI 模式 0 和模式 3,模拟 SPI 固定用模式 0:SCK 空闲为低,上升沿采样数据。模式 3 是空闲为高,如果之前写过别的器件代码,最容易出错的就是这一条。

STC 的 IO 模式要单独处理。STC89C52 这类经典 51 只有准双向口,MISO 读之前必须先写 1,否则读回来全是 0。STC15、STC8 系列可以通过 PnM0/PnM1 寄存器把 SCK、MOSI、CS 配成推挽输出,MISO 保持准双向输入:

// STC15/STC8 系列:P1.0(SCK)、P1.1(MOSI)、P1.3(CS) 配推挽, // P1.2(MISO) 保持准双向输入,读之前写 1 P1M0 = 0x0B; // 对应 bit0、bit1、bit3 推挽 P1M1 = 0x00;

模拟 SPI 的速率用 12MHz 的 STC89C52 算一下:一条_nop_()约 1us,一个 bit 至少 4~6us,一个字节约 40~50us,读一次 Sn_SR 的完整帧是 3 字节头加 1 字节数据,约 200us。这意味着每秒可以轮询几千次 Socket 状态,对 TCP 服务端来说完全够用。真正要留意的是别在模拟 SPI 函数里穿插其他耗时操作,否则一帧被拆成两半,W5500 会把后面的数据当成新帧解析。

3. 用模拟 SPI 把 W5500 跑成 TCP 服务端:最小例程的完整骨架

3.1 模拟 SPI 底层读写函数:先保证 VERSIONR 读回 0x04

代码按 Keil C51 的写法给出,重点是寄存器流程。底层就两个函数:写一个字节、读一个字节。写字节时 SCK 拉低后先放 MOSI,再拉高让 W5500 在上升沿采样;读字节时相反,先拉高 SCK 等 MISO 稳定,再采样。

#include <reg52.h> #include <intrins.h> sbit W5500_RST = P1^4; sbit W5500_SCK = P1^0; sbit W5500_MOSI = P1^1; sbit W5500_MISO = P1^2; sbit W5500_CS = P1^3; #define BLOCK_COMMON 0 // 通用寄存器块 #define BLOCK_SOCK0 1 // Socket0 寄存器块 #define BLOCK_TXBUF 5 // TX 缓冲区 #define BLOCK_RXBUF 6 // RX 缓冲区 static void spi_delay(void) { _nop_(); _nop_(); // 12MHz 下约 2us,主频更高时适当加长 } void spi_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { W5500_SCK = 0; W5500_MOSI = (dat & 0x80) ? 1 : 0; dat <<= 1; spi_delay(); W5500_SCK = 1; // 上升沿,W5500 采样 MOSI spi_delay(); } W5500_SCK = 0; } unsigned char spi_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { W5500_SCK = 1; spi_delay(); // 等 MISO 稳定 dat <<= 1; if (W5500_MISO) dat |= 0x01; W5500_SCK = 0; // 下降沿,W5500 切换下一位 spi_delay(); } return dat; }

读函数里每次先拉高 SCK 再读,是因为 W5500 在下降沿之后更新 MISO,等到 SCK 回到高电平并延时后,数据线已经稳定。如果读出来全是 0xFF 或者固定 0x00,先检查 MISO 是不是接对,再检查读之前有没有把 MISO 引脚写 1。

基于这两个函数,封装寄存器读写。每次访问都是 CS 拉低、发 3 字节头、传数据、CS 拉高:

void w5500_write_reg(unsigned char block, unsigned int addr, unsigned char dat) { W5500_CS = 0; spi_write_byte((unsigned char)(addr >> 8)); spi_write_byte((unsigned char)(addr & 0xFF)); spi_write_byte(block << 5); // 写命令,可变数据长度 spi_write_byte(dat); W5500_CS = 1; } unsigned char w5500_read_reg(unsigned char block, unsigned int addr) { unsigned char val; W5500_CS = 0; spi_write_byte((unsigned char)(addr >> 8)); spi_write_byte((unsigned char)(addr & 0xFF)); spi_write_byte((block << 5) | 0x10); // 读命令 val = spi_read_byte(); W5500_CS = 1; return val; }

一个关键点是 CS 必须在整个帧期间保持低电平,不能在地址、控制字节、数据之间拉高。模拟 SPI 时如果误把每个字节都包一次 CS 拉高拉低,W5500 会把每个字节当成新帧的地址,寄存器读写全部错乱。

调通底层的验证方法很简单,读版本寄存器 VERSIONR,地址是通用寄存器块的 0x0039,W5500 常见返回值是 0x04:

unsigned char ver = w5500_read_reg(BLOCK_COMMON, 0x0039); // ver 应该等于 0x04,否则说明 SPI 时序、接线或复位有问题

3.2 网络参数与 Socket 初始化:MAC、IP、端口一次配好

W5500 上电后要先把网关、子网掩码、MAC、本机 IP 写进通用寄存器。这些寄存器都是多字节,全部用可变数据长度模式一帧写完。注意 W5500 是大端字节序,IP 地址的 192 要放在高字节。

void w5500_net_init(void) { unsigned char mac[6] = {0x00, 0x08, 0xDC, 0x01, 0x02, 0x03}; unsigned char ip[4] = {192, 168, 1, 200}; unsigned char gw[4] = {192, 168, 1, 1}; unsigned char mask[4] = {255, 255, 255, 0}; w5500_write_burst(BLOCK_COMMON, 0x0001, gw, 4); // GAR w5500_write_burst(BLOCK_COMMON, 0x0005, mask, 4); // SUBR w5500_write_burst(BLOCK_COMMON, 0x0009, mac, 6); // SHAR w5500_write_burst(BLOCK_COMMON, 0x000F, ip, 4); // SIPR }

多字节 Burst 写函数就是把单字节写循环套进同一个 CS 帧里,地址只发一次:

void w5500_write_burst(unsigned char block, unsigned int addr, unsigned char *buf, unsigned int len) { unsigned int i; W5500_CS = 0; spi_write_byte((unsigned char)(addr >> 8)); spi_write_byte((unsigned char)(addr & 0xFF)); spi_write_byte(block << 5); for (i = 0; i < len; i++) { spi_write_byte(buf[i]); } W5500_CS = 1; }

Socket0 的 TCP 服务端初始化顺序是固定的:先写 Sn_MR 为 TCP 模式,再写本地端口 Sn_PORT,然后写 Sn_CR 执行 OPEN,等 Sn_CR 清 0,再等 Sn_SR 变成 SOCK_INIT,最后写 LISTEN 命令,等状态变成 SOCK_LISTEN。端口号的字节序同样是大端,5000 要拆成高字节 0x13、低字节 0x88。

void tcp_server_start(unsigned char sock, unsigned int port) { w5500_write_reg(BLOCK_SOCK0 + sock, 0x0000, 0x01); // Sn_MR = TCP w5500_write_reg(BLOCK_SOCK0 + sock, 0x0004, (unsigned char)(port >> 8)); w5500_write_reg(BLOCK_SOCK0 + sock, 0x0005, (unsigned char)(port & 0xFF)); w5500_write_reg(BLOCK_SOCK0 + sock, 0x0001, 0x01); // Sn_CR = OPEN while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0001) != 0); while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0003) != 0x13); // SOCK_INIT w5500_write_reg(BLOCK_SOCK0 + sock, 0x0001, 0x02); // Sn_CR = LISTEN while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0001) != 0); while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0003) != 0x14); // SOCK_LISTEN }

每个命令后等 Sn_CR 清 0 这一步不能省,尤其是 OPEN 后马上写 LISTEN,如果 Sn_CR 还在执行中,LISTEN 命令会被吞掉,Socket 就停在 SOCK_INIT 永远不进监听。

3.3 服务端主循环:Listen、收发与断线重接

进入监听后主循环只需要做三件事:查 Sn_SR 看当前状态,ESTABLISHED 就收发数据,CLOSE_WAIT 就做断开清理,CLOSED 就重新初始化。收发数据的核心是四个 16 位寄存器:发送前查 Sn_TX_FSR 剩余空间,写完数据后更新 Sn_TX_WR;接收前查 Sn_RX_RSR 可读字节数,读完更新 Sn_RX_RD 并执行 RECV 命令释放缓冲。

unsigned int g_tx_wr = 0; unsigned int g_rx_rd = 0; unsigned int tcp_send(unsigned char sock, unsigned char *buf, unsigned int len) { unsigned int free_size; free_size = w5500_read16(BLOCK_SOCK0 + sock, 0x0020); // Sn_TX_FSR if (free_size < len) return 0; w5500_write_burst(BLOCK_TXBUF, g_tx_wr, buf, len); g_tx_wr += len; w5500_write16(BLOCK_SOCK0 + sock, 0x0024, g_tx_wr); // Sn_TX_WR w5500_write_reg(BLOCK_SOCK0 + sock, 0x0001, 0x20); // Sn_CR = SEND while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0001) != 0); return len; } unsigned int tcp_recv(unsigned char sock, unsigned char *buf, unsigned int len) { unsigned int rx_size; rx_size = w5500_read16(BLOCK_SOCK0 + sock, 0x0026); // Sn_RX_RSR if (rx_size == 0) return 0; if (rx_size > len) rx_size = len; w5500_read_burst(BLOCK_RXBUF, g_rx_rd, buf, rx_size); g_rx_rd += rx_size; w5500_write16(BLOCK_SOCK0 + sock, 0x0028, g_rx_rd); // Sn_RX_RD w5500_write_reg(BLOCK_SOCK0 + sock, 0x0001, 0x40); // Sn_CR = RECV while (w5500_read_reg(BLOCK_SOCK0 + sock, 0x0001) != 0); return rx_size; }

w5500_read16w5500_write16是对 16 位寄存器的封装,先读高字节再读低字节,和 W5500 的大端序保持一致:

unsigned int w5500_read16(unsigned char block, unsigned int addr) { unsigned int val; val = (unsigned int)w5500_read_reg(block, addr) << 8; val |= w5500_read_reg(block, addr + 1); return val; }

主循环里把状态分支写清楚。回显是最容易验证的服务端行为:收到什么就原样发回去。

void main(void) { unsigned char status; unsigned char rx_buf[64]; unsigned int n; w5500_reset(); // RST 拉低 50ms,释放后延时 200ms w5500_net_init(); tcp_server_start(0, 5000); while (1) { status = w5500_read_reg(BLOCK_SOCK0, 0x0003); // Sn_SR if (status == 0x17) { // SOCK_ESTABLISHED n = tcp_recv(0, rx_buf, sizeof(rx_buf)); if (n > 0) { tcp_send(0, rx_buf, n); // 回显 } } else if (status == 0x1C) { // SOCK_CLOSE_WAIT w5500_write_reg(BLOCK_SOCK0, 0x0001, 0x08); // DISCON while (w5500_read_reg(BLOCK_SOCK0, 0x0001) != 0); w5500_write_reg(BLOCK_SOCK0, 0x0001, 0x10); // CLOSE while (w5500_read_reg(BLOCK_SOCK0, 0x0001) != 0); while (w5500_read_reg(BLOCK_SOCK0, 0x0003) != 0x00); tcp_server_start(0, 5000); } else if (status == 0x00) { // SOCK_CLOSED tcp_server_start(0, 5000); } } }

CLOSE_WAIT 分支里,先 DISCON 再 CLOSE 是标准做法。DISCON 让 Socket 从 CLOSE_WAIT 回到 INIT,CLOSE 再把它彻底关成 CLOSED,之后重新走 OPEN + LISTEN。如果不做 DISCON 直接 CLOSE,部分固件版本会卡在中间态。

3.4 关键参数和常见误用,逐条说清

先看tcp_send。发送前必须查 Sn_TX_FSR,这个寄存器表示当前 Socket 的发送缓冲剩余字节数。W5500 每个 Socket 默认有 2KB TX 缓冲,但数据进来不会自动排队,写入时超出剩余空间就会覆盖未发送的数据,表现出来是粘包、乱码、甚至丢失。空间不足时不能阻塞等待,返回值 0 交给上层稍后重试。

tcp_recv里最容易漏的是 RECV 命令。Sn_RX_RSR 表示接收缓冲里有多少字节可读,读走数据后 W5500 不会自动释放这块缓冲,必须把 Sn_RX_RD 更新到读结束位置,再执行 RECV 命令,缓冲才会回收。漏掉这一步,Sn_RX_RSR 会一直保持旧值或越读越乱。

缓冲区指针是 16 位的,环形边界需要自己处理。g_tx_wrg_rx_rd增加到接近 0xFFFF 时,要考虑分段读写,不然数据跨越缓冲区末尾时会写到错误位置。工程里常见的做法是先算剩余到缓冲区末尾的长度,不够就拆两次传。

字节序问题集中在端口号、IP 地址和 16 位寄存器上。Sn_PORT 高字节在前,Sn_TX_WR 高字节在前,IP 地址第一字节是高字节。写错过一次端口,表现是服务端监听在错误端口,PC 端连不上但 Socket 状态又确实在 LISTEN。

4. 服务端连不上、收不到、发不出:按链路逐段定位 W5500 的坑

4.1 第一步永远是 VERSIONR:SPI 链路通不通

拿到模块先别配 IP,先把模拟 SPI 跑通。在主函数最开始读一次版本寄存器:

unsigned char ver = w5500_read_reg(BLOCK_COMMON, 0x0039); // 返回 0x04 说明 SPI 通路正常;读回 0xFF 或 0x00 则先查硬件

读不到 0x04 的常见原因有三类。第一类是接线,MOSI 和 MISO 接反,或者模块上的电平转换方向不对。第二类是 SPI 模式极性错了,SCK 空闲在高电平,上升沿采样改成下降沿,W5500 把数据采在错误的边沿上。第三类是 CS 时序,帧内字节之间 CS 被拉高过,或者 CS 悬空没有默认上拉。用示波器或者逻辑分析仪看 SCK 和 MOSI 是最直接的排查方式,没有仪器就降低spi_delay的延时,很多高速下不稳定的问题降速后立刻暴露。

4.2 链路层通了但连接不上:查状态机与网络参数

VERSIONR 读回 0x04 之后,下一步是 ping。PC 上和 W5500 配同一个网段,比如模块是 192.168.1.200,PC 就配 192.168.1.10,然后:

ping 192.168.1.200

ping 不通,基本是网络参数问题。网关、子网掩码、IP 三个寄存器只要有一个写错,链路层就不通。ping 通了但 TCP 连不上,把焦点转到 Socket 状态上。在 main 循环里把 Sn_SR 读出来看,如果停在 0x13 而不是 0x14,说明 LISTEN 命令没有真正执行成功,最常见是 OPEN 后没等 Sn_CR 清 0 就写了 LISTEN。

还有一种隐蔽情况:Socket 已经进入 ESTABLISHED,但客户端程序没有正常关闭连接就退出了,W5500 在 CLOSE_WAIT 状态一直不处理。新连接进来时它会尝试接入已占用的端口,表现是偶尔能连上、偶尔连不上,或者连接成功后立刻断开。主循环里加 CLOSE_WAIT 分支后这个问题就消失。

现象对照表可以直接拿来当排查清单:

现象最可能原因验证手段
VERSIONR 读不到 0x04接线错误、SPI 极性不对、CS 被拉高过检查 MOSI/MISO 是否接反,降速重试
能读寄存器但 ping 不通IP/掩码/网关配置错误PC 与模块同一网段,核对四个通用寄存器
能 ping 通但连不上Socket 不在 LISTEN 状态读 Sn_SR,停在 0x13 就查 Sn_CR 时序
连上但收不到数据没执行 RECV 命令释放缓冲看 Sn_RX_RSR 是否一直增长不清零
收发乱码字节序错误、缓冲区指针越界检查 16 位寄存器高低字节顺序

4.3 能连但收发异常:缓冲指针、字节序和中断引脚的误导

连上之后收发不对,优先怀疑三个地方。第一个是缓冲指针。发送流程里必须先写 TX 缓冲,再把新的写指针回写到 Sn_TX_WR,最后发 SEND 命令。有人把 Sn_TX_WR 固定写成 0,每次从缓冲头开始发,数据全乱。

第二个是字节序。调试时在 PC 端发 0x01 0x02 0x03 0x04,单片机收回来如果变成 0x04 0x03 0x02 0x01,那不是 SPI 位序错,而是有人在应用层手动翻了字节序。W5500 的收发缓冲不修改数据内容,TCP 是字节流,收到什么就是什么。

第三个坑在中断引脚。W5500 的 INT 引脚在 Socket 有事件时拉低,很多例程用外部中断触发接收。问题是 TCP 数据可以多段到达,INT 只在第一次到达时拉低一次,后面如果单片机没及时处理、缓冲又没满,INT 不会再次触发,数据就压在缓冲里没人读。服务端模式里最稳的还是主循环轮询 Sn_RX_RSR,INT 只用来做快速唤醒,不要当成唯一的数据到达信号。

5. 把服务端例程接到工程里:分时调度、掉线重连与吞吐验证

5.1 把 Socket 状态机放进主循环 tick,避免阻塞业务

例程的 while(1) 是纯轮询,真实工程里不可能让单片机一直耗在 W5500 上。常见做法是把状态机拆成一个 tick 函数,定时器产生 5ms 时基,主循环在每轮任务间隙调用一次。注意不要在定时器中断里直接跑 W5500 的 SPI 读写,模拟 SPI 一帧要几百微秒,关中断太久会破坏其他实时任务,应该只置一个标志位,主循环里处理。

每秒轮询 200 次对 TCP 服务端足够。数据到达后 Sn_RX_RSR 会保持可读字节数,晚几个毫秒读不会丢,只要别让缓冲溢出就行。W5500 的接收缓冲默认 2KB,轮询间隔大时要估算最坏情况下的数据积压量,必要时调大 Sn_RXBUF_SIZE。缓冲配置寄存器影响的是 W5500 片内 SRAM 划分,和单片机 RAM 大小无关,STC 这类小内存单片机反而适合把缓冲尽量留给 W5500。

5.2 心跳、CLOSE_WAIT 与重连策略

W5500 硬件不实现 TCP keepalive,连接是否还活着靠应用层自己判断。服务端侧的心跳一般做两件事:周期性向已连接的客户端发送自定义心跳包,同时记录最后一次收到客户端数据的时间,超过阈值就主动断开重连。心跳包不要影响业务数据,建议单独用一个端口号或者复用现有连接但靠包头标志区分。

重连策略要带退避。客户端拉网线、路由器断电这些情况,W5500 侧不会立刻感知,Sn_SR 可能长时间停留在 ESTABLISHED。连续心跳超时后,执行 DISCON + CLOSE 回到 CLOSED,再重新 OPEN + LISTEN。如果状态机里不加延迟,异常情况下会陷入“重连失败立刻再重连”的死循环,把模拟 SPI 的带宽全部吃掉。

5.3 用 PC 端 Python 脚本验证收发与吞吐

验证服务端例程最简单的方式是 PC 上跑一段 Python 脚本。先测回显,再测重连:

import socket import time s = socket.create_connection(("192.168.1.200", 5000), timeout=3) print("connected") total = 0 t0 = time.time() for i in range(100): s.sendall(b"ping-%03d" % i) data = s.recv(64) total += len(data) dt = time.time() - t0 print("round-trip avg: %.2f ms" % (dt * 1000 / 100)) s.close() time.sleep(2) s2 = socket.create_connection(("192.168.1.200", 5000), timeout=3) print("reconnect ok") s2.close()

往返延迟能稳定在几十毫秒内,说明主循环及时。重连检测要重点看 CLOSE_WAIT 分支是否生效,脚本里 close 之后两秒再连,如果能成功,说明 Socket 状态机正确处理了断开。吞吐方面,模拟 SPI 在 12MHz STC89C52 上能跑到十几 KB/s 量级,大包测试时发送缓冲一旦不够,tcp_send返回 0 的次数会明显增加,这是正常的背压表现,不要靠延长延时硬扛,应该让业务层按返回值做分段发送。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 11:54:16

西门子S7-1200 PLC运料小车控制系统设计与仿真

1. 项目背景与核心需求 在工业自动化领域&#xff0c;物料搬运系统的自动化控制一直是生产线优化的关键环节。基于西门子S7-1200 PLC的运料小车控制系统&#xff0c;通过TIA Portal&#xff08;博途&#xff09;平台实现&#xff0c;能够有效解决传统人工搬运效率低、精度差的问…

作者头像 李华
网站建设 2026/9/12 11:52:53

Qt 5.14.2 aarch64静态交叉编译实战:从configure到部署全流程解析

把 Qt5.14.2 以静态方式交叉编译到 aarch64 架构&#xff0c;这件事听起来像一条命令&#xff0c;实际上踩起来全是坑。前阵子我接手一个嵌入式 Linux 项目&#xff0c;目标板是 ARMv8 开发板&#xff0c;要在上面跑一个 Qt 客户端&#xff0c;既没有桌面环境&#xff0c;也不能…

作者头像 李华
网站建设 2026/9/12 11:52:21

真空干泵磁阻式同步电动机设计与控制优化

1. 项目概述&#xff1a;真空干泵驱动用磁阻式同步电动机真空干泵作为半导体制造、真空镀膜等工业领域的核心设备&#xff0c;其驱动电机性能直接影响系统能效和稳定性。传统感应电机存在效率低、温升高等问题&#xff0c;而磁阻式同步电动机&#xff08;Synchronous Reluctanc…

作者头像 李华
网站建设 2026/9/12 11:51:47

ESP32+FPGA协同调试框架:从AXI接口到Micro-ROS发布

1. 先说结论&#xff1a;CYW240128 驱动例程里没有 ESP32 FPGA 的完整调试代码 这个问题我去年在做一款高精度时间戳采集设备时就踩过——当时拿到 CYW240128 芯片的 SDK 包&#xff0c;第一反应也是“官方例程应该包含主流 MCU 与 FPGA 协同调试的参考”&#xff0c;结果解压…

作者头像 李华
网站建设 2026/9/12 11:51:34

SpringCloud配置加载机制与动态刷新实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 11:50:20

JDBC核心技术解析与高效数据库交互实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华