STM32连接阿里云物联网平台
开通实例,创建产品设备可参考blog开通阿里云物联网平台实例 – 青木ckyf有关MQTT协议内容介绍及AT指令集相关知识,可浏览主页长空有风-CSDN博客。
前言
开通实例,创建产品设备可参考blog
有关MQTT协议内容介绍及AT指令集相关知识,可浏览主页长空有风-CSDN博客https://blog.csdn.net/weixin_64899638?spm=1011.2124.3001.5343
1 MQTT协议
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。
MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,因此易于实现。这些特点使得它对很多场景来说都是很好的选择,包括受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT),这些场景要求很小的代码封装或者网络带宽非常昂贵。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。
MQTT消息发布步骤:
1)发布者通过TCP/IP协议连接到代理服务器;
2)发布者向代理服务器发送CONNECT消息,请求连接;
3)代理服务器接收CONNECT消息后,将进行连接验证和身份认证;
4)连接验证和身份认证完成后,代理服务器向发布者发送CONNACK(连续报文确认)消息,表示连接已建立;
5)发布者向代理服务器发送PUBLISH消息,包含发布的消息内容和指定的主题。
MQTT消息订阅步骤:
1)订阅者通过TCP/IP协议连接到MQTT代理服务器;
2)订阅者想代理服务器发送CONNECT消息,请求连接;
3)代理服务器接收CONNECT消息后,将进行连接验证和身份认证;
4)连接验证和身份认证完成后,代理服务器向订阅者发送CONNACK(连续报文确认)消息,表示连接已建立;
5)订阅者向代理服务器发送SUBSCIRBE消息后,指定要订阅的主题和效应的QoS级别;
6)代理服务器接收到SUBSCRIBE消息后,根据订阅者请求进行主题订阅;
7)代理服务器向订阅者发送SUBACK(订阅请求报文确认)消息,表示订阅已被接受;
8)当有新的消息发布到订阅者订阅的主题上时,代理服务器将消息推送给订阅者;
MQTT消息取消订阅:
1)订阅者可以随时发送UNSUBSCRIBE消息,取消对某个或者多个主题的订阅;
2)代理服务器接受到UNSUBSCRIBE消息后,删除响应的订阅关系。
2 Espressif AT 指令集功能以及使用方法
指令集主要分为: 基础 AT 命令、 Wifi 功能 AT 命令、 TCP/IP 工具箱 AT 命令等。
可参考→【免费】EspressifAT指令集v018资源-CSDN文库
3 stm32硬件配置
设备 | 功能 |
STM32F103C8T6 | MCU |
OLED屏幕 | 显示连接 |
ESP8266 | PB12\PB13\PB14\PB15 |
传感器 | 温湿度传感器(SHT30) |
光照传感器 | |
可燃气体传感器 | |
执行器 | 蜂鸣器 |
电磁锁(LY-011A) | |
调光灯(RGB) |
4 stm32代码设计
ESP8266_at.c中设计在透传模式下使用指定协议(TCP)连接到服务器,基本包括检查ESP8266是否正常、初始化ESP8266、恢复出厂设置、连接热点和使用指定协议(TCP/UDP)连接到服务器等功能
/**
* 功能:初始化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;
}
ESP8266_MQTT.c中不涉及硬件问题,进行对MQTT协议的包装,包括MQTT连续服务器的打包函数、MQTT订阅/取消订阅数据打包函数、MQTT发布数据打包函数等内容
//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;
}
main.c中设计主要的连接参数
//WIFI配置
#define WIFI_NAME "xxxxx" //wifi名称
#define WIFI_PASSWD "xxxxxxxxx" //wifi密码
//阿里云服务器的登陆配置 阿里云物联网设备参数
#define MQTT_BROKERADDRESS "****mqttHostUrl****"
#define MQTT_CLIENTID "****clientId********"
#define MQTT_USARNAME "****username******"
#define MQTT_PASSWD "****password******"
//在头文件中添加订阅和发布的话题
#define MQTT_PUBLISH_TOPIC "/sys/*ProductKey*/*devicename*/thing/event/property/post"
#define MQTT_SUBSCRIBE_TOPIC "/sys/*ProductKey*/*devicename*/thing/service/property/set"
错误代码模式输出
/****************************** 进入错误模式代码 *****************************/
//进入错误模式等待手动重启
void Enter_ErrorMode(uint8_t mode)
{
GPIO_SetBits(GPIOG,GPIO_Pin_11);
while(1)
{
switch(mode){
case 0:user_main_error("ESP8266初始化失败!\r\n");break;
case 1:user_main_error("ESP8266连接热点失败!\r\n");break;
case 2:user_main_error("ESP8266连接阿里云服务器失败!\r\n");break;
case 3:user_main_error("ESP8266阿里云MQTT登陆失败!\r\n");break;
case 4:user_main_error("ESP8266阿里云MQTT订阅主题失败!\r\n");break;
default:user_main_info("Nothing\r\n");break;
}
user_main_info("请重启开发板");
delay_ms(200);
}
}
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);
}
}
设置临时字串用以接收连接成功后,服务器应答信号或者指令信息,将消息处理,可通过串口收到消息提示(此处可用于网络侧控制执行器)
char temp_str[30]; // 临时子串
// 从母串中获取与子串长度相等的临时子串
void ReadStrUnit(char * str,char *temp_str,int idx,int len)
{
int index;
for(index = 0; index < len; index++)
{
temp_str[index] = str[idx+index];
}
temp_str[index] = '\0';
}
int GetSubStrPos(char *str1,char *str2)
{
int idx = 0;
int len1 = strlen(str1);
int len2 = strlen(str2);
if( len1 < len2)
{
//printf("error 1 \n"); // 子串比母串长
return -1;
}
while(1)
{
ReadStrUnit(str1,temp_str,idx,len2); // 不断获取的从 母串的 idx 位置处更新临时子串
if(strcmp(str2,temp_str)==0)break; // 若临时子串和子串一致,结束循环
idx++; // 改变从母串中取临时子串的位置
if(idx>=len1)return -1; // 若 idx 已经超出母串长度,说明母串不包含该子串
}
return idx; // 返回子串第一个字符在母串中的位置
}
//处理MQTT下发的消息
void deal_MQTT_message(uint8_t* buf,uint16_t len)
{
memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf)); //清空接收缓冲
usart_rxcounter=0;
user_main_info("MQTT收到消息,数据长度=%d \n",len);
}
单片机属性上报(仅以温湿度为例)
//单片机状态上报
void TempAndHumi_StatusReport(void)
{
float t=0.0, h=0.0;
SHT30_GetValue(&t, &h); // 采集温湿度
//上报一次数据
sprintf(mqtt_message,
"{\"params\":{\"temperature\":%.1f,\"humi\":%.1f}}",(float)t,(float)h);
MQTT_PublishData(MQTT_PUBLISH_TOPIC,mqtt_message,0);
}
主程序中设置连接与上报功能
while(1)
{
//运行灯闪烁
if(Counter_RUNLED_Blink++>COUNTER_LEDBLINK)
{
Counter_RUNLED_Blink = 0;
LED_Toggle();
}
//运行状态打印
if(Counter_RUNInfo_Send++>COUNTER_RUNINFOSEND)
{
Counter_RUNInfo_Send = 0;
user_main_info("程序正在运行!\r\n");
}
//心跳包发送
if(Counter_MQTT_Heart++>COUNTER_MQTTHEART)
{
Counter_MQTT_Heart = 0;
MQTT_SentHeart();
}
//本机状态上报
if(Counter_StatusReport++>COUNTER_STATUSREPORT)
{
Counter_StatusReport = 0;
TempAndHumi_StatusReport();
}
//如果接收缓存有数据
if(usart_rxcounter)
{
deal_MQTT_message(ESP8266_rxbuf,usart_rxcounter);
OLED_ShowString(0,4,"CONNECTING",16);
}
delay_ms(LOOPTIME);
}
5 实验步骤
1.建立keil工程
2.添加相关代码
3.配置工程设置
4.填写个人MQTT参数
5连接ST-Link仿真器,下载程序
6 实验结果
实验程序烧写完毕后,使用MiniUSB线连接串口,使用串口工具查看打印信息。串口2正常打印出程序工作信息;
阿里云物联网平台可以观测到在线状态和日志信息,在设备的物模型数据中,打开实时刷新功能,即可在云端观测到当前温湿度。
无线节点模块OLED显示CONNECTING,GPIO_LED闪烁。
7.源码文件及相关
01-STM32-WIFI-阿里云物联网平台温湿度sht30实验
更多推荐
所有评论(0)