# 指纹开门
**Repository Path**: jswyll_com/fingerprint-open-the-door
## Basic Information
- **Project Name**: 指纹开门
- **Description**: 指纹开门方案、源代码
- **Primary Language**: C
- **License**: LGPL-3.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 2
- **Created**: 2021-03-29
- **Last Updated**: 2023-10-26
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 宿舍指纹开门
> 原文:https://jswyll.com/note/fingerprint_opendoor/
## 效果展示

指纹匹配,开门成功

指纹不匹配,不开门
---
## 控制方案

1. 单片机向指纹模块发送指令,控制模块执行相应操作;
2. 单片机读取指纹模块返回的结果码,判断按下的指纹与录入过的指纹是否匹配;
3. 如果匹配则驱动电机开门,否则不动作。
---
## 硬件选择
- 指纹模块。指纹识别算法较为复杂、困难,选择集成了各功能的指纹模块可以大大减少开发周期,我们只需通过UART协议向模块发送手册制定的指令即可。由于光学式指纹模块的背光需要常亮,这里采用了电容式指纹模块:深圳市三能智控电子科技有限公司的ID1016C指纹模块,¥51,链接: [https://m.tb.cn/h.VyfWAZe?sm=4e567d](https://m.tb.cn/h.VyfWAZe?sm=4e567d)
- 开门驱动。对于如下图的锁,牵引把手即可开门。使用舵机来驱动开门,成本约¥15,链接:[【淘宝】https://m.tb.cn/h.fpYlFGd?tk=01NW2ghNvCz「MG995 MG996R 金属标准舵机数码数字机器人13KG舵机180度 360度」](https://m.tb.cn/h.fpYlFGd?tk=01NW2ghNvCz)。

- 单片机。STC的单片机代码通用,本设计只需要一对UART(串口)和一个普通IO口,为了缩小电路板面积,选择STC15W204S,成本¥1~3。
- 电源接口。用一个输出5V的USB接口,用当下流行的Type-C接口供电到电路板;同时Type-C也可以接到电脑上进行调试、下载程序。
---
## 电路板绘制
使用立创EDA绘制,原理图及PCB见:[https://oshwhub.com/jswy/opendoor-fingerprint](https://oshwhub.com/jswy/opendoor-fingerprint)
### Type-C母座
整个电路由Type-C供电,在电路板上画一个16引脚的Type-C母座。其中`CC1`、`CC2`、`SUB1`、`SUB2`引脚没有使用到,可以悬空处理,这里直接在封装将这几个引脚去掉。`DP1/DP2`、`DN1/DN2`分别是USB的`D+`、`D-`信号.

### USB转串口
电脑的USB信号和单片机的串口信号是不一样的,使用小巧的CH340E芯片进行转换

### 复位电路
STC单片机下载程序时需要将电源断电然后重新上电,这里使用按键+MOS管控制电源的通断。(由于断电时串口仍需正常工作,所以USB转串口的电源直接接自USB。)

### 稳压电路
单片机和指纹模块的工作电压是3.3V,使用AMS1117-3.3芯片将USB电源的5V转为3.3V

### 舵机接口
舵机的电源是5\~6V,舵机自带母头的杜邦线+延长的公对公杜邦线,所以预留一个3PIN的排母接口

### 单片机
除了3.3V以外,TXD、RXD接指纹模块,再加一个普通IO口接舵机PWM控制引脚

### 指纹模块接口
指纹模块有6PIN 1.0mm间距的排线,留出4个洞。

### 3D预览
画完PCB后在立创EDA打开3D预览:

---
### 接线
指纹模块红线开始、白线结束, 分别为模块的GND、RX、TX、VIN、IRQ、VCC
---
## 程序设计
- 单片机与指纹模块UART通信。查看卖家提供的模块手册知,模块默认波特率为115200bps,每次控制命令发26字节(以0x55AA开头),返回26字节应答码(以0xAA55开头),其它各字节含义见手册。
- 判断有无手指按下。直接发送26个字节"\x55\xAA\x00\x00\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x01"(\x表示16进制转义),返回的第11个字节的值为1则表示有手指按下。循环发送该指令可以检测有无手指按下,如果有才进行后续操作,否则继续轮询。
- 指纹是否匹配判断。发送26个字节"\x55\xAA\x00\x00\x63\x00\x06\x00\x00\x00\x01\x00\xC8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x31\x02"来验证,返回的第7个字节等于5表示指纹与录入过的匹配,第11个字节表示匹配的指纹编号。
- 驱动开门。由舵机控制原理可知,PWM的控制周期为20ms,高电平持续的时间为0.5、1.0、1.5、2.0、2.5分别对应舵机转到0、90、180、270、360度的位置。根据实际安装位置,经过调试后选取0.5\~1.3ms的控制范围对应门的关、开。设置定时器的中断周期为10微秒,每次中断计数变量加1,则PWM阈值50和130分别对应0.5和1.3ms。为了避免累积误差,下载程序时使用12.000MHz主频。
```c
/** @brief 定时器计数、PWM值 */
int T0_count, pwm = 50;
/**
* @brief 定时器0初始化,设置每10微秒产生中断
*/
void Timer0Init(void) // 10微秒@12.000MHz
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x88; //设置定时初值
TH0 = 0xFF; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
}
/**
* @brief 定时器中断,用于产生PWM
*/
void T0_IRQHandler() interrupt 1
{
if (T0_count < pwm)
door = 1;
else
door = 0;
if (++T0_count == 2000)
T0_count = 0;
}
```
开门是舵机PWM值从50变为130,为了避免瞬间阻力过大导致电路欠压,从而致使单片机复位,打舵时平滑过渡到130:
```c
/**
* @brief 开门。绿灯亮,PWM驱动舵机开门
*/
void open_door(void)
{
int i = 0;
send_cmd(CMD_LED_GREEN_ON);
/* 平滑打舵 */
for (i = 0; i < 130; i += 3)
{
pwm = 50 + i;
delay_ms(1);
}
delay_ms(800);
pwm = 50;
}
```
- 录入新指纹。由于指纹比对时返回的第11个字节表示匹配的指纹编号,我们可以根据自己录入的其中一个或者某个范围的编号来“定义”谁是管理员,当检测管理员的指纹时,将后续按下的几个指纹作为新录入的指纹。
使用Keil C51编译代码,使用STC-ISP软件下载程序到单片机。
---
## 完整源代码
```c
/*
* Copyright (c) 2019-2021, jswy
*
* SPDX-License-Identifier: LGPL-3.0
*
* Change Logs:
* Date Author Notes
* 2019-03-01 jswy the first version
*/
/**
* @brief 相关寄存器
*/
#include
#include "intrins.h"
sfr AUXR = 0x8E;
sfr T2H = 0xD6;
sfr T2L = 0xD7;
sbit door = P3 ^ 3;
/**
* @brief 指纹模块控制命令
*/
typedef enum
{
CMD_LED_RED_ON, /**< 亮红灯 */
CMD_LED_GREEN_ON, /**< 亮绿灯 */
CMD_LED_YELLOW_ON, /**< 亮黄灯 */
CMD_LED_ALL_OFF, /**< 熄灭所有灯 */
CMD_FINGER_DETECT, /**< 检测有无手指按下 */
CMD_GET_IMAGE, /**< 从指纹模块采集指纹数据 */
CMD_SEARCH_FINGERPRINT, /**< 验证是否匹配已录入的指纹 */
CMD_GET_EMPTY_ID, /**< 获取未录入指纹的第一个编号 */
CMD_GENERATE_0, /**< 暂存录入的第一次指纹 */
CMD_GENERATE_1, /**< 暂存录入的第二次指纹 */
CMD_GENERATE_2, /**< 暂存录入的第三次指纹 */
CMD_MERGE_CHAR, /**< 合并录入的三次指纹特征 */
CMD_STORE_CHAR /**< 存储合并后的指纹 */
} Command_t;
/**
* @brief 控制命令数据,和前面的枚举一一对应
*/
code unsigned char cmd_data[][26] =
{
"\x55\xAA\x00\x00\x24\x00\x04\x00\x03\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2C\x01",
"\x55\xAA\x00\x00\x24\x00\x04\x00\x03\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2B\x01",
"\x55\xAA\x00\x00\x24\x00\x04\x00\x03\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x01",
"\x55\xAA\x00\x00\x24\x00\x04\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2A\x01",
"\x55\xAA\x00\x00\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x01",
"\x55\xAA\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1F\x01",
"\x55\xAA\x00\x00\x63\x00\x06\x00\x00\x00\x01\x00\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xB9\x01",
"\x55\xAA\x00\x00\x45\x00\x04\x00\x01\x00\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\x01",
"\x55\xAA\x00\x00\x60\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x61\x01",
"\x55\xAA\x00\x00\x60\x00\x02\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x62\x01",
"\x55\xAA\x00\x00\x60\x00\x02\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x63\x01",
"\x55\xAA\x00\x00\x61\x00\x03\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x66\x01",
};
/** @brief 接收缓冲区,接收字符个数,已录入过指纹个数 */
unsigned char recv_buff[26], recv_count, empty_number;
/** @brief 定时器计数、PWM值 */
int T0_count, pwm = 50;
/**
* @brief 软件延时1毫秒
*
* @note 仅适用于STC15Fxx/STC15Lxx/STC15Wxx系列单片机,其它系列请使用STC-ISP软件生成,
* 方法参见: @ref https://jswyll.com/MCU51.html#软件延时代码生成
*/
void Delay1ms(void)
{
unsigned char i, j;
i = 12;
j = 169;
do
{
while (--j)
;
} while (--i);
}
/**
* @brief 软件延时n毫秒
*
* @note 仅适用于STC15Fxx/STC15Lxx/STC15Wxx系列单片机,其它系列请使用STC-ISP软件生成,
* 参见 @ref https://jswyll.com/MCU51.html#软件延时代码生成
*/
void delay_ms(unsigned int ms)
{
while (ms--)
Delay1ms();
}
/**
* @brief 串口初始化,波特率115200bps,启用中断
*
* @note 使用STC-ISP软件生成,
* 参见 @ref https://jswyll.com/MCU51.html#波特率代码生成;
* 对于STC15W204S单片机,只有定时器0和定时器2,请勿使用定时器1产生波特率
*/
void UartInit() // 115200bps@12.000MHz
{
SCON = 0x50; // 8位数据,可变波特率
AUXR |= 0x01; //串口1选择定时器2为波特率发生器
AUXR |= 0x04; //定时器2时钟为Fosc,即1T
T2L = 0xE6; //设定定时初值
T2H = 0xFF; //设定定时初值
AUXR |= 0x10; //启动定时器2
ES = 1; //打开串口发送/接收中断
}
/**
* @brief 从串口1以阻塞方式发送一个字节
*
* @param ch 待发送字符
*/
void uart_putchar(unsigned char ch)
{
SBUF = ch;
while (TI == 0)
;
TI = 0;
}
/**
* @brief 从串口1以阻塞方式发送字符数组
*
* @param tx_data 待发送字符数组
* @param num 需要发送的字符个数
*/
void uart_puts(unsigned char *tx_data, unsigned char num)
{
unsigned char i = 0;
while (i++ < num)
{
uart_putchar(*tx_data++);
}
}
/**
* @brief 根据枚举值的不同发送不同的指令给指纹模块
*
* @param cmd 指定命令 @ref Command_t
*/
void send_cmd(Command_t cmd)
{
switch (cmd)
{
case CMD_STORE_CHAR:
{
/* 根据指纹模块通讯协议,以录入编号计算检验和 */
unsigned int sum = 0x143 + empty_number;
uart_puts("\x55\xAA\x00\x00\x40\x00\x04\x00", 8);
uart_putchar(empty_number);
uart_puts("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 15);
uart_putchar(sum);
uart_putchar(sum >> 8);
break;
}
default:
uart_puts(cmd_data[cmd], 26);
break;
}
/* 等待指纹模块返回26个字节应答数据(中断接收) */
while (recv_count < 26)
;
recv_count = 0;
}
/**
* @brief 阻塞等待指纹模块上有手指按下
*/
void waitForFingerTouch()
{
/* 应答码第11个字节为1表示有手指按下 */
do
{
send_cmd(CMD_FINGER_DETECT);
} while (recv_buff[10] != 1);
/* 从指纹模块采集指纹数据 */
send_cmd(CMD_GET_IMAGE);
}
/**
* @brief 定时器0初始化,设置每10微秒产生中断
*/
void Timer0Init(void) // 10微秒@12.000MHz
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x88; //设置定时初值
TH0 = 0xFF; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
}
/**
* @brief UART1中断处理
*/
void UART1_IRQHandler() interrupt 4
{
if (RI)
{
RI = 0;
if (SBUF == 0xAA)
{
recv_count = 0;
}
recv_buff[recv_count++] = SBUF;
}
}
/**
* @brief 定时器中断,用于产生PWM
*/
void T0_IRQHandler() interrupt 1
{
if (T0_count < pwm)
door = 1;
else
door = 0;
if (++T0_count == 2000)
T0_count = 0;
}
/**
* @brief 开门。绿灯亮,PWM驱动舵机开门
*/
void open_door(void)
{
int i = 0;
send_cmd(CMD_LED_GREEN_ON);
/* 平滑打舵 */
for (i = 0; i < 130; i += 3)
{
pwm = 50 + i;
delay_ms(1);
}
delay_ms(800);
pwm = 50;
}
/**
* @brief 程序入口
*/
void main()
{
P3M0 = 0xff;
P3M1 = 0x00;
door = 1;
Timer0Init();
UartInit();
EA = 1;
pwm = 50;
while (1)
{
/* 指示灯灭, 阻塞等待指纹模块上有手指按下 */
send_cmd(CMD_LED_ALL_OFF);
do
{
send_cmd(CMD_GET_IMAGE);
} while (recv_buff[8] != 0x00);
/* 生成指纹特征并验证是否匹配已录入的指纹 */
send_cmd(CMD_GENERATE_0);
send_cmd(CMD_SEARCH_FINGERPRINT);
if (recv_buff[6] == 0x05 && recv_buff[10] > 0)
{
/* 如果匹配的是已录入的编号1(管理员),录入新指纹 */
if (recv_buff[10] == 1)
{
/* 获取第一个未被注册的编号 */
send_cmd(CMD_GET_EMPTY_ID);
empty_number = recv_buff[10];
/* 连续采集3次 */
send_cmd(CMD_LED_YELLOW_ON); //亮黄灯,等待手指按下
delay_ms(500); //延时,避免录入的第一个是管理员未弹起的手指
waitForFingerTouch(); //调用采集指纹函数
send_cmd(CMD_GENERATE_0); //暂存录入的第一次指纹
send_cmd(CMD_LED_ALL_OFF); //灭灯,提示手指移开
delay_ms(500); //等待手指移开
send_cmd(CMD_LED_YELLOW_ON);
waitForFingerTouch();
send_cmd(CMD_GENERATE_1);
send_cmd(CMD_LED_ALL_OFF);
delay_ms(500);
send_cmd(CMD_LED_YELLOW_ON);
waitForFingerTouch();
send_cmd(CMD_GENERATE_2);
send_cmd(CMD_LED_ALL_OFF);
/* 合成三次录入的指纹特征并存储 */
send_cmd(CMD_MERGE_CHAR);
send_cmd(CMD_STORE_CHAR);
}
/* 绿灯亮,表示指纹匹配或者指纹录入完成,控制继电器开门 */
open_door();
}
else
{
/* 指纹数据和录入过的都不匹配,亮红灯 */
send_cmd(CMD_LED_RED_ON);
/* 延时再作匹配判断,避免短时间触发判断两次同样的指纹 */
delay_ms(500);
}
}
}
```
---
## 注意事项
STC15W204S只有T0和T2定时器, 不能用T1产生波特率
---
## 代码开源仓库地址
[https://gitee.com/jswyll_com/fingerprint-open-the-door](https://gitee.com/jswyll_com/fingerprint-open-the-door)