TCPWIFI发送代码

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-08-29 08:09:36 Views: 0 Likes: 0 Comments: 0

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 是否交叉ATOK、返回乱码
AT 状态机是否等到上一条指令成功后再发下一条busyERROR、连接过程被打断
网络SSID、密码、服务器 IP、端口、防火墙是否正确能入网但 CIPSTART 失败

第一阶段与第二阶段可以使用同一份问题清单,但第二阶段还要额外检查单片机晶振、定时器初值、共享串口和代码发送长度。

1.3 课程相关所有链接汇总

遇到实验失败,可先查课程资料中 ESP8266/CH340 的常见问题,再回到模块实际返回的信息。最有价值的诊断证据不是“没有现象”,而是 ESP8266 返回的最后一行,例如 ERRORFAILbusy p...CLOSEDWIFI DISCONNECT

调试时建议保存以下信息:

  • ESP8266 AT 固件版本,可用 AT+GMR 查询;
  • 当前 UART 配置,可用支持该命令的固件执行 AT+UART_DEF?
  • AT+CWJAP 的错误码或状态文本;
  • 电脑的实际局域网 IP、子网掩码和监听端口;
  • 单片机晶振频率及串口初始化代码。

1.4 TCP实验第二阶段

这一阶段的核心是让单片机依次完成:设置 Wi-Fi 模式、加入热点、连接服务器、声明发送长度、发送数据。当前程序使用固定延时,便于理解流程,但它没有判断 OK>CONNECTSEND 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

五个参数依次表示:

参数含义
baudrate9600每秒 9600 bit
databits88 个数据位
stopbits11 个停止位
parity0无校验
flow control0不使用 RTS/CTS 硬件流控

UART_DEF 会把默认串口配置保存到非易失存储区,重新上电后仍然有效。指令返回 OK 后,模块已经切换到 9600;此时继续用 115200 发送 AT 通常不会得到可读回复,需要把电脑串口改成 9600 8N1 再验证。

不同 ESP8266 AT 固件可能只支持 AT+UART_CURAT+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 实际发出的字符
\""
\rCR,即 0x0D
\nLF,即 0x0A
\\一个反斜杠

字符串最外侧的一对双引号负责界定 C 字符串,不会被发出;内部的 \" 才会产生 AT 指令需要的英文双引号。

1.4.3.3 接收解析数据

本节代码虽然开启了串口接收中断,但中断服务函数只是把每个新字节覆盖到单个变量 recv 中,主程序并没有解析它。因此当前程序严格说只是“接收了最后一个字节”,还没有实现完整的 AT 回复解析。

固定延时版本采用:

动作示例等待时间实际应等待的关键回复
设置模式500 msOK
加入 Wi-Fi6000 msWIFI GOT IPOK
连接服务器6000 msCONNECTOK
声明发送长度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 共用串口:

  1. 关闭单片机板和 ESP8266 电源;
  2. 断开 ESP8266 的 TXD、RXD,或使用板载跳帽隔离;
  3. 给开发板上电并下载程序;
  4. 下载完成后再次断电;
  5. 按 TX→RX、RX→TX、GND 共地、3.3 V 安全供电重新连接;
  6. 先打开电脑 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 峰值电流拉低、复位电路、蜂鸣器默认电平和是否存在信号线反向供电。最重要的是先断电再改线,并用万用表或示波器确认电源变化。