STM32F103C8T6与ESP8266实战:利用AT指令配置TCP服务器实现局域网通信
2026/9/9 16:10:35 网站建设 项目流程

简介:STM32F103C8T6与ESP8266联调实现TCP服务器,是物联网嵌入式开发中的典型实践。整套方案以AT指令控制为主线,通过串口1进行用户交互、串口2与ESP8266通信,完整演示ESP8266配置为STA模式并建立TCP服务器的过程,解决开发者对Wi-Fi模块配置不熟、串口与网络协议衔接困难等问题,适合正在学习STM32、无线数据传输或远程控制的朋友参考。

资料包共244个文件,压缩后7.16MB。工程以Keil MDK项目形式组织,包含大量C源码、头文件、编译生成的.o/.axf/.hex以及uvprojx等工程配置文件,另有htm与PDF说明文档,可直接打开编译或烧录验证,目录结构清晰,便于对照源码理解TCP服务器的工作流程。

当前已有2955人浏览学习。通过这套资料可获取完整的TCP服务器STA模式工程源码、AT指令交互逻辑、串口收发与数据解析思路,以及常见调试排错方法,为后续开发更复杂的物联网终端提供扎实参考。 我这两年折腾了不少STM32和WiFi模块的项目,从智能家居网关到小型数据采集终端,踩过的坑能装满一麻袋。今天这篇就专门聊聊那个最经典的组合:STM32F103C8T6最小系统板加ESP8266模块,通过AT指令把ESP8266配置成TCP服务器,实现局域网内的数据收发。这个方案在物联网入门、实验室设备数据上报、简易远程控制场景里非常常见,也是很多人从纯单片机开发转向联网开发的第一道坎。

写这篇文章的起因是最近帮一个做设备运维的朋友解决了一个类似问题,他手头有十几个STM32F103C8T6做的小控制板,希望通过WiFi接入现场局域网,让上位机能够直接访问每块板子的数据。他没有上RTOS,没有用复杂的协议栈,就是用最朴素的串口加AT指令,硬是把TCP服务器跑起来了。这套思路很适合刚接触嵌入式联网的开发者,也适合需要在产线上快速验证联调的场景。

1. 项目整体设计与思路拆解

1.1 为什么选F103C8T6加ESP8266这套组合

先说选型逻辑。STM32F103C8T6是Cortex-M3内核,主频72MHz,64KB Flash,20KB RAM,在目前的市场环境下性价比极高,国产替代型号也已经很成熟。虽然它没有以太网MAC,也没有WiFi射频,但胜在串口资源丰富、生态完善、开发资料多,作为主控做协议解析、IO控制、数据处理完全够用。

ESP8266这边,我用的最多的是ESP-01S或者ESP-12F。它的核心价值在于把完整的WiFi协议栈和TCP/IP协议栈都封装好了,用户只需要通过UART发AT指令就能完成联网、建连、收发数据的操作。这对MCU侧的算力要求极低,F103C8T6只需要管好串口数据流,剩下的网络脏活累活全交给ESP8266。

为什么不直接用ESP8266裸跑固件开发?当然可以,但产品形态不同。如果你要做的系统里还有其他外设需要控制,比如传感器采集、电机驱动、屏幕显示,那MCU主控的角色还是必要的。而且AT指令方案调试方便,串口助手一接就能看到模块在干什么,不用烧录调试固件,也不用折腾编译环境,非常适合快速验证和工程落地。

1.2 TCP服务器模式解决什么问题

很多人刚开始接触ESP8266的时候,习惯用TCP客户端模式去连服务器,比如连OneNET、连巴法云之类的物联网平台。但实际场景里还有一种非常常见需求:设备本身作为服务端,等待上位机或手机APP来连接它。这就是TCP服务器模式。

举个例子,我之前做过一个车间环境监测终端,STM32采集温湿度和粉尘数据,ESP8266开启TCP服务器,车间主任用电脑上的小工具连接这个服务器的IP和端口,就能实时看数据。这种模式下不需要公网服务器中转,局域网内直连,延迟低,安全性也相对可控。另外在产线调试的时候,工程师用网络调试助手直接连设备,比每次拆机接串口方便太多。

从协议层面说,TCP服务器模式需要ESP8266开启多连接功能(AT+CIPMUX=1),因为服务器要能同时挂多个客户端。这一点和TCP客户端模式有本质区别,也是配置AT指令时最容易忽略的地方。

2. 硬件连接与电路设计要点

2.1 最小系统板与ESP8266的接线参考

STM32F103C8T6最小系统板现在很便宜,引脚也基本兼容,我用的是经典的蓝板。和ESP8266通信我建议用USART1,因为PA9和PA10引出方便,而且USART1的时钟在APB2总线上,性能稍好一些。接线表如下:

STM32F103C8T6ESP8266(ESP-01S/ESP-12F)说明
3.3VVCC(或VDD)模块供电,必须3.3V稳压
GNDGND共地
PA9(USART1_TX)RXD注意是交叉连接
PA10(USART1_RX)TXD注意是交叉连接
3.3V(或者直接接VCC)CH_PD(EN/ENABLE)使能引脚,必须拉高
不接GPIO0正常运行时悬空或接高电平

这里有几个硬件上的坑我必须提醒你。

第一,供电问题。ESP8266在WiFi发射瞬间电流可以达到300mA以上,很多最小系统板上的AMS1117-3.3稳压芯片在输入电压偏低的时候会扛不住,导致模块反复重启。我的建议是给ESP8266单独用一片低压差LDO供电,或者至少确保USB转串口供电时用质量好一点的线。如果你发现模块连上WiFi就重启,十有八九是供电问题。

第二,电平匹配。STM32F103C8T6的IO是5V容忍的,但ESP8266的IO不是。如果最小系统板上已经做了电平转换那另说,如果是直接用杜邦线飞线,注意别把5V电源引到ESP8266的VCC上,否则模块会冒烟。串口信号线上,F103的TX输出3.3V电平,可以直接和ESP8266的RXD相连,问题不大。但反过来ESP8266的TXD输出也是3.3V,STM32的RX引脚可以接收,所以逻辑电平基本兼容,不需要额外转换。

第三,CH_PD引脚。这是个老生常谈的点,但每次都有新手在这里翻车。CH_PD必须接高电平,否则模块不工作,AT指令没反应。我习惯把它直接和VCC短接,简单粗暴,省得怀疑人生。

2.2 串口调试环境的准备

在动手写STM32代码之前,强烈建议先用USB转TTL工具把ESP8266单独接电脑上,用串口助手把AT指令全部跑通。很多人跳过这一步直接上MCU,出了问题就很难判断是模块配置问题还是MCU代码问题。

串口调试助手我推荐用SSCOM或者XCOM,波特率设置成115200,注意勾选发送新行(\r\n),这个细节特别关键,ESP8266的AT指令必须要以回车换行结尾,否则模块不识别。如果发现发送AT没有回复OK,依次检查接线、波特率、CH_PD引脚电平、模块是否损坏。

在电脑端配置好TCP服务器之后,你可以先用网络调试助手(或者手机上的TCP工具)连接模块的IP和端口,验证能否收发数据。这一层验证通过,再进入MCU编程阶段,一次成功率会高很多。

3. 核心实操:AT指令配置TCP服务器全流程

3.1 关键指令参数与配置顺序

ESP8266的AT指令固件版本比较多,我以最常见的出厂AT固件(乐鑫官方)为例。标准的TCP服务器配置流程如下:

AT // 测试模块是否正常,返回OK ATE0 // 关闭回显,让返回数据更干净 AT+CWMODE=1 // 设置为Station模式,连接外部路由器 AT+CWJAP="YourSSID","YourPassword" // 连接WiFi,返回WIFI CONNECTED和WIFI GOT IP AT+CIPMUX=1 // 开启多连接模式,TCP服务器必须 AT+CIPSERVER=1,8080 // 建立TCP服务器,端口8080 AT+CIFSR // 查询模块IP地址,返回类似192.168.1.100

这套顺序是固定的,尤其是CIPMUX必须在CIPSERVER之前设置,因为固件在单连接模式下不允许多路监听。如果你先开了服务器再开多连接,会返回ERROR。

还有一个细节是AT+CIPSERVER=1,8080这条指令。第二个参数是端口号,范围是1到65535,但不要用常见的21、23、80这些被占用的端口,建议选8000以上的高位端口,避免和路由器的管理页面端口冲突。我习惯用8266这个数字做端口,好记又不会撞。

3.2 数据收发时的AT指令处理

TCP服务器建立成功后,当有客户端连入,ESP8266会主动上报一条消息,类似:

0,CONNECT

这里的0是连接ID,也就是channel号。多连接模式下,每个客户端对应一个channel,取值范围0到3。客户端断开时会收到:

0,CLOSED

当客户端发来数据时,串口上会出现这样的帧:

+IPD,0,5:hello

这个帧的解析要仔细说明一下。+IPD是固定前缀,逗号后面的第一个数字是连接ID,第二个数字是数据长度,冒号后面是真正的数据内容。注意数据本身可能包含任意字节,不一定是可打印字符,所以MCU端不能简单地按字符串处理,要严格按照长度来截取。

向指定channel发送数据,指令是:

AT+CIPSEND=0,5 // 向channel 0发送5字节数据

模块返回">"提示符后,再发送实际数据,如果发送成功会返回SEND OK。

这里有个比较隐蔽的问题:多连接模式下,模块会在串口上混着输出各种事件通知,比如客户端连接状态变化、+IPD数据帧、指令回复等等。MCU端如果只做一个简单的阻塞式等待,很容易被意料之外的输出打乱节奏。这也是我后面要重点讲的代码设计问题。

4. STM32端代码实现与数据透传

4.1 串口接收的环形缓冲区设计

MCU端最核心的模块是串口接收处理。我用的是标准外设库,也可以用HAL库,思路都是一样的。首先在串口中断里逐字节把数据扔进环形缓冲区,主循环里再统一解析。

#define RING_BUFFER_SIZE 512 typedef struct { uint8_t buffer[RING_BUFFER_SIZE]; volatile uint16_t head; volatile uint16_t tail; } RingBuffer; RingBuffer esp8266_rx; void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART1); uint16_t next_head = (esp8266_rx.head + 1) % RING_BUFFER_SIZE; if (next_head != esp8266_rx.tail) { esp8266_rx.buffer[esp8266_rx.head] = data; esp8266_rx.head = next_head; } // 如果环形缓冲区满了,数据会被丢弃,后续要留打印标志 } }

为什么用环形缓冲区而不是在中断里直接做字符串判断?因为+IPD帧可能有半包的情况,一次中断根本收不完,必须先把数据缓存起来,等到主循环里凑齐完整帧再做解析。我一开始图省事直接在中断里判断,结果数据一多就丢状态,后来老老实实改成缓冲区,世界清静了。

4.2 AT指令应答状态机

发送AT指令不能发完就不管了,必须等待模块回复再继续下一步。我用一个简单的状态机管理配置流程:

typedef enum { CMD_IDLE, CMD_SEND_AT, CMD_SEND_CWMODE, CMD_SEND_CWJAP, CMD_SEND_CIPMUX, CMD_SEND_CIPSERVER, CMD_WAIT_CONNECT } AT_Command_State; volatile uint8_t at_cmd_ack = 0; void AT_SendCommand(const char *cmd) { // 这里通过串口发送指令 USART_SendString(USART1, cmd); USART_SendString(USART1, "\r\n"); }

主循环里不断扫描接收缓冲区的内容,当收到"OK"或者"ERROR"的时候置标志位,状态机再决定发下一条指令。这里要特别注意,不要用strstr简单查找"OK"就完事,因为模块返回的数据里可能出现"SEND OK",如果判断不够精确,可能误触发。我一般会检查行首是不是"OK\r\n",或者至少检查前面不是"ERROR"。

等待WiFi连接这条指令比较特殊,它不会立刻返回OK,而是会先返回WIFI CONNECTED,再返回WIFI GOT IP,最后才返回OK。如果你的代码只等"OK",其实没问题,因为最终它会等到。但如果你想确认是否拿到了IP地址,就要额外解析"WIFI GOT IP"这个关键字。

4.3 +IPD数据帧的解析与处理

接收数据解析是重点中的重点。我写了一个专门的处理函数,在主循环里提取出完整的一行数据再加解析:

void ESP8266_ParseData(void) { // 先检查缓冲区里有没有+IPD前缀 char *pre = find_str_in_buffer(esp8266_rx.buffer, "+IPD,"); if (NULL == pre) { return; } // 提取连接ID,也就是+IPD后面的第一个数字 uint8_t channel = pre[5] - '0'; // 找到冒号位置,冒号之前是长度 char *colon = find_char(pre, ':'); if (NULL == colon) { return; } // 将长度字符串转换成数字 int len = atoi_str(pre + 7, colon); // 复制数据内容 memcpy(app_rx_buf, colon + 1, len); app_rx_len = len; // 调用业务处理函数 ProcessAppData(channel, app_rx_buf, app_rx_len); }

代码里用了一些自封装的字符串函数,你实际写的时候可以直接用标准库的strstr和atoi,但要注意处理缓冲区中的溢出问题。

解析+IPD帧有个关键点:如果你在客户端发送完数据立刻判断,可能第一次收到的只有部分是+IPD头部,数据还没到齐。我的处理方法是在主循环里加上超时判断,如果发现部分帧先缓存起来,等数据齐了再解析。这个场景在波特率较低或者数据量大时尤为明显。

4.4 数据发送流程

发送数据时,我封装了一个底层函数:

void ESP8266_SendData(uint8_t channel, const uint8_t *data, uint16_t len) { if (channel > 4) { return; } // 构造AT+CIPSEND指令 char cmd[32]; sprintf(cmd, "AT+CIPSEND=%d,%d\r\n", channel, len); USART_SendString(USART1, cmd); // 等待模块返回">" if (wait_prompt(100)) { // 收到提示符后发送实际数据 // 这里是纯数据发送,不能加\r\n,必须按字节精确发送 USART_SendBytes(USART1, data, len); // 等待SEND OK } }

这里有个大坑:AT+CIPSEND的数据部分长度必须和实际发送长度完全一致。如果len是10但你实际发了11个字节,模块会认为数据错误。反过来如果你只发9个字节,模块会一直等剩下的数据。所以计算长度的逻辑一定要仔细,特别是当数据中含有字符串结尾符、转义字符时更要小心。

还有一点,发送完数据后的"SEND OK"确认是必需的。我一开始图快直接sleep,结果下一帧数据和前面的回复混在一起,解析直接爆炸。后来老老实实等了确认才继续发,问题就消失了。

5. 常见问题与排查技巧实录

5.1 模块反复重启

这个现象我在帖子里见过无数次,自己也被坑过。ESP8266连上WiFi后如果出现周期性重启,或者发送数据时重启,优先怀疑供电。把供电电压用万用表量一下,如果掉到3.3V以下,换电源或者加一个大电容(470uF以上)试试。常见罪魁祸首是USB延长线压降、劣质电源适配器、还有那种插在开发板上的面包板电源模块,虚标严重。

5.2 TCP服务器启动失败

AT+CIPSERVER返回ERROR,先确认是不是已经在服务器状态。如果模块已经开启了服务器,再次执行CIPSERVER会返回ERROR或者不改动。还有就是必须先AT+CIPMUX=1,这个是硬性顺序。最后确认固件版本,部分精简固件可能不支持服务器模式,重新烧录乐鑫官方固件即可。

5.3 收不到+IPD数据

可能原因很多,我按优先级排查:

  1. 客户端连接是否成功——看模块是否上报了0,CONNECT,没有的话说明客户端根本没连上
  2. 串口波特率是否匹配——如果模块输出的是乱码,大概率波特率不对
  3. 缓冲区是否溢出——如果你的一次数据超过256字节,RING_BUFFER_SIZE改大
  4. 数据处理逻辑是否有误——比如没等数据收完就dispatch了

还有一个很隐蔽的问题:ESP8266的串口默认可能有透传模式残留。如果之前配置过AT+CIPMODE=1并且进入了透传状态,串口上不会出现+IPD帧,而是直接输出原始数据。这时候发送"+++"退出透传,或者直接重启模块。

5.4 UDP和TCP的混淆

我必须单独提一下这个问题。很多新手在配置TCP服务器时,会把AT+CIPSTART和AT+CIPSERVER搞混。CIPSTART是建立单一连接,CIPSERVER是建立服务器监听。在TCP服务器场景下,客户端连接上来之后,模块会分配一个channel,服务器不需要也不应该通过CIPSTART去连接客户端。如果代码里混用了这两种模式,会出现指令返回错误或者数据收发混乱。

我个人建议在调试阶段,用手机上的网络调试助手或者电脑上的TCP工具做客户端,一步步验证模块侧的收发情况。

5.5 连接断线和心跳机制

TCP连接本质上是一个长连接,但WiFi环境不稳定、路由器老化、客户端休眠等都可能导致连接断开。我处理的方式是让客户端定期发送心跳帧,比如每5秒一个0x00字节,服务器端如果连续15秒没有收到任何数据,就认为客户端掉线,主动调用AT+CIPCLOSE=0清理连接资源。这样能有效防止模块侧channel被僵尸连接耗尽。

另一个排查技巧是:开发阶段在模块旁放一个USB转串口线,同时引出ESP8266的TXD到电脑串口助手,这样你能同时看到模块侧收到的所有数据,调试效率翻倍。

6. 一个完整的项目应用场景示例

光讲原理和代码有点干,我拿一个实际跑过的项目收尾。

之前做了一个车间温湿度监测终端,STM32F103C8T6最小系统板加DHT22传感器加ESP8266-01S。STM32每2秒采集一次温湿度,通过串口把数据拼成JSON格式,通过TCP服务器模式等待车间主任的电脑连接。上位机是一个用Python写的简单工具,连接设备的IP和端口后,以TCP客户端身份接收JSON数据,实时绘制温度曲线。

这个项目有几个亮点值得你参考。第一,我把ESP8266的TCP服务端口设在8266,固定好端口后,把设备的IP地址在路由器管理后台设置成静态IP绑定。这样上位机配置的设备地址永久有效,不会因为DHCP租约到期导致IP漂移。第二,我在STM32的Flash里保存了一个设备ID,每次客户端连接后就把设备信息和当前状态推过去,相当于一个简单的注册握手。第三,如果WiFi断线了,STM32会定时检测ESP8266有没有返回WIFI DISCONNECT事件,一旦发现就自动重新执行AT+CWJAP和AT+CIPSERVER流程,实现断线重连。

这套代码和硬件总共花了不到3天时间就调通了,从那以后我把这套框架沉淀下来,后续的很多项目都是在这个基础上扩展传感器和协议。回头来看,STM32F103C8T6加ESP8266的AT指令方案虽然谈不上技术含量多高,但是它皮实、可控、好查问题,对中小型项目和快速验证来说,依然是性价比极高的选择。

如果你也在做类似的东西,建议你按我上面的顺序一步一步来,先在电脑端用串口助手下好AT指令,再写MCU代码。不要一上来就想搞高大上的项目,先把TCP服务器这个基础框架吃透,后面不管是接物联网平台还是做局域网控制,都是顺水推舟的事。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询