# 指纹开门 **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/ ## 效果展示 ![](figures/fingerprintVerifyOK.gif "指纹匹配,开门成功") 指纹匹配,开门成功 ![](figures/fingerprintVerifyFail.gif "指纹不匹配,不开门") 指纹不匹配,不开门 --- ## 控制方案 ![](figures/control_plan.png "控制方案") 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)。 ![](figures/door.png "舵机开门") - 单片机。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-`信号. ![](figures/typec_power.png "Type-C母座电路") ### USB转串口 电脑的USB信号和单片机的串口信号是不一样的,使用小巧的CH340E芯片进行转换 ![](figures/usb2ttl.png "USB转串口电路") ### 复位电路 STC单片机下载程序时需要将电源断电然后重新上电,这里使用按键+MOS管控制电源的通断。(由于断电时串口仍需正常工作,所以USB转串口的电源直接接自USB。) ![](figures/power_control.png "复位电路") ### 稳压电路 单片机和指纹模块的工作电压是3.3V,使用AMS1117-3.3芯片将USB电源的5V转为3.3V ![](figures/power_3v3.png "稳压电路") ### 舵机接口 舵机的电源是5\~6V,舵机自带母头的杜邦线+延长的公对公杜邦线,所以预留一个3PIN的排母接口 ![](figures/steer_pin.png "舵机排针接口") ### 单片机 除了3.3V以外,TXD、RXD接指纹模块,再加一个普通IO口接舵机PWM控制引脚 ![](figures/mcu.png "单片机电路") ### 指纹模块接口 指纹模块有6PIN 1.0mm间距的排线,留出4个洞。 ![](figures/20220319175704.png "指纹模块接口") ### 3D预览 画完PCB后在立创EDA打开3D预览: ![](figures/3d_preview.png "电路板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)