TCPWIFI发送代码
058.8.3 TCP WIFI发送代码
/* ==================== main.c ==================== */
#include "reg52.h"
#include "delay.h"
#include "main.h"
#include "uart.h"
/* 中断服务程序与主程序都会访问该变量,因此声明为 volatile。 */
volatile uchar recv;
void main(void)
{
/* ESP8266 上电时会输出启动信息,先等待电源和串口电平稳定。 */
Delay_xms(1000);
UART_Init();
/* 请把 SSID、密码、服务器 IP 和端口替换为自己的实验参数。 */
UART_Send_Str("AT+CWMODE=3\r\n");
Delay_xms(500);
UART_Send_Str("AT+CWJAP=\"YOUR_SSID\",\"YOUR_PASSWORD\"\r\n");
Delay_xms(6000);
UART_Send_Str("AT+CIPSTART=\"TCP\",\"192.168.1.100\",5132\r\n");
Delay_xms(6000);
UART_Send_Str("AT+CIPSEND=4\r\n");
Delay_xms(500);
/* CIPSEND 已声明 4 字节,这里只能发送 qwer,不能再附加 CR-LF。 */
UART_Send_Str("qwer");
while (1)
{
Delay_xms(1000);
}
}
/* 串口中断服务函数:接收 ESP8266 返回的每一个字节。 */
void UART_Routine(void) interrupt 4
{
if (RI == 1)
{
RI = 0;
recv = SBUF;
}
}
/* ==================== main.h ==================== */
#ifndef _MAIN_H_
#define _MAIN_H_
#include "reg52.h"
typedef unsigned char uchar;
typedef unsigned int uint;
sbit LED1 = P1^0;
sbit LED2 = P1^1;
sbit LED3 = P1^2;
sbit LED4 = P1^3;
sbit BEEP = P1^6;
sbit JDQ1 = P2^0;
#endif
/* ==================== Uart/uart.c ==================== */
#include "uart.h"
void UART_Send_Byte(uchar send_byte)
{
SBUF = send_byte;
while (TI == 0);
TI = 0;
}
void UART_Send_Str(const char *send_str)
{
while (*send_str != '\0')
{
UART_Send_Byte((uchar)*send_str);
send_str++;
}
}
void UART_Init(void)
{
SCON = 0x50; /* 串口方式 1,允许接收 */
PCON &= 0x7F; /* SMOD=0,波特率不加倍 */
TMOD = (TMOD & 0x0F) | 0x20; /* 定时器 1,方式 2 */
TH1 = 0xFD;
TL1 = 0xFD; /* 11.0592 MHz 下为 9600 bit/s */
RI = 0;
TI = 0;
TR1 = 1;
ES = 1;
EA = 1;
}
/* ==================== Uart/uart.h ==================== */
#ifndef _UART_H_
#define _UART_H_
#include "main.h"
void UART_Send_Byte(uchar send_byte);
void UART_Send_Str(const char *send_str);
void UART_Init(void);
#endif
/* ==================== Delay/delay.c ==================== */
#include "delay.h"
#include <intrins.h>
void Delay_xms(uint xms) /* 适用于 11.0592 MHz 晶振 */
{
uchar data i, j;
while (xms != 0)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
/* ==================== Delay/delay.h ==================== */
#ifndef _DELAY_H_
#define _DELAY_H_
#include "main.h"
void Delay_xms(uint xms);
#endif
一、TCP实验第二阶段
第一阶段由电脑串口助手手动发送 AT 指令;第二阶段把同一串 AT 指令写进 51 单片机程序,由单片机通过 UART 自动控制 ESP8266。两阶段的网络原理没有改变,改变的是 AT 指令的发送者。
flowchart LR
A[第一阶段<br/>电脑串口助手] -->|UART/AT 指令| B[ESP8266]
C[第二阶段<br/>51 单片机程序] -->|UART/AT 指令| B
B -->|2.4 GHz Wi-Fi| D[路由器]
D -->|TCP| E[电脑 TCP Server]
1.1 任务信息资源导图
第二阶段开始前,应确保第一阶段的最小链路已经成功:ESP8266 能回应 AT,能加入 2.4 GHz 热点,能连接电脑的 TCP Server,并能双向传输数据。否则,加入单片机后会同时面对供电、串口、AT 指令和网络四类问题,很难判断故障位置。
推荐按以下顺序操作:
第一阶段通信成功
↓
把 ESP8266 的 AT 串口改为 9600 bit/s
↓
断电后改接 51 单片机,检查供电和电平
↓
编写并下载自动发送 AT 指令的程序
↓
先打开电脑 TCP Server,再复位单片机
↓
观察串口回复和服务器收到的 qwer
1.2 实验常⻅问题总结
定位问题时,不要一次改动多处。可以把整个实验分成四层:
| 层级 | 先检查什么 | 典型现象 |
|---|---|---|
| 电源/电平 | ESP8266 是否使用稳定 3.3 V,GND 是否共地,51 TX 是否降压 | 反复重启、乱码、模块异常发热 |
| UART | 两端是否同为 9600 8N1,TX/RX 是否交叉 | AT 无 OK、返回乱码 |
| AT 状态机 | 是否等到上一条指令成功后再发下一条 | busy、ERROR、连接过程被打断 |
| 网络 | SSID、密码、服务器 IP、端口、防火墙是否正确 | 能入网但 CIPSTART 失败 |
第一阶段与第二阶段可以使用同一份问题清单,但第二阶段还要额外检查单片机晶振、定时器初值、共享串口和代码发送长度。
1.3 课程相关所有链接汇总
遇到实验失败,可先查课程资料中 ESP8266/CH340 的常见问题,再回到模块实际返回的信息。最有价值的诊断证据不是“没有现象”,而是 ESP8266 返回的最后一行,例如 ERROR、FAIL、busy p...、CLOSED 或 WIFI DISCONNECT。
调试时建议保存以下信息:
- ESP8266 AT 固件版本,可用
AT+GMR查询; - 当前 UART 配置,可用支持该命令的固件执行
AT+UART_DEF?; AT+CWJAP的错误码或状态文本;- 电脑的实际局域网 IP、子网掩码和监听端口;
- 单片机晶振频率及串口初始化代码。
1.4 TCP实验第二阶段
这一阶段的核心是让单片机依次完成:设置 Wi-Fi 模式、加入热点、连接服务器、声明发送长度、发送数据。当前程序使用固定延时,便于理解流程,但它没有判断 OK、>、CONNECT 和 SEND OK,网络稍慢时就可能失败。更可靠的状态机将在后续优化中完善。
1.4.1 波特率修改原因
课程工程使用传统 8051 串口、11.0592 MHz 晶振、定时器 1 方式 2,TH1=TL1=0xFD 时得到 9600 bit/s。并不是“所有 51 单片机都只支持 9600”,而是当前硬件与初始化方案选择了 9600;其他 8051 芯片若具有独立波特率发生器或采用不同晶振,也可能支持更高波特率。
在 ESP8266 仍通过 USB 转 TTL 连接电脑时,先以它当前的波特率打开串口并发送 AT。收到 OK 后发送:
AT+UART_DEF=9600,8,1,0,0
五个参数依次表示:
| 参数 | 值 | 含义 |
|---|---|---|
| baudrate | 9600 | 每秒 9600 bit |
| databits | 8 | 8 个数据位 |
| stopbits | 1 | 1 个停止位 |
| parity | 0 | 无校验 |
| flow control | 0 | 不使用 RTS/CTS 硬件流控 |
UART_DEF 会把默认串口配置保存到非易失存储区,重新上电后仍然有效。指令返回 OK 后,模块已经切换到 9600;此时继续用 115200 发送 AT 通常不会得到可读回复,需要把电脑串口改成 9600 8N1 再验证。
不同 ESP8266 AT 固件可能只支持 AT+UART_CUR、AT+UART_DEF 中的一部分,或采用不同命令名称。发送前可用 AT+GMR 记录固件版本,并查对应版本手册。如果改错波特率,可扫描常用波特率或重新烧录已知配置的 AT 固件,而不是盲目反复断电。
1.4.2 接线方式
单片机控制 ESP8266 时,UART 必须交叉连接:
数据方向
51 P3.1 / TXD ───────────> ESP8266 RXD
51 P3.0 / RXD <─────────── ESP8266 TXD
51 GND ──────────── ESP8266 GND
稳定 3.3 V ──────────── ESP8266 VCC、EN/CH_PD
电路构成与作用如下:
flowchart LR
P[5 V 输入] --> R[3.3 V 稳压器<br/>足够的瞬态电流能力]
R --> C[去耦电容<br/>靠近模块]
C --> V[ESP8266 VCC/EN]
M[51 单片机 TXD<br/>通常接近 5 V] --> L[分压器或电平转换]
L --> X[ESP8266 RXD<br/>3.3 V 逻辑]
Y[ESP8266 TXD<br/>3.3 V 逻辑] --> N[51 单片机 RXD]
G[公共 GND] --- R
G --- M
G --- V
需要特别区分“板上的 5 V 电源口”和“ESP8266 的 3.3 V VCC”:
- 裸 ESP-01/ESP-01S 的 VCC、EN/CH_PD 和 I/O 均不能直接接 5 V;
- 只有当转接板明确带有 3.3 V 稳压器,并把某个接口明确标为
5V/VIN时,5 V 才能接到这个转接板输入; - 看到导线标签写着
VCC 3.3V,却准备插到开发板5V针脚时,必须先查原理图或用万用表确认中间确实有稳压器,不能凭接口外观判断; - 传统 5 V 51 单片机的 TXD 高电平可能接近 5 V,应经分压或专用电平转换后再进入 ESP8266 RXD;ESP8266 的 3.3 V TXD 通常可被 5 V 51 的 RXD 识别,但仍应核对具体芯片输入高电平门限。
若开发板下载器与 ESP8266 共用 P3.0/P3.1,烧录单片机程序时两路发送端可能同时驱动同一根线,造成冲突。安全做法是关闭相关电源后完整断开 ESP8266 的 TXD、RXD 或使用跳帽/开关隔离,再下载程序;不要只拔 GND 而保留 VCC 和信号线,因为电流可能经 I/O 保护二极管反向供电。改线必须断电,避免热插拔造成瞬态干扰。
1.4.3 代码编写
程序先初始化 UART,然后通过 UART_Send_Str() 把每一条 AT 指令逐字节送入 SBUF。AT 指令阶段采用 CR-LF 结尾,网络数据阶段则严格按 CIPSEND 声明的字节数发送。
main()
├─ 延时,避开 ESP8266 上电启动信息
├─ UART_Init()
├─ CWMODE + CR-LF
├─ 等待
├─ CWJAP + CR-LF
├─ 等待
├─ CIPSTART + CR-LF
├─ 等待
├─ CIPSEND=4 + CR-LF
├─ 等待“>”的时间
└─ 发送 4 字节 qwer,不带 CR-LF
1.4.3.1 回顾之前实验
第一阶段中,串口助手帮忙完成了两件事:把键盘输入转换为字节流,以及在 AT 指令末尾自动追加回车换行。单片机程序必须自己完成这两件事。
UART_Send_Str() 的工作过程是:读取当前指针指向的字符,调用 UART_Send_Byte() 发送,再让指针加 1,直到遇到 C 字符串结束标志 \0。� 只存在于单片机内存中,用于标记字符串结尾,不会被发送给 ESP8266。
串口发送一个字节时:
CPU 写 SBUF → 串口硬件按起始位、8 数据位、停止位移出 → TI 置 1
→ 程序发现 TI=1 → 软件清 TI → 可以发送下一字节
1.4.3.2 发送数据
AT 指令必须以 CR-LF 结束,在 C 字符串中写作 \r\n:
UART_Send_Str("AT+CWMODE=3\r\n");
这里 \r 是回车,十六进制为 0x0D;\n 是换行,十六进制为 0x0A。它们是两个独立字节。
当 AT 指令本身需要双引号时,要用反斜杠转义:
UART_Send_Str("AT+CWJAP=\"YOUR_SSID\",\"YOUR_PASSWORD\"\r\n");
从 C 语言和实际发送结果两个角度看:
| C 源码写法 | UART 实际发出的字符 |
|---|---|
\" | " |
\r | CR,即 0x0D |
\n | LF,即 0x0A |
\\ | 一个反斜杠 |
字符串最外侧的一对双引号负责界定 C 字符串,不会被发出;内部的 \" 才会产生 AT 指令需要的英文双引号。
1.4.3.3 接收解析数据
本节代码虽然开启了串口接收中断,但中断服务函数只是把每个新字节覆盖到单个变量 recv 中,主程序并没有解析它。因此当前程序严格说只是“接收了最后一个字节”,还没有实现完整的 AT 回复解析。
固定延时版本采用:
| 动作 | 示例等待时间 | 实际应等待的关键回复 |
|---|---|---|
| 设置模式 | 500 ms | OK |
| 加入 Wi-Fi | 6000 ms | WIFI GOT IP 和 OK |
| 连接服务器 | 6000 ms | CONNECT 和 OK |
| 声明发送长度 | 500 ms | 提示符 > |
| 发送正文 | 无固定延时要求 | SEND OK |
课程为了演示把 Wi-Fi 和服务器等待时间增加到 6 秒,使实验能够运行,但固定时间并不能保证所有网络环境都成功。正确的优化方向是把接收字节存入缓冲区,识别上述关键回复,并加入超时与重试。
还有一个关键长度问题:
UART_Send_Str("AT+CIPSEND=4\r\n");
UART_Send_Str("qwer"); /* 正确:恰好 4 字节 */
如果写成 UART_Send_Str("qwer\r\n"),程序实际会送出 6 字节。ESP8266 收到前 4 字节后已完成本次网络发送,剩余 CR-LF 可能滞留在 AT 命令输入流中,干扰下一条指令。
1.4.3.4 完善代码
烧录程序前先关闭占用下载串口的串口助手,确认下载软件选择的是单片机开发板对应的 COM 口,而不是 USB 转 TTL 对应的 COM 口。若开发板下载器和 ESP8266 共用串口:
- 关闭单片机板和 ESP8266 电源;
- 断开 ESP8266 的 TXD、RXD,或使用板载跳帽隔离;
- 给开发板上电并下载程序;
- 下载完成后再次断电;
- 按 TX→RX、RX→TX、GND 共地、3.3 V 安全供电重新连接;
- 先打开电脑 TCP Server,再给系统上电或复位单片机。
只断开 ESP8266 的 VCC/GND、却仍保留 UART 信号线,可能发生信号线反向供电;在带电状态下反复插拔 VCC/GND 还会制造电源毛刺,应避免这种操作。
1.4.3.5 实验展示
电脑端先建立与代码一致的 TCP Server,例如 192.168.1.100:5132。单片机复位后,预期顺序是:
ESP8266 加入热点
↓
ESP8266 主动连接 TCP Server,服务器显示客户端在线
↓
ESP8266 收到 CIPSEND=4 和 qwer
↓
服务器接收区显示 qwer
调试时可在 ESP8266 TXD 上并联一个只接收的 USB 转 TTL RXD,并与系统共地,用串口助手观察模块回复;不要把两个 TXD 直接并联。若只能看到单片机发出的 AT 指令,却看不到模块返回内容,应检查监听位置和接线方向。
视频中插拔 ESP8266 电源时蜂鸣器偶尔鸣响,更可能与供电跌落、地弹、电源回灌或 MCU 复位瞬态有关,不能直接归因于“静电”。蜂鸣器连接在 P1.6,UART 使用 P3.0/P3.1,两者并不直接共用引脚。应检查 3.3 V 稳压器容量、5 V 电源是否被 Wi-Fi 峰值电流拉低、复位电路、蜂鸣器默认电平和是否存在信号线反向供电。最重要的是先断电再改线,并用万用表或示波器确认电源变化。