【esp32s3】ZW101指纹模块
·
发送应答
数据包不能单独进入执行流程,必须跟在指令包或应答包后面。
◆ 下传或上传的数据包格式相同。
◆ 包长度 = 包长度至校验和(指令、参数或数据)的总字节数,包含校验和,但不包含包长
度本身的字节数。
◆ 校验和是从包标识至校验和之间所有字节之和,包含包标识不包含校验和,超出 2 字节的进
位忽略。
◆ 设备地址在没有生成之前为缺省的 0xffffffff,一旦主控通过指令生成了设备地址,则所有的
数据包都必须按照生成的地址收发。
◆ 对于多字节的高字节在前低字节在后(如 2bytes 的 00 06 表示 0006,而不是 0600)


/**
* @brief 发送指令包
* 帧格式: [Header(2)] [Addr(4)] [Type(1)] [Len(2)] [Cmd(1)] [Data(N)] [Checksum(2)]
*/
static esp_err_t fp_send_cmd(uint8_t cmd, const uint8_t *data, uint16_t data_len)
{
uint16_t pkg_len = data_len + 3; // 指令(1) + 校验和(2)
uint8_t buf[256];
uint16_t idx = 0;
// Header
buf[idx++] = FP_HEADER_H;
buf[idx++] = FP_HEADER_L;
// Module Address
buf[idx++] = (FP_MODULE_ADDR >> 24) & 0xFF;
buf[idx++] = (FP_MODULE_ADDR >> 16) & 0xFF;
buf[idx++] = (FP_MODULE_ADDR >> 8) & 0xFF;
buf[idx++] = FP_MODULE_ADDR & 0xFF;
// Package Type & Length
buf[idx++] = FP_PKG_CMD;
buf[idx++] = (pkg_len >> 8) & 0xFF;
buf[idx++] = pkg_len & 0xFF;
// Command
buf[idx++] = cmd;
// Data Payload
if (data && data_len > 0)
{
memcpy(&buf[idx], data, data_len);
idx += data_len;
}
// Checksum
uint16_t sum = calc_checksum(FP_PKG_CMD, pkg_len, cmd, data, data_len);
buf[idx++] = (sum >> 8) & 0xFF;
buf[idx++] = sum & 0xFF;
int sent = uart_write_bytes(UART1_PORT, buf, idx);
if (sent != idx)
{
ESP_LOGE(TAG, "UART write failed: sent=%d expected=%d", sent, idx);
return ESP_FAIL;
}
return ESP_OK;
}
/**
* @brief 接收并验证应答包
* @param out_ack 输出确认码
* @param out_data 输出应答数据载荷
* @param out_len 输入缓冲区大小,输出实际数据长度
* @param timeout_ms 超时时间(毫秒)
*/
static esp_err_t fp_recv_ack(uint8_t *out_ack, uint8_t *out_data,
uint16_t *out_len, uint32_t timeout_ms)
{
uint8_t buf[256];
uint16_t received = 0;
TickType_t start = xTaskGetTickCount();
uint16_t frame_start = 0;
while ((xTaskGetTickCount() - start) < pdMS_TO_TICKS(timeout_ms))
{
int n = uart_read_bytes(UART1_PORT, &buf[received],
sizeof(buf) - received, pdMS_TO_TICKS(50));
if (n <= 0)
continue;
received += n;
// 在当前缓冲区内搜索包头
uint16_t hdr_pos = 0;
bool found = false;
while (hdr_pos + 1 < received)
{
if (buf[hdr_pos] == FP_HEADER_H && buf[hdr_pos + 1] == FP_HEADER_L)
{
found = true;
break;
}
hdr_pos++;
}
if (!found)
{
ESP_LOGE(TAG, "No valid header in %" PRIu16 " bytes", received);
return ESP_ERR_INVALID_RESPONSE;
}
// 找到了包头,后续所有解析都基于 hdr_pos 偏移,零拷贝
frame_start = hdr_pos;
// 最小帧头: Header(2)+Addr(4)+Type(1)+Len(2) = 9 字节才能解析 pkg_len
if (received < 9)
continue;
/*
包长度至校验和(指令、参数或数据)的总字节数,包含校验和,但不包含包长度本身的字节数。
*/
// 高位左移8位,低位直接或运算
uint16_t pkg_len = (buf[frame_start + 7] << 8) | buf[frame_start + 8]; // 包长度
uint16_t total = 9 + pkg_len; // 总长
// 防止溢出
if (total > sizeof(buf))
{
ESP_LOGE(TAG, "Frame too large: %" PRIu16 "", total);
return ESP_ERR_NO_MEM;
}
// 还没收完整帧,继续等
if (received < total)
continue;
// 验证包标识
if (buf[frame_start + 6] != FP_PKG_ACK)
{
ESP_LOGW(TAG, "Not ACK packet: 0x%02X", buf[frame_start + 6]);
return ESP_ERR_INVALID_RESPONSE;
}
/*
校验和是从包标识至校验和之间所有字节之和,包含包标识不包含校验和,超出2字节的进位忽略。
*/
// 校验 Checksum
uint16_t recv_sum = (buf[frame_start + total - 2] << 8) | buf[frame_start + total - 1];
uint16_t calc_sum = 0;
for (uint16_t i = frame_start + 6; i < frame_start + total - 2; i++)
{
calc_sum += buf[i];
}
if (calc_sum != recv_sum)
{
ESP_LOGW(TAG, "CRC mismatch: calc=0x%04X recv=0x%04X", calc_sum, recv_sum);
return ESP_ERR_INVALID_RESPONSE;
}
// 成功解析
*out_ack = buf[frame_start + 9];
uint16_t data_len = pkg_len - 3; // 去掉 确认码(1) + 校验和(2)
if (data_len > *out_len)
return ESP_ERR_NO_MEM;
if (data_len > 0 && out_data)
{
memcpy(out_data, &buf[frame_start + 10], data_len);
}
*out_len = data_len;
return ESP_OK;
}
ESP_LOGW(TAG, "Recv timeout after %" PRIu32 " ms, received %" PRIu16 " bytes",
timeout_ms, received);
return ESP_ERR_TIMEOUT;
}
/**
* @brief 封装"发送指令+等待应答"原子操作
*/
static esp_err_t fp_execute(uint8_t cmd, const uint8_t *tx_data, uint16_t tx_len,
uint8_t *rx_ack, uint8_t *rx_data, uint16_t *rx_len,
uint32_t timeout_ms)
{
esp_err_t ret = fp_send_cmd(cmd, tx_data, tx_len);
if (ret != ESP_OK)
return ret;
ret = fp_recv_ack(rx_ack, rx_data, rx_len, timeout_ms);
if (ret != ESP_OK)
return ret;
return ESP_OK;
}
指纹注册



esp_err_t fp_manual_enroll(uint16_t save_page)
{
uint8_t send_buf[8] = {0};
uint8_t recv_buf[32] = {0};
uint16_t recv_len = sizeof(recv_buf);
esp_err_t ret;
uint8_t ack;
// ========== 第一次采集指纹 ==========
ESP_LOGI(TAG, "手动注册:请第一次按压指纹");
while (1)
{
recv_len = sizeof(recv_buf);
ret = fp_execute(0x01, NULL, 0, &ack, recv_buf, &recv_len, 800);
if (ret == ESP_OK && ack == 0x00)
{
ESP_LOGI(TAG, "第一次采图成功");
break;
}
else if (ack == 0x02)
{ // 无手指
vTaskDelay(pdMS_TO_TICKS(200));
continue;
}
ESP_LOGE(TAG, "第一次采图失败 code:0x%02X", ack);
return ESP_FAIL;
}
// ========== 生成特征存入缓存1 ==========
send_buf[0] = 0x01; // 缓存1
ret = fp_execute(0x02, send_buf, 1, &ack, recv_buf, &recv_len, 800);
if (ret == ESP_OK && ack == 0x00)
{
ESP_LOGI(TAG, "第一次生成特征成功");
}
else
{
ESP_LOGE(TAG, "第一次生成特征失败 code:0x%02X", ack);
return ESP_FAIL;
}
// ========== 第二次采集指纹 ==========
ESP_LOGI(TAG, "手动注册:请第二次按压指纹");
while (1)
{
recv_len = sizeof(recv_buf);
ret = fp_execute(0x01, NULL, 0, &ack, recv_buf, &recv_len, 800);
if (ret == ESP_OK && ack == 0x00)
{
ESP_LOGI(TAG, "第二次采图成功");
break;
}
else if (ack == 0x02)
{ // 无手指
vTaskDelay(pdMS_TO_TICKS(200));
continue;
}
ESP_LOGE(TAG, "第二次采图失败 code:0x%02X", ack);
return ESP_FAIL;
}
// ========== 生成特征存入缓存2 ==========
send_buf[0] = 0x02; // 缓存2
ret = fp_execute(0x02, send_buf, 1, &ack, recv_buf, &recv_len, 800);
if (ret == ESP_OK && ack == 0x00)
{
ESP_LOGI(TAG, "第二次生成特征成功");
}
else
{
ESP_LOGE(TAG, "第二次生成特征失败 code:0x%02X", ack);
}
// ========== 合并两份特征生成完整模板 PS_RegModel 0x05 ==========
recv_len = sizeof(recv_buf);
ret = fp_execute(0x05, NULL, 0, &ack, recv_buf, &recv_len, 1000);
if (ret != ESP_OK || ack != 0x00)
{
ESP_LOGE(TAG, "合并模板失败 code:0x%02X", ack);
return ESP_FAIL;
}
ESP_LOGI(TAG, "特征合并完成");
// ========== 存储模板到指定ID PS_StoreChar 0x06 ==========
send_buf[0] = 0x01; // 使用缓存1的合并模板
send_buf[1] = (save_page >> 8) & 0xFF;
send_buf[2] = save_page & 0xFF;
recv_len = sizeof(recv_buf);
ret = fp_execute(0x06, send_buf, 3, &ack, recv_buf, &recv_len, 1000);
if (ret != ESP_OK || ack != 0x00)
{
ESP_LOGE(TAG, "存储指纹失败 code:0x%02X", ack);
return ESP_FAIL;
}
ESP_LOGI(TAG, "ID:%d 手动注册指纹成功", save_page);
return ESP_OK;
}
指纹搜索

esp_err_t fp_manual_verify(uint16_t *match_id, uint16_t *score)
{
uint8_t send_buf[8] = {0};
uint8_t recv_buf[32] = {0};
uint16_t recv_len;
esp_err_t ret;
uint8_t ack;
// 1、等待手指,采集图像
ESP_LOGI(TAG, "请放上指纹");
while(1)
{
recv_len = sizeof(recv_buf);
ret = fp_execute(0x01, NULL, 0, &ack, recv_buf, &recv_len, 800);
if(ret == ESP_OK && ack == 0x00) break;
if(ack != 0x02)
{
ESP_LOGE(TAG, "采图异常 0x%02X", ack);
return ESP_FAIL;
}
vTaskDelay(pdMS_TO_TICKS(200));
}
// 2、生成特征存入缓冲区1
send_buf[0] = 0x01;
recv_len = sizeof(recv_buf);
ret = fp_execute(0x02, send_buf, 1, &ack, recv_buf, &recv_len, 1000);
if(ret != ESP_OK || ack != 0x00)
{
ESP_LOGE(TAG, "生成特征失败");
return ESP_FAIL;
}
// 3、全库搜索:缓冲1,起始ID=1,搜索50个
send_buf[0] = 0x01;
send_buf[1] = 0x00; send_buf[2] = 0x01; // 起始1
send_buf[3] = 0x00; send_buf[4] = 0x32; // 数量50
recv_len = sizeof(recv_buf);
ret = fp_execute(0x04, send_buf, 5, &ack, recv_buf, &recv_len, 1000);
if(ret != ESP_OK) return ESP_FAIL;
for(int i=0; i<recv_len; i++)
{
ESP_LOGI(TAG, "搜索结果[%d]: 0x%02X", i, recv_buf[i]);
}
if(ack == 0x00)
{
*match_id = (recv_buf[0] << 8) | recv_buf[1];
*score = (recv_buf[2] << 8) | recv_buf[3];
ESP_LOGI(TAG, "比对成功!匹配ID:%d 匹配分数:%d", *match_id, *score);
return ESP_OK;
}
else if(ack == 0x09)
{
ESP_LOGW(TAG, "无匹配指纹");
return ESP_ERR_NOT_FOUND;
}
ESP_LOGE(TAG, "搜索失败 0x%02X", ack);
return ESP_FAIL;
}
测试代码
esp_err_t fingerprint_test(void)
{
//fp_manual_enroll(1);
uint16_t fp_id = 0;
uint16_t fp_score = 0;
esp_err_t res = fp_manual_verify(&fp_id, &fp_score);
if (res == ESP_OK)
{
// 匹配成功,执行开锁/授权
ESP_LOGI(TAG, "匹配成功 ID:%d 分数:%d", fp_id, fp_score);
}
else if(res == ESP_ERR_NOT_FOUND)
{
// 指纹未注册
ESP_LOGI(TAG, "匹配失败");
}
return ESP_OK;
}
更多推荐
所有评论(0)