开发者学堂课程【物联网平台开发全栈教程:AT 指令做 MTQQ 连接】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/562/detail/7701
AT 指令做 MTQQ 连接
基本内容
一、 单片机程序的实现
二、 简单的子函数
一、单片机程序的实现
1、AT 指令
AT 指令的说明书可以在网上找到,在百度搜索“上海庆科”-找到“官网”-找到“硬件”-找到“EM2086”-然后点击“AT 固件说明及下载”-进入页面之后点击左侧的“指令说明”-就可以直接的找到相关的 AT 指令该怎么使用。也可以在官网首页将页面滑动到下方,点击“通用指令条例”。这节课主要讲述的是“Socket 通信条例”。
2、Socket 通信条例
目前该课程是工作在 STATION 模式下的 TCP 客户端, 就是要去连接下其他发出的 WIFI 信号,然后慢慢地去参考模式,在参考模式的最后是透传模式, 透传模式最后就是数据的双向通信。
(1)UDP 广播(服务器端)
(2)单播(客户端)
3、单片机程序里面主要新建了一个 WIFI.c,在里面可以首先看 WIFI.h,在 WIFI.h 里面可以学习到它实现了什么功能。
a#ifndef __WIFI_H
,
#define __WIFI_H
,
#include
//初始化 WIFI,包括链接 WIFI,进入 TCP 透传,设置 SSID 等参数
void Init WIFI (void) ;
(1)第一个最重要的是初始化 WIFI,在初始化 WIFI 之前,先做一个简单的工作,编译了一个程序,这个程序是发送AT 指令的函数。
发送 AT 指令:
//发送 AT 指令.这里会等待回复的 OK,否则会重发
void Send AT (unsigned char *Str)
//首先这个字符串是以地址的形式传参数过来的,传输过来之后同 WIFI 发送字符串这个子函数将对于的AT指令发出去,发出去之后将在这个死循环里一直等待,等待串口收到。
{
unsigned char Dat=0;
unsigned char Count=0;
unsigned char Loop Count=0;
unsigned char ReSend Count=0;
WIFITSend str(Str);
//先通过串口发送出去
while(1)
//等待回复
{
Delaylms(50) ;
Loop Count++;
if (Loop Count >= 100)
{
ReSend Count++;
if (ReSend Count< 3 )
{
//在交互的时候,每次发送一个信号,就会返回一个 ok;
如果没有返回 ok,那就继续重复发送;这里简单的定义一下,重复发送三次。
//当发送了三次,失败之后,该程序就会退出。这里就是检测有没有 OK 产生。简单理解就是 Send_AT 就是这个作用,它要保证数据可靠一点的发送过去,这样写的程序不是工业级的应用,只是做一个参考,简单的用法。这里只是简单的介绍。
Loop_Count = 0;
send_SErl
("\r\n 重发指令: ");
Send_Strl (str) ;
send_Strl ("\r\n") ;
WIFI_Send str(str);
//重发一遍
}
else
{
send_SErl
("\r\n发送失败: ");
Send_Strl (str) ;
Send Strl ("lrn") ;
return;
//重发失败,退出
}
}
if(Get_ Byte_ WIFI (&Datq )
{
if (Dat == 'O' )
{
Delaylms (20) ;
Get Byte WIFI (&Dat) ;
if (Dat == 'K')
{
send strl
("\r\n成功执行一条指令: ");
Send Strl (Str) ;
send strl ("\r\n") ;
return;
}
}
}
二、简单的子函数
下面又写了几条简单的子函数;
1、拼接两条字符串,把字符串2添加到字符串1后面;
它也是按照地址的形式传参的,在传参的时候,所定义的字符串1, 它在内存里的空间要足够的大, 不能说将字符串2添加到字符串1后面就溢出了, 数据在内存里就会出现问题, 正常情况下如果要写这个函数如果要做到通用, 最好在程序里面限制字符串1和字符串2的长度, 但是这个子函数是针对于这个特殊场合所使用的,要必须非常清楚字符串1和字符串2的大小,才可以不做限制。
2、拼接两条字符串,把字符串2添加到字符串1前面;
3、初始化 WIFI,包括链接 WIFI,进入 TCP 透传,设置 SSID 等参数
void Init WIFI (void)
{
unsigned char DataBuf [256];
// 定义一个空间,保证它是256字节的, 所有的内容加起来不可能超过256,将字符串2添加到字符串1后面就溢出了,这样就查不出哪个步骤出现了问题。
unsigned char DataBuf2[30];
//主要是给 WIFI 密码用的, 所以限定了密码的最大长度是30
unsigned int DataLen=0;
unsigned int i=0;
//首先发送,退出透传模式 ; 不管程序有没有进入,先退出;
Send AT("+++") ;
Send AT("ATr\n\") ;
// 然后发送两个 AT;
Send AT ("ATr\n\") ;
//关闭回显;
//回显就是正常情况下发送一个程序,然后模组返回一个程序; 如果不希望它返回什么程序过来,将它关闭就可以了;
Send AT ("AT+UARTE=OFFlrn") ;
//打开 TCP 的 EVENT(事件)
// 刚进入透传模式的时候,这个事件没有被关闭, 在打开的状态,这个事件就会被发送过来,会影响透传,本身是透传,突然来了一个事件,此时将无法判断这是模组发的事件还是服务器发的透传数据,所以说到后面会在合适的场合把它给关掉。
Send AT ("AT+WEVENT=ON\r\n") ;
Send AT ("AT+CIPEVENT=ON\r\n") ;
//首先关闭 TCP1通道,用于调试使用 ;
Send AT ("\r\n AT+CIPSTOP=1\r\n") ;
//这里是关闭 TCP 通道,它是干什么用的?首先要设置 TCP1,之前要关闭它,在这里,就不管它有没有打开,反正最后要关闭它,如果之前打开过,那么正好关掉。如果之前什么都没有操作,那反正要关闭一次,为了保证万一哪天打开了 TCP1,一定要在设置它之前把它给关掉,这里要注意,这是个1,之后要和这个1有关系的。
//查看 Flash 内容,WIFI 的 SSID 部分,如果是 FF 或00,表示第一次开机 ,那么就会直接进入死循环,执行 CLI 逻辑。CLI 逻辑执行的目的就是为了让 MQTT 这个小工具相关的逻辑去执行,因为只有它打开之后,才可以读取单片机的数据。接着写一些内容在 MQTT 这个小工具的 flash 里面。
DataLen = Read One Byte (SSID Addr) ;
DataLen <<= 8;
DataLen += Read One Byte (SSID Addr+1) ;
if( (DataLen == 0XFFFF) I (DatlaLen == 0) )
{
Send Strl
(“\r\n 设备第一次开机,请进行设置! \r\n");
while (1)
{
CLI();
//执行 CLI 逻辑
}
}
按下 reset 复位,就可以跳过这些,程序再次执行的时候就会跳过“查看 Flash”。
//设置模块做 TCP Client 的参数
Read_Flash_Message(MQTT_URL_Addr,DataBuf,&DataLen);
// 此时设置了 MQTT 里面的地址;并且读取 flash 里面的信息,
4、例子:打开 Message 函数
//保存内容到 Flash
void Save Flash Message (unsigned int Addr, unsigned char *Buf, unsigned int Len)
{
unsigned int i=0;
Erase_ IAP (Addr);
//擦除对应扇区
if(Len >= 511)
Len = 511;
// 首先在地址前面的两个字节中写入长度, 意思是指这个 Buf 有多长,将它放入最前面。
write_ One_ Byte (Addr, Len>>8);
//长度写入首地址
write One_ Byte (Addr+1, (Len&0X00FF));
//长度写入首地址
for(i=0;i
//顺序存储有效信息
// 接着从第三个字节开始往里面保存有效信息, 只有这样保存信息,才能够如下列程序中所写的那样读取数据。
{
write_ One_ Byte ( (Addr+2+i),Buf [i] ) ;
}
//从 flash 里面读取信息
void Read Flash Message (unsigned int Addr , unsigned char *Buf, unsigned int *Len)
第一个参数是地址,这个是 flash
里面的地址, 第二个参数是缓冲区域,就是读取之后要放入缓冲区里, 第三个参数是长度,长度也需要放在缓冲区里面传参。
// 分析这里面的程序就会发现:首先,在 flash 的地址里面读取长度,flash 的前面两个字节其实是在 flash 里面存入的信息,必须所含有的一个数字。
{
unsigned int i-0;
*Len - Read One_ Byte (Addr);
// 首先读取长度信息
*Len <<= 8;
*Len Read_ One_ Byte (Addr+1) ;
// 将长度进行拆分,放入 flash 的前面两个字节中,接着从,flash 的前面两个字节开始就是 Buf。这就是所有的内容。最前面的两个字节被跳过了,这样写程序必须有个前提:怎样读取数据,就必须怎样保存数据。
if(*Len >= 511)
*Len = 511;
for(i-0;i<*Len;i++)
{
Buf [i] . Read_ One_ Byte (Addz+2+i) ;
}
问:这些页数是从哪里来的?
答:观察单片机的手册,在第九章 EEPROM 的应用,该程序是15W4K32S4系列的,型号是4K48S4,4K48S4的EEPROM 有10K 个字节,从 IAP 指令读取的时候是从0000开始的,它有20个扇区,每一个扇区有520个字节才够用,所以说,每一个扇区存放一个内容在该程序中完全可以;在程序中读取的时候直接使用宏定义来读取,这样方便程序设计者进行程序设计,比如说 MQTT_UL 它的地址就是
0X0400,这样就可以方便理解 Buf 的长度是如何保存的,这样就可以知道在发送的时候 Buf 它的长度是多大, 缓冲区到底有多大,每一次都有长度,这样会很方便编程,比如说在前面添加或者在后面添加的时候,都会有一个DataBuf,包括长度在哪里会有用?其实只在发送的时候有用,比如发送 connect,就可以根据长度一个字节一个字节的将程序发送,此时,一定是正确的。
Sum_str_Tail(DataBuf,",1883r\n\");
//在后面加上
// 1883是它的端口是固定的一个数据;
Sum_str_Header(DataBuf,"AT+CIPSTART=1,tcp_client,") ;
// 在前面加上
send AT (DataBuf);
//AT指令发射出去
//链接 WIFI 到路由器 station 模式,它主要读取的是 SSID 和 WIFI Pass Addr, 下面的这两行代码和在“设置模块做TCP Client 的参数”中的代码会在接下来的课程中讲解,
Read Flash Message (SSID Addr, DataBuf, &DataLen) ;
Read Flash Message (WIFI Pass Addr, DataBuf2, &DataLen) ;
Sum Str Header (DataBuf, "AT+WJAP=") ;
Sum str Tail (DataBuf,",") ;
Sum Str Tail (DataBuf, DataBuf2) ;
Sum str Tail (DataBuf, "lrn") ;
Send AT (DataBuf) ;
Clear WIFI() ;
//等待 TCP 链接成功事件 ,此时该程序认为已经连接到了可靠的 Wi-Fi, 等待程序发送一个 EVENT 事件过来;
while (l)
{
Delaylms(10) ;
if (Get_ Mess_ Count_ WIFI () )
{
DataLen=0;
while (Get_ Byte_ WIFI (&DataBuf [DataLen++]) )
{
Send Data1 (DataBuf [DataLen-1]) ;
}
DataBuf [DataLen++] =0;
if (Compare str(DataBuf, "SERVER, CONNECTED") != 255 )
{
// Compare str 这是一个子函数, 它是比较字符串, 他就是比较一下再 str1里面有没有包含完整的 str2, 如果有的话就会反应 str2在 str1里面的位置, 如果没有就会直接返回255 。所以需要对比一下是不是255, 如果不是255那就说明里面包含了完整的 SERVER, CONNECTED, 然后就会继续进行下面的相关操作。
// 上面这段代码会一直检测它是否发送了一个 EVENT 事件过来?
(1)发送 CONNECT 报文,并等待服务器返回代码。
void MQTT connect (void) ;
{
unsigned char DataBuf [256] ;
unsigned int DataLen=0;
unsigned char Dat=0;
unsigned int i=0;
Delaylms (2000) ;
//首先需要延时,因为刚刚延时成功,延时2秒左右, 这个不一定准确,它是大概的2秒,因为有可能是1.几秒,
//先发送断开链接,断开链接是1000,再讲解 MQTT 的时候已经讲解了这部分内容,
unsigned char MQTT_Heart[]={0xc0, 0x00};
unsigned char MQTT_Heart_Reply[]={0xd0, 0x00};
unsigned char MQTT_Disconnect[]={0xe0, 0x00,};
unsigned int MQTT_Heart_Count=0;
unsigned int MQTT_PUB_Count=0;
//防止上次链接还在线;不管他有没有连接成功,先断开一次。
for(i=0 ; i<2 ; i++)
{
WIFI_Send_Byte (MQTT_DisConnect[i]) ;
}
Clear WIFI();
//清空 WIFI 接收缓冲区
Delaylms (2000) ;
//链接 MQTT 服务器
Read_Flash_Message(MQTT_Connect_Addr,DataBuf,&DataLen);
//读取 MQTT 报文;
//此时发送 connect 里面的报文,读取 Flash 里面的报文到 DataBuf;将 DataBuf 发送出去,
for(i=0;i
{
WIFI Send Byte (DataBuf[i]) ;
}
//等待服务器返回
Delaylms (2000) ;
Dat = 0;
while(Get_Byte_WIFI (&DataBuf [Dat++]));
//获取接受缓冲区数据
if ( (DataBuf [0]==0X20) & (DataBuf [1]==0X02) )
{
Send Strl
("r\n\ 链接 MQTT 服务器成功 \r\n”)
// 等待服务器返回20020000, 如果有20020000则成功,如果没有20020000则失败,
LED3 = 1;
}
else
{
Send_ Strl
("r\n\ 链接 MQTT 服务器失败 \r\n”) ;
for(i=0;i
{
WIFI_Send_Byte (DataBuf[i]) ;
}
Delaylms (2000) ;
}
//订阅主题 服务器设置设备属性用来接收服务器下发的消息
Read_Flash_Message(MQTT_Sub_Addr, DataBuf, &DataLen) ;
//读取 MQTT 报文,从 flash 里面读出主题,DataLen 为主题的长度,DataBuf 为主题的发送区,
for(i=0; i
{
WIFI_ Send Byte (DataBuf[i]) ;
//通过 WIFI 将它发送出去,
}
//等待服务器返回
Delaylms (2000) ;
Dat=0;
while(Get_ Byte_ WIFI (&DataBuf [Dat++]));
//获取接受缓冲区数据
if( (DataBuf [0]==0X90) & (DataBuf[l]==0X03))
{
Send strl
("r\n\ 订阅属性成功\r\n”);
}
else
{
Send strl
("r\n\ 订阅属性失败\r\n");
//发送出去之后检查是否有9003,有9003成功,没有9003失败;
for(i=0;i
{
WIFI_Send Byte (DataBuf [i]) ;
{
Delaylms (2000) ;
}
发送完之后就会进入主循环了,打开 WIFI.c 程序文件。
CLI();
//执行 CLI 逻辑
Send_ Heart();
//定时发送心跳包.
Analyze_ MQTT_ Read();
//解析服务器下发的消息
Pub_ Temperature();
//定时,上报温度信息
其实,这些程序都是极其简单的逻辑,容易理解, 在讲解了理论知识之后,逻辑无非就是编写代码比较浪费时间, 最重要的一点就是如何把数据从 flash 里面读取出来。
(2)发送心跳放到主循环中定时器标志位到了就发送心跳。
void Send Heart (void) ;
(3)接收MQTT服务器发送过来的数据,解析服务器下发的数据。
void Analyze MQTT Read (void) ;
(4)定时,上报温度数据。
void Pub_ Temperature(void);