欢迎光临
我们一直在努力

51单片机 + ESP8266 TCP通信实战:从零实现WiFi远程控制

一、前言

在物联网开发中,51单片机凭借其低成本、易上手的优势,依然是许多初学者的首选平台。而ESP8266 WiFi模块则为51单片机插上了“无线的翅膀”,让原本只能本地运行的单片机具备了联网通信的能力。

本文将基于STC89C52单片机与ESP8266-01S模块,手把手带你实现:

  • ESP8266 TCP发送:单片机通过WiFi向电脑上的TCP服务器发送数据

  • ESP8266 TCP接收:单片机接收来自TCP服务器的数据,并解析执行LED控制等操作

整个项目的硬件框图如下:

二、硬件准备与接线

2.1 所需硬件

硬件说明
51单片机开发板(STC89C52) 主控芯片
ESP8266-01S WiFi模块 乐鑫/安信可出品
CH340 USB转TTL模块 用于调试和烧录
杜邦线若干 连接各模块

2.2 ESP8266-01S引脚说明

ESP8266-01S模块共有8个引脚,我们主要使用其中4个:

引脚功能
VCC 3.3V供电(实际可接5V,模块内部有稳压)
GND 接地
TX 串口发送(连接单片机RX)
RX 串口接收(连接单片机TX)

2.3 接线方式(最终实验接线)

ESP8266与51单片机接线(交叉连接) :

ESP8266引脚连接单片机引脚
3V3/VCC 5V(开发板5V输出)
GND GND
TX P3.0(RX)
RX P3.1(TX)

⚠️ 重要:TX与RX必须交叉连接——ESP8266的TX接单片机的RX,ESP8266的RX接单片机的TX。

2.4 ESP8266与CH340调试模块接线

在调试阶段,我们需要用CH340模块连接ESP8266,通过串口助手发送AT指令:

ESP8266引脚连接CH340引脚
3V3 5V
GND GND
TX RX
RX TX

⚠️ 注意:连接CH340时,需要去掉CH340模块上的跳帽。如果不拔掉跳帽,可能会影响串口通信。

三、ESP8266核心知识储备

3.1 三种WiFi模式

ESP8266支持三种WiFi工作模式:

模式指令说明
Station模式 AT+CWMODE=1 模块连接其他WiFi热点(作为客户端)
AP模式 AT+CWMODE=2 模块自己发射WiFi热点(作为服务器)
AP+Station混合模式 AT+CWMODE=3 两种模式共存

类比理解:

  • Station模式 = 手机连接家里WiFi

  • AP模式 = 手机开启热点

  • 混合模式 = 手机同时连接WiFi又开热点

我们的代码中使用的是模式3(混合模式),既能让ESP8266连接路由器上网,又能作为热点被其他设备发现。

3.2 透传模式 vs 非透传模式

模式指令特点
非透传模式 AT+CIPMODE=0 每次发送数据前必须先发AT+CIPSEND=长度,再发数据
透传模式 AT+CIPMODE=1 只需发一次AT+CIPSEND,之后所有内容都当作数据发送

透传模式的优势:不需要每次计算数据长度,数据可以连续发送,就像一根“无线串口线”。

透传模式的条件:

  • 必须是单连接模式(AT+CIPMUX=0)

  • 仅支持TCP单连接或UDP固定通信

  • 我们的代码用的是非透传模式,所以每次发送数据前都要先发AT+CIPSEND=4指定长度,再发qwer。

    3.3 必须修改波特率!

    这是一个新手最容易忽略的问题:

    • ESP8266出厂默认波特率:115200

    • 51单片机串口最高稳定波特率:9600(11.0592MHz晶振下)

    不匹配的后果:单片机发送的指令ESP8266无法识别,通信完全失败。

    解决方法:先用CH340模块连接ESP8266,在串口助手中以115200波特率发送以下指令:

    AT+UART_DEF=9600,8,1,0,0

    修改成功后,ESP8266的波特率会永久保存为9600。之后把串口助手波特率也改成9600,发送AT测试,收到OK即表示修改成功。

    ⚠️ 修改完波特率后,需要给ESP8266重新上电才能生效。

    四、代码逐行解析

    4.1 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

    这里定义了:

    • uchar和uint类型别名,方便代码书写

    • LED1~LED4分别对应P1.0~P1.3

    • 蜂鸣器(BEEP)对应P1.6

    • 继电器(JDQ1)对应P2.0

    4.2 delay.c —— 毫秒级延时

    void Delay_xms(uint xms)//@11.0592MHz
    {
    uchar data i, j;
    while(xms)
    {
    _nop_();
    i = 2;
    j = 199;
    do
    {
    while (–j);
    } while (–i);
    xms–;
    }
    }

    这是一个软件延时函数,通过CPU空转实现毫秒级延时。在11.0592MHz晶振下,i=2, j=199的双层循环约等于1ms。

    💡 为什么用软件延时而不是定时器? 在AT指令配置阶段,对延时精度要求不高(500ms、1000ms),软件延时足够且代码简单。但在实际项目中,建议使用定时器中断实现更精确的延时。

    4.3 uart.c —— 串口通信底层

    串口初始化(最关键!)

    void UART_Init(void)
    {
    SCON = 0x50; // 串口配成工作方式1
    PCON &= 0x7F; // 波特率不加倍
    TMOD &= 0x0f;
    TMOD |= 0x20;// 定时器1,模式二,自动重装初值
    TH1 = 0xFD;
    TL1 = 0xFD; // @11.0592MHz,9600波特率
    TR1 = 1; // 定时器开始运行
    ES = 1; // 串口中断打开
    EA=1;// 总中断打开
    }

    逐个寄存器解析:

    寄存器设置值含义
    SCON=0x50 0101 0000 工作方式1(8位UART),允许接收
    PCON&=0x7F 清零最高位 波特率不加倍(SMOD=0)
    TMOD&=0x0F 保留低4位 不清除定时器0的设置
    TMOD|=0x20 高4位=0010 定时器1工作方式2(8位自动重装)
    TH1=0xFD 253 9600波特率初值(11.0592MHz)

    波特率计算公式(方式1):

    波特率 = (2^SMOD / 32) × (定时器溢出率)
    定时器溢出率 = 晶振频率 / (12 × (256 – TH1))

    代入:9600 = (1/32) × (11059200 / (12 × (256 – 253))) = 9600 ✓

    串口发送

    void UART_Send_Byte(uchar send_byte)
    {
    SBUF = send_byte;
    while(!TI); // 等待发送完成
    TI=0; // 清除发送中断标志
    }

    void UART_Send_Str(uchar *send_str)
    {
    while(*send_str != '\\0')
    {
    UART_Send_Byte(*send_str++);
    }
    }

    UART_Send_Str通过指针遍历字符串,逐个字节发送,直到遇到字符串结束符\\0。

    💡 为什么AT指令结尾都有\\r\\n? ESP8266的AT指令解析器以\\r\\n作为指令结束标志。如果没有这两个字符,ESP8266会认为指令不完整而不执行。

    4.4 main.c —— 主控逻辑

    全局变量

    char recv; // 当前接收的字符
    char esp_recv_buf[15] = {0}; // 接收缓冲区
    char esp_flag = 1; // 指令执行状态标志(1=等待OK,0=已收到OK)

    esp_flag的工作机制:

    • 初始值为1

    • 发送一条AT指令后,单片机等待ESP8266返回OK

    • 串口中断中收到OK后,将esp_flag置0

    • do-while循环检测到esp_flag=0,跳出循环,执行下一条指令

    main函数流程

    void main()
    {
    Delay_xms(1000); // 上电延时,滤掉ESP8266上电TX引脚的数据
    UART_Init();

    // 1. 设置WiFi模式为AP+STA混合模式
    do {
    UART_Send_Str("AT+CWMODE=3\\r\\n");
    Delay_xms(500);
    } while(esp_flag);
    esp_flag = 1;

    // 2. 连接路由器(WiFi必须是2.4G,不支持5G)[reference:25]
    do {
    UART_Send_Str("AT+CWJAP=\\"i am god\\",\\"god05430543\\"\\r\\n");
    Delay_xms(1000);
    } while(esp_flag);
    esp_flag = 1;

    // 3. 作为TCP客户端连接服务器
    do {
    UART_Send_Str("AT+CIPSTART=\\"TCP\\",\\"192.168.31.118\\",5132\\r\\n");
    Delay_xms(1000);
    } while(esp_flag);
    esp_flag = 1;

    // 4. 发送数据(非透传模式,先指定长度)
    do {
    UART_Send_Str("AT+CIPSEND=4\\r\\n");
    Delay_xms(500);
    } while(esp_flag);

    UART_Send_Str("qwer\\r\\n"); // 实际数据

    while(1) {
    Delay_xms(1000);
    }
    }

    关键点解析:

    ① 上电延时:Delay_xms(1000)是为了等待ESP8266上电初始化完成。ESP8266上电后TX引脚会输出一些启动信息,如果立即初始化串口,这些垃圾数据可能被误读。

    ② 双引号转义:在C语言字符串中,双引号需要用反斜杠转义:\\"i am god\\"。否则编译器会认为字符串提前结束。

    ③ 网段匹配:AT+CIPSTART中的服务器IP(192.168.31.118)必须和ESP8266获取的STA IP在同一网段。

    ④ AT+CIPSEND=4:告诉ESP8266接下来要发送4个字节的数据。ESP8266收到后会返回>提示符,等待输入数据。

    4.5 串口中断 —— 数据接收与解析(代码升级版)

    这是TCP接收功能的核心,也是代码中最复杂的部分:

    void UART_Routine(void) interrupt 4
    {
    static char recv_count = 0;
    if(1 == RI)
    {
    RI = 0;
    recv = SBUF;

    // 检测到'O'或'+',认为是新数据的开始
    if(recv == 'O' || recv == '+')
    {
    memset(esp_recv_buf,'\\0',sizeof(esp_recv_buf));
    recv_count = 0;
    esp_recv_buf[recv_count] = recv;
    }
    else
    {
    recv_count++;
    esp_recv_buf[recv_count] = recv;
    }

    // 判断是否收到"OK"
    if(esp_recv_buf[0] == 'O' && esp_recv_buf[1] == 'K')
    {
    esp_flag = 0;
    memset(esp_recv_buf,'\\0',sizeof(esp_recv_buf));
    }

    // 解析TCP接收数据(格式:+IPD,长度:数据)
    if(esp_recv_buf[0] == '+' && esp_recv_buf[3] == 'D')
    {
    // 控制LED1
    if(esp_recv_buf[7] == 'L'&& esp_recv_buf[10] == '1'&& esp_recv_buf[11] == '0')
    LED1 = 0;
    if(esp_recv_buf[7] == 'L'&& esp_recv_buf[10] == '1'&& esp_recv_buf[11] == '1')
    LED1 = 1;
    // 控制LED2
    if(esp_recv_buf[7] == 'L'&& esp_recv_buf[10] == '2'&& esp_recv_buf[11] == '0')
    LED2 = 0;
    if(esp_recv_buf[7] == 'L'&& esp_recv_buf[10] == '2'&& esp_recv_buf[11] == '1')
    LED2 = 1;
    // 控制蜂鸣器
    if(esp_recv_buf[7] == 'B'&& esp_recv_buf[10] == 'P'&& esp_recv_buf[11] == '0')
    BEEP = 0;
    if(esp_recv_buf[7] == 'B'&& esp_recv_buf[10] == 'P'&& esp_recv_buf[11] == '1')
    BEEP = 1;
    }
    recv_count = recv_count % 14; // 防止数组越界
    }
    }

    ESP8266接收数据格式解析

    当ESP8266从TCP服务器收到数据时,会通过串口发送如下格式的数据:

    +IPD,<长度>:<数据>

    实际例子:

    +IPD,3:556 // 收到3个字节:556[reference:32]
    +IPD,5:hello // 收到5个字节:hello[reference:33]

    我们的数据协议:

    +IPD,4:L10 // 控制LED1灭
    +IPD,4:L11 // 控制LED1亮
    +IPD,4:L20 // 控制LED2灭
    +IPD,4:L21 // 控制LED2亮
    +IPD,4:BP0 // 蜂鸣器关
    +IPD,4:BP1 // 蜂鸣器开

    解析逻辑:

    • esp_recv_buf[0] == '+':检测到数据包开始

    • esp_recv_buf[3] == 'D':确认是+IPD格式(+是第0位,I是第1位,P是第2位,D是第3位)

    • esp_recv_buf[7]:判断控制对象(L=LED,B=蜂鸣器)

    • esp_recv_buf[10]和esp_recv_buf[11]:判断控制值(10=关,11=开)

    五、TCP通信完整流程

    5.1 TCP发送流程(代码已实现)

    单片机 ESP8266 电脑TCP服务器
    | | |
    |– AT+CWMODE=3 ————–>| |
    |<————- OK ————| |
    | | |
    |– AT+CWJAP="SSID","PWD" —->| |
    |<————- OK ————| |
    | |—- 连接WiFi热点 ————->|
    | |<——- 分配IP —————-|
    | | |
    |– AT+CIPSTART="TCP",IP,Port->| |
    |<————- OK ————| |
    | |—- 三次握手建立TCP连接 ——>|
    | |<——- 连接成功 ————–|
    | | |
    |– AT+CIPSEND=4 ————>| |
    |<————- ">" ———–| |
    | | |
    |– "qwer\\r\\n" ————–>| |
    |<———- SEND OK ———-| |
    | |——- 转发"qwer" ———–>|
    | | |

    5.2 TCP接收流程(代码已实现)

    电脑TCP服务器 ESP8266 单片机
    | | |
    |——- 发送"L10" ———–>| |
    | |– +IPD,4:L10 —————->|
    | | |– 解析指令
    | | |– LED1 = 0
    | | |
    |——- 发送"L11" ———–>| |
    | |– +IPD,4:L11 —————->|
    | | |– 解析指令
    | | |– LED1 = 1

    六、TCP服务器配置(电脑端)

    6.1 使用网络调试助手

  • 打开网络调试助手,选择TCP Server模式

  • 设置端口号:可自定义(如5132、8080等)

  • 点击"开始监听" ,按钮变为红色表示成功

  • 6.2 确保同一网段

    关键步骤:

  • 先用串口助手向ESP8266发送AT+CIFSR查询IP地址

  • 返回示例:

  • +CIFSR:APIP,"192.168.4.1"
    +CIFSR:STAIP,"192.168.31.151" ← 这是ESP8266的STA IP

  • 电脑网络调试助手中选择的服务器IP,前三个数字必须与STA IP相同(即同一网段)

    • 如果STA IP是192.168.31.151,服务器IP应为192.168.31.xxx

    • 如果STA IP是192.168.43.xxx,服务器IP应为192.168.43.xxx

  • 💡 什么是网段? IP地址192.168.31.151中,前三个数字192.168.31就是网段。同一网段的设备才能直接通信。

    6.3 常见网络问题

    问题解决方法
    WiFi必须是2.4G ESP8266不支持5G WiFi
    WiFi名字不能有中文 改为英文或数字
    电脑防火墙阻挡 关闭电脑网络防火墙
    苹果手机开热点 建议用安卓手机开2.4G热点
    校园网不行 校园网通常有隔离,建议用手机热点
    服务器点不开 检查端口是否被占用,换个端口试试

    七、实验常见问题与避坑指南

    7.1 ESP8266模块发热发烫

    正常现象!ESP8266在工作时会有一定发热,只要不是异常高温都属正常。

    7.2 代码烧录失败(ESP8266连接单片机时)

    现象:连接ESP8266后,单片机代码烧录失败。

    原因:ESP8266的串口占用了单片机的TX/RX引脚,影响了烧录通信。

    解决方法:烧录代码时先拔掉ESP8266模块,烧录完成后再插上。

    7.3 AT指令返回ERROR

    检查清单:

  • 波特率是否匹配?(单片机9600 vs ESP8266是否也是9600)

  • AT指令结尾是否有\\r\\n?

  • 指令中是否有多余空格?

  • WiFi密码是否正确?

  • ESP8266是否已经连接上WiFi?(用AT+CIFSR查询)

  • 7.4 收不到+IPD数据

    可能原因:

  • TCP连接未建立成功(检查AT+CIPSTART返回)

  • 服务器没有发送数据

  • 串口中断未正确配置

  • esp_recv_buf数组越界导致数据丢失

  • 7.5 数组越界问题(代码优化点)

    原代码中esp_recv_buf大小为15,但接收数据时如果没有边界检查,可能导致数组越界。优化代码中加入了:

    recv_count = recv_count % 14; // 确保recv_count始终在0~13之间

    7.6 大小写识别问题

    ESP8266返回的OK是大写,ok不会被识别。代码中判断条件esp_recv_buf[0] == 'O' && esp_recv_buf[1] == 'K'严格匹配大写。

    八、代码优化建议

    8.1 增加超时机制

    当前代码使用do-while无限等待OK,如果ESP8266一直不返回,程序会卡死。建议增加超时计数:

    uint timeout = 0;
    do {
    UART_Send_Str("AT+CWMODE=3\\r\\n");
    Delay_xms(500);
    timeout++;
    if(timeout > 20) break; // 10秒超时
    } while(esp_flag);

    8.2 使用状态机管理AT指令

    当前代码顺序执行,可读性一般。可以使用状态机+switch-case管理:

    enum {STATE_INIT, STATE_SET_MODE, STATE_CONNECT_WIFI, STATE_CONNECT_TCP, STATE_SEND_DATA};
    uint8_t state = STATE_INIT;

    while(1) {
    switch(state) {
    case STATE_INIT:
    UART_Send_Str("AT+CWMODE=3\\r\\n");
    state = STATE_SET_MODE;
    break;
    // …
    }
    }

    8.3 使用环形缓冲区

    当前代码使用静态数组存储接收数据,对于大量数据可能不够用。可以使用环形缓冲区:

    #define BUFFER_SIZE 64
    typedef struct {
    uint8_t data[BUFFER_SIZE];
    volatile uint8_t head;
    volatile uint8_t tail;
    } RingBuffer;

    8.4 增加AT指令响应完整性判断

    当前只判断OK,实际ESP8266可能返回ERROR、FAIL等。建议增加更多判断:

    if(strstr(esp_recv_buf, "OK") != NULL) {
    esp_flag = 0;
    } else if(strstr(esp_recv_buf, "ERROR") != NULL) {
    // 处理错误
    }

    九、总结

    通过本文的讲解,你应该已经掌握了:

  • ESP8266的三种工作模式:Station、AP、混合模式

  • 透传与非透传的区别:透传无需每次指定长度,非透传需要

  • 波特率匹配的重要性:51单片机用9600,ESP8266需从115200改到9600

  • TCP发送流程:设置模式→连接WiFi→建立TCP连接→发送数据

  • TCP接收与解析:识别+IPD格式,提取控制指令

  • 常见踩坑点:WiFi必须2.4G、同一网段、AT指令结尾加\\r\\n

  • 整个实验的核心逻辑是:51单片机通过串口发送AT指令控制ESP8266,ESP8266负责WiFi通信。理解了这一点,你就可以在这个基础上扩展更多功能——比如温湿度上传、远程开关控制、智能家居等。

    希望这篇教程能帮助你顺利完成ESP8266的TCP通信实验!如有问题,欢迎交流讨论。

    赞(0)
    未经允许不得转载:171主机测评 » 51单片机 + ESP8266 TCP通信实战:从零实现WiFi远程控制
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址