知识就是力量,时间就是生命。这篇文章主要讲述STM32+华为云IOT设计的动态密码锁相关的知识,希望能为你提供帮助。
1. 前言随着人们生活水平的提高及科学技术的发展,个人信息保护显得至关重要,设计了一款物联网智能电子密码锁,以STM32单片机为主控制器,由触摸矩阵键盘、ESP8266、步进电机等模块组成,具有远程控制、随机密码生成等功能。经软硬件测试,系统响应迅速,灵敏度高,实时性好,系统识别准确率高达99%,该系统运行稳定,安全可靠,功耗低及具有较好的扩展性。
当前支持的开锁方式:
(1)支持手机APP远程开锁。通过华为云物联网平台实现远程发送指令开锁,设备上的ESP8266通过连接家里路由器,在连接华为云物联网平台,可以在手机APP上对设备端的RTC时间进行校准,设备唯一ID获取,生成随机开锁密码,可以点击APP上的开锁按钮,通过物联网平台提供的API发送指令给STM32设备完成开锁。
(2)随机密码开锁。手机APP与本地设备都采用时间、作为算法种子,采用算法生成开锁密码,每一串的密码有效时间为一分钟。查看手机APP上显示的密码之后,在本地设备上输入完成密码对比开锁。
文章图片
文章图片
文章图片
文章图片
2. 相关硬件 2.1 WIFI模块
文章图片
2.2 步进电机模块
文章图片
2.3 OLED显示屏
文章图片
2.4 STM32开发板
文章图片
2.5 矩阵键盘模块
文章图片
3. 手机APP设计 3.1 开发环境介绍
上位机软件采用Qt框架设计,Qt是一个跨平台的C++图形用户界面应用程序框架。Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。简单来说,QT可以很轻松的帮你做带界面的软件,甚至不需要你投入很大精力。
QT官网:https://www.qt.io/
文章图片
3.2 学习教程
QT入门实战专栏:https://blog.csdn.net/xiaolong1126626497/category_11400392.html
QT5环境安装教程:https://xiaolong.blog.csdn.net/article/details/120654599
【STM32+华为云IOT设计的动态密码锁】下载QT5.12.6下载地址:
https://download.qt.io/archive/qt/5.12/5.12.6/
打开链接后选择:
qt-opensource-windows-x86-5.12.6.exe13-Nov-2019 07:283.7GDetails
软件安装时断网安装,否则会提示输入账户。
安装的时候,勾选一个mingw 32编译器即可。
3.3 实现效果
文章图片
4. 创建云端设备 4.1 创建设备
登录官网:https://www.huaweicloud.com/
直接搜索物联网,打开页面。
https://www.huaweicloud.com/product/iothub.html
文章图片
选择设备接入:
文章图片
选择免费试用:
文章图片
在产品页面,点击右上角创建产品:
文章图片
填上产品信息:
文章图片
得到产品ID,保存好ID,点击查看详情:
产品ID为:61b9ba3a2b2aa20288c1e7f1.
文章图片
点击设备页面,注册设备:
文章图片
填充信息进行注册:
文章图片
保存设备密匙和设备ID,点击保存关闭会自动下载文件保存,后面生成密码和登录账号需要使用
文章图片
关闭后就看到创建好的设备了:
文章图片
点击产品页面,选择刚才创建的产品:
文章图片
选择自定义模型---创建数据模型服务:
文章图片
文章图片
选择新增属性,创建设备的属性
文章图片
4.2 创建MQTT登录账号和密匙
设备创建完成接来下生成MQTT登录账号、密匙,方便设备登录云端平台。
官网工具地址: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
文章图片
打开刚才创建设备时,下载的密匙文件,把内容复制出来对应的填进去,生成即可。
文章图片
4.3 拼接主题订阅与发布的格式
官方文档介绍: https://support.huaweicloud.com/devg-iothub/iot_01_2127.html
文章图片
在产品页面可以,看到主题的全部格式:
文章图片
帮助文档: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和属性里的名称,在设备页面,影子设备页面查看。
文章图片
4.4 MQTT客户端模拟设备登录云端
下面使用MQTT客户端模拟设备登录服务器测试,看设备创建的是否OK。
服务器的IP地址是:121.36.42.100
端口号是:1883
打开MQTT客户端软件,按照提示,输入相关参数后,点击连接,然后再点击订阅主题,发布主题即可:
文章图片
查看云端服务器的情况:可以看到设备已经在线了,并且收到上传的数据。
文章图片
修改一下锁的状态,上报属性再查看:
文章图片
发现云端的状态也已经改变,现在设备上报已经OK。
文章图片
接下来测试命令下发,实现远程开锁关锁的功能:
打开产品页面,新增加命令:
文章图片
文章图片
文章图片
命令添加成功:
文章图片
在设备页面,选择同步命令下发:
文章图片
文章图片
点击确定后,查看MQTT客户端,发现已经收到数据了:
文章图片
$oc/devices/61b9ba3a2b2aa20288c1e7f1_QQ1126626497/sys/commands/request_id=88e2626f-290d-405e-962d-51554445a8fd"paras":"lock":1,"service_id":"lock","command_name":"lock"
设备端解析收到的数据,就可以完成多步进电机的控制,完成开锁关锁。
5. STM32设备端代码设计STM32连接华为云IOT的工程代码Get: https://download.csdn.net/download/xiaolong1126626497/81993720
5.1 硬件相关原理图
文章图片
文章图片
文章图片
文章图片
5.2 程序下载配置
文章图片
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接GND3. ULN2003控制28BYJ-48步进电机接线:ULN2003接线:
IN4: PC9d
IN3: PC8c
IN2: PC7b
IN1: PC6a
+: 5V
-: GND4. OLED显示屏
D0----SCK-----PB14
D1----MOSI----PB13
RES—复位(低电平有效)—PB12
DC---数据和命令控制管脚—PB1
CS---片选引脚-----PA75. 板载按键
KEY1---PA0
KEY2---PC136.板载LED灯
LED1---PB5
LED2---PB0
LED3---PB1 7. 板载蜂鸣器
BEEP---PA8
5.4 服务器连接核心代码
//华为物联网服务器的设备信息
#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");
elseUSART1_Printf("主题订阅成功.\\n");
..................
..................
...................
5.5 随机密码生成
#include <
stdio.h>
#include <
time.h>
#include <
stdlib.h>
#include <
string.h>
#include <
windows.h>
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++)//基准年份:20170101000000if(RTC_GetYearState(i))SecCnt+=366*24*60*60;
//闰年一年的秒数elseSecCnt+=365*24*60*60;
//平年一年的秒数/*2. 累加过去的月份*/
for(i=0;
i<
mon-1;
i++)if(RTC_GetYearState(year))SecCnt+=mon_r[i]*24*60*60;
//闰年一月的秒数elseSecCnt+=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;
elseif(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;
elseif(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;
elsesec_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;
elsesec_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;
推荐阅读
- httpd开启ssl认证
- 进程管理
- 97条 Linux 常用命令总结,速速收藏!
- Linux系统-curl命令
- mybatis中扫不到mapper的常见原因
- Spring项目用JUnit调试时出现错误 Failed to load ApplicationContext 的解决方法
- WebApplicationContext()
- 从Android 9(API级别28)开始,默认情况下禁用明文支持。
- pandas 的DataFrame.apply()