0. 前言
最近在做一个基于 STC12 单片机的小项目,需要把传感器数据通过 Wi-Fi 发送到上位机。我选择了最常见的 ESP8266 模块作为无线通信方案。考虑到现场没有路由器,而且只需要一对一数据传输,我决定把 ESP8266 配置成AP 模式 + TCP Server,这样电脑或手机直接连接 ESP8266 发出的热点,就能收到数据了。
这篇文章主要记录我在开发过程中对esp8266.c这个模块的封装和调试过程,方便以后回顾。整体代码基于 STC12C5A60S2,使用 UART1 与 ESP8266 通信。文末也会总结一些踩过的坑,希望对有同样需求的朋友有帮助。
1. 系统整体结构
整个系统由 STC12 单片机作为主控制器,外接多个传感器:
- 超声波测距模块(用于检测钻头位置,统计打孔次数)
- 声音传感器 + 单片机 ADC(用于估算环境分贝)
- HX711 称重传感器(测量压力)
- OLED 显示屏(本地显示数据)
- ESP8266 Wi-Fi 模块(无线发送数据到上位机)
while(1) { Get_Weight(); // 读取 HX711 重量 adc_raw = GetADCResult(ADC_CH1)*100; adc_smooth = ADC_LowPass(adc_raw); // 低通滤波 dkcs(); // 超声波检测打孔动作并计数 ESP8266_SendHexPacket(distance, sound_db, drill_count, pressure); }可以看到,esp8266.c中的ESP8266_SendHexPacket函数负责将 4 个数据打包成二进制帧并发送出去。这就是本文的重点。
2. ESP8266 的 AT 指令配置流程
在写单片机程序之前,我习惯先用 USB 转串口模块连接 ESP8266,手动发送 AT 指令验证模块是否正常工作。我使用 9600 波特率(和单片机程序一致),发送以下命令:
| 命令 | 作用 |
|---|---|
AT | 串口通信测试,返回 OK |
AT+CWMODE=2 | 设置 Wi-Fi 模式为 AP(热点) |
AT+CWSAP="Drill_AP","12345678",5,3 | 创建热点:SSID 为 Drill_AP,密码 12345678,通道 5,加密方式 WPA2_PSK |
AT+CIPMUX=1 | 开启多连接模式(TCP Server 必须) |
AT+CIPSERVER=1,8080 | 开启 TCP 服务器,监听 8080 端口 |
配置完成后,电脑连接热点Drill_AP,用网络调试助手连接192.168.4.1:8080(ESP8266 默认网关 IP),就能接收到后续单片机发来的数据。
这些命令都封装在了esp8266.c的ESP8266_InitAPServer()函数中,单片机启动时自动执行一次。
3. esp8266.c 代码详解
3.1 宏定义区
/* ESP8266热点配置 */ #define ESP_AP_NAME "Drill_AP" #define ESP_AP_PASS "12345678" #define ESP_SERVER_PORT "8080"把热点名称、密码和端口号定义为宏,方便修改。注意密码长度至少 8 位,否则 ESP8266 会拒绝创建 WPA2 加密的热点。
3.2 软件延时函数
void ESP8266_Delay_ms(unsigned int ms) { unsigned int i, j; for(i = 0; i < ms; i++) for(j = 0; j < 110; j++); }这个函数基于 STC12 的机器周期做粗略延时。实际延时时间与单片机主频有关,我用 11.0592MHz 晶振时,内层循环 110 次大约接近 1ms。这个延时并不精确,但对 ESP8266 的 AT 指令等待来说足够用了。
3.3 AT 命令发送函数
void ESP8266_SendCommand(char *cmd, unsigned int delay_time) { UART_SendString(cmd); UART_SendString("\r\n"); ESP8266_Delay_ms(delay_time); }ESP8266 的每条 AT 命令都需要以回车换行\r\n结尾。这个函数将命令字符串加上\r\n发送出去,然后等待一段时间让模块处理。注意这里并没有检查模块是否返回OK,是一个简化设计。
3.4 初始化 AP 模式并开启 TCP Server
void ESP8266_InitAPServer(void) { ESP8266_Delay_ms(5000); ESP8266_SendCommand("AT", 1000); ESP8266_SendCommand("AT+CWMODE=2", 1000); // 发送 AT+CWSAP="热点名","密码",通道,加密方式 UART_SendString("AT+CWSAP=\""); UART_SendString(ESP_AP_NAME); UART_SendString("\",\""); UART_SendString(ESP_AP_PASS); UART_SendString("\",5,3\r\n"); ESP8266_Delay_ms(2000); ESP8266_SendCommand("AT+CIPMUX=1", 1000); // 开启 TCP Server UART_SendString("AT+CIPSERVER=1,"); UART_SendString(ESP_SERVER_PORT); UART_SendString("\r\n"); ESP8266_Delay_ms(2000); }这里有一点需要注意:AT+CWSAP命令的参数中包含引号,所以不能直接使用ESP8266_SendCommand发送,而是分步拼接字符串。通道我固定使用 5,加密方式 3 代表WPA2_PSK。如果不需要密码,可以改为 0(OPEN),但为了安全起见还是设置了密码。
3.5 十六进制数据帧发送函数
这是最核心的函数,负责将 4 个unsigned int类型的数据打包成 12 字节的二进制帧,并通过 ESP8266 发送给上位机。
unsigned char ESP8266_SendHexPacket(unsigned int distance, unsigned int db, unsigned int count, unsigned int pressure) { unsigned char frame[12]; unsigned char i; frame[0] = 0xAA; frame[1] = 0x55; frame[2] = distance >> 8; frame[3] = distance & 0xFF; frame[4] = db >> 8; frame[5] = db & 0xFF; frame[6] = count >> 8; frame[7] = count & 0xFF; frame[8] = pressure >> 8; frame[9] = pressure & 0xFF; frame[10] = 0x0D; frame[11] = 0x0A; // 发送 AT+CIPSEND=0,12 UART_SendString("AT+CIPSEND=0,12\r\n"); // 等待 '>' 提示 if(UART_WaitString(">", 5000) == 0) { return 0; } for(i = 0; i < 12; i++) { UART_SendByte(frame[i]); } // 等待 SEND OK if(UART_WaitString("SEND OK", 8000) == 0) { return 0; } return 1; }帧格式设计
| 字节索引 | 内容 | 说明 |
|---|---|---|
| 0 | 0xAA | 帧头 |
| 1 | 0x55 | 帧头 |
| 2 | distance 高字节 | 距离数据高 8 位 |
| 3 | distance 低字节 | 距离数据低 8 位 |
| 4 | db 高字节 | 分贝高 8 位 |
| 5 | db 低字节 | 分贝低 8 位 |
| 6 | count 高字节 | 打孔次数高 8 位 |
| 7 | count 低字节 | 打孔次数低 8 位 |
| 8 | pressure 高字节 | 压力高 8 位 |
| 9 | pressure 低字节 | 压力低 8 位 |
| 10 | 0x0D | 帧尾回车 |
| 11 | 0x0A | 帧尾换行 |
所有 16 位数据都采用大端模式(高字节在前)。帧头0xAA 0x55和帧尾0x0D 0x0A可以帮助接收端快速定位一帧数据。目前没有加校验和,后续可以考虑增加 XOR 或 CRC 校验。
发送流程
ESP8266 在 TCP Server 多连接模式下,发送数据需要先发送AT+CIPSEND=<连接号>,<数据长度>命令。如果连接正常,模块会返回>提示符,此时才能发送真正的数据。数据发送完成后,模块若成功发送到客户端,会返回SEND OK。
函数中使用UART_WaitString()来等待特定字符串,并设置了超时时间(等待>为 5 秒,等待SEND OK为 8 秒)。如果超时或没有收到预期回复,函数返回 0 表示发送失败;否则返回 1 表示成功。
4. 主程序中的调用与联调
在main.c中,系统启动后先执行各种初始化:
OLED_Init(); Ultrasonic_Init(); OLED_ShowFrame(); OLED_ShowData(); UART_Init(); IO_Init(); Get_Zero(); ADC_Init(ADC_PORT1); last_distance = Ultrasonic_GetDistance(); ESP8266_InitAPServer(); // 初始化 ESP8266 delay_ms(2000);然后主循环周期性地采集数据、显示 OLED,并调用ESP8266_SendHexPacket发送。
实际测试时,电脑连接热点Drill_AP,打开网络调试助手,以 TCP Client 模式连接192.168.4.1:8080,就能看到类似下面的十六进制数据流(十六进制显示):
Text
AA 55 00 0C 00 50 00 03 00 0C 0D 0A解析出来就是:距离 12,分贝 80,打孔次数 3,压力 12。
5. 调试过程中踩过的坑
5.1 串口波特率不一致
刚开始 ESP8266 默认波特率是 115200,而我的 STC12 程序使用 9600,导致模块完全没反应。后来用AT+UART_DEF=9600,8,1,0,0把 ESP8266 的默认波特率改成了 9600,或者也可以在初始化时发送AT+UART=9600,8,1,0,0临时修改。注意修改后需要重启模块生效。
5.2 ESP8266 供电不足
ESP8266 在发送数据时瞬间电流可能达到 300mA 以上,如果使用单片机板载的 3.3V 供电,很可能导致模块不断复位,表现为 AT 命令响应异常。解决方法:使用独立 5V 转 3.3V 模块给 ESP8266 供电,并共地。不要直接用 STC12 的 IO 口给 ESP8266 供电。
5.3 发送失败
由于ESP8266_SendHexPacket在等待>或SEND OK时用了较长的超时时间,单片机的发送速度太快,一下发送了好几条指令,ESP8266来不及接收,给单片机的串口发送加上一段延时就解决了这个问题
5.4 调试串口与 ESP8266 共用串口导致数据错乱
在main.c里有一行调试代码:
UART_SendNumber(distance);这会把距离值通过同一个串口发送给 ESP8266,而 ESP8266 会将其当作 AT 命令的一部分,导致后续通信异常。调试信息不能和 ESP8266 共用一个串口,要么删除,要么改用其他串口输出。这是本次调试中浪费最多时间的一个坑。
5.5 ESP8266 返回数据残留
多次发送后,串口接收缓冲区可能会残留SEND OK或CLOSED等字符串,影响下一次UART_WaitString(">")的判断。建议在发送AT+CIPSEND前调用一个清空接收缓冲区的函数,或者使用时在每次发送前清空 UART 接收缓存。
5.6 单片机串口5V转3.3V
在这里值得一提的是单片机的串口电平是5V而ESP8266的串口电平是3.3V,其实很简单,只要给串口交互部分加上一个电平转换电路就能解决这个问题
6. 改进方向
目前这个版本虽然能跑通,但还有很多值得优化的地方:
增加 AT 命令响应的检测:
ESP8266_InitAPServer中发送每条命令后都应该检查返回OK,否则若初始化失败,程序仍然继续运行,导致后续发送全部失败。发送函数改为非阻塞或状态机:避免卡死主循环,提高系统实时性。
处理 TCP 客户端断开:当客户端断开时,ESP8266 会返回
CLOSED,单片机没有处理,下一次发送会失败。可以检测到CLOSED后自动重启 Server 或等待新客户端连接。增加数据校验:在数据帧中增加一个简单的 XOR 校验字节,提高可靠性。
使用定时器精确延时:替换粗糙的软件延时,提高移植性。
7. 总结
本文记录了我使用 STC12C5A60S2 驱动 ESP8266 实现 AP 模式 TCP 服务器数据透传的过程。esp8266.c模块封装了初始化配置和二进制帧发送的功能,通过 AT 指令与模块交互。虽然目前还有一些不足,但已经可以完成基本的数据传输需求。
这次开发让我对 ESP8266 的 AT 指令有了更深入的理解,也积累了不少调试经验。后续会继续优化代码,尝试加入断线重连和缓冲区管理,让系统更稳定。
如果你也在做类似的项目,希望这篇文章能给你一些参考。如果有错误或更好的实现方式,欢迎交流讨论。