一.简介:
该作品主要实现了使用指纹以及4*4矩阵键盘解锁的功能。主控为STM32F103C8T6最小系统板,指纹模块采用了ZW101型号的指纹传感器,其余还有无源蜂鸣器,0.96寸OLED屏幕,按键和2000mAh锂电池等。总览图如下:
图一 系统总览图
二.操作介绍:
1.上电初始化:上电首先对程序进行初始化,初始化完成后进入解锁界面,在此界面时可以使用矩阵键盘输入密码解锁或者使用指纹传感器进行解锁且在输入密码时任意时刻都可被指纹解锁打断。输入时,按#键确认输入;按*键撤销输入;按A键进入菜单。矩阵键盘值如下:
1 | 2 | 3 | A |
4 | 5 | 6 | B |
7 | 8 | 9 | C |
* | 0 | # | D |
下图为使用指纹解锁成功时的示例:
图二 指纹解锁成功示例
2.菜单操作界面:进入菜单界面后,出现若干选项,主要功能有注册指纹,删除指定ID号指纹,修改解锁密码等。其中任意选项都需要使用管理员密码才能继续操作。
A-注册指纹:进入这个功能并正确输入管理员密码后,屏幕会提示输入ID号,ZW101型号的指纹库容量有50枚,因此输入的ID号限制在00~49内。程序中有对输入的ID号进行规范,若输入不符合规范则需重新输入。输入ID号确认后可以开始录入指纹,该型号的录入次数推荐为4次,每次间隔手指需要离开模块。录入成功蜂鸣器鸣叫同时模块会闪烁绿灯提示。
B-删除指纹:进入这个功能并正确输入管理员密码后,屏幕会提示输入需要删除的ID号,同样的ID序号从00~49,若输入不符合规范也会要求重新输入。成功输入后会显示”Hold on...",并开始删除对应ID号的指纹模板。若删除成功会提示,失败也会提示,并且返回当前指纹模块中剩余的指纹数量。
C-修改密码:进入该功能输入管理员密码后,屏幕提示输入新的密码,这时只需输入新的六位密码,按#键确认即可。修改成功后蜂鸣器长鸣2S,提示修改成功。又会重新返回解锁界面,这时再输入已修改的密码就可以解锁。
其他功能:在菜单界面按‘1’键可以删除所有指纹,成功后屏幕会提示删除成功,并返回指纹库容量为0。目前已开发的功能为常用功能,其他功能还可继续开发。
三.硬件原理图:
图三 指纹锁原理图
这里指纹模块与单片机之间的连接参照ZW101手册里的连接方式,设计了低功耗唤醒电路。根据手册里的介绍,指纹模块有3V3和VCC两个接电端口,其中3V3是直接接到电池供电,VCC则由CTRL控制开启。为什么会有这样的设计呢,仔细阅读手册是可以发现ZW101指纹模块有手指触摸唤醒的功能的,主要是使用该模块的TOUCH引脚实现的。但是需要注意的是,TOUCH引脚只有在连接3V3,断开VCC的时候才会有电平变化,所以想实现触摸唤醒还要软件程序设计的参与。
图四 指纹锁pcb顶层
图五 指纹锁pcb底层
图六 3D仿真图
四.软件设计:
因为指纹锁系统需要多模块的共同参与,所以在开发时首先需要确保各个模块独立时能够正常工作。那作为指纹锁,能够与指纹模块进行通信肯定是最重要的。单片机与模块之间的通信采用串口通信,接下来就是需要对片上串口进行初始化,下面的代码Serial.c来自江科大:
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
char Serial_RxPacket[100]; //定义接收数据包数组,数据包格式"@MSG\r\n"
uint8_t Serial_RxFlag; //定义接收数据包标志位
/**
* 函 数:串口初始化
* 参 数:无
* 返 回 值:无
*/
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA3引脚初始化为上拉输入RX
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 57600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART2, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
/*中断输出配置*/
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); //开启串口空闲中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*USART使能*/
USART_Cmd(USART2, ENABLE); //使能USART1,串口开始运行
}
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART2, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
/**
* 函 数:串口发送一个数组
* 参 数:Array 要发送数组的首地址
* 参 数:Length 要发送数组的长度
* 返 回 值:无
*/
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++) //遍历数组
{
Serial_SendByte(Array[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
//接收模组发送的数据,存储到缓冲数组中
void USART2_IRQHandler(void)
{
uint8_t DR=0,SR=0;
uint8_t RxData = 0x00;
static uint8_t position = 0;
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET) //数据寄存器非空
{
RxData = USART_ReceiveData(USART2);
USART_ClearITPendingBit(USART2, USART_IT_RXNE); //清除标志位
if(position < BUFFER_SIZE)
{
USART_ReceiveBuf[position] = RxData;
position++;
if(position>=BUFFER_SIZE)
{
position=0;
OLED_Clear();
OLED_ShowString(1,1,"Error");
OLED_ShowString(3,1,"The ReceBuff");
OLED_ShowString(4,2,"Overflow!");
}
}
}
if(USART_GetITStatus(USART2, USART_IT_IDLE) == SET) //空闲状态检测
{
// 清除空闲中断标志
SR=USART2->SR;
DR=USART2->DR;
USART_STA = 1; //读取状态标志位,1表示读取完成,0表示未读完
position = 0;
}
}
在Serial.c里已经有了发送数据和接收的函数,接收数据用到了空闲中断检测。为什么要用到空闲中断检测呢,因为指纹模块发送的数据不是恒定字节长度的,不方便直接判断,而空闲中断检测是在一段时间没有接收字节后会触发的中断,可以在这个中断函数里写标志位用于判断数据接收完成,比如在这里就使用USART_STA来判断是否接收完毕。初始化串口完成之后,接下来就是准备与指纹模块进行通信。可以先将手册里的操作指令存到数组中:
//模块包头
uint8_t PS_Head[2] = {0xEF,0x01};
//模块地址
uint8_t PS_Addr[4] = {0xFF,0xFF,0xFF,0xFF};
//定义SysPara结构体
typedef struct
{
uint16_t PS_num; //注册时,录入次数
uint16_t PS_cap; //指纹模板大小
uint16_t PS_max; //指纹库容量
uint16_t PS_level; //对比等级
uint32_t PS_addr; //地址
uint16_t PS_size; //大小
uint16_t PS_N; //波特率相关参数
}Model;
//握手指令
uint8_t PS_HandShake[12] = {0xEF,0x01,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x03,0x35,0x00,0x39};
//休眠指令-设置传感器进入休眠模式
uint8_t PS_SleepBuf[12] = {0xEF,0x01,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x03,0x33,0x00,0x37};
//恢复出厂设置
uint8_t PS_RestSetting[12] = {0xEF,0x01,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x03,0x3B,0x00,0x3F};
//清空指纹库-删除 flash 数据库中所有指纹模板
uint8_t PS_EmptyBuf[12] = {0xEF,0x01,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x03,0x0D,0x00,0x11};
//自动注册模板-一站式注册指纹,包含采集指纹、生成特征、组合模板、存储模板等功能
uint8_t PS_AutoEnrollBuf[17] = {0xEF,0x01,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x08,0x31,'\0','\0',0x04,0x00,0x10,'\0','\0'};
//验证用获取图像-验证指纹时,探测手指,探测到后录入指纹图像存于图像缓冲区。返回确认码表示:录入成功、无手指等
uint8_t PS_GetImageBuf[12] = {0xEF,0x01,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x03,0x01,0x00,0x05};
//生成特征值-将图像缓冲区中的原始图像生成指纹特征文件存于模板缓冲区
uint8_t PS_GetCharBuf[13] = {0xEF,0x01,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x04,0x02,'\0','\0','\0'};
//搜索指纹-以模板缓冲区中的特征文件搜索整个或部分指纹库。若搜索到,则返回页码。加密等级设置为 0 或 1 情况下支持
uint8_t PS_SearchBuf[16] = {0xEF,0x01, 0xFF,0xFF,0xFF,0xFF, 0x01, 0x00,0x07, 0x3E, 0x00, 0x01,0x00, 0x05,0x00, 0x4C};
//删除模板-删除 flash 数据库中指定 ID 号开始的N 个指纹模板
uint8_t PS_DeleteBuf[16] = {0xEF,0x01,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x07,0x0C,'\0','\0',0x00,0x01,'\0','\0'};
//精确对比两枚指纹特征
uint8_t PS_MatchBuf[12] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x03, 0x03, 0x00, 0x07};
//合并特征-将特征文件融合后生成一个模板,结果存于模板缓冲区中
uint8_t PS_RegModelBuf[12] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x03, 0x05, 0x00, 0x09};
//储存模板-将模板缓冲区中的模板文件存储到OageID号flash数据库位置
uint8_t PS_StoreBuf[15] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x06, 0x06, 0x01, 0x00, '\0', '\0', '\0'};
//读出模板-将flash数据库中指定ID号的指纹模板读入到模板缓冲区中
uint8_t PS_LoadCharBuf[15] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x06, 0x07, 0x01, '\0', '\0', '\0', '\0'};
//上传模板-将保存在模板缓冲区中的模板文件上传给主控
uint8_t PS_UpCharBuf[13] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x04, 0x08, '\0', '\0', '\0'};
//下载模板-主控下载模板到模组的一个模板缓冲区
uint8_t PS_DownCharBuf[13] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x04, 0x09, '\0', '\0', '\0'};
//写系统寄存器-写模组寄存器
uint8_t PS_WriteRegBuf[14] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x05, 0x0E, '\0', '\0', '\0', '\0'};
//读模组基本参数-读取模组的基本参数(波特率、包大小等),参数表前16个字节存放了模组的基本通讯和配置信息,称为模组的基本参数
uint8_t PS_ReadSysParaBuf[12] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x03, 0x0F, 0x00, 0x13};
//读参数页-读取flash information page 所在的参数页(512bytes)
uint8_t PS_ReadINFpageBuf[12] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x03, 0x16, 0x00, 0x1A};
//读有效模板个数
uint8_t PS_ValidTempleteNumBuf[12] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x03, 0x1D, 0x00, 0x21};
//读索引表-读取录入模板的索引表
uint8_t PS_ReadIndexTableBuf[13] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x04, 0x1F, '\0', '\0', '\0'};
//注册用获取图像-注册指纹时,探测手指,探测到后录入指纹图像缓冲区
uint8_t PS_GetEnrollImageBuf[12] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x03, 0x29, 0x00, 0x2D};
//获取模组附加参数
uint8_t PS_ReadAddParaBuf[13] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x03, 0x62, 0x00, 0x66};
//自动验证指纹-自动采集指纹包括获取图像、生成特征、搜索指纹等功能
uint8_t PS_AutoIdentifyBuf[17] = {0xEF, 0x01,0xFF, 0xFF, 0xFF, 0xFF,0x01,0x00, 0x08,0x32,0x00,0x00, 0x01,0x00, 0x00,0x00, 0x3C };
//休眠指令
uint8_t PS_sleep[12] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x03, 0x33 ,0x00, 0x37};
//取消指令
uint8_t PS_Cancel[12] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x03, 0x30 ,0x00, 0x34};
接下来就要开始控制指纹模块工作了,下面是finger.c的详细代码:
#include "stm32f10x.h" // Device header
#include "Serial.h"
#include "Delay.h"
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
#include "OLED.h"
#include "Timer.h"
#include "Keyboard.h"
#include "Buzzer.h"
#define BUFFER_SIZE 30
extern uint8_t flag;
extern uint8_t MenuFlag;
extern uint8_t ReturnFlag; //用于判断是否回到上一级菜单
extern uint8_t EndFlag;
extern uint8_t IsManager;
//USART串口接收长度以及标志位
uint8_t USART_STA = 0;
//USART串口接收缓冲数组
uint8_t USART_ReceiveBuf[30];
//主循环状态标志位
uint8_t ScanStatus = 0;
uint8_t temp=0;
void Finger_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 开启 GPIOA 的时钟
// 使用各个外设前必须开启时钟,否则对外设的操作无效
/*GPIO 初始化 - 指纹模块控制引脚(PA4)*/
GPIO_InitTypeDef GPIO_InitStructure; // 定义结构体变量
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // GPIO 模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // GPIO 引脚,赋值为第 4 号引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // GPIO 速度,赋值为 50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); // 将赋值后的结构体变量传递给 GPIO_Init 函数
// 函数内部会自动根据结构体的参数配置相应寄存器
// 实现 GPIOA 的初始化
GPIO_ResetBits(GPIOA, GPIO_Pin_4); // 关闭指纹模块的控制开关
/*GPIO 初始化 - PA0 输入引脚*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // GPIO 模式,赋值为浮空输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // GPIO 引脚,赋值为第 0 号引脚
// 输入模式下,速度参数无效,可忽略
GPIO_Init(GPIOA, &GPIO_InitStructure); // 将赋值后的结构体变量传递给 GPIO_Init 函数
// 实现 PA0 的初始化
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
}
//接收模组发送的数据,存储到缓冲数组中
void USART2_IRQHandler(void)
{
uint8_t DR=0,SR=0;
uint8_t RxData = 0x00;
static uint8_t position = 0;
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET) //数据寄存器非空
{
RxData = USART_ReceiveData(USART2);
USART_ClearITPendingBit(USART2, USART_IT_RXNE); //清除标志位
if(position < BUFFER_SIZE)
{
USART_ReceiveBuf[position] = RxData;
position++;
if(position>=BUFFER_SIZE)
{
position=0;
OLED_Clear();
OLED_ShowString(1,1,"Error");
OLED_ShowString(3,1,"The ReceBuff");
OLED_ShowString(4,2,"Overflow!");
}
}
}
if(USART_GetITStatus(USART2, USART_IT_IDLE) == SET) //空闲状态检测
{
// 清除空闲中断标志
SR=USART2->SR;
DR=USART2->DR;
USART_STA = 1; //读取状态标志位,1表示读取完成,0表示未读完
position = 0;
}
}
//MCU通过串口发送数据
void Send_Data(uint8_t *Array, uint16_t Length)
{
Serial_SendArray(Array,Length);
}
//应答包指定数据返回
uint8_t Answer(uint8_t position)
{
return USART_ReceiveBuf[position];
}
uint16_t Sum(void)
{
uint16_t length=0; //包长度
uint16_t sum=0; //校验和
uint8_t p; //应答包数据位置
length = (Answer(7)<<8)+Answer(8);
for(p=7;p<length+7;p++)
{
// OLED_ShowHexNum(1,i,Answer(p),2);
// i+=2;
sum+=Answer(p);
}
return length;
}
//检测模组是否正常工作
void HandShake(void)
{
Send_Data(PS_HandShake,12);
Delay_ms(20); //给程序判断流出充裕时间,若不加延时,则即使回传数据正确也不会判断成功连接
while(!USART_STA); //等待空闲中断,数据接收完全
if(Answer(6)==0x07&&Answer(9)==0x00)
{
OLED_ShowString(1,1,"Connect success");
USART_STA=0;
}
else
{
OLED_ShowString(1,1,"Connect error");
USART_STA=0;
}
Delay_s(1);
OLED_Clear();
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
}
uint8_t ValidTempleteNum(void)
{
uint8_t num;
Send_Data(PS_ValidTempleteNumBuf,12);
Delay_ms(30);
if(Answer(6)==0x07&&Answer(9)==0x00)
{
num=Answer(11);
}
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
return num;
}
//取消操作
void Cancel(void)
{
Send_Data(PS_Cancel,12);
Delay_ms(100);
if(Answer(6)==0x07&&Answer(9)==0x00)
{
OLED_ShowString(4,1,"Cancel success");
}
else
{
// OLED_ShowHexNum(1,1,Answer(9),2);
OLED_ShowString(4,1,"Cancel failed");
}
Delay_s(1);
OLED_Clear();
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
}
//清空指纹库
void Empty(void)
{
OLED_ShowString(1,1,"Hold on...");
GPIO_SetBits(GPIOA, GPIO_Pin_4); //打开指纹锁电源
Delay_s(1);
Send_Data(PS_EmptyBuf,12);
while(!USART_STA); //等待空闲中断,数据接收完全
Delay_ms(400); //清空指令发出后,模块会不断返还数据包,待全部删除后确认码才是0x00
OLED_Clear();
if(Answer(9)==0x00)
{
OLED_ShowString(1,1,"Empty succss");
Buzzer1_on(800);
temp=ValidTempleteNum();
OLED_ShowString(3,1,"Current Finger");
OLED_ShowString(4,1,"Quantity:");
OLED_ShowNum(4,10,temp,2);
Delay_s(1);
}
else if(Answer(9)==0x01)
{
OLED_ShowString(1,1,"Receive Error");
Delay_s(1);
}
else if(Answer(9)==0x11)
{
OLED_ShowString(1,1,"Empty failed");
Delay_s(1);
}
else
{
OLED_ShowString(1,1,"Error");
Delay_s(1);
}
OLED_Clear();
EndFlag=1;
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
}
//进入休眠模式
void Sleep(void)
{
Send_Data(PS_sleep,12);
Delay_ms(20);
if(Answer(6)==0x07&&Answer(9)==0x00)
{
OLED_ShowString(1,1,"sleep");
}
Delay_s(1);
OLED_Clear();
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
}
//读取模组基本参数
void ReadSysPara(void)
{
uint8_t ensure;
Model model;
Send_Data(PS_ReadSysParaBuf,12);
Delay_ms(20); //必要等待时间
if(Answer(6)==0x07&&Answer(9)==0x00)
{
model.PS_num = (USART_ReceiveBuf[10]<<8)+USART_ReceiveBuf[11];
model.PS_cap = (USART_ReceiveBuf[12]<<8)+USART_ReceiveBuf[13];
model.PS_max = (USART_ReceiveBuf[14]<<8)+USART_ReceiveBuf[15];
model.PS_level = (USART_ReceiveBuf[16]<<8)+USART_ReceiveBuf[17];
model.PS_addr = (USART_ReceiveBuf[18]<<24)+(USART_ReceiveBuf[19]<<16)+(USART_ReceiveBuf[20]<<8)+USART_ReceiveBuf[21];;
model.PS_size = (USART_ReceiveBuf[22]<<8)+USART_ReceiveBuf[23];
model.PS_N = (USART_ReceiveBuf[24]<<8)+USART_ReceiveBuf[25];
}
ensure = (Answer(9) == 0x00 ? Answer(9):0xFF);
if(ensure == 0x00)
{
OLED_Clear();
OLED_ShowString(1,1,"num:");
OLED_ShowHexNum(1,5,model.PS_num,5);
OLED_ShowString(2,1,"max:");
OLED_ShowHexNum(2,5,model.PS_max,5);
OLED_ShowString(3,1,"size:");
OLED_ShowHexNum(3,6,model.PS_size,5);
OLED_ShowString(4,1,"N:");
OLED_ShowHexNum(4,3,model.PS_N,5);
}
else
{
OLED_ShowString(1,1,"Error");
}
Delay_s(3);
OLED_Clear();
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
}
void Delet(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_4); //打开指纹锁电源
uint8_t pageID=1;
Input:
OLED_ShowString(1,1,"Delet ID(00-49)");
OLED_ShowString(2,1,":");
InputPlay(2,2,keyarr,2);
OLED_Clear();
if(ReturnFlag==1) //按返回键退出当前进程
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
return;
}
pageID=(keyarr[0]-'0')*10+(keyarr[1]-'0');
if(pageID>50)
goto Input;
PS_DeleteBuf[10]=(pageID>>8);
PS_DeleteBuf[11]=(pageID); //ID有效位
PS_DeleteBuf[14]=(PS_DeleteBuf[10]+PS_DeleteBuf[11]+0x15)>>8;
PS_DeleteBuf[15]=(PS_DeleteBuf[10]+PS_DeleteBuf[11]+0x15);
Send_Data(PS_DeleteBuf,16);
EndFlag=1;
OLED_Clear();
OLED_ShowString(1,1,"Hold on...");
while(!USART_STA);
Delay_ms(500);
if(Answer(8)==0x03&&Answer(9)==0x00)
{
OLED_Clear();
temp=ValidTempleteNum();
OLED_Clear();
OLED_ShowString(1,1,"Delet Success");
OLED_ShowString(3,1,"Current Finger");
OLED_ShowString(4,1,"Quantity:");
OLED_ShowNum(4,10,temp,2);
temp=0;
}
else if(Answer(8)==0x03&&Answer(9)==0x10)
{
OLED_Clear();
temp=ValidTempleteNum();
OLED_Clear();
OLED_ShowString(1,1,"Delet Failed");
OLED_ShowString(3,1,"Current Finger");
OLED_ShowString(4,1,"Quantity:");
OLED_ShowNum(4,10,temp,2);
temp=0;
}
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
Delay_s(2);
OLED_Clear();
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
}
void AutoEnroll(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_4); //打开指纹锁电源
uint8_t pageID;
Input:
OLED_ShowString(1,1,"Input ID(00-49)");
OLED_ShowString(2,1,":");
InputPlay(2,2,keyarr,2);
OLED_Clear();
if(ReturnFlag==1)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
return;
}
pageID=(keyarr[0]-'0')*10+(keyarr[1]-'0');
if(pageID>50)
goto Input;
PS_AutoEnrollBuf[10]=(pageID>>8);
PS_AutoEnrollBuf[11]=(pageID); //ID有效位
PS_AutoEnrollBuf[15]=(PS_AutoEnrollBuf[10]+PS_AutoEnrollBuf[11]+0x4E)>>8;
PS_AutoEnrollBuf[16]=(PS_AutoEnrollBuf[10]+PS_AutoEnrollBuf[11]+0x4E);
Send_Data(PS_AutoEnrollBuf,17); //0x4E表示要求模组返回关键步骤
EndFlag=1;
Delay_ms(20); //必要等待时间
while(!USART_STA);
while(1)
{
if(Answer(9)==0x26) //超时
{
OLED_Clear();
OLED_ShowString(1,4,"Time out");
Cancel();
break;
}
if(Answer(9)==0x22)
{
OLED_Clear();
OLED_ShowString(1,1,"The ID is");
OLED_ShowString(2,1,"Registered");
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
Delay_s(1);
OLED_Clear();
goto Input;
}
if(Answer(9)==0x27)
{
OLED_Clear();
OLED_ShowString(1,1,"The Finger is");
OLED_ShowString(2,1,"Registered");
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
Delay_s(1);
OLED_Clear();
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
Cancel();
}
if(Answer(9)==0x00)
{
OLED_ShowString(1,1,"Press your");
OLED_ShowString(2,1,"Finger");
if(Answer(10)==0x03) //判断手指已经离开
{
OLED_Showgou(3,1);
Delay_ms(500);
OLED_Clear();
OLED_ShowString(1,1,"Press your");
OLED_ShowString(2,1,"Finger");
OLED_ShowHexNum(2,8,Answer(11),2);
}
if(Answer(11)==0xF2) //注册成功
{
temp=ValidTempleteNum();
OLED_Clear();
OLED_ShowString(1,1,"Enroll success");
OLED_ShowString(3,1,"Current Finger");
OLED_ShowString(4,1,"Quantity:");
OLED_ShowNum(4,10,temp,2);
temp=0;
Buzzer2_on();
Delay_s(2);
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
break;
}
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf)); //每次循环进入确认码为0x00的分支,跑完后重置一边接收数组
}
if(Answer(9)==0x01) //注册失败
{
OLED_Clear();
OLED_ShowString(1,1,"Enroll failed");
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
break;
}
}
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
}
void AutoIdentify(void)
{
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
Send_Data(PS_AutoIdentifyBuf,17);
Delay_ms(50);
while(!USART_STA);
while(Answer(9)!=0x26)
{
while(Answer(10)!=0x01) //判断指纹是否按下且成功采图或是否超时
{ //成功采图执行下一步
//未成功则阻塞
if(Answer(8)==0x08&&Answer(9)==0x23) //判断指纹库是否为空
{
// TIM_Cmd(TIM2, DISABLE);
OLED_ShowString(1,1,"The finger lib");
OLED_ShowString(2,1,"is Empty");
flag=1;
Delay_s(1);
OLED_Clear();
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
break;
}
if(flag==1) //超时函数置flag为1,此时应退出当前函数
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
break;
}
Send_Data(PS_AutoIdentifyBuf,17);
Delay_ms(30);
OLED_ShowString(1,1,"Press your");
OLED_ShowString(2,1,"Finger");
}
OLED_Clear();
if(flag==1) //超时函数置flag为1,此时应退出当前函数
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
break;
}
if(Answer(9)==0x00&&Answer(10)==0x05) //确认码和参数
{
// TIM_Cmd(TIM2, DISABLE);
OLED_ShowString(1,5,"Unlock");
OLED_ShowString(2,3,"Successful");
OLED_Showgou(3,7);
Buzzer1_on(200);
Delay_s(1);
OLED_Clear();
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
break;
}
if(Answer(9)==0x09&&Answer(10)==0x05)
{
TIM_Cmd(TIM2, DISABLE);
OLED_ShowString(1,5,"Unlock");
OLED_ShowString(2,5,"Failed");
OLED_Showcha(3,7);
Buzzer2_on();
Delay_s(1);
OLED_Clear();
memset(USART_ReceiveBuf,0xFF,sizeof(USART_ReceiveBuf));
break;
}
}
flag=1; //退出指纹解锁函数,置flag为1,方便识别解锁方式,避免与密码解锁混乱
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //关闭指纹锁电源
}
接下来是矩阵键盘Keyboard.c的代码:
#include "stm32f10x.h" // Device header
#include "Keyboard.h"
#include "OLED.h"
#include "Delay.h"
#include "finger.h"
#include "main.h"
#include "Timer.h"
#include "Buzzer.h"
uint8_t keyarr[10]; //按键输入存储的数组
extern uint8_t flag; //主函数变量,用于判断是否应该回到main函数
extern uint8_t MenuFlag;//在注册指纹函数中用于判断是否回到main函数
extern uint8_t ReturnFlag;//用于判断是否回到上一级菜单
extern uint8_t EndFlag;
extern uint8_t CheckFlag;
extern uint8_t overtime;
// 定义矩阵键盘的行和列引脚
#define ROW_PORT GPIOA
#define ROW_PIN_1 GPIO_Pin_8
#define ROW_PIN_2 GPIO_Pin_9
#define ROW_PIN_3 GPIO_Pin_10
#define ROW_PIN_4 GPIO_Pin_11
#define COL_PORT GPIOB
#define COL_PIN_1 GPIO_Pin_12
#define COL_PIN_2 GPIO_Pin_13
#define COL_PIN_3 GPIO_Pin_14
#define COL_PIN_4 GPIO_Pin_15
// 定义按键值数组
const uint8_t keymap[4][4] =
{ //*:撤销键
{'1', '2', '3', 'A'}, //E:注册指纹键
{'4', '5', '6', 'B'}, //C:取消操作键
{'7', '8', '9', 'C'}, //U:修改密码键
{'*', '0', '#', 'D'} //D:验证指纹键
}; //#:确认键
// 初始化矩阵键盘的GPIO引脚
void KeyBoard_Init(void)
{
// 使能GPIOA和GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
// 配置行引脚为推挽输出
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = ROW_PIN_1 | ROW_PIN_2 | ROW_PIN_3 | ROW_PIN_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(ROW_PORT, &GPIO_InitStructure);
// 配置列引脚为上拉输入
GPIO_InitStructure.GPIO_Pin = COL_PIN_1 | COL_PIN_2 | COL_PIN_3 | COL_PIN_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(COL_PORT, &GPIO_InitStructure);
}
// 扫描矩阵键盘,返回按键值
char KeyBoard_Scan(void)
{
uint8_t row, col;
// 逐行扫描
for (row = 0; row < 4; row++)
{
// 将所有行引脚置高电平
GPIO_SetBits(ROW_PORT, ROW_PIN_1 | ROW_PIN_2 | ROW_PIN_3 | ROW_PIN_4);
// 将当前行引脚置低电平
switch (row)
{
case 0:
GPIO_ResetBits(ROW_PORT, ROW_PIN_1);
break;
case 1:
GPIO_ResetBits(ROW_PORT, ROW_PIN_2);
break;
case 2:
GPIO_ResetBits(ROW_PORT, ROW_PIN_3);
break;
case 3:
GPIO_ResetBits(ROW_PORT, ROW_PIN_4);
break;
}
// 读取列引脚状态
for (col = 0; col < 4; col++)
{
if (!GPIO_ReadInputDataBit(COL_PORT, (GPIO_Pin_12 << col)))
{
// 消抖处理
for (int i = 0; i < 10000; i++);
if (!GPIO_ReadInputDataBit(COL_PORT, (GPIO_Pin_12 << col)))
{
// 等待按键释放
while (!GPIO_ReadInputDataBit(COL_PORT, (GPIO_Pin_12 << col)));
return keymap[row][col];
}
}
}
}
return 10; //没有按键按下
}
/***************************************************************
* @brief 按键输入数字并显示在屏幕上
* @param hang:屏幕开始显示的行数
* @param lie:屏幕开始显示的列数
* @param keyarr:存储按键数值的数组
* @param len:允许输入的最多个数
* @note 备注:按'#'确认
* @Sample usage: 函数的使用方法
**************************************************************/
void InputPlay(uint8_t hang,uint8_t lie,uint8_t keyarr[],uint8_t len)
{
// TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
KeyBoard_Init(); //初始化矩阵键盘
uint8_t temp; //键盘输入缓存变量
char i=0,j=0;
for(i=0;i<=len;i++) //清零键盘.输入存储数组
{
keyarr[i]=0;
}
i=0;
do{ //循环输入,‘#’退出
temp=KeyBoard_Scan();
if(MenuFlag!=1)
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1) //手指活体检测,检测到手指按下后开始指纹解锁
{
Delay_ms(50); //加延时,适当降低反应灵敏度
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1)
{
overtime=0; //操作计时清零
OLED_Clear();
GPIO_SetBits(GPIOA, GPIO_Pin_4);
Delay_ms(80); //上电后等待80ms,指纹模块再开始工作
AutoIdentify();
}
for(i=0;i<=len;i++) //清空按键输入数组的数据
{
keyarr[i]=' ';
}
for(j=lie;j<lie+len+1;j++) //清空屏幕上显示的数据
{
OLED_ShowChar(hang,j,' ');
}
i=0;
break;
}
}
if(temp!=10&&temp!='#'&&temp!='*'&&temp>='0'&&temp<='9') //确认键和撤销键以及空值不输出
{
overtime=0; //操作计时清零
keyarr[i]=temp;
OLED_ShowChar(hang,lie+i,keyarr[i]);
i++; //必须放在有值语句中,否则会一直自增
}
if(temp=='*'&&i>0)
{
overtime=0;
i--;
keyarr[i]=' ';
OLED_ShowChar(hang,lie+i,' ');
}
if(i>len)
{
/*删除错误数据*/
for(j=lie;j<lie+len+1;j++)
{
OLED_ShowChar(hang,j,' ');
}
OLED_ShowString(2,5,"Error");
OLED_ShowString(3,2,"Input Again");
Delay_s(2);
/*提示词消失*/
for(j=5;j<10;j++)
{
OLED_ShowChar(2,j,' ');
}
for(j=2;j<13;j++)
{
OLED_ShowChar(3,j,' ');
}
i=0;
}
if(temp=='A'&&MenuFlag!=1)//在主界面按A会进入菜单,在功能界面按A则不会进入菜单
{
overtime=0;
Menu();
if(MenuFlag==1)
{
OLED_Clear();
break;
}
}
if(temp=='D'&&MenuFlag==1)
{
overtime=0;
ReturnFlag=1;
CheckFlag=1;
break;
}
if(temp=='#')
{
overtime=0;
break;
}
}while(1);
}
void CheckPassword(uint8_t *Input,uint8_t *Password,uint8_t len)
{
uint8_t i;
uint8_t com=0;
for(i=0;i<6;i++)
{
if(Input[i]==Password[i]+'0')
{
com++;
}
OLED_ShowChar(2,1+i,keyarr[i]);
}
if(com<6)
{
OLED_Clear();
OLED_ShowString(1,5,"Unlock");
OLED_ShowString(2,5,"Failed");
OLED_Showcha(3,7);
Buzzer2_on();
Delay_s(1);
OLED_Clear();
}
if(com==6)
{
OLED_Clear();
OLED_ShowString(1,5,"Unlock");
OLED_ShowString(2,3,"Successful");
OLED_Showgou(3,7);
Buzzer1_on(200);
Delay_s(1);
OLED_Clear();
}
}
接下来是主函数,main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Buzzer.h"
#include "Serial.h"
#include "finger.h"
#include "Timer.h"
#include "Keyboard.h"
uint8_t ManagerPassword[6]={2,0,0,4,0,9}; //管理员密码
uint8_t password[6]={2,0,2,3,0,5}; //初始密码
uint8_t flag=0; //指纹解锁标志位
uint8_t MenuFlag=0; //菜单标志位
uint8_t ReturnFlag; //返回标志位
uint8_t EndFlag=0; //功能完成标志位
uint8_t CheckFlag=0; //检查管理员身份过程标志位
uint8_t IsManager=0; //管理员身份验证
void CheckManager(void)
{
uint8_t com=0,i=0;
OLED_ShowString(1,1,"Input Manager");
OLED_ShowString(2,1,"Pwd:");
InputPlay(2,5,keyarr,6);
if(ReturnFlag==1)
{
OLED_Clear();
return;
}
for(i=0;i<6;i++)
{
if(keyarr[i]==ManagerPassword[i]+'0')
{
com++;
}
}
if(com<6)
{
OLED_Showcha(4,6);
Delay_s(1);
OLED_Clear();
IsManager=0;
}
else if(com==6)
{
OLED_Showgou(4,6);
Delay_s(1);
OLED_Clear();
IsManager=1;
}
}
void ChangePassword(void)
{
uint8_t i=0;
OLED_Clear();
EndFlag=1;
OLED_ShowString(1,1,"Input new");
OLED_ShowString(2,1,"Pwd:");
InputPlay(2,5,keyarr,6);
for(i=0;i<6;i++)
{
password[i]=keyarr[i]-'0';
keyarr[i]=0;
}
OLED_Clear();
OLED_ShowString(1,1,"Change Success");
Delay_s(1);
OLED_Clear();
}
void Menu(void)
{
OLED_Clear();
uint8_t KeyValue;
MenuFlag=1;
while(1)
{
OLED_ShowString(1,7,"MENU");
OLED_ShowString(2,1,"1.Enroll");
OLED_ShowString(3,1,"2.DeletFinger");
OLED_ShowString(4,1,"3.ChangePassword");
KeyValue = KeyBoard_Scan();
if(KeyValue!=10)
{
OLED_Clear();
switch(KeyValue)
{
case 'A':CheckManager();ReturnFlag=0;
if(IsManager==1)
{
AutoEnroll();
IsManager=0;
};break;
case 'B':CheckManager();ReturnFlag=0;
if(IsManager==1)
{
Delet();
IsManager=0;
};break;
case 'C':CheckManager();ReturnFlag=0;
if(IsManager==1)
{
ChangePassword();
IsManager=0;
};break;
case '1':CheckManager();ReturnFlag=0;
if(IsManager==1)
{
Empty();
IsManager=0;
};break;
case 'D':EndFlag=1;break;
}
}
ReturnFlag=0;
if(EndFlag==1)
{
break;
}
}
}
int main(void)
{
Timer_Init(); //初始化定时器
Delay_ms(80); //手册建议延时
Serial_Init(); //初始化串口
Finger_Init(); //初始化指纹模块
OLED_Init(); //初始化OLED模块
KeyBoard_Init(); //初始化矩阵键盘
BuzzerInit(999,71); //初始化蜂鸣器1KHz
while(1)
{
OLED_ShowString(1,12,"Menu");
OLED_ShowString(1,1,"Password");
OLED_ShowString(2,1,":");
InputPlay(2,2,keyarr,6); //在键盘输入函数里会检测是否使用到了指纹识别
if(flag!=1&&MenuFlag!=1&&ReturnFlag!=1) //指纹解锁(flag==1)后不再进行密码解锁,注册指纹(MenuFlag==1)后也不再进行密码解锁
{
CheckPassword(keyarr,password,6);
}
flag=0;
MenuFlag=0;
ReturnFlag=0;
EndFlag=0;
}
}
基本上到这里就结束了,肉眼可见这个作品还可以继续优化,比如低功耗模式,再比如优化菜单的互动等等,但受限于作者水平,只能等待后续学习再来完善了。