1. 通过蓝牙控制LED(217.62)
2. 蓝牙概述(218.63)
- www.hc01.com/HCbluetooth.apk
- 蓝牙模块,蓝牙串口模块
串口透传技术
透传即透明传送,是指在数据的传输过程中,通过无线的方式这组数据不发生任何形式的改变,仿佛传输过程是透明的一样,同时保证传输的质量,原封不动地到了最终接收者手里。
- 以太网、蓝牙、Zigbee、GPRS 等模块玩法一样,对嵌入式程序员来说,不需关心通讯模块内部数据及协议栈工作原理,只要通过串口编程获得数据即可
2. AT指令修改蓝牙名字(219.64)
- 正确连接ttl-usb工具
- VCC–>3.3V/5V
- GND–>GND
- TXD–>RXD
- RXD–>TXD
- 波特率设置为9600,不要勾选发送新行
- 关闭手机的蓝牙以及蓝牙调试助手
- 发送AT+NAME=Jessie-BLE
3. WiFi模块课程目标概述(220.65)
- ESP8266 官网
- https://www.espressif.com.cn/zh-hans/products/socs/esp8266
- ESP8266-01S
4. wifi模块的AT指令联网数据交互(221.66)
- 蓝牙、ESP-01s、Zigbee、NB-Iot 等通信模块都是基于 AT 指令的设计
- 接线:
4.1 AT指令
- AT 指令集 是从终端设备(Terminal Equipment,TE)或数据终端设备(Data Terminal Equipment,DTE)向终端适配器(Terminal Adapter,TA)或数据电路终端设备(Data Circuit Terminal Equipment,DCE)发送的。
- 其对所传输的数据包大小有定义:即对于AT指令的发送,除AT两个字符外,最多可以接收1056个字符的长度(包括最后的空字符)。
- 每个AT命令行中只能包含一条AT指令。对于由终端设备主动向 PC 端报告的 URC 指示或者 response 响应,也要求一行最多有一个,不允许上报的一行中有多条指示或者响应。AT指令以回车作为结尾,响应或上报以回车换行为结尾。
4.2 初始配置和验证
ESP-01s出厂波特率正常是115200, 注意:AT指令、控制类都要加回车(即勾选发送新行),数据传输时不加回车
-
上电后,通过串口输出一串系统开机信息,购买的部分模块可能电压不稳,导致乱码,以 ready 为准(也可能不是 ready,本人的是 invalid)
- AT+RST(重启,上次如有连接WiFi,此次会自动连接WiFi)
- AT+RST(重启,上次如有连接WiFi,此次会自动连接WiFi)
-
上电后发送 AT指令测试通信及模块功能是否正常
-
AT
-
通过命令配置成9600波特率
- AT+UART=9600,8,1,0,0
- AT+UART=9600,8,1,0,0
4.3 入网设置(AT指令不用特意去记忆,需要时看手册即可)
- 设置工作模式
AT+CWMODE=3 //1. 是station(设备)模式 2.是AP(路由)模式 3.是双模
OK
- 以设备模式接入家中路由器配置
AT+CWJAP="Jessie","12345678" //指令
WIFI CONNECTED //结果
WIFI GOT IP //结果
- 查询IP地址
AT+CIFSR //指令
+CIFSR:APIP,"192.168.4.1"//esp作为路由器的网关
+CIFSR:APMAC,"4a:3f:da:6a:a5:4b"
+CIFSR:STAIP,"192.168.2.37"
+CIFSR:STAMAC,"48:3f:da:6a:a5:4b"
OK
4.4 连接到 TCP server
-
开关网络助手,设立TCP服务器
- 端口号设置为1到65535中的任意一个
2. 连接服务器
AT+CIPSTART="TCP","192.168.0.113",8888 //指令,注意双引号逗号都要半角(英文)输入,带回车
CONNECT //结果:成功
OK //结果:成功
- 发送数据
AT+CIPSEND=4 // 设置即将发送数据的长度 (这里是4个字节),带回车
>CLCA // 看到大于号后,输入消息,ABCD,不要带回车(取消勾选换新行)
Response :SEND OK //结果:成功
//这种每次发送前都要先发送AT+CIPSEND=长度 的指令,再发数据。即每发一次数据,发一次此指令
4.5 透传
- 上一节每次发送数据都要进行字符长度设定,如果设置成透传,就有点像蓝牙模块的玩法,在4.3.2步之后
AT+CIPMODE=1 //开启透传模式,带回车
Response :OK
AT+CIPSEND //带回车
Response: > //这个时候随意发送接收数据咯
- 退出透传模式
- 在透传发送数据过程中,若识别到单独的⼀包数据 “+++”,则退出透传发送//退出消息互传,进入at指令模式
4.6 问题解决
- 指令报错:双向传输,esp这边可以接收,但是发送时如遇到link is not valid:
- 原因是连接方挂了,通信断开。重新连接TCP服务器就解决了。
- 原因是连接方挂了,通信断开。重新连接TCP服务器就解决了。
- 指令报错:AT+CIPSEND ERROR,开启透传,开始发送时报错
- 重启,重新连接TCP服务器
- 重启,重新连接TCP服务器
5. 单片机发送AT指令实现联网(222.67)
- 先用串口助手验证单片机中的AT指令代码是否写的正确:(直接单片机连电脑)
- 可对比英文符号、AT字符大小写格式等
- 确认无误后,“白盒测试”,看电脑中串口助手的接收窗结果:
- 单片机串口中的TX-esp8266的RX,8266的TX-电脑的RX
-
- 代码(15./wifi模块01_AT指令)
#include "reg52.h"
#include "intrins.h"//<.h>会首先在标准库路径下查找头文件,
#include <string.h> //而".h"会首先在当前工作目录或指定的包含路径下查找。
#define SIZE 12
sfr AUXR = 0x8E;
sbit D5 = P3^7;
char cmd[SIZE];
char reset[] = "AT+RST\r\n";//重启
code char connIn[] = "AT+CWJAP=\"Jessie\",\"88888888\"\r\n";/* 转义:在每个"之前加\ 连接WiFi网络*/
code char connSer[] = "AT+CIPSTART=\"TCP\",\"192.168.2.9\",8888\r\n";//记得发送新行 连接TCP服务器
//overflow溢出时,用关键字code解决
char tcMode[] = "AT+CIPMODE=1\r\n";//打开透传模式
char dataTrans[] = "AT+CIPSEND\r\n"; //开始数据传送
void UartInit()
{
AUXR = 0x01;//降低单片机时钟对外界的电磁辐射(EMD
SCON = 0x50;//选择串口工作方式1,REN使能接收//PCON默认的复位就是0,所以可以不用写
TMOD &= 0x0F;//高八位清0,第八位不变
TMOD |= 0x20;//高八位变为0010,第八位不变//定时器1工作在8位自动重装载
TH1 = 0xFD;
TL1 = 0xFD;//9600波特率根据公式 算出的定时器1的初始值
TR1 = 1;//定时器1开始工作
EA = 1;//开启总中断
ES = 1;//开启串口中断
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void sendByte(char data_msg)
{
SBUF=data_msg; //移位寄存器会耗时
//Delay10ms(); //不准确的延时
//while(TI == 0);//智能延时
while(!TI); //等待串口发送完成
TI=0;
}
void sendString(char* str)//直接指向字符串的内存地址
{
while(*str != '\0'){//字符串的结尾是\0
sendByte(*str);
str++;
}
}
void main()
{
D5=1;
//配置C51串口的通信方式
UartInit();
while(1)
{ //像心跳包
//Delay1000ms();
//往发送缓冲区写入数据,即完成数据的发送
//sendString("Hello,Jessie!\r\n");//串口中要写\r\n
sendString(reset); //重启
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
sendString(connIn); //连接路由器网络
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
sendString(connSer); //连接TCP服务器
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
sendString(tcMode); //打开透传模式
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
sendString(dataTrans);//开始数据传送
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
}
}
void UartHepler() interrupt 4//中断有点像多任务线程
{
static int i=0; //静态变量,只被初始化一次
if(RI) //中断处理函数中,对于接收中断的响应
{
RI=0; //须软件复位,清除接收中断标志位
cmd[i]=SBUF; //读数据给cmd
i++;
if(i==SIZE) //使用循环缓冲区的方式存储数据
{
i=0;
}
if(strstr(cmd,"open")){
D5=0; //点亮D5
i=0;
memset(cmd,'\0',SIZE);
}
if(strstr(cmd,"close")){
D5=1; //熄灭D5
i=0;
memset(cmd,'\0',SIZE);
}
}
if(TI);
}
6. 通过网络TCP通信控制LED(223.68)
- 代码(15./wifi模块02_通过TCP通信点灯)
#include "reg52.h"
#include "intrins.h"//<.h>会首先在标准库路径下查找头文件,
#include <string.h> //而".h"会首先在当前工作目录或指定的包含路径下查找。
#define SIZE 12
sfr AUXR = 0x8E;
sbit D5 = P3^7;
char cmd[SIZE];
char reset[] = "AT+RST\r\n";//重启
code char connIn[] = "AT+CWJAP=\"Jessie\",\"88888888\"\r\n";/* 转义:在每个"之前加\ 连接WiFi网络*/
code char connSer[] = "AT+CIPSTART=\"TCP\",\"192.168.2.9\",8888\r\n";//记得发送新行 连接TCP服务器
//overflow溢出时,用关键字code解决
char tcMode[] = "AT+CIPMODE=1\r\n";//打开透传模式
char dataTrans[] = "AT+CIPSEND\r\n"; //开始数据传送
void UartInit()
{
AUXR = 0x01;//降低单片机时钟对外界的电磁辐射(EMD
SCON = 0x50;//选择串口工作方式1,REN使能接收//PCON默认的复位就是0,所以可以不用写
TMOD &= 0x0F;//高八位清0,第八位不变
TMOD |= 0x20;//高八位变为0010,第八位不变//定时器1工作在8位自动重装载
TH1 = 0xFD;
TL1 = 0xFD;//9600波特率根据公式 算出的定时器1的初始值
TR1 = 1;//定时器1开始工作
EA = 1;//开启总中断
ES = 1;//开启串口中断
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void sendByte(char data_msg)
{
SBUF=data_msg; //移位寄存器会耗时
//Delay10ms(); //不准确的延时
//while(TI == 0);//智能延时
while(!TI); //等待串口发送完成
TI=0;
}
void sendString(char* str)//直接指向字符串的内存地址
{
while(*str != '\0'){//字符串的结尾是\0
sendByte(*str);
str++;
}
}
void main()
{
int mark=0;
D5=1;
//配置C51串口的通信方式
UartInit();
while(1)
{
//Delay1000ms();
//往发送缓冲区写入数据,即完成数据的发送
//sendString("Hello,Jessie!\r\n");//串口中要写\r\n
if(mark==0){
sendString(reset); //重启
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
sendString(connIn); //连接路由器网络
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
sendString(connSer); //连接TCP服务器
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
sendString(tcMode); //打开透传模式
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
sendString(dataTrans);//开始数据传送
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
mark=1;
}else{
sendString("Hello,Jessie!\r\n");
Delay1000ms();
}
}
}
void UartHepler() interrupt 4//中断有点像多任务线程
{
if(RI) //中断处理函数中,对于接收中断的响应
{
RI=0; //须软件复位,清除接收中断标志位
cmd[0]=SBUF; //读数据给cmd
if(cmd[0]=='1'){
D5=0; //点亮D5
}
if(cmd[0]=='0'){
D5=1; //熄灭D5
}
}
if(TI);
}
7. 白盒方式看到连接不上的原因,调试手段(224.69)
- 此时8266串口助手和网络调试助手都只能接收,因为8266的tx没有接到上官一号
8. 优化8266使用,监测AT执行结果(225.70)
- 模拟AT指令给上官一号
- 代码(15./wifi模块03_优化连接过程)
#include "reg52.h"
#include "intrins.h"//<.h>会首先在标准库路径下查找头文件,
#include <string.h> //而".h"会首先在当前工作目录或指定的包含路径下查找。
#define SIZE 12
sfr AUXR = 0x8E;
sbit D5 = P3^7;
sbit D6 = P3^6;
char buffer[SIZE];
code char connIn[] = "AT+CWJAP=\"Jessie\",\"88888888\"\r\n";/* 转义:在每个"之前加\ 连接WiFi网络*/
code char connSer[] = "AT+CIPSTART=\"TCP\",\"192.168.2.9\",8888\r\n";//记得发送新行 连接TCP服务器
char tcMode[] = "AT+CIPMODE=1\r\n";//打开透传模式
char dataTrans[] = "AT+CIPSEND\r\n"; //开始数据传送
char reset[] = "AT+RST\r\n"; //重启模块指令
char AT_ConnIn_flag = 0; //WIFI GOT IP返回值的标志位
char AT_OK_flag = 0; //OK返回值的标志位
void UartInit()
{
AUXR = 0x01;//降低单片机时钟对外界的电磁辐射(EMD
SCON = 0x50;//选择串口工作方式1,REN使能接收//PCON默认的复位就是0,所以可以不用写
TMOD &= 0x0F;//高八位清0,第八位不变
TMOD |= 0x20;//高八位变为0010,第八位不变//定时器1工作在8位自动重装载
TH1 = 0xFD;
TL1 = 0xFD;//9600波特率根据公式 算出的定时器1的初始值
TR1 = 1;//定时器1开始工作
EA = 1;//开启总中断
ES = 1;//开启串口中断
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void sendByte(char data_msg)
{
SBUF=data_msg; //移位寄存器会耗时
while(!TI); //等待串口发送完成
TI=0;
}
void sendString(char* str)//直接指向字符串的内存地址
{
while(*str != '\0'){//字符串的结尾是\0
sendByte(*str);
str++;
}
}
void main()
{
D5=1;
D6=1;
//配置C51串口的通信方式
UartInit();
Delay1000ms(); //给espwifi模块上电时间
sendString(connIn); //发送联网AT指令并等待成功
while(!AT_ConnIn_flag);//若出现卡在“WIFI GOT IP OK”,注释此行
while(!AT_OK_flag);
//AT_ConnIn_flag=1;//若卡在联网标志位,手动开启D5灯
AT_OK_flag=0;
sendString(connSer); //发送连服务器AT指令并等待成功
while(!AT_OK_flag);
AT_OK_flag=0;
sendString(tcMode); //发送透传模式AT指令并等待成功
while(!AT_OK_flag);
AT_OK_flag=0;
sendString(dataTrans);//发送数据传输AT指令并等待成功
while(!AT_OK_flag);
if(AT_ConnIn_flag){
D5=0; //点亮D5,代表入网成功
}
if(AT_OK_flag){
D6=0; //点亮D6,代表连接服务器并打开透传模式成功
}
while(1){
Delay1000ms();//心跳包
sendString("Hello,Jessie!\r\n");
}
}
void UartHepler() interrupt 4//中断有点像多任务线程
{
static int i = 0;
char tmp;
char a;
if(RI){ //中断处理函数中,对于接收中断的响应
RI=0; //须软件复位,清除接收中断标志位
tmp=SBUF; //读数据给tmp
if(tmp=='W'||tmp=='O'||tmp=='L'||tmp=='b'){//busy p...
i=0;
}
buffer[i++]=tmp;
//入网成功的判断依据WIFI GOT IP
if(buffer[0]=='W' && buffer[5]=='G'){
AT_ConnIn_flag=1;
memset(buffer,'\0',SIZE);
}
//连接服务器等OK返回值指令的判断
if(buffer[0]=='O' && buffer[1]=='K'){
AT_OK_flag=1;
memset(buffer,'\0',SIZE);
}
//出现busy...p字样捕获
if(buffer[0]== 'b' && buffer[1]=='u'){
for(a=0;a<5;a++){
D5 = 0;
Delay1000ms();
D5 = 1;
Delay1000ms();
}
sendString(reset);
memset(buffer, '\0', SIZE);
}
//灯控指令
if(buffer[0]=='L' && buffer[2]=='1'){
D5=0;
memset(buffer,'\0',SIZE);
}
if(buffer[0]=='L' && buffer[2]=='0'){
D5=1;
memset(buffer,'\0',SIZE);
}
if(i==12)i=0;
}
if(TI);
}
- 若左边8266不返回OK,删除“AT+RST”重启代码;
若串口助手模拟指令发OK给单片机无反应,可重启STC软件;建议每次重新上电前,都关闭串口
- 若出现直接跳过“连接TCP服务器”情况,或tcp busy,可重启网络调试助手软件
- 若出现卡在 “WIFI GOT IP OK”,注释掉网络连接的标志位即可
分析和解决bug
- 会概率性的出现,模拟指令发送给单片机“WIFI GOT IP”,无反应
- 分析:W、F、G 字眼,都被识别到,系统将他们都视为了 buffer[0],导致系统卡死
尝试一
- 尝试将每种情况都单独做if
- 结果:逻辑会乱,失败
尝试二
- 用switchcase语句做每种情况
结果:直接死在WiFi GOT IP,失败
尝试三
- 删除F的情况,仅输入“WIFI G”
结果:成功
9. 优化8266,捕获联网失败的状态(226.71)
- 加入“busy p…”的情况,出现bp时,灯闪、WIFI重启
- 代码(15./wifi模块03_优化连接过程)
//出现busy...p字样捕获
if(buffer[0]== 'b' && buffer[1]=='u'){
for(a=0;a<5;a++){
D5 = 0;
Delay1000ms();
D5 = 1;
Delay1000ms();
}
sendString(reset);
memset(buffer, '\0', SIZE);
}