STM32+华为云IOT设计的动态密码锁

1. 前言

随着人们生活水平的提高及科学技术的发展,个人信息保护显得至关重要,设计了一款物联网智能电子密码锁,以STM32单片机为主控制器,由触摸矩阵键盘、ESP8266、步进电机等模块组成,具有远程控制、随机密码生成等功能。经软硬件测试,系统响应迅速,灵敏度高,实时性好,系统识别准确率高达99%,该系统运行稳定,安全可靠,功耗低及具有较好的扩展性。

当前支持的开锁方式:

(1)支持手机APP远程开锁。通过华为云物联网平台实现远程发送指令开锁,设备上的ESP8266通过连接家里路由器,在连接华为云物联网平台,可以在手机APP上对设备端的RTC时间进行校准,设备唯一ID获取,生成随机开锁密码,可以点击APP上的开锁按钮,通过物联网平台提供的API发送指令给STM32设备完成开锁。

(2)随机密码开锁。手机APP与本地设备都采用时间、作为算法种子,采用算法生成开锁密码,每一串的密码有效时间为一分钟。查看手机APP上显示的密码之后,在本地设备上输入完成密码对比开锁。
在这里插入图片描述


2. 相关硬件

2.1 WIFI模块

STM32+华为云IOT设计的动态密码锁_第1张图片

2.2 步进电机模块

STM32+华为云IOT设计的动态密码锁_第2张图片

2.3 OLED显示屏

STM32+华为云IOT设计的动态密码锁_第3张图片

2.4 STM32开发板

STM32+华为云IOT设计的动态密码锁_第4张图片

2.5 矩阵键盘模块

STM32+华为云IOT设计的动态密码锁_第5张图片

3. 手机APP设计

3.1 开发环境介绍

上位机软件采用Qt框架设计,Qt是一个跨平台的C++图形用户界面应用程序框架。Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。简单来说,QT可以很轻松的帮你做带界面的软件,甚至不需要你投入很大精力。

QT官网: https://www.qt.io/

STM32+华为云IOT设计的动态密码锁_第6张图片

3.2 学习教程

QT入门实战专栏: https://blog.csdn.net/xiaolong1126626497/category_11400392.html

QT5环境安装教程:https://xiaolong.blog.csdn.net/article/details/120654599

下载QT5.12.6下载地址:
https://download.qt.io/archive/qt/5.12/5.12.6/

打开链接后选择:

qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details

软件安装时断网安装,否则会提示输入账户。

** 安装的时候,勾选一个mingw 32编译器即可。**

3.3 实现效果

在这里插入图片描述

4. 创建云端设备

4.1 创建设备

登录官网: https://www.huaweicloud.com/

直接搜索物联网,打开页面。

https://www.huaweicloud.com/product/iothub.html

STM32+华为云IOT设计的动态密码锁_第7张图片

选择设备接入:

STM32+华为云IOT设计的动态密码锁_第8张图片

选择免费试用:

STM32+华为云IOT设计的动态密码锁_第9张图片

在产品页面,点击右上角创建产品:

STM32+华为云IOT设计的动态密码锁_第10张图片

填上产品信息:

STM32+华为云IOT设计的动态密码锁_第11张图片

得到产品ID,保存好ID,点击查看详情:

产品ID为:61b9ba3a2b2aa20288c1e7f1.

STM32+华为云IOT设计的动态密码锁_第12张图片

点击设备页面,注册设备:

STM32+华为云IOT设计的动态密码锁_第13张图片

填充信息进行注册:

STM32+华为云IOT设计的动态密码锁_第14张图片

保存设备密匙和设备ID,点击保存关闭会自动下载文件保存,后面生成密码和登录账号需要使用

STM32+华为云IOT设计的动态密码锁_第15张图片

关闭后就看到创建好的设备了:

STM32+华为云IOT设计的动态密码锁_第16张图片

点击产品页面,选择刚才创建的产品:

STM32+华为云IOT设计的动态密码锁_第17张图片

选择自定义模型—创建数据模型服务:

STM32+华为云IOT设计的动态密码锁_第18张图片

STM32+华为云IOT设计的动态密码锁_第19张图片

选择新增属性,创建设备的属性

STM32+华为云IOT设计的动态密码锁_第20张图片

4.2 创建MQTT登录账号和密匙

设备创建完成接来下生成MQTT登录账号、密匙,方便设备登录云端平台。

官网工具地址: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/

STM32+华为云IOT设计的动态密码锁_第21张图片

打开刚才创建设备时,下载的密匙文件,把内容复制出来对应的填进去,生成即可。

STM32+华为云IOT设计的动态密码锁_第22张图片

4.3 拼接主题订阅与发布的格式

官方文档介绍: https://support.huaweicloud.com/devg-iothub/iot_01_2127.html

STM32+华为云IOT设计的动态密码锁_第23张图片

在产品页面可以,看到主题的全部格式:

STM32+华为云IOT设计的动态密码锁_第24张图片

帮助文档:https://support.huaweicloud.com/iothub/index.html

总结的格式如下:

格式: $oc/devices/{device_id}/sys/messages/down
//订阅主题: 平台下发消息给设备
$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510/sys/messages/down


格式: $oc/devices/{device_id}/sys/properties/report
//设备上报数据
$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510/sys/properties/report

上属性的数据格式:
//上报的属性消息 (一次可以上报多个属性,在json里增加就行了)
{"services": [{"service_id": "lock","properties":{"门锁":1}}]}

上面属性里的服务ID和属性里的名称,在设备页面,影子设备页面查看。

STM32+华为云IOT设计的动态密码锁_第25张图片

4.4 MQTT客户端模拟设备登录云端

下面使用MQTT客户端模拟设备登录服务器测试,看设备创建的是否OK。

服务器的IP地址是: 121.36.42.100

端口号是: 1883

打开MQTT客户端软件,按照提示,输入相关参数后,点击连接,然后再点击订阅主题,发布主题即可:

STM32+华为云IOT设计的动态密码锁_第26张图片

查看云端服务器的情况: 可以看到设备已经在线了,并且收到上传的数据。

STM32+华为云IOT设计的动态密码锁_第27张图片

修改一下锁的状态,上报属性再查看:

STM32+华为云IOT设计的动态密码锁_第28张图片

发现云端的状态也已经改变,现在设备上报已经OK。

STM32+华为云IOT设计的动态密码锁_第29张图片

接下来测试命令下发,实现远程开锁关锁的功能:

打开产品页面,新增加命令:

STM32+华为云IOT设计的动态密码锁_第30张图片

STM32+华为云IOT设计的动态密码锁_第31张图片

STM32+华为云IOT设计的动态密码锁_第32张图片

命令添加成功:

STM32+华为云IOT设计的动态密码锁_第33张图片

在设备页面,选择同步命令下发:

STM32+华为云IOT设计的动态密码锁_第34张图片

STM32+华为云IOT设计的动态密码锁_第35张图片

点击确定后,查看MQTT客户端,发现已经收到数据了:

STM32+华为云IOT设计的动态密码锁_第36张图片

$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497/sys/commands/request_id=88e2626f-290d-405e-962d-51554445a8fd{"paras":{"lock":1},"service_id":"lock","command_name":"lock"}

设备端解析收到的数据,就可以完成多步进电机的控制,完成开锁关锁。

5. STM32设备端代码设计

5.1 硬件相关原理图

STM32+华为云IOT设计的动态密码锁_第37张图片

STM32+华为云IOT设计的动态密码锁_第38张图片

STM32+华为云IOT设计的动态密码锁_第39张图片

STM32+华为云IOT设计的动态密码锁_第40张图片

5.2 程序下载配置

STM32+华为云IOT设计的动态密码锁_第41张图片

5.3 硬件接线

1. 板载ESP8266串口WIFI模块与STM32的串口3相连接。
PB10--RXD 模块接收脚
PB11--TXD 模块发送脚
PB8---CH-PD---悬空
PB9---RST---悬空
GND---GND 地
VCC---VCC 电源(3.3V~5.0V)


2. 触摸按键使用TTP229型号的驱动芯片
SCL接PC11
SDA-OUT接PC10
电源接VCC-3.3
GND接GND

3. ULN2003控制28BYJ-48步进电机接线:

ULN2003接线:
IN4: PC9   d
IN3: PC8   c
IN2: PC7   b
IN1: PC6   a
+  : 5V
-  : GND

4. OLED显示屏
D0----SCK-----PB14
D1----MOSI----PB13
RES—复位(低电平有效)—PB12
DC---数据和命令控制管脚—PB1
CS---片选引脚-----PA7


5. 板载按键
KEY1---PA0 
KEY2---PC13


6.板载LED灯
LED1---PB5
LED2---PB0
LED3---PB1 

7. 板载蜂鸣器
BEEP---PA8

5.4 服务器连接核心代码

项目代码: https://download.csdn.net/download/xiaolong1126626497/81993720

//华为物联网服务器的设备信息
#define MQTT_ClientID "61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510"
#define MQTT_UserName "61b9ba3a2b2aa20288c1e7f1_QQ1126626497"
#define MQTT_PassWord "385ce91dfe7da5b7431868d5d87e7998163c493344040935d5a00024d6324242"

//订阅与发布的主题
#define SET_TOPIC  "$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510/sys/messages/down"  //订阅
#define POST_TOPIC "$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497_0_0_2021121510/sys/properties/report"  //发布

char mqtt_message[200];//上报数据缓存区

int main()
{
   u32 time_cnt=0;
   u32 i;
   u8 key;
   LED_Init();
   BEEP_Init();
   KEY_Init();
   USART1_Init(115200);
   TIMER1_Init(72,20000); //超时时间20ms
   USART2_Init(9600);//串口-蓝牙
   TIMER2_Init(72,20000); //超时时间20ms
   USART3_Init(115200);//串口-WIFI
   TIMER3_Init(72,20000); //超时时间20ms
   USART1_Printf("正在初始化WIFI请稍等.\n");
   if(ESP8266_Init())
   {
      USART1_Printf("ESP8266硬件检测错误.\n");  
   }
   else
   {
      //非加密端口
      USART1_Printf("WIFI:%d\n",ESP8266_STA_TCP_Client_Mode("CMCC-Cqvn","99pu58cb","121.36.42.100",1883,1));
  
   }
   
    //2. MQTT协议初始化	
    MQTT_Init(); 
    //3. 连接华为服务器        
    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");
    }
    ..................
    ..................
    ...................
}     

5.5 随机密码生成

#include 
#include 
#include 
#include 
#include 

char pwdcont[] = "0123456789abcdefghijklmn";

char* get_Password(int pwd_size)
{
	int i;
	int random;
	char *Password = (char *)malloc(pwd_size + 1);

	//获取时间种子
	srand((unsigned)time(NULL));

	for (i = 0; i < pwd_size; i++)
	{
		random = rand() % (strlen(pwdcont));
		*(Password + i) = pwdcont[random];
	}

	*(Password + i) = '\0';
	return Password;
}

int main()
{
	int random;
	char *Password;
	srand((unsigned)time(NULL));

	for (int i = 0; i < 10; i++)
	{
		Sleep(100);
		random = rand() % 10;//密码的长度范围 (6-63) 
		printf("random = %d\n", random);
		Password = get_Password(random);
		printf("Password = %s\n", Password);
	}
	free(Password);
	return 0;
}

5.6 RTC实时时钟代码

#include "rtc.h"

//定义RTC标准结构体
struct RTC_CLOCK rtc_clock; 

/*
函数功能: RTC初始化函数
*/
void RTC_Init(void)
{
	 if(BKP->DR1!=0xAB) //表示RTC第一次初始化
	 {
			//1. 备份寄存器时钟
			RCC->APB1ENR|=1<<27; //备份时钟接口
			RCC->APB1ENR|=1<<28; //电源时钟接口
			PWR->CR|=1<<8; 			 //允许写入RTC和后备寄存器
		  
		  //2. 配置RTC时钟源
			RCC->BDCR|=1<<0;     //开启外部32.768K时钟
		  while(!(RCC->BDCR&1<<1)){} //等待时钟就绪
			RCC->BDCR&=~(0x3<<8);  //清空时钟配置
			RCC->BDCR|=0x1<<8;     //选择外部32.768K时钟
			
			//3. 配置RTC核心寄存器
			RCC->BDCR|=1<<15;     //开启RTC时钟
      while(!(RTC->CRL&1<<5)){} //判断上一次寄存器是否写完成
			RTC->CRL|=1<<4;  //进入配置模式
			RTC->PRLH=0;      //预分频高位
			RTC->PRLL=0x7FFF; //32767	预分频低位
			RTC->CNTH=0;      //计数器高位
			RTC->CNTL=0;      //计数器低位
			RTC->ALRH=0;      //闹钟寄存器高位
			RTC->ALRL=60;      //闹钟寄存器低位
			RTC->CRL&=~(1<<4);//退出配置模式	
			while(!(RTC->CRL&1<<5)){} //判断上一次寄存器是否写完成
			BKP->DR1=0xAB; 		//表示配置成功了	
		}

		RTC->CRH|=1<<0; //秒中断
		RTC->CRH|=1<<1; //闹钟中断
		STM32_SetPriority(RTC_IRQn,2,2); //优先级
        
        RTC_SetTime(2022,4,9,0,36,1);
}

extern void Update_FrameShow(void);
/*
函数功能: RTC闹钟中断服务函数
*/
void RTC_IRQHandler(void)
{
	  u32 SecCnt;
		if(RTC->CRL&1<<0)
		{
				SecCnt=RTC->CNTH<<16;//获取高位
				SecCnt|=RTC->CNTL;   //获取低位
				RTC_GetTime(SecCnt); //转换标准时间
				RTC_GetWeek(SecCnt);
			 // printf("%d-%d-%d %d:%d:%d week:%d\n",rtc_clock.year,rtc_clock.mon,rtc_clock.day,rtc_clock.hour,rtc_clock.min,rtc_clock.sec,rtc_clock.week);
				Update_FrameShow(); //更新显示
				RTC->CRL&=~(1<<0); //清除秒中断标志位
		}
		
		if(RTC->CRL&1<<1)
		{
//				printf("闹钟时间到达!....\n");
//			  BEEP=1;
//			  DelayMs(500);
//				BEEP=0;
				RTC->CRL&=~(1<<1); //清除闹钟中断标志位
		}
}



//闰年的月份
static int mon_r[12]={31,29,31,30,31,30,31,31,30,31,30,31};
//平年的月份
static int mon_p[12]={31,28,31,30,31,30,31,31,30,31,30,31};


/*
函数功能: 设置RTC时间
函数形参:
    u32 year;   2018
	  u32 mon;     8
	  u32 day;
		u32 hour;
		u32 min;
	  u32 sec;
*/
void RTC_SetTime(u32 year,u32 mon,u32 day,u32 hour,u32 min,u32 sec)
{
		u32 i;
	  u32 SecCnt=0; //总秒数
		/*1. 累加已经过去的年份*/
		for(i=2017;i<year;i++)  //基准年份:20170101000000
	  {
				if(RTC_GetYearState(i))
				{
						SecCnt+=366*24*60*60; //闰年一年的秒数
				}
				else
				{
						SecCnt+=365*24*60*60; //平年一年的秒数
				}
		}
		/*2. 累加过去的月份*/
		for(i=0;i<mon-1;i++)
		{
			  if(RTC_GetYearState(year))
				{
						SecCnt+=mon_r[i]*24*60*60; //闰年一月的秒数
				}
				else
				{
						SecCnt+=mon_p[i]*24*60*60; //平年一月的秒数
				}	
		}
		
		/*3. 累加过去的天数*/
		SecCnt+=(day-1)*24*60*60;
		
		/*4. 累加过去小时*/
		SecCnt+=hour*60*60;
		
		/*5. 累加过去的分钟*/
		SecCnt+=min*60;
		
		/*6. 累加过去的秒*/
		SecCnt+=sec;
		
		/*7. 设置RTC时间*/
		RCC->APB1ENR|=1<<27; //备份时钟接口
		RCC->APB1ENR|=1<<28; //电源时钟接口
		PWR->CR|=1<<8; 			 //允许写入RTC和后备寄存器
		while(!(RTC->CRL&1<<5)){} //判断上一次寄存器是否写完成
		RTC->CRL|=1<<4;           //进入配置模式
		RTC->CNTH=SecCnt>>16;     //计数器高位
		RTC->CNTL=SecCnt&0xFFFF;  //计数器低位
		RTC->CRL&=~(1<<4);//退出配置模式	
		while(!(RTC->CRL&1<<5)){} //判断上一次寄存器是否写完成
}


/*
函数功能: 获取RTC时间
函数参数: u32 sec 秒单位时间
*/
void RTC_GetTime(u32 sec)
{
		u32 i;
		rtc_clock.year=2017; //基准年份
		
	  /*1. 计算当前的年份*/
	  while(1)
		{
			  if(RTC_GetYearState(rtc_clock.year))
				{
						if(sec>=366*24*60*60) //够一年
						{
								sec-=366*24*60*60;
								rtc_clock.year++;
						}
						else break;
				}
				else
				{
						if(sec>=365*24*60*60) //够一年
						{
								sec-=365*24*60*60;
								rtc_clock.year++;
						}
						else break;
				}
		}
		
		/*2. 计算当前的月份*/
		rtc_clock.mon=1;
		for(i=0;i<12;i++)
		{
				if(RTC_GetYearState(rtc_clock.year))
				{
					 	if(sec>=mon_r[i]*24*60*60)
						{
							 sec-=mon_r[i]*24*60*60;
							 rtc_clock.mon++;
						}
						else break;		
				}
				else
				{
						if(sec>=mon_p[i]*24*60*60)
						{
							 sec-=mon_p[i]*24*60*60;
							 rtc_clock.mon++;
						}
						else break;	
				}
		}
		
		/*3. 计算当前的天数*/
		rtc_clock.day=1;
		while(1)
		{
				if(sec>=24*60*60)
				{
						sec-=24*60*60;
						rtc_clock.day++;
				}
				else break;
		}
		
		/*4. 计算当前的小时*/
		rtc_clock.hour=0;
		while(1)
		{
				if(sec>=60*60)
				{
						sec-=60*60;
						rtc_clock.hour++;
				}
				else break;
		}
		
		/*5. 计算当前的分钟*/
		rtc_clock.min=0;
		while(1)
		{
				if(sec>=60)
				{
						sec-=60;
						rtc_clock.min++;
				}
				else break;
		}
		
		/*6. 计算当前的秒*/
		rtc_clock.sec=sec;
}


/*
函数功能: 判断年份是否是平年、闰年
返回值  : 0表示平年 1表示闰年
*/
u8 RTC_GetYearState(u32 year)
{
	 if((year%4==0&&year%100!=0)||year%400==0)
	 {
		 return 1;
	 }
	 return 0;
}


/*
函数功能: 获取星期
*/
void RTC_GetWeek(u32 sec)
{
	u32 day1=sec/(60*60*24); //将秒单位时间转为天数
	switch(day1%7)
	{
		case 0:
			rtc_clock.week=0;
			break;
	 	case 1:
			rtc_clock.week=1;
			break;
		case 2:
			rtc_clock.week=2;
			break;
		case 3:
			rtc_clock.week=3;
			break;
		case 4:
			rtc_clock.week=4;
			break;
		case 5:
			rtc_clock.week=5;
			break;
		case 6:
			rtc_clock.week=6;
			break;
	}
}

/*
将标准时间转为秒单位时间
思路: 全程加法
时间基准点: 1970年1月1日0时0分0秒
返回值: 得到的秒单位时间
*/
unsigned int TimeToSec(int year, int mon, int mdeay, int hour, int min)
{
	int i;
	int sec_cnt = 0; //记录秒单位的时间
	/*1. 转换年*/
	for (i = 1970; i < year; i++)
	{
		if (RTC_GetYearState(i)) //闰年
		{
			sec_cnt += 366 * 24 * 60 * 60;
		}
		else
		{
			sec_cnt += 365 * 24 * 60 * 60;
		}
	}

	/*2. 转换月*/
	for (i = 0; i < mon - 1; i++)
	{
		if (RTC_GetYearState(year)) //闰年
		{
			sec_cnt += mon_r[i] * 24 * 60 * 60;
		}
		else
		{
			sec_cnt += mon_p[i] * 24 * 60 * 60;
		}
	}

	/*3. 转换天数*/
	sec_cnt += (mdeay - 1) * 24 * 60 * 60;

	/*4. 转换小时*/
	sec_cnt += hour * 60 * 60;

	/*5. 转换分钟*/
	sec_cnt += min * 60;
	return sec_cnt;
}

你可能感兴趣的:(stm32,单片机,华为云)