一、关于本项目:
博主纯小白,本文适合于初学者,大佬还请勿喷,欢迎提出意见,有纰漏之处将及时纠正。在浅学了stmf103c8t6后,想着依据现在所拥有的知识和能力做一个小项目。
注:工程代码在文章末尾。
二、准备工作:
- 掌握C语言基础....这个最基础啦...
- 接触过类似单片机,稍微看得懂芯片手册,会烧录啥的...
- 画板子,焊接(这个项目里自己给画了一个板子,整体更整洁美观一点,其实也不是必要的,个人选择)..
- 需要用到的一些硬件...
三、硬件需要:(大概估计了一下成本在100左右)
1.stm32f103c8t6核心板:某宝上十几元一块吧,如下
2.AS608指纹模块:(价格的话四十几元左右)
3.0.96寸OLED模块:用的IIC,要想用spi的话可以自己改改代码。主要用于菜单选择。
4.蜂鸣器:我这直接买的模块,给个低电平就能响,用于判断指纹锁的状态
5.薄膜按键:用来对应oled,进行操作的选择。
6.SG90舵机
四、功能介绍及大致流程
先来个整体图:这是开机通电情况下的,从这个图基本能看到所有的外设。右上角的是总开关。这个开关用的一种金属按钮,这个按钮接线确实需要研究一下,最快的办法就是用万用表测按钮按下和未按下时各个脚的连通情况。左上角的的是一个小型的直流电压表,因为我用的是两节18650输出7.4v电压供电,为了实时监测电池状态,所以加了一个电压表。注:18650电池放电电压低到一定值会对电池造成不可逆的伤害,一般6.3v左右吧。
中间的oled可以用下面的按键来显示不同功能,无操作时就会显示Main Menu,也就是主菜单的意思。按下2,则进入刷指纹功能,将已经录入过的指纹按在AS608上扫描可以被识别出来,使下面的舵机转动,舵机带动IC进行刷卡动作,(这个透明盒子的下面是留有缺口的的,IC卡能伸出去)宿舍门解锁成功。按下1,会进入录入指纹操作,我这里设置了一个安全防护,也就是说如果需要录入新的指纹,需要先进行一次刷指纹,才能进行录入指纹,也就是说必须要有之前录入过的人去刷一下,然后才能录入新指纹。这里的话安全防护我不是采用输入密码的方式,用指纹解锁替代。3键是返回主菜单键,例如要执行刷指纹操作,按下2后,中途不想继续了,可以按下3直接回到主菜单。4键这里没有用到(其实是当时设计板子的时候没有看这个薄膜按键的实物,如果需要用全四个键,则需要五个接口,而我却以为只需要四个接口ᕙ༼ ͝°益° ༽ᕗ,属实是大意了)。
其实本来想用3D打印做个外壳,但是这价格确实有点高(๑ó﹏ò๑)啊。
出发点的话比较简单:考虑到宿舍的门锁的类型(我们宿舍门是需要用学生IC卡刷一下解锁才能推进来),所以就想用这个东西来代替人为刷卡。其实说明白点,就是如果没带IC卡,还能通过这一方式来解锁门禁。
五、PCB设计
这个项目的板子是用嘉立创eda画的,毕竟可以免费打板๑乛◡乛๑。项目要求不是很高,随便布的线,如下图
具体实物:
其实就是几个电源模块,因为输入的是7.4v,用的AMS1117转化为5v或者3.3v供外设使用。 然后引出了一些脚。
之前在开发板上测试,然后才打的板子。
六、功能代码实现
(一)本项目AS608使用:
对于这个模块它是有现成的函数去操作它,只需要知道怎么使用即可(可以用资料里的软件测试一下AS608)。下面进行说明:
PS_GetImage(void); //录入图像 | |
PS_GenChar(u8 BufferID);//生成特征 | |
PS_Match(void);//精确比对两枚指纹特征 | |
PS_Search(u8 BufferID,u16 StartPage,u16 PageNum,SearchResult *p);//搜索指纹 | |
PS_RegModel(void);//合并特征(生成模板) | |
PS_StoreChar(u8 BufferID,u16 PageID);//储存模板 | |
PS_DeletChar(u16 PageID,u16 N);//删除模板 | |
PS_Empty(void);//清空指纹库 | |
PS_ValidTempleteNum(u16 *ValidN);//读有效模板个数 | |
PS_HandShake(u32 *PS_Addr); //与AS608模块握手 |
其实大致流程是这样的:
1.用PS_HandShake( )函数与AS608握手,这个握手类似于单片机向AS608发送一些信息,如果AS608能正常工作的话它会返回一些对应东西来回答你。这个函数用于确认AS608能正常运行,开始运行时先执行这个函数。
2.用PS_GetImage( )来获取按在模块上手指的指纹图像,然后执行PS_GenChar( )来获取这个图像里面的特征,将这个特征存在CharBuffer1或CharBuffer2,是AS08中存储的两个区域。这边的两个函数要执行两次,也就是说录入一个指纹的时候需要按两次,然后AS608执行PS_Match( )去对比刚刚采集到的两个特征,如果符合条件,则执行生成模板PS_RegModel( )函数,然后再存储PS_StoreChar( );具体的每个函数需要的变量大家还是参考keil工程里的文件。
3.PS_DeletChar(u16 PageID,u16 N)这个是用来删除指定位置的指纹,PS_Empty(void);这个函数是清空指纹库。
4.读取内部指纹个数:PS_ValidTempleteNum(u16 *ValidN),可以令一个数等于ValidN,再打印出来即可看见。
5.PS_Search(u8 BufferID,u16 StartPage,u16 PageNum,SearchResult *p);搜索函数,也就是搜索现在刷的指纹在指纹库库中有没有对应的指纹,如果有的话,可以自定义输出一些提示文字啥的,比如find。
关于AS608:
引脚接线:一共有八个接口,这里只用到了前六个,一般也不会用到后两个。
- Vi 模块电源正输入端,3.3。
- Tx 串行数据输出。 TTL 逻辑电平,接c8t6的Rx。
- Rx 串行数据输入。 TTL 逻辑电平,接c8t6的Tx。
- GND 信号地。内部与电源地连接
- WAK 感应信号输出,默认高电平有效(用户可读取状态引脚(WAK)判断有无手指按下,该项目中提供一个中断)
- Vt 触摸感应电源输入端,3.3v 供电(没看错,如果需要检测是否有指纹在上面,就需要接入3.3v,总共两个3.3v)
- U+ USB D+
- U- USB D-
资源分布:
1.缓冲区与指纹库
系统内设有一个 72K 字节的图像缓冲区与二个 512bytes 大小的特征文件缓冲区,名字分别称为:ImageBuffer,CharBuffer1 和 CharBuffer2。用户可以通过指令读写任意一个缓冲区。
CharBuffer1 或 CharBuffer2 既可以用于存放普通特征文件也可以用于存放模板特征文件。通过 UART 口上传或下载图像时为了加快速度,只用到像素字节的高 4 位,即将两个像素合
成一个字节传送。通过 USB 口则是整 8 位像素。
指纹库容量根据挂接的 FLASH 容量不同而改变,系统会自动判别。指纹模板按照序号存放,序号定义为:0—(N-1)(N 为指纹库容量)。用户只能根据序号访问指纹库内容。
2.用户记事本
系统在 FLASH 中开辟了一个 512 字节的存储区域作为用户记事本,该记事本逻辑上被分成 16 页,每页 32 字节。上位机可以通过 PS_WriteNotepad 指令和 PS_ReadNotepad 指令
访问任意一页。注意写记事本某一页的时候,该页 32 字节的内容被整体写入,原来的内容被覆盖。
3.随机数产生器
系统内部集成了硬件 32 位随机数生成器(不需要随机数种子),用户可以通过指令让模块产生一个随机数并上传给上位机。
4.软件开发
模块地址 (大小:4bytes ,属性:读写) )
模块的默认地址为0xFFFFFFFF,可通过指令修改,数据包的地址域必须与该地址相配,命令包/数据包才被系统接收。 注:与上位机通讯必须是默认地址 0xFFFFFFFF !
模块口令 (大小:4bytes ,属性:写)
系统默认口令为 0,可通过指令修改。若默认口令未被修改,则系统不要求验证口令,
上位机和 MCU 与芯片通讯;若口令被修改,则上位机与芯片通讯的第一个指令必须是验证
口令,只有口令验证通过后,芯片才接收其它指令。 注:不建议修改口令!
数据包大小设置(大小:1bytes ,属性:读写)
发送数据包和接收数据包的长度根据该值设定。
波特率数 系数 N 设置 (大小:1bytes ,属性:读写)
USART 波特率=N×9600,N=1~12。
安全等级 level 设置(大小:1bytes ,属性:读写)
系统根据安全等级设定比对阀值,level=1~5。安全等级为 1 时认假率最高,拒认率最低。
安全等级为 5 时认假率最低,拒认率最高。
原文链接:https://blog.csdn.net/qq_44629109/article/details/108582138
此处为as608.c代码:对应功能,代码以注释上。部分函数并没有用到。
#include #include "delay.h" #include "usart2.h" #include "as608.h" #include "stm32f10x.h" #include "OLED_I2C.h" #include "sys.h" #include "BEEP.h" //extern u16 user_ID; u32 AS608Addr = 0XFFFFFFFF; //默认 u8 Have; //手指按下标志 extern u16 ValidN;//模块内有效模板个数 extern SysPara AS608Para;//指纹模块AS608参数 //刷指纹 void press_FR(void) { SearchResult seach; u8 ensure; ensure=PS_GetImage(); if(ensure==0x00)//获取图像成功 { ensure=PS_GenChar(CharBuffer1); if(ensure==0x00) //生成特征成功 { ensure=PS_HighSpeedSearch(CharBuffer1,0,300,&seach); if(ensure==0x00)//搜索成功 { OLED_ShowStr(64,3," --INSERT OK--",1); // printf("\r\n 指纹匹配成功 \r\n");//搜索指纹成功 // open_door_flag=1; // printf("Match ID:%d Match score:%d",seach.pageID,seach.mathscore);//显示匹配指纹的ID和分数 //user_ID=seach.pageID; } else { OLED_ShowStr(0,1," --NO finger--",1); // // BEEP=1;DelayMs(300);BEEP=0; DelayMs(300); } } else { OLED_ShowStr(0,6," --NO finger--",1); // DelayMs(1000); printf("\r\n%s",EnsureMessage(ensure)); } } } //初始化PA6为下拉输入 //读摸出感应状态(触摸感应时输出高电平信号) void PS_StaGPIO_Init(void) { EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//外部中断,需要使能AFIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能GPIOA时钟 //打开PA端口时钟,并且打开复用时钟 //初始化读状态引脚GPIOA GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//输入下拉模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO //GPIOA.6 中断线以及中断初始化配置 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource6); EXTI_InitStructure.EXTI_Line=EXTI_Line6; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器 NVIC_InitStructure.NVIC_IRQChannel =EXTI9_5_IRQn; //使能按键所在的外部中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级1 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 } void EXTI9_5_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line6)!=RESET){ Have=1; } EXTI_ClearITPendingBit(EXTI_Line6); //清除LINE5上的中断标志位 } //串口发送一个字节 static void MYUSART_SendData(u8 data) { while((USART2->SR&0X40)==0); USART2->DR = data; } //发送包头 static void SendHead(void) { MYUSART_SendData(0xEF); MYUSART_SendData(0x01); } //发送地址 static void SendAddr(void) { MYUSART_SendData(AS608Addr>>24); MYUSART_SendData(AS608Addr>>16); MYUSART_SendData(AS608Addr>>8); MYUSART_SendData(AS608Addr); } //发送包标识, static void SendFlag(u8 flag) { MYUSART_SendData(flag); } //发送包长度 static void SendLength(int length) { MYUSART_SendData(length>>8); MYUSART_SendData(length); } //发送指令码 static void Sendcmd(u8 cmd) { MYUSART_SendData(cmd); } //发送校验和 static void SendCheck(u16 check) { MYUSART_SendData(check>>8); MYUSART_SendData(check); } //判断中断接收的数组有没有应答包 //waittime为等待中断接收数据的时间(单位1ms) //返回值:数据包首地址 static u8 *JudgeStr(u16 waittime) { char *data; u8 str[8]; str[0]=0xef;str[1]=0x01;str[2]=AS608Addr>>24; str[3]=AS608Addr>>16;str[4]=AS608Addr>>8; str[5]=AS608Addr;str[6]=0x07;str[7]='\0'; USART2_RX_STA=0; while(--waittime) { DelayMs(1); if(USART2_RX_STA&0X8000)//接收到一次数据 { USART2_RX_STA=0; data=strstr((const char*)USART2_RX_BUF,(const char*)str); if(data) return (u8*)data; } } return 0; } //上传图像: PS_UPImage //功能:将图像缓冲区中的数据上传给上位机 //模块返回确认字 u8 PS_UpImage(void) { u16 temp; u8 ensure; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(0x03); //数据包长度 Sendcmd(0x0a); //命令包 temp = 0x01+0x03+0x0a; SendCheck(temp); //校验和 data=JudgeStr(8000); if(data) ensure=data[9]; else ensure=0xff; return ensure; } //录入图像 PS_GetImage //功能:探测手指,探测到后录入指纹图像存于ImageBuffer。 //模块返回确认字 u8 PS_GetImage(void) { u16 temp; u8 ensure; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(0x03); Sendcmd(0x01); temp = 0x01+0x03+0x01; SendCheck(temp); data=JudgeStr(2000); if(data) ensure=data[9]; else ensure=0xff; return ensure; } //生成特征 PS_GenChar //功能:将ImageBuffer中的原始图像生成指纹特征文件存于CharBuffer1或CharBuffer2 //参数:BufferID --> charBuffer1:0x01 charBuffer1:0x02 //模块返回确认字 u8 PS_GenChar(u8 BufferID) { u16 temp; u8 ensure; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(0x04); Sendcmd(0x02); MYUSART_SendData(BufferID); temp = 0x01+0x04+0x02+BufferID; SendCheck(temp); data=JudgeStr(2000); if(data) ensure=data[9]; else ensure=0xff; return ensure; } //精确比对两枚指纹特征 PS_Match //功能:精确比对CharBuffer1 与CharBuffer2 中的特征文件 //模块返回确认字 u8 PS_Match(void) { u16 temp; u8 ensure; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(0x03); Sendcmd(0x03); temp = 0x01+0x03+0x03; SendCheck(temp); data=JudgeStr(2000); if(data) ensure=data[9]; else ensure=0xff; return ensure; } //搜索指纹 PS_Search //功能:以CharBuffer1或CharBuffer2中的特征文件搜索整个或部分指纹库.若搜索到,则返回页码。 //参数: BufferID @ref CharBuffer1 CharBuffer2 //说明: 模块返回确认字,页码(相配指纹模板) u8 PS_Search(u8 BufferID,u16 StartPage,u16 PageNum,SearchResult *p) { u16 temp; u8 ensure; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(0x08); Sendcmd(0x04); MYUSART_SendData(BufferID); MYUSART_SendData(StartPage>>8); MYUSART_SendData(StartPage); MYUSART_SendData(PageNum>>8); MYUSART_SendData(PageNum); temp = 0x01+0x08+0x04+BufferID +(StartPage>>8)+(u8)StartPage +(PageNum>>8)+(u8)PageNum; SendCheck(temp); data=JudgeStr(2000); if(data) { ensure = data[9]; p->pageID =(data[10]>8)+(u8)PageID; SendCheck(temp); data=JudgeStr(2000); if(data) ensure=data[9]; else ensure=0xff; return ensure; } //删除模板 PS_DeletChar //功能: 删除flash数据库中指定ID号开始的N个指纹模板 //参数: PageID(指纹库模板号),N删除的模板个数。 //说明: 模块返回确认字 u8 PS_DeletChar(u16 PageID,u16 N) { u16 temp; u8 ensure; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(0x07); Sendcmd(0x0C); MYUSART_SendData(PageID>>8); MYUSART_SendData(PageID); MYUSART_SendData(N>>8); MYUSART_SendData(N); temp = 0x01+0x07+0x0C +(PageID>>8)+(u8)PageID +(N>>8)+(u8)N; SendCheck(temp); data=JudgeStr(2000); if(data) ensure=data[9]; else ensure=0xff; return ensure; } //清空指纹库 PS_Empty //功能: 删除flash数据库中所有指纹模板 //参数: 无 //说明: 模块返回确认字 u8 PS_Empty(void) { u16 temp; u8 ensure; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(0x03); Sendcmd(0x0D); temp = 0x01+0x03+0x0D; SendCheck(temp); data=JudgeStr(2000); if(data) ensure=data[9]; else ensure=0xff; return ensure; } //写系统寄存器 PS_WriteReg //功能: 写模块寄存器 //参数: 寄存器序号RegNum:4\5\6 //说明: 模块返回确认字 u8 PS_WriteReg(u8 RegNum,u8 DATA) { u16 temp; u8 ensure; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(0x05); Sendcmd(0x0E); MYUSART_SendData(RegNum); MYUSART_SendData(DATA); temp = RegNum+DATA+0x01+0x05+0x0E; SendCheck(temp); data=JudgeStr(2000); if(data) ensure=data[9]; else ensure=0xff; if(ensure==0) printf("\r\n设置参数成功!"); else printf("\r\n%s",EnsureMessage(ensure)); return ensure; } //读系统基本参数 PS_ReadSysPara //功能: 读取模块的基本参数(波特率,包大小等) //参数: 无 //说明: 模块返回确认字 + 基本参数(16bytes) u8 PS_ReadSysPara(SysPara *p) { u16 temp; u8 ensure; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(0x03); Sendcmd(0x0F); temp = 0x01+0x03+0x0F; SendCheck(temp); data=JudgeStr(1000); if(data) { ensure = data[9]; p->PS_max = (data[14]PS_addr=(data[18]PS_N*9600); } else printf("\r\n%s",EnsureMessage(ensure)); return ensure; } //设置模块地址 PS_SetAddr //功能: 设置模块地址 //参数: PS_addr //说明: 模块返回确认字 u8 PS_SetAddr(u32 PS_addr) { u16 temp; u8 ensure; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(0x07); Sendcmd(0x15); MYUSART_SendData(PS_addr>>24); MYUSART_SendData(PS_addr>>16); MYUSART_SendData(PS_addr>>8); MYUSART_SendData(PS_addr); temp = 0x01+0x07+0x15 +(u8)(PS_addr>>24)+(u8)(PS_addr>>16) +(u8)(PS_addr>>8) +(u8)PS_addr; SendCheck(temp); AS608Addr=PS_addr;//发送完指令,更换地址 data=JudgeStr(2000); if(data) ensure=data[9]; else ensure=0xff; AS608Addr = PS_addr; if(ensure==0x00) printf("\r\n设置地址成功!"); else printf("\r\n%s",EnsureMessage(ensure)); return ensure; } //功能: 模块内部为用户开辟了256bytes的FLASH空间用于存用户记事本, // 该记事本逻辑上被分成 16 个页。 //参数: NotePageNum(0~15),Byte32(要写入内容,32个字节) //说明: 模块返回确认字 u8 PS_WriteNotepad(u8 NotePageNum,u8 *Byte32) { u16 temp; u8 ensure,i; u8 *data; SendHead(); SendAddr(); SendFlag(0x01);//命令包标识 SendLength(36); Sendcmd(0x18); MYUSART_SendData(NotePageNum); for(i=0;i8); MYUSART_SendData(StartPage); MYUSART_SendData(PageNum>>8); MYUSART_SendData(PageNum); temp = 0x01+0x08+0x1b+BufferID +(StartPage>>8)+(u8)StartPage +(PageNum>>8)+(u8)PageNum; SendCheck(temp); data=JudgeStr(2000); if(data) { ensure=data[9]; p->pageID =(data[10]