一、硬件说明
1、接口
注意:其中Touch_VCC 3.3V是需要常供电的,用于Touch_Out触摸唤醒供电,VDD3.3V可以检测到Touch_Out高电平信号再上电。如果不需要低功耗设计,可以忽略Touch_Out和Touch_VCC 3.3V引脚,悬空即可。
2、供电
注意: 需要考虑电路驱动能力,电压和电流稳定,避免指纹头运行异常。
3、串口配置
注意:就算单片机和指纹头设置一样的波特率,但是也有可能有误差注意配置好单片机时钟。
以华大32位单片机为例,可以看出4MHz和24MHz主频下误差率差别很大。
二、软件说明
1、协议格式
主要针对命令包,其他包格式没用过,不做说明,自查手册。
注意:特别需要注意的是校验和的计算(校验和16bit = 包长度+指令+参数1+参数二+参数N)
每个命令都有应答包响应格式如下:
2、简单指纹操作
相信使用指纹头做指纹识别,首先肯定是最基础的就是指纹的增删改查了,不过改就是覆盖,和添加指纹是一样,所以就是增删查了!
对指纹头来说,修改数据是高级操作,是需要权限的,所以就需要先验证密码,来获取权限。
2.1、验证口令FP_VfyPwd
芯片地址一般默认0xffffffff,口令默认0x00000000,通过查询手册参数表可以得到。
接下来就可以愉快的拼凑出通信报文进行通信了。
2.2自动录入指纹
手册提供了指纹录入的一站式命令(自动注册模板 PS_AutoEnroll),命令格式和录入流程如下。
注意:不同指纹头要求最少录入的次数不同,如果设置次数不够在验证指纹时可能无法识别,一般2-6次,如果不清楚6次最稳妥。
2.3 清空指纹
会添加指纹了,也要回删除指纹,先从最简单的清空指纹命令开始,
清空指纹库 PS_Empty
参数都不用设置,直接发,省事。
2.3自动指纹验证
手册提供了,自动验证指纹的一站式命令(自动验证指纹 PS_AutoIdentify),格式和识别流程如下。
注意:安全等级1-5,我使用的0x1,id号使用0xffff,参数使用0x0000 就好了,可以根据自己需求修改。
2.4 整体操作流程图
注意:这里指纹识别只识别一次,如果不用触摸中断,可以在循环中使用PS_GetImage命令判断手指是否按下,另外由于我录入指纹是需要id自动累加的,所以先读取索引表计算空闲的id,发送休眠指令是为了识别完成给指纹头断电节能,断电之前要先发送休眠指令,直接断电指纹头可能会出现异常。
3、分步指纹操作
一站式命令固然好用,但是很多内部处理步骤我们看不到,使用起来也不够灵活,所以我们尝试手动一步一步来做识别。
为了方便我直接录入和识别的流程全部贴出来
注意:一个空间存储的指纹是由多个指纹特征合成的,你甚至可以循环录入指纹的时候每次都按不同的手指,就可以一次添加多个手指了,但是识别准确度会下降,并不推荐。
另外PS_GetImage 和PS_GetEnrollImage作用差不多,PS_GetImage 用于识别, PS_GetEnrollImage用于录入,有些甚至可以串用,但是有些指纹头可能不行,不推荐串用。
PS_GetImage是个很好用的命令,可以在循环中使用检测手指是否按下,检测到指纹再生成特征进行指纹识别。
录入指纹大致说来就是获取一次图像生成一个特征,然后将多个特征合并存储就好了。
识别指纹就是录入指纹生成特征,然后用特征去指纹库里检索。
三、代码片段
1、宏定义
//晟元协议
#define DATA_SIZE_MAX (256-2) //数据区域最大值 减去校验和2字节
#define CMD_FLAG 1 //命令标志
#define DATA_FLAG 2 //数据标志
#define ACT_FLAG 7 //应答标志
#define DATA_END_FLAG 8 //数据包结束标志
typedef enum {
CMD_VERIFY_PASSWORD = 1,
CMD_VERIFY_DEFPASSWORD,
CMD_SET_PASSWORD,
CMD_SET_DEFPASSWORD,
CMD_GET_IMAGE,
CMD_GEN_TEMPLET1,
CMD_GEN_TEMPLET2,
CMD_GEN_TEMPLET3,
CMD_SEARCH_FINGER,
CMD_MERGE_TEMPLET,
CMD_STORE_TEMPLET,
CMD_GETENROLLIMAGE,
CMD_CMP_TEMPLET,
CMD_GET_NUMBERS,
CMD_READINDEXTABLE,
CMD_CONTROLLED,
CMD_DEFINELED,
CMD_READSYSPARA,
CMD_SETBAUDRATE,
CMD_SETDEFADDR,
CMD_HANDSHAKE,
CMD_READ_NOTE,
CMD_CLEAR_NOTE,
CMD_DELETCHAR,
CMD_EMPTY,
CMD_SLEEP,
CMD_AUTOENROLL,
CMD_AUTOIDENTIFY
}COMMON_CMD;
struct fp_package {
uint16_t header;
uint32_t addr;
uint8_t flag;
uint16_t length;
uint8_t data[DATA_SIZE_MAX];
uint16_t sum;
};
struct fp_send_package {
uint16_t header;
uint32_t addr;
uint8_t flag;
uint16_t length;
uint8_t code;
uint8_t data[DATA_SIZE_MAX-1];
uint16_t sum;
};
struct fp_recv_package {
uint16_t header;
uint32_t addr;
uint8_t flag;
uint16_t length;
uint8_t ret;
uint8_t data[DATA_SIZE_MAX-1];
uint16_t sum;
};
struct uart_rcbuf_s{
uint8_t u8ResponeData[FP_USART_BUFF_MAX];
uint16_t u8ResponeCnt;
uint16_t u8finish;
};
//一站式注册指纹
#define USE_AUTOENROLL(N) {N.length=8;N.data[0]=0x31;N.data[1]=0x0;N.data[2]=0x01;N.data[3]=0x6;}
//自动采集指纹
#define USE_AUTOIDENTIFY(N) {N.length=8;N.data[0]=0x32;N.data[1]=0x1;N.data[2]=0xff;N.data[3]=0xff;}
//验证设备握手口令
#define USE_VERIFY_PASSWORD(N) {N.length=7;N.data[0]=0x13;N.data[1]=0x12;N.data[2]=0x34;N.data[3]=0x43;N.data[4]=0x21;}
#define USE_VERIFY_DEFPASSWORD(N) {N.length=7;N.data[0]=0x13;N.data[1]=0;N.data[2]=0;N.data[3]=0;N.data[4]=0;}
//设置设备握手口令
#define USE_SET_PASSWORD(N) {N.length=7;N.data[0]=0x12;N.data[1]=0x12;N.data[2]=0x34;N.data[3]=0x43;N.data[4]=0x21;}
#define USE_SET_DEFPASSWORD(N) {N.length=7;N.data[0]=0x12;N.data[1]=0;N.data[2]=0;N.data[3]=0;N.data[4]=0;}
//探测手指并从传感器上读入图像
#define USE_GET_IMAGE(N) {N.length=3;N.data[0]=0x1;}
//根据原始图像生成指纹特征1
#define USE_GEN_TEMPLET1(N) {N.length=4;N.data[0]=0x2;N.data[1]=0x1;}
//根据原始图像生成指纹特征2
#define USE_GEN_TEMPLET2(N) {N.length=4;N.data[0]=0x2;N.data[1]=0x2;}
//根据原始图像生成指纹特征3
#define USE_GEN_TEMPLET3(N) {N.length=4;N.data[0]=0x2;N.data[1]=0x3;}
//以CharBufferA或CharBufferB中的特征文件搜索整个或部分指纹库
#define USE_SEARCH_FINGER(N) {N.length=8;N.data[0]=0x04;N.data[1]=0x1;N.data[2]=0x0;N.data[3]=0x0;N.data[4]=0x00;N.data[5]=0x64;}
//将CharBufferA与CharBufferB中的特征文件合并生成模板,结果存于ModelBuffer
#define USE_MERGE_TEMPLET(N) {N.length=3;N.data[0]=0x5;}
//将ModelBuffer中的文件储存到flash指纹库中
#define USE_STORE_TEMPLET(N) {N.length=6;N.data[0]=0x6;N.data[1]=0x1;N.data[2]=0x0;N.data[3]=0x0;}
//GetEnrollImage
#define USE_GETENROLLIMAGE(N) {N.length=3;N.data[0]=0x29;}
//cmp
#define USE_CMP_TEMPLET(N) {N.length=3;N.data[0]=0x3;}
//GET NUMBERS
#define USE_GET_NUMBERS(N) {N.length=3;N.data[0]=0x1d;}
//READ INDEXTABLE
#define USE_READINDEXTABLE(N) {N.length=4;N.data[0]=0x1f;}
//set baudrate
#define USE_SETBAUDRATE(N) {N.length=5;N.data[0]=0x0E;N.data[1]=0x04;N.data[2]=0x06;}
//Control LED
#define USE_CONTROLLED(N) {N.length=4;N.data[0]=0x40;N.data[1]=0x0f;}
//Define LED
#define USE_DEFINELED(N) {N.length=7;N.data[0]=0x3c;N.data[1]=0xf5;N.data[2]=0x0;N.data[3]=0x00;}
//读系统基本参数
#define USE_READSYSPARA(N) {N.length=3;N.data[0]=0x0f;}
//设置默认地址
#define USE_SETDEFADDR(N) {N.length=7;N.data[0]=0x15;N.data[1]=0xFF;N.data[2]=0xFF;N.data[3]=0xFF;N.data[4]=0xFF;}
//握手
#define USE_HANDSHAKE(N) {N.length=3;N.data[0]=0x35;}
//Read Note
#define USE_READ_NOTE(N) {N.length=4;N.data[0]=0x19;}
//Clear Note
#define USE_CLEAR_NOTE(N) {N.length=36;N.data[0]=0x18;N.data[1]=0x2;}
//删除一个指纹
#define USE_DELETCHAR(N) {N.length=7;N.data[0]=0x0C;N.data[4]=0x1;}
//清空指纹库
#define USE_EMPTY(N) {N.length=3;N.data[0]=0x0D;}
//休眠
#define USE_SLEEP(N) {N.length=3;N.data[0]=0x33;}
//错误标志
enum {
FP_ERROR = -1,
FP_OK = 0,
FP_TIMEOUT = 1,
};
enum FP_RES_CODE{
RES_OK = 0, // 00H:表示指令执行完毕或 OK;
RES_RCERR, // 01H:表示数据包接收错误;
RES_NOTOUCH, // 02H:表示传感器上没有手指;
RES_TINERR, // 03H:表示录入指纹图像失败;
RES_PTOOWATER, // 04H:表示指纹图像太干、太淡而生不成特征;
RES_PTOODRY, // 05H:表示指纹图像太湿、太糊而生不成特征;
RES_PTOOMESS, // 06H:表示指纹图像太乱而生不成特征;
RES_PFTOOFEW, // 07H:表示指纹图像正常,但特征点太少(或面积太小)而生不成特征;
RES_FPNOPASS, // 08H:表示指纹不匹配;
RES_FPNOSEARCH, // 09H:表示没搜索到指纹;
RES_FMERERR, // 0aH:表示特征合并失败;
RES_IDXOVER, // 0bH:表示访问指纹库时地址序号超出指纹库范围;
RES_RDMODERR, // 0cH:表示从指纹库读模板出错或无效;
RES_ULFUERR, // 0dH:表示上传特征失败;
RES_CNRDDPAK, // 0eH:表示模块不能接收后续数据包;
RES_ULPICERR, // 0fH:表示上传图像失败;
RES_DELMODERR, // 10H:表示删除模板失败;
RES_ENPFPFAULT, // 11H:表示清空指纹库失败;
RES_CNENLPMOD, // 12H:表示不能进入低功耗状态;
RES_PWDERR, // 13H:表示口令不正确;
RES_RESETFAULT, // 14H:表示系统复位失败;
RES_BUFNORPIC, // 15H:表示缓冲区内没有有效原始图而生不成图像;
RES_FPNOMOVE, // 17H:表示残留指纹或两次采集之间手指没有移动过;
RES_RWFLAERR, // 18H:表示读写 FLASH 出错;
RES_SHDPAK, // f0H:有后续数据包的指令,正确接收后用 0xf0 应答;
RES_SHCPAK, // f1H:有后续数据包的指令,命令包用 0xf1 应答;
RES_ENROLLERR, // 1eH:自动注册(enroll)失败;
RES_FPLIBFULL, // 1fH:指纹库满;
};
2、关键命令发送处理函数
//协议包接收和发送缓冲区
struct fp_package send_pack;
struct fp_recv_package recv_pack;
//高字节和低字节转换
void ConvByteToggleHL(uint8_t * dest,uint8_t * src,uint32_t size)
{
uint32_t lidx;
if(size<1 || dest==NULL || src==NULL){
return ;
}
for(lidx=0;lidx<size;lidx++)
{
dest[lidx]=src[size-lidx-1];
}
}
/**
* Name: FP_SendPack
* Function: 发送包
* Param: void
* Return: int:错误代码
*/
int FP_SendPack(void)
{
int lIdx;
uint8_t lsendbuf[10]={0};
send_pack.header = 0xEF01;
send_pack.addr = 0xFFFFFFFF;
//计算校验和
send_pack.sum = send_pack.flag + (uint16_t)(send_pack.length & 0xff) + (uint16_t)(send_pack.length >> 8);
for (lIdx = 0; lIdx < send_pack.length-2 && lIdx < DATA_SIZE_MAX; lIdx++) {
send_pack.sum += send_pack.data[lIdx];
}
rt_enter_critical();
//发送头标志
ConvByteToggleHL(lsendbuf,(uint8_t *)&send_pack.header,sizeof(send_pack.header));
Uart0_SendBytes(lsendbuf,sizeof(send_pack.header));
//发送地址
ConvByteToggleHL(lsendbuf,(uint8_t *)&send_pack.addr,sizeof(send_pack.addr));
Uart0_SendBytes(lsendbuf,sizeof(send_pack.addr));
//发送标志位
Uart0_SendBytes(&send_pack.flag,sizeof(send_pack.flag));
//发送数据长度
ConvByteToggleHL(lsendbuf,(uint8_t *)&send_pack.length,sizeof(send_pack.length));
Uart0_SendBytes(lsendbuf,sizeof(send_pack.length));
//发送数据
Uart0_SendBytes(send_pack.data,send_pack.length-2);
//发送校验和
ConvByteToggleHL(lsendbuf,(uint8_t *)&send_pack.sum,sizeof(send_pack.sum));
Uart0_SendBytes(lsendbuf,sizeof(send_pack.sum));
rt_exit_critical();
return 0;
}
/**
* Name: FP_RecvPack
* Function: 接收包
* Param: void
* Return: int:错误代码
*/
int FP_RecvPack(struct fp_recv_package * recv)
{
int ret,lto,lIdx;
uint16_t temp_sum;
struct fp_package * recv_pack = (struct fp_package *)recv;
uint8_t * u8SrcAddr = uart_rcbuf.u8ResponeData;
memset(recv_pack,0,sizeof(struct fp_recv_package));
if(!uart_rcbuf.u8finish){
return FP_TIMEOUT;
}
uart_rcbuf.u8finish = 0;
//接收包头
ConvByteToggleHL((uint8_t *)&recv_pack->header,u8SrcAddr,sizeof(recv_pack->header));
u8SrcAddr+=sizeof(recv_pack->header);
if (recv_pack->header != 0xEF01) {
goto FP_RC_ERROR;
}
//接收地址
ConvByteToggleHL((uint8_t *)&recv_pack->addr,u8SrcAddr,sizeof(recv_pack->addr));
u8SrcAddr+=sizeof(recv_pack->addr);
if (recv_pack->addr != 0xFFFFFFFF) {
goto FP_RC_ERROR;
}
//接收包类型标志
ConvByteToggleHL((uint8_t *)&recv_pack->flag,u8SrcAddr,sizeof(recv_pack->flag));
u8SrcAddr+=sizeof(recv_pack->flag);
switch (recv_pack->flag)
{
case CMD_FLAG:
case DATA_FLAG:
case ACT_FLAG:
case DATA_END_FLAG:
break;
default:
goto FP_RC_ERROR;
}
//接收数据长度
ConvByteToggleHL((uint8_t *)&recv_pack->length,u8SrcAddr,sizeof(recv_pack->length));
u8SrcAddr+=sizeof(recv_pack->length);
if (recv_pack->length > 256) {
goto FP_RC_ERROR;
}
//接收数据
memcpy(recv_pack->data,u8SrcAddr,recv_pack->length-2);
//计算校验和
temp_sum = recv_pack->flag + (recv_pack->length & 0xff) + (recv_pack->length >> 8);
for (lIdx = 0; lIdx < (recv_pack->length-2); lIdx++) {
temp_sum += u8SrcAddr[lIdx];
}
u8SrcAddr+=recv_pack->length-2;
//接收校验和
ConvByteToggleHL((uint8_t *)&recv_pack->sum,u8SrcAddr,sizeof(recv_pack->sum));
if (recv_pack->sum != temp_sum) {
goto FP_RC_ERROR;
}
return FP_OK;
FP_RC_ERROR:
return FP_ERROR;
}
/**
* Name: FP_SendCmdPack
* Function: 发送常用命令包
* Param: cmd:命令名
* Return: int:错误代码
*/
int FP_SendCmdPack(COMMON_CMD cmd,uint32_t timeout)
{
rt_err_t rt_err;
int ret,lto;
if(timeout==0){
timeout = FP_DEFAULT_TIMEOUT;
}
memset(&send_pack, 0, sizeof(send_pack));
send_pack.flag = CMD_FLAG;
switch (cmd)
{
case CMD_VERIFY_PASSWORD:
USE_VERIFY_PASSWORD(send_pack);
break;
case CMD_VERIFY_DEFPASSWORD:
USE_VERIFY_DEFPASSWORD(send_pack);
break;
case CMD_SET_PASSWORD:
USE_SET_PASSWORD(send_pack);
break;
case CMD_SET_DEFPASSWORD:
USE_SET_DEFPASSWORD(send_pack);
break;
case CMD_GET_IMAGE:
USE_GET_IMAGE(send_pack);
break;
case CMD_GEN_TEMPLET1:
USE_GEN_TEMPLET1(send_pack);
break;
case CMD_GEN_TEMPLET2:
USE_GEN_TEMPLET2(send_pack);
break;
case CMD_GEN_TEMPLET3:
USE_GEN_TEMPLET3(send_pack);
break;
case CMD_SEARCH_FINGER:
USE_SEARCH_FINGER(send_pack);
break;
case CMD_MERGE_TEMPLET:
USE_MERGE_TEMPLET(send_pack);
break;
case CMD_STORE_TEMPLET:
USE_STORE_TEMPLET(send_pack);
break;
case CMD_GETENROLLIMAGE:
USE_GETENROLLIMAGE(send_pack);
break;
case CMD_CMP_TEMPLET:
USE_CMP_TEMPLET(send_pack);
break;
case CMD_GET_NUMBERS:
USE_GET_NUMBERS(send_pack);
break;
case CMD_READINDEXTABLE:
USE_READINDEXTABLE(send_pack);
break;
case CMD_CONTROLLED:
USE_CONTROLLED(send_pack);
break;
case CMD_DEFINELED:
USE_DEFINELED(send_pack);
break;
case CMD_READSYSPARA:
USE_READSYSPARA(send_pack);
break;
case CMD_SETBAUDRATE:
USE_SETBAUDRATE(send_pack);
break;
case CMD_SETDEFADDR:
USE_SETDEFADDR(send_pack);
break;
case CMD_HANDSHAKE:
USE_HANDSHAKE(send_pack);
break;
case CMD_READ_NOTE:
USE_READ_NOTE(send_pack);
break;
case CMD_CLEAR_NOTE:
USE_CLEAR_NOTE(send_pack);
break;
case CMD_DELETCHAR:
USE_DELETCHAR(send_pack);
break;
case CMD_EMPTY:
USE_EMPTY(send_pack);
break;
case CMD_SLEEP:
USE_SLEEP(send_pack);
break;
case CMD_AUTOENROLL:
USE_AUTOENROLL(send_pack);
break;
case CMD_AUTOIDENTIFY:
USE_AUTOIDENTIFY(send_pack);
break;
default:
return FP_ERROR;
}
memset(&uart_rcbuf,0,sizeof(struct uart_rcbuf_s));
ret = FP_SendPack();
rt_sem_detach(&usart_rcfi_sem);
rt_sem_init(&usart_rcfi_sem,"rcfisem",0,RT_IPC_FLAG_FIFO);
rt_err = rt_sem_take(&usart_rcfi_sem,rt_tick_from_millisecond(timeout));
ret = FP_RecvPack(&recv_pack);
return ret;
}
注意:请忽略我rt-thread的函数调用,主要就是为了等待指纹头串口的响应罢了。