经过以下学习,我们掌握:
1、ESP8266介绍:可以在乐鑫科技官网查询。
2、AT指令:蓝牙,ESP-01s,Zigbee, NB-Iot等通信模块都是基于AT指令的设计。所有的指令没必要去记。
AT指令的功能就是驱动终端适配器干活,比如联网、数据交互等。
3、AT指令和普通数据的区别:
4、ESP-01S初始配置和验证:ESP-01s出厂波特率正常是115200,也有可能是9600,需要连在CH340驱动上,再与电脑进行连接,进行验证。此时PC就是终端设备,ESP-01S就是终端适配器。
0、互联网的4个协议层:从上到下依次是应用层、传输层、网络层、链路层。
网络层 | 应用层 |
1发送和接受数据包时,要注明发送者和接收者的IP地址; 2IP协议版本:IPV4:四个8位二进制数(十进制下就是0~255)例如 192.168.0.123; 3连上路由器的无线终端设备会被分配独立(但每次连接不固定)的IP地址; 4查找我的电脑在WLAN中的IP地址方法:ipconfig; 5默认网关(gateway): 连上“中国移动…服务器”的路由器可以使它的局域网连接互联网,路由器(Router)的IP地址是网关,它由上述服务器分配,一个局域网下只有一个网关; 6子网掩码(subnet):255.255.255.0 子网是巨型网络的分支,用于区分IPV4中的子网地址和设备序号,二者组成了设备的IP 7实例:用连上WiFi的电脑在互联网上打开某网站的过程:已知网站IP地址 数据包传输路线:电脑(浏览器)→路由器→中国移动服务器→互联网→某网站 |
1HTTP:Hyper Text Transfer Protocol,超文本传输协议; 2HTTP请求和响应演示:用连上WiFi的电脑在互联网上打开某网站的过程: 电脑浏览器←→互联网←→网站服务器(HTTP请求数据包→、HTTP响应数据包←); 3HTTP请求数据包: ①组成:请求行 & 请求头; ②请求行:请求方法 + 网站首页 + 协议版本,例如:GET/HTTP/1.1; ③请求头: HOST:网址 ……………… 4网站IP地址的确定: 请求数据包中的网址先被送至DNS服务器进行域名解析,之后会返回网站服务器的IP地址 5HTTP响应数据包: ①组成:响应状态行 & 响应头; ②响应状态行:协议版本 + 状态码 + 状态码含义,例如:HTTP/1.1 200 OK; ③响应头: 时间信息 Content-Type:响应体内容类型;字符集 = 类型 (例如:Content-Type: text/html; charset = UTF-8) 6注:请求行、请求头以及响应行、响应头之间用 \r\n 区分开(表示隔一行), \r\n\r\n 则表示空行,头与体之间用空行分隔。 |
传输层 | |
1TCP:Transmission Control Protocol,传输控制协议; 应用:电子邮件、文件; 2UDP:User Datagram Protocol,用户数据报协议; 应用:语音、视频、网游; |
1、设置wifi模块的工作模式:这属于网络的链路层,共有三种模式可选。指令是:AT+CWMODE=(1/2/3)
2、以设备模式联入路由器:告知wifi名字和密码。
3、查询ESP-01S的IP地址:指令是:AT+CIFSR
1、数据传输路线:我们接下来在PC上用网络调试助手构建一个基于TCP协议的服务器。接下来也是发送AT指令,让ESP8266当做客户端工作。这个时候ESP8266和外部的通讯方式分为两类。
2、网络调试助手设置:像这个服务器我们以后也能自己写,基于Linux的或者基于Windows的,通过Scoket编程,用java或者C语言编程。或者说用手机上的网络助手app也是可以的。
3、wifi模块连接TCP服务器:指令是:AT+CIPSTART="TCP","192.168.1.7",8880 (这里的IPV4地址是PC的IP地址,在PC的命令提示符中通过config指令查询)
4、向服务器发送固定字节数据:这种模式比较蠢,每次发数据都要先设置数据的长度。
5、透传模式下数据交互:我们不希望每次发数据之前都设置数据长度,同时我们也不希望受到是否发送新行的限制。在透传模式下,随便你怎么发,随便你怎么收。
1、单片机串口需要做哪些事情:终端设备从PC变成单片机(MCU),让单片机通过串口给模块发送一系列AT指令,指挥wifi模块以客户端模式工作。
注:波特率是提前用电脑通过AT指令给wifi模块配置好的,用9600。
2、白盒测试:【项目工程文件夹】
#include "reg52.h"
#include "intrins.h"
#include
#define len 12
sfr AUXR = 0x8E;
sbit ledD5 = P3^7;
char cmd[len];
char esp01s_modeSetting[] = "AT+CWMODE=3\r\n"; //配置wifi模块的工作模式
code char esp01s_connectLAN[] = "AT+CWJAP=\"CMCC-XyVF\",\"XyVFVsrz\"\r\n"; //连接上局域网络
code char esp01s_connectTCPServer[] = "AT+CIPSTART=\"TCP\",\"192.168.1.7\",8880\r\n"; //连接上TCP服务器
// code char esp01s_connectLAN[] = "AT+CWJAP=\"HUAWEI P20\",\"abcdefgh\"\r\n"; //连接上局域网络
// code char esp01s_connectTCPServer[] = "AT+CIPSTART=\"TCP\",\"192.168.199.201\",8880\r\n";//连接上TCP服务器
char esp01s_serialNet_mode[] = "AT+CIPMODE=1\r\n"; //设置透传模式
char esp01s_dataSend_waiting[] = "AT+CIPSEND\r\n"; //进入数据待发状态,正式进入透传模式
/* API1. 测试,用于每隔5秒给串口缓冲寄存器发送代表AT指令的字符串 */
void Delay5000ms();
/* API2. 初始化串口 */
void UartInit(void);
/* API3. 通过串口给PC发送一个字符 */
void sendByte(char data_msg);
/* API4. 通过串口给PC发送一个字符串 */
void sendString(char *str);
void main(void)
{
UartInit();
ledD5 = 1;
while(1){
Delay5000ms();
sendString(esp01s_modeSetting);
Delay5000ms();
sendString(esp01s_connectLAN);
Delay5000ms();
sendString(esp01s_connectTCPServer);
Delay5000ms();
sendString(esp01s_serialNet_mode);
Delay5000ms();
sendString(esp01s_dataSend_waiting);
}
}
void Uart_Routine() interrupt 4
{
static int i = 0; //静态全局区的变量,数组下标
/* 中断处理程序中,对于接收中断的响应 */
if(RI == 1){
RI = 0;
cmd[i] = SBUF;
i++;
if(i == len){
i = 0;
}
if(strstr(cmd,"en")!=NULL){
ledD5 = 0;
i = 0;
memset(cmd,'\0',len);
}else if(strstr(cmd,"se")!=NULL){
ledD5 = 1;
i = 0;
memset(cmd,'\0',len);
}
}
/* 中断处理程序中,对于发送中断的响应 */
if(TI == 1){
// 暂时不做任何事情
}
}
void Delay5000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 36;
j = 5;
k = 211;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void UartInit(void) //[email protected]
{
AUXR = 0x01;
SCON = 0x50; //8位UART,允许串口接收
TMOD &= 0xDF;
TMOD |= 0x20; //定时器8位重载工作模式
TH1 = 0xFD;
TL1 = 0xFD; //9600波特率初值
TR1 = 1;
EA = 1;
ES = 1; //开启串口中断
}
void sendByte(char data_msg)
{
SBUF = data_msg;
// Delay10ms();
while(TI == 0);
TI = 0;
}
void sendString(char *str)
{
char *p = str;
while(*p != '\0'){
sendByte(*p);
p++;
}
}
3、连接服务器失败的原因:关于指令响应时间的讨论
4、单片机处理wifi模块响应信息:
配置wifi模块的工作模式 | 连接上局域网络 | 连接上TCP服务器 | 设置透传模式 | 进入数据待发状态 |
成功:“OK” | 成功:“WIFI GOT IP” & “OK” 注:一上电,自动匹配上wifi时不会响应“OK” 失败:“FAIL” |
成功:“OK” | 成功:“OK” | 成功:“OK” |
成功标志位: AT_OK_Flag | 成功标志位:AT_ConnectLAN_Flag | 成功标志位:AT_OK_Flag | 成功标志位:AT_OK_Flag | 成功标志位:AT_OK_Flag |
灯控指令 | 亮灯:“L-1” | 灭灯:“L-0” |
习题1(优化wifi透传控制LED):【项目工程文件夹】
宏定义:
1. 定义符号常量len,用它代表用于接收SBUF中缓冲字符串的全局数组的长度: #define len 12
全局变量:
1. sfr指令直接找到AUXR寄存器: sfr AUXR = 0X8E; //因为AUXR没有在reg52.h中声明
2. sbit指令找到P3这个I/O口组的第7位P3^7,也就是D5这个LED: sbit ledD5 = P3^7;
3. sbit指令找到P3这个I/O口组的第6位P3^6,也就是D6这个LED: sbit ledD6 = P3^6;
4. “设置wifi模块工作模式的AT指令”: char esp01s_modeSetting[] = "AT+CWMODE=3\r\n";
5. “设置wifi模块连接网络的AT指令”: code char esp01s_connectLAN[] = "AT+CWJAP=\"HUAWEI P20\",\"abcdefgh\"\r\n";
6. “设置wifi模块连接TCP服务器的AT指令”: code char esp01s_connectTCPServer[] = "AT+CIPSTART=\"TCP\",\"192.168.45.201\",8880\r\n";
//5和6都要在前面加个关键词code,因为太长了
7. “设置wifi模块透传模式的AT指令”: char esp01s_serialNet_mode[] = "AT+CIPMODE=1\r\n";
8. “设置wifi模块数据传输的AT指令”: char esp01s_dataSend_waiting[] = "AT+CIPSEND\r\n";
9. 定义wifi模块响应AT指令"OK"的标志位: char AT_OK_Flag = 0;
//AT_OK_Flag的传递路线为:SBUF ——> 串口中断(中断4)——> API2: wifiModule_Config();
10. 定义wifi模块响应AT指令"WIFI GOT IP"的标志位: char AT_ConnectLAN_Flag = 0;
//AT_ConnectLAN_Flag的传递路线为:SBUF ——> 串口中断(中断4)——> API2: wifiModule_Config();
11. 定义一个用于接收串口中缓冲区字符串的全局数组serial_buffer: char serial_buffer[len];
//serial_buffer的传递路线为:SBUF ——> 串口中断(中断4)
1. 一上电先让指示灯D5和D6灭: ledD5 = ledD6 = 1;
2. 调用API1. 初始化串口: UartInit();
3. 调用API5. 软件延时1s,给wifi模块上电后ready的准备预留时间: Delay1000ms();
4. 调用API2. 配置wifi模块,进入透传模式: wifiModule_Config();
5. while死循环,每隔一秒通过串口给PC发送一个字符串,当做心跳包
5.1 调用API5,软件延时1s: Delay1000ms();
5.2 调用API4,给输出数据缓冲寄存器SBUF发送一条字符串: sendString("hello shuaige\r\n");
中断:
中断4: 封装串口中断的中断服务程序, void Uart_Routine() interrupt 4
4.1 定义一个静态全局区的静态变量,用来表示数组serial_buffer的下标: static int i = 0;
4.2 定义一个临时字符变量temp,用于检测关键字眼,保证我们的字符串是从字符数粗的第0位开始存放的。
char temp;
4.3 中断处理程序中,对于接收中断的响应,判据是RI == 1
4.3.1 在接受到1字节数据后,程序复位RI: RI = 0;
4.3.2 串口缓冲区接收到的字符先存放在临时变量temp中: temp = SBUF;
4.3.3 从数据缓冲寄存器SBUF中读到字符后,根据我们提前设计好的关键字眼,关心四件事:
"WIFI GOT IP"的'W'、 "OK"的'O'、 "L-1"的'L',"FAIL"的'F',
判据是temp=='W' || temp=='O' || temp=='L' || temp=='F'
4.3.3.1 如果是,那么需要从头开始存放: i = 0;
4.3.3.2 否则,那么什么也不做,继续往下执行
4.3.4 将temp的值保存在数组serial_buffer的第i个元素中:
serial_buffer[i] = temp;
4.3.5 偏移数组下标: i++;
4.3.6 判断字符数组serial_buffer是否存满了,判据是 i == len
//内在逻辑:由于serial_buffer长度的限制,当字符串超过len时,我们需要覆盖掉原先的字符
4.3.6.1 如果是,那么需要从头开始存放: i = 0;
4.3.6.2 否则,那么什么也不做,继续往下执行
4.3.7 通过字符数组的第0位和第5位捕捉关键字眼,判断wifi模块是否响应"WIFI GOT IP",判据是
serial_buffer[0]=='W' && serial_buffer[5]=='G'
4.3.7.1 如果是,
令标志位为1: AT_ConnectLAN_Flag = 1;
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.3.7.2 否则,那么什么也不做,继续往下执行
4.3.8 通过字符数组的第0位和第1位捕捉关键字眼,判断wifi模块是否响应"OK",判据是
serial_buffer[0]=='O' && serial_buffer[1]=='K'
4.3.8.1 如果是,
令标志位为1: AT_OK_Flag = 1;
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.3.8.2 否则,那么什么也不做,继续往下执行
4.3.9 通过字符数组的第0位和第1位捕捉关键字眼,判断wifi模块是否响应"FAIL",判据是
serial_buffer[0]=='F' && serial_buffer[1]=='L'
4.3.9.1 如果是,
用i当做循环变量,使用for循环语句闪烁D5五次,亮一秒,灭一秒
再次调用API4,通过串口发送对应入网的AT指令: sendString(esp01s_connectLAN);
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.3.9.2 否则,那么什么也不做,继续往下执行
4.3.10 通过字符数组的第0位和第2位捕捉关键字眼,判断wifi模块是否收到透传数据"L-1",判据是
serial_buffer[0]=='L' && serial_buffer[2]=='1'
4.3.10.1 如果是,
点亮D5: ledD5 = 0;
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.3.10.2 否则,如果wifi模块收到透传数据"L-0"
熄灭D5: ledD5 = 1;
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.4 中断处理程序中,对于发送中断的响应,判据是TI == 1
暂时不做任何事情
/* 一级函数:f1、f2、f4、f5 */
f1. 封装初始化串口的API: void UartInit(void);
f1.1 禁用ALE信号: AUXR = 0X01;
f1.2 让串口以方式1工作(8位UART,可变波特率),并允许串口接收: SCON = 0x50;
f1.3 让定时器1以8位重载工作模式工作:
TMOD &= 0xDF;
TMOD |= 0x20;
f1.4 根据波特率为9600,波特率不翻倍,设置定时器1的初值:
TH1 = 0xFD;
TL1 = 0xFD;
f1.5 定时器开始数数: TR1 = 1;
f1.6 开启串口中断:
EA = 1;
ES = 1;
f2. 封装配置wifi模块以客户端模式工作的API: void wifiModule_Config();
f2.1 配置wifi模块工作模式为双模式(设备模式+路由模式):
调用API4,通过串口发送对应AT指令: sendString(esp01s_modeSetting);
空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);
为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;
f2.2 配置wifi模块,进行入网设置:
调用API4,通过串口发送对应AT指令: sendString(esp01s_connectLAN);
空循环体等待,直到wifi模块响应"WIFI GOT IP": while(!AT_ConnectLAN_Flag);
为了不影响下一个条指令响应,复位标志位: AT_ConnectLAN_Flag = 0;
空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);
为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;
f2.3 配置wifi模块,连接TCP服务器:
调用API4,通过串口发送对应AT指令: sendString(esp01s_connectTCPServer);
空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);
为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;
f2.4 配置wifi模块,设置成透传模式:
调用API4,通过串口发送对应AT指令: sendString(esp01s_serialNet_mode);
空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);
为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;
f2.5 配置wifi模块,进入数据待发状态,正式进入透传模式:
调用API4,通过串口发送对应AT指令: sendString(esp01s_dataSend_waiting);
空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag); //不复位,留给后面判断
f2.6 测试代码,经COM3这个端口发给PC上的串口助手看:
sendString("wifi Module setting success!\r\n");
f2.7 测试代码,如果AT_OK_Flag==1,就点亮D6,代表成功: ledD6 = 0;
f4. 封装给PC发送字符串的API: void sendString(char *str); //形参是字符串的地址
f4.1 定义一个字符指针变量p用来保存字符串首地址: char *p = str;
f4.2 while循环,控制循环的变量是*p,当*p != '\0' 时,进入循环,进行单个字符的发送
f4.2.1 通过指针间接访问字符串字符,再调用API3. 发送单个字符: sendByte(*p);
f4.2.2 修改循环变量p的值,让指针p偏移: p++;
f5. 封装软件延时1s的API,用于每隔1秒发送心跳包,以及串口初始化后的短暂休眠: void Delay1000ms();
/* 二级函数:f3 */
f3. 封装定时给PC发送一个字符的API: void sendByte(char data_msg); //形参是字符值
f3.1 往SBUF寄存器中写入字符data_msg: SBUF = data_msg;
f3.2 根据串口发送中断触发位TI,利用空循环体暂停程序: while(TI == 0);
f3.3 程序复位TI: TI = 0;
#include "reg52.h"
#include "intrins.h"
#include
#define len 12
sfr AUXR = 0x8E;
sbit ledD5 = P3^7;
sbit ledD6 = P3^6;
char esp01s_modeSetting[] = "AT+CWMODE=3\r\n"; //配置wifi模块的工作模式
// code char esp01s_connectLAN[] = "AT+CWJAP=\"CMCC-XyVF\",\"XyVFVsrz\"\r\n"; //连接上局域网络
// code char esp01s_connectTCPServer[] = "AT+CIPSTART=\"TCP\",\"192.168.1.7\",8880\r\n"; //连接上TCP服务器
code char esp01s_connectLAN[] = "AT+CWJAP=\"HUAWEI P20\",\"abcdefgh\"\r\n"; //连接上局域网络
code char esp01s_connectTCPServer[] = "AT+CIPSTART=\"TCP\",\"192.168.45.201\",8880\r\n";//连接上TCP服务器
char esp01s_serialNet_mode[] = "AT+CIPMODE=1\r\n"; //设置透传模式
char esp01s_dataSend_waiting[] = "AT+CIPSEND\r\n"; //进入数据待发状态,正式进入透传模式
// char esp01s_reset[] = "AT+RST\r\n"; //wifi连接失败重启wifi模块
char AT_OK_Flag = 0; //关键字眼OK的标志位,用1代表AT指令响应成功,0代表AT指令响应失败
char AT_ConnectLAN_Flag = 0; //关键字眼WIFI GOT IP的标志位,用1代表AT指令响应成功,0代表AT指令响应失败
char serial_buffer[len];
/* API1. 初始化串口 */
void UartInit(void);
/* API2. 配置wifi模块以客户端模式工作,进入透传模式 */
void wifiModule_Client_Config();
/* API3. 通过串口给PC发送一个字符 */
void sendByte(char data_msg);
/* API4. 通过串口给PC发送一个字符串 */
void sendString(char *str);
/* API5. 用于每隔1秒给wifi模块发送心跳包,以及串口初始化后的短暂休眠 */
void Delay1000ms();
void main(void)
{
ledD5 = ledD6 = 1;
UartInit();
Delay1000ms(); //给wifi模块上电后ready的准备时间
wifiModule_Client_Config();
while(1){
Delay1000ms();
sendString("hello shuaige\r\n"); //心跳包
}
}
void Uart_Routine() interrupt 4
{
static int i = 0; //静态全局区的变量,数组下标
char temp;
/* 中断处理程序中,对于接收中断的响应 */
if(RI == 1){
RI = 0;//清除接收中断标志位
temp = SBUF;
if(temp=='W' || temp=='O' || temp=='L' || temp=='F'){ //从数据缓冲寄存器SBUF中读到字符后,关心四件事
i = 0;
}
serial_buffer[i] = temp;
i++;
if(i == len){
i = 0;
}
//wifi模块响应值的判断
if(serial_buffer[0]=='W' && serial_buffer[5]=='G'){
AT_ConnectLAN_Flag = 1;
memset(serial_buffer, '\0', len);
}
if(serial_buffer[0]=='O' && serial_buffer[1]=='K'){
AT_OK_Flag = 1;
memset(serial_buffer, '\0', len);
}
if(serial_buffer[0]=='F' && serial_buffer[1]=='A'){
for(i=0; i<5; i++){ //测试:入网失败后闪灯,并发送联网指令
ledD5 = 0;
Delay1000ms();
ledD5 = 1;
Delay1000ms();
}
// sendString(esp01s_reset);
sendString(esp01s_connectLAN);
memset(serial_buffer, '\0', len);
}
//灯控指令
if(serial_buffer[0]=='L' && serial_buffer[2]=='1'){
ledD5 = 0;
memset(serial_buffer, '\0', len);
}else if(serial_buffer[0]=='L' && serial_buffer[2]=='0'){
ledD5 = 1;
memset(serial_buffer, '\0', len);
}
}
/* 中断处理程序中,对于发送中断的响应 */
if(TI == 1){
// 暂时不做任何事情
}
}
void UartInit(void) //[email protected]
{
AUXR = 0x01;
SCON = 0x50; //8位UART,允许串口接收
TMOD &= 0xDF;
TMOD |= 0x20; //定时器8位重载工作模式
TH1 = 0xFD;
TL1 = 0xFD; //9600波特率初值
TR1 = 1;
EA = 1;
ES = 1; //开启串口中断
}
void wifiModule_Client_Config()
{
/* 经过白盒测试以及优化后,以下AT指令只需要发送一遍所以不需要反复配置,暂停程序直到AT指令响应成功 */
sendString(esp01s_modeSetting);
while(!AT_OK_Flag); //等待,直到wifi模块响应OK
AT_OK_Flag = 0;
sendString(esp01s_connectLAN);
while(!AT_ConnectLAN_Flag); //等待,直到wifi模块响应WIFI GOT IP
ledD5 = 0; //测试,点亮D5,代表入网成功
while(!AT_OK_Flag); //等待,直到wifi模块响应OK
AT_OK_Flag = 0;
sendString(esp01s_connectTCPServer);
while(!AT_OK_Flag); //等待,直到wifi模块响应OK
AT_OK_Flag = 0;
sendString(esp01s_serialNet_mode);
while(!AT_OK_Flag); //等待,直到wifi模块响应OK
AT_OK_Flag = 0;
sendString(esp01s_dataSend_waiting);
while(!AT_OK_Flag); //等待,直到wifi模块响应OK
sendString("wifi Module setting success!\r\n"); //测试,经COM3这个端口发给PC上的串口助手看
if(AT_OK_Flag == 1){
ledD6 = 0; //测试,点亮D6,代表连接服务器并打开透传模式
}
}
void sendByte(char data_msg)
{
SBUF = data_msg;
while(TI == 0);
TI = 0;
}
void sendString(char *str)
{
char *p = str;
while(*p != '\0'){
sendByte(*p);
p++;
}
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
5、白盒调试跟踪代码:优化代码写完之后,再按照白盒测试的套路进行调试,跟踪代码执行过程。
我们这个代码烧进去之后,由于51单片机的RX引脚没有接在wifi模块的TX引脚上,所以51单片机从接收缓冲寄存器SBUF中接收不到任何数据。所以白盒测试中的串口助手(COM6端口:CH340和wifi模块的串口)一定会卡着。所以需要我们在另一个串口助手(COM3端口:51单片机和PC的串口)中手动模拟wifi模块本来应该响应给51单片机的数据。最后测试成功后,就可以认为代码是可靠的了,可以烧录进单片机中。
注:白盒测试下不能够用透传来测试wifi控制LED,也得用串口手动模拟。
1、数据传输路线:前面4小节我们都是让wifi以客户端模式进行工作。我们接下来在PC上用网络调试助手构建一个基于TCP协议的客户端。也是给wifi模块发送AT指令,让ESP8266在路由模式下以TCP服务器模式工作。然后让PC上的TCP客户端联入wifi模块构建的局域网。这个时候ESP8266和外部的通讯方式也分为两类。
2、串口测试时的AT指令及响应(测试截图略,总的响应信息见5.3中的图):
3、与客户端的数据交互:
4、单片机配置服务器模式:这里的步骤和4.1~4.5一致。
AT指令响应成功的关键字眼及标志位 | 配置wifi模块为路由模式 | 使能多链接 | 建立TCP服务器 | 与TCP客户端建立连接 | 进入数据待发状态(5字节、通道0) |
成功:“OK” | 成功:“OK” | 成功:“OK” | 成功:“0,CONNECT” | 成功:“OK” | |
成功标志位: AT_OK_Flag | 成功标志位: AT_OK_Flag | 成功标志位: AT_OK_Flag | 成功标志位: Client_Connect_Flag | 不检测 | |
透传有效指令的关键字眼 | 灯控指令 | ||||
亮灯:“:op” 灭灯:“:cl” |
习题2(wifi模块作为服务器):在习题1的代码上做适当修改【项目工程文件夹】
宏定义:
1. 定义符号常量len,用它代表用于接收SBUF中缓冲字符串的全局数组的长度: #define len 12
全局变量:
1. sfr指令直接找到AUXR寄存器: sfr AUXR = 0X8E; //因为AUXR没有在reg52.h中声明
2. sbit指令找到P3这个I/O口组的第7位P3^7,也就是D5这个LED: sbit ledD5 = P3^7;
3. sbit指令找到P3这个I/O口组的第6位P3^6,也就是D6这个LED: sbit ledD6 = P3^6;
4. “设置wifi模块工作模式的AT指令”: char esp01s_modeSetting[] = "AT+CWMODE=2\r\n";
5. “设置wifi模块使能多链接的AT指令”: char esp01s_multiLink[] = "AT+CIPMUX=1\r\n";
6. “设置wifi模块建立TCP服务器的AT指令”: char esp01s_setTCPServer[] = "AT+CIPSERVER=1\r\n";
7. “设置wifi模块数据传输的AT指令”: char esp01s_dataSend_waiting[] = "AT+CIPSEND=0,5\r\n";
8. 定义wifi模块响应AT指令"OK"的标志位: char AT_OK_Flag = 0;
//AT_OK_Flag的传递路线为:SBUF ——> 串口中断(中断4)——> API2: wifiModule_Server_Config();
9. 定义wifi模块响应AT指令"0,CONNECT"的标志位: char Client_Connect_Flag = 0;
//Client_Connect_Flag的传递路线为:SBUF ——> 串口中断(中断4)——> API2: wifiModule_Server_Config();
10. 定义一个用于接收串口中缓冲区字符串的全局数组serial_buffer: char serial_buffer[len];
//serial_buffer的传递路线为:SBUF ——> 串口中断(中断4)——> 临时字符变量temp
1. 一上电先让指示灯D5和D6灭: ledD5 = ledD6 = 1;
2. 调用API1. 初始化串口: UartInit();
3. 调用API5. 软件延时1s,给wifi模块上电后ready的准备预留时间: Delay1000ms();
4. 调用API2. 配置wifi模块以TCP服务器模式工作: wifiModule_Server_Config();
5. while死循环,每隔4秒以两种方式(串口和透传)给PC发送一个字符串,当做心跳包
5.1 调用API4,通过串口发送数据传输的AT指令: sendString(esp01s_0Channel_dataSend);
5.2 调用API5,软件延时2s:
Delay1000ms();
Delay1000ms();
5.3 调用API4,给输出数据缓冲寄存器SBUF发送一条字符串: sendString("hello");
5.2 调用API5,软件延时2s:
Delay1000ms();
Delay1000ms();
中断:
中断4: 封装串口中断的中断服务程序, void Uart_Routine() interrupt 4
4.1 定义一个静态全局区的静态变量,用来表示数组serial_buffer的下标: static int i = 0;
4.2 定义一个临时字符变量temp,用于检测关键字眼,保证我们的字符串是从字符数粗的第0位开始存放的。
char temp;
4.3 中断处理程序中,对于接收中断的响应,判据是RI == 1
4.3.1 在接受到1字节数据后,程序复位RI: RI = 0;
4.3.2 串口缓冲区接收到的字符先存放在临时变量temp中: temp = SBUF;
4.3.3 从数据缓冲寄存器SBUF中读到字符后,根据我们提前设计好的关键字眼,关心四件事:
"OK"的'O'、 "0,CONNECT"的'0'、 "+IPD,0,n:op"的':',
判据是temp=='O' || temp=='0' || temp==':'
4.3.3.1 如果是,那么需要从头开始存放: i = 0;
4.3.3.2 否则,那么什么也不做,继续往下执行
4.3.4 将temp的值保存在数组serial_buffer的第i个元素中:
serial_buffer[i] = temp;
4.3.5 偏移数组下标: i++;
4.3.6 判断字符数组serial_buffer是否存满了,判据是 i == len
//内在逻辑:由于serial_buffer长度的限制,当字符串超过len时,我们需要覆盖掉原先的字符
4.3.6.1 如果是,那么需要从头开始存放: i = 0;
4.3.6.2 否则,那么什么也不做,继续往下执行
4.3.7 通过字符数组的第0位和第1位捕捉关键字眼,判断wifi模块是否响应"OK",判据是
serial_buffer[0]=='O' && serial_buffer[1]=='K'
4.3.7.1 如果是,
令标志位为1: AT_OK_Flag = 1;
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.3.7.2 否则,那么什么也不做,继续往下执行
4.3.8 通过字符数组的第0位和第2位捕捉关键字眼,判断wifi模块是否响应"0,CONNECT",判据是
serial_buffer[0]=='0' && serial_buffer[2]=='C'
4.3.8.1 如果是,
令标志位为1: Client_Connect_Flag = 1;
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.3.8.2 否则,那么什么也不做,继续往下执行
4.3.9 通过字符数组的第0位和第2位捕捉关键字眼,判断wifi模块是否收到透传数据"+IPD,0,n:open",判据是
serial_buffer[0]==':' && serial_buffer[1]=='o' && serial_buffer[2]=='p'
4.3.9.1 如果是,
点亮D5: ledD5 = 0;
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.3.9.2 否则,如果wifi模块收到透传数据"+IPD,0,n:close"
熄灭D5: ledD5 = 1;
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.4 中断处理程序中,对于发送中断的响应,判据是TI == 1
暂时不做任何事情
/* 一级函数:f1、f2、f4、f5 */
f1. 封装初始化串口的API: void UartInit(void);
f1.1 禁用ALE信号: AUXR = 0X01;
f1.2 让串口以方式1工作(8位UART,可变波特率),并让REN使能允许串口接收: SCON = 0x50;
f1.3 让定时器1以8位重载工作模式工作:
TMOD &= 0xDF;
TMOD |= 0x20;
f1.4 根据波特率为9600,波特率不翻倍,设置定时器1的初值:
TH1 = 0xFD;
TL1 = 0xFD;
f1.5 定时器开始数数: TR1 = 1;
f1.6 开启串口中断:
EA = 1;
ES = 1;
f2. 封装配置wifi模块以客户端模式工作的API: void wifiModule_Server_Config();
f2.1 配置wifi模块工作模式为路由模式:
调用API4,通过串口发送对应AT指令: sendString(esp01s_modeSetting);
空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);
为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;
f2.2 配置wifi模块,使能多链接模式:
调用API4,通过串口发送对应AT指令: sendString(esp01s_multiLink);
空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);
为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;
f2.3 配置wifi模块,建立TCP服务器:
调用API4,通过串口发送对应AT指令: sendString(esp01s_setTCPServer);
空循环体等待,直到wifi模块响应"OK": while(!AT_OK_Flag);
为了不影响下一个条指令响应,复位标志位: AT_OK_Flag = 0;
测试代码:点亮D5,代表成功建立TCP服务器: ledD5 = 0;
f2.4 空循环体等待,直到wifi模块响应"0,CONNECT":
while(!Client_Connect_Flag); //不复位,留给后面判断
测试代码,如果Client_Connect_Flag==1,就点亮D6,代表成功与客户端建立链接: ledD6 = 0;
f4. 封装给PC发送字符串的API: void sendString(char *str); //形参是字符串的地址
f4.1 定义一个字符指针变量p用来保存字符串首地址: char *p = str;
f4.2 while循环,控制循环的变量是*p,当*p != '\0' 时,进入循环,进行单个字符的发送
f4.2.1 通过指针间接访问字符串字符,再调用API3. 发送单个字符: sendByte(*p);
f4.2.2 修改循环变量p的值,让指针p偏移: p++;
f5. 封装软件延时1s的API,用于每隔1秒发送心跳包,以及串口初始化后的短暂休眠: void Delay1000ms();
/* 二级函数:f3 */
f3. 封装定时给PC发送一个字符的API: void sendByte(char data_msg); //形参是字符值
f3.1 往SBUF寄存器中写入字符data_msg: SBUF = data_msg;
f3.2 根据串口发送中断触发位TI,利用空循环体暂停程序: while(!TI);
f3.3 程序复位TI: TI = 0;
#include "reg52.h"
#include "intrins.h"
#include
#define len 12
sfr AUXR = 0x8E;
sbit ledD5 = P3^7;
sbit ledD6 = P3^6;
char serial_buffer[len];
char esp01s_modeSetting[] = "AT+CWMODE=2\r\n"; //配置wifi模块的工作模式
char esp01s_multiLink[] = "AT+CIPMUX=1\r\n"; //使能多链接
char esp01s_setTCPServer[] = "AT+CIPSERVER=1\r\n"; //建立TCP服务器
char esp01s_0Channel_dataSend[] = "AT+CIPSEND=0,5\r\n"; //发送5个字节数据在连接0通道上
char AT_OK_Flag = 0; //关键字眼OK的标志位,用1代表AT指令响应成功,0代表AT指令响应失败
char Client_Connect_Flag = 0; //关键字眼0,CONNECT的标志位,用1代表客户端接入成功,0代表客户端接入失败
/* API1. 初始化串口 */
void UartInit(void);
/* API2. 配置wifi模块以服务器模式工作 */
void wifiModule_Server_Config();
/* API3. 通过串口给PC发送一个字符 */
void sendByte(char data_msg);
/* API4. 通过串口给PC发送一个字符串 */
void sendString(char *str);
/* API5. 用于每隔1秒给wifi模块发送心跳包,以及串口初始化后的短暂休眠 */
void Delay1000ms();
void main()
{
ledD5 = ledD6 = 1;//灭状态灯
UartInit();
Delay1000ms();//给espwifi模块上电时间
wifiModule_Server_Config();
while(1){
sendString(esp01s_0Channel_dataSend);
Delay1000ms(); //虽然说发送也是需要时间,但是不要用检测“OK”来暂停程序
Delay1000ms();
sendString("Hello"); //服务器给客户端的心跳包(串口助手上也能看见)
Delay1000ms();
Delay1000ms();
}
}
void Uart_Routine() interrupt 4
{
static int i = 0;//静态变量,被初始化一次
char temp;
/* 中断处理程序中,对于接收中断的响应 */
if(RI)//中断处理函数中,对于接收中断的响应
{
RI = 0;//清除接收中断标志位
temp = SBUF;
if(temp == 'O' || temp == '0' || temp == ':'){
i = 0;
}
serial_buffer[i] = temp;
i++;
if(i == len) i = 0;
//wifi模块响应值的判断
if(serial_buffer[0] == 'O' && serial_buffer[1] == 'K'){
AT_OK_Flag = 1;
memset(serial_buffer, '\0', len);
}
if(serial_buffer[0] == '0' && serial_buffer[2] == 'C'){
Client_Connect_Flag = 1;
memset(serial_buffer, '\0', len);
}
//灯控指令
if(serial_buffer[0] == ':' && serial_buffer[1] == 'o' && serial_buffer[2]=='p'){
ledD5 = 0;//点亮D5
memset(serial_buffer, '\0', len);
}else if(serial_buffer[0] == ':' && serial_buffer[1] == 'c' && serial_buffer[2]=='l'){
ledD5 = 1;//熄灭D5
memset(serial_buffer, '\0', len);
}
}
/* 中断处理程序中,对于发送中断的响应 */
if(TI == 1){
// 暂时不做任何事情
}
}
void UartInit(void) //[email protected]
{
AUXR = 0x01;
SCON = 0x50; //8位UART,允许串口接收
TMOD &= 0xDF;
TMOD |= 0x20; //定时器8位重载工作模式
TH1 = 0xFD;
TL1 = 0xFD; //9600波特率初值
TR1 = 1; //启动定时器
EA = 1;
ES = 1; //开启串口中断
}
void wifiModule_Server_Config()
{
sendString(esp01s_modeSetting);
while(!AT_OK_Flag);
AT_OK_Flag = 0;
sendString(esp01s_multiLink);
while(!AT_OK_Flag);
AT_OK_Flag = 0;
sendString(esp01s_setTCPServer);
while(!AT_OK_Flag);
AT_OK_Flag = 0;
ledD5 = 0; //点亮D5,代表成功建立TCP服务器
while(!Client_Connect_Flag);
if(Client_Connect_Flag){
ledD6 = 0; //点亮D6,代表有客户端接入
}
}
void sendByte(char data_msg)
{
SBUF = data_msg;
while(!TI);
TI = 0;
}
void sendString(char *str)
{
char *p = str;
while(*p != '\0'){
sendByte(*p);
p++;
}
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}