简易指纹锁の制作V1.7

一个简易指纹锁的制作

我们学校似乎有一个不成文的习俗:
电创每年都有人在大一的时候给宿舍改装指纹锁
刚好有闲置的51单片机,查了下常用的指纹传感器是AS608主控,采用UART协议或USB协议。

查了下AS608传感器资料,发现这货指纹对比是在传感器内处理的,不需要自己写指纹对比算法,也不需要把指纹图形回传,只需要简单的串口通信就能完成指纹识别。(PS:相关资料我会放到底部)

按照最简洁的程序逻辑,仅仅只需要四步:

  1. 从传感器获取图像
  2. 根据原始图像生成指纹特征存于缓存
  3. 将缓存中的特征文件搜索整个指纹库
  4. 开锁

查阅模块说明可得:
前三步对应的官方指令为:

  1. PS_GetImage
  2. PS_GenChar
  3. PS_Search

翻译为16进制命令包为:

  1. \xef\x01\xff\xff\xff\xff\x01\x00\x03\x01\x00\x05
  2. \xef\x01\xff\xff\xff\xff\x01\x00\x04\x02\x01\x00\x08
  3. \xef\x01\xff\xff\xff\xff\x01\x00\x08\x04\x01\x00\x00\x01\x2b\x00\x3a

应答包格式为:

确认码定义为:

1. 00h:表示指令执行完毕或 OK;
2. 01h:表示数据包接收错误;
3. 02h:表示传感器上没有手指;
4. 03h:表示录入指纹图像失败;
5. 04h:表示指纹图像太干、太淡而生不成特征;
6. 05h:表示指纹图像太湿、太糊而生不成特征;
7. 06h:表示指纹图像太乱而生不成特征;
8. 07h:表示指纹图像正常,但特征点太少(或面积太小)而生不成特征;
9. 08h:表示指纹不匹配;
10. 09h:表示没搜索到指纹;
11. 0ah:表示特征合并失败;
12. 0bh:表示访问指纹库时地址序号超出指纹库范围;
13. 0ch:表示从指纹库读模板出错或无效;
14. 0dh:表示上传特征失败;
15. 0eh:表示模块不能接受后续数据包;
16. 0fh:表示上传图像失败;
17. 10h:表示删除模板失败;
18. 11h:表示清空指纹库失败;
19. 12h:表示不能进入低功耗状态;
20. 13h:表示口令不正确;
21. 14h:表示系统复位失败;
22. 15H:表示缓冲区内没有有效原始图而生不成图像;
23. 16H:表示在线升级失败;
24. 17H:表示残留指纹或两次采集之间手指没有移动过;
25. 18H:表示读写 FLASH 出错;
26. 0xf0:有后续数据包的指令,正确接收后用 0xf0 应答;
27. 0xf1:有后续数据包的指令,命令包用 0xf1 应答;
28. 0xf2:表示烧写内部 FLASH 时,校验和错误;
29. 0xf3:表示烧写内部 FLASH 时,包标识错误;
30. 0xf4:表示烧写内部 FLASH 时,包长度错误;
31. 0xf5:表示烧写内部 FLASH 时,代码长度太长;
32. 0xf6:表示烧写内部 FLASH 时,烧写 FLASH 失败;
33. 0x19:未定义错误;
34. 0x1a:无效寄存器号;
35. 0x1b:寄存器设定内容错误号;
36. 0x1c:记事本页码指定错误;
37. 0x1d:端口操作失败;
38. 0x1e:自动注册(enroll)失败;
39. 0x1f:指纹库满
40. 0x20—0xefh:Reserved。 

其中,39种在最简逻辑下都用不到 只需要判断是否返回成功00h就行了。
也就是判断应答包第十位是否为00h即可
将上面三行命令包发送给AS608并判断返回值就能完成指纹验证。

不过网上有现成的指令包代码,那我为什么要重复造轮子呢?
简单删改之后,就完成了

另外一个事情就是AS608工作原理的选择:
现在市面上就两种指纹传感器:

  1. 光学指纹
  2. 电容指纹

数码圈的都知道 电容的好用,速度快,但是湿手不能解锁
不过问题不大,因为光学的传感器实在是太大了!!!

*到这里 一切都很顺利 原本以为不到1天就能做完一个指纹锁,能打破本校最快记录来着… *

为什么是V1.6呢?因为失败了5次。。。

接下来需要硬件进行测试
线怎么接呢?

官方的模块就按照官方给的引脚定义就行了

不过不同卖家卖的AS608模块引脚定义可能不一样,具体的自己去找

别问我为什么要强调上面一句,因为我曾经就按照官方引脚定义接了个第三方的指纹模块
刚接上去的时候它很热,还有点烫手,不过现在它已经凉了

PS:指纹模块也太贵了吧!根本烧不起
用杜邦线和手头小舵机进行原理验证,很成功

随手焊好板子验证原理,接上我的小舵机,没有任何问题

但是这个舵机明显带不动门栓啊,SG90寿命又不好,那只能换上一个大舵机了。

逛了下淘宝,嗯哼?这么便宜?
够大 够黑 够暴力

不换不知道,一换踩了个大坑。
当时我已经跟舍友打包票说三天后舵机一到 指纹锁就能用来着。。。
看样子flag不能随便立

换上去之后舵机根本就不动
首先排除了舵机问题 因为我用STM32的舵机控制板是能正常工作的
因为舵机控制是50HZ的,并且我本人还没有示波器,只能成天往电创实验室跑
为啥呢?
穷!

首先怀疑供电问题
电压从5V一路调到了8.4V,还是不动
为什么用8.4V呢?因为我想着两节锂电池串联刚好8.4V,网上说了,MG996R能抗7.4V,那不就是锂电池的标准电压么?那两节锂电池充满电压应该没问题
然后示波器打了下供电,发现供电波纹很平稳
然后怀疑PWM吧,空载输出频率正常,电压正常,占空比正常…
接上小舵机 一切正常
接上大舵机 测PWM 一切正常…

见鬼了

然后回到宿舍,连上STM32的舵机控制板,咋TM不转了???
嗯,一定是舵机的问题
然后从网上下单舵机,等快递。
一天..两天…三天…四天…五天……十三天…十四天…
咋还卡在重庆分拨中心?
一看快递商 “百世快递”
算了 我还是重新买吧。。。
三天后 拿到舵机 确保万无一失的情况下 接了上去。。。
咋还不能用???
没事 坚强。。。

然后示波器往供电上打,正常
然后打PWM,,等等,不对劲啊

好嘞 问题找到了
然后怎么处理呢?
59C52的IO口可没有推挽输出,这种内部10K上拉电阻的,本来推力就不大
我就就想到两种解决方案:

  1. 再外接上拉电阻
  2. PNP三极管放大
  3. 换主控

1的话,不稳定 排除。
3的话,懒得换了..
那就2吧。。。常用的PNP是啥来着?S8550?查了下S8550的极限输出,可以,1.5安,够了。
然后我又又又上网买三极管去了。
为啥叫又又又?因为第一回烧指纹,第二回烧舵机,这是第三回。。。别急,后面还有第四回。。。

S8550加进去,PWM取反,一顿操作猛如虎,一看是个二百五
舵机接上去 咋MCU又TM疯狂重启了???
这回想都不用想,肯定是舵机启动瞬间拉低了单片机电流,然后重启

这不简单嘛~ DC-DC升压输出端接个二极管,二极管后面再来几个大电容,这样掉电瞬间就把电源隔离了,足够稳定。
顺便处理一下舵机待机耗电问题吧,毕竟里面也是有一个控制IC的
用什么控制呢?

  1. 场效应管
  2. 继电器

用继电器吧,因为继电器能发出声音提示指纹不符

好家伙 又踩进一个大坑。

二极管加进去之后,因为是硅二极管,所以理论上有0.5V的压降的,然后实际一测,0.7V。
算了,几伏无所谓,最后到单片机还有5.2V左右就行了。
然后一不小心又把电压拉高了,单片机又死一个。。。。

还记得刚刚说的大坑么?
51的IO没法直接控制继电器我还是知道的,然后手头还有PNP二极管,就直接用PNP推继电器了
然后发现一个问题:
TM我给51切断VCC之后 为什么继电器是常开状态?
TM没道理啊,51都没电了 51IO内部的等效三极管应该是断开了啊?
算了 不管了 换NPN。
还好NPN比较常用 能直接从电创白嫖
……
…..

我想去死一死

51的IO输出实在是太小了,随手在NPN基极和IO之间接上的10K限流电阻经过放大之后居然连继电器都带不动???
然后一个一个试 直到换成220欧,才能稳定导通。
一测此时继电器电压 4V…

算了 4V就4V吧…
又不是不能用…

然后电路图就成了这样(单击放大)

然后一切正常,焊接,安装,刷入…
终于完成了
待机电流,150ma…
算了 懒得管了 又不是不够用,毕竟白天都是有电源输入的,配上4000mah的锂电池,绝对够用。

本代码仅适用于11.0592MHz的12T 51单片机使用,推荐89C52,便宜
新手千万不要买成AT89C52,需要专门的下载器才能下载程序!!!
因为用了PNP三极管去推动舵机信号输入,所以这里的占空比是反向的,IO输出为高时舵机接收到的信号为低!!!
其他频率和机器周期的51单片机需要修改波特率和舵机的相关配置!
不同门锁需要修改舵机角度(占空比)的相关配置!

↓FingerPrintLock.c↓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#include <reg52.h>
#include <stdio.h>
#include <string.h>
#include "AS608.h"
unsigned char count; //0.5ms次数标识
sbit pwm = P1 ^ 0 ; //PWM信号输出,连舵机黄线
sbit Relay = P1 ^ 1 ;
sbit check = P2 ^ 0;
unsigned char jd; //角度标识
uchar xdata Data_buff[MAX]; //初始化结构体
struct as608 As_608_data = {0};
int judge = 1;
char locked = 0;

void delayms(unsigned int i) //延时
{
unsigned int j, k;
for (j = i; j > 0; j--)
for (k = 110; k > 0; k--);
}

void Time0_Init() //定时器0初始化
{
TH0 = (65536 - 440) / 256; //1.0851us一个计数周期
TL0 = (65536 - 440) % 256; //初始化中断器
TMOD = 0x01; //定时器0工作在方式1
IE = 0x82; //IE=0x82=1000 0010 等价于 EA=1 开总中断 ET0=1 开定时器0中断
TR0 = 1; //开定时器0
}

void Time0_Int() interrupt 1 //中断程序
{
TH0 = (65536 - 440) / 256; //1.0851us一个计数周期
TL0 = (65536 - 440) % 256; //重新初始化中断器
if (count < jd)
{
pwm = 0; //高电平次数小于目标角度,PWM输出高电平(一次高电平输出0.5ms)
count = (count + 1); //总次数加1
count = count % 40; //确保总次数始终保持低于40 即保持周期为20ms
} else {
pwm = 1; //高电平次数大于等于目标角度,PWM输出低电平
count = (count + 1); //总次数加1
count = count % 40; //确保总次数始终保持低于40 即保持周期为20ms
}

}

void UART_Init(void) //波特率已最高为57600,12T 晶振11.0592MHz
{
SCON = 0x50;
TMOD &= 0x0f;
TMOD = 0x20;
PCON = 0x80;
TH1 = 0xFF;
TL1 = 0xFF;
ET1 = 0;
TR1 = 1;
}

//*******************************************
//函数名:Send_Bytes
//功能:发送多个字节
//参数:*c:首地址len:长度
//指令代码:无
//返回值: 无
//*******************************************
void Send_Bytes(uchar *c, uchar len)
{
uchar i = 0;
for (i = 0; i < len; i++)
{
SBUF = *(c + i);
while (!TI);
TI = 0;
}
}

//*******************************************
//函数名:Receive_Bytes
//功能:接收多个字节
//参数:*c:首地址len:长度
//指令代码:无
//返回值: 无
//*******************************************
char Receive_Bytes(uchar *c, uchar len)
{
uchar i = 0;
uchar time = 200;
for (i = 0; i < len; i++)
{
while (!RI && time--)
{
if (time <= 1)
return MI_NOTAGERR;
}
*(c + i) = SBUF;
RI = 0;
}
return MI_OK;
}

//*******************************************
//函数名:PS_GetImage
//功能:从传感器上读入图像存于图像缓冲区
//参数:无
//指令代码:01H
//返回值:有
//*******************************************
char PS_GetImage()
{

uchar *ps2 = "\xef\x01\xff\xff\xff\xff\x01\x00\x03\x01\x00\x05"; //指令码

Send_Bytes(ps2, 12); //发送指令码

while (RI == 0);

if (Receive_Bytes(Data_buff, 12)) //接收应答包
{
return MI_ERR;
}

if (Data_buff[9] == 0x00 && Data_buff[11] == 0x0a) //判断确认码是否成功 后面为校验和判断
{
return MI_OK;
}
return MI_ERR;
}

//*******************************************
//函数名:PS_GenChar
//功能:将 ImageBuffer(图像缓冲区) 中的原始图像生成指纹特征文件存于 CharBuffer1 或 CharBuffer2
//参数:BufferID(特征缓冲区号)
//指令代码:02H
//返回值:有
//*******************************************
char PS_GenChar(uchar BufferID)
{

uchar *ps1 = "\xef\x01\xff\xff\xff\xff\x01\x00\x04\x02\x01\x00\x08"; //存放CharBuffer1的指令码
uchar *ps2 = "\xef\x01\xff\xff\xff\xff\x01\x00\x04\x02\x02\x00\x09"; //存放CharBuffer1的指令码
if (BufferID == 0x01)
{
Send_Bytes(ps1, 13); //发送指令码
} else {
Send_Bytes(ps2, 13); //发送指令码
}

while (RI == 0);

if (Receive_Bytes(Data_buff, 12)) //接收应答包
{
return MI_ERR;
}

if (Data_buff[9] == 0x00 && Data_buff[11] == 0x0a) //判断确认码是否成功 后面为校验和判断
{
return MI_OK;
}

return MI_ERR;
}

//*******************************************
//函数名:PS_Search
//功能:以 CharBuffer1 或 CharBuffer2 中的特征文件搜索整个或部分指纹库。若搜索到,则返回页码
//参数:BufferID, (StartPage(起始页),PageNum(页数))(默认从0到300全局搜索)
//指令代码:04H
//返回值:确认码、页码(ID)
//*******************************************
char PS_Search(uchar BufferID)
{

uchar *ps1 = "\xef\x01\xff\xff\xff\xff\x01\x00\x08\x04\x01\x00\x00\x01\x2b\x00\x3a"; //存放CharBuffer1的指令码
uchar *ps2 = "\xef\x01\xff\xff\xff\xff\x01\x00\x08\x04\x02\x00\x00\x01\x2b\x00\x3b";


if (BufferID == 0x01)
{
Send_Bytes(ps1, 17); //发送指令码
} else {
Send_Bytes(ps2, 17); //发送指令码
}

while (RI == 0);

if (Receive_Bytes(Data_buff, 16)) //接收应答包
{
return MI_ERR;
}


if (Data_buff[9] == 0x00) //判断确认码是否成功
{
return MI_OK;
}

return MI_ERR;
}

void lock()
{
Relay = 1;
delayms(100);
Time0_Init();
count = 0;
jd = 2;
delayms(1000);
locked = 1;
Relay = 0;
}

void unlock()
{
Relay = 1;
delayms(100);
Time0_Init();
count = 0;
jd = 5;
delayms(1000);
if (check == 1) { //门未开时为高电平
delayms(2500);
} else {
delayms(1500);
}
Time0_Init();
count = 0;
jd = 2;
delayms(1000);
Relay = 0;
}
void main()
{
check = 1;
lock();
unlock();
RI = 0;
while (1) {
UART_Init();
if (PS_GetImage() == MI_OK) {
if (PS_GenChar(0x01) == MI_OK) {
if (PS_Search(0x01) == MI_OK) {
unlock();
} else {
Relay = 1;
delayms(250);
Relay = 0;
delayms(250);
Relay = 1;
delayms(250);
Relay = 0;
delayms(250);
}
}
}
}
}

↓AS608.h↓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
作者:超神NK
发处:51hei论坛
时间:2018年8月3号


ps:纯个人整理,都是根据手册命名的函数,帮助小白学习,本人纯手打,绝对没有任何抄袭。(全网可查)

*ps:2018年8月5号更新,有些函数没有去添加,感觉不太常用的,剩下的也就是记事本功能有点用,这个还是留给大家去添加吧!
*/


#ifndef _AS608_H_


#include "reg52.h"

#ifndef uchar
#define uchar unsigned char
#endif

#ifndef uint
#define uint unsigned int
#endif

#ifndef ulong
#define ulong unsigned long
#endif


#define MAX 128 //定义最大接收多少字节数据(根据数据包大小定义,默认是128)

/*错误码*/
#define MI_OK 0
#define MI_NOTAGERR (-1)
#define MI_ERR (-2)




/*

PS:这里只是定义了一些,还有很多可以添加。
例如:芯片地址保存、记事本的用户信息等,可自行在里面定义

*/
typedef struct as608
{
uint StateRegister; //保存设备的状态寄存器

uint SensorType; //保存传感器类型

uint zw_library; //保存设备的指纹库大小

uint SecurLevel; //保存设备的安全等级

unsigned long addr; //保存设备地址

uint CFG_PktSize; //保存设备的数据包大小

unsigned long UARTs; //保存设备的波特率大小

unsigned long Random; //用来存取返回的随机数

// ......
};



extern struct as608 As_608_data;





/*返回的确认码说明合集*/ /*
1. 00h:表示指令执行完毕或 OK;
2. 01h:表示数据包接收错误;
3. 02h:表示传感器上没有手指;
4. 03h:表示录入指纹图像失败;
5. 04h:表示指纹图像太干、太淡而生不成特征;
6. 05h:表示指纹图像太湿、太糊而生不成特征;
7. 06h:表示指纹图像太乱而生不成特征;
8. 07h:表示指纹图像正常,但特征点太少(或面积太小)而生不成特征;
9. 08h:表示指纹不匹配;
10. 09h:表示没搜索到指纹;
11. 0ah:表示特征合并失败;
12. 0bh:表示访问指纹库时地址序号超出指纹库范围;
13. 0ch:表示从指纹库读模板出错或无效;
14. 0dh:表示上传特征失败;
15. 0eh:表示模块不能接受后续数据包;
16. 0fh:表示上传图像失败;
17. 10h:表示删除模板失败;
18. 11h:表示清空指纹库失败;
19. 12h:表示不能进入低功耗状态;
20. 13h:表示口令不正确;
21. 14h:表示系统复位失败;
22. 15H:表示缓冲区内没有有效原始图而生不成图像;
23. 16H:表示在线升级失败;
24. 17H:表示残留指纹或两次采集之间手指没有移动过;
25. 18H:表示读写 FLASH 出错;
26. 0xf0:有后续数据包的指令,正确接收后用 0xf0 应答;
27. 0xf1:有后续数据包的指令,命令包用 0xf1 应答;
28. 0xf2:表示烧写内部 FLASH 时,校验和错误;
29. 0xf3:表示烧写内部 FLASH 时,包标识错误;
30. 0xf4:表示烧写内部 FLASH 时,包长度错误;
31. 0xf5:表示烧写内部 FLASH 时,代码长度太长;
32. 0xf6:表示烧写内部 FLASH 时,烧写 FLASH 失败;
33. 0x19:未定义错误;
34. 0x1a:无效寄存器号;
35. 0x1b:寄存器设定内容错误号;
36. 0x1c:记事本页码指定错误;
37. 0x1d:端口操作失败;
38. 0x1e:自动注册(enroll)失败;
39. 0x1f:指纹库满
40. 0x20—0xefh:Reserved。
*/


/*函数声明*/
void SendOneByte(unsigned char c);
char Receive_Bytes(uchar *c,uchar len);
void UART_Init(void);
char PS_GetImage();
char PS_GenChar(uchar BufferID);
char PS_Match();
char PS_Search(uchar BufferID);
char PS_RegModel();
char PS_StoreChar(uchar BufferID,uint PageID);
char PS_LoadChar(uchar BufferID,uint PageID);
char PS_Empty();
char PS_VfyPwd();
char PS_Enroll();
char PS_Identify();
char PS_DeletChar(uint PageID,uint count);
char PS_WriteReg(uchar reg,uchar cont);
char PS_ReadSysPara();
char PS_Enroll();
char PS_Identify();
char PS_SetPwd(unsigned long PassWord);
char PS_VfyPwd(unsigned long PassWord);
char PS_GetRandomCode();
char PS_StoreChar(uchar BufferID,uint PageID);



char Record_SaveLibrary(uint ID);

#endif

一算时间,花了快一个月…
这尼玛不就是平均水平么???
尼玛,快递卡路上卡了15天…
当初要是用Arduino或者Stm32就没有这么多事!
算了,挑战不了最快,那就挑战最省电吧…
用32还是51?管他什么RTC,我就用51了,用外围电路就干他RTC…
Emmmm…看了下时间…快期末了啊…寒假再说吧…

PS

  1. 关于录指纹:
    |建议用电脑直接录,因为有屏幕,单片机再外接一个屏幕做UI太鸡肋了。毕竟都上大学了 谁宿舍还没台电脑啊
    |建议使用USB协议而不是串口协议,因为串口实在是太慢了。
    |建议一个人只录一个指头 每个指头不同角度录5-10个就行了。

  2. 下一版本将于一个月内发布。

更新日志

2020-01
电路图上增加了续流二极管

2020-02-15
优化了一字节的ROM

Video

Download

AS608官方资料
文件太大了 百度一堆就不放了
程序&电路图