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,之后所有内容都当作数据发送

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

透传模式的条件

  1. 必须是单连接模式AT+CIPMUX=0

  2. 仅支持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 ------ 引脚与类型定义

cpp 复制代码
#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

这里定义了:

  • ucharuint类型别名,方便代码书写

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

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

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

4.2 delay.c ------ 毫秒级延时

cpp 复制代码
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 ------ 串口通信底层

串口初始化(最关键!)
cpp 复制代码
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
TH1=0xFD 253 9600波特率初值(11.0592MHz)

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

cpp 复制代码
波特率 = (2^SMOD / 32) × (定时器溢出率)
定时器溢出率 = 晶振频率 / (12 × (256 - TH1))

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

串口发送
cpp 复制代码
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 ------ 主控逻辑

全局变量
cpp 复制代码
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函数流程
cpp 复制代码
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接收功能的核心,也是代码中最复杂的部分:

cpp 复制代码
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服务器收到数据时,会通过串口发送如下格式的数据:

cpp 复制代码
+IPD,<长度>:<数据>

实际例子

cpp 复制代码
+IPD,3:556    // 收到3个字节:556[reference:32]
+IPD,5:hello  // 收到5个字节:hello[reference:33]

我们的数据协议

cpp 复制代码
+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发送流程(代码已实现)

cpp 复制代码
单片机                         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接收流程(代码已实现)

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

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

6.1 使用网络调试助手

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

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

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

6.2 确保同一网段

关键步骤

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

  2. 返回示例:

cpp 复制代码
+CIFSR:APIP,"192.168.4.1"
+CIFSR:STAIP,"192.168.31.151"   ← 这是ESP8266的STA IP
  1. 电脑网络调试助手中选择的服务器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

检查清单

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

  2. AT指令结尾是否有\r\n

  3. 指令中是否有多余空格?

  4. WiFi密码是否正确?

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

7.4 收不到+IPD数据

可能原因

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

  2. 服务器没有发送数据

  3. 串口中断未正确配置

  4. esp_recv_buf数组越界导致数据丢失

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

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

cpp 复制代码
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一直不返回,程序会卡死。建议增加超时计数:

cpp 复制代码
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管理:

cpp 复制代码
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 使用环形缓冲区

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

cpp 复制代码
#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可能返回ERRORFAIL等。建议增加更多判断:

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

九、总结

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

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

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

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

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

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

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

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

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

相关推荐
自小吃多1 小时前
IVD设备-以GB4793.1做安规摸底
笔记·嵌入式硬件
雾削木2 小时前
B语言经典教程现代化重构
java·前端·stm32·单片机·嵌入式硬件
Hello-FPGA2 小时前
Camera Link 与 CoaXPress 技术对比 如何选择你的相机接口
单片机·嵌入式硬件
Digitally2 小时前
如何快速将文件从电脑传输到平板电脑
stm32·嵌入式硬件·电脑
2601_958352902 小时前
嵌入式对讲收音降噪难题根治方案|AP-0316语音模组原理、实测与落地教程
人工智能·嵌入式硬件·语音识别·ai降噪·回音消除·音频处理模块
济6172 小时前
BMS系统专栏:电池状态监控任务
嵌入式硬件·嵌入式·bms电池系统管理
济6173 小时前
BMS系统专栏: BMS_ProtectTask 电池保护任务
嵌入式硬件·嵌入式·bms电池管理
XTIOT6663 小时前
多形态护照 OCR 读取器传输机制、识别算法与行业落地技术对比
大数据·人工智能·嵌入式硬件·物联网·ocr
欢乐熊嵌入式编程3 小时前
选型避坑:ESP32 vs STM32+模组 vs NB-IoT,不同场景怎么选
stm32·单片机·嵌入式硬件·物联网·esp32·嵌入式iot