发送应答

数据包不能单独进入执行流程,必须跟在指令包或应答包后面。
◆ 下传或上传的数据包格式相同。
◆ 包长度 = 包长度至校验和(指令、参数或数据)的总字节数,包含校验和,但不包含包长
度本身的字节数。
◆ 校验和是从包标识至校验和之间所有字节之和,包含包标识不包含校验和,超出 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;
}

更多推荐