硬件: |
PC电脑、无线节点模块、ST-LINK仿真器、MiniUSB线 |
软件: |
阿里云物联网平台、KEIL、串口工具 |
与阿里云物联网平台的方式主要是MQTT协议,首先通过无线模块的AT指令与物联网平台建立TCP连接,随后向物联网平台发送MQTT连接请求报文,在物联网回复连接确认报文后,则成功建立MQTT连接,随后向平台发送订阅用于属性设置的主题的请求报文,成功订阅后进入下一步。需要注意的是,MQTT连接需要定时发送心跳报文,不然会超时断开连接。
在本次演示中使用了两个串口分别是 USART2 和 USART3 串口,其中串口2用于打印运行信息,串口3用于向阿里云物联网平台发送MQTT协议报文和接收物联网平台的回复报文。
3.1、首先实现对ESP8266模块的操作,主要利用模块底板上STM32的串口3与ESP8266 串口进行通信,发送AT指令的方式控制Wifi模块,包括Wifi模块配置、Wifi连接热点、Wifi使用指定协议连接到服务器等功能的实现。
/**
* 功能:检查ESP8266是否正常
* 参数:None
* 返回值:ESP8266返回状态
* 非0 ESP8266正常
* 0 ESP8266有问题
*/
uint8_t ESP8266_Check(void)
{
uint8_t check_cnt=5;
while(check_cnt--)
{
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf)); //清空接收缓冲
ESP8266_ATSendString("AT\r\n"); //发送AT握手指令
if(FindStr((char*)ESP8266_rxbuf,"OK",200) != 0)
{
return 1;
}
}
return 0;
}
/**
* 功能:初始化ESP8266
* 参数:None
* 返回值:初始化结果,非0为初始化成功,0为失败
*/
uint8_t ESP8266_Init(void)
{
//清空发送和接收数组
memset(ESP8266_txbuf,0,sizeof(ESP8266_txbuf));
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));
ESP8266_ExitUnvarnishedTrans(); //退出透传
delay_ms(500);
ESP8266_ATSendString("AT+RST\r\n");
delay_ms(800);
if(ESP8266_Check()==0) //使用AT指令检查ESP8266是否存在
{
return 0;
}
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf)); //清空接收缓冲
ESP8266_ATSendString("ATE0\r\n"); //关闭回显
if(FindStr((char*)ESP8266_rxbuf,"OK",500)==0) //设置不成功
{
return 0;
}
return 1; //设置成功
}
/**
* 功能:恢复出厂设置
* 参数:None
* 返回值:None
* 说明:此时ESP8266中的用户设置将全部丢失回复成出厂状态
*/
void ESP8266_Restore(void)
{
ESP8266_ExitUnvarnishedTrans(); //退出透传
delay_ms(500);
ESP8266_ATSendString("AT+RESTORE\r\n"); //恢复出厂
}
/**
* 功能:连接热点
* 参数:
* ssid:热点名
* pwd:热点密码
* 返回值:
* 连接结果,非0连接成功,0连接失败
* 说明:
* 失败的原因有以下几种(UART通信和ESP8266正常情况下)
* 1. WIFI名和密码不正确
* 2. 路由器连接设备太多,未能给ESP8266分配IP
*/
uint8_t ESP8266_ConnectAP(char* ssid,char* pswd)
{
uint8_t cnt=5;
while(cnt--)
{
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));
ESP8266_ATSendString("AT+CWMODE_CUR=1\r\n"); //设置为STATION模式
if(FindStr((char*)ESP8266_rxbuf,"OK",200) != 0)
{
break;
}
}
if(cnt == 0)
return 0;
cnt=2;
while(cnt--)
{
memset(ESP8266_txbuf,0,sizeof(ESP8266_txbuf));//清空发送缓冲
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));//清空接收缓冲
sprintf((char*)ESP8266_txbuf,"AT+CWJAP_CUR=\"%s\",\"%s\"\r\n",ssid,pswd);//连接目标AP
ESP8266_ATSendString((char*)ESP8266_txbuf);
if(FindStr((char*)ESP8266_rxbuf,"OK",8000)!=0) //连接成功且分配到IP
{
return 1;
}
}
return 0;
}
//开启透传模式
static uint8_t ESP8266_OpenTransmission(void)
{
//设置透传模式
uint8_t cnt=2;
while(cnt--)
{
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));
ESP8266_ATSendString("AT+CIPMODE=1\r\n");
if(FindStr((char*)ESP8266_rxbuf,"OK",200)!=0)
{
return 1;
}
}
return 0;
}
/**
* 功能:使用指定协议(TCP/UDP)连接到服务器
* 参数:
* mode:协议类型 "TCP","UDP"
* ip:目标服务器IP
* port:目标是服务器端口号
* 返回值:
* 连接结果,非0连接成功,0连接失败
* 说明:
* 失败的原因有以下几种(UART通信和ESP8266正常情况下)
* 1. 远程服务器IP和端口号有误
* 2. 未连接AP
* 3. 服务器端禁止添加(一般不会发生)
*/
uint8_t ESP8266_ConnectServer(char* mode,char* ip,uint16_t port)
{
uint8_t cnt;
ESP8266_ExitUnvarnishedTrans(); //多次连接需退出透传
delay_ms(500);
//连接服务器
cnt=2;
while(cnt--)
{
memset(ESP8266_txbuf,0,sizeof(ESP8266_txbuf));//清空发送缓冲
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));//清空接收缓冲
sprintf((char*)ESP8266_txbuf,"AT+CIPSTART=\"%s\",\"%s\",%d\r\n",mode,ip,port);
ESP8266_ATSendString((char*)ESP8266_txbuf);
if(FindStr((char*)ESP8266_rxbuf,"CONNECT",500) !=0 )
{
break;
}
}
if(cnt == 0)
return 0;
//设置透传模式
if(ESP8266_OpenTransmission()==0) return 0;
//开启发送状态
cnt=2;
while(cnt--)
{
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf)); //清空接收缓冲
ESP8266_ATSendString("AT+CIPSEND\r\n");//开始处于透传发送状态
if(FindStr((char*)ESP8266_rxbuf,">",200)!=0)
{
return 1;
}
}
return 0;
}
/**
* 功能:主动和服务器断开连接
* 参数:None
* 返回值:
* 连接结果,非0断开成功,0断开失败
*/
uint8_t DisconnectServer(void)
{
uint8_t cnt;
ESP8266_ExitUnvarnishedTrans(); //退出透传
delay_ms(500);
while(cnt--)
{
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf)); //清空接收缓冲
ESP8266_ATSendString("AT+CIPCLOSE\r\n");//关闭链接
if(FindStr((char*)ESP8266_rxbuf,"CLOSED",200)!=0)//操作成功,和服务器成功断开
{
break;
}
}
if(cnt) return 1;
return 0;
}
3.2、根据MQTT协议工作原理,编写MQTT协议驱动文件,包括MQTT协议的连接服务器的打包、订阅/取消订阅数据打包、发布数据打包等功能。
//连接成功服务器回应 20 02 00 00
//客户端主动断开连接 e0 00
const uint8_t parket_connetAck[] = {0x20,0x02,0x00,0x00};
const uint8_t parket_disconnet[] = {0xe0,0x00};
const uint8_t parket_heart[] = {0xc0,0x00};
const uint8_t parket_heart_reply[] = {0xc0,0x00};
const uint8_t parket_subAck[] = {0x90,0x03};
volatile uint16_t MQTT_TxLen;
//MQTT发送数据
void MQTT_SendBuf(uint8_t *buf,uint16_t len)
{
ESP8266_ATSendBuf(buf,len);
}
//发送MQTT心跳
void MQTT_SentHeart()
{
MQTT_SendBuf((uint8_t *)parket_heart,sizeof(parket_heart));
}
//MQTT无条件断开
void MQTT_Disconnect()
{
MQTT_SendBuf((uint8_t *)parket_disconnet,sizeof(parket_disconnet));
}
//MQTT连续服务器的打包函数
uint8_t MQTT_Connect(char *ClientID,char *Username,char *Password)
{
int ClientIDLen = strlen(ClientID);
int UsernameLen = strlen(Username);
int PasswordLen = strlen(Password);
int DataLen;
MQTT_TxLen=0;
//可变报头+Payload 每个字段包含两个字节的长度标识
DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2);
//固定报头
//控制报文类型
ESP8266_txbuf[MQTT_TxLen++] = 0x10; //MQTT Message Type CONNECT
//剩余长度(不包括固定头部)
do
{
uint8_t encodedByte = DataLen % 128;
DataLen = DataLen / 128;
// if there are more data to encode, set the top bit of this byte
if ( DataLen > 0 )
encodedByte = encodedByte | 128;
ESP8266_txbuf[MQTT_TxLen++] = encodedByte;
}while ( DataLen > 0 );
//可变报头
//协议名
ESP8266_txbuf[MQTT_TxLen++] = 0; // Protocol Name Length MSB
ESP8266_txbuf[MQTT_TxLen++] = 4; // Protocol Name Length LSB
ESP8266_txbuf[MQTT_TxLen++] = 'M'; // ASCII Code for M
ESP8266_txbuf[MQTT_TxLen++] = 'Q'; // ASCII Code for Q
ESP8266_txbuf[MQTT_TxLen++] = 'T'; // ASCII Code for T
ESP8266_txbuf[MQTT_TxLen++] = 'T'; // ASCII Code for T
//协议级别
ESP8266_txbuf[MQTT_TxLen++] = 4; // MQTT Protocol version = 4
//连接标志
ESP8266_txbuf[MQTT_TxLen++] = 0xc2; // conn flags
ESP8266_txbuf[MQTT_TxLen++] = 0; // Keep-alive Time Length MSB
ESP8266_txbuf[MQTT_TxLen++] = 60; // Keep-alive Time Length LSB 60S心跳包
ESP8266_txbuf[MQTT_TxLen++] = BYTE1(ClientIDLen);// Client ID length MSB
ESP8266_txbuf[MQTT_TxLen++] = BYTE0(ClientIDLen);// Client ID length LSB
memcpy(&ESP8266_txbuf[MQTT_TxLen],ClientID,ClientIDLen);
MQTT_TxLen += ClientIDLen;
if(UsernameLen > 0)
{
ESP8266_txbuf[MQTT_TxLen++] = BYTE1(UsernameLen); //username length MSB
ESP8266_txbuf[MQTT_TxLen++] = BYTE0(UsernameLen); //username length LSB
memcpy(&ESP8266_txbuf[MQTT_TxLen],Username,UsernameLen);
MQTT_TxLen += UsernameLen;
}
if(PasswordLen > 0)
{
ESP8266_txbuf[MQTT_TxLen++] = BYTE1(PasswordLen); //password length MSB
ESP8266_txbuf[MQTT_TxLen++] = BYTE0(PasswordLen); //password length LSB
memcpy(&ESP8266_txbuf[MQTT_TxLen],Password,PasswordLen);
MQTT_TxLen += PasswordLen;
}
//uint8_t cnt=2;
uint8_t wait;
//while(cnt--)
{
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));
MQTT_SendBuf(ESP8266_txbuf,MQTT_TxLen);
wait=30;//等待3s时间
while(wait--)
{
//CONNECT
if(ESP8266_rxbuf[0]==parket_connetAck[0] && ESP8266_rxbuf[1]==parket_connetAck[1]) //连接成功
{
return 1;//连接成功
}
delay_ms(100);
}
}
return 0;
}
/*
* MQTT订阅/取消订阅数据打包函数
* topic 主题
* qos 消息等级
* whether 订阅/取消订阅请求包
*/
uint8_t MQTT_SubscribeTopic(char *topic,uint8_t qos,uint8_t whether)
{
int topiclen;
int DataLen;
uint8_t cnt;
uint8_t wait;
MQTT_TxLen=0;
cnt=2;
topiclen = strlen(topic);
DataLen = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度
//固定报头
//控制报文类型
if(whether) ESP8266_txbuf[MQTT_TxLen++] = 0x82; //消息类型和标志订阅
else ESP8266_txbuf[MQTT_TxLen++] = 0xA2; //取消订阅
//剩余长度
do
{
uint8_t encodedByte = DataLen % 128;
DataLen = DataLen / 128;
// if there are more data to encode, set the top bit of this byte
if ( DataLen > 0 )
encodedByte = encodedByte | 128;
ESP8266_txbuf[MQTT_TxLen++] = encodedByte;
}while ( DataLen > 0 );
//可变报头
ESP8266_txbuf[MQTT_TxLen++] = 0; //消息标识符 MSB
ESP8266_txbuf[MQTT_TxLen++] = 0x01; //消息标识符 LSB
//有效载荷
ESP8266_txbuf[MQTT_TxLen++] = BYTE1(topiclen);//主题长度 MSB
ESP8266_txbuf[MQTT_TxLen++] = BYTE0(topiclen);//主题长度 LSB
memcpy(&ESP8266_txbuf[MQTT_TxLen],topic,topiclen);
MQTT_TxLen += topiclen;
if(whether)
{
ESP8266_txbuf[MQTT_TxLen++] = qos;//QoS级别
}
while(cnt--)
{
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));
MQTT_SendBuf(ESP8266_txbuf,MQTT_TxLen);
wait=30;//等待3s时间
while(wait--)
{
if(ESP8266_rxbuf[0]==parket_subAck[0] && ESP8266_rxbuf[1]==parket_subAck[1]) //订阅成功
{
return 1;//订阅成功
}
delay_ms(100);
}
}
if(cnt) return 1; //订阅成功
return 0;
}
/*
* MQTT发布数据打包函数
* topic 主题
* message 消息
* qos 消息等级
*/
uint8_t MQTT_PublishData(char *topic, char *message, uint8_t qos)
{
int topicLength = strlen(topic);
int messageLength = strlen(message);
static uint16_t id=0;
int DataLen;
MQTT_TxLen=0;
//有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度
//QOS为0时没有标识符
//数据长度 主题名 报文标识符 有效载荷
if(qos) DataLen = (2+topicLength) + 2 + messageLength;
else DataLen = (2+topicLength) + messageLength;
//固定报头
//控制报文类型
ESP8266_txbuf[MQTT_TxLen++] = 0x30; // MQTT Message Type PUBLISH
//剩余长度
do
{
uint8_t encodedByte = DataLen % 128;
DataLen = DataLen / 128;
// if there are more data to encode, set the top bit of this byte
if ( DataLen > 0 )
encodedByte = encodedByte | 128;
ESP8266_txbuf[MQTT_TxLen++] = encodedByte;
}while ( DataLen > 0 );
ESP8266_txbuf[MQTT_TxLen++] = BYTE1(topicLength); //主题长度MSB
ESP8266_txbuf[MQTT_TxLen++] = BYTE0(topicLength); //主题长度LSB
memcpy(&ESP8266_txbuf[MQTT_TxLen],topic,topicLength); //拷贝主题
MQTT_TxLen += topicLength;
//报文标识符
if(qos)
{
ESP8266_txbuf[MQTT_TxLen++] = BYTE1(id);
ESP8266_txbuf[MQTT_TxLen++] = BYTE0(id);
id++;
}
memcpy(&ESP8266_txbuf[MQTT_TxLen],message,messageLength);
MQTT_TxLen += messageLength;
MQTT_SendBuf(ESP8266_txbuf,MQTT_TxLen);
return MQTT_TxLen;
}
3.3、最后在main.c中编写函数调用这两个文件里的函数以实现与阿里云物联网平台的MQTT协议连接。
//MQTT初始化函数
void ES8266_MQTT_Init(void)
{
uint8_t status=0;
//初始化
if(ESP8266_Init())
{
user_main_info("ESP8266初始化成功!\r\n");
status++;
}
else Enter_ErrorMode(0);
//连接热点
if(status==1)
{
if(ESP8266_ConnectAP(WIFI_NAME,WIFI_PASSWD))
{
user_main_info("ESP8266连接热点成功!\r\n");
status++;
}
else Enter_ErrorMode(1);
}
//连接阿里云IOT服务器
if(status==2)
{
if(ESP8266_ConnectServer("TCP",MQTT_BROKERADDRESS,1883)!=0)
{
user_main_info("ESP8266连接阿里云服务器成功!\r\n");
status++;
}
else Enter_ErrorMode(2);
}
//登陆MQTT
if(status==3)
{
if(MQTT_Connect(MQTT_CLIENTID, MQTT_USARNAME, MQTT_PASSWD) != 0)
{
user_main_info("ESP8266阿里云MQTT登陆成功!\r\n");
status++;
}
else Enter_ErrorMode(3);
}
//订阅主题
if(status==4)
{
if(MQTT_SubscribeTopic(MQTT_SUBSCRIBE_TOPIC,0,1) != 0)
{
user_main_info("ESP8266阿里云MQTT订阅主题成功!\r\n");
}
else Enter_ErrorMode(4);
}
}
为了大家能够更好的理解代码,本文只写出了用于建立连接的关键函数以供大家参考,其余细节就不在此赘述了,祝你们在2024年前途似锦,事业高飞~