三能(SANNENG)指纹模块通信与应用实战
本文基于某 UART 指纹识别模块官方协议文档,结合本人在 STM32 + FreeRTOS 实际项目中的踩坑与验证经验,总结出一套 从硬件接线 → 通信协议 → 指纹登记 / 识别 / 查询 / 删除 的完整流程说明。
一、指纹模块说明
本文章所用到的是 三能(SANNENG)指纹模块------1016C 电容式指纹识别模块 ,属于一款常见的 串口通信门禁指纹模块,集成了指纹采集、特征提取、模板生成、比对与存储等功能,主控只需通过 UART 发送指令即可完成指纹相关操作。
1. 模块基本特性
- 指纹类型:电容式指纹传感器
- 通信接口:UART(TTL 串口)
- 供电电压:3.3V / 5V(以实际模块版本为准)
- 内部存储:模块内置指纹模板库(无需 MCU 额外存储模板)
- 主要功能 :
- 指纹录入:根据不同型号,最大可录入的指纹容量为 40/80/100/170/200/400/500/1000/1700/3000 枚,本文用的是500枚的
- 指纹删除
- 指纹验证
- 指纹识别
- 上传/下载指纹模板数据: 上传指纹模板数据到主机/下载指纹模板数据到模块
- 上传/下载指纹图像: 上传指纹图像到主机/下载指纹图像到模块(提取指纹特征/录入/与活体指纹验证/识别)
- 检查指定编号范围内的指纹模板数据是否有坏损情况
- 读取模块中已注册的用户总数及用户列表
- 设置/读取指纹模块参数(安全等级、允许/禁止自学习、允许/禁止指纹重复检查、波特率 等)
- 设置/读取指纹模块的序列号
- 在板固件更新
- 读取指纹模块信息(固件类型及版本、算法芯片、指纹传感器)等模块的软硬件信息。
2. 串口通讯
-
起始位:1 位(1bit)
-
数据位:8 位 (8bit)
-
停止位:1 位(1bit)
-
校验位:无
-
波特率:9600/19200/38400/57600/115200/230400/460800/921600 ,默认值:115200BPS
3. 通讯过程:
所有指令的发送、接收必须要遵循一发一收的原则。 主机(Host)在没有收到应答时,不可以向目标模块(TARGET )发送指令。
4. 通讯帧序列传送顺序:
-
字节(Byte)遵循最低有效位优先传送的规则
-
字(Word)遵循低字节优先高字节在后传送的规则。
5. 1016C 指纹模块 优势
- 协议成熟、资料相对完整,代码编写起来很爽
- UART 通信,上手门槛低
- 模块独立完成算法,MCU 压力小
- 支持指纹登记、搜索、删除等完整功能
当然,不同厂家(如 AS608、R503、R307 等)在 通信包格式和命令码 上会略有差异,但整体流程思路基本一致,本文的方法同样具有较强的参考价值。
二、硬件连接
模块通常由 采集头 + 背板 组成,真正对外通信的是背板接口。
1、背板引脚定义

| 序号 | 引脚名 | 说明 |
|---|---|---|
| 1 | GND | 公共地 |
| 2 | UART_RX | UART 接收(连接 MCU_TX) |
| 3 | UART_TX | UART 发射(连接 MCU_RX) |
| 4 | VIN | 电源输入(+3.3V),在超低待机功耗场景下建议接可控电源 |
| 5 | IRQ / WAKEUP | 手指感应唤醒信号输出,高电平有效;有手指触碰即输出高电平 |
| 6 | VCC | 采集器常供电电源(3.3V) |
注意:
- VIN 与 VCC 并非同一电源用途:
- VCC:用于指纹采集器本体,通常保持常供电
- VIN:用于模块逻辑供电,可由 MCU 控制以降低待机功耗
- 若项目对功耗不敏感,可直接将 VIN 与 VC 同接 3.3V
2. 硬件参考图
背板引脚编号

硬件连接参考图
在这里插入图片描述

三、通信整体流程概览
指纹模块采用 主从式通信模型:
- Host(主机):STM32 / MCU
- Target(从机):指纹模块
基本流程:
- Host 发送 命令包(Command Packet)
- 模块解析命令
- 模块返回 响应包(Response Packet)
- 若涉及大数据,再进入 数据包交互阶段

四、通信数据包结构详解
1. 通讯包 Packet 识别代码
| Packet 类别 | Code(包类别识别码) | 描述 |
|---|---|---|
| 命令包(Command Packet) | 0xAA55 |
主设备发送的命令请求包 |
| 响应包(Response Packet) | 0x55AA |
从设备对命令包的响应 |
| 指令数据包(Command Data Packet) | 0xA55A |
携带指令相关数据的命令数据包 |
| 响应数据包(Response Data Packet) | 0x5AA5 |
携带数据内容的响应数据包 |
各种包的类型已经对应包类别识别码,主要用于发送给指纹模块或者从指纹模块接收后识别包的类型。
2. 命令包 Command packet

| 偏移值(OFFSET) | 域定义(FIELD) | 数据类型(TYPE) | 字节数(SIZE) | 描述(DESCRIPTION) |
|---|---|---|---|---|
| 0 | PREFIX | WORD | 2 byte | 包识别码(Packet Identify Code) |
| 2 | SID | BYTE | 1 byte | 源标识(Source Device ID) |
| 3 | DID | BYTE | 1 byte | 目标标识(Destination Device ID) |
| 4 | CMD | WORD | 2 byte | 命令字(Command Code) |
| 6 | LEN | WORD | 2 byte( =n, n< 16) | 数据长度(Length of DATA,n < 16) |
| 8 | DATA | Byte Array | 16 byte | 命令参数(Command Parameter,实际数据长度为 n) |
| 24 | CKS | WORD | 2 byte | 校验和(Check Sum:从 PREFIX 到 DATA 所有字节算术和的低 2 字节) |
3. 响应包 Response packet

| 偏移值(OFFSET) | 域定义(FIELD) | 数据类型(TYPE) | 字节数(SIZE) | 描述(DESCRIPTION) |
|---|---|---|---|---|
| 0 | PREFIX | WORD | 2 byte | 包识别码(Packet Identify Code) |
| 2 | SID | BYTE | 1 byte | 源标识(Source Device ID) |
| 3 | DID | BYTE | 1 byte | 目标标识(Destination Device ID) |
| 4 | CMD | WORD | 2 byte | 命令字(Command Code) |
| 6 | LEN | WORD | 2 byte( =n, n< 16) | 数据长度(Length of RET and DATA) |
| 8 | RET | WORD | 2byte | 结果码 Result Code( 0: 成功 , 1 : 失败) |
| 10 | DATA | Byte Array | 16 byte | 响应数据 Response Data (实际为 n - 2byte ) |
| 24 | CKS | WORD | 2 byte | 校验和(Check Sum:从 PREFIX 到 DATA 所有字节算术和的低 2 字节) |
注意:
- LEN 是 RET 长度 + DATA 长度
4. 指令数据包 Command Data Packet

| 偏移值(OFFSET) | 域定义(FIELD) | 数据类型(TYPE) | 字节数(SIZE) | 描述(DESCRIPTION) |
|---|---|---|---|---|
| 0 | PREFIX | WORD | 2 byte | 包识别码(Packet Identify Code) |
| 2 | SID | BYTE | 1 byte | 源标识(Source Device ID) |
| 3 | DID | BYTE | 1 byte | 目标标识(Destination Device ID) |
| 4 | CMD | WORD | 2 byte | 命令字(Command Code) |
| 6 | LEN | WORD | 2 byte ( =n, n< 500) | 数据长度(Length of DATA) |
| 8 | DATA | Byte Array | n byte | 命令参数Command parameter |
| 8+n | CKS | WORD | 2 byte | 校验和(Check Sum:从 PREFIX 到 DATA 所有字节算术和的低 2 字节) |
- Host 须在发送指令数据包之前先传输命令包(Command packet),使得模块 Target 进入指令数据包 (Command Data packet)接收等待状态。
- 在该命令包(Command packet)的数据域(DATA field)中,须设定待传输的指令数据包的长度。
- Host 应在确认 Target 处于指令数据包接收等待状态后传输指令数据包(Command Data Packet)。
5. 响应数据包 Response data packet

| 偏移值(OFFSET) | 域定义(FIELD) | 数据类型(TYPE) | 字节数(SIZE) | 描述(DESCRIPTION) |
|---|---|---|---|---|
| 0 | PREFIX | WORD | 2 byte | 包识别码(Packet Identify Code) |
| 2 | SID | BYTE | 1 byte | 源标识(Source Device ID) |
| 3 | DID | BYTE | 1 byte | 目标标识(Destination Device ID) |
| 4 | CMD | WORD | 2 byte | 命令字(Command Code) |
| 6 | LEN | WORD | 2 byte( =n, n<500) | 结果接数据长度(Length of RET and DATA) |
| 8 | RET | WORD | 2byte | 结果码 Result Code( 0: 成功 , 1 : 失败) |
| 10 | DATA | Byte Array | n - 2 byte | 响应数据 Response Data (实际为 n - 2byte ) |
| 8+n | CKS | WORD | 2 byte | 校验和(Check Sum:从 PREFIX 到 DATA 所有字节算术和的低 2 字节) |
注:
- 从模块 Target 至 Host 中传输 14byte 以上数据时,需利用响应数据包(Response data packet)
五、命令列表
| 序号 | 命令名称 | 命令码 (Hex) | 命令功能说明 |
|---|---|---|---|
| 1 | CMD_TEST_CONNECTION | 0x0001 | 与设备进行通信连接测试 |
| 2 | CMD_SET_PARAM | 0x0002 | 设置模块参数(DeviceID、SecurityLevel、Baudrate、DuplicationCheck、AutoLearn、TimeOut)※ TimeOut 仅适用于滑动采集器 |
| 3 | CMD_GET_PARAM | 0x0003 | 获取模块参数(DeviceID、SecurityLevel、Baudrate、DuplicationCheck、AutoLearn、TimeOut) |
| 4 | CMD_GET_DEVICE_INFO | 0x0004 | 获取模块设备信息 |
| 5 | CMD_ENTER_IAP_MODE | 0x0005 | 使模块进入 IAP(应用内编程)模式 |
| 6 | CMD_GET_IMAGE | 0x0020 | 采集指纹图像并暂存至 ImageBuffer |
| 7 | CMD_FINGER_DETECT | 0x0021 | 检测当前是否有手指按压 |
| 8 | CMD_UP_IMAGE | 0x0022 | 将 ImageBuffer 中的指纹图像上传至 HOST |
| 9 | CMD_DOWN_IMAGE | 0x0023 | 从 HOST 下载指纹图像到模块 ImageBuffer |
| 10 | CMD_SLED_CTRL | 0x0024 | 控制采集器背光灯或 LED 指示灯 |
| 11 | CMD_STORE_CHAR | 0x0040 | 将指定 RamBuffer 中的模板保存到指纹库指定编号 |
| 12 | CMD_LOAD_CHAR | 0x0041 | 从指纹库读取模板到指定 RamBuffer(0 / 1 / 2) |
| 13 | CMD_UP_CHAR | 0x0042 | 将 RamBuffer 中的指纹模板上传至 HOST |
| 14 | CMD_DOWN_CHAR | 0x0043 | 从 HOST 下载指纹模板到指定 RamBuffer |
| 15 | CMD_DEL_CHAR | 0x0044 | 删除指定编号范围内的指纹模板 |
| 16 | CMD_GET_EMPTY_ID | 0x0045 | 获取指定范围内第一个未注册的模板编号 |
| 17 | CMD_GET_STATUS | 0x0046 | 获取指定编号模板的注册状态 |
| 18 | CMD_GET_BROKEN_ID | 0x0047 | 检测指定范围内是否存在损坏模板 |
| 19 | CMD_GET_ENROLL_COUNT | 0x0048 | 获取指定范围内已注册模板数量 |
| 20 | CMD_GENERATE | 0x0060 | 将 ImageBuffer 中的指纹图像生成模板并存入指定 RamBuffer |
| 21 | CMD_MERGE | 0x0061 | 融合 RamBuffer 中的 2 或 3 个模板※ 融合结果存于 RamBuffer 0 |
| 22 | CMD_MATCH | 0x0062 | RamBuffer 中两个模板进行 1:1 比对(验证) |
| 23 | CMD_SEARCH | 0x0063 | RamBuffer 模板与指纹库进行 1:N 搜索(识别) |
| 24 | CMD_VERIFY | 0x0064 | RamBuffer 模板与指纹库指定模板进行 1:1 验证 |
| 25 | CMD_SET_MODULE_SN | 0x0008 | 设置模块序列号(ModuleSN) |
| 26 | CMD_GET_MODULE_SN | 0x0009 | 获取模块序列号(ModuleSN) |
| 27 | CMD_ADJUST_SENSOR | 0x0025 | 调节指纹采集器参数※ 部分型号不支持 |
| 28 | CMD_GET_ENROLLED_ID_LIST | 0x0049 | 获取已注册的 UserID 列表 |
| 29 | CMD_ENTER_STANDY_STATE | 0x000C | 使模块进入休眠状态※ 部分型号不支持 |
六、常用的指令介绍
关于登记与识别的指令这里不会涉及,他们会在下面介绍。
1. 连接测试(CMD_TEST_CONNECTION 0x0001)
概述:
- 名字:连接测试
- 指令:CMD_TEST_CONNECTION
- 命令代码:0x0001
**用途:**测试指纹模块通讯是否正常。
官方图文:

代码参考:
c
/**
* @brief 指纹模块 测试
* @note None
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_test_connection(SFM_Event_e sfm_event_e)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX (0xAA55, 低字节在前)
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD 0x0001 (低字节在前)
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0001);
// 6~7: LEN 0x0000
send_buf[6] = 0x00;
send_buf[7] = 0x00;
// 8~23: DATA (len为0,填0)
memset(&send_buf[8], 0, 16);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_TEST_CONNECTION;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
2. 设置参数(CMD_SET_PARAM 0x0002)
概述:
- 名字:设置参数
- 指令:CMD_SET_PARAM
- 命令代码:0x0002
说明:
- 该指令是设置指纹模块各种参数,最主要是**指纹重复检查(Duplication Check)**他能在登记时自动识别指纹是否注册。由于本模块属于是旧版本,这个功能不支持,只能手动判断。
- 其他参数设置默认出厂已经设置好了,直接使用即可。
官方图文:


代码参考:
c
/**
* @brief 指纹模块 读取参数
* @note 根据指定的参数类型(ParameterType),设置设备参数(DeviceID,SecurityLevel,
* Baudrate,DuplicationCheck,AutoLearn,FPTimeOut)的值并返回其结果。
* @param sfm_event_e : 传入的事件
* @param model_type : 指定的参数类型
* @param model_val : 设置设备参数
* @retval None
*/
void SFM_Set_Dev_Val(SFM_Event_e sfm_event_e, uint8_t model_type, uint8_t model_val)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0002);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0005);
// 8~23: DATA
// 指定的参数类型
send_buf[8] = model_type;
// 设置设备参数
SFM_OrderNum(&send_buf[9], &send_buf[10] , 0x0000 + model_val);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_SET_PARAM;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
3. 读取设备信息(CMD_DEVICE_INFO 0x0004)
概述:
- 名字:读取设备信息
- 指令:CMD_DEVICE_INFO
- 命令代码:0x0004
说明:
- 该指令是了读取设备信息的,可以根据设备信息知道模块的类型。
- 最主要是指纹容量
注意:
- 这条指令除了会回发响应包之外,还有响应数据包。
- 响应数据包接收到的数据直接
%c打印即可。
官方图文:


代码参考:
c
/**
* @brief 指纹模块 删除指定编号范围内的指纹
* @note 删除指定编号范围(起始Template编号~结束Template编号)内全部已注册的Template。
* @note 若指定范围无效,则返回ERR_INVALID_PARAM。
* 若指定范围内没有注册Template,则返回ERR_TMPL_EMPTY。
* 删除指定范围内已注册的所有Template并返回其结果。
* @param sfm_event_e : 传入的事件
* @param start_template : 起始Template编号
* @param end_template : 结束Template编号
* @retval None
*/
void SFM_Del_char(SFM_Event_e sfm_event_e, uint16_t start_template, uint16_t end_template)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0044);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0004);
// 8~23: DATA
// 起始Template编号
SFM_OrderNum(&send_buf[8], &send_buf[9], 0x0000 + start_template);
// 结束Template编号
SFM_OrderNum(&send_buf[10], &send_buf[11], 0x0000 + end_template);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_DEL_CHAR;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
4. 获取已注册ID列表(CMD_GET_ENROLLED_ID_LIST 0x0049)
概述:
- 名字:获取已注册ID列表
- 指令:CMD_GET_ENROLLED_ID_LIST
- 命令代码:0x0049
官方文档:


代码参考:
c
/**
* @brief 指纹模块 获取已注册ID列表
* @note 其ID列表信息结构如下:
* 每个字节的每个位表示第x(x=字节号(从0开始)*8+位号(从0开始))个编号的指
* 纹注册状态。
* 若为0,则表示没有注册。若为1,则表示已注册。
* 例如;假设ID列表信息的第二个字节为01000001(2进制),每个位的含义如下:
* 从右开始第0位(1) :8*2+0=16(第16编号中已注册指纹)
* 从右开始第1位(0):8*2+1=17(第17编号中没注册指纹)
* ...
* 从右开始第6位(1) :8*2+6=22(第22编号中已注册指纹)
* 从右开始第7位(0) :8*2+7=23(第23编号中没注册指纹)
* [工作Sequence]
* 1. 以指令应答包的形式将HOST待接收的ID列表信息的大小设为应答数据发送应答。
* 2. 以应答数据包发送模块中已注册ID列表信息。
* @param sfm_event_e : 传入的事件
* @retval None
*/
void SFM_Get_enrolled_id_list(SFM_Event_e sfm_event_e)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0049);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0005);
// 8~23: DATA
// 无
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GET_ENROLLED_ID_LIST;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
5. 删除指定编号范围内的指纹(CMD_DEL_CHAR 0x0044)
概述:
- 名字:删除指定编号范围内的指纹
- 指令:CMD_DEL_CHAR
- 命令代码:0x0044

代码参考:
c
/**
* @brief 指纹模块 删除指定编号范围内的指纹
* @note 删除指定编号范围(起始Template编号~结束Template编号)内全部已注册的Template。
* @note 若指定范围无效,则返回ERR_INVALID_PARAM。
* 若指定范围内没有注册Template,则返回ERR_TMPL_EMPTY。
* 删除指定范围内已注册的所有Template并返回其结果。
* @param sfm_event_e : 传入的事件
* @param start_template : 起始Template编号
* @param end_template : 结束Template编号
* @retval None
*/
void SFM_Del_char(SFM_Event_e sfm_event_e, uint16_t start_template, uint16_t end_template)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0044);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0004);
// 8~23: DATA
// 起始Template编号
SFM_OrderNum(&send_buf[8], &send_buf[9], 0x0000 + start_template);
// 结束Template编号
SFM_OrderNum(&send_buf[10], &send_buf[11], 0x0000 + end_template);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_DEL_CHAR;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
七、登记指纹流程(注册指纹)
1. 登记指纹流程说明
官方流程:
登记指纹:参考§6.1章节的登记流程,内附了各流程步骤用到的每条指令,即执行如下几条指令:
- 执行{采集指纹图像(CMD_GET_IMAGE)
- 转化成特征模板(CMD_GENERATE) (采集和转化特征模板共同需要执行3次)
- 指定编号范围内的 1:N 识别(CMD_SEARCH 0x0063) (可选,下面会说明)
- 融合指纹模板 (CMD_MERGE)
- 保存指纹模板(CMD_STORE_CHAR)
说明:
- 采集指纹图像(CMD_GET_IMAGE) ,会将指纹信息 存储到 ImageBuffer里
- 转化成特征模板(CMD_GENERATE),需要采集到的 ImageBuffer 里指纹数据存储到 RamBuffer0/1/2,所以需要采集3次指纹图像并分别存储到 RamBuffer0/1/2 3个数组中。
- 指定编号范围内的 1:N 识别(CMD_SEARCH 0x0063),由于某些设备中的CMD_MERGE,支持自动识别已经注册的功能进行选择,但博主模块不支持,所以需要手动识别。如果不知道支不支持的,也手动调用。
- 融合指纹模板 (CMD_MERGE),将存储的RamBuffer0/1/2中(选择其二或全选)融合成一个新的特征模板,并存储到指定RamBuffer(随便选一个即可)
- 保存指纹模板(CMD_STORE_CHAR),存储融合后的模板到指纹模块自己的指纹库中。
官方图文:

2. 指纹登记步骤
(1) 采集指纹图像(CMD_GET_IMAGE 0x0020)
概述:
- 名字:采集指纹图像
- 指令:CMD_GET_IMAGE
- 命令代码:0x0020
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
代码参考:
c
/**
* @brief 指纹模块 采集指纹图像
* @note 从采集器采集指纹图像并保存于ImageBuffer中。
* @note 从采集器采集指纹图像。若采集图像正确,则返回ERR_SUCCESS。否则返回错误码。
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_Get_Image(SFM_Event_e sfm_event_e)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0020);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0000);
// 8~23: DATA
// 无
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GET_IMAGE;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
(2) 从暂存在ImageBuffer中的指纹图像产生模板(CMD_GENERATE 0x0060)
概述:
- 名字:从暂存在ImageBuffer中的指纹图像产生模板
- 指令:CMD_GENERATE
- 命令代码:0x0060
注意:
- 该代码需要执行之前,请确保已经调用CMD_GET_IMAGE指令,将图像保存于ImageBuffer中。
- 在上述流程说明中提到,需要与采集3次图像,生成3次模板到RamBuffer0/1/2中。

代码参考:
c
/**
* @brief 指纹模块 从暂存在ImageBuffer中的指纹图像产生模板
* @note 从ImageBuffer中的指纹图像产生指纹模板Template并保存于指定RamBuffer中。
* @param sfm_event_e: 传入的事件
* @param ram_buf_code: RamBuffer编号(0~2)
* @retval None
*/
void SFM_Generate(SFM_Event_e sfm_event_e, uint8_t ram_buf_code)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0060);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0000);
// 8~23: DATA
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000 + ram_buf_code);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GENERATE;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
(3) 指定编号范围内的 1:N 识别(CMD_SEARCH 0x0063)
概述:
- 名字:指定编号范围内的 1:N 识别
- 指令:CMD_SEARCH
- 命令代码:0x0063
说明:
- 随便寻找RamBuffer0/1/2中一个与指纹库中的模板进行对比。
- 起始与结束Template编号直接搜索整个库(1 ~ 最大指纹库容量)

代码参考:
c
/**
* @brief 指纹模块 指定编号范围内的 1:N 识别
* @note 指定Ram Buffer 中的模板与指定搜索范围(起始 Template 编号 ~ 结束 Template 编号)内的
* 所有已注册指纹 Template 之间进行 1:N 比对并返回其结果。
* @note 1. 若指定 Ram Buffer 编号无效,则返回错误码 ERR_INVALID_BUFFER_ID 。
* 2. 若指定搜索范围无效,则返回错误码 ERR_INVALID_BUFFER_ID 。
* 3. 若没有已注册 Template ,则返回错误码 ERR_ALL_TMPL_EMPTY 。
* 4. 指定 Ram Buffer 中的 Template 与已注册的所有模板之间进行比对并返回其结果。
* 若搜索成功,则 RET 返回 ERR_SUCCESS 且在 DATA 域返回被搜索出的模板编号和智能
* 更新结果。否则,RET 返回 ERR_IDENTIFY 。
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_Search(SFM_Event_e sfm_event_e, uint8_t ram_buf_code, uint8_t template_start_code, uint8_t template_end_code)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0063);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0003);
// 8~23: DATA
// 指定Template编号
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000 + ram_buf_code);
// 指定的RamBuffer
SFM_OrderNum(&send_buf[10], &send_buf[11] , 0x0000 + template_start_code);
// 指定的RamBuffer
SFM_OrderNum(&send_buf[12], &send_buf[13] , 0x0000 + template_end_code);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_SEARCH;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
(4) 合成指纹模板数据用于入库(CMD_MERGE 0x0061)
概述:
- 名字:合成指纹模板数据用于入库
- 指令:CMD_MERGE
- 命令代码:0x0061
说明:
- 合成指纹模板有两种二合一与三合一,二合一我认为是需要更精确的指纹才能识别,三合一是指纹特征更多,识别更快。
- 将融合后的指纹保存在RamBuffer0/1/2 中任意一个。
注意:
- 融合后的指纹信息是不能参与识别的,指纹库是识别不出融合后的指纹信息的。

代码参考:
c
/**
* @brief 指纹模块 合成指纹模板数据用于入库
* @note 将暂存在RamBuffer中的模板合并生成模板数据并后保存于指定的RamBuffer中,这里指定保存到RamBuffer[0]中。
* 合成个数可为2或3:
* 若为2:则合成RamBuffer0和RamBuffer1的Template。
* 若为3:则合成RamBuffer0、RamBuffer1和RamBuffer2的Template。
* @param sfm_event_e: 传入的事件
* @param method: 合成方式,只能传入2或3
* 2: 合成个数为2
* 3: 合成个数为3
* @retval None
*/
void SFM_Merge(SFM_Event_e sfm_event_e, uint8_t method)
{
if (method != 2 && method != 3)
return;
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0061);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0003);
// 8~23: DATA
// RamBuffer编号
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000);
// 合成个数(2/3)
SFM_OrderNum(&send_buf[10], &send_buf[11] , 0x0000 + method);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_MERGE;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
(5) 获取指定编号范围内可注册的首个编号(CMD_GET_EMPTY_ID 0x0045)
概述:
- 名字:获取指定编号范围内可注册的首个编号
- 指令:CMD_GET_EMPTY_ID
- 命令代码:0x0045
**注意:**该ID是用于下一步骤中 保存指纹模板数据到模块指纹库 使用的。
官方文档:

代码参考:
c
/**
* @brief 指纹模块 获取第一个未注册的ID
* @note None
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_Get_empty_id(SFM_Event_e sfm_event_e)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0045);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0004);
// 8~23: DATA
// 起始Template编号 0x0001
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0001);
// 结束Template编号 0x01F4
SFM_OrderNum(&send_buf[10], &send_buf[11], 0x01F4);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GET_EMPTY_ID;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
(6) 保存指纹模板数据到模块指纹库(CMD_STORE_CHAR 0x0040)
概述:
- 名字:保存指纹模板数据到模块指纹库
- 指令:CMD_STORE_CHAR
- 命令代码:0x0040
注意:
- 要将 DuplicationCheck 设置为1(ON),即开启状态。如已经手动识别是否已注册,无需理会该设置。

代码参考:
c
/**
* @brief 指纹模块 保存指纹模板数据到模块指纹库
* @note 指定Template编号无效,则返回错误码ERR_INVALID_TMPL_NO。
* 1. 若指定RamBuffer编号无效,则返回错误码ERR_INVALID_BUFFER_ID。
* 2. 若DuplicationCheck设置为OFF,则直接将指定RamBuffer中的指纹模板数据注册
* 于指定编号的指纹库中并返回其结果。
* 3. 若DuplicationCheck设置为ON,则将指定RamBuffer中的Template和已注册的
* 指纹库中的所有Template之间进行1:N比对。
* 4. 若存在比对成功的模板,说明该指纹已注册,则返回(RET):ERR_DUPLICATION_ID,
* 且DATA返回比对成功的Template编号。
* 否则,将该模板注册于指定Template编号的指纹库中并返回其结果。
* @param sfm_event_e : 传入的事件
* @param template_code : 指定的Template编号,存储到某个编号的指纹库中。
* @param ram_buf_code : 指定的RamBuffer中,这个RamBuffer存储的是指纹融合后的模板
* @retval None
*/
void SFM_Store_Char(SFM_Event_e sfm_event_e, uint8_t template_code , uint8_t ram_buf_code)
{
if (template_code < 0 || template_code >= 500) // 这个指纹模块最大容量是500
return;
if (ram_buf_code > 3 || ram_buf_code < 0) // RamBuffer 0 ~ 2
return;
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0040);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0003);
// 8~23: DATA
// 指定Template编号
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000 + template_code);
// 指定的RamBuffer
SFM_OrderNum(&send_buf[10], &send_buf[11] , 0x0000 + ram_buf_code);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_STORE_CHAR;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
八、指纹识别步骤
(1) 采集指纹图像(CMD_GET_IMAGE 0x0020)
概述:
- 名字:采集指纹图像
- 指令:CMD_GET_IMAGE
- 命令代码:0x0020

代码参考:
c
/**
* @brief 指纹模块 采集指纹图像
* @note 从采集器采集指纹图像并保存于ImageBuffer中。
* @note 从采集器采集指纹图像。若采集图像正确,则返回ERR_SUCCESS。否则返回错误码。
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_Get_Image(SFM_Event_e sfm_event_e)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0020);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0000);
// 8~23: DATA
// 无
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GET_IMAGE;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
(2) 从暂存在ImageBuffer中的指纹图像产生模板(CMD_GENERATE 0x0060)
概述:
- 名字:从暂存在ImageBuffer中的指纹图像产生模板
- 指令:CMD_GENERATE
- 命令代码:0x0060
注意:
- 该代码需要执行之前,请确保已经调用CMD_GET_IMAGE指令,将图像保存于ImageBuffer中。
- 该代码需执行3次,分别将图像保存到3个数组里。

代码参考:
c
/**
* @brief 指纹模块 从暂存在ImageBuffer中的指纹图像产生模板
* @note 从ImageBuffer中的指纹图像产生指纹模板Template并保存于指定RamBuffer中。
* @param sfm_event_e: 传入的事件
* @param ram_buf_code: RamBuffer编号(0~2)
* @retval None
*/
void SFM_Generate(SFM_Event_e sfm_event_e, uint8_t ram_buf_code)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0060);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0000);
// 8~23: DATA
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000 + ram_buf_code);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GENERATE;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
(3) 指定编号范围内的 1:N 识别(CMD_SEARCH 0x0063)
概述:
- 名字:指定编号范围内的 1:N 识别
- 指令:CMD_SEARCH
- 命令代码:0x0063
注意:
- 将融合后的Ram Buffer进行对比,我这里默认存储到Ram Buffer[0] 中。

代码参考:
c
/**
* @brief 指纹模块 指定编号范围内的 1:N 识别
* @note 指定Ram Buffer 中的模板与指定搜索范围(起始 Template 编号 ~ 结束 Template 编号)内的
* 所有已注册指纹 Template 之间进行 1:N 比对并返回其结果。
* @note 1. 若指定 Ram Buffer 编号无效,则返回错误码 ERR_INVALID_BUFFER_ID 。
* 2. 若指定搜索范围无效,则返回错误码 ERR_INVALID_BUFFER_ID 。
* 3. 若没有已注册 Template ,则返回错误码 ERR_ALL_TMPL_EMPTY 。
* 4. 指定 Ram Buffer 中的 Template 与已注册的所有模板之间进行比对并返回其结果。
* 若搜索成功,则 RET 返回 ERR_SUCCESS 且在 DATA 域返回被搜索出的模板编号和智能
* 更新结果。否则,RET 返回 ERR_IDENTIFY 。
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_Search(SFM_Event_e sfm_event_e, uint8_t ram_buf_code, uint8_t template_start_code, uint8_t template_end_code)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0063);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0003);
// 8~23: DATA
// 指定Template编号
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000 + ram_buf_code);
// 指定的RamBuffer
SFM_OrderNum(&send_buf[10], &send_buf[11] , 0x0000 + template_start_code);
// 指定的RamBuffer
SFM_OrderNum(&send_buf[12], &send_buf[13] , 0x0000 + template_end_code);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_SEARCH;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
九、代码参考
- 我的代码思路是将所有串口输入的信息接收,采取拼帧的方式进行信息的整合,因为所有接收的信息都是知道长度的,根据长度就知道单次接收的消息全部内容。
- 所有的指令都采取事件状态机的思路执行,通过消息队列传递执行相关操作。
串口接收与发送消息
c
/**
* @brief UART2的发送数据(下位机-》上位机)
* @note 发送的是字符串数据
* @param None
* @retval None
*/
void USART2_SendStr(const char *str)
{
while(*str != '\0')
{
USART_SendData(USART2, *str++); // 发送一个字符
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == 0); // 等待字符发送结束
}
}
/**
* @brief UART2的接收数据中断服务函数(上位机-》下位机)
* @note 接收来自上位机(电脑的串口助手、手机蓝牙APP、手机wifi的app)的信息,并进行处理
* @param None
* @retval None
*/
void USART2_IRQHandler(void)
{
if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
{
uint8_t byte = USART_ReceiveData(USART2);
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(sfm_byte_queue, &byte, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
拼帧任务
c
/**
* @brief 指纹模块 SFM 1016C 拼帧操作
* @note 串口将所有的位都塞进消息队列里,在此任务中执行拼帧操作
* @param pvParameters:创建任务时传递的参数
* @retval None
*/
static void app_task_SFM_ParseTask(void *pvParameters)
{
uint8_t byte;
static int16_t sfm_data_all_len = 0; // 数据总大小
static int16_t sfm_data_index_len = 0; // 数据当前大小 用于索引
static SFM_ParseState_e sfm_state = SFM_WAIT_PREFIX1;
static SFM_Packet_e sfm_packet = SFM_Packet_Empty; // 当前命令包类型
static SFM_Frame_Resp_t frame;
for (;;)
{
if (xQueueReceive(sfm_byte_queue, &byte, portMAX_DELAY) == pdPASS)
{
// printf("byte: %02X\r\n", byte);
switch (sfm_state)
{
// 先接收低字节
/* 等 PREFIX 低字节 包标识 */
case SFM_WAIT_PREFIX1:
{
// 重置数据,清空数据
memset(&frame, 0, sizeof(SFM_Frame_Resp_t));
sfm_data_index_len = 0;
sfm_data_all_len = 0;
sfm_packet = SFM_Packet_Empty;
if (byte == 0xAA || byte == 0xA5)
{
printf("\r\n#########################\r\n");
printf("%-20s == %02X\r\n", "SFM_WAIT_PREFIX1", byte);
frame.prefix = byte;
sfm_state = SFM_WAIT_PREFIX2;
}
} break;
/* 等 PREFIX 高字节 包标识 */
case SFM_WAIT_PREFIX2:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_PREFIX2", byte);
uint16_t prefix = (frame.prefix | (byte << 8) );
// 更新 数据
frame.prefix = prefix;
if (prefix == 0x55AA || prefix == 0x5AA5)
{
if (prefix == 0x55AA)
{
sfm_packet = SFM_Packet_Response;
printf("sfm_packet == SFM_Packet_Response\r\n");
}
else if (prefix == 0x5AA5)
{
sfm_packet = SFM_Packet_Response_Data;
printf("sfm_packet == SFM_Packet_Response_Data\r\n");
}
else
{
printf("sfm_packet == None Packet\r\n");
sfm_state = SFM_WAIT_PREFIX1;
}
printf("%-20s == %04X\r\n", "SFM_WAIT_PREFIX ALL", prefix);
sfm_state = SFM_WAIT_SID;
}
else
{
sfm_state = SFM_WAIT_PREFIX1; // 非法前缀,重新找
printf("非法包头!\r\n");
}
} break;
/* 等 SID 字节 源标识 */
case SFM_WAIT_SID:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_SID", byte);
frame.sid = byte;
sfm_state = SFM_WAIT_DID;
} break;
/* 等 DID 字节 目标标识 */
case SFM_WAIT_DID:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_DID", byte);
frame.did = byte;
sfm_state = SFM_WAIT_RCM1;
} break;
/* 等 RCM 低字节 响应码 */
case SFM_WAIT_RCM1:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_RCM1", byte);
frame.rcm = byte;
sfm_state = SFM_WAIT_RCM2;
} break;
/* 等 RCM 高字节 响应码 */
case SFM_WAIT_RCM2:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_RCM2", byte);
uint16_t rcm = (frame.rcm | (byte << 8) );
// 数据 更新
frame.rcm = rcm;
printf("%-20s == %04X\r\n", "SFM_WAIT_RCM ALL", frame.rcm);
sfm_state = SFM_WAIT_LEN1;
} break;
/* 等 LEN 低字节 结果接数据长度 */
case SFM_WAIT_LEN1:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_LEN1", byte);
frame.len = byte;
sfm_state = SFM_WAIT_LEN2;
} break;
/* 等 LEN 高字节 结果接数据长度 */
case SFM_WAIT_LEN2:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_LEN2", byte);
uint16_t len = (frame.len | (byte << 8) );
// 如果 响应数据 DATA 过大,直接返回
if (len > (SFM_MAX_DATA_LEN))
{
printf("LEN overflow: %d\r\n", len);
sfm_state = SFM_WAIT_PREFIX1;
break;
}
if (sfm_packet == SFM_Packet_Response)
{
frame.len = len; // 接收长度 RET + DATA_LEN
}
else if (sfm_packet == SFM_Packet_Response_Data)
{
frame.len = len; // 接收长度 RET + DATA_LEN
}
printf("%-20s == %04X\r\n", "SFM_WAIT_LEN ALL", frame.len);
sfm_state = SFM_WAIT_RET1;
} break;
/* 等 RET 低字节 结果码 Result code(0 : 成功, 1 : 失败) */
case SFM_WAIT_RET1:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_RET1", byte);
frame.ret = byte;
sfm_data_index_len++;
sfm_state = SFM_WAIT_RET2;
} break;
/* 等 RET 高字节 结果码 Result code(0 : 成功, 1 : 失败) */
case SFM_WAIT_RET2:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_RET2", byte);
uint16_t ret = (frame.ret | (byte << 8) );
sfm_data_index_len++;
// 数据 更新
frame.ret = ret;
printf("%-20s == %04X\r\n", "SFM_WAIT_RET2 ALL", frame.ret);
printf("\r\nSFM_WAIT_DATA Start\r\n");
sfm_state = SFM_WAIT_DATA;
} break;
/* 等 DATA 数据 响应数据 */
case SFM_WAIT_DATA:
{
frame.data[sfm_data_index_len - 2] = byte;
printf("Byte == %02X ,len == %02X\r\n",byte, sfm_data_index_len);
sfm_data_index_len++;
// 数据包固定长度为 16字节
if (sfm_packet == SFM_Packet_Response)
{
if (sfm_data_index_len >= 16) // DATA 数据接收完毕
{
printf("\r\nSFM_WAIT_DATA Done\r\n");
sfm_state = SFM_WAIT_CKS1;
}
}
else
{
if (sfm_data_index_len >= frame.len) // DATA 数据接收完毕
{
printf("\r\nSFM_WAIT_DATA Done\r\n");
sfm_state = SFM_WAIT_CKS1;
}
}
} break;
/* 等 CKS 低字节 校验和 从 PREFIX 到DATA 域 的所有数据算术和运算后的最低 2 个字节 */
case SFM_WAIT_CKS1:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_CKS1", byte);
frame.cks = byte;
sfm_state = SFM_WAIT_CKS2;
} break;
/* 等 CKS 高字节 校验和 从 PREFIX 到DATA 域 的所有数据算术和运算后的最低 2 个字节 */
case SFM_WAIT_CKS2:
{
printf("%-20s == %02X\r\n", "SFM_WAIT_CKS2", byte);
uint16_t cks = (frame.cks | (byte << 8) );
frame.cks = cks;
printf("%-20s == %04X\r\n", "SFM_WAIT_CKS ALL", frame.cks);
// 计算校验
uint16_t calc_cks = SFM_GetSumCKSByStruct(&frame);
if (frame.cks == calc_cks)
{
/* 校验通过,一帧完整 */
// // 打印信息
// printf("SFM_Frame_Resp_t\n");
// printf("prefix: 0x%04X (%u)\n", frame.prefix, frame.prefix);
// printf("sid : 0x%04X (%u)\n", frame.sid, frame.sid);
// printf("did : 0x%04X (%u)\n", frame.did, frame.did);
// printf("rcm : 0x%04X (%u)\n", frame.rcm, frame.rcm);
// printf("len : %u\n" , frame.len);
// printf("ret : 0x%04X (%u)\n", frame.ret, frame.ret);
// printf("data : ");
// for (int i = 0; i < (frame.len - 2) && i < 500; i++)
// {
// printf("%c", frame.data[i]);
// if ((i + 1) % 16 == 0)
// printf("\n "); // 每16个字节换行
// }
// printf("\r\n");
printf("CKS OK, Frame DONE\r\n");
printf("#########################\r\n");
xQueueSend(sfm_frame_queue, &frame, portMAX_DELAY);
}
else
{
printf("CKS ERROR !!!\r\n");
printf("#########################\r\n");
}
/* 重置状态 */
sfm_state = SFM_WAIT_PREFIX1;
} break;
default:
{
printf("非法位 %02X", byte);
sfm_state = SFM_WAIT_PREFIX1;
}
}
}
vTaskDelay(pdMS_TO_TICKS(5));
}
}
事务状态机任务
c
/**
* @brief 指纹模块 SFM 1016C
* @note None
* @param pvParameters:创建任务时传递的参数
* @retval None
*/
static void app_task_SFM(void *pvParameters)
{
int8_t ret;
SFM_CmdEvent_t sfm_cmd_event_t;
SFM_Frame_Resp_t frame;
SFM_GenerateStep_t sfm_generate_t = SFM_GENERATE_STEP_IDLE;
Ui_Btn_Status_t ui_Btn_Status_t;
uint8_t error_flag = 0;
uint8_t success_flag = 1;
// 等待SFM 初始化完成
vTaskDelay(pdMS_TO_TICKS(500));
for(;;)
{
// 错误通过 continue 退出 ,这里就是处理错误状态的
if (error_flag == 1 || success_flag == 1)
{
if (uxQueueMessagesWaiting(ui_event_ui_btn_queue) != 0)
{
// 通知UI更新 按钮恢复
xEventGroupSetBits(ui_event_group, UI_EVT_BTN_ENABLE);
}
error_flag = 0;
success_flag = 0;
}
// 当前处于什么状态
if(xQueueReceive(sfm_cmd_event_queue, &sfm_cmd_event_t, portMAX_DELAY) == pdPASS)
{
// 等待消息接收
if(xQueueReceive(sfm_frame_queue, &frame, pdMS_TO_TICKS(2000)) == pdPASS)
{
switch(sfm_cmd_event_t.sfm_cmd_e)
{
/* 连接测试 */
case SFM_CMD_TEST_CONNECTION:
{
if(frame.ret != ERR_SUCCESS)
{
printf("SFM_CMD_TEST_CONNECTION Error!\r\n");
error_flag = 1;
continue;
}
printf("SFM_CMD_TEST_CONNECTION Success!\r\n");
} break;
/* 读取设备信息(算法/容量等) */
case SFM_CMD_DEVICE_INFO:
{
// 等待响应数据包
if(xQueueReceive(sfm_frame_queue, &frame, pdMS_TO_TICKS(2000)) == pdPASS)
{
if(frame.ret != ERR_SUCCESS)
{
printf("SFM_CMD_DEVICE_INFO Error!\r\n");
error_flag = 1;
continue;
}
// 解析数据 DATA
SFM_PrintData_Char(&frame);
printf("SFM_CMD_DEVICE_INFO Success!\r\n");
}
else
{
printf("等待响应数据包失败 命令为 %04X!\r\n" , sfm_cmd_event_t.sfm_cmd_e);
}
} break;
/* 读取模块参数 */
case SFM_CMD_GET_PARAM:
{
if(frame.ret == ERR_INVALID_PARAM)
{
printf("SFM_CMD_GET_PARAM Error! ERR_INVALID_PARAM 指定ParameterType无效\r\n");
error_flag = 1;
continue;
}
printf("SFM_CMD_GET_PARAM Success!\r\n");
SFM_PrintData_Int(&frame);
} break;
/* 设置模块参数(设备ID/安全等级等) */
case SFM_CMD_SET_PARAM:
{
if(frame.ret == ERR_INVALID_PARAM)
{
printf("SFM_CMD_SET_PARAM Error! ERR_INVALID_PARAM 指定ParameterType无效\r\n");
error_flag = 1;
continue;
}
if(frame.ret == ERR_INVALID_PARAM)
{
printf("SFM_CMD_SET_PARAM Error! ERR_INVALID_PARAM 指定ParameterType无效\r\n");
error_flag = 1;
continue;
}
printf("SFM_CMD_SET_PARAM Success!\r\n");
SFM_PrintData_Int(&frame);
} break;
/* 采集指纹图像(存入ImageBuffer) */
case SFM_CMD_GET_IMAGE:
{
// 采集超时
if(frame.ret == ERR_TIME_OUT)
{
printf("SFM_CMD_GET_IMAGE RET Error! ERR_TIME_OUT ! 采集超时 !\r\n");
error_flag = 1;
continue;
}
// 采集器上没有指纹 -- 可能需要不断轮询
if(frame.ret == ERR_FP_NOT_DETECTED)
{
printf("SFM_CMD_GET_IMAGE RET Error! ERR_FP_NOT_DETECTED ! 采集器上没有指纹 !\r\n");
vTaskDelay(pdMS_TO_TICKS(20));
printf("Please Try Again!\r\n");
if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_ENROLL_FINGER)
{
// 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_ADD_SCREEN);
xEventGroupSetBits(ui_event_finger_search_group, UI_EVT_FINGER_ADD_UP_AND_PLACE);
// 等待手指放在指纹模块
ret = SFM_Wait_press_finger();
// 非正常退出
if(ret != 0)
{
continue;
}
// 重新执行任务
SFM_Get_Image(sfm_cmd_event_t.sfm_event_e);
}
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_SEATCH_SINGLE_FINGER)
{
// 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_SEARCH_SCREEN);
xEventGroupSetBits(ui_event_finger_search_group, UI_EVT_FINGER_SEARCH_PLACE);
// 等待手指放在指纹模块
ret = SFM_Wait_press_finger();
// 非正常退出
if(ret != 0)
{
continue;
}
// 重新执行任务
SFM_Get_Image(sfm_cmd_event_t.sfm_event_e);
}
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_INENTIFY_FINGER)
{
// 不做处理
finger_status = SFM_STATE_IDLE;
}
}
// 采集成功
if(frame.ret == ERR_SUCCESS)
{
printf("SFM_CMD_GET_IMAGE RET Success!\r\n");
// 指纹登记
if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_ENROLL_FINGER)
{
// 采集成功后 ,开始将指纹图像保存到数组里
if (sfm_generate_t == SFM_GENERATE_STEP_IDLE)
{
printf("To SFM_Generate(0)\r\n");
SFM_Generate(SFM_EVENT_ENROLL_FINGER, 0);
}
else if (sfm_generate_t == SFM_GENERATE_STEP_1)
{
printf("To SFM_Generate(1)\r\n");
SFM_Generate(SFM_EVENT_ENROLL_FINGER, 1);
}
else if (sfm_generate_t == SFM_GENERATE_STEP_2)
{
printf("To SFM_Generate(2)\r\n");
SFM_Generate(SFM_EVENT_ENROLL_FINGER, 2);
}
}
// 指纹识别
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_INENTIFY_FINGER)
{
printf("To SFM_Generate(0)\r\n");
SFM_Generate(SFM_EVENT_INENTIFY_FINGER, 0);
}
// 指纹单个查询
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_SEATCH_SINGLE_FINGER)
{
printf("To SFM_Generate(0)\r\n");
SFM_Generate(SFM_EVENT_SEATCH_SINGLE_FINGER, 0);
}
}
} break;
/* 从图像生成指纹模板 */
case SFM_CMD_GENERATE:
{
// 1. 若指定RamBuffer编号无效,则返回错误码ERR_INVALID_BUFFER_ID。
if (frame.ret == ERR_INVALID_BUFFER_ID)
{
uint8_t i = 0;
switch (sfm_generate_t)
{
case SFM_GENERATE_STEP_1: i = 1; break;
case SFM_GENERATE_STEP_2: i = 2; break;
case SFM_GENERATE_STEP_3: i = 3; break;
default: i = 0;break;
}
printf("SFM_CMD_GENERATE -> SFM_GENERATE_STEP_%d ERR_INVALID_BUFFER_ID Error! 指定RamBuffer编号无效!\r\n", i);
sfm_generate_t = SFM_GENERATE_STEP_IDLE;
error_flag = 1;
continue;
}
// 2. 检查ImageBuffer中图像的正确性。若不正确,则返回ERR_BAD_QUALITY。
if (frame.ret == ERR_BAD_QUALITY)
{
uint8_t i = 0;
switch (sfm_generate_t)
{
case SFM_GENERATE_STEP_1: i = 1; break;
case SFM_GENERATE_STEP_2: i = 2; break;
case SFM_GENERATE_STEP_3: i = 3; break;
default: i = 0;break;
}
printf("SFM_CMD_GENERATE -> SFM_GENERATE_STEP_%d ERR_INVALID_BUFFER_ID Error! 检查ImageBuffer中图像的正确性错误!\r\n", i);
sfm_generate_t = SFM_GENERATE_STEP_IDLE;
error_flag = 1;
continue;
}
// 3. 将生成的Template保存于指定RamBuffer中并返回ERR_SUCCESS。
// 指纹登记
if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_ENROLL_FINGER)
{
switch (sfm_generate_t)
{
case SFM_GENERATE_STEP_IDLE:
{
if(frame.ret == ERR_SUCCESS)
{
// 采集第二次
// 等待手指抬起
// 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_ADD_SCREEN);
xEventGroupSetBits(ui_event_finger_add_group, UI_EVT_FINGER_ADD_MOVE);
lv_obj_update_layout(guider_ui.screen_home);
ret = SFM_Wait_up_finger();
if (ret != 0)
{
continue;
}
SFM_Get_Image(SFM_EVENT_ENROLL_FINGER);
// 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_ADD_SCREEN);
xEventGroupSetBits(ui_event_finger_add_group, UI_EVT_FINGER_ADD_SECOND);
lv_obj_update_layout(guider_ui.screen_home);
sfm_generate_t = SFM_GENERATE_STEP_1;
}
} break;
case SFM_GENERATE_STEP_1:
{
if(frame.ret == ERR_SUCCESS)
{
// 采集第三次
// 等待手指抬起
// 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_ADD_SCREEN);
xEventGroupSetBits(ui_event_finger_add_group, UI_EVT_FINGER_ADD_MOVE);
ret = SFM_Wait_up_finger();
if (ret != 0)
{
continue;
}
SFM_Get_Image(SFM_EVENT_ENROLL_FINGER);
// 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_ADD_SCREEN);
xEventGroupSetBits(ui_event_finger_add_group, UI_EVT_FINGER_ADD_THIRD);
sfm_generate_t = SFM_GENERATE_STEP_2;
}
} break;
case SFM_GENERATE_STEP_2:
{
if(frame.ret == ERR_SUCCESS)
{
// 3 次指纹已经录入成功
sfm_generate_t = SFM_GENERATE_STEP_IDLE;
// 融合多个模板为一个
printf("To SFM_Merge(3)\r\n");
SFM_Merge(SFM_EVENT_ENROLL_FINGER, 3);
}
} break;
default:
{
printf("SFM_CMD_GENERATE Default Error! sfm_generate_t 状态出错!\r\n");
sfm_generate_t = SFM_GENERATE_STEP_IDLE;
error_flag = 1;
continue;
} break;
}
}
// 指纹识别
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_INENTIFY_FINGER)
{
// 指定编号范围内的 1:N 识别
SFM_Search(SFM_EVENT_INENTIFY_FINGER, 0, 1, 500); // 该模块式 1 ~ 500
}
// 指纹查询单个
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_SEATCH_SINGLE_FINGER)
{
// 指定编号范围内的 1:N 识别
SFM_Search(SFM_EVENT_SEATCH_SINGLE_FINGER, 0, 1, 500); // 该模块式 1 ~ 500
}
} break;
/* 融合多个模板为一个 */
case SFM_CMD_MERGE:
{
// BufferID 值不正确
if(frame.ret == ERR_INVALID_BUFFER_ID)
{
printf("SFM_CMD_MERGE RET Error! ERR_INVALID_BUFFER_ID ! BufferID 值不正确!\r\n");
error_flag = 1;
continue;
}
// 指纹合成个数无效
if(frame.ret == ERR_GEN_COUNT)
{
printf("SFM_CMD_MERGE RET Error! ERR_GEN_COUNT ! 指纹合成个数无效 !\r\n");
error_flag = 1;
continue;
}
// Template合成失败
if(frame.ret == ERR_MERGE_FAIL)
{
printf("SFM_CMD_MERGE RET Error! ERR_MERGE_FAIL ! Template合成失败!\r\n");
error_flag = 1;
continue;
}
// 融合成功
if(frame.ret == ERR_SUCCESS)
{
// 指纹登记
if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_ENROLL_FINGER)
{
printf("SFM_CMD_MERGE RET Success!\r\n");
// 指定编号范围内的 1:N 识别
// 注意:这里需要使用Ram Buffer[1], 因为0已经是融合后的模板了,这个模板不能使用
SFM_Search(SFM_EVENT_ENROLL_FINGER, 1, 1, 500); // 该模块式 1 ~ 500
}
}
} break;
/* 指定编号范围内的 1:N 识别 */
case SFM_CMD_SEARCH:
{
// 指定 Ram Buffer 编号无效 或者 指定搜索范围无效
if(frame.ret == ERR_INVALID_BUFFER_ID )
{
printf("SFM_CMD_SEARCH RET Error! ERR_INVALID_BUFFER_ID ! 指定 Ram Buffer 编号无效 或者 指定搜索范围无效 !\r\n");
error_flag = 1;
finger_status = SFM_STATE_IDLE;
continue;
}
// 指纹登记
if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_ENROLL_FINGER)
{
// 搜索出来了(表示已经注册了)
if(frame.ret == ERR_SUCCESS)
{
printf("SFM_CMD_SEARCH RET Error! This is Registered! 搜索出来了(表示已经注册了) !\r\n");
error_flag = 1;
// 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_ADD_SCREEN);
xEventGroupSetBits(ui_event_finger_add_group, UI_EVT_FINGER_ADD_ALREADY_REG);
continue;
}
// 没有已注册 Template (为空),未搜索出来结果,继续登记
if(frame.ret == ERR_ALL_TMPL_EMPTY || frame.ret == ERR_IDENTIFY)
{
printf("SFM_CMD_SEARCH RET Success!\r\n");
// 获取指定范围首个可注册ID
SFM_Get_empty_id(SFM_EVENT_ENROLL_FINGER);
}
}
// 指纹识别
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_INENTIFY_FINGER)
{
// 没有搜索出来了(表示没有注册),指纹识别失败
if(frame.ret == ERR_ALL_TMPL_EMPTY || frame.ret == ERR_IDENTIFY)
{
printf("SFM_CMD_SEARCH RET Error! 没有该指纹信息!\r\n");
error_flag = 1;
finger_status = SFM_STATE_IDLE;
continue;
}
// 搜索出来了(表示识别成功)
if(frame.ret == ERR_SUCCESS)
{
printf("SFM_CMD_SEARCH RET Success! 搜索出来了(表示识别成功) !\r\n");
// 打印注册信息
uint16_t id = frame.data[0] | (frame.data[1] << 8);
printf("Registered ID == %4X\r\n", id);
// 识别已经完成
// 添加日志
add_log(UNLOCK_TYPE_FINGER, id);
lock_open();
finger_status = SFM_STATE_IDLE;
}
}
// 指纹单个查找
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_SEATCH_SINGLE_FINGER)
{
// 没有该指纹信息
if(frame.ret == ERR_ALL_TMPL_EMPTY || frame.ret == ERR_IDENTIFY)
{
printf("SFM_CMD_SEARCH RET Success! 没有该指纹信息 !\r\n");
// 没有该指纹信息
// 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_SEARCH_SCREEN);
xEventGroupSetBits(ui_event_finger_search_group, UI_EVT_FINGER_SEARCH_NO_DATA);
success_flag = 1;
}
// 有该指纹信息
if(frame.ret == ERR_SUCCESS)
{
printf("SFM_CMD_SEARCH RET Success! 有该指纹信息 !\r\n");
// 打印注册信息
uint16_t id = frame.data[0] | (frame.data[1] << 8);
printf("Registered ID == %d\r\n", id);
// 有指纹信息
// 通知 UI 更新
// 解析DATA
xQueueSend(ui_event_ui_finger_search_queue, &id , 0);
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_SEARCH_SCREEN);
xEventGroupSetBits(ui_event_finger_search_group, UI_EVT_FINGER_SEARCH_SINGLE_SUCCESS);
success_flag = 1;
}
}
} break;
/* 获取指定范围首个可注册ID */
case SFM_CMD_GET_EMPTY_ID:
{
if(frame.ret != ERR_SUCCESS)
{
printf("SFM_CMD_GET_EMPTY_ID RET Error!\r\n");
error_flag = 1;
continue;
}
// 成功:4
if (frame.len == 4)
{
printf("SFM_CMD_GET_EMPTY_ID LEN == 4 Success!\r\n");
// 指纹登记
if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_ENROLL_FINGER)
{
// 将获取到ID 用于 保存指纹模板数据到模块指纹库
uint32_t template_code = (uint32_t)(frame.data[0] | frame.data[1] << 8);
printf("template_code == %u", template_code);
SFM_Store_Char(SFM_EVENT_ENROLL_FINGER, template_code, 0); // 融合后模板存储到RamBuffer 0中
}
}
else if (frame.len == 2) // 失败:2
{
printf("SFM_CMD_GET_EMPTY_ID LEN == 2 Error!\r\n");
error_flag = 1;
continue;
}
} break;
/* 保存模板到指纹库 */
case SFM_CMD_STORE_CHAR:
{
// 指定Template编号无效
if(frame.ret == ERR_INVALID_TMPL_NO)
{
printf("SFM_CMD_STORE_CHAR ERR_INVALID_TMPL_NO Error! 指定Template编号无效 !\r\n");
error_flag = 1;
continue;
}
// 指定RamBuffer编号无效
if(frame.ret == ERR_INVALID_BUFFER_ID)
{
printf("SFM_CMD_STORE_CHAR ERR_INVALID_BUFFER_ID Error! 指定RamBuffer编号无效 !\r\n");
error_flag = 1;
continue;
}
// 该指纹已注册
if(frame.ret == ERR_DUPLICATION_ID)
{
printf("SFM_CMD_STORE_CHAR ERR_DUPLICATION_ID Error! 该指纹已注册 !\r\n");
// DATA返回比对成功的Template编号
uint32_t already_register_id = (uint32_t)(frame.data[0] | frame.data[1] << 8);
printf("template_code == %u", already_register_id);
error_flag = 1;
// 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_ADD_SCREEN);
xEventGroupSetBits(ui_event_finger_add_group, UI_EVT_FINGER_ADD_ALREADY_REG);
continue;
}
// 注册成功
if(frame.ret == ERR_SUCCESS)
{
// 指纹登记
if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_ENROLL_FINGER)
{
printf("SFM_CMD_STORE_CHAR ERR_SUCCESS Success!\r\n");
SFM_PrintData_Int(&frame);
// 注册成功
// 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_ADD_SCREEN);
xEventGroupSetBits(ui_event_finger_add_group, UI_EVT_FINGER_ADD_SUCCESS);
}
}
} break;
/* 获取全部已注册ID列表 */
case SFM_CMD_GET_ENROLLED_ID_LIST:
{
// 注册成功
if(frame.ret == ERR_SUCCESS)
{
// 按钮获取全部ID
if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_SEATCH_ALL_FINGER)
{
printf("SFM_CMD_GET_ENROLLED_ID_LIST ERR_SUCCESS Success!\r\n");
// 等待响应数据包
if(xQueueReceive(sfm_frame_queue, &frame, pdMS_TO_TICKS(2000)) == pdPASS)
{
if(frame.ret != ERR_SUCCESS)
{
printf("SFM_CMD_GET_ENROLLED_ID_LIST Error!\r\n");
error_flag = 1;
continue;
}
// 解析数据 DATA
uint8_t search_buf[42];
for (int i = 0; i < (frame.len - 2) && i < 500; i++)
{
search_buf[i] = frame.data[i];
printf("%X ", search_buf[i]);
}
printf("\r\n");
xQueueSend(ui_event_ui_finger_search_queue, search_buf, 0);
// // 注册成功
// // 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_SEARCH_SCREEN);
xEventGroupSetBits(ui_event_finger_search_group, UI_EVT_FINGER_SEARCH_ALL_SUCCESS);
printf("SFM_CMD_GET_ENROLLED_ID_LIST Success!\r\n");
success_flag = 1;
}
else
{
printf("等待响应数据包失败 命令为 %04X!\r\n" , sfm_cmd_event_t.sfm_cmd_e);
error_flag = 1;
}
}
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_DELETE_SEARCH_FINGER)
{
printf("SFM_CMD_GET_ENROLLED_ID_LIST ERR_SUCCESS Success!\r\n");
// 等待响应数据包
if(xQueueReceive(sfm_frame_queue, &frame, pdMS_TO_TICKS(2000)) == pdPASS)
{
if(frame.ret != ERR_SUCCESS)
{
printf("SFM_CMD_GET_ENROLLED_ID_LIST Error!\r\n");
error_flag = 1;
continue;
}
// 解析数据 DATA
uint8_t search_buf[42];
for (int i = 0; i < (frame.len - 2) && i < 500; i++)
{
search_buf[i] = frame.data[i];
printf("%X ", search_buf[i]);
}
printf("\r\n");
xQueueSend(ui_event_ui_finger_delete_queue, search_buf, 0);
// // 注册成功
// // 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_DELETE_SCREEN);
xEventGroupSetBits(ui_event_finger_delete_group, UI_EVT_FINGER_DELETE_SEARCH);
printf("SFM_CMD_GET_ENROLLED_ID_LIST SFM_EVENT_DELETE_SEARCH_FINGER Success!\r\n");
}
}
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_DELETE_SEARCH_AGAIN_FINGER)
{
printf("SFM_CMD_GET_ENROLLED_ID_LIST ERR_SUCCESS Success!\r\n");
// 等待响应数据包
if(xQueueReceive(sfm_frame_queue, &frame, pdMS_TO_TICKS(2000)) == pdPASS)
{
if(frame.ret != ERR_SUCCESS)
{
printf("SFM_CMD_GET_ENROLLED_ID_LIST Error!\r\n");
error_flag = 1;
continue;
}
// 解析数据 DATA
uint8_t search_buf[42];
for (int i = 0; i < (frame.len - 2) && i < 500; i++)
{
search_buf[i] = frame.data[i];
printf("%X ", search_buf[i]);
}
printf("\r\n");
xQueueSend(ui_event_ui_finger_delete_queue, search_buf, 0);
// // 注册成功
// // 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_DELETE_SCREEN);
xEventGroupSetBits(ui_event_finger_delete_group, UI_EVT_FINGER_DELETE_SEARCH_AGAIN);
printf("SFM_CMD_GET_ENROLLED_ID_LIST SFM_EVENT_DELETE_SEARCH_AGAIN_FINGER Success!\r\n");
}
else
{
printf("等待响应数据包失败 命令为 %04X!\r\n" , sfm_cmd_event_t.sfm_cmd_e);
}
}
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_BIND_SEARCH)
{
printf("SFM_CMD_GET_ENROLLED_ID_LIST ERR_SUCCESS Success!\r\n");
// 等待响应数据包
if(xQueueReceive(sfm_frame_queue, &frame, pdMS_TO_TICKS(2000)) == pdPASS)
{
if(frame.ret != ERR_SUCCESS)
{
printf("SFM_CMD_GET_ENROLLED_ID_LIST Error!\r\n");
error_flag = 1;
continue;
}
// 解析数据 DATA
uint8_t search_buf[42];
for (int i = 0; i < (frame.len - 2) && i < 500; i++)
{
search_buf[i] = frame.data[i];
printf("%X ", search_buf[i]);
}
printf("\r\n");
xQueueSend(ui_event_ui_finger_bind_queue, search_buf, 0);
// // 注册成功
// // 通知 UI 更新
xEventGroupSetBits(ui_event_group, UI_EVT_BIND_SCREEN);
xEventGroupSetBits(ui_event_bind_group, UI_EVT_BIND_SEARCH_SUCCESS);
printf("SFM_CMD_GET_ENROLLED_ID_LIST SFM_EVENT_BIND_SEARCH Success!\r\n");
}
}
}
} break;
/* 删除指定编号范围内的指纹 */
case SFM_CMD_DEL_CHAR:
{
// 指定范围无效
if(frame.ret == ERR_INVALID_PARAM)
{
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_DELETE_SCREEN);
xEventGroupSetBits(ui_event_finger_delete_group, UI_EVT_FINGER_DELETE_ERROR_RANGE_NO_LAW);
}
// 指定删除
if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_DELETE_SINGLE_FINGER)
{
// 指定范围内没有注册Template
if(frame.ret == ERR_TMPL_EMPTY)
{
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_DELETE_SCREEN);
xEventGroupSetBits(ui_event_finger_delete_group, UI_EVT_FINGER_DELETE_ERROR_RANGE_NO_REGISTER);
}
// 删除成功
if(frame.ret == ERR_SUCCESS)
{
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_DELETE_SCREEN);
xEventGroupSetBits(ui_event_finger_delete_group, UI_EVT_FINGER_DELETE_SINGLE_SUCCESS);
// 重新执行查询
// 指纹查找
SFM_Delete_search_again();
}
}
// 删除全部
else if (sfm_cmd_event_t.sfm_event_e == SFM_EVENT_DELETE_ALL_FINGER)
{
// 删除成功
if(frame.ret == ERR_TMPL_EMPTY || frame.ret == ERR_SUCCESS)
{
xEventGroupSetBits(ui_event_group, UI_EVT_FINGER_DELETE_SCREEN);
xEventGroupSetBits(ui_event_finger_delete_group, UI_EVT_FINGER_DELETE_ALL_SUCCESS);
}
}
} break;
default:
{
printf("SFM Error status!\r\n");
error_flag = 1;
continue;
} break;
}
}
else
{
printf("等待响应包失败 命令为 %04X!\r\n" , sfm_cmd_event_t.sfm_cmd_e);
error_flag = 1;
continue;
}
}
vTaskDelay(pdMS_TO_TICKS(20));
}
}
指纹模块相关函数
sfm_1016.c
c
#include "sfm_1016c.h"
/* 信号量 */
QueueHandle_t sfm_cmd_event_queue = NULL; // 串口传递数据
QueueHandle_t sfm_frame_queue = NULL; // 命令
QueueHandle_t sfm_byte_queue = NULL; // 命令
/**
* @brief 初始化 指纹模块 SFM 1016C
* @note None
* @param None
* @retval None
*/
void SFM_Init(uint32_t baud_rate)
{
sfm_cmd_event_queue = xQueueCreate(5, sizeof(SFM_CmdEvent_t));
sfm_frame_queue = xQueueCreate(2, sizeof(SFM_Frame_Resp_t));
sfm_byte_queue = xQueueCreate(128, sizeof(uint8_t));
SFM_GPIO_Init();
UART2_Init(baud_rate);
}
/**
* @brief 初始化 指纹模块GPIO SFM 1016C
* @note IRQ/WAKEUP PB7
* @param None
* @retval None
*/
void SFM_GPIO_Init(void)
{
// 0、GPIO片内外设信息初始化结构体
GPIO_InitTypeDef GPIO_InitStructure;
// 1、使能GPIO片内外设的硬件时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
// 2、配置GPIO片内外设的引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; // 引脚:第7根引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; // 模式:输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed; // 速度:高速(100MHz)
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 上下拉:不拉
GPIO_Init(GPIOB, &GPIO_InitStructure); // 使用该函数,将配置的信息写入到相应的寄存器中
}
/* ----------------------------- 封装指令函数 -----------------------------*/
/**
* @brief 指纹模块 指纹登记
* @note None
* @param None
* @retval None
*/
void SFM_Enroll()
{
SFM_Get_Image(SFM_EVENT_ENROLL_FINGER);
}
/**
* @brief 指纹模块 指纹识别
* @note None
* @param None
* @retval None
*/
void SFM_Identify(void)
{
SFM_Get_Image(SFM_EVENT_INENTIFY_FINGER);
}
/**
* @brief 指纹模块 指纹查找单个
* @note None
* @param None
* @retval None
*/
void SFM_Search_single(void)
{
SFM_Get_Image(SFM_EVENT_SEATCH_SINGLE_FINGER);
}
/**
* @brief 指纹模块 查找界面 指纹查找全部
* @note None
* @param None
* @retval None
*/
void SFM_Search_all(void)
{
SFM_Get_enrolled_id_list(SFM_EVENT_SEATCH_ALL_FINGER);
}
/**
* @brief 指纹模块 删除界面 指纹查找全部
* @note None
* @param None
* @retval None
*/
void SFM_Delete_search(void)
{
SFM_Get_enrolled_id_list(SFM_EVENT_DELETE_SEARCH_FINGER);
}
/**
* @brief 指纹模块 绑定界面 指纹查找
* @note None
* @param None
* @retval None
*/
void SFM_Bind_search(void)
{
SFM_Get_enrolled_id_list(SFM_EVENT_BIND_SEARCH);
}
/**
* @brief 指纹模块 删除界面 指纹再次查找全部
* @note None
* @param None
* @retval None
*/
void SFM_Delete_search_again(void)
{
SFM_Get_enrolled_id_list(SFM_EVENT_DELETE_SEARCH_AGAIN_FINGER);
}
/**
* @brief 指纹模块 删除界面 删除指定指纹
* @note None
* @param id : 指纹ID
* @retval None
*/
void SFM_Delete_single(uint16_t id)
{
SFM_Del_char(SFM_EVENT_DELETE_SINGLE_FINGER, id, id);
}
/**
* @brief 指纹模块 删除界面 删除全部指纹
* @note 删除全部, 指纹库 1~500
* @param id : 指纹ID
* @retval None
*/
void SFM_Delete_all(void)
{
SFM_Del_char(SFM_EVENT_DELETE_ALL_FINGER, 1, 500);
}
/* ------------------------------- 指令函数 -------------------------------*/
/**
* @brief 指纹模块 测试
* @note None
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_test_connection(SFM_Event_e sfm_event_e)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX (0xAA55, 低字节在前)
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD 0x0001 (低字节在前)
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0001);
// 6~7: LEN 0x0000
send_buf[6] = 0x00;
send_buf[7] = 0x00;
// 8~23: DATA (len为0,填0)
memset(&send_buf[8], 0, 16);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_TEST_CONNECTION;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 读取设备信息
* @note None
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_Get_device_info(SFM_Event_e sfm_event_e)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0004);
// 6~7: LEN 0x0000
send_buf[6] = 0x00;
send_buf[7] = 0x00;
// 8~23: DATA (len为0,填0)
memset(&send_buf[8], 0, 16);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_DEVICE_INFO;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 获取第一个未注册的ID
* @note None
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_Get_empty_id(SFM_Event_e sfm_event_e)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0045);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0004);
// 8~23: DATA
// 起始Template编号 0x0001
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0001);
// 结束Template编号 0x01F4
SFM_OrderNum(&send_buf[10], &send_buf[11], 0x01F4);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GET_EMPTY_ID;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 采集指纹图像
* @note 从采集器采集指纹图像并保存于ImageBuffer中。
* @note 从采集器采集指纹图像。若采集图像正确,则返回ERR_SUCCESS。否则返回错误码。
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_Get_Image(SFM_Event_e sfm_event_e)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0020);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0000);
// 8~23: DATA
// 无
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GET_IMAGE;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 从暂存在ImageBuffer中的指纹图像产生模板
* @note 从ImageBuffer中的指纹图像产生指纹模板Template并保存于指定RamBuffer中。
* @param sfm_event_e: 传入的事件
* @param ram_buf_code: RamBuffer编号(0~2)
* @retval None
*/
void SFM_Generate(SFM_Event_e sfm_event_e, uint8_t ram_buf_code)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0060);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0000);
// 8~23: DATA
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000 + ram_buf_code);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GENERATE;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 合成指纹模板数据用于入库
* @note 将暂存在RamBuffer中的模板合并生成模板数据并后保存于指定的RamBuffer中,这里指定保存到RamBuffer[0]中。
* 合成个数可为2或3:
* 若为2:则合成RamBuffer0和RamBuffer1的Template。
* 若为3:则合成RamBuffer0、RamBuffer1和RamBuffer2的Template。
* @param sfm_event_e: 传入的事件
* @param method: 合成方式,只能传入2或3
* 2: 合成个数为2
* 3: 合成个数为3
* @retval None
*/
void SFM_Merge(SFM_Event_e sfm_event_e, uint8_t method)
{
if (method != 2 && method != 3)
return;
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0061);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0003);
// 8~23: DATA
// RamBuffer编号
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000);
// 合成个数(2/3)
SFM_OrderNum(&send_buf[10], &send_buf[11] , 0x0000 + method);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_MERGE;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 保存指纹模板数据到模块指纹库
* @note 指定Template编号无效,则返回错误码ERR_INVALID_TMPL_NO。
* 1. 若指定RamBuffer编号无效,则返回错误码ERR_INVALID_BUFFER_ID。
* 2. 若DuplicationCheck设置为OFF,则直接将指定RamBuffer中的指纹模板数据注册
* 于指定编号的指纹库中并返回其结果。
* 3. 若DuplicationCheck设置为ON,则将指定RamBuffer中的Template和已注册的
* 指纹库中的所有Template之间进行1:N比对。
* 4. 若存在比对成功的模板,说明该指纹已注册,则返回(RET):ERR_DUPLICATION_ID,
* 且DATA返回比对成功的Template编号。
* 否则,将该模板注册于指定Template编号的指纹库中并返回其结果。
* @param sfm_event_e : 传入的事件
* @param template_code : 指定的Template编号,存储到某个编号的指纹库中。
* @param ram_buf_code : 指定的RamBuffer中,这个RamBuffer存储的是指纹融合后的模板
* @retval None
*/
void SFM_Store_Char(SFM_Event_e sfm_event_e, uint8_t template_code , uint8_t ram_buf_code)
{
if (template_code < 0 || template_code >= 500) // 这个指纹模块最大容量是500
return;
if (ram_buf_code > 3 || ram_buf_code < 0) // RamBuffer 0 ~ 2
return;
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0040);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0003);
// 8~23: DATA
// 指定Template编号
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000 + template_code);
// 指定的RamBuffer
SFM_OrderNum(&send_buf[10], &send_buf[11] , 0x0000 + ram_buf_code);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_STORE_CHAR;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 指定编号范围内的 1:N 识别
* @note 指定Ram Buffer 中的模板与指定搜索范围(起始 Template 编号 ~ 结束 Template 编号)内的
* 所有已注册指纹 Template 之间进行 1:N 比对并返回其结果。
* @note 1. 若指定 Ram Buffer 编号无效,则返回错误码 ERR_INVALID_BUFFER_ID 。
* 2. 若指定搜索范围无效,则返回错误码 ERR_INVALID_BUFFER_ID 。
* 3. 若没有已注册 Template ,则返回错误码 ERR_ALL_TMPL_EMPTY 。
* 4. 指定 Ram Buffer 中的 Template 与已注册的所有模板之间进行比对并返回其结果。
* 若搜索成功,则 RET 返回 ERR_SUCCESS 且在 DATA 域返回被搜索出的模板编号和智能
* 更新结果。否则,RET 返回 ERR_IDENTIFY 。
* @param sfm_event_e: 传入的事件
* @retval None
*/
void SFM_Search(SFM_Event_e sfm_event_e, uint8_t ram_buf_code, uint8_t template_start_code, uint8_t template_end_code)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0063);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0003);
// 8~23: DATA
// 指定Template编号
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000 + ram_buf_code);
// 指定的RamBuffer
SFM_OrderNum(&send_buf[10], &send_buf[11] , 0x0000 + template_start_code);
// 指定的RamBuffer
SFM_OrderNum(&send_buf[12], &send_buf[13] , 0x0000 + template_end_code);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_SEARCH;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 读取参数
* @note 根据指定的参数类型(ParameterType),获取模块(DeviceID,SecurityLevel,
* Baudrate,DuplicationCheck,AutoLearn)等的参数值。
* 有关ParameterType,请参考上述CMD_SET_PARAM。
* @param sfm_event_e : 传入的事件
* @param model : 指定的参数类型
* @retval None
*/
void SFM_Get_Dev_Val(SFM_Event_e sfm_event_e, uint8_t model)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0003);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0001);
// 8~23: DATA
// 指定的参数类型
SFM_OrderNum(&send_buf[8], &send_buf[9] , 0x0000 + model);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GET_PARAM;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 读取参数
* @note 根据指定的参数类型(ParameterType),设置设备参数(DeviceID,SecurityLevel,
* Baudrate,DuplicationCheck,AutoLearn,FPTimeOut)的值并返回其结果。
* @param sfm_event_e : 传入的事件
* @param model_type : 指定的参数类型
* @param model_val : 设置设备参数
* @retval None
*/
void SFM_Set_Dev_Val(SFM_Event_e sfm_event_e, uint8_t model_type, uint8_t model_val)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0002);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0005);
// 8~23: DATA
// 指定的参数类型
send_buf[8] = model_type;
// 设置设备参数
SFM_OrderNum(&send_buf[9], &send_buf[10] , 0x0000 + model_val);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_SET_PARAM;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 获取已注册ID列表
* @note 其ID列表信息结构如下:
* 每个字节的每个位表示第x(x=字节号(从0开始)*8+位号(从0开始))个编号的指
* 纹注册状态。
* 若为0,则表示没有注册。若为1,则表示已注册。
* 例如;假设ID列表信息的第二个字节为01000001(2进制),每个位的含义如下:
* 从右开始第0位(1) :8*2+0=16(第16编号中已注册指纹)
* 从右开始第1位(0):8*2+1=17(第17编号中没注册指纹)
* ...
* 从右开始第6位(1) :8*2+6=22(第22编号中已注册指纹)
* 从右开始第7位(0) :8*2+7=23(第23编号中没注册指纹)
* [工作Sequence]
* 1. 以指令应答包的形式将HOST待接收的ID列表信息的大小设为应答数据发送应答。
* 2. 以应答数据包发送模块中已注册ID列表信息。
* @param sfm_event_e : 传入的事件
* @retval None
*/
void SFM_Get_enrolled_id_list(SFM_Event_e sfm_event_e)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0049);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0005);
// 8~23: DATA
// 无
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_GET_ENROLLED_ID_LIST;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/**
* @brief 指纹模块 删除指定编号范围内的指纹
* @note 删除指定编号范围(起始Template编号~结束Template编号)内全部已注册的Template。
* @note 若指定范围无效,则返回ERR_INVALID_PARAM。
* 若指定范围内没有注册Template,则返回ERR_TMPL_EMPTY。
* 删除指定范围内已注册的所有Template并返回其结果。
* @param sfm_event_e : 传入的事件
* @param start_template : 起始Template编号
* @param end_template : 结束Template编号
* @retval None
*/
void SFM_Del_char(SFM_Event_e sfm_event_e, uint16_t start_template, uint16_t end_template)
{
uint8_t send_buf[26] = {0};
// 0~1: PREFIX
SFM_OrderNum(&send_buf[0], &send_buf[1], 0xAA55);
// 2: SID
send_buf[2] = 0x00;
// 3: DID
send_buf[3] = 0x00;
// 4~5: CMD
SFM_OrderNum(&send_buf[4], &send_buf[5], 0x0044);
// 6~7: LEN 0x0000
SFM_OrderNum(&send_buf[6], &send_buf[7], 0x0004);
// 8~23: DATA
// 起始Template编号
SFM_OrderNum(&send_buf[8], &send_buf[9], 0x0000 + start_template);
// 结束Template编号
SFM_OrderNum(&send_buf[10], &send_buf[11], 0x0000 + end_template);
// 24~25: CKS (校验和,从PREFIX到DATA的所有字节算术和最低2字节)
uint16_t cks = SFM_GetSumCKS(send_buf, 24);
SFM_OrderNum(&send_buf[24], &send_buf[25], cks);
SFM_CmdEvent_t sfm_cmd_event_t;
sfm_cmd_event_t.sfm_cmd_e = SFM_CMD_DEL_CHAR;
sfm_cmd_event_t.sfm_event_e = sfm_event_e;
xQueueSend(sfm_cmd_event_queue, &sfm_cmd_event_t, NULL);
// 发送命令包
SFM_SendCmdPacket(send_buf);
}
/* ------------------------------- 工具函数 -------------------------------*/
/**
* @brief 指纹模块 小端存储转换
* @note 严格遵循协议"低字节在前"规则,并存储到数组中
* @param prev 前一个存放的数组
* @param next 后一个存放的数组
* @param prev 16位需要小端存储转换数值(如0xAA55、0x55AA等协议规定值)
* @retval None
*/
void SFM_OrderNum(uint8_t* prev, uint8_t* next, uint16_t num)
{
*prev = ((num & 0x00FF) << 0);
*next = ((num & 0xFF00) >> 8);
}
/**
* @brief 指纹模块 获取校验码
* @note 严格遵循协议:校验和 = PREFIX到DATA域所有数据算术和的最低2字节
* @param buf :发送给的BUF
* @param len :发送给的BUF的长度
* @retval uint16_t :16位校验码(符合协议要求)
*/
uint16_t SFM_GetSumCKS(uint8_t *buf, uint16_t len)
{
uint32_t sum = 0;
for (uint16_t i = 0; i < len; i++)
{
sum += buf[i];
}
return (uint16_t)(sum & 0xFFFF);
}
/**
* @brief 指纹模块 获取校验码
* @note 严格遵循协议:校验和 = PREFIX到DATA域所有数据算术和的最低2字节
* @param buf :发送给的BUF
* @retval uint16_t :16位校验码(符合协议要求)
*/
uint16_t SFM_GetSumCKSByStruct(const SFM_Frame_Resp_t *frame)
{
uint32_t sum = 0;
/* PREFIX */
sum += (frame->prefix & 0xFF);
sum += (frame->prefix >> 8);
/* SID */
sum += (frame->sid & 0xFF);
sum += (frame->sid >> 8);
/* DID */
sum += (frame->did & 0xFF);
sum += (frame->did >> 8);
/* RCM */
sum += (frame->rcm & 0xFF);
sum += (frame->rcm >> 8);
/* LEN */
sum += (frame->len & 0xFF);
sum += (frame->len >> 8);
/* RET */
sum += (frame->ret & 0xFF);
sum += (frame->ret >> 8);
/* DATA */
for (uint16_t i = 0; i < (frame->len - 2); i++)
{
sum += frame->data[i];
}
/* 只取最低 16 位 */
return (uint16_t)(sum & 0xFFFF);
}
/**
* @brief 指纹模块 发送命令包
* @note None
* @param send_buf :发送给的BUF
* @retval None
*/
void SFM_SendCmdPacket(uint8_t* send_buf)
{
for(int i = 0; i < 26; i++)
{
USART_SendData(USART2, send_buf[i]);
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}
}
/**
* @brief 指纹模块 发送命令包
* @note None
* @param send_buf :发送给的BUF
* @retval None
*/
void SFM_PrintData_Char(const SFM_Frame_Resp_t *frame)
{
printf("data: ");
for (int i = 0; i < (frame->len - 2) && i < 500; i++)
{
printf("%c", frame->data[i]);
}
printf("\r\n");
}
/**
* @brief 指纹模块 发送命令包
* @note None
* @param send_buf :发送给的BUF
* @retval None
*/
void SFM_PrintData_Int(const SFM_Frame_Resp_t *frame)
{
printf("data: ");
for (int i = 0; i < (frame->len - 2) && i < 500; i++)
{
printf("%02X", frame->data[i]);
}
printf("\r\n");
}
/**
* @brief 指纹模块 等待手指离开模块(至少200ms稳定,每500ms提示一次)
* @note 防止模块误判
* @param None
* @retval 返回 0, 正常
* 返回 1, 超时
* 返回 2, 被打断
*/
int8_t SFM_Wait_up_finger(void)
{
uint32_t stable_time = 0;
uint32_t print_timer = 0;
uint32_t time_out_timer = 0;
const uint32_t check_period = 50; // 50ms 检查一次
const uint32_t stable_need = 500; // 500ms 稳定
const uint32_t print_period = 1000; // 1s 提示一次
const uint32_t time_out_period = 5000; // 5秒超时
finger_wait_get_image = 1;
while (1)
{
// 被打断跳出
if (finger_wait_get_image_interrupt == 1)
{
finger_wait_get_image_interrupt = 0;
finger_wait_get_image = 0;
printf("finger interrupt !\r\n");
return 2;
}
if (SFM_TOUCH == 0) // 手指离开
{
stable_time += check_period;
}
else
{
stable_time = 0;
}
print_timer += check_period;
if (print_timer >= print_period)
{
printf("SFM Please up your finger...!\r\n");
print_timer = 0;
}
time_out_timer += check_period;
if(time_out_timer >= time_out_period)
{
printf("SFM Time Out !\r\n");
finger_wait_get_image = 0;
return 1;
}
if (stable_time >= stable_need)
{
finger_wait_get_image = 0;
return 0;
}
vTaskDelay(pdMS_TO_TICKS(check_period)); // 每50ms检查一次
}
vTaskDelay(pdMS_TO_TICKS(50));
}
/**
* @brief 指纹模块 等待手指按下模块(至少200ms稳定,每500ms提示一次)
* @note 防止模块误判
* @param None
* @retval 返回 0, 正常
* 返回 1, 超时
* 返回 2, 被打断
*/
int8_t SFM_Wait_press_finger(void)
{
uint32_t stable_time = 0;
uint32_t print_timer = 0;
uint32_t time_out_timer = 0;
const uint32_t check_period = 50; // 50ms 检查一次
const uint32_t stable_need = 500; // 500ms 稳定
const uint32_t print_period = 1000; // 1s 提示一次
const uint32_t time_out_period = 5000; // 5秒超时
finger_wait_get_image = 1;
while (1)
{
// 被打断跳出
if (finger_wait_get_image_interrupt == 1)
{
finger_wait_get_image_interrupt = 0;
finger_wait_get_image = 0;
printf("finger interrupt !\r\n");
return 2;
}
if (SFM_TOUCH == 1)
{
stable_time += check_period;
}
else
{
stable_time = 0;
}
print_timer += check_period;
if (print_timer >= print_period)
{
printf("SFM Please place your finger...!\r\n");
print_timer = 0;
}
time_out_timer += check_period;
if(time_out_timer >= time_out_period)
{
printf("SFM Time Out !\r\n");
finger_wait_get_image = 0;
return 1;
}
if (stable_time >= stable_need)
{
finger_wait_get_image = 0;
return 0;
}
vTaskDelay(pdMS_TO_TICKS(check_period));
}
vTaskDelay(pdMS_TO_TICKS(50));
}
sfm_1016.h
c
#ifndef __SFM_1016C_H // 定义以防止递归包含
#define __SFM_1016C_H
// 一、其它头文件
#include "stm32f4xx.h"
#include "includes.h"
// 二、宏定义(函数、变量、常量)
#define SFM_ACK_SUCCESS 0x00 //执行成功
#define SFM_ACK_FAIL 0x01 //执行失败
#define SFM_ACK_FULL 0x04 //数据库满
#define SFM_ACK_NOUSER 0x05 //没有这个用户
#define SFM_ACK_USER_EXIST 0x07 //用户已存在
#define SFM_ACK_TIMEOUT 0x08 //图像采集超时
#define SFM_ACK_HARDWAREERROR 0x0A //硬件错误
#define SFM_ACK_IMAGEERROR 0x10 //图像错误
#define SFM_ACK_BREAK 0x18 //终止当前指令
#define SFM_ACK_ALGORITHMFAIL 0x11 //贴膜攻击检测
#define SFM_ACK_HOMOLOGYFAIL 0x12 //同源性校验错误
// SFM 响应包回复 错误码宏定义
#define ERR_SUCCESS 0x00 // 指令处理成功
#define ERR_FAIL 0x01 // 指令处理失败
#define ERR_VERIFY 0x10 // 与指定编号中Template的1:1比对失败
#define ERR_IDENTIFY 0x11 // 已进行1:N比对,但相同Template不存在
#define ERR_TMPL_EMPTY 0x12 // 在指定编号中不存在已注册的Template
#define ERR_TMPL_NOT_EMPTY 0x13 // 在指定编号中已存在Template
#define ERR_ALL_TMPL_EMPTY 0x14 // 不存在已注册的Template
#define ERR_EMPTY_ID_NOEXIST 0x15 // 不存在可注册的TemplateID
#define ERR_BROKEN_ID_NOEXIST 0x16 // 不存在已损坏的Template
#define ERR_INVALID_TMPL_DATA 0x17 // 指定的TemplateData无效
#define ERR_DUPLICATION_ID 0x18 // 该指纹已注册
#define ERR_BAD_QUALITY 0x19 // 指纹图像质量不好
#define ERR_MERGE_FAIL 0x1A // Template合成失败
#define ERR_NOT_AUTHORIZED 0x1B // 没有进行通讯密码确认
#define ERR_MEMORY 0x1C // 外部Flash烧写出错
#define ERR_INVALID_TMPL_NO 0x1D // 指定Template编号无效
#define ERR_INVALID_PARAM 0x22 // 使用了不正确的参数
#define ERR_TIME_OUT 0x23 // 在TimeOut时间内没有输入指纹
#define ERR_GEN_COUNT 0x25 // 指纹合成个数无效
#define ERR_INVALID_BUFFER_ID 0x26 // BufferID值不正确
#define ERR_FP_NOT_DETECTED 0x28 // 采集器上没有指纹输入
#define ERR_FP_CANCEL 0x41 // 指令被取消
// 参数索引
#define SFM_PARAM_DEVICE_ID 0 // 设备编号 DeviceID
#define SFM_PARAM_SECURITY_LEVEL 1 // 安全等级 SecurityLevel
#define SFM_PARAM_DUPLICATION_CHECK 2 // 指纹重复检查 DuplicationCheck
#define SFM_PARAM_BAUDRATE 3 // 波特率 Baudrate 索引
#define SFM_PARAM_AUTO_LEARN 4 // 指纹模板自学习 AutoLearn
#define SFM_PARAM_FP_TIMEOUT 5 // 采集指纹超时时间 FpTimeOut
// 手指感应唤醒信号输出,高电平输出, 有手指触碰就有高电平输出
#define SFM_TOUCH SFM_IRO
#define SFM_IRO PBin(7)
// 手指按下
#define PRESS 1
// 手指抬升
#define UP 0
// 三、自定义的数据类型(结构体、联合体、枚举等)
// 状态状态机枚举
typedef enum {
SFM_STATE_IDLE = 0, // 空闲状态,模块上电后未发送命令
SFM_STATE_BUSY, // 忙
} SFM_State_e;
// SFM 模块命令枚举
typedef enum {
// 命令状态机
SFM_CMD_IDLE = 0x0000, // 空闲命令
SFM_CMD_TEST_CONNECTION = 0x0001, // 连接测试
SFM_CMD_SET_PARAM = 0x0002, // 设置模块参数(设备ID/安全等级等)
SFM_CMD_GET_PARAM = 0x0003, // 读取模块参数
SFM_CMD_DEVICE_INFO = 0x0004, // 读取设备信息(算法/容量等)
SFM_CMD_ENTER_IAP_MODE = 0x0005, // 进入IAP固件更新模式
SFM_CMD_SET_MODULE_SN = 0x0008, // 设置模块序列号
SFM_CMD_GET_MODULE_SN = 0x0009, // 读取模块序列号
SFM_CMD_ENTER_STANDBY_STATE = 0x000C, // 进入休眠状态
// 指纹图像相关命令
SFM_CMD_GET_IMAGE = 0x0020, // 采集指纹图像(存入ImageBuffer)
SFM_CMD_FINGER_DETECT = 0x0021, // 检测手指有无
SFM_CMD_UP_IMAGE_CODE = 0x0022, // 上传指纹图像到主机
SFM_CMD_DOWN_IMAGE = 0x0023, // 下载指纹图像到模块
SFM_CMD_SLED_CTRL = 0x0024, // 控制采集器背光灯/LED
SFM_CMD_ADJUST_SENSOR = 0x0025, // 自动调整指纹传感器
// 指纹模板操作命令
SFM_CMD_STORE_CHAR = 0x0040, // 保存模板到指纹库
SFM_CMD_LOAD_CHAR = 0x0041, // 读取指纹库模板到RamBuffer
SFM_CMD_UP_CHAR = 0x0042, // 上传RamBuffer模板到主机
SFM_CMD_DOWN_CHAR = 0x0043, // 下载模板到RamBuffer
SFM_CMD_DEL_CHAR = 0x0044, // 删除指定范围指纹模板
SFM_CMD_GET_EMPTY_ID = 0x0045, // 获取指定范围首个可注册ID
SFM_CMD_GET_STATUS = 0x0046, // 检查指定ID是否已注册
SFM_CMD_GET_BROKEN_ID = 0x0047, // 检查指定范围模板是否损坏
SFM_CMD_GET_ENROLL_COUNT = 0x0048, // 获取指定范围已注册模板数
SFM_CMD_GET_ENROLLED_ID_LIST = 0x0049, // 获取已注册ID列表
// 模板生成与比对命令
SFM_CMD_GENERATE = 0x0060, // 从图像生成指纹模板
SFM_CMD_MERGE = 0x0061, // 融合多个模板为一个
SFM_CMD_MATCH = 0x0062, // 两个RamBuffer模板1:1比对
SFM_CMD_SEARCH = 0x0063, // RamBuffer模板与指纹库1:N识别
SFM_CMD_VERIFY = 0x0064, // RamBuffer模板与指定ID模板1:1比对
// 通讯错误响应(模块返回)
SFM_CMD_INCORRECT = 0x00FF // 接收错误命令
} SFM_Cmd_e;
// SFM 事件
typedef enum {
SFM_EVENT_IDLE= 0x0000, // 空闲事件
SFM_EVENT_IND, // 独立事件(单独一条指令)
SFM_EVENT_ENROLL_FINGER, // 登记指纹
SFM_EVENT_INENTIFY_FINGER, // 指纹识别
SFM_EVENT_SEATCH_SINGLE_FINGER, // 搜索界面 查找单个指纹
SFM_EVENT_SEATCH_ALL_FINGER, // 搜索界面 查找全部指纹信息
SFM_EVENT_DELETE_SEARCH_FINGER, // 删除界面 查找全部指纹信息
SFM_EVENT_DELETE_SINGLE_FINGER, // 删除界面 删除指定指纹
SFM_EVENT_DELETE_SEARCH_AGAIN_FINGER, // 删除界面 删除后再次查找
SFM_EVENT_DELETE_ALL_FINGER, // 删除界面 删除全部指纹
SFM_EVENT_BIND_SEARCH, // 绑定界面 搜索指纹
} SFM_Event_e;
typedef enum {
SFM_WAIT_PREFIX1, // 等 PREFIX 低字节
SFM_WAIT_PREFIX2, // 等 PREFIX 高字节
SFM_WAIT_SID, // 等 SID 字节 源标识
SFM_WAIT_DID, // 等 DID 字节 目标标识
SFM_WAIT_RCM1, // 等 RCM 低字节 响应码
SFM_WAIT_RCM2, // 等 RCM 高字节 响应码
SFM_WAIT_LEN1, // 等 LEN 低字节 结果接数据长度
SFM_WAIT_LEN2, // 等 LEN 高字节 结果接数据长度
SFM_WAIT_RET1, // 等 RET 低字节 结果码
SFM_WAIT_RET2, // 等 RET 高字节 结果码
SFM_WAIT_DATA, // 等 DATA 数据 响应数据
SFM_WAIT_CKS1, // 等 CKS 低字节 校验和
SFM_WAIT_CKS2 // 等 CKS 高字节 校验和
} SFM_ParseState_e;
// 发送的命令包 封装 指令 + 事件
typedef struct SFM_CmdEvent {
SFM_Cmd_e sfm_cmd_e; // 指令
SFM_Event_e sfm_event_e; // 事件
} SFM_CmdEvent_t;
typedef struct SFM_Frame_Resp {
uint16_t prefix;
uint16_t sid;
uint16_t did;
uint16_t rcm;
uint16_t len;
uint16_t ret;
uint8_t data[500];
uint16_t cks;
} SFM_Frame_Resp_t;
typedef enum {
SFM_Packet_Empty, // 空包
SFM_Packet_Command, // 命令包
SFM_Packet_Response, // 响应包
SFM_Packet_Response_Data, // 指令/响应的数据包
} SFM_Packet_e;
typedef enum
{
SFM_GENERATE_STEP_IDLE = 0,
SFM_GENERATE_STEP_1,
SFM_GENERATE_STEP_2,
SFM_GENERATE_STEP_3,
SFM_GENERATE_STEP_DONE,
SFM_GENERATE_STEP_ERROR
} SFM_GenerateStep_t;
// 四、全局变量声明
/* 消息队列 */
typedef void * QueueHandle_t;
typedef QueueHandle_t SemaphoreHandle_t;
extern QueueHandle_t sfm_cmd_event_queue;
extern QueueHandle_t sfm_frame_queue;
extern QueueHandle_t sfm_byte_queue;
// 五、函数声明
void SFM_Init(uint32_t baud_rate);
void SFM_GPIO_Init(void);
// 事物函数
void SFM_Enroll(void);
void SFM_Identify(void);
void SFM_Search_single(void);
void SFM_Search_all(void);
void SFM_Delete_search(void);
void SFM_Delete_single(uint16_t id);
void SFM_Delete_search_again(void);
void SFM_Delete_all(void);
void SFM_Bind_search(void);
// 命令函数
void SFM_test_connection(SFM_Event_e sfm_event_e);
void SFM_Get_device_info(SFM_Event_e sfm_event_e);
void SFM_Get_empty_id(SFM_Event_e sfm_event_e);
void SFM_Get_Image(SFM_Event_e sfm_event_e);
void SFM_Generate(SFM_Event_e sfm_event_e, uint8_t ram_buf_code);
void SFM_Merge(SFM_Event_e sfm_event_e, uint8_t method);
void SFM_Store_Char(SFM_Event_e sfm_event_e, uint8_t template_code , uint8_t ram_buf_code);
void SFM_Search(SFM_Event_e sfm_event_e, uint8_t ram_buf_code, uint8_t template_start_code, uint8_t template_end_code);
void SFM_Get_Dev_Val(SFM_Event_e sfm_event_e, uint8_t model);
void SFM_Set_Dev_Val(SFM_Event_e sfm_event_e, uint8_t model_type, uint8_t model_val);
void SFM_Get_enrolled_id_list(SFM_Event_e sfm_event_e);
void SFM_Del_char(SFM_Event_e sfm_event_e, uint16_t start_template, uint16_t end_template);
// 工具函数
void SFM_OrderNum(uint8_t* prev, uint8_t* next, uint16_t num);
uint16_t SFM_GetSumCKS(uint8_t *buf, uint16_t len);
uint16_t SFM_GetSumCKSByStruct(const SFM_Frame_Resp_t *frame);
void SFM_SendCmdPacket(uint8_t* send_buf);
void SFM_PrintData_Char(const SFM_Frame_Resp_t *frame);
void SFM_PrintData_Int(const SFM_Frame_Resp_t *frame);
int8_t SFM_Wait_up_finger(void);
int8_t SFM_Wait_press_finger(void);
// 六、静态变量、函数定义
#endif /* __SFM_1016C_H */
注:以上均为我个人学习笔记,资料参考指纹模块的 《ID三能指纹模块通讯协议手册202005第三版.PDF》。最后提供的代码参考,是博主自己手搓的,可能会有一些bug,只是提供参考 OwO。