基于单片机的密码锁是很多基础嵌入式设计的练习题目之一,所以资料也比较多,本设计是最简单的一个指纹开锁的示例,本设计的主要功能是设置两个按键,一个是录入指纹按键,一个是删除指纹按键,当按下不同按键时LCD1602会显示出当前的状态信息,在没有录入指纹的时候,如果直接指纹解锁是无法实现的,并且在三次识别不成功的情况下单片机会驱动蜂鸣器实现报警功能。所以需要进行正确的步骤操作,在录入指纹按键按下后将手指放在指纹识别模块处录入自己的指纹,待录入完成显示器上提示成功后即可放开手指,此时就可以实现指纹开锁的实际功能了,删除指纹同样的道理。可以根据本设计的思路扩展,比如用STM32作为主控去实现一个指纹锁,或者外加矩阵键盘做成密码+指纹两种解锁方式,外加蓝牙模块做成远程开锁+指纹解关于锁方式,外加RC522刷卡解锁+指纹解锁方式等,不是很复杂,只要搞清基本原理就可以。
1 硬件部分
1.1 硬件模块
1、STC89C52RC单片机最小系统板1个
2、AS608指纹模块1个
3、LCD1602液晶模块1个
4、高电平触发有源蜂鸣器1个
5、普通直插四脚微动开关(按键)2个
1.2 原理图及PCB图
2 软件程序
关于单片机指纹锁的软件代码网上随便一搜都会出现很多,基本就是copy然后改一下就行。代码框架如下:
从代码框架截图可以看到是用Keil C51编译器做的软件代码开发,把每个模块的驱动分别放在不同的文件,这种分模块化的思想有利于代码的移植和调试,比如LCD1602的代码是通用的,如果你不是做指纹锁的开发,你是做温湿度显示的开发,如果也要用到LCD1602的话,就可以把LCD1602.c和LCD1602.h放到你的工程中,根据你实际的硬件接线把引脚定义改一下就行。
对于整个工程代码有上千行,这里我只说AS608相关部分的代码,因为很多人在这写这个模块的驱动代码难度较大,先附上AS608.c驱动代码如下:
#include<reg52.h>
#include <intrins.h> //包含C51库文件
#include "AS608.h"
#include "DELAY.h"
#include "USART.h"
#include "LCD1602.h"
#include "main.h"
/******************************************************************/
/******本程序只供学习使用,未经作者许可,不得用于其它任何用途******/
/************************AS608子程序源文件*************************/
//Author:phdd
//修改日期:2023/1/14
//版本:V1.0
//版权所有,盗版必究。
//All rights reserved
/******************************************************************/
unsigned char warning_flag = 0;
unsigned char flag=0;
extern char local_date=0; //全局变量,当前箭头位置
unsigned int finger_id = 0;
volatile unsigned char AS608_RECEICE_BUFFER[32]; //volatile:系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据
//蜂鸣器函数
void Beep_Times(unsigned char times)
{
unsigned char i=0;
for(i=0;i<times;i++)
{
beep=0;
Delay_ms(200);
beep=1;
Delay_ms(200);
}
}
//FINGERPRINT通信协议定义
code unsigned char AS608_Get_Device[10] ={0x01,0x00,0x07,0x13,0x00,0x00,0x00,0x00,0x00,0x1b};//口令验证
code unsigned char AS608_Pack_Head[6] = {0xEF,0x01,0xFF,0xFF,0xFF,0xFF}; //协议包头
code unsigned char AS608_Get_Img[6] = {0x01,0x00,0x03,0x01,0x00,0x05}; //获得指纹图像
code unsigned char AS608_Get_Templete_Count[6] ={0x01,0x00,0x03,0x1D,0x00,0x21 }; //获得模版总数
code unsigned char AS608_Search[11]={0x01,0x00,0x08,0x04,0x01,0x00,0x00,0x03,0xE7,0x00,0xF8}; //搜索指纹搜索范围0 - 999,使用BUFFER1中的特征码搜索
code unsigned char AS608_Search_0_9[11]={0x01,0x00,0x08,0x04,0x01,0x00,0x00,0x00,0x13,0x00,0x21}; //搜索0-9号指纹
code unsigned char AS608_Img_To_Buffer1[7]={0x01,0x00,0x04,0x02,0x01,0x00,0x08}; //将图像放入到BUFFER1
code unsigned char AS608_Img_To_Buffer2[7]={0x01,0x00,0x04,0x02,0x02,0x00,0x09}; //将图像放入到BUFFER2
code unsigned char AS608_Reg_Model[6]={0x01,0x00,0x03,0x05,0x00,0x09}; //将BUFFER1跟BUFFER2合成特征模版
code unsigned char AS608_Delete_All_Model[6]={0x01,0x00,0x03,0x0d,0x00,0x11};//删除指纹模块里所有的模版
volatile unsigned char AS608_Save_Finger[9]={0x01,0x00,0x06,0x06,0x01,0x00,0x0B,0x00,0x19};//将BUFFER1中的特征码存放到指定的位置
//发送包头
void AS608_Cmd_Send_Pack_Head(void)
{
int i;
for(i=0;i<6;i++) //包头
Uart_Send_Byte(AS608_Pack_Head[i]);
}
//发送指令
void AS608_Cmd_Check(void)
{
int i=0;
AS608_Cmd_Send_Pack_Head(); //发送通信协议包头
for(i=0;i<10;i++)
Uart_Send_Byte(AS608_Get_Device[i]);
}
//接收反馈数据缓冲
void AS608_Receive_Data(unsigned char ucLength)
{
unsigned char i;
for (i=0;i<ucLength;i++)
AS608_RECEICE_BUFFER[i] = Uart_Receive_Byte();
}
//FINGERPRINT_获得指纹图像命令
void AS608_Cmd_Get_Img(void)
{
unsigned char i;
AS608_Cmd_Send_Pack_Head(); //发送通信协议包头
for(i=0;i<6;i++) //发送命令 0x1d
Uart_Send_Byte(AS608_Get_Img[i]);
}
//将图像转换成特征码存放在Buffer1中
void FINGERPRINT_Cmd_Img_To_Buffer1(void)
{
unsigned char i;
AS608_Cmd_Send_Pack_Head(); //发送通信协议包头
for(i=0;i<7;i++) //发送命令 将图像转换成 特征码 存放在 CHAR_buffer1
Uart_Send_Byte(AS608_Img_To_Buffer1[i]);
}
//将图像转换成特征码存放在Buffer2中
void FINGERPRINT_Cmd_Img_To_Buffer2(void)
{
unsigned char i;
AS608_Cmd_Send_Pack_Head(); //发送通信协议包头
for(i=0;i<7;i++) //发送命令 将图像转换成 特征码 存放在 CHAR_buffer1
Uart_Send_Byte(AS608_Img_To_Buffer2[i]);
}
//搜索全部用户
void AS608_Cmd_Search_Finger(void)
{
unsigned char i;
AS608_Cmd_Send_Pack_Head(); //发送通信协议包头
for(i=0;i<11;i++)
Uart_Send_Byte(AS608_Search[i]);
}
//转换成特征码
void AS608_Cmd_Reg_Model(void)
{
unsigned char i;
AS608_Cmd_Send_Pack_Head(); //发送通信协议包头
for(i=0;i<6;i++)
Uart_Send_Byte(AS608_Reg_Model[i]);
}
//保存指纹
void AS608_Cmd_Save_Finger( unsigned int storeID )
{
unsigned long temp = 0;
unsigned char i;
AS608_Save_Finger[5] =(storeID&0xFF00)>>8;
AS608_Save_Finger[6] = (storeID&0x00FF);
for(i=0;i<7;i++) //计算校验和
temp = temp + AS608_Save_Finger[i];
AS608_Save_Finger[7]=(temp & 0x00FF00) >> 8; //存放校验数据
AS608_Save_Finger[8]= temp & 0x0000FF;
AS608_Cmd_Send_Pack_Head(); //发送通信协议包头
for(i=0;i<9;i++)
Uart_Send_Byte(AS608_Save_Finger[i]); //发送命令 将图像转换成 特征码 存放在 CHAR_buffer1
}
//删除模板
unsigned char PS_DeletChar(unsigned int PageID,unsigned int N)
{
unsigned int temp;
AS608_Cmd_Send_Pack_Head(); //发送通信协议包头
Uart_Send_Byte(0x01);//命令包标识
Uart_Send_Byte(0x00);
Uart_Send_Byte(0x07);
Uart_Send_Byte(0x0C); //Sendcmd
Uart_Send_Byte(PageID>>8);
Uart_Send_Byte(PageID);
Uart_Send_Byte(N>>8);
Uart_Send_Byte(N);
temp = 0x01+0x07+0x0C
+(PageID>>8)+(unsigned char)PageID
+(N>>8)+(unsigned char)N;
Uart_Send_Byte(temp>>8);
Uart_Send_Byte(temp);
AS608_Receive_Data(12);
//判断接收到的确认码,等于0指纹获取成功
if(AS608_RECEICE_BUFFER[9]==0)
return 1;
else
return 0;
return 0;
}
//添加指纹
void AS608_Add_Fingerprint(void)
{
// unsigned char id_show[]={0,0,0};
LCD1602_WriteCMD(0x01); //清屏
while(1)
{
LCD1602_Display(0x80," Add ",0,16);
LCD1602_Display(0xc0," Please finger ",0,16);
AS608_Cmd_Get_Img(); //获得指纹图像
AS608_Receive_Data(12);
//判断接收到的确认码,等于0指纹获取成功
if(AS608_RECEICE_BUFFER[9]==0)
{
Delay_ms(100);
FINGERPRINT_Cmd_Img_To_Buffer1();
AS608_Receive_Data(12);
AS608_Cmd_Search_Finger();
AS608_Receive_Data(16);
if(AS608_RECEICE_BUFFER[9] == 0) //搜索到
{
warning_flag = 0;
//解锁成功//
LCD1602_Display(0x80," Already exist ",0,16);
LCD1602_Display(0xc0," ",0,16);
Beep_Times(3);
Delay_ms(1000);
break;
}
else //没有找到
{
finger_id = finger_id + 1;
//指纹iD值显示处理
// LCD1602_WriteCMD(0xc0+10);
// LCD1602_Display_Number(finger_id);
//按确认键开始录入指纹信息
LCD1602_Display(0x80," Please Again ",0,16);
LCD1602_Display(0xc0," ",0,16);
AS608_Cmd_Get_Img(); //获得指纹图像
AS608_Receive_Data(12);
//判断接收到的确认码,等于0指纹获取成功
if(AS608_RECEICE_BUFFER[9]==0)
{
Delay_ms(100);
FINGERPRINT_Cmd_Img_To_Buffer1();
AS608_Receive_Data(12);
// LCD1602_Display(0x80,"Successful entry",0,16);
Beep_Times(1);
Delay_ms(1000);
LCD1602_Display(0x80," Please Again ",0,16);
LCD1602_Display(0xc0," ",0,16);
while(1)
{
AS608_Cmd_Get_Img(); //获得指纹图像
AS608_Receive_Data(12);
//判断接收到的确认码,等于0指纹获取成功
if(AS608_RECEICE_BUFFER[9]==0)
{
Delay_ms(200);
LCD1602_Display(0x80," Add success ",0,16);
LCD1602_Display(0xc0," ",0,16);
//指纹iD值显示处理
// LCD1602_WriteCMD(0xc0+10);
// LCD1602_Display_Number(finger_id);
FINGERPRINT_Cmd_Img_To_Buffer2();
AS608_Receive_Data(12);
AS608_Cmd_Reg_Model();//转换成特征码
AS608_Receive_Data(12);
AS608_Cmd_Save_Finger(finger_id);
AS608_Receive_Data(12);
Beep_Times(1);
Delay_ms(1000);
finger_id=finger_id+1;
break;
}
}
break;
}
}
} else {
//按返回键直接回到主菜单
if(KEY_ADD == 0)
{
Delay_ms(5);
if(KEY_ADD == 0)
{
while(KEY_ADD==0);
break;
}
}
}
}
}
// 删除指纹
void AS608_Delete_Fingerprint()
{
unsigned int find_fingerid = 0;
while(1)
{
LCD1602_Display(0x80," Delete ",0,16);
LCD1602_Display(0xc0," Please finger ",0,16);
AS608_Cmd_Get_Img(); //获得指纹图像
AS608_Receive_Data(12);
//判断接收到的确认码,等于0指纹获取成功
if(AS608_RECEICE_BUFFER[9]==0)
{
Delay_ms(100);
FINGERPRINT_Cmd_Img_To_Buffer1();
AS608_Receive_Data(12);
AS608_Cmd_Search_Finger();
AS608_Receive_Data(16);
if(AS608_RECEICE_BUFFER[9] == 0) //搜索到
{
warning_flag = 0;
//解锁成功//
LCD1602_Display(0x80," Search success ",0,16);
LCD1602_Display(0xc0," ",0,16);
Beep_Times(1);
//拼接指纹ID数
find_fingerid = AS608_RECEICE_BUFFER[10]*256 + AS608_RECEICE_BUFFER[11];
//指纹iD值显示处理
// LCD1602_WriteCMD(0xc0+10);
// LCD1602_Display_Number(find_fingerid);
Delay_ms(1000);
if(PS_DeletChar(find_fingerid,1))
LCD1602_Display(0x80," Delete success ",0,16); //液晶开机显示界面
Delay_ms(1000);
break;
}
else //没有找到
{
LCD1602_Display(0x80," Delete failed ",0,16);
LCD1602_Display(0xc0," ",0,16);
Beep_Times(3);
Delay_ms(1000);
break;
}
} else {
//按返回键直接回到主菜单
if(KEY_DELETE == 0)
{
Delay_ms(5);
if(KEY_DELETE == 0)
{
while(KEY_DELETE==0);
break;
}
}
}
}
}
//搜索指纹
void AS608_Find_Fingerprint()
{
unsigned int find_fingerid = 0;
do
{
if(warning_flag >= 3) {
LCD1602_Display(0x80," Warning ",0,16);
LCD1602_Display(0xc0," ",0,16);
} else {
LCD1602_Display(0x80," Identify ",0,16);
LCD1602_Display(0xc0," Please finger ",0,16);
}
AS608_Cmd_Get_Img(); //获得指纹图像
AS608_Receive_Data(12);
//判断接收到的确认码,等于0指纹获取成功
if(AS608_RECEICE_BUFFER[9]==0)
{
Delay_ms(100);
FINGERPRINT_Cmd_Img_To_Buffer1();
AS608_Receive_Data(12);
AS608_Cmd_Search_Finger();
AS608_Receive_Data(16);
if(AS608_RECEICE_BUFFER[9] == 0) //搜索到
{
//解锁成功//
warning_flag = 0;
LCD1602_Display(0x80,"Identify success",0,16);
LCD1602_Display(0xc0," ",0,16);
Beep_Times(1);
//拼接指纹ID数
find_fingerid = AS608_RECEICE_BUFFER[10]*256 + AS608_RECEICE_BUFFER[11];
//指纹iD值显示处理
// LCD1602_WriteCMD(0xc0+10);
// LCD1602_Display_Number(find_fingerid);
Delay_ms(1000);
break;
}
else //没有找到
{
warning_flag++;
LCD1602_Display(0x80,"Identify failed",0,16);
LCD1602_Display(0xc0," ",0,16);
Beep_Times(3);
Delay_ms(1000);
break;
}
} else {
if(KEY_ADD == 0 && warning_flag < 3)
{
Delay_ms(5);
if(KEY_ADD == 0)
{
while(KEY_ADD==0);
Beep_Times(1);
AS608_Add_Fingerprint();
}
}
if(KEY_DELETE == 0 && warning_flag < 3)
{
Delay_ms(5);
if(KEY_DELETE == 0)
{
while(KEY_DELETE==0);
Beep_Times(1);
AS608_Delete_Fingerprint();
}
}
if(warning_flag >= 3)
{
beep = 0;
} else {
beep = 1;
}
}
}while(1);
}
//验证比对指纹
void Device_Check(void)
{
unsigned char i=0;
AS608_RECEICE_BUFFER[9]=1; //串口数组第九位可判断是否通信正常
LCD1602_Display(0xc0,"Loading",0,7); //设备加载中界面
for(i=0;i<8;i++) //进度条式更新,看起来美观
{
LCD1602_WriteDAT(42); //42对应ASIC码的 *
Delay_ms(20); //控制进度条速度
}
LCD1602_Display(0xc0,"Docking failure",0,16); //液晶先显示对接失败,如果指纹模块插对的话会将其覆盖
AS608_Cmd_Check(); //单片机向指纹模块发送校对命令
AS608_Receive_Data(12); //将串口接收到的数据转存
if(AS608_RECEICE_BUFFER[9] == 0) //判断数据低第9位是否接收到0
LCD1602_Display(0xc0,"Docking success",0,16); //符合成功条件则显示对接成功
}
其实你想驱动任何一个模块或者传感器,除了要具备编程语言的能力之外就是查找这个模块的资料,研究这个模块是怎么用的,AS608模块资料比较全,而且是中文的,因为毕竟它是国产的模块,所以资料中文的比较多,对于英语不好的开发人员比较友好,不需要去看英文的datasheet,这里我针对AS608的通讯协议简单分析,AS608和51单片机通信主要是串口通信,但是串口为了提高数据传输效率采用的是数据包的方式,其实很多通信协议都是大同小异,不采用数据包的话那数据传输不仅慢而且还容易出现收发不一致的情况,比如丢帧,堵塞甚至错发。
AS608的通信协议的指令包主要包括三种:命令包、数据包和结束包,截图如下:
重点是我圈出来的红色部分,首先说地址,AS608出厂默认的地址就是0xFFFFFFFF,当然你可以根据相关指令修改,但是一般人没那么闲,基本都用的是0xFFFFFFFF, 这里命令包、数据包和结束包的包头和芯片地址都是一样的,包头都是以0xEF01开始,地址都是0xFFFFFFFF,所以在写代码的时候就把包头做成了一个数组传递给其他需要用的函数,也就是上述AS608.c里的
code unsigned char AS608_Pack_Head[6] = {0xEF,0x01,0xFF,0xFF,0xFF,0xFF}; //协议包头
这个数组其实是配合这个函数来用的:
//发送包头
void AS608_Cmd_Send_Pack_Head(void)
{
int i;
for(i=0;i<6;i++) //包头
Uart_Send_Byte(AS608_Pack_Head[i]);
}
数组里有6个元素,所以就通过一个for循环来按位取出包头数组AS608_Pack_Head里的每个元素,然后通过Uart_Send_Byte串口发送函数发出去,那么这个串口发送函数又在哪呢,它就在USART.c这个文件里:
//UART Send a byte
void Uart_Send_Byte(unsigned char c)
{
SBUF = c;
while(!TI); //发送完为1
TI = 0;
}
我们都知道51单片机有个串口发送和接收共用一个缓冲区叫SBUF,缓冲区就是串口先把发出去的数据先准备好暂时存在这里,收到发送指令再把数据从SBUF里发出去(接收也是一个道理),这个SBUF关键字在单片机的代码里就是这么写的,你不能改,就像你叫张三,别人就得叫你张三一样,叫你张四你肯定不同意, 所以串口也不认别的关键字作为缓冲区。
上面这个函数就是有个字符是c,把先给SBUF,然后串口启动SBUF就把数据发出去了,TI是发送标志位,如果TI为0就代表数据还没有发送完,!TI就是1,所以程序就会一直卡在这里执行while(!TI),直到TI为1,当TI为1的时候代表发送完成,那么!TI就是0,那while(!TI)这句就直接跳过了,代表发送完成,然后把TI=0,等待下一次的数据发送,否则TI不手动清零,那么下次数据发送标志位就起不到作用了,单片机也不知道发没发送完。
接下来我们再看一个函数:
//接收反馈数据缓冲
void AS608_Receive_Data(unsigned char ucLength)
{
unsigned char i;
for (i=0;i<ucLength;i++)
AS608_RECEICE_BUFFER[i] = Uart_Receive_Byte();
}
这个函数是单片机接收AS608返回的数据,从AS608的协议可以看到AS608发送完数据包要有相应的应答包,简单来说就是单片机作为主机给AS608模块发送一个指令,AS608接收到单片机发来的指令后需要给单片机回复一下,也就是应答一下,应答包的数据格式如下:
应答包相对于指令包 可以看到他们的包头也是一样的,都是0xEF01,然后地址是0xFFFFFFFF,不同的是包标识,还有就是多了一个确认码和返回参数,确认码0x00就是收到了正确的
指令,如果是其他的确认码就对应出现了不同的错误,这个你可以根据不同的确认码自己去实现处理方式,我这段代码里没去处理(懒得处理).
所以AS608的所有指令包和应答包都是遵循上面的格式的,不同的就是每个指令包和应答包内容不一样而已。AS608的指令大概有31个,常用的没几个,不需要用到的我们就不要往代码里写了,浪费内存。指令截图如下:
我们开发的话其实常用的就是这几个,别的不需要去关心。
2.1 指令包解析
我这里以删除指纹模版这个指令为例,
//删除模板
unsigned char PS_DeletChar(unsigned int PageID,unsigned int N)
{
unsigned int temp;
AS608_Cmd_Send_Pack_Head(); //发送通信协议包头
Uart_Send_Byte(0x01);//命令包标识
Uart_Send_Byte(0x00);
Uart_Send_Byte(0x07);
Uart_Send_Byte(0x0C); //Sendcmd
Uart_Send_Byte(PageID>>8);
Uart_Send_Byte(PageID);
Uart_Send_Byte(N>>8);
Uart_Send_Byte(N);
temp = 0x01+0x07+0x0C
+(PageID>>8)+(unsigned char)PageID
+(N>>8)+(unsigned char)N;
Uart_Send_Byte(temp>>8);
Uart_Send_Byte(temp);
AS608_Receive_Data(12);
//判断接收到的确认码,等于0指纹获取成功
if(AS608_RECEICE_BUFFER[9]==0)
return 1;
else
return 0;
return 0;
}
模板是啥?模板就是你录好的指纹后会存在AS608的内存里(比如你的大拇指录入一个),AS608自带AS608就把你的指纹存成一个模板,你要是用食指再去录一个指纹,那么AS608就会把你的这个食指指纹再存成另一个模板。我们从AS608通讯协议书里找到删除模板的指令包和应答包,截图如下:
先说指令包,主要由:包头+芯片地址+包标识+包长度+指令码+页码+删除个数+校验和这八个部分组成,页码就是你的指纹模版个数,比如你录了三个指纹到AS608:大拇指、无名指、食指。那么他们三个按录入的先后顺序的模板页码就是:1、2、3。你要是想只删除大拇指的模板,那么你的PageID这个地方就是1,N也是1。如果你想删除食指的模板,那么你的PageID这个地方就是3,N也是1。
这个删除模板的函数的形参有两个,一个就是PageID,一个是删除个数,因为其他的都是固定的,只有这俩是变化不确定的,系统也不知道你会录几个手指头到AS608,所以就不知道你有几个PageID,当你想删除的时候也是,系统也不知道你到底想同时删除几个模板,这个是随心所欲的,有些人就是好奇非要把自己的10个手指都录入进去,那PageID就从1-10,有的人为了省事,就录了一个手指头,那么PageID就是1,所以就是把这两个不确定的东西作为函数参数,参数正好是变量。
接着这个函数首先要调用发送包头的函数,也就是单片机把删除模板的包头和芯片地址发送给AS608,然后我们看删除模板的指令包第三个部分就是包标识,是01H,01H就是十六进制数,那么0x01和它是同一个意思(不懂的你该去看看C语言进制单位标识和转换),通过Uart_Send_Byte串口发送函数发送0x01,接着干嘛,接着就是第四个部分:包长度,为啥是0x07呢,看这里:
没看懂?那么再看一下删除模板的指令包格式:
包长度直到校验和的所有字节数相加之和就是包长度:1byte+2bytes+2bytes+2bytes=7bytes=7(十进制数)=0x07(十六进制数) 。
然后第五部分是指令码,这个是固定的,就是0x0c,这就是AS608官方自己规定的,对应代码看是不是0x0c,再通过Uart_Send_Byte串口发送函数把0x0c发送给AS608。
第六部分就到了PageID了,PageID是这个函数形参的第一个变量,但是我们看删除模板的PageID部分是2bytes,也就是2个字节,串口一次是发送一个字节数据的,而且是低位先发,高位后发,举个例子:比如有两个字节的数据要发送:0x5732,我再给你分析一下为啥0x5763是两个字节数据,首先1byte=8bit,也就是一个字节(byte)是八位(bit),0x5763是十六进制数,换算成二进制数就是
十六进制数 | 二进制数 |
0x5763 | 0101 0111 0110 0011 |
所以你看表里的二进制数有几个bit?是不是16个bit,那不就是2byte嘛,也就是两个字节,而且57是高字节,63是低字节,所以表格里最左边的0是高位,最右边的1是地位,串口是低位先发,高位后发。再普及一个知识点,MSB和LSB分别是最高有效位(Most Significant Bit)和最低有效位(Least Significant Bit),它们是对于数据位的描述。
MSB:最高有效位,也被称为高位。在一个多位数据中,MSB是位值最高的一位,其权值最大。
LSB :最低有效位,也被称为低位。在一个多位数据中,LSB是位值最低的一位,其权值最小。
所以串口属于LSB先发,也就是最低有效位先发送,所以上面的0x5763实际发送的顺序是:
1 1 0 0 0 1 1 0 1 1 1 0 1 0 1 0
那我们都知道了串口一次只发一个字节数据,所以第一次串口发送函数只能发PageID两个字节中的低字节,PageID的高字节需要等低字节发完了才能发,所以重点就来了,这就是为啥这段代码是这么写的了:
Uart_Send_Byte(PageID>>8);
Uart_Send_Byte(PageID);
第一行就是发送了 PageID低字节,但是它需要往右移动八位,也就是移动一个字节,回到自己低字节的位置去,不准占着高字节的位置,要不第二行代码发送的·PageID高字节来了咋办?你让它去哪待着,就相当于你每天上公交车一样,你在车门的地方刷完卡了,你得往车厢里走,你不走你后面排队想刷卡上车的人怎么办?难道挂在车外面跟着车走吗?
然后就到了第七个部分删除个数,因为它也是变化的,系统哪知道你一次想删除几个,就像你把十个手指头都录入了,那你就想一次删除10个,那你也可以只删除2个,所以N是不固定的,这个N的代码部分移位问题和PageID一样,都是两个字节,先发低字节再发高字节。
第八部分就是校验和了,校验和就是从包标识符到校验和校验和之前的部分的字节数相加,记住是字节数,不是数量,因为这个PageID和N是不固定的,所以校验和这里不是一个固定数字,就定义了一个变量temp,先把前面的都加起来,固定部分的字节:包标识是0x01,包长度是0x07,指令码是0x0c,所以校验和temp就是:
temp = 0x01+0x07+0x0C+(PageID>>8)+(unsigned char)PageID+(N>>8)+(unsigned char)N;
因为校验和这个也是2bytes,也就是两个字节,所以通过Uart_Send_Byte串口发送函数发送也需要分两次发,第一次发低字节,第二次发高字节,所以也需要右移八位。
2.2应答包解析
前面介绍过,指令包是由单片机发给AS608的,那么AS608需要给单片机一个回应,就是应答包,删除模版的应答包格式如下:
这里最重要的就是单片机对这个应答包里的确认码的识别,代码如下:
AS608_Receive_Data(12);
//判断接收到的确认码,等于0指纹获取成功
if(AS608_RECEICE_BUFFER[9]==0)
return 1;
else
return 0;
return 0;
这个AS608_Receive_Data()函数是啥意思呢?这个函数定义在USART.c文件里
//接收反馈数据缓冲
void AS608_Receive_Data(unsigned char ucLength)
{
unsigned char i;
for (i=0;i<ucLength;i++)
AS608_RECEICE_BUFFER[i] = Uart_Receive_Byte();
}
AS608_Receive_Data()函数又调用了Uart_Receive_Byte()函数,这个Uart_Receive_Byte()函数定义如下:
unsigned char Uart_Receive_Byte()
{
unsigned char dat;
while(!RI); //接收完为1
RI = 0;
dat = SBUF;
return (dat);
}
这个函数是不是和 Uart_Send_Byte串口发送函数很像?其实它俩就是串口真正执行的函数,一个是发送,一个是接收,我们在介绍Uart_Send_Byte函数的时候说过串口有一个发送缓冲区SBUF,其实这个SBUF也是接收缓冲区,它俩是共用的,所以Uart_Receive_Byte函数接收一个数据dat,接收到以后呢就判断RI是不是1,RI是接收完成标志位,如果RI=0,就代表数据dat还没接收完,!RI就等于1,所以程序就一直卡在while(!RI)执行,直到接收完成RI=1,那么!RI=0,while(!RI)就不成立了,程序就跳转到下一句执行了,此时接收完了,那么SBUF就把数据给单片机了:dat = SBUF;,然后这个函数就返回接收到的数据dat给到调用它的函数了。
所以AS608_RECEICE_BUFFER[i] = Uart_Receive_Byte();这句代码的意思就是单片机通过串口接收到AS608的应答包数据了,把这个应答包数据存到一个AS608_RECEICE_BUFFER数组里,因为应答包里有好几个字节的数据,所以用数组比较方便接收,你当然可以用指针,但是指针一般人好像都用不好,容易出问题。那么这个数组怎么定义的呢:
volatile unsigned char AS608_RECEICE_BUFFER[32]; //volatile:系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据
这里涉及到一个小的知识点,这里定义了一个voltaile限制的字符数组,大小是32个byte,因为我们也不知道你用哪个指令,相应的应答包会回复多少字节的数据回来,所以就定义个大一点字节数组,voltaile是C语言的关键字,主要的功能就是告诉数组,我不管你上次里面存的是啥,我不看,我只看你这次的数组,我就要你最新的,不要以前的。
if(AS608_RECEICE_BUFFER[9]==0)
所以这段代码的意思就是删除模板里的应答包的第10个字节单片机要看 ,那么我们看删除模板应答指令包的第10个字节是什么,它就是确认码啊,因为应答包前面是:包头(2bytes)+芯片地址(4bytes)+指令码(1bytes)+包长度(2bytes)= 9bytes,那么确认码就是第10个bytes了,而且数组是从下标0开始的,所以 AS608_RECEICE_BUFFER[9]就是取AS608_RECEICE_BUFFER的第10个元素,也就是第10个字节的数据。如果它等于0,也就是00H,也相当于0x00,我们看删除模板的应答包格式下面写的确认码=00H表示删除模板成功,然后我们的代码就return 1 ,也就是如果这个函数返回值为1表示完成了,否则就return 0返回0表示删除失败。
在删除指纹函数里确实调用了这个函数,并且判断它的返回值是不是1来看删除模版是否成功。
3 实物效果
我自己测试用的是万用表焊接的,这种的省钱自己焊接还快,如果做PCB的话需要发给制作PCB的代工厂去加工,还需要等快递,这里提醒一下,嘉立创每个月有两次免费的打板机会,不过有尺寸规定,可以利用起来。
焊接好的实物
上电添加指纹成功
添加成功指纹后再用同一个手指去按AS608,显示识别成功
4 最后
资料都在这里