赞
踩
与阿里云物联网平台的方式主要是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年前途似锦,事业高飞~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。