拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32F103C8T6与ESP8266实战:利用AT指令配置TCP服务器实现局域网通信

简介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内核主频72MHz64KB Flash20KB 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开启多连接功能ATCIPMUX1因为服务器要能同时挂多个客户端。这一点和TCP客户端模式有本质区别也是配置AT指令时最容易忽略的地方。2. 硬件连接与电路设计要点2.1 最小系统板与ESP8266的接线参考STM32F103C8T6最小系统板现在很便宜引脚也基本兼容我用的是经典的蓝板。和ESP8266通信我建议用USART1因为PA9和PA10引出方便而且USART1的时钟在APB2总线上性能稍好一些。接线表如下STM32F103C8T6ESP8266ESP-01S/ESP-12F说明3.3VVCC或VDD模块供电必须3.3V稳压GNDGND共地PA9USART1_TXRXD注意是交叉连接PA10USART1_RXTXD注意是交叉连接3.3V或者直接接VCCCH_PDEN/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.3VSTM32的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 // 关闭回显让返回数据更干净 ATCWMODE1 // 设置为Station模式连接外部路由器 ATCWJAPYourSSID,YourPassword // 连接WiFi返回WIFI CONNECTED和WIFI GOT IP ATCIPMUX1 // 开启多连接模式TCP服务器必须 ATCIPSERVER1,8080 // 建立TCP服务器端口8080 ATCIFSR // 查询模块IP地址返回类似192.168.1.100这套顺序是固定的尤其是CIPMUX必须在CIPSERVER之前设置因为固件在单连接模式下不允许多路监听。如果你先开了服务器再开多连接会返回ERROR。还有一个细节是ATCIPSERVER1,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发送数据指令是ATCIPSEND0,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; } // 构造ATCIPSEND指令 char cmd[32]; sprintf(cmd, ATCIPSEND%d,%d\r\n, channel, len); USART_SendString(USART1, cmd); // 等待模块返回 if (wait_prompt(100)) { // 收到提示符后发送实际数据 // 这里是纯数据发送不能加\r\n必须按字节精确发送 USART_SendBytes(USART1, data, len); // 等待SEND OK } }这里有个大坑ATCIPSEND的数据部分长度必须和实际发送长度完全一致。如果len是10但你实际发了11个字节模块会认为数据错误。反过来如果你只发9个字节模块会一直等剩下的数据。所以计算长度的逻辑一定要仔细特别是当数据中含有字符串结尾符、转义字符时更要小心。还有一点发送完数据后的SEND OK确认是必需的。我一开始图快直接sleep结果下一帧数据和前面的回复混在一起解析直接爆炸。后来老老实实等了确认才继续发问题就消失了。5. 常见问题与排查技巧实录5.1 模块反复重启这个现象我在帖子里见过无数次自己也被坑过。ESP8266连上WiFi后如果出现周期性重启或者发送数据时重启优先怀疑供电。把供电电压用万用表量一下如果掉到3.3V以下换电源或者加一个大电容470uF以上试试。常见罪魁祸首是USB延长线压降、劣质电源适配器、还有那种插在开发板上的面包板电源模块虚标严重。5.2 TCP服务器启动失败ATCIPSERVER返回ERROR先确认是不是已经在服务器状态。如果模块已经开启了服务器再次执行CIPSERVER会返回ERROR或者不改动。还有就是必须先ATCIPMUX1这个是硬性顺序。最后确认固件版本部分精简固件可能不支持服务器模式重新烧录乐鑫官方固件即可。5.3 收不到IPD数据可能原因很多我按优先级排查客户端连接是否成功——看模块是否上报了0,CONNECT没有的话说明客户端根本没连上串口波特率是否匹配——如果模块输出的是乱码大概率波特率不对缓冲区是否溢出——如果你的一次数据超过256字节RING_BUFFER_SIZE改大数据处理逻辑是否有误——比如没等数据收完就dispatch了还有一个很隐蔽的问题ESP8266的串口默认可能有透传模式残留。如果之前配置过ATCIPMODE1并且进入了透传状态串口上不会出现IPD帧而是直接输出原始数据。这时候发送退出透传或者直接重启模块。5.4 UDP和TCP的混淆我必须单独提一下这个问题。很多新手在配置TCP服务器时会把ATCIPSTART和ATCIPSERVER搞混。CIPSTART是建立单一连接CIPSERVER是建立服务器监听。在TCP服务器场景下客户端连接上来之后模块会分配一个channel服务器不需要也不应该通过CIPSTART去连接客户端。如果代码里混用了这两种模式会出现指令返回错误或者数据收发混乱。我个人建议在调试阶段用手机上的网络调试助手或者电脑上的TCP工具做客户端一步步验证模块侧的收发情况。5.5 连接断线和心跳机制TCP连接本质上是一个长连接但WiFi环境不稳定、路由器老化、客户端休眠等都可能导致连接断开。我处理的方式是让客户端定期发送心跳帧比如每5秒一个0x00字节服务器端如果连续15秒没有收到任何数据就认为客户端掉线主动调用ATCIPCLOSE0清理连接资源。这样能有效防止模块侧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事件一旦发现就自动重新执行ATCWJAP和ATCIPSERVER流程实现断线重连。这套代码和硬件总共花了不到3天时间就调通了从那以后我把这套框架沉淀下来后续的很多项目都是在这个基础上扩展传感器和协议。回头来看STM32F103C8T6加ESP8266的AT指令方案虽然谈不上技术含量多高但是它皮实、可控、好查问题对中小型项目和快速验证来说依然是性价比极高的选择。如果你也在做类似的东西建议你按我上面的顺序一步一步来先在电脑端用串口助手下好AT指令再写MCU代码。不要一上来就想搞高大上的项目先把TCP服务器这个基础框架吃透后面不管是接物联网平台还是做局域网控制都是顺水推舟的事。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门