三能(SANNENG)指纹模块通信与应用实战

三能(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(从机):指纹模块

基本流程:

  1. Host 发送 命令包(Command Packet)
  2. 模块解析命令
  3. 模块返回 响应包(Response Packet)
  4. 若涉及大数据,再进入 数据包交互阶段

四、通信数据包结构详解

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章节的登记流程,内附了各流程步骤用到的每条指令,即执行如下几条指令:

  1. 执行{采集指纹图像(CMD_GET_IMAGE)
  2. 转化成特征模板(CMD_GENERATE) (采集和转化特征模板共同需要执行3次)
  3. 指定编号范围内的 1:N 识别(CMD_SEARCH 0x0063) (可选,下面会说明)
  4. 融合指纹模板 (CMD_MERGE)
  5. 保存指纹模板(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);
}

概述:

  • 名字:指定编号范围内的 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);
}

概述:

  • 名字:指定编号范围内的 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。

相关推荐
不做无法实现的梦~14 小时前
ros2实现路径规划---nav2部分
linux·stm32·嵌入式硬件·机器人·自动驾驶
熊猫_豆豆18 小时前
同步整流 Buck 降压变换器
单片机·嵌入式硬件·matlab
chenchen000000001 天前
49元能否买到四核性能?HZ-RK3506G2_MiniEVM开发板评测:MCU+三核CPU带来的超高性价比
单片机·嵌入式硬件
孤芳剑影1 天前
反馈环路设计总结
嵌入式硬件·学习
dump linux1 天前
设备树子系统与驱动开发入门
linux·驱动开发·嵌入式硬件
专注VB编程开发20年1 天前
简易虚拟 PLC 服务器-流水线自动化,上位机程序维护升级,西门子PLC仿真
服务器·单片机·自动化·上位机·plc·流水线·工控
LeoZY_1 天前
CH347/339W开源项目:集SPI、I2C、JTAG、SWD、UART、GPIO多功能为一体(3)
stm32·单片机·嵌入式硬件·mcu·开源
chenchen000000001 天前
国产显示芯势力新篇章:内置DDR+四核A35!MY-SSD2351-MINI开发板深度评测
驱动开发·嵌入式硬件
BackCatK Chen1 天前
第13篇:TMC2240 StallGuard4失速检测|寄存器配置+状态读取(保姆级)
单片机·嵌入式硬件·tmc2240·stm32实战·stallguard4·失速检测·电机故障识别
Hello_Embed1 天前
libmodbus STM32 板载串口实验(双串口主从通信)
笔记·stm32·单片机·学习·modbus