写点什么

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

作者:DS小龙哥
  • 2022 年 5 月 07 日
  • 本文字数:7036 字

    阅读完需:约 23 分钟

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


下载 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



选择设备接入:



选择免费试用:



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



填上产品信息:



得到产品 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接PC11SDA-OUT接PC10电源接VCC-3.3GND接GND
3. ULN2003控制28BYJ-48步进电机接线:
ULN2003接线:IN4: PC9 dIN3: PC8 cIN2: PC7 bIN1: PC6 a+ : 5V- : GND
4. OLED显示屏D0----SCK-----PB14D1----MOSI----PB13RES—复位(低电平有效)—PB12DC---数据和命令控制管脚—PB1CS---片选引脚-----PA7

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

6.板载LED灯LED1---PB5LED2---PB0LED3---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"); } else { USART1_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++) //基准年份: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;}
复制代码


发布于: 刚刚阅读数: 2
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022.01.06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
STM32+华为云IOT设计的动态密码锁_5月月更_DS小龙哥_InfoQ写作社区