简介以两块STM32F103C8T6配合两个ESP8266模块实现Wi-Fi无线通信为主线完整展示服务器端与客户端串口透传及指令执行流程。工程面向嵌入式/物联网入门及进阶学习者解决了双机间通过AT指令建立TCP连接、自定义控制指令如LED开关解析与响应的常见设计问题。压缩包大小75.15MB共379个文件主要包含C/H源码、o/d目标文件、Keil工程配置uvprojx/uvoptx、axf/hex/bin固件以及PDF说明文档源码工程可直接打开编译与烧录验证。目前已有1938人学习下载工程内保留串口初始化、ESP8266初始化与配网、服务器/客户端建链、数据接收解析及LED驱动等模块方便参照移植。通过阅读服务器端发送与客户端接收匹配逻辑可举一反三扩展到传感器数据上报、远程控制等场景。 前阵子给实验室做一套“双机联动控制”的板子手头正好有两块STM32F103C8T6最小系统板和两个ESP8266模块。需求其实不复杂一块板子做主控另一块做远端执行中间通过WiFi通信收到指令后控制LED、继电器或电机。但真正把这套东西调通比想象中多花了不少时间尤其集中在ESP8266的AT指令配置、上电时序、TCP链路保活这几个环节。今天就把整个过程拆开写一遍从硬件接线到STM32端代码再到无线链路调试给准备做类似项目的朋友一条能直接照着走的路。1. 项目整体思路与方案选型1.1 双机指令链路的真实需求先明确一个核心我们不是让两个ESP8266自己“互相聊天”而是让两块STM32通过各自的ESP8266来交换控制指令。数据链路是STM32(A) - UART - ESP8266(A) - WiFi - ESP8266(B) - UART - STM32(B)A端STM32通过串口把指令发给ESP8266ESP8266以TCP数据包形式发出B端的ESP8266接收后再通过串口把数据原样交给B端STM32B端程序解析指令并执行对应动作。所以对STM32来说ESP8266可以理解成一个“串口转网络”的透明管道难点主要在两块一是ESP8266的AT指令流程要对二是STM32端对串口数据的解析要稳。这个场景非常适合做远程传感器采集、双机协作、无线遥控车、机房环境监控这类项目。如果你想做的是一块板子控制电机、另一块板子控制传感器本质上都可以用这套链路实现。1.2 为什么选 F103C8T6 ESP8266 组合STM32F103C8T6这颗芯片在嵌入式圈子里算是“国民级”芯片72MHz主频20KB RAM64KB FlashUSB、CAN、多个USART都有。最要紧的是价格低、资料多、库函数和HAL库都能用很适合做控制中枢。ESP8266则是一款集成了WiFi功能的串口模块出厂大多烧好AT固件单片机只需要通过UART发送“AT指令”就能联网收发数据不需要在STM32上跑WiFi协议栈。这套组合还有一个隐性的好处ESP8266本身也能单独跑程序但如果你把协议栈、控制逻辑、指令解析全塞进去代码复杂度会明显上升尤其是调试硬件外设时会非常别扭。用STM32做逻辑、ESP8266做透明传输职责清晰出问题时也容易定位。1.3 通信模式选型对比ESP8266的AT固件支持三种典型工作模式AP模式、STA模式、APSTA共存模式。两个8266要互传数据通常的做法是方案A端B端适用场景APTCP ServerB端开APA端作Station连入B端开CIPSERVER监听典型双机直连简单可靠路由器双STA两块都连同一个路由通过TCP/IP互相连接需要更多设备接入或有现成WiFi环境UDP广播两端同网段用UDP互发无连接速度快不要求可靠交互数据量实时性强如果只是两台设备互相通信我推荐直接用“B端开AP A端连入”的方式相当于B端自己搭建一个微型热点网络。这样做的好处是调试时不依赖外部路由器现场部署也方便。TCP是面向连接的协议能保证数据顺序和完整性比UDP更适合“指令传输”。UDP虽然快但丢包时不会有任何反馈指令一旦丢了执行端就“哑火”了还是老老实实用TCP更稳妥。2. 硬件接线与基础调试2.1 引脚接线与供电设计STM32F103C8T6一般通过USART1与ESP8266通信因为USART1的PA9/PA10引出方便也方便调试时直接用板载USB转串口观察。接线方式是“交叉连接”单片机的TX接模块的RX单片机的RX接模块的TX然后GND必须和STM32共地。STM32F103C8T6ESP8266NodeMCU或纯模块PA9 (USART1_TX)RXDPA10 (USART1_RX)TXDGNDGND3.3V借用板载LDOVCC、CH_PDEN3.3VRST经10k电阻拉高或直接悬空这里有个非常容易踩的坑很多ESP8266模块的峰值电流能到300mA以上而STM32F103C8T6最小系统板上的AMS1117-3.3稳压芯片输出能力有限如果直接从板子上的3.3V引脚给ESP8266供电模块发射瞬间拉低电压可能导致STM32复位或者串口数据变成乱码。我实际测试时也遇到过类似问题最后是给ESP8266单独用一个小型AMS1117-3.3模块供电并在模块电源引脚旁边并联一个100uF电解电容和一个0.1uF瓷片电容稳定不少。注意电平匹配STM32的串口TXD输出是3.3V可以直接接ESP8266的RXD但如果你用的是一块5V逻辑的单片机就必须做电平转换否则容易烧模块。2.2 固件确认与AT同步拿到ESP8266模块第一件事不是急着接线而是确认固件版本和波特率。先把模块通过USB转TTL接到电脑打开串口助手上电后一般会先看到一串“boot”启动日志波特率可能是74880然后出现“ready”。AT固件默认波特率常见是115200发一条AT回车换行看是否返回OK。如果收到乱码或者毫无反应挨个试9600、57600、74880、115200这几个波特率。还有一个经验很多模块即使波特率错了上电时的“ready”或启动信息还是会乱码但AT指令是能解析的所以别因为启动日志乱码就认定模块坏了。确认模块正常后我建议先用串口助手把后续要用到的AT配置手动跑一遍确认链路通了再写单片机程序。不要一上来就把模块焊到板子里调试成本会高很多。2.3 上电时序的隐藏坑ESP8266不是一个简单的“上电就能用”的模块。CH_PDEN引脚必须拉高模块才正常启动部分模块上电后需要几百毫秒到一两秒时间启动固件如果太快发AT指令前面几条会“吃掉”无响应。另外如果程序里做了“上电立即初始化ESP8266”的操作很容易失败。建议在STM32上电后延时2秒左右再开始AT配置或者手动复位一次ESP8266并等待“ready”。稳妥的做法是在代码里发送AT同步指令前先循环发几次AT直到收到OK这样即使模块启动慢一点程序也能自动同步上。3. STM32 串口收发与指令协议3.1 CubeMX 工程配置要点用HAL库开发时建议直接用STM32CubeMX生成工程。配置USART1为异步收发模式波特率与ESP8266一致比如115200数据位8无校验停止位1。开启USART1的全局中断这样可以用中断方式接收数据。如果后续还要用串口做调试输出可以考虑再加一个USART2接到电脑波特率也用115200把调试信息和AT交互日志分离开。经验之谈开发阶段“两根串口线”非常救命能看到STM32发给8266的原样数据和8266返回的AT响应排查问题效率翻倍。GPIO方面如果要控制LED可以配置PA0、PA1为推挽输出如果控制继电器建议输出引脚加三极管或光耦驱动不要直接用PA口去拖电磁继电器线圈电流不够还会烧引脚。3.2 串口中断接收与环形缓冲很多初学朋友喜欢在串口中断的回调函数里直接做指令解析这是大忌串口中断触发频率高解析逻辑稍微复杂一点就会影响后续数据接收甚至造成数据丢失。我习惯的做法是中断里只做一件事——把收到的字节放进环形缓冲区主循环里再取数据解析。环形缓冲区本质是一块循环使用的内存通过写索引和读索引管理。不能用简单数组覆盖的方式否则8266一次返回多行AT响应时后半段很可能会被新数据冲掉。#define RING_SIZE 512 static uint8_t ring_buf[RING_SIZE]; static volatile uint16_t write_idx 0; static volatile uint16_t read_idx 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { ring_buf[write_idx] rx_byte; write_idx (write_idx 1) % RING_SIZE; HAL_UART_Receive_IT(huart, rx_byte, 1); } }这里用rx_byte接收单字节每接收到一个字节就再次调用HAL_UART_Receive_IT实现“连续接收”。主循环里做类似这样的事while (1) { while (read_idx ! write_idx) { uint8_t byte ring_buf[read_idx]; read_idx (read_idx 1) % RING_SIZE; // 把 byte 喂给状态解析机 handle_rx_byte(byte); } }3.3 指令帧格式与应答重发机制TCP层虽然能保证两个ESP8266之间的数据不乱序、不丢包但串口这端不一定干净。供电波动、误码、通信干扰都可能让STM32收到的数据“串味”。所以应用层一定要设计一套自己的指令协议不能裸发裸收。我常用的帧格式是这样的帧头功能码数据长度数据区校验和帧尾0xAA1字节1字节N字节1字节0x55比如控制LED1点亮数据区是0x01时代表亮0x00代表灭整帧可以设计为0xAA 0x01 0x01 0x01 0x03 0x55其中校验和是把“功能码数据长度数据区”所有字节相加取低8位即0x01 0x01 0x01 0x03。接收方校验通过后才执行指令。如果校验失败直接丢弃整帧等待下一帧的帧头重新同步。发送端需要重发机制发送一条指令后等接收端回一个ACK确认帧比如功能码0x80表示确认成功。假如1秒内没有收到ACK就重发同一帧最多重发3次。这个机制看着简单但能挡掉很多“看似连上但偶尔丢指令”的尴尬情况。4. 无线链路搭建AT指令与透传调试4.1 Server端配置我习惯把“远端执行板”作为AP和TCP Server因为执行端往往是固定的负责创建网络主控端主动连入更灵活。远端板的ESP8266需要这样配置ATCWMODE2 ATCWSAPESP8266_AP,12345678,11,3 ATCIPMUX1 ATCIPSERVER1,8266解释一下ATCWMODE2切到AP模式ATCWSAP设置热点名称、密码、信道和加密方式信道11、加密方式3对应WPA2-PSK。ATCIPMUX1开启多连接模式因为Server端需要同时监听连接ATCIPSERVER1,8266启动TCP Server端口我用8266方便记忆也可以用8080等端口。配置完成后用ATCWLIF可以查看当前连接到热点的设备列表这是排查“Client到底有没有连上来”的最直接手段。4.2 Client端配置与透传主控端作为TCP Client不能各自建热点否则没法互通。ATCWMODE1 ATCWJAPESP8266_AP,12345678 ATCIPSTARTTCP,192.168.4.1,8266 ATCIPMODE1 ATCIPSENDATCWJAP执行后会返回WIFI CONNECTED之后才开始TCP连接。192.168.4.1是ESP8266 AP模式下的默认网关地址也就是Server端自己的IP。连上AP后Client端一般会被分配192.168.4.2或192.168.4.3这样的地址。ATCIPMODE1进入透传模式ATCIPSEND之后模块会把串口收到的所有数据直接通过TCP发出去。这个模式对单向指令传输特别友好STM32代码里只要往串口写数据数据就会自动发到对端。但要注意透传模式只支持单连接所以Server端不能开透传Server端必须用普通模式通过IPD前缀来提取数据。如果要做双向互发两端都不建议开透传而是每次发送前用ATCIPSEND长度明确发送长度。工作流程是先下发ATCIPSEND5拿到提示符后再发送5字节数据模块返回SEND OK才算完成。对STM32代码来说这多了一点状态判断但换来的是双向通信的确定性和可调试性。4.3 心跳保活与掉线检测TCP连接并不是“物理上的线”长时间没有数据传输路由器、模块或对端都可能在空闲一段时间后主动断开连接。测试时经常出现“刚才还能传放了一会儿就没反应了”的现象十有八九是TCP连接已经断开但两边程序都不知道。解决办法是设计一个心跳帧比如主控端每隔2秒发出功能码0xFF的空帧uint8_t heartbeat[] {0xAA, 0xFF, 0x00, 0xFF, 0x55};接收端每收到一帧就记录“最近一次心跳时间”主循环里判断如果距离上次心跳超过5秒就认为链路异常点亮一个错误指示灯或者重新走一遍AT配置流程。这个动作虽然增加了一点代码量但实际使用中能避免大量“假死”问题。另外断线重连不能只在Client端做。有时候Server模块没重启Client已经断开了这时候Client程序周期性检查TCP状态一旦发现异常就重发ATCIPSTART重新连接。还要注意透传模式下如果链路断开要先发送退出透传回到AT命令模式然后才能重新配置网络。前后各需要一定保留时间不能紧跟其它字符否则不会被识别。5. 常见问题与排查5.1 8266不响应AT、串口乱码这是新手最常见的问题。先检查模块供电用万用表量一下模块VCC对地电压如果在3.0V以下波动优先解决供电然后检查CH_PD引脚是否拉高最后确认波特率。如果拿串口助手测试时一直乱码试着把GND接好很多“乱码”其实是共地没接好地线悬空会引入大量干扰。还有一个小技巧有的模块上电后可能处于透传模式这时候发AT不会返回OK而是直接把“AT”两个字发送到远端了。这种情况需要先按RST复位模块或发送退出透传。5.2 连接不上、刚连上就断开如果Client返回CONNECT FAIL或ERROR顺序检查Server端是否已经执行了ATCIPMUX1和ATCIPSERVER1,端口。热点的SSID和密码是否与ATCWJAP里完全一致包括大小写。AP热点是否隐藏在后台设置了AP隔离。有些路由器或手机热点会开启隔离导致客户端无法访问网关TCP连接能连上但传输立即断开。纯8266做AP时一般没有这个问题。如果Client在ATCIPSTART返回CONNECT后又马上出现CLOSED典型的Server端没有进入多连接监听模式或者AP与STA在同一模块上模式冲突。5.3 数据丢包、粘包、不完整ESP8266的IPD数据包有时会分多段到达比如Server端收到一长串数据时AT固件可能分成几次上报每行都带IPD,n:前缀。串口驱动如果按单字节或固定长度接收容易把一帧数据拆散。解决方法是STM32端只按帧结构解析不依赖“一次性收完”用帧头0xAA开始缓存直到收到帧尾0x55才认为一帧完成中间即使被IPD前缀打断也没关系要做的是剔除掉非帧头开始的干扰字符。如果数据经常收不全优先怀疑串口波特率过高导致单片机中断响应不及时。57600以下的稳定性会好很多。我这里用的115200在开优化和较高主频下没有问题但如果你在主循环里塞了大量阻塞延时最好降低到38400或更低的波特率。5.4 实测中的两个高频坑第一个是“电脑串口助手能收发换成STM32就不行”。这种问题多半是STM32发送的AT指令没有加回车换行。AT固件对\r\n非常敏感AT后必须拼接\r\n少了回车可能完全无响应或者返回ERROR。第二个是“CIPSEND长度和实际发送字节数不一致”。比如命令写ATCIPSEND5但实际数据只发了4个字节模块会一直等待直到超时表现就是数据发不出去、链路卡住。所以代码里一定要用同一个变量计算长度不要手写。最后再说点实际体会。这套设计本身不复杂但每一个环节都踩得上坑供电不稳、上电时序、AT指令缺回车、链路空闲断开、CIPSEND长度算错……任何一个都能让你对着串口助手怀疑人生。我个人强烈建议按“先串口助手手动测AT流程再写STM32驱动最后联调”的顺序来推进不要一上来就画板子写代码。把每一层都验证妥了这种双机通信项目其实半天就能打通。后面如果你想扩展往这套链路上加传感器采集、加云平台上报、加两个执行端的握手同步都是顺理成章的事。本文还有配套的精品资源点击获取