本项目基于stm32c8t6单片机,拥有四种解锁方式:蓝牙解锁,指纹解锁,IC卡解锁,密码解锁。
通过舵机旋转模拟开关锁,指纹解锁和IC卡解锁多次失败之后,蜂鸣器发出警报声,其次无法再用指纹或者IC卡解锁,
技术栈
包括 STM32C8T6 USART SPI 指纹模块 MFRC522 蓝牙
key是按键模块,这个项目用不到,OLED是显示屏模块,主要作用是显示文字和图案。MySPI是进行SPI输出的,RC522是指纹模块,MatrixKey是4*4矩阵键盘模块,Serial是串口1通信,为蓝牙解锁提供服务。PWM是输出PWM波形的模块用于控制舵机,Servo是舵机模块,as608是指纹模块,usart2是串口2通信,为指纹解锁提供服务的,Horn是蜂鸣器模块。
main代码解析
引入头文件
main文件中函数声明,主要是在里面写了指纹模块的函数
变量解析
31行是用于串口1接收的一个中间变量
32是密码锁的初始密码,所以你一开始就可以用这个密码(123456D)解
33是管理员密码,主要作用是,这个密码和密码锁初始密码都可以作为蓝牙解锁的密码,34password_Input[]是密码锁解锁时的一个中间变量,demo[]是将输入密码变成*的数组
35行中KeyNum是4*4矩阵键盘输入的数据,key_error是密码输入错误的次数,i和password_Input[]搭配使用,输入密码代表的密码位数,y是矩阵键盘输入数值的一个中间变量。
36-38是存储IC卡,这里并没有存储到内部FLASH去,无法做到断电不丢失指纹模块同理没有存储到内部FLASH中
39行的作用是将键盘输入类型的转化为字符类型所保存的变量
/*
Num是一个界面确定的变量 Num=9代表关锁界面 Num=1代表开锁界面 Num=0是输入密码界面 Num=10是关于界面 Num=3是更改密码界面
Num=4是添加IC卡界面 Num=5是删除卡的界面 Num=6 添加指纹 Num=7删除指纹 Num=8恢复出厂设置
ID_i是卡号数量,i<2,也就是最多存储两张卡
ID_Start是寻卡 1开始寻卡 0停止寻卡
ID_key代表存储到第几个IC卡号中,(0-5)最多存储6张IC卡
*/
uint8_t Num = 9,Num_Key,ID_i,ID_Start,ID_State,ID_key;
uint8_t demo_1,del_ID,Return,repeat;
uint8_t horn = 0; //蜂鸣器 0代表关闭 1代表打开
uint8_t card = 0; //卡号是否正确标志位置,这里再修改一下,这里card的作用是是(0)否(1)能进入锁屏界面和关于界面,初始化为0,如果ID卡解锁或者指纹解锁失败一次后就置1 ,在锁屏界面进按D键进入输入密码界面后就置0.
初始化解析
74行的波特率设置为你的指纹模块比特率,刚买来的话一般不用动就是57600
上面主要进行密码锁各个模块的初始化
while循环解析
while(1)
{
/*
**********************************************
指纹 解锁
************************************************
*/
if(PS_Sta && Num == 9 && (as608_SUO == 0)) //如果检测到有手指按下(GPIOA1为高电平) as608_SUO为是(0)否(1)允许指纹解锁标志位
{
press_FR_demo = press_FR(); //press_FR_demo为press_FR(识别指纹的函数)的返回值,成功则返回1
if(press_FR_demo) //如果解锁成功
{
KeyNum = 1; //进入操作界面
Num = 1; //解锁标志
as608_demo = 0; //
}
}
/*
**********************************************
指纹 录入 as608_add下面来的
************************************************
*/
if(as608_add == 1) //开始录入指纹
{
if(as608_ID_Date[as608_ID] == 1) //指纹已存在的情况
{
Horn_ON();
OLED_ClearArea(0,16,16*7,16);
OLED_ShowChinese(32,16,"指纹已存在");
OLED_ShowNum(16*7,0,as608_ID_i,1,OLED_8X16);//显示当前录入指纹数
OLED_Update();
as608_add = 0; //停止录入指纹
Delay_ms(1500);
Horn_OFF();
}
else //指纹不存在的情况
{
as608_ttt = as608_add_fingerprint(as608_ID); //后面是录指纹的函数,返回值为0则说明录入成功
if(as608_ttt == 0) //录入成功
{
Horn_ON();
as608_ID_i++; //卡号数量++
as608_ID_Date[as608_ID] = 1; //录入标志
OLED_ClearArea(0,16,16*7,16);
OLED_ShowChinese(32,16,"录入成功");
OLED_ShowNum(16*7,0,as608_ID_i,1,OLED_8X16);//显示当前录入指纹数
OLED_Update();
as608_add = 0; //停止录入指纹
Delay_ms(500);
Horn_OFF();
}
else
{
Horn_ON();
OLED_ClearArea(0,16,16*7,16);
OLED_ShowChinese(32,16,"录入失败");
OLED_Update();
Delay_ms(1500);
Horn_OFF();
}
}
}
if(horn == 1) //horn是蜂鸣器,蜂鸣器打开 1000ms之后,horn=0,蜂鸣器关闭,
{
Horn_ON();
Delay_ms(1000);
Horn_OFF();
horn = 0;
}
/*
**********************************************
ID卡 解锁
************************************************
*/
if(ID_i && (Num == 9)&&IC_ST) //有卡号且在关锁的状态且IC_ST表示可以用卡解锁
{
IC_IDGet(ID); //读卡
if(strcmp(ID,"00000000") != 0) //成功读到卡号(ID不等于00000000) strcmp函数相等返回0 ,不像等返回1
{
for(j = 0;j < 7;j++)
{
if(strcmp(ID_data[j],ID) == 0)
{
Num = 1; //开锁
KeyNum = 1;
}
}
if(Num == 9) //如果读到的卡号不正确情况下 Num=9代表锁着前面没有将Num置1,所以这里还是9代表卡号不正确
{
card_error++; //卡错误次数+1
Horn_ON(); //蜂鸣器打开
card = 1; //卡号是否正确标志位置1
OLED_Clear();
OLED_ShowChinese(0,0,"解锁失败!");
OLED_ShowNum(16*7,0,card_error,2,OLED_8X16); //显示失败次数
OLED_ShowChinese(0,16,"卡号不存在!");
OLED_ShowChinese(16*4,48,"密码解锁");
OLED_Update();
if(card_error > 4) //如果输入的卡号错误5次
{
OLED_ShowChinese(0,16,"警告警告!!!");
OLED_Update();
card_error = 0;
IC_ST = 0; //无法用卡解锁
}
Delay_ms(1500);
Horn_OFF();
}
}
}
/*
**********************************************
ID卡 卡号录入,由下面函数跳转而来
************************************************
*/
if(ID_Start == 1 && repeat == 0) //开始读取录卡卡号
{
IC_IDGet(ID); //读卡
if(strcmp(ID,"00000000") != 0) //读卡成功,也就是读的ID卡号不是00000000
{
if(ID_i > 1) //阈值,最多输入两张卡
{
Horn_ON();
OLED_Clear();
OLED_ShowChinese(0,0,"录入失败!");
OLED_ShowChinese(0,16,"卡号已达阈值!");
OLED_ShowChinese(54,48,"返回");
OLED_Update();
Delay_ms(1500);
Horn_OFF();
}
else //卡号没有超过上限,可以录入ID卡
{
Horn_ON(); //蜂鸣器打开
for(j = 0;j < 7;j++)
{
if(strcmp(ID_data[j],ID) == 0)
{
repeat = 1; //判断是否重复录取
}
}
if(repeat == 0)
{
OLED_ClearArea(0,16,16*8,16);
strcpy(ID_Data,ID); //获取ID值
OLED_ShowString(0,16,"ID:",OLED_8X16);
OLED_Update();
ID_Start = 0; //停止寻卡
}
else
{
OLED_ClearArea(0,16,16*8,16);
OLED_ShowChinese(16,16,"卡号已存在!");
OLED_Update();
}
Delay_ms(500);
Horn_OFF();
}
}
}
}
}
上面主要是主循环中的代码,主要由四部分组成,分别为指纹解锁/录入,IC卡解锁/录入。
操作界面代码解析
uint32_t count;
uint8_t Num_demo,z;
void TIM1_UP_IRQHandler(void) //每1毫秒进一次中断
{
if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET)
{
count++;
if(count > 20)
{
/********************************************************************************
矩阵按键识别部分,Key_Num代表经过键盘处理之后的指令
1 2 3 A
4 5 6 B (向下键)
7 8 9 C (向上键)
>(返回) =(0) < D (确认键)
**********************************************************************************/
count = 0;
//GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_10 | GPIO_Pin_11);
//GPIO_ResetBits(GPIOB, GPIO_Pin_0);
GPIO_SetBits(GPIOA, GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7); //拉高4,5,6,7行
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //拉低第4行
y = Key_LoopY(1); //检测按键是否按下
if(y) //判断第一行是否按下
{
if(y == 4)y = 17; //A
KeyNum = y; //1-3,这里因为那个函数返回第(1.2.3.4)列被按下,所以这里不用计算直接返回出去
}
//GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_10 | GPIO_Pin_11);
//GPIO_ResetBits(GPIOB, GPIO_Pin_1);
GPIO_SetBits(GPIOA, GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
y = Key_LoopY(2); //检测按键是否按下
if(y) //判断第二行是否按下
{
if(y == 4)y = 18-3; //B
KeyNum = y + 3; //4-6,返回的是(1.2.3.4),因为第二列第(1.2.3.4)对应的是(5.6.7.B),所以需要进行一下处理,将其+3
}
//GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_10 | GPIO_Pin_11);
//GPIO_ResetBits(GPIOB, GPIO_Pin_10);
GPIO_SetBits(GPIOA, GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);
GPIO_ResetBits(GPIOA, GPIO_Pin_6);
y = Key_LoopY(3); //检测按键是否按下
if(y)
{
if(y == 4)y = 19-6; //C
KeyNum = y + 6; //7-9,与上一个相同,不解释
}
//GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_10 | GPIO_Pin_11);
//GPIO_ResetBits(GPIOB, GPIO_Pin_11);
GPIO_SetBits(GPIOA, GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);
GPIO_ResetBits(GPIOA, GPIO_Pin_7);
y = Key_LoopY(4); //检测按键是否按下
if(y)
{
if(y == 4)y = 20-9; //D
if(y == 3)y = 5; //> 14
if(y == 2)y = 4; //= 13 后面会转换为0
if(y == 1)y = 3; //< 12
KeyNum = y + 9;
}
/****************************************************************************************************************
操作主界面
*****************************************************************************************************************/
if((KeyNum && (Key_error<4))) //如果按键按下(KeyNum!=0)或者接收到蓝牙的信号
{ //如果输入的密码错误达到5次,蜂鸣器报警,按键无法再输入密码
if(KeyNum == 13)KeyNum = 0; //转换成0
password_Bit = '0' + KeyNum; //获取密码字节,将数字转换为字符串
if(Num == 9) //锁屏界面,Num=9代表锁屏中,前面提到很多次了
{
if(card == 0) //前面的卡错误标志位没有标为1(存疑)
{
Servo_SetAngle(0);
OLED_Clear();
OLED_ShowImage(50,8,32,32,lock);
OLED_ShowChinese(0,48,"关于");
OLED_ShowChinese(96,48,"解锁");
OLED_Update(); //锁屏界面
}
if(password_Bit == 'D') //按下D键(这里猜测是输入密码的功能)的情况
{
Num = 0; //输入密码界面,那么下面可以根据这个进入输入密码界面
card = 0; //卡错误位置0(存疑)
OLED_Clear();
OLED_ShowChinese(0,0,"密码:");
OLED_ShowChinese(0,48,"返回");
OLED_ShowChinese(96,48,"确定");
OLED_Update();
password_Bit = ' '; //将按键清空,防止再触发这个函数,其实这里根本不用解释
}
if(password_Bit == '<' && card == 0)
{
Num = 10; //关于界面
}
}
if(Num == 10) //*******项目信息界面**********
{
OLED_Clear();
OLED_ShowChinese(0,0,"项目:");
OLED_ShowChinese(16,24,"智能门禁锁系统");
OLED_ShowChinese(96,48,"返回");
OLED_Update();
if(password_Bit == 'D') //按下D键进入锁屏界面,退出这个函数,也就是退出关于界面
{
Num = 9; //锁屏界面
Return = 1; //退出函数
}
}
if(Num == 0) //***********输入密码界面******************************************
{
if((password_Bit < ':' && password_Bit > '/') || password_Bit == 'D') //存储密码,根据ASSII码’/‘和’:‘中间对应的是0-9,也就是键盘输入的为0-9或者是D
{
password_Input[i] = password_Bit; //存储输入的密码
demo[i] = '*'; //隐藏密码字符
i++; //密码位数,存储一位之后+1
}
if(password_Bit == 'C') //删除密码(删除输入的一位密码)
{
i--; //密码位数
password_Input[i] = ' ';
demo[i] = ' ';
}
if(i < 7) //OLED屏显示
{
if(password_Bit == 'A') //按下的是’A‘,切换显示和隐藏模式
{
demo_1++; //demo_1的初始值为0,按一下就是1,这个作用就是每按一下都能切换一下显示和隐藏
}
if(demo_1 % 2 == 1) //不显示密码的情况
{
OLED_ShowString(48,0,demo,OLED_8X16); //隐藏密码,横向偏移
OLED_Update();
}
else //显示密码的情况
{
OLED_ShowString(48,0,password_Input,OLED_8X16); //显示密码
OLED_Update();
}
}
if(password_Bit == '<') //返回键,按下后清除储存密码的数组和返回主屏界面(Num=9)
{
i = 0; //密码位数
strcpy(password_Input," "); //清除输入的密码
strcpy(demo," "); //清除输入的密码
Num = 9; //返回锁屏界面
Return = 1;
}
if(password_Input[6] == 'D') //如果按下确定键
{
i = 0; //密码位数
if(strcmp(password_Input,password) == 0) //判断密码是否正确
{
strcpy(password_Input," "); //清除输入的密码
strcpy(demo," "); //清除输入的密码
Num = 1; //密码正确,进入打开锁的界面(功能)
}
else
{
Key_error++; //错误的次数
if(Key_error > 3) //密码输错三次就嗡嗡叫
{
horn = 1; //蜂鸣器鸣叫
}
strcpy(password_Input," "); //清除输入的密码
strcpy(demo," "); //清除输入的密码
Num = 0; //密码错误 进入输密码界面(重新输入密码)
OLED_ShowString(120,0,"!",OLED_8X16);
OLED_ShowNum(112,0,Key_error,1,OLED_8X16);
OLED_Update();
}
}
}
if(Num > 0 && Num < 8) //密码输入正确,进入下面的这几个界面
{
if(password_Bit == '<') //关锁模式
{
Servo_SetAngle(0);
Num = 9; //进入关锁界面
ID_Start = 0; //停止寻卡录卡
i = 0; //清除密码位数
strcpy(password_Input," "); //清除输入的密码
OLED_Clear();
OLED_ShowImage(50,8,32,32,lock);
OLED_ShowChinese(0,48,"关于");
OLED_ShowChinese(96,48,"解锁");
OLED_Update();
}
}
if(Num == 1) //密码输入正确后选择模式界面(之前做的步骤)
{
IC_ST = 1; //允许IC卡解锁,之前可能错误太多把这个锁住了
Num_demo = 1;
OLED_Clear(); //清屏
Servo_SetAngle(180); //开锁
Num = 2; //进入选择模式
password_Bit = ' '; //键盘输入清零
Key_error = 0; //下面都是各种错误的次数清零
card_error = 0;
as608_SUO = 0;
}
if(Num == 2) //选择模式
{
if(password_Bit == 'B') //切换键,向上
{
Num_demo--;
if(Num_demo == 0) //因为光标到1的位置就见底了所以只能减到1,所以到0了自动赋1
{
Num_demo = 1;
}
}
if(password_Bit == 'C') //切换键,向下
{
Num_demo++;
if(Num_demo == 7) //同理
{
Num_demo = 6;
}
}
if(Num_demo < 4) //切换光标,可以理解为有两页,这是第一页的显示内容
{
OLED_Clear();
OLED_ShowChinese(0,0,"模式选择:");
OLED_ShowChinese(16*2,16,"更改密码");
OLED_ShowChinese(16*2,32,"添加卡号");
OLED_ShowChinese(16*2,48,"删除卡号");
OLED_ShowImage(16*6,16*Num_demo,16,16,direction); //16*Num_demo这里就改变了光标移动的位置
OLED_Update();
}
if(Num_demo > 3) //切换光标 ,可以理解为这是第二页的显示内容
{
OLED_Clear();
OLED_ShowChinese(0,0,"模式选择:");
OLED_ShowChinese(16*2,16,"添加指纹");
OLED_ShowChinese(16*2,32,"删除指纹");
OLED_ShowChinese(16*2,48,"恢复出厂");
OLED_ShowImage(16*6,16*(Num_demo-3),16,16,direction); //同理改变光标移动的位置
OLED_Update();
}
if(password_Bit == 'D') //确认键
{
Num = Num_demo+2; //按下确认键之后,光标位置+2就是要去的界面,然后根据前面显示信息不难判断功能呢,这里也很好理解,不解释
password_Bit = ' '; //键盘命令清0
}
}
if(Num == 3) //更改密码界面
{
OLED_Clear();
OLED_ShowChinese(0,0,"新密码:");
OLED_ShowChinese(54,48,"返回");
OLED_ShowChinese(16*6,48,"确定");
OLED_Update();
if((password_Bit < ':' && password_Bit > '/') || password_Bit == 'D') //如果输入的数值是0-9
{
password_Input[i] = password_Bit; //存储输入的密码
i++; //密码位数
}
if(password_Bit == 'C') //删除密码
{
i--;
password_Input[i] = ' ';
}
if(i < 7) //也就是密码没有输入完成的情况下
{
OLED_ShowString(4*16,0,password_Input,OLED_8X16); //显示6位密码
OLED_Update();
}
if(password_Input[6] == 'D') //如果按下确定键,下面功能就是修改密码并清除输入的密码
{
i = 0;
strcpy(password,password_Input); //修改密码,将输入的密码赋值到保存密码password中
OLED_ShowChinese(32,24,"修改成功!");
OLED_Update();
strcpy(password_Input," "); //清除输入的密码
}
if(password_Bit == '>') //返回键,按下后直接清除输入的密码且返回主页面
{
i = 0;
strcpy(password_Input," "); //清除输入的密码
Num = 1;
Return = 1;
}
}
if(Num == 4) //输入卡号,添加IC卡(号)的界面
{
/*
***********************************************************************************************************************************************************************************************************
按下一个0-6的数字给ID_key,作为要存储到第几个卡号,然后只要输入的这个号码所代表的卡号没有存满,那么就显示录入成功,然后
在不按返回的情况下让ID_Start等于1也就是开始寻卡,然后就跳转到前面233行ID卡号录入的函数中去了
************************************************************************************************************************************************************************************************************
*/
if(password_Bit != ' ') //如果键盘输入的字符不等于''
{
if((KeyNum < 6) && (KeyNum > 0)) //&!ID_Start
{
OLED_ShowString(0,16,"ID:",OLED_8X16);
OLED_ShowNum(8*3,16,KeyNum,1,OLED_8X16); //显示输入的ID号 最多输入0-5 6个ID
OLED_Update();
ID_key = KeyNum;
}
if(password_Bit == 'D' && ID_key) //确认键
{
if(strcmp(ID_data[ID_key]," ") == 0) //如果ID不存在,以ID_key为下表的数组,内容为0,代表ID不存在
{
OLED_ShowChinese(16*2,16,"录入成功!");
strcpy(ID_data[ID_key],ID_Data); //存储ID号
ID_i++; //卡号+1
OLED_ShowNum(16*7,0,ID_i,1,OLED_8X16); //显示当前卡数
OLED_Update();
ID_key = 0;
}
else //如果ID已存在
{
OLED_ShowChinese(16*2,16,"录入失败!");
OLED_ShowChinese(16*2,16*2,"卡号已存在!");
OLED_ShowNum(16*7,0,ID_i,1,OLED_8X16);//显示当前卡数
OLED_Update();
ID_key = 0;
}
}
if(password_Bit == '>') //返回键
{
Num = 1; //去主界面
Return = 1;
ID_Start = 0; //停止寻卡
repeat = 0;
}
}
else
{
ID_Start = 1; //开始寻卡录卡
OLED_Clear();
OLED_ShowChinese(0,0,"添加卡号:");
OLED_ShowChinese(54,48,"返回");
OLED_ShowChinese(16*6,48,"确定");
OLED_Update();
}
}
if(Num == 5) //删除卡号界面*****************************************************************
{
if(password_Bit != ' ')
{
if((KeyNum < 6) && (KeyNum > 0)) //删除卡号的ID
{
del_ID = KeyNum; //将键盘输入的数字给del_ID
OLED_ShowNum(8*3,16,KeyNum,1,OLED_8X16); //显示输入的ID号 最多输入0-5 6个ID
OLED_Update();
}
if(password_Bit == 'D') //确定
{
if(strcmp(ID_data[del_ID]," ") == 0) //这个编号的代表的在密码集里面不存在,也就是等于空
{
OLED_ShowChinese(16,32,"卡号不存在!");
OLED_Update();
}
else
{
OLED_ShowChinese(16*4,16,"删除成功"); //如果不为空就显示删除成功并删除ID号
strcpy(ID_data[del_ID]," "); //删除ID号
ID_i--; //卡号-1
OLED_ShowNum(16*7,0,ID_i,1,OLED_8X16); //显示当前卡数
OLED_Update();
}
}
if(password_Bit == '>') //返回,按下后,返回主界面并跳出函数
{
Num = 1;
Return = 1;
}
}
else //否则就一直显示着
{
OLED_Clear();
OLED_ShowChinese(0,0,"删除卡号:");
OLED_ShowChinese(54,48,"返回");
OLED_ShowChinese(16*6,48,"确定");
OLED_ShowString(0,16,"ID: ",OLED_8X16);
OLED_Update();
}
}
if(Num == 6) //添加指纹界面
{
if(password_Bit != ' ')
{
if((KeyNum < 6) && (KeyNum > 0)) //获取指纹ID号,ID号的作用和指纹差不多
{
OLED_ShowNum(8*3,16,KeyNum,1,OLED_8X16); //显示输入的ID号 最多输入0-5 6个ID
OLED_Update();
as608_demo = KeyNum; //获取ID值的临时变量
}
if(password_Bit == 'D' && as608_demo) //确认键
{
as608_add = 1; //开始录入指纹
as608_ID = as608_demo; //获取ID值
OLED_ClearArea(0,16,16*7,16);
OLED_ShowChinese(16*2,16,"请放下手指");
OLED_Update();
as608_demo = 0; //防止误按
}
if(password_Bit == '>') //返回键
{
OLED_Clear();
Num = 1;
Return = 1;
as608_demo = 0;
as608_add = 0; //停止录入指纹
}
}
else
{
OLED_Clear();
OLED_ShowChinese(0,0,"添加指纹:");
OLED_ShowString(0,16,"ID:",OLED_8X16);
OLED_ShowChinese(54,48,"返回");
OLED_ShowChinese(16*6,48,"确定");
OLED_Update();
}
}
if(Num == 7) //删除指纹
{
if(password_Bit != ' ') //确定
{
if((KeyNum < 6) && (KeyNum > 0))//删除指纹的ID
{
del_ID = KeyNum;
OLED_ShowNum(8*3,16,KeyNum,1,OLED_8X16); //显示输入的ID号 最多输入0-5 6个ID
OLED_Update();
}
if(password_Bit == 'D') //确定
{
if(as608_ID_Date[del_ID] == 1)
{
as608_ID_Date[del_ID] = 0;
as608_ID_i--; //卡号-1
Del_FR_One(del_ID);
OLED_ShowChinese(32,32,"删除成功");
OLED_ShowNum(16*7,0,as608_ID_i,2,OLED_8X16);//显示当前录入指纹数as608_ID_i
OLED_Update();
}
else
{
OLED_ShowChinese(16,32,"指纹不存在!");
OLED_Update();
}
}
if(password_Bit == '>') //返回
{
Num = 1;
Return = 1;
}
}
else
{
OLED_Clear();
OLED_ShowChinese(0,0,"删除指纹:");
OLED_ShowChinese(54,48,"返回");
OLED_ShowChinese(16*6,48,"确定");
OLED_ShowString(0,16,"ID: ",OLED_8X16);
OLED_Update();
}
}
if(Num == 8) //恢复出厂设置
{
OLED_Clear();
OLED_ShowChinese(0,0,"正在恢复出厂:");
OLED_ShowChinese(16*2,16,"请稍后。。。");
OLED_Update();
Num = 9;
ID_Start = 0; //停止录卡
i = 0;
Key_error = 0;
card_error = 0;
strcpy(password,"123456D"); //恢复初值密码
Del_FR_Whole(); //清空所有指纹
for(z = 0;z<10;z++)
{
as608_ID_Date[z] = 0; //清空所有指纹ID
ID_Data[z] = 0; //清空所有卡号ID
strcpy(ID_data[z]," "); //清空所有卡号
}
Return = 1;
}
KeyNum = 0;
if(Return == 1) //返回键
{
KeyNum = 1;
Return = 0;
}
}
}
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
}
}
上面代码为操作系统主界面的代码的编写,主要包含在一个1ms进行一次中断的函数中,通过if判断Num的值来判断应该去哪一个界面,所有的界面在参数解析中Num的解析中就可以看见
/*
蓝牙解锁模块
*/
char Serial_RxPacket[10]; //定义接收数据包数组,数据包格式"123456"
void USART1_IRQHandler(void)
{
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
Serial_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket ++; //数据包的位置自增
if(pRxPacket == 6)
{
Serial_RxPacket[pRxPacket] = 'D';
pRxPacket = 0;
if(strcmp(Serial_RxPacket,password) == 0 || strcmp(Serial_RxPacket,password1) == 0) //密码正确
{
Num = 1;
KeyNum = 1;
Num_demo = 1;
Key_error = 0;
strcpy(Serial_RxPacket," "); //清空输入的密码
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除USART1的RXNE标志位
//读取数据寄存器会自动清除此标志位
//如果已经读取了数据寄存器,也可以不执行此代码
}
}
以上代码是蓝牙解锁代码实现,因为蓝牙模块是和串口1相连接的,使用根据串口1接收的数据与与解锁密码(两个,包括管理员密码和密码锁密码)做对比,对比成功之后就解锁,这里对接收的格式并没有做严格的规范,且没有返回数据,只能根据蓝牙发送给MCU的数据来判断是否解锁
//录入指纹
uint8_t as608_add_fingerprint(uint16_t PageID) //主要思路就是,按步骤一步步来,成功的话就显示OK,跳到下一步,某一个步骤不成功的话就,显示NO并且退出函数。
{
uint8_t processnum = 0; //录入的阶段
uint8_t ensure; //录入的结果
while(1)
{
switch (processnum)
{
case 0:
ensure=PS_GetImage();
if(ensure==0x00) //至于为什么返回值为0x00就成功请追一下函数
{
OLED_ClearArea(0,16,16*7,16);
ensure=PS_GenChar(CharBuffer1);//生成特征到CharBuffer1
if(ensure==0x00)
{
OLED_ShowString(32,16,"No.1 OK!",OLED_8X16);
OLED_Update();
processnum=1;//跳到第二步
}
else
{
OLED_ShowString(32,16,"No.1 NO!",OLED_8X16);
OLED_Update();
return 1;
}
}
break;
case 1:
ensure=PS_GetImage();
if(ensure==0x00)
{
ensure=PS_GenChar(CharBuffer2);//生成特征到CharBuffer2
if(ensure==0x00)
{
OLED_ShowString(32,16,"No.2 OK!",OLED_8X16);
OLED_Update();
processnum=2;//跳到第三步
}
else
{
OLED_ShowString(32,16,"No.2 NO!",OLED_8X16);
OLED_Update();
return 1;
}
}
else
{
OLED_ShowString(32,16,"No.2 NO!",OLED_8X16);
OLED_Update();
return 1;
}
break;
case 2:
ensure=PS_Match(); //比对刚刚存储的两个指纹的特征,可以的话跳到第四步
if(ensure==0x00)
{
OLED_ShowString(32,16,"No.3 OK!",OLED_8X16);
OLED_Update();
processnum=3;//跳到第四步
}
else
{
OLED_ShowString(32,16,"No.3 NO!",OLED_8X16);
OLED_Update();
return 1;
}
Delay_ms(1200);
break;
case 3:
ensure=PS_RegModel(); //将CharBuffer1与CharBuffer2中的特征文件合并生成 模板,结果存于CharBuffer1与CharBuffer2
if(ensure==0x00)
{
OLED_ShowString(32,16,"No.4 OK!",OLED_8X16);
OLED_Update();
processnum=4;//跳到第五步
}
else
{
OLED_ShowString(32,16,"No.4 NO!",OLED_8X16);
OLED_Update();
return 1;
}
Delay_ms(1200);
break;
case 4:
ensure=PS_StoreChar(CharBuffer2,PageID); //储存模板将 CharBuffer1 或 CharBuffer2 中的模板文件存到 PageID 号flash数据库位置。 PageID(指纹库位置号)
if(ensure==0x00)
{
OLED_ShowString(32,16,"No.5 OK!",OLED_8X16);
OLED_Update();
processnum = 5;
Delay_ms(1500);
}
else
{
OLED_ShowString(32,16,"No.5 NO!",OLED_8X16);
OLED_Update();
return 1;
}
break;
}
if(processnum==5)
{
break;
}
}
return 0;
}
int press_FR(void)
{
SearchResult seach;
u8 ensure;
u8 ii = 0;
while(1)
{
ensure=PS_GetImage();
if(ensure==0x00)//获取图像成功
{
ensure=PS_GenChar(CharBuffer1);
if(ensure==0x00) //生成特征成功
{
ensure=PS_HighSpeedSearch(CharBuffer1,0,99,&seach);
if(ensure==0x00)//搜索成功
{
as608_SUO = 0;
return 1;
}
else
{
Horn_ON();
ii++;
OLED_Clear();
OLED_ShowNum(16*7,0,ii,2,OLED_8X16);
OLED_ShowChinese(0,0,"解锁失败!");
OLED_ShowChinese(0,16,"请再次放下手指");
OLED_ShowChinese(16*4,48,"密码解锁");
OLED_Update();
card = 1;
Delay_ms(500);
Horn_OFF();
Delay_ms(1800);
if(ii > 2) //多次输入指纹失败之后,便不再允许指纹识别,同时蜂鸣器发出声响
{
as608_SUO = 1; //无法再输入指纹
OLED_ShowChinese(0,16,"警告警告!!!");
OLED_Update();
Horn_ON();
Delay_ms(3000);
Horn_OFF();
return 0;
}
}
}
}
}
}
//删除单个指纹
uint8_t Del_FR_One(u16 id)
{
PS_DeletChar(id,1);//删除单个指纹
return 0;
}
//删除全部指纹
uint8_t Del_FR_Whole(void)
{
u8 ensure;
ensure=PS_Empty();//清空指纹库
if(ensure==0)
{
return 1;
}
return 0;
}
上面的代码主要包括录入指纹,检测指纹,删除(单个/全部)指纹,
AS608模块简单解析
首先,我们先初始化PA1为下拉输入,来检测是否有指纹按下(AS608有指纹按下时就显示为高电平状态),所以面在while循环里面PS_Sta就是代表的是PA1高电平(指纹按下)条件作为指纹识别的条件
//初始化PA1为下拉输入
//读摸出感应状态(触摸感应时输出高电平信号)
void PS_StaGPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//初始化读状态引脚GPIOA
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //IPD
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
}
写串口发送函数,利用USART2发送8字节数据
//串口发送一个字节
static void MYUSART_SendData(u8 data)
{
while((USART2->SR&0X40)==0); //0X40表示第六位(从第0位开始),TC发送完成标志位
USART2->DR = data; //给DR中写数据
}
由于包头之类都是固定的,下面通过函数方便以后代码的书写,因为地址默认为0XFFFFFFFF,所以需要用串口发送四次,每一次都发送一个FF
//发送包头
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);
}
下面是判断中断接收的数组有没有规定的应答包,注释写的已经很仔细了,主要是判断 返回代码中(存到了接收缓冲区USART2_RX_BUF)中有没有指定格式的应答包,这里需要我们知道strstr函数的意思,该函数返回在 A中第一次出现 B字符串的位置,如果未找到则返回 null。可以认为返回应答包的首地址,至于str为什么是前面那些数值,需要我们去AS608参考手册去看应答包的格式
其中str[0]和str[1]组成了应答包的包头,以此类推。。。。。。。。。。
//判断中断接收的数组有没有应答包
//waittime为等待中断接收数据的时间(单位1ms)
//返回值:数据包首地址
static u8 *JudgeStr(u16 waittime)
{
char *data;
u8 str[8]; //AS608向MCU发送的返回包格式,大概为2byt包头0xef01,4byt芯片地址,1byt包标识0x07,2byt包长度,1byt校验码,2byt校验和,
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)
{
Delay_ms(1);
if(USART2_RX_STA&0X8000) //接收到一次数据(最高位被置1)
{
USART2_RX_STA=0; //串口2状态标志位置0,表示未被接收的状态,这样就可以去接收下一个了。
data=strstr((const char*)USART2_RX_BUF,(const char*)str); //strstr返回的是字符相同的首地址,也就是和str数组相同后,返回与USART2_RX_BUF中str[0]相同的地址。简而言之,这个的作用就是将接收缓冲区的与应答包相同的数据包的首地址给data ********
//由应答包格式可知 data[0]=0xef data[1]=0x01 .....data[9]是确认码,以后一般读取这个确认码来判断是否读取成功。
if(data) //data不为0,就返回data
return (u8*)data;
}
}
return 0;
}
其中以录入图像的函数为例,通过芯片手册上指令包格式提示,用串口发送一系列信息
下面代码的注释很详细,唯一可能不懂得是为什么data[9]是指令码,这个我们可以看前面应答包的数据,因为*data指向的是应答包的首地址,我们需要判断的是确认码,就是第十个字节的数据也就是data[9],根据数据手册,成功之后一般指令码为0x00,还有其他问题就去看看数据手册给的指令码
//录入图像 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; //temp是校验和,实际上这里应该是0x0005,包长度一个也占两个字节为0x0003,那么0x01+0x0003+0x01=0x0005
SendCheck(temp); //这里也可以证明temp实际上是两个Byt(0x0005),需要调用函数来发送 ,(因为串口只能发送一个字节的数据)
data=JudgeStr(2000);
if(data)
ensure=data[9]; //***********************这里是一个重点,根据上面得知这是一个确认码,一般成功为0x00,具体看数据手册,最下面也有********
else
ensure=0xff;
return ensure;
}
以下为内部FLASH存储的参考代码,可以参考一下利用内部FLASH包存密码,实现掉电不丢失
stmflash.h
//
//用户根据自己的需要设置
#define STM32_FLASH_SIZE 512 //所选STM32的FLASH容量大小(单位为K)
#define STM32_FLASH_WREN 1 //使能FLASH写入(0,不是能;1,使能)
//
//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址
//FLASH解锁键值
u16 STMFLASH_ReadHalfWord(u32 faddr); //读出半字
void STMFLASH_WriteLenByte(u32 WriteAddr,u32 DataToWrite,u16 Len); //指定地址开始写入指定长度的数据
u32 STMFLASH_ReadLenByte(u32 ReadAddr,u16 Len); //指定地址开始读取指定长度数据
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite); //从指定地址开始写入指定长度的数据
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead); //从指定地址开始读出指定长度的数据
//测试写入
void Test_Write(u32 WriteAddr,u16 WriteData);
#endif
stmfalsh.c
#include "stm32f10x.h" // Device header
#include "stmflash.h"
#include "Delay.h"
#include "USART1.h"
//读取指定地址的半字(16位数据)
//faddr:读地址(此地址必须为2的倍数!!)
//返回值:对应数据.
u16 STMFLASH_ReadHalfWord(u32 faddr)
{
return *(vu16*)faddr;
}
#if STM32_FLASH_WREN //如果使能了写
//不检查的写入
//WriteAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u16 i;
for(i=0;i<NumToWrite;i++)
{
FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
WriteAddr+=2;//地址增加2.
}
}
//从指定地址开始写入指定长度的数据
//WriteAddr:起始地址(此地址必须为2的倍数!!)
//pBuffer:数据指针
//NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
#if STM32_FLASH_SIZE<256
#define STM_SECTOR_SIZE 1024 //字节
#else
#define STM_SECTOR_SIZE 2048
#endif
u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//最多是2K字节
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u32 secpos; //扇区地址
u16 secoff; //扇区内偏移地址(16位字计算)
u16 secremain; //扇区内剩余地址(16位字计算)
u16 i;
u32 offaddr; //去掉0X08000000后的地址
if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
FLASH_Unlock(); //解锁
offaddr=WriteAddr-STM32_FLASH_BASE; //实际偏移地址.
secpos=offaddr/STM_SECTOR_SIZE; //扇区地址 0~127 for STM32F103RBT6
secoff=(offaddr%STM_SECTOR_SIZE)/2; //在扇区内的偏移(2个字节为基本单位.)
secremain=STM_SECTOR_SIZE/2-secoff; //扇区剩余空间大小
if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围
while(1)
{
STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
for(i=0;i<secremain;i++)//校验数据
{
if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除
}
if(i<secremain)//需要擦除
{
FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区
for(i=0;i<secremain;i++)//复制
{
STMFLASH_BUF[i+secoff]=pBuffer[i];
}
STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区
}else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间.
if(NumToWrite==secremain)break;//写入结束了
else//写入未结束
{
secpos++; //扇区地址增1
secoff=0; //偏移位置为0
pBuffer+=secremain; //指针偏移
WriteAddr+=secremain; //写地址偏移
NumToWrite-=secremain; //字节(16位)数递减
if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
else secremain=NumToWrite;//下一个扇区可以写完了
}
};
FLASH_Lock();//上锁
}
#endif
//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
{
u16 i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节.
ReadAddr+=2;//偏移2个字节.
}
}
//
//WriteAddr:起始地址
//WriteData:要写入的数据
void Test_Write(u32 WriteAddr,u16 WriteData)
{
STMFLASH_Write(WriteAddr,&WriteData,1);//写入一个字
}
STMFLASH_Write(SYS_SAVEADDR,(uint16_t*)&password,sizeof(password));写入FLASH
STMFLASH_Read(SYS_SAVEADDR,(uint16_t*)&password,3);读取FLASH