目录
说在前面
本文大概3W字包含代码,预计阅读时间20分钟。
先上演示视频,感兴趣再往下看 视频地址:基于ESP32实现的带透明显示,接入米家,支持小爱开门的智能指纹门锁_哔哩哔哩_bilibili
基于ESP32实现的带透明显示,接入米家,支持小爱开门的智能指纹门锁
大学四年一眨眼就过去了,大学期间好像也没搞出来什么,想着毕设是大学最后一次大作业了,我就想着自己做个什么出来也给大学画上一个句号,大学学的软件,课设基本上都是各种系统,前后端设计,各个机构来给我们培训的也是这种,就不太想做这个,最终在三月初决定做这个跟硬件模块相关的,自己也感兴趣,就从零开始学了一点arduino,我是跟着B站“太极创客”学的,感觉讲的很好。
虽然学了一点,但是基本上还是门外汉,有说错的地方大佬们轻点喷【狗头保命】
一. Arduino_ESP32环境搭建
我是使用Arduino IDE 作为编译器,再搭建ESP32的开发环境,当然你也可以用VScode或者ESP32自己的开发软件。
- 下载Arduino ,官网下载1.X版本尽量不要下2.0以上的: 链接,我使用的是1.8.15版本;
- 安装ESP32开发环境 文件-> 首选项->附加开发板管理器 输入https://dl.espressif.com/dl/package_esp32_index.json;
这样就添加了ESP32开发板管理器的网址。
重启arduino - 打开 开发板管理器 ,工具-> 开发板->开发板管理器,搜索ESP32下载;
搜索ESP32开发板下载,应该会很慢,需要科学。或者采用将文件夹下Espressif文件拷到Arduino安装目录/hardware下,可以直接完成ESP32的配置。
至此,ESP32开发环境就配好了,接下来就要连接开发板,照着上图选择好开发板 频率和闪存大小,选择端口,一般esp32开发板都自带驱动,连上之后电脑会有提示音,打开电脑设备管理器查看端口号。然后在上图端口选择新增的这个开发板的端口。
OK,这样环境就配好了,为了验证是否成功配置好环境,可以点击文件->示例->BLINK,这是官方示例,打开烧录到开发板上,观察开发板上的内置LED灯是否闪烁,正常闪烁的话恭喜你,环境配置的没毛病。
二. 硬件选型
使用到的硬件模块有ESP-WROOM-32开发板(30引脚),DS166圆形七彩指纹模块,4X4软膜键盘模块,5V继电器,DHT11温湿度传感器,按键模块,TFT屏幕,分光棱镜
模块 | 用途 |
---|---|
ESP32开发板 | 处理运算,通信 |
1.44寸TFT屏幕 | 显示 |
分光棱镜 | 折射显示(非必要) |
4X4键盘模块 | 密码输入,功能选择 |
5V继电器 | 控制5V电磁锁 |
有源蜂鸣器 | 发出警报 |
DTH11温湿度传感器 | 测量室内实时温湿度(非必要) |
商品链接和价格我就不放了,放一下我的购买截图,大家可以按需购买。
其中有一些功能没加上,比如说红外线模块和RFID(NFC)刷卡模块原因就是开发板引脚不够了,我在网上找了很久如何扩展ESP32的引脚也问了我们学院其他专业精通这个的老师,最后找到一个但是价格非常美丽,加上已经花了很多钱所以就没继续扩展。缺少的就是连接RFID的SPI类型引脚,这个刷NFC的坑就留给下个人来实现吧。
继续回到硬件选型上
- ESP32
本系统采用ESP32开发板作为核心组件,ESP32开发板是一款开源平台,相比于其他开发板其主要优势在于具有高性能、低功耗、易扩展、低成本等特点。主控芯片为双核处理器,包含一个主频高达240MHz的Xtensa® 32-bit LX6微处理器以及一个Ultra低功耗协处理器,可实现高性能计算和低功耗操作的平衡。ESP32开发板具有大量的I/O接口,包括数字口、模拟口、PWM口、SPI口、I2C口、UART口等,可方便地连接多种功能的传感器、显示屏幕和各种输入输出模块,满足不同应用场景的需求。此外,ESP32开发板还具有Wi-Fi和蓝牙功能,可实现无线通信和远程控制。本系统就是使用WIFI获取时间、天气等实时信息,利用Blinker平台进行远程控制。 - DS166指纹模块
DS166圆形指纹模块是一款基于AS608算法芯片的指纹识别模块,其外观呈圆形,直径约为25毫米。DS166模块采用了高清晰度光学指纹传感器技术,能够实现快速、精准的指纹识别。它支持多种接口和协议,如UART、SPI、I2C等,可以方便地与各种主控板进行通信,比如常用的Arduino开发板和本系统所使用的ESP32。
DS166模块的工作原理是:当用户将手指放在圆形传感器上时,传感器会将指纹图像转化为电信号,DS166模块通过对电信号进行分析和处理,提取出指纹的特征值,再与存储在模块内的指纹特征库进行比对。本系统选择的是七彩系列,当指纹没有识别成功或者没有在指纹库中搜索到匹配指纹时,会发出红色错误提醒灯光,正确时则是绿色提示灯光,正常待机则是其他颜色的呼吸效果,从而实现指纹识别的功能。DS166模块通过串口与微控制器进行通信,可以方便地集成到各种电子产品和系统中。DS166圆形指纹模块的指纹库容量较大,最多可以存储3000个指纹信息,支持1:N或1:1指纹比对方式,识别速度快,准确率高。此外,该模块还具有指纹录入、删除、更新等功能,操作简单易用,符合本系统设计智能门锁的要求。
按照下表将DS166和ESP32进行连接
DS166 | ESP32 |
---|---|
GND | GND |
RX | RX2 |
TX | TX2 |
VDD | 3.3V |
Touch | 接空 |
Vsen | 3.3V |
- 4X4软膜键盘模块
按键解锁部分,为了保证既能进行密码输入,又能提供除了数字之外的按键信息,本系统使用的是4×4矩阵键盘。4×4矩阵键盘是一种常见的电子元器件,通常用于输入密码、控制器参数设定等场景。它由4行4列共16个按键组成,每个按键上面有一个导电膜,当按下按键时,导电膜接触到相应的行列导电线路,从而完成一个电路连接。在单片机中,通过对矩阵键盘按键位置的扫描,可以获取到按键的触发情况,从而进行相应的逻辑控制。而且只占用8个标准I/O接口,可实现按键模块与开发板之间的通信,完成密码解锁、密码修改、指纹添加等相关功能。
薄膜键盘和ESP32按照下表进行正确连接,连接的引脚只要是正常I/O口均可。
矩阵键盘 | ESP32 |
---|---|
R1 | D13 |
R2 | D12 |
R3 | D14 |
R4 | D33 |
C1 | D3 |
C2 | D5 |
C3 | D4 |
C4 | D15 |
- TFT屏幕
屏幕显示部分选用的是基于SPI模式的1.44寸TFT屏幕模块,该显示屏色彩鲜艳、分辨率较高、显示效果优秀。其通过SPI接口和主控板进行通信,支持多种驱动芯片,本设计选用的是驱动为ST7735的模块。通过调用TFT_eSPI库文件,可以非常方便地编写显示文字的大小、位置,也可以利用图形转码软件,将图形或汉字转为代码,进而通过程序显示在TFT屏幕上,支持各种颜色的显示[8]。同时,该模块成本较低,易于使用和维护,具有广泛的应用前景。
按照下表把TFT屏幕的各个引脚和ESP32开发板进行连接,本次接线的引脚必须要支持SPI通信。
TFT屏幕 | ESP32 |
---|---|
GND | GND |
VCC | 3.3V |
MISO | D19 |
MOSI | D23 |
SCLK | D18 |
CS | D27 |
DC | D25 |
RST | D26 |
- 分光棱镜
分光棱镜是一种用于分离光线的水平偏振和垂直偏振的光学元件,是由两个三棱镜组成,中间镀制了多层膜结构,其中透射和反射是1:1。在TFT屏幕镜像显示的情况下放上分光棱镜就会在棱镜上显示出透明的效果。 - 5V 继电器
继电器是一种电子器件,用于控制磁铁线圈的开关状态,以打开或关闭外接电路。通常使用继电器控制电磁锁的电路开关,以控制电磁锁。电磁锁通常由磁铁线圈和机械锁组成,磁铁线圈受电激励后,产生磁力使机械锁打开或关闭。当继电器用于控制电磁锁时,通常将电磁锁电路连接到继电器的输出端口,而继电器的控制终端连接到控制电路。如果控制电路输出高电平,则继电器的电磁铁激活,使其关闭,从而将输出打开并提供给电磁锁。相反,如果控制电路输出低电平,则继电器的磁铁线圈未激活,导致输出切断电磁锁电路。通过这种方式,当控制电路输出高电平时,电磁锁就可以打开,从而实现对电磁锁的控制。这种控制方法广泛应用于门禁、安防系统等领域。按照下表对继电器和ESP32进行连接,正负极不能连错。
继电器 | ESP32 |
---|---|
DC- | GND |
DC+ | 5V |
IN | D32 |
NO | 电磁锁正级 |
COM | 电磁锁负极 |
NC | \ |
- DHT11温湿度传感器
DHT11传感器模块是一种低成本,数字输出的传感器。它的使用非常简单,只需要将它连接到单片机的数字引脚,通过读取传感器输出的数据即可得到实时的温度的值和湿度的百分比。而且它的精确度相比其他温湿度传感器也非常高,温度测量的误差范围为±2℃,湿度测量的误差范围为±5%RH。非常适合空间不是很大的室内温湿度检测。
按照下表对DHT11和ESP32进行连接,DATA引脚可以接在ESP32的任意引脚上。
DTH11 | ESP32 |
---|---|
GND | GND |
VCC | 3.3V |
DATA | D22 |
- 蜂鸣器
下图所示为有源蜂鸣器。由于蜂鸣器振动才可以发声,所以给有源蜂鸣器加上直流电源设置好频率和占空比,内部电路会自动给线圈加上不断变化的电流,让磁场吸引音膜不断地变形,从而发出声音,提示用户指纹或者密码错误。蜂鸣器随便接一个可输入输出引脚均可。
2.1系统整体接线图
本来想在fritzing上把详细连接图画出来,但是元器件真的难找,找了些替代的,看个乐呵吧,接线还是尽量按照上面各个接线表接线。
三. 软件设计和关键代码
3.1 软件工作流程逻辑
3.2显示设计
3.2.1 TFT_eSPI库安装
由于使用的是TFT屏幕,所以我们要安装TFT_eSPI库,字符,文字,字体大小,颜色,显示方向等都要用到这个库。具体安装方法:工具->管理库。然后搜索TFT_eSPI下载安装。
3.2.2TFT库配置
- 找到刚刚下载的库文件,在Arduino文件夹里的libraries里,这个地方就是专门放下载的库文件的,所以第一种库文件下载方法就是直接在网上下载.ZIP的库文件压缩包,然后解压到libraries这个文件夹即可。
D:\mydoc\Arduino\libraries\TFT_eSPI-2.5.0 - 找到这个<User_Setup.h>的文件,里面有很多类型驱动的屏幕,我使用的是ST7735驱动的128X128的1.44TFT屏幕,所以找到这个驱动代码,把其他用不到的都注释掉。直接看着前面的代码行数来改,位置基本一样。
3.接下来继续在这个文件里设置屏幕的显示的颜色,屏幕的大小尺寸,
我们设置屏幕的宽度为128×128,屏幕颜色有RGB和BGR两种类型。TFT默认的是BGR类型,我们图片一般是RGB,后面在显示图片教程中会详细说明。
4.下面这行代码取消注释,是解决屏幕显示偏移的问题,主要是针对7735驱动的,出现屏幕下方和右方一条黑白花纹线,没有颜色显示,原因就是屏幕原点(0,0)发生了偏移,把这行代码取消注释就可以了。
5.引脚也要在这里说明一下,照着代码前面的行数找更快一点。
6.最后设置一下频率 - 新建一个文件,复制下面代码烧录到开发板测试一下。
#include <SPI.h> //导入库
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
void setup() {
// put your setup code here, to run once:
tft.init(); //初始化
tft.fillScreen(TFT_BLACK); //屏幕颜色
tft.setTextColor(TFT_YELLOW); //设置文本颜色为白色
tft.setTextSize(2); //设置文字的大小 (1~7)
tft.println("Hello Arduino"); //显示文字
}
void loop() {
// put your main code here, to run repeatedly:
}
观察屏幕上是否出现“Hello Arduino”字样,出现就是屏幕配置成功了。
3.2.3中文汉字显示
- 由于TFT屏幕没办法直接显示汉字,所以我们要使用汉字取模工具把想要的汉字转换成对应的16进制数,就可以显示任意中文词语或者句子,但其实显示汉字或图片这种操作是比较占用单片机运行内存的,我们需要将这些不会变化的数据定义为不可变的const类型,因为用const修饰的变量,在硬件上会被保存到ROM中即“程序存储器”,而用于计算的“随机存储器”RAM空间比ROM小很多。
- 定义三种大小的汉字,以便后面使用,因为一旦取模之后,汉字在屏幕上的大小就固定了,也就说,取模取的汉字越大,显示的就越大,我以16X16大小的汉字来演示说明。
- 打开字体取模软件PCtoLCD2002.exe,或者在线取模工具分别进行如下设置:
-
然后在输入框内输入要取模的汉字,点击生成字模,复制下方生成的16进制的字模
-
新建一个MyFont.h文件专门用来存放汉字字模,在文件中写入如下格式的字模
#include <pgmspace.h>
const unsigned char hz_zhou PROGMEM[] =
{
0x00,0x00,0x3F,0xF8,0x21,0x08,0x21,0x08,0x2F,0xE8,0x21,0x08,0x21,0x08,0x3F,0xF8,
0x20,0x08,0x27,0xC8,0x24,0x48,0x24,0x48,0x27,0xC8,0x40,0x08,0x40,0x28,0x80,0x10/*"周",0*/
};
const unsigned char hz_Mon PROGMEM[] =
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFE,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00/*"一",1*/
};
...
...
... // 省略其余字模数据
struct FNT_HZ // 汉字字模数据结构
{
char Index[4]; // 汉字内码索引,如"中",在UTF-8编码下,每个汉字占3个字节,第四个是结束符0
const unsigned char* hz_Id; // 点阵码数据,存放内码后对应的点阵序列每个字需要32个字节的点阵序列
unsigned char hz_width; //序列长度
};
//定义结构数组
PROGMEM const FNT_HZ hanzi[] =
{
{"周", hz_zhou,16}, {"一", hz_Mon,16}, {"二", hz_Tue,16}, {"三", hz_Wed,16}, {"四", hz_Thu,16},
{"五", hz_Fri,16}, {"六", hz_Sat,16}, {"日", hz_Sunday,16}, {"晴", hz_sun,16}, {"阴", hz_cloudy,16},
{"雨", hz_rain,16}, {"雪", hz_snow,16}, {"多", hz_duo,16}, {"云", hz_yun,16}, {"许",hz_xu,16}, {"昌", hz_chang, 16},
{"东", hz_dong,16}, {"西", hz_xi,16}, {"南", hz_nan,16}, {"北",hz_bei,16}, {"风", hz_feng, 16}
};
-
注意结构数组里面的数字 {“周”, hz_zhou,16}中的16,就是你取字模时的设置的字宽。一会还要用到这个数字。
-
下面介绍显示汉字的函数,因为我们要在多处显示汉字,所以封装成函数使用起来也更加方便。但是我使用了三种大小的字体,所以我设置了三个函数,就是在下面函数中改一下 16和x0+=17,中的数值即可。
/*******************单个汉字显示****************/ void showMyFont(int32_t x, int32_t y, const char c[3], uint32_t color) { for (int k = 0; k < 79; k++)// 根据字库的字数调节循环的次数 if (hanzi[k].Index[0] == c[0] && hanzi[k].Index[1] == c[1] && hanzi[k].Index[2] == c[2]) { tft.drawBitmap(x, y, hanzi[k].hz_Id, hanzi[k].hz_width, 16, color); } //16是字模的高度,设置为你取字模的高度就行。 } /*******************整句汉字显示****************/ void showMyFonts(int32_t x, int32_t y, const char str[], uint32_t color) { //显示整句汉字,字库比较简单,上下、左右输出是在函数内实现 int x0 = x; for (int i = 0; i < strlen(str); i += 3) { showMyFont(x0, y, str+i, color); x0 += 17; //每个汉字宽度为16 所以设置17。 } }
showsMyFonts(22,116,"小爱开门已部署",TFT_WHITE);//整句汉字显示函数调用示例
showsMyFont(97,104,"级",TFT_WHITE);//单个汉字显示调用示例
3.2.4 时间显示
-
NTPClient timeClient(ntpUDP, "ntp1.aliyun.com", 60 * 60 * 8, 30 * 60 * 1000);
这行代码是使用NTPClient库来连接一个NTP服务器,获取和同步网络时间的。NTPClient是一个Arduino的库,可以用来从NTP服务器获取Unix时间戳12。
所以要添加这个库文件,方法在上面说过了,就不在赘述。
-
这行代码的含义是:
- 创建一个NTPClient对象,命名为timeClient,用来连接NTP服务器
- ntpUDP是一个WiFiUDP对象,用来发送和接收UDP数据包
- "ntp1.aliyun.com"是阿里云服务器的域名,可以换成其他的NTP服务器地址
- 60 * 60 * 8是时间偏移量,以秒为单位,表示要在获取的时间上加上8个小时,用来调整时区使其显示标准的北京时间。
- 30 * 60 * 1000是更新间隔,以毫秒为单位,表示每隔30分钟校准一次时间
使用上面创建的库函数来获取时间。
String formattedTime = timeClient.getFormattedTime();
int tm_Hour = timeClient.getHours();
int tm_Minute = timeClient.getMinutes();
int tm_Second = timeClient.getSeconds();
........................................
/*******************时间界面显示****************/
void show_time(uint16_t fg,uint16_t bg, String Hour,int Minute, int MON,int DAY, int tm_Year,const char* week)
{
showtext(15,5,2,3,fg,bg,Hour);
tft.setCursor(63,5, 2);
tft.setTextSize(3);
tft.printf(":");
tft.setCursor(75,5, 2);
tft.setTextSize(3);
tft.setTextColor(tft.color565(255, 80, 80));
if(Minute<10){
tft.printf("0%d",Minute);
}
else tft.printf("%d",Minute);
//月
tft.setCursor(3,55, 2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE);
tft.printf("%d",MON);
showsMyFont(11,57,"月",TFT_YELLOW);
//日
tft.setCursor(22,55, 2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE);
tft.printf("%d",DAY);
showsMyFont(37,57,"日",TFT_YELLOW);
//showtext(10,80,1,2,fg,bg, currentDate);
// showMyFonts(15, 100, week, TFT_YELLOW);
//周
tft.setCursor(50,55, 2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE,tft.color565(255, 80, 80));
tft.println(week);
tft.pushImage(8, 71, 52, 44, xa);
tft.drawFastHLine(10, 115, 108, tft.alphaBlend(0, bg, fg));
showsMyFonts(22,116,"小爱开门已部署",TFT_WHITE);
上面这段代码就是把我们通过库函数获取的时间进行显示。这样我们就完成时间的显示了。
3.2.5 天气显示
- 天气信息获取
采用的是心知天气的免费API,免费版可以提供一少部分天气信息,毕竟是免费的要啥自行车,提供了两种天气信息获取方式,一种是天气实况(我是用来在天气界面显示实时的天气和温度,DID迪大佬是直接用的第二种方式)——第二种就是未来三天的天气,这个天气相比实况天气的数据就差点有点远,比如外面是太阳,屏幕上却显示多云,手机显示外面15度,屏幕显示10度等等
- API获取
打开心知天气官网:心知天气 - 高精度气象数据 - 天气数据API接口 - 行业气象解决方案
注册登录,点击控制台->免费版
获取自己的私钥,用于API的访问。接下来你就可以看着文档来获取具体的接口地址,参数说明和返回结果说明。如点击文档-> 产品文档->天气实况
由于 现在版本的文档不写明免费版显示的数据有哪几项,所以就放一个之前的图。
- 常用接口地址
-
天气实况API:
https://api.seniverse.com/v3/weather/now.json?key=你的私钥&location=查询城市的拼音&language=zh-Hans&unit=c
-
当天+未来3天天气API:
https://api.seniverse.com/v3/weather/daily.json?key=你的私钥&location=查询城市的拼音&language=zh-Hans&unit=c&start=0&days=5
-
生活指数API :
https://api.seniverse.com/v3/life/suggestion.json?key=你的私钥&location=查询城市的拼音&language=zh-Hans
我们把我们的私钥填入位置然后填入我们要查询的城市的拼音,把这个网站填到浏览器上打开
我以北京实况天气来演示
{"results":
[{"location":{"id":"WX4FBXXFKE4F",
"name":"北京","country":"CN",
"path":"北京,北京,中国","timezone":"Asia/Shanghai",
"timezone_offset":"+08:00"},
"now":{"text":"多云","code":"4",
"temperature":"25"},
"last_update":"2023-05-24T10:12:13+08:00"}]}
我们可以看出返回的数据是JSON格式,所以我们要想办法在Arduino中解析这段JSON数据格式,
1.首先我们要在程序中采用Http协议去访问这个API获得json数据。
2.然后在Arduino中安装httpclient
库用来发送请求,建立连接。
3.最后安装arduino json
库用于json数据的解析。
解析过程演示
1.复制上面JSON数据,用官方的json处理网站:arduinojson.org 点击Assistant,进行如下设置:
填好之后一路Next
复制这段解析好的代码到将其加入到主程序中,其中第二行input 修改为之前返回的json数据,可以把判断error的代码去掉,因为我们前面得到的肯定是正确的json数据。
#include <WiFi.h> //wifi库
#include <ArduinoJson.h> //Json库
#include <HTTPClient.h> //HTTP库
const char* ssid = " "; //wifi账号
const char* password = " "; //wifi密码
const char* host = "api.seniverse.com"; //心知天气服务器地址
String now_address="",now_time="",now_temperature="";//用来存储报文得到的字符串
void setup()
{
Serial.begin(115200);
// 连接网络
WiFi.begin(ssid, password);
//等待wifi连接
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected"); //连接成功
Serial.print("IP address: "); //打印IP地址
Serial.println(WiFi.localIP());
}
void loop()
{
//创建TCP连接
WiFiClient client;
const int httpPort = 80;
if (!client.connect(host, httpPort))
{
Serial.println("connection failed"); //网络请求无响应打印连接失败
return;
}
//URL请求地址
String url ="/v3/weather/now.json?key="自己的私钥"&location=beijing&language=zh-Hans&unit=c";
//发送网络请求
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
delay(5000);
//定义answer变量用来存放请求网络服务器后返回的数据
String answer;
while(client.available())
{
String line = client.readStringUntil('\r');
answer += line;
}
//断开服务器连接
client.stop();
Serial.println();
Serial.println("closing connection");
//获得json格式的数据
String jsonAnswer;
int jsonIndex;
//找到有用的返回数据位置i 返回头不要
for (int i = 0; i < answer.length(); i++) {
if (answer[i] == '{') {
jsonIndex = i;
break;
}
}
jsonAnswer = answer.substring(jsonIndex);
Serial.println();
Serial.println("JSON answer: ");
Serial.println(jsonAnswer);
}
修改替换后的代码长这样
#include <WiFi.h> //wifi库
#include <ArduinoJson.h> //Json库
#include <HTTPClient.h> //HTTP库
const char *ssid = "HUAWEI-86BPKD";
const char *password = "changgebaitong";
const char* host = "api.seniverse.com"; //心知天气服务器地址
String now_address="",now_weather="",now_temperature="";//用来存储报文得到的字符串
void setup()
{
Serial.begin(115200);
// 连接网络
WiFi.begin(ssid, password);
//等待wifi连接
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected"); //连接成功
Serial.print("IP address: "); //打印IP地址
//Serial.println(WiFi.localIP());
}
void loop()
{
//创建TCP连接
WiFiClient client;
const int httpPort = 80;
if (!client.connect(host, httpPort))
{
Serial.println("connection failed"); //网络请求无响应打印连接失败
return;
}
//URL请求地址
String url ="/v3/weather/now.json?key="换成自己私钥,不带双引号"&location=beijing&language=zh-Hans&unit=c";
//发送网络请求
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
delay(5000);
//定义answer变量用来存放请求网络服务器后返回的数据
String answer;
while(client.available())
{
String line = client.readStringUntil('\r');
answer += line;
}
//断开服务器连接
client.stop();
Serial.println();
Serial.println("closing connection");
//获得json格式的数据
String jsonAnswer;
int jsonIndex;
//找到有用的返回数据位置i 返回头不要
for (int i = 0; i < answer.length(); i++) {
if (answer[i] == '{') {
jsonIndex = i;
break;
}
}
jsonAnswer = answer.substring(jsonIndex);
Serial.println();
Serial.println("JSON answer: ");
Serial.println(jsonAnswer);
// Stream& input;
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, jsonAnswer);
}
JsonObject results_0 = doc["results"][0];
JsonObject results_0_location = results_0["location"];
const char* results_0_location_id = results_0_location["id"]; // "WX4FBXXFKE4F"
const char* results_0_location_name = results_0_location["name"]; // "北京"
const char* results_0_location_country = results_0_location["country"]; // "CN"
const char* results_0_location_path = results_0_location["path"]; // "北京,北京,中国"
const char* results_0_location_timezone = results_0_location["timezone"]; // "Asia/Shanghai"
const char* results_0_location_timezone_offset = results_0_location["timezone_offset"]; // "+08:00"
JsonObject results_0_now = results_0["now"];
const char* results_0_now_text = results_0_now["text"]; // "多云"
const char* results_0_now_code = results_0_now["code"]; // "4"
const char* results_0_now_temperature = results_0_now["temperature"]; // "25"
const char* results_0_last_update = results_0["last_update"]; // "2023-05-24T10:12:13+08:00"
测试一下这段代码,点开串口监视器(右上角一个放大镜形状),观察是否正确获取天气(类似于下面的代码段)
出现这个就成功了。(代码中出现的#include开头的都是库文件,按照我前面说的两种方法先下载一下。直接搜include后面的英文就行。)
- 最后进行天气显示
/*******************天气界面显示****************/
void show_weather(uint16_t fg,uint16_t bg)
{
tft.setSwapBytes(true); //使图片颜色由RGB->BGR
tft.pushImage(20, 0, 64, 64, weather[ph]);
//showMyFonts(90, 20, now_address.c_str(), TFT_WHITE);
if(ph==1){
tft.fillRect(89, 19, 36, 18, tft.color565(255, 80, 80));
}
else tft.fillRect(89, 19, 18, 18, tft.color565(255, 80, 80));
showMyFonts(90, 20, now_wea, TFT_WHITE);
showtext(90,40,2,1,fg,bg,now_temperature);
showsMyFont(107,43,"℃",TFT_YELLOW);
tft.pushImage(0, 65, 25, 25, temIcon);
tft.pushImage(0, 95, 25, 25, humIcon);
tft.pushImage(57, 65, 25, 25, rainIcon);
tft.pushImage(57, 95, 25, 25, windIcon);
showtext(28,105,2,1,fg,bg,now_hum+"%");
showtext(28,75,2,1,fg,bg,now_high_tem + "/" + now_low_tem);
showtext(87,102,2,1,fg,bg,now_wind_scale);
showsMyFont(97,104,"级",TFT_WHITE);
showtext(85,75,2,1,fg,bg,now_rainfall);
}
无非就是把刚刚获取到的天气信息用文字显示出来,再显示几个图片素材到指定位置就完成了天气的显示,图片显示我后面会说 先大概看一下。
3.2.6 图片显示
- 静态图片显示
- 像汉字取模软件一样,图片也是要取模后才能在屏幕上显示,所以我们同样新建一个图片文件
pic.h
,将图片取模的的数据用相同的方式存进去,例如:
#include <pgmspace.h>
const uint16_t weather0[0x1000] PROGMEM ={
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0010 (16)
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
...
...
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0FE0 (4064)
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0FF0 (4080)
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1000 (4096)
};
const uint16_t weather1[0x1000] PROGMEM ={..取模后的数据填在这里.};
const uint16_t weather2[0x1000] PROGMEM ={...};
const uint16_t weather3[0x1000] PROGMEM ={...};
- 图片取模方法:打开图片取模软件ImageConverter565.exe,Open image选择要转换的图片文件,可以看到像素大小,选择Arduino,保存为.c文件。
- 将其改为const uint16_t Name[ ] PROGMEM ={...}方式,和字体一样存到程序存储器中。
- 下面就可以采用以下程序在TFT屏幕上显示图片了:
tft.setSwapBytes(true); // RGB->BGR 加上这句才能正确显示颜色。
tft.pushImage(4, 4, 120, 120, Name); // 在(4,4)处显示Name图片 128×128像素
2.动态图片显示
动态图片其实就是把动态视频或者动图转换成每一帧gif转成帧-在线工具-BeJSON.com,
选取其中关键的几帧取模加入到程序中,利用循环使其动起来。可以在loop函数中,定义一个全局变量i控制显示的帧数,每次运行loop显示一帧照片并延时,就可以完成动态照片的显示。
#include <pgmspace.h>
//每一帧太空人的照片,一共九帧
const uint16_t astronaut1[0x1000] PROGMEM ={
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0010 (16)
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0020 (32)
...
...
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}
const uint16_t astronaut2[0x1000] PROGMEM ={...};
const uint16_t astronaut3[0x1000] PROGMEM ={...};
const uint16_t astronaut4[0x1000] PROGMEM ={...};
const uint16_t astronaut5[0x1000] PROGMEM ={...};
const uint16_t astronaut6[0x1000] PROGMEM ={...};
const uint16_t astronaut7[0x1000] PROGMEM ={...};
const uint16_t astronaut8[0x1000] PROGMEM ={...};
const uint16_t astronaut9[0x1000] PROGMEM ={...};
//定义太空人照片的指针数组
const uint16_t* Astronaut [] PROGMEM = {astronaut1,astronaut2,astronaut3,astronaut4,astronaut5,astronaut6,astronaut7,astronaut8,astronaut9};
int i = 0;
void loop{
tft.setSwapBytes(true); //使图片颜色由RGB->BGR
tft.pushImage(10, 55, 64, 64, Astronaut[i]); // (10,55) 显示 64 × 64 像素的图片
delay(100); //延时
i+=1; //下一帧
if(i>8){i=0;}
}
如果要在其他函数使用动态图片,也是类似的方法,不能用for循环,同时在loop函数中不用使用delay延时,因为动态图片就是要不断调用你要显示那个函数才能完成流畅的动态显示。所以不能影响时序。多个动态图片在不同函数中可以使用switch语句避免影响时序。
3.3 指纹算法
3.3.1指纹录入
指纹模块买我这个DS166也行,买AS608也行,两个算法是一样的,下面这两段主要是使用了
#include <Adafruit_Fingerprint.h>这个库函数。下载安装好这个库函数之后,在Arduino库函数中像之前打开Blink示例程序一样,打开这个库函数的示例程序,看一下就能看懂下面的代码了。
void Add_FR() //添加指纹
{
int i, ensure, processnum = 0;
int ID_NUM = 0;
int IDstate =1;
char str2[10];
while (1)
{
switch (processnum)
{
case 0:
i++;
tft.fillScreen(TFT_BLACK);
showMyFonts(32, 54,"请按手指",TFT_WHITE);
ensure = finger.getImage();
if (ensure == FINGERPRINT_OK)
{
ensure = finger.image2Tz(1); //生成特征
if (ensure == FINGERPRINT_OK)
{ const char* state5=State[5].c_str();
tft.fillScreen(TFT_BLACK);
showMyFonts(32, 54,"指纹正常",TFT_WHITE);
Serial.println(" 000 is true");
i = 0;
processnum = 1; //跳到第二步
}
else {};
}
else {};
break;
case 1:
{
i++;
tft.fillScreen(TFT_BLACK);
showMyFonts(32, 54, "再按一次",TFT_WHITE);
ensure = finger.getImage();
if (ensure == FINGERPRINT_OK)
{
ensure = finger.image2Tz(2); //生成特征
if (ensure == FINGERPRINT_OK)
{
tft.fillScreen(TFT_BLACK);
showMyFonts(32, 54, "指纹正常",TFT_WHITE);
i = 0;
processnum = 2; //跳到第三步
}
else {};
}
else {};
break;
}
case 2:
tft.fillScreen(TFT_BLACK);
showMyFonts(32, 54, "创建模板",TFT_WHITE);
ensure = finger.createModel();
if (ensure == FINGERPRINT_OK)
{
tft.fillScreen(TFT_BLACK);
showMyFonts(12, 54, "模板创建成功",TFT_WHITE);
processnum = 3; //跳到第四步
}
第四步是给录入指纹添加一个编号,方便后续管理。
case 3:
tft.fillScreen(TFT_BLACK);
showMyFonts(28,20,"请输入",TFT_WHITE);
showtext(75, 20,2,1,TFT_WHITE,TFT_BLACK,"ID");
showtext(40, 50,2,1,TFT_WHITE,TFT_BLACK,"ID=00");
showtext(30,74,2,1,TFT_WHITE,TFT_BLACK,"0=<ID<=99");
tft.setCursor(74, 110,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(134, 0, 179));
tft.println('#');
showMyFonts(84,110,"确认",TFT_WHITE);
while (IDstate)
{
char key = keypad.getKey();
if (key != NO_KEY) {
if (isdigit(key)) {
ID_NUM = (ID_NUM * 10) + (key - '0');
if (ID_NUM > 99) {
ID_NUM = 99;
}
} else if (key == '*') { // Handle clearing ID_NUM
ID_NUM = 0;
} else if (key == '#') { // Handle saving ID_NUM
// ID=ID_NUM; // Do something with the saved ID_NUM value here
IDstate=0;
//break;
}
// Update the display with the current ID_NUM value
if (ID_NUM < 10) {
sprintf(str2, "ID=0%d", ID_NUM);
} else {
sprintf(str2, "ID=%d", ID_NUM);
}
tft.fillScreen(TFT_BLACK);
// showtext(12, 34,1,1,TFT_WHITE,TFT_BLACK,"K1+ K2- K4 save");
showMyFonts(28,20,"请输入",TFT_WHITE);
showtext(75, 20,2,1,TFT_WHITE,TFT_BLACK,"ID");
showtext(40, 50,2,1,TFT_WHITE,TFT_BLACK,str2);
showtext(30,74,2,1,TFT_WHITE,TFT_BLACK,"0=<ID<=99");
tft.setCursor(74, 110,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(134, 0, 179));
tft.println('#');
showMyFonts(84,110,"确认",TFT_WHITE);
}
}
ensure = finger.storeModel(ID_NUM); //储存模板 它把finger对象(一个指纹识别器)的storeModel方法的返回值赋给ensure这个变量。
//storeModel方法是用来把指纹模板储存到指定的位置
//ID_NUM是一个变量,表示储存的位置编号
if (ensure == 0x00) //0x00是指纹识别器返回的一个状态码,表示储存成功。
{
const char* state10=State[10].c_str();
tft.fillScreen(TFT_BLACK);
showMyFonts(15, 54, state10,TFT_WHITE);
Serial.println("FR receive OK");
delay(1500);
return;
}
else
{
processnum = 0;
}
break;
}
delay(400);
if (i == 10) //超过5次没有按手指则退出
{
break;
}
}
我重新设计了编号的输入——用键盘更加高效方便的输入。键盘部分留在后面讲
3.3.2 指纹验证
指纹验证也是用的 <Adafruit_Fingerprint.h>库的getImage()和image2Tz()和fingerFastSearch()等方法/函数,详细用法可以参考示例程序。
void Check_FR()
{
tft.setTextColor(TFT_BLACK);
int ensure, i; // 验证指纹并开锁 函数
char str[20];
char cishu[5];
//u8g2.firstPage();
ensure = finger.getImage();
if (ensure == 0x00) //获取图像成功
{
ensure = finger.image2Tz();
if (ensure == 0x00) //生成特征成功
{
ensure = finger.fingerFastSearch();
if (ensure == 0x00) //搜索成功
{
// Mg966r();
open_door();
q++;
sprintf(str, "ID:%d Score:%d", finger.fingerID, finger.confidence);//这行代码的意思是将finger.fingerID和finger.confidence两个变量的值按照"ID: % d Score: % d"的格式写入到str字符串中
sprintf(cishu, " % d", q);
tft.setSwapBytes(true);
tft.fillScreen(TFT_BLACK);
for(int l=0;l<2;l++){
tft.pushImage(42, 32, 32, 32, Open_door [l]);
showtext(100,36,1,1, tft.color565(220,20,60),TFT_BLACK,cishu);
showtext(20,70,1,1,TFT_WHITE,TFT_BLACK,str);
const char* state11=State[11].c_str();
showMyFonts(16, 80, state11,TFT_WHITE);
delay(300);
}
tft.fillScreen(TFT_BLACK);
show_indoor();
delay(1500);
tft.fillScreen(TFT_BLACK);
}
else
{
buzzer();
tft.fillScreen(TFT_BLACK);
showMyFonts(16, 50, "未搜索到指纹",TFT_WHITE);
delay(500);
tft.fillScreen(TFT_BLACK);
}
}
}
3.3.3指纹删除
删除指纹主要包括删除单个指纹和删除指纹库,输入之前设定好的指纹ID或者按指定键盘上的字母功能键触发清除指纹库函数。
void Del_FR() //删除指纹
{
//tft.fillScreen(TFT_BLACK);
int ID_state=1;
int ensure;
int ID_NUM = 0;
char str2[10];
sprintf(str2, "ID=0%d", ID_NUM);
tft.fillScreen(TFT_BLACK);
showtext(25,50,2,2,TFT_WHITE,TFT_BLACK,str2);
showMyFonts(20,20,"请输入删除",TFT_WHITE);
tft.setCursor(0, 110,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(38, 115, 77));
tft.println('C');
showMyFonts(10,110,"清空指纹库",TFT_WHITE);
tft.setCursor(74, 110,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(134, 0, 179));
tft.println('#');
showMyFonts(84,110,"确认",TFT_WHITE);
while (ID_state)
{
char key = keypad.getKey();
if (key != NO_KEY) {
if (isdigit(key)) {
ID_NUM = (ID_NUM * 10) + (key - '0');
if (ID_NUM > 99) {
ID_NUM = 99;
}
}
else if (key == '*') { // Handle clearing ID_NUM
ID_NUM = 0;
}
else if (key == '#') { // Handle saving ID_NUM
ID_state=0;
}
else if (key='B') {
return ;
}
else if (key='C'){
finger.emptyDatabase(); //清空指纹库
tft.fillScreen(TFT_BLACK);
const char* state13=State[13].c_str(); //删除指纹库
const char* state15=State[15].c_str(); //成功
showMyFonts(8, 16, "删除指纹库成功",TFT_WHITE);
//showMyFonts(88, 16, state15,TFT_WHITE);
return;
delay(1500);
return ;
}
// Update the display with the current ID_NUM value
if (ID_NUM < 10) {
sprintf(str2, "ID=0%d", ID_NUM);
} else {
sprintf(str2, "ID=%d", ID_NUM);
}
tft.fillScreen(TFT_BLACK);
showMyFonts(20,20,"请输入删除",TFT_WHITE);
showtext(25,50,2,2,TFT_WHITE,TFT_BLACK,str2);
tft.setCursor(0, 110,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(38, 115, 77));
tft.println('C');
showMyFonts(10,110,"清空指纹库",TFT_WHITE);
tft.setCursor(74, 110,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(134, 0, 179));
tft.println('#');
showMyFonts(84,110,"确认",TFT_WHITE);
}
}
ensure = finger.deleteModel(ID_NUM); //删除单个指纹
if (ensure == 0)
{
tft.fillScreen(TFT_BLACK);
const char* state14=State[14].c_str(); //删除指纹
const char* state15=State[15].c_str(); //成功
showMyFonts(16, 56, "删除指纹成功",TFT_WHITE);
}
else
{
tft.fillScreen(TFT_BLACK);
const char* state14=State[14].c_str(); //删除指纹
const char* state16=State[16].c_str(); //失败
showMyFonts(16, 56, state14,TFT_WHITE);
showMyFonts(80, 56, state16,TFT_WHITE);
}
delay(1500);
return;
}
3.4 接入米家,远程控制
- 我们可以通过Blinker物联网开发平板把智能门锁接入到米家,原理就是Blinker可以提供米家控制的接口,把我们要接入的东西伪装成智能插座,风扇,台灯之类的。
- 首先我们下载点灯科技APP米家APP小爱音箱或者小爱同学
- 打开点灯科技APP,右上角+号添加一个独立设备
复制自己的KEY到下面代码
#define BLINKER_WIFI
#include <Blinker.h>
char auth[] = "Your Device Secret Key";
char ssid[] = "Your WiFi network SSID or name";
char pswd[] = "Your WiFi network WPA password or WEP key";
//APP
#define BUTTON_1 "ButtonKey"
BlinkerButton Button1(BUTTON_1); //点灯按键
void button1_callback(const String & state)
{
BLINKER_LOG("get button state: ", state);
if (state == BLINKER_CMD_BUTTON_TAP) {
BLINKER_LOG("Button tap!");
open_door();
tft.setSwapBytes(true);
tft.fillScreen(TFT_BLACK);
for(int l=0;l<2;l++){
showMyFonts(40,80,"验证成功",TFT_WHITE);
tft.pushImage(50, 32, 32, 32, Open_door [l]);
delay(300);
}}
else {
open_door();
}
}
void miotPowerState(const String & state) //点灯电源
{
BLINKER_LOG("need set power state: ", state);
if (state == BLINKER_CMD_ON) {
open_door();
tft.setSwapBytes(true);
tft.fillScreen(TFT_BLACK);
for(int l=0;l<2;l++){
showMyFonts(40,80,"验证成功",TFT_WHITE);
tft.pushImage(50, 32, 32, 32, Open_door [l]);
delay(300);
BlinkerMIOT.powerState("on");
BlinkerMIOT.print();
oState = true;
oState = false;
}}
else if (state == BLINKER_CMD_OFF) {
BlinkerMIOT.powerState("off");
BlinkerMIOT.print();
oState = false;
}
}
void miotQuery(int32_t queryCode) //状态回调
{
BLINKER_LOG("MIOT Query codes: ", queryCode);
switch (queryCode)
{
case BLINKER_CMD_QUERY_ALL_NUMBER :
BLINKER_LOG("MIOT Query All");
BlinkerMIOT.powerState(oState ? "on" : "off");
BlinkerMIOT.print();
break;
case BLINKER_CMD_QUERY_POWERSTATE_NUMBER :
BLINKER_LOG("MIOT Query Power State");
BlinkerMIOT.powerState(oState ? "on" : "off");
BlinkerMIOT.print();
break;
default :
BlinkerMIOT.powerState(oState ? "on" : "off");
BlinkerMIOT.print();
break;
}
}
void dataRead(const String & data) //点灯数据保存
{
BLINKER_LOG("Blinker readString: ", data);
Blinker.vibrate();
uint32_t BlinkerTime = millis();
Blinker.print("millis", BlinkerTime);
}
void setup()
{
// 初始化串口
Serial.begin(115200);
BLINKER_DEBUG.stream(Serial);
BLINKER_DEBUG.debugAll();
Blinker.begin(auth, ssid, pswd);
Blinker.attachData(dataRead);
Blinker.attachHeartbeat(heartbeat);
Blinker.begin(auth, ssid, pswd);
Blinker.attachData(dataRead);
Blinker.attachHeartbeat(heartbeat);
}
void loop() {
Blinker.run();
}
代码有点乱可以看一下Blinker官网的文档https://diandeng.tech/doc/xiaoai
代码中有四处需要修改
- 组件键名
就是APP上你创建一个按键这个按键的名字
下面是我的界面(供参考)
2.blinker appkey
3.wifi 名字
4.wifi 密码
- 实现小爱语音控制
1.打开米家app–>>我的–>>其他平台设备,添加设备
这样就在米家添加了我们的设备,就可以用小爱远程语音开门。
3.5 密码验证,菜单按键
- 键盘模块使用的是<Keypad.h>这个库函数,下面这是初始化,我就不多说了。
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
- 输入密码函数我主要的创新就是密码保护,像手机输密码一样输一位显示后立马加密,防止密码泄露。
void input_password() {
char buffer[5] = "";
int bufferIndex = 0;
// 在屏幕上显示输入框
tft.fillScreen(TFT_BLACK);
tft.pushImage(10, 50, 20, 20, user);
showMyFonts(20,20,"请输入密码",TFT_WHITE);
tft.setCursor(0, 0,2);
tft.setTextSize(1);
tft.setTextColor(TFT_GREEN);
tft.drawRect(30, 53, 80, 20, TFT_GREEN);
tft.setCursor(0, 110,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(38, 115, 77));
tft.println('*');
showMyFonts(10,110,"清空",TFT_WHITE);
tft.setCursor(0, 90,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE);
tft.setTextColor(TFT_WHITE,tft.color565(255, 80, 80));
tft.println('#');
showMyFonts(10,90,"确认",TFT_WHITE);
tft.setCursor(54, 90,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(134, 0, 179));
tft.println('A');
showMyFonts(64,90,"添加指纹",TFT_WHITE);
tft.setCursor(54, 110,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE ,tft.color565(30,144,255));
tft.println('D');
showMyFonts(64,110,"删除指纹",TFT_WHITE);
tft.setCursor(35, 55,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE,TFT_BLACK);
// 等待用户输入密码
while (true) {
// 读取键盘输入
char key = keypad.getKey();
if (key != NO_KEY) {
// 处理键盘输入
if (key == '#') {
// 如果输入完成,判断密码是否正确
if (strcmp(buffer, password) == 0) {
// 如果密码正确,调用开门函数
open_door();
tft.setSwapBytes(true);
tft.fillScreen(TFT_BLACK);
for(int l=0;l<2;l++){
showMyFonts(40,80,"验证成功",TFT_WHITE);
tft.pushImage(50, 32, 32, 32, Open_door [l]);
// tft.fillScreen(TFT_BLACK);
delay(300);
}
delay(1000);
tft.fillScreen(TFT_BLACK);
show_indoor();
delay(2000);
char buffer[5] = "";
//show_firstpage();
return;
}
else {
// 如果密码错误,提示重新输入
tft.fillScreen(TFT_BLACK);
tft.pushImage(32, 32, 64, 64, wrong);
showMyFonts(30,100,"密码错误",tft.color565(255, 80, 80));
delay(2000);
// delay(2000);
input_password();
}
} else if (key == '*') {
// 如果用户取消输入,返回时间显示界面
input_password();
return;
}
else if(key=='A'){
Check_password(1);
return;
}
else if(key=='D'){
Check_password(0);
return;
}
else if(key=='B'){
return;
}
else {
// 如果用户输入了字符,添加到缓冲区并在屏幕上显示
if (bufferIndex < 4) {
buffer[bufferIndex++] = key;
//tft.setCursor(0, 60,1);
tft.setTextSize(1);
tft.print(key);
delay(200);
tft.fillRect(tft.getCursorX() - 9, tft.getCursorY()+3, 10, 10, TFT_BLACK); // 清除时间显示区域
tft.setCursor(tft.getCursorX()-5 , tft.getCursorY());
tft.print("*");
}
}
}
}
}
- 最后就是,密码核验函数,用来检查密码是否正确。
void Check_password(int match){
char buffer1[5] = "";
int bufferIndex = 0;
// 在屏幕上显示输入框
tft.fillScreen(TFT_BLACK);
showMyFonts(20,20,"请输管理密码",TFT_WHITE);
tft.pushImage(10, 50, 20, 20, admin);
tft.drawRect(30, 50, 80, 20, TFT_WHITE);
tft.setCursor(0, 110,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(38, 115, 77));
tft.println('*');
showMyFonts(10,110,"清空",TFT_WHITE);
tft.setCursor(0, 90,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(220,20,60));
tft.println('B');
showMyFonts(10,90,"返回",TFT_WHITE);
tft.setCursor(74, 90,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(30,144,255));
tft.println('#');
showMyFonts(84,90,"确认",TFT_WHITE);
tft.setCursor(74, 110,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, tft.color565(134, 0, 179));
tft.println('C');
showMyFonts(84,110,"首页",TFT_WHITE);
tft.setCursor(35, 55,2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE);
// 等待用户输入密码
while (true) {
// 读取键盘输入
char key = keypad.getKey();
if (key != NO_KEY) {
// 处理键盘输入
if (key == '#') {
// 如果输入完成,判断密码是否正确
if ((strcmp(buffer1, admin_password) == 0)&&(match==0)){
delay(100);
Del_FR();
char buffer1[5] = "";
return;}
else if((strcmp(buffer1, admin_password) == 0)&&(match==1)){
delay(100);
Add_FR();
char buffer1[5] = "";
return;}
else {
// 如果密码错误,提示重新输入
tft.fillScreen(TFT_BLACK);
tft.pushImage(32, 32, 64, 64, wrong);
showMyFonts(20,100,"密码错误",tft.color565(255, 80, 80));
delay(2000);
Check_password(1);
}
} else if (key == '*') {
// 如果用户取消输入,返回时间显示界面
Check_password(1);
//char buffer1[5] = "";
return;
}else if (key == 'B') {
return;}
else if (key == 'C') {
return;}
else {
// 如果用户输入了字符,添加到缓冲区并在屏幕上显示
if (bufferIndex < 4) {
buffer1[bufferIndex++] = key;
//tft.setCursor(0, 60,1);
tft.setTextSize(1);
tft.print(key);
delay(200);
tft.fillRect(tft.getCursorX() - 9, tft.getCursorY()+3, 10, 10, TFT_BLACK); //清除显示区域
tft.setCursor(tft.getCursorX() - 5, tft.getCursorY());
tft.print("*");
}
}
}
}
}
四. 完结补充
4.1 屏幕镜像效果设置
按照上面一步步进行就可以完美实现视频中的效果,还有一些细节我补充一些,第一个就是要是需要透明显示,就要把屏幕镜像显示,这样经过分光棱镜的折射才是正常的显示效果。
打开D:\mydoc\Arduino\libraries\TFT_eSPI-2.5.0\TFT_Drivers下的ST7735_Rotation.h文件
打开之后在最后加多一会case4,屏幕镜像有好多种镜像方式,我注释的那一个是XY轴互换镜像放上棱镜要从侧面才是镜像前的镜像,下面那种就是xy轴都没变化只是镜像,放上去棱镜就是和没镜像的显示效果一样,如果想使用其他镜像效果请参考。
第二行
这样也改一下。
4.2 打印外壳
为了能固定住棱镜并且增加美观度,3D打印出来一个外壳。
4.3 结语
第一次写博客,第一次用Markdown语法,肝了两天十多个小时终于写完了。完整代码等我后续整理好后放出来,有什么疑问或者问题都可以在评论区提出,谢谢大家。
上文所使用的一些软件我放这里了链接:https://pan.baidu.com/s/1TSlxtZtBALKJ6aZ1NNHbCw?pwd=6wv9
提取码:6wv9 。
主要参考如下: