天翼云代理,天翼云代理商,北京代理商
天翼云2.5折专线:18510009100/18510009200(全国市话)

基于STM32+ESP8266设计物联网产品(重点:支持微信小程序一键配网连接腾讯云平台)

2021-12-20 01:42:51
简介: 基于STM32+ESP8266设计物联网产品(重点:支持微信小程序一键配网连接腾讯云平台)

一、环境介绍

编程软件: keil5


主控MCU: STM32F103C8T6


WIFI: ESP8266


协议:  MQTT


完整项目源码下载地址:https://download.csdn.net/download/xiaolong1126626497/19137788


二、前言

这里的 WIFI型号不重要、主控MCU不重要,连接的物联网平台也不重要。


要完成本章节的内容,只要会熟悉某款单片机的编程、了解基本的网络编程,明白MQTT协议、能读懂每个物联网云平台的帮助文档都可以完成最终的效果。

三、功能介绍

前面有几篇内容都介绍了如何使用在腾讯物联网平台创建设备,完成微信小程序与设备进行交互;这些设备代码里的连接的WIFI名称和密码都是固定,只能通过每次修改程序、编译、下载才能更改。一个正常的物联网智能设备,这样操作肯定是不合理的,所以这篇内容就完成如何使用微信小程序一键配网,完成设备的WIFI切换、连接。


现在我们购买的智能设备都有自己的配网方式,比如: 小米的很多设备,小爱音箱,摄像头,扫地机器人等。这些设备买回来之后,用户可以参考说明书,完成对设备的配置,让设备连接上家里的WIFI,完成网络连接。


本次我以智能锁为产品模型,在腾讯物联网平台创建一个设备,使用STM32F103系统板+ESP8266+LED灯完成智能锁产品的模拟开发;用户设备端可以按下指定的按键进入配网模式,打开腾讯官方的微信小程序,扫描产品二维码,根据步骤完成对设备的配网操作。


腾讯物联网支持了好几种配网模式,我这里选择的是“softAP”模式来完成配网操作。


softAP 模式配网的原理介绍:  正常情况下我们买回来的新设备内部是没有我们自己家WIFI的信息的,也就是说这个设备上电之后自己不知道该连接哪一个WIFI;这时我们就需要想办法把我们自己家里的WIFI名称、WIFI密码告诉这个设备,这个设备就可以去连接了。  那问题是怎么去告诉设备这些信息?  设备一般都有进入配网模式的按钮,进入配网模式之后,会将设备内部的WIFI设置成“softAP”模式,也就是设备自己会创建一个WIFI热点出来并创建UDP服务器监听连接,这时我们打开腾讯官方的微信小程序,按照指引去连接这个WIFI,连上之后,微信小程序会通过UDP协议将WIFI的配置信息传输给设备WIFI,设备WIFI收到之后,再切换模式为STA模式,去连接目标WIFI,连接成功之后,登录云平台,绑定设备,完成配网。  这其中的交互协议,后面再细说。


四、在腾讯云平台上创建智能锁

本章节只会展示几个关键步骤,如果之前没有使用过腾讯物联网云平台可以参考这里学习一遍:https://blog.csdn.net/xiaolong1126626497/article/details/116902653

image.png

这里可以配置微信小程序的详细参数,配网的设置也这个页面上:

image.png

下面进行配网设置:

image.png

选择配网模式:

image.png

这个页面比较重要,需要将设备进入配网的方法告诉用户,引导用户去操作,完成进入配网模式:

image.png

image.png

image.png

保存之后,打开微信小程序“腾讯连连”,扫描右下角的这个二维码,进行配网,完成设备添加。(一般正常产品,会将这个二维码打印出来,贴在设备上,方便用户扫描)

(提示: 做这一步,要先设计好设备端的程序,设备上电能正常的运行,才能做)

image.png

下面是手机上的截图: (根据页面上的提示操作设备)

image.pngimage.pngimage.png

按下开发板子上的S2进入配网模式:

image.png

在串口上也可以看到提示信息。

image.png

image.png

这时继续操作微信小程序上的步骤,选择设备的WIFI进行连接: 之后在下一个页面会自动等待配网成功(没有截图),设备配网成功之后会出现提示,这时设备就已经在线了

image.pngimage.pngimage.png

image.png

打开控制台已经看到设备上线了:

image.png

这时进入小程序页面,就可以对智能锁进行操作了:

image.png

到此,配网已经完成,接下来就介绍设备端的代码。

五、STM32设备端代码--这才是核心

关于配网的流程,在腾讯官网有详细介绍,看这里:https://cloud.tencent.com/document/product/1081/48404

image.png

由于关联代码较多,这里只提供主要的逻辑代码,其他的代码可以自己下载完整源码查看:https://download.csdn.net/download/xiaolong1126626497/19137788

image.png

main.c 代码:

#include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include <string.h> #include "timer.h" #include "bluetooth.h" #include "esp8266.h" #include "mqtt.h"  /* 智能锁(自己的设备) MQTT服务器地址: 106.55.124.154 MQTT服务器端口: 1883 MQTT客户端ID: 3XM7FNOG4Llock MQTT用户名: 3XM7FNOG4Llock;12010126;F8Q4P;1624710719 MQTT登录密码: 5d87e9a5bf8ae6295493c263b91aaebc4311f3e95763efe7f31be76c8578f9ec;hmacsha256 订阅主题: $thing/down/property/3XM7FNOG4L/lock 发布主题: $thing/up/property/3XM7FNOG4L/lock 发布消息: {"method":"report","clientToken":"123","params":{"lock":1}} */  #define SERVER_IP "106.55.124.154"//服务器IP #define SERVER_PORT 1883 //端口号  #define CONNECT_WIFI  "_CMCC-Cqvn"   //将要连接的路由器名称 --不要出现中文、空格等特殊字符 #define CONNECT_PASS "_99pu58cb"     //将要连接的路由器密码  //腾讯物联网服务器的设备信息 #define MQTT_ClientID "3XM7FNOG4Llock" #define MQTT_UserName "3XM7FNOG4Llock;12010126;F8Q4P;1624710719" #define MQTT_PassWord "5d87e9a5bf8ae6295493c263b91aaebc4311f3e95763efe7f31be76c8578f9ec;hmacsha256"  //订阅与发布的主题 #define SET_TOPIC  "$thing/down/property/3XM7FNOG4L/lock"  //订阅 #define POST_TOPIC "$thing/up/property/3XM7FNOG4L/lock"  //发布  //微信小程序配网数据订阅与发布 #define SET_WEIXIN_TOPIC  "$thing/down/service/3XM7FNOG4L/lock"//订阅 #define POST_WEIXIN_TOPIC "$thing/up/service/3XM7FNOG4L/lock"//发布  char mqtt_message[200];//上报数据缓存区  int main() {  u32 time_cnt=0;  u32 i;  u8 key;  u8 stat=0;   //1.初始化需要使用的硬件  LED_Init();  BEEP_Init();  KEY_Init();   //2. 初始化串口1(打印调试信息)与串口3(与WIFI通信)  USART1_Init(115200);  TIMER1_Init(72,20000); //超时时间20ms  USART3_Init(115200);//串口-WIFI  TIMER3_Init(72,20000); //超时时间20ms  USART1_Printf("正在初始化WIFI请稍等.\n");   //3. 检测WIFI硬件  while(1)  {  //如果硬件有问题. 蜂鸣器以300ms的频率报警  if(ESP8266_Init())  {  delay_ms(300);  BEEP=!BEEP;  USART1_Printf("ESP8266硬件检测错误.\n");  }  else  {  BEEP=0; //关闭蜂鸣器  break;  //硬件没有问题. 退出检测  }  }   //4. 上电如果检测到S2按键按下就表示需要进入配网状态  if(KEY_S2)  {  delay_ms(100);  if(KEY_S2)  {  while(1)//连接服务器  {  printf("进入配网模式.....\n");  BEEP=1;  delay_ms(100);  BEEP=0;   //清除之前的WIFI连接信息(连接个无效的WIFI),防止默认连接上 上次的WIFI,导致配网错误  ESP8266_SendCmd("AT+CWJAP=\"666\",\"12345678\"\r\n");  delay_ms(200);   stat=Esp8266_STA_TCPclinet_Init((u8 *)SERVER_IP,SERVER_PORT);  if(stat==0 || stat==0x80)break;  delay_ms(500);  printf("stat=%d\r\n",stat);  }  }  else  {  stat=0xFF;  }  }  else  {  stat=0xFF;  }   //连接默认WIFI  if(stat==0xFF)  {  printf("连接默认的WIFI.\n");  //非加密端口  USART1_Printf("WIFI:%d\n",ESP8266_STA_TCP_Client_Mode(CONNECT_WIFI,CONNECT_PASS,SERVER_IP,SERVER_PORT,1));  }   //2. MQTT协议初始化  MQTT_Init();  //3. 连接OneNet服务器  while(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord))  {  USART1_Printf("服务器连接失败,正在重试...\n");  delay_ms(500);  }  USART1_Printf("服务器连接成功.\n");   //3. 订阅主题  if(MQTT_SubscribeTopic(SET_TOPIC,0,1))  {  USART1_Printf("主题订阅失败.\n");  }  else  {  USART1_Printf("主题订阅成功.\n");  }    if(stat==0x80)//进入配网模式需要给微信小程序返回token值  {  //订阅微信topic  if(MQTT_SubscribeTopic(SET_WEIXIN_TOPIC,0,1))printf("订阅失败\r\n");  //返回平台数据,告知微信连连连接服务器成功  snprintf(mqtt_message,sizeof(mqtt_message),"{\"method\":\"app_bind_token\",\"clientToken\":\"client-1234\",\"params\": {\"token\":\"%s\"}}",esp8266_info.token);  MQTT_PublishData(POST_WEIXIN_TOPIC,mqtt_message,0);  //Smart_home{"method":"app_bind_token_reply","clientToken":"client-1234","code":0,"status":"success"} 配网成功后微信小程序返回数据  }   while(1)  {  key=KEY_Scan(0);  if(key==2)  {  time_cnt=0;  sprintf(mqtt_message,"{\"method\":\"report\",\"clientToken\":\"123\",\"params\":{\"lock\":1}}");  MQTT_PublishData(POST_TOPIC,mqtt_message,0);  USART1_Printf("开锁.\r\n");  }  else if(key==3)  {  time_cnt=0;  sprintf(mqtt_message,"{\"method\":\"report\",\"clientToken\":\"123\",\"params\":{\"lock\":0}}");  MQTT_PublishData(POST_TOPIC,mqtt_message,0);  USART1_Printf("关锁\r\n");  }   if(USART3_RX_FLAG)  {  USART3_RX_BUFFER[USART3_RX_CNT]='\0';  for(i=0;i<USART3_RX_CNT;i++)  {  USART1_Printf("%c",USART3_RX_BUFFER[i]);  }   //关锁  if(strstr((char*)USART3_RX_BUFFER,"\"lock\":0"))  {  LED4=1;  }  //开锁  else if(strstr((char*)USART3_RX_BUFFER,"\"lock\":1"))  {  LED4=0;  }   USART3_RX_CNT=0;  USART3_RX_FLAG=0;  }   //定时发送心跳包,保持连接  delay_ms(10);  time_cnt++;  if(time_cnt==500)  {  LED1=!LED1;  MQTT_SentHeart();//发送心跳包  time_cnt=0;  }  } }  

ESP8266的核心代码:

//存放ESP8266的详细信息 struct ESP8266 esp8266_info;  /*SoftAP配网*/ u8 ESP8266_SoftAP_MOde(void) {  u8 token[]="{\"cmdType\":2,\"productId\":\"3XM7FNOG4L\",\"deviceName\":\"lock\",\"protoVersion\":\"2.0\"}\r\n";//连接状态信息  char *p=NULL;  char data[256];  char buff[100];  u8 i=0;  u32 time1=0,time2=0;  USART3_RX_CNT=0;  USART3_RX_FLAG=0;  while(1)  {  if(USART3_RX_FLAG)  {  USART3_RX_BUFFER[USART3_RX_CNT]='\0';  printf("rx=%s",USART3_RX_BUFFER);  //+IPD,97,192.168.4.2,52021:{"cmdType":1,"ssid":"wbyq_wifi","password":"12345678","token":"df4a4c90abee98c9a443ae8ffd8cc16b"  p=strstr((char *)USART3_RX_BUFFER,"+IPD");  if(p)  {  strcpy(data,p);//将接收到的数据拷贝一份保存  p+=strlen("+IPD");  p+=1;  while(*p!=',' && *p!='\0')p++;  p++;//跳过字符',',获取到IP地址起始位置  i=0;  //IP地址解析  while(*p!=',' && *p!='\0')  {  buff[i++]=*p++;  }  buff[i]='\0';  strcpy((char *)esp8266_info.esp8266_ip,buff);  //端口号解析  p++;  i=0;  while(*p!=':' && *p!='\0')  {  buff[i++]=*p++;  }  buff[i]='\0';  esp8266_info.esp8266_prot=atoi(buff);//字符串转整数  //printf("ip=%s:%d\r\n",esp8266_info.esp8266_ip,esp8266_info.esp8266_prot);  printf("ret:%d\r\n",Esp8266_UDP_SendData((u8*)esp8266_info.esp8266_ip,esp8266_info.esp8266_prot,token));//上报连接状态  }  ESP8266_GetData(data,(char *)esp8266_info.esp8266_name,"ssid");//WIFI名  ESP8266_GetData(data,(char *)esp8266_info.esp8266_key,"password");//密码  ESP8266_GetData(data,(char *)esp8266_info.token,"token");//token数据,需要返回给平台  printf("wifi_name:%s\r\n",esp8266_info.esp8266_name);  printf("wifi_key:%s\r\n",esp8266_info.esp8266_key);  printf("wifi_token:%s\r\n",esp8266_info.token);  LED1=1;  return 0;  }  delay_ms(1);  time1++;  time2++;  if(time2>=100)  {  time2=0;  LED1=!LED1;  }  if(time1>=1000*300)  {  LED1=1;  break;//超时退出  }  }  return 1; }   /******************************************************************************************************************* **形参: wifi_name --WIFI名 **      password  --密码 **      remote_ip  --远端IP地址(255.255.255.255为广播地址) **      remote_prot  --远端端口号 **      localhost    ---本地端口号 **返回值:0 --成功, **       其它值 --失败 **示例:ESP8266_UDP_STA_Mode("360WIFI_123","12345678","172.20.7.2",10500,8080); *********************************************************************************************************************/ u8 ESP8266_UDP_STA_Mode(u8 *wifi_name,u8 *password,u8 *remote_ip,u16 remote_prot,u16 localprot) {  char buff[100];  USARTx_StringSend(USART3,"+++"); //退出透传模式  delay_ms(1000);   printf("重启模块.......\r\n");  USARTx_StringSend(USART3,"AT+RST\r\n");  delay_ms(1000);  delay_ms(1000);  printf("关回显.......\r\n");  if(ESP8266_SendCmd("ATE0\r\n"))return 2;  printf("设置为STA模式.......\r\n");  if(ESP8266_SendCmd("AT+CWMODE=1\r\n"))return 3;  printf("连接WIFI.......\r\n");  snprintf(buff,sizeof(buff),"AT+CWJAP=\"%s\",\"%s\"\r\n",wifi_name,password);  if(ESP8266_SendCmd(buff))return 5;  printf("查询IP.......\r\n");  if(ESP8266_SendCmd("AT+CIFSR\r\n"))return 6;  printf("建立UDP连接.....\r\n");  snprintf(buff,sizeof(buff),"AT+CIPSTART=\"UDP\",\"%s\",%d,%d,0\r\n",remote_ip,remote_prot,localprot);  if(ESP8266_SendCmd(buff))return 7;  printf("设置透传.......\r\n");  if(ESP8266_SendCmd("AT+CIPMODE=1\r\n"))return 8;  printf("发送数据.......\r\n");  USARTx_StringSend(USART3,"AT+CIPSEND\r\n");  return 0; }   /****************STA+TCPclinet初始化************* ** ** const char *STA_TCPCLINET[]= {  "AT\r\n",//测试指令  "ATE0\r\n",//关回显  "AT+CWMODE=1\r\n",//设置STA模式  "AT+RST\r\n",//模块复位  "ATE0\r\n",//关回显  "AT+CWJAP=\"HUAWEIshui\",\"asdfghjkl12\"\r\n",//连接wifi  "AT+CIPMUX=0\r\n",//设置单连接  "AT+CIFSR\r\n",//查询IP  "AT+CIPSTART=\"TCP\",\"192.168.43.204\",8080\r\n",//连接服务器  "AT+CIPMODE=1\r\n",//设置透传模式  "AT+CIPSEND\r\n",//开始发送数据 }; 返回值: 0x7f --退出透传模式失败 **      0x80 --进入配网模式正常退出 **      0    --未进入配网模式正常退出 **      其他值  --异常退出 *****************************************************/ u8 Esp8266_STA_TCPclinet_Init(u8 *server_ip,u16 server_port) {  char buff[100];  /*退出透传模式*/  u8 i=0;  u8 stat=0;  u32 id;  for(i=0;i<5;i++)  {  USARTx_StringSend(USART3,"+++");//退出透传模式  delay_ms(100);  if(Esp8266_SendCmdCheckStat("AT\r\n","OK\r\n")==0)  {  i=0;  break;  }  }  if(i!=0)  {  printf("退出透传模式失败\r\n");  return 0x7f;  }  printf("1.模块复位\r\n");  if(Esp8266_SendCmdCheckStat("AT+RST\r\n","OK\r\n"))return 1;  delay_ms(1000);  delay_ms(1000);  printf("2.关回显\r\n");  if(Esp8266_SendCmdCheckStat("ATE0\r\n","OK\r\n"))return 2;  if(ESP8266_GetWifi_Stat())//查询WIFI连接状态,未连接成功则进入配网模式  {  BEEP=1;  delay_ms(100);  BEEP=0;  delay_ms(100);  BEEP=1;  delay_ms(100);  BEEP=0;  stat=1;//进入配网模式标志位  //查询IP地址  printf("3.设置模式AP\r\n");  if(Esp8266_SendCmdCheckStat("AT+CWMODE=2\r\n","OK\r\n"))return 3;  printf("4.设置IP地址\r\n");  if(Esp8266_SendCmdCheckStat("AT+CIPAP=\"192.168.4.1\",\"192.168.4.1\",\"255.255.255.0\"\r\n","OK"))return 4;  printf("4.设置热点信息\r\n");  id=*(vu32*)(0x1FFFF7E8);//使用STM32的ID作为WIFI名  snprintf((char *)esp8266_info.esp8266_name,sizeof(esp8266_info.esp8266_name),"wbyq_%d",id);  snprintf(buff,sizeof(buff),"AT+CWSAP=\"%s\",\"12345678\",1,4\r\n",esp8266_info.esp8266_name);  printf("wif_name:%s\r\n",esp8266_info.esp8266_name);   if(Esp8266_SendCmdCheckStat(buff,"OK\r\n"))return 5;  printf("5.显示端口.......\r\n");  if(Esp8266_SendCmdCheckStat("AT+CIPDINFO=1\r\n","OK"))return 6;  printf("6.设置要连接的UDP\r\n");  if(Esp8266_SendCmdCheckStat("AT+CIPSTART=\"UDP\",\"192.168.4.255\",8266,8266,0\r\n","OK\r\n"))return 7;  printf("7.获取微信小程序传递过来的热点信息\r\n");  if(ESP8266_SoftAP_MOde())return 8;  printf("8.设置模式STA\r\n");  if(Esp8266_SendCmdCheckStat("AT+CWMODE=1\r\n","OK\r\n"))return 9;  printf("9.模块复位\r\n");  if(Esp8266_SendCmdCheckStat("AT+RST\r\n","OK\r\n"))return 10;  delay_ms(1000);  delay_ms(1000);  printf("10.连接WIFI\r\n");  snprintf((char *)buff,sizeof(buff),"AT+CWJAP=\"%s\",\"%s\"\r\n",esp8266_info.esp8266_name,esp8266_info.esp8266_key);//字符串拼接  if(Esp8266_SendCmdCheckStat(buff,"WIFI GOT IP"))return 11;  }  printf("11.设置单连接\r\n");  if(Esp8266_SendCmdCheckStat("AT+CIPMUX=0\r\n","OK"))return 12;  snprintf(buff,sizeof(buff),"AT+CIPSTART=\"TCP\",\"%s\",%d\r\n",server_ip,server_port); //  printf("buff:%s\r\n",buff);  printf("12.连接服务器\r\n");  if(Esp8266_SendCmdCheckStat(buff,"OK"))return 13;  printf("13.配置透传模式\r\n");  if(Esp8266_SendCmdCheckStat("AT+CIPMODE=1\r\n","OK\r\n"))return 14;  printf("14.开始发送数据\r\n");  if(Esp8266_SendCmdCheckStat("AT+CIPSEND\r\n",">"))return 15;  if(stat)return 0x80;//进入配网模式并且正常退出  else return 0;//未进入配网模式,正常退出 }  /**************************获取WIFI连接状态信息***************************/ u8 ESP8266_GetWifi_Stat(void) {  u16 i=0;  u16 time=0;  u16 time2=0;  USART3_RX_CNT=0;  USART3_RX_FLAG=0;  USARTx_StringSend(USART3,"AT+CWJAP?\r\n");//查询WIFI连接状态  while(1)  {  if(USART3_RX_FLAG)  {  USART3_RX_BUFFER[USART3_RX_CNT]='\0';  printf("rx=%s\r\n",USART3_RX_BUFFER);  if(strstr((char *)USART3_RX_BUFFER,"+CWJAP") || strstr((char *)USART3_RX_BUFFER,"WIFI GOT IP"))  {  USART3_RX_CNT=0;  USART3_RX_FLAG=0;  LED1=1;  return 0;  }  else  {  USART3_RX_CNT=0;  USART3_RX_FLAG=0;  memset(USART3_RX_BUFFER,0,sizeof(USART3_RX_BUFFER));  }  }  delay_ms(10);  i++;  time++;  time2++;  if(time>=1000)  {  time=0;  USARTx_StringSend(USART3,"AT+CWJAP?\r\n");  }  if(time2>=300)  {  LED1=!LED1;  time2=0;  }  if(i>=100*60)  {  LED1=1;  break;  }  }  return 1; }

12年经验 · 提供上云保障

服务热线:132-6161-6125(手机) 400-150-1900(全国市话)

站内导航: 天翼云服务器价格| 天翼云购买流程| 天翼云代理| 北京天翼云代理商| 杭州天翼云代理| 深圳天翼云代理商| 钉钉代理商| 阿里云代理| 公司官网

我公司收款账号| 天翼云备案系统

CopyRight © 2019 天翼云代理商. All Rights Reserved 京ICP备2024071855号-7 管理-北京优胜智连科技有限公司