基于STM32的指纹识别系统实现方案

基于STM32的指纹识别系统实现方案

一、系统架构与硬件选型

1.1 硬件连接表(以AS608光学指纹模块为例)

指纹模块引脚 STM32F103C8T6引脚 说明
VCC 3.3V / 5V AS608支持3.3V-5V,建议5V供电以保证LED亮度
GND GND 共地
TXD PA3 (USART2_RX) 模块发送,STM32接收
RXD PA2 (USART2_TX) 模块接收,STM32发送
Touch PA0 触摸感应输出(高电平有效,可选)

1.2 系统工作流程

[用户触摸] -> [STM32唤醒] -> [发送“采集指纹”指令] 
    -> [模块采集图像] -> [生成特征值] -> [搜索指纹库]
    -> [返回结果] -> [控制继电器/LED显示]

二、核心代码实现

2.1 指纹模块协议定义(fingerprint.h

#ifndef __FINGERPRINT_H
#define __FINGERPRINT_H

#include "stm32f10x.h"
#include <string.h>

// AS608 指令定义
#define FP_HEAD           0xEF01
#define FP_ADDR          0xFFFFFFFF

// 指令包标识
#define CMD_GENIMG       0x01  // 采集图像
#define CMD_GENCHAR      0x02  // 生成特征
#define CMD_MATCH        0x03  // 精确比对(1:1)
#define CMD_SEARCH       0x04  // 搜索指纹(1:N)
#define CMD_REG_MODEL    0x05  // 合并特征(生成模板)
#define CMD_STORE       0x06  // 储存模板
#define CMD_DELETE      0x0C  // 删除模板
#define CMD_UP_CHAR     0x08  // 上传特征
#define CMD_DOWN_CHAR   0x09  // 下载特征
#define CMD_UP_IMAGE    0x0A  // 上传图像
#define CMD_DOWN_IMAGE  0x0B  // 下载图像
#define CMD_EMPTY       0x0D  // 清空指纹库
#define CMD_READ_CONF   0x0E  // 读系统参数

// 确认码(返回值)
#define ACK_OK          0x00  // 指令执行完毕或OK
#define ACK_FAIL        0x01  // 数据包接收错误
#define ACK_NO_FINGER   0x02  // 没有手指
#define ACK_ENTRY_FAIL  0x03  // 录入指纹图像失败
#define ACK_DISORDER    0x06  // 指纹库满
#define ACK_NOT_FOUND   0x09  // 没搜索到

// 缓冲区定义
#define CHAR_BUFFER_A    0x01
#define CHAR_BUFFER_B    0x02

// 全局变量
extern uint8_t fp_rx_buffer[64];
extern uint8_t fp_tx_buffer[64];
extern uint8_t fp_rx_flag;

// 函数声明
void FP_Init(uint32_t baudrate);
void FP_SendCmd(uint8_t cmd, uint8_t *data, uint8_t len);
uint8_t FP_GetResponse(void);
uint8_t FP_GetImage(void);
uint8_t FP_GenChar(uint8_t buffer_id);
uint8_t FP_Search(uint8_t buffer_id, uint16_t start_page, uint16_t page_num);
uint8_t FP_Store(uint8_t buffer_id, uint16_t page_id);
uint8_t FP_Delete(uint16_t page_id, uint16_t num);
uint8_t FP_Empty(void);
void FP_ShowSystemParams(void);

#endif

2.2 串口驱动与指令发送(fingerprint.c

#include "fingerprint.h"
#include "usart.h"
#include "delay.h"

uint8_t fp_rx_buffer[64];
uint8_t fp_rx_flag = 0;
static uint8_t rx_cnt = 0;

/**
  * @brief  指纹模块串口初始化
  */
void FP_Init(uint32_t baudrate)
{
    USART_InitTypeDef USART_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    
    // PA2 -> TX, PA3 -> RX
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    USART_InitStructure.USART_BaudRate = baudrate;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART2, &USART_InitStructure);
    
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
    USART_Cmd(USART2, ENABLE);
    
    printf("Fingerprint Module Init OK\r\n");
}

/**
  * @brief  发送指令包
  * @param  cmd: 指令码
  * @param  data: 附加数据
  * @param  len: 数据长度
  */
void FP_SendCmd(uint8_t cmd, uint8_t *data, uint8_t len)
{
    uint16_t checksum = 0;
    uint8_t i;
    
    // 包头
    USART_SendData(USART2, 0xEF); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    USART_SendData(USART2, 0x01); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    
    // 地址
    USART_SendData(USART2, 0xFF); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    USART_SendData(USART2, 0xFF); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    USART_SendData(USART2, 0xFF); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    USART_SendData(USART2, 0xFF); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    
    // 包标识 (0x01 命令包)
    USART_SendData(USART2, 0x01); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    
    // 包长度 (指令码1字节 + 数据长度 + 校验和2字节)
    uint16_t pkg_len = 1 + len + 2;
    USART_SendData(USART2, pkg_len >> 8); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    USART_SendData(USART2, pkg_len & 0xFF); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    
    // 指令码
    USART_SendData(USART2, cmd); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    checksum += cmd;
    
    // 数据
    for(i = 0; i < len; i++)
    {
        USART_SendData(USART2, data[i]); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
        checksum += data[i];
    }
    
    // 校验和
    USART_SendData(USART2, checksum >> 8); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    USART_SendData(USART2, checksum & 0xFF); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}

/**
  * @brief  获取图像
  */
uint8_t FP_GetImage(void)
{
    FP_SendCmd(CMD_GENIMG, NULL, 0);
    return FP_GetResponse();
}

/**
  * @brief  生成特征
  * @param  buffer_id: 缓冲区号 (1 or 2)
  */
uint8_t FP_GenChar(uint8_t buffer_id)
{
    uint8_t data[1] = {buffer_id};
    FP_SendCmd(CMD_GENCHAR, data, 1);
    return FP_GetResponse();
}

/**
  * @brief  搜索指纹
  * @param  buffer_id: 缓冲区号
  * @param  start_page: 起始页
  * @param  page_num: 页数
  */
uint8_t FP_Search(uint8_t buffer_id, uint16_t start_page, uint16_t page_num)
{
    uint8_t data[5];
    data[0] = buffer_id;
    data[1] = start_page >> 8;
    data[2] = start_page & 0xFF;
    data[3] = page_num >> 8;
    data[4] = page_num & 0xFF;
    FP_SendCmd(CMD_SEARCH, data, 5);
    return FP_GetResponse();
}

/**
  * @brief  存储模板
  */
uint8_t FP_Store(uint8_t buffer_id, uint16_t page_id)
{
    uint8_t data[3];
    data[0] = buffer_id;
    data[1] = page_id >> 8;
    data[2] = page_id & 0xFF;
    FP_SendCmd(CMD_STORE, data, 3);
    return FP_GetResponse();
}

/**
  * @brief  删除指纹
  */
uint8_t FP_Delete(uint16_t page_id, uint16_t num)
{
    uint8_t data[4];
    data[0] = page_id >> 8;
    data[1] = page_id & 0xFF;
    data[2] = num >> 8;
    data[3] = num & 0xFF;
    FP_SendCmd(CMD_DELETE, data, 4);
    return FP_GetResponse();
}

/**
  * @brief  清空指纹库
  */
uint8_t FP_Empty(void)
{
    FP_SendCmd(CMD_EMPTY, NULL, 0);
    return FP_GetResponse();
}

/**
  * @brief  获取系统参数
  */
void FP_ShowSystemParams(void)
{
    FP_SendCmd(CMD_READ_CONF, NULL, 0);
    Delay_ms(200);
    // 解析fp_rx_buffer中的状态寄存器、容量等信息
    printf("Status: 0x%02X\r\n", fp_rx_buffer[7]);
}

/**
  * @brief  获取响应
  */
uint8_t FP_GetResponse(void)
{
    uint32_t timeout = 0;
    while(fp_rx_flag == 0)
    {
        Delay_ms(10);
        timeout++;
        if(timeout > 200) // 2秒超时
        {
            printf("FP Response Timeout!\r\n");
            return 0xFF;
        }
    }
    fp_rx_flag = 0;
    return fp_rx_buffer[7]; // 返回确认码
}

2.3 中断接收(stm32f10x_it.c

#include "fingerprint.h"

void USART2_IRQHandler(void)
{
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
        uint8_t data = USART_ReceiveData(USART2);
        if(rx_cnt < sizeof(fp_rx_buffer))
        {
            fp_rx_buffer[rx_cnt++] = data;
        }
        // 简单判断是否接收完成(通常看最后两个字节校验和,这里简化为固定长度)
        if(rx_cnt >= 12) // 假设最小包长度为12
        {
            fp_rx_flag = 1;
            rx_cnt = 0;
        }
    }
}

2.4 主程序逻辑(main.c

#include "stm32f10x.h"
#include "fingerprint.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "relay.h"

int main(void)
{
    SystemInit();
    Delay_Init();
    USART1_Init(115200); // 调试串口
    LED_Init();
    Relay_Init();
    
    // 初始化指纹模块,波特率57600(AS608默认)
    FP_Init(57600);
    
    uint8_t result;
    uint16_t user_id = 0;
    
    while(1)
    {
        printf("Place your finger...\r\n");
        LED_Blink(500); // LED闪烁提示
        
        // 1. 采集图像
        result = FP_GetImage();
        if(result != ACK_OK)
        {
            if(result == ACK_NO_FINGER) printf("No finger detected.\r\n");
            else printf("Get Image Failed: 0x%02X\r\n", result);
            Delay_ms(1000);
            continue;
        }
        printf("Image captured.\r\n");
        
        // 2. 生成特征
        result = FP_GenChar(CHAR_BUFFER_A);
        if(result != ACK_OK)
        {
            printf("Generate Char Failed: 0x%02X\r\n", result);
            Delay_ms(1000);
            continue;
        }
        
        // 3. 搜索指纹库
        printf("Searching...\r\n");
        result = FP_Search(CHAR_BUFFER_A, 0, 100); // 搜索前100个指纹
        
        if(result == ACK_OK)
        {
            // 搜索成功,fp_rx_buffer中包含页码和匹配度
            user_id = (fp_rx_buffer[8] << 8) | fp_rx_buffer[9];
            printf("Match Found! User ID: %d, Score: %d\r\n", user_id, fp_rx_buffer[10]);
            
            // 控制继电器开门
            Relay_ON();
            LED_ON();
            Delay_ms(3000);
            Relay_OFF();
            LED_OFF();
        }
        else if(result == ACK_NOT_FOUND)
        {
            printf("No Match Found.\r\n");
            LED_Blink(100); // 快闪表示拒绝
        }
        else
        {
            printf("Search Error: 0x%02X\r\n", result);
        }
        
        Delay_ms(2000); // 防止重复识别
    }
}

三、指纹录入流程(需在上位机或按键触发)

录入通常需要两步(录入两次以确认):

uint8_t Enroll_Fingerprint(uint16_t user_id)
{
    uint8_t res;
    
    printf("Enroll: Place finger first time\r\n");
    res = FP_GetImage(); if(res != ACK_OK) return res;
    res = FP_GenChar(CHAR_BUFFER_A); if(res != ACK_OK) return res;
    
    printf("Remove finger\r\n");
    Delay_ms(2000);
    
    printf("Enroll: Place finger second time\r\n");
    res = FP_GetImage(); if(res != ACK_OK) return res;
    res = FP_GenChar(CHAR_BUFFER_B); if(res != ACK_OK) return res;
    
    // 精确比对两次特征是否一致
    // 这里省略精确比对指令,直接存储
    res = FP_Store(CHAR_BUFFER_A, user_id);
    if(res == ACK_OK) printf("Enroll Success! ID: %d\r\n", user_id);
    
    return res;
}

参考代码 基于STM32的指纹识别 www.youwenfan.com/contentcsu/56232.html

四、调试与避坑指南

4.1 常见问题排查

现象 原因 解决方案
无响应 波特率不匹配 AS608默认57600,STM32串口初始化需一致
图像采集失败 手指太干/太湿 提示用户湿润手指或擦拭传感器
搜索速度慢 搜索范围过大 缩小搜索页码范围,或升级为1:N搜索算法
误识率高 指纹质量差 增加录入时的图像质量检测,录入时旋转手指

4.2 硬件优化建议

  1. 电源稳定性:指纹模块工作时电流较大(>100mA),建议在VCC并联100uF电容。
  2. 抗干扰:指纹模块尽量远离电机、继电器等强干扰源。
  3. 触摸唤醒:使用模块的Touch引脚连接STM32外部中断,平时STM32休眠,触摸时唤醒,降低功耗。

五、总结

这套方案实现了基于STM32的指纹录入、存储、1:N搜索全流程。核心在于串口协议的正确封装和对返回状态码的严谨处理。

应用场景

专注于matlab/simulink,电子电路,编程