前言
在嵌入式系统中,指纹识别作为一种生物识别技术,广泛应用于门禁系统、考勤机、智能锁等场景。本文将分享如何在 STM32F103C8T6 开发板上使用 AS608 指纹模块,实现指纹的录入和识别功能。
硬件准备
- STM32F103C8T6 开发板
- AS608 指纹模块
- OLED 显示屏(用于显示提示信息)
- 杜邦线若干
硬件连接
确保硬件连接正确,以下是主要的连接:
-
AS608 指纹模块与 STM32 的连接:
- AS608 TXD(发送端) ==> STM32 PA3(USART2 RX)
- AS608 RXD(接收端) ==> STM32 PA2(USART2 TX)
- AS608 VCC ==> 3.3V
- AS608 GND ==> GND
-
OLED 显示屏与 STM32 的连接:
按照 OLED 屏幕的接线要求进行连接,一般使用 I2C 或 SPI 接口。
软件设计
项目结构
- main.c:主程序入口,负责整体流程控制。
- AS608.c / AS608.h:封装与 AS608 指纹模块的通信与操作函数。
- 其他外设驱动文件:如 OLED 显示屏的驱动代码。
主程序流程
- 初始化外设:OLED 显示屏、USART2(用于与 AS608 通信)等。
- 等待 3 秒:给用户准备时间。
- 指纹录入 :
- 提示用户放置手指。
- 获取指纹图像,并生成特征文件。
- 提示用户再次放置手指,重复上述步骤。
- 合成指纹模板并存储到指纹库中。
- 等待 2 秒。
- 指纹识别 :
- 提示用户放置手指。
- 获取指纹图像,生成特征文件。
- 在指纹库中搜索匹配的指纹。
- 显示识别结果(匹配的指纹 ID 或未找到)。
代码实现
1. main.c
c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "AS608.h"
int main(void)
{
// 初始化外设
OLED_Init();
AS608_Init();
OLED_ShowString(1, 1, "AS608:");
while(1)
{
Delay_s(3); // 等待3秒
OLED_ShowString(2, 1, "Enrolling...");
if (AS608_Enroll() == 0)
{
OLED_ShowString(2, 1, "Enroll OK ");
}
else
{
OLED_ShowString(2, 1, "Enroll Failed");
}
Delay_s(2); // 再等待2秒(总共5秒)
OLED_ShowString(2, 1, "Identifying...");
uint16_t id;
if (AS608_Identify(&id) == 0)
{
OLED_ShowString(2, 1, "ID:");
OLED_ShowNum(2, 4, id, 5);
}
else
{
OLED_ShowString(2, 1, "Not Found ");
}
Delay_s(2); // 等待2秒显示结果
}
}
2. AS608.h
c
#ifndef __AS608_H
#define __AS608_H
#include "stm32f10x.h"
// 指令代码
#define PS_GetImage 0x01
#define PS_GenChar 0x02
#define PS_Match 0x03
#define PS_Search 0x04
#define PS_RegModel 0x05
#define PS_StoreChar 0x06
#define PS_LoadChar 0x07
#define PS_UpChar 0x08
#define PS_DownChar 0x09
#define PS_UpImage 0x0A
#define PS_DownImage 0x0B
#define PS_DeleteChar 0x0C
#define PS_Empty 0x0D
#define PS_SetSysPara 0x0E
#define PS_ReadSysPara 0x0F
#define PS_SetPwd 0x12
#define PS_VerifyPwd 0x13
#define PS_TemplateNum 0x1D
// 确认码
#define ACK_SUCCESS 0x00
void AS608_Init(void);
uint8_t AS608_Enroll(void);
uint8_t AS608_Identify(uint16_t *id);
#endif /* __AS608_H */
3. AS608.c
c
#include "AS608.h"
#include "Delay.h"
#include "OLED.h"
#include <stdio.h>
void AS608_Init(void)
{
// USART2 初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置 USART2 引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // RX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 USART2
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 57600; // AS608默认波特率
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_Cmd(USART2, ENABLE);
}
void AS608_SendByte(uint8_t Byte)
{
USART_SendData(USART2, Byte);
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}
void AS608_SendArray(uint8_t *Array, uint16_t Length)
{
for (uint16_t i = 0; i < Length; i++)
{
AS608_SendByte(Array[i]);
}
}
uint8_t AS608_ReceiveByte(void)
{
uint32_t timeout = 0xFFFFF; // 增加超时时间
while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET)
{
if (--timeout == 0)
{
return 0xFF; // 超时返回
}
}
return USART_ReceiveData(USART2);
}
// 发送指令包
void AS608_SendCommand(uint8_t instruction_code, uint8_t *parameters, uint16_t parameter_length)
{
uint16_t packet_length = parameter_length + 2; // instruction code + parameters
uint8_t packet[12 + parameter_length];
uint16_t checksum = 0;
// 包头
packet[0] = 0xEF;
packet[1] = 0x01;
// 地址
packet[2] = 0xFF;
packet[3] = 0xFF;
packet[4] = 0xFF;
packet[5] = 0xFF;
// 包标识
packet[6] = 0x01; // 指令包
// 包长度
packet[7] = (packet_length >> 8) & 0xFF;
packet[8] = packet_length & 0xFF;
// 指令码
packet[9] = instruction_code;
// 参数
for (uint16_t i = 0; i < parameter_length; i++)
{
packet[10 + i] = parameters[i];
}
// 计算校验和
checksum = packet[6] + packet[7] + packet[8];
for (uint16_t i = 9; i < 10 + parameter_length; i++)
{
checksum += packet[i];
}
// 校验和
packet[10 + parameter_length] = (checksum >> 8) & 0xFF;
packet[11 + parameter_length] = checksum & 0xFF;
// 发送包
AS608_SendArray(packet, 12 + parameter_length);
}
// 接收响应包
uint8_t AS608_ReceivePacket(uint8_t *buffer, uint16_t *length)
{
uint16_t idx = 0;
uint16_t i;
uint16_t checksum = 0;
// 读取包头和地址,共6字节
for (i = 0; i < 6; i++)
{
buffer[idx++] = AS608_ReceiveByte();
}
// 检查包头和地址
if (buffer[0] != 0xEF || buffer[1] != 0x01)
{
return 1; // 包头错误
}
// 读取包标识和长度,共3字节
for (i = 0; i < 3; i++)
{
buffer[idx++] = AS608_ReceiveByte();
}
uint16_t packet_length = (((uint16_t)buffer[7] << 8) | buffer[8]) - 2; // 减去校验和长度
// 读取包内容和校验和
for (i = 0; i < packet_length + 2; i++)
{
buffer[idx++] = AS608_ReceiveByte();
}
*length = idx;
// 计算校验和
for (i = 6; i < idx - 2; i++)
{
checksum += buffer[i];
}
uint16_t received_checksum = ((uint16_t)buffer[idx - 2] << 8) | buffer[idx - 1];
if (checksum != received_checksum)
{
return 2; // 校验和错误
}
return 0; // 成功
}
// 指纹录入函数
uint8_t AS608_Enroll(void)
{
uint8_t ack;
uint8_t buffer[64];
uint16_t length;
uint16_t page_id = 0; // 假设将指纹存储在ID为0的位置
uint8_t retry;
// 提示放置手指
OLED_ShowString(2, 1, "Place Finger ");
// Step 1: 获取图像
retry = 0;
do
{
AS608_SendCommand(PS_GetImage, NULL, 0);
ack = AS608_ReceivePacket(buffer, &length);
if (ack == 0 && buffer[9] == ACK_SUCCESS)
{
break;
}
else if (ack == 0 && buffer[9] == 0x02)
{
OLED_ShowString(3, 1, "No Finger ");
}
else
{
OLED_ShowString(3, 1, "GetImage Err ");
}
Delay_ms(500);
retry++;
} while (retry < 10);
if (retry >= 10)
{
return 1; // 超时
}
// Step 2: 生成特征文件到CharBuffer1
uint8_t param[1] = {0x01}; // Buffer1
AS608_SendCommand(PS_GenChar, param, 1);
ack = AS608_ReceivePacket(buffer, &length);
if (ack != 0 || buffer[9] != ACK_SUCCESS)
{
return 1; // 错误
}
// 提示移开手指
OLED_ShowString(2, 1, "Remove Finger");
Delay_s(2);
// 提示再次放置手指
OLED_ShowString(2, 1, "Place Again ");
// Step 3: 再次获取图像
retry = 0;
do
{
AS608_SendCommand(PS_GetImage, NULL, 0);
ack = AS608_ReceivePacket(buffer, &length);
if (ack == 0 && buffer[9] == ACK_SUCCESS)
{
break;
}
Delay_ms(500);
retry++;
} while (retry < 10);
if (retry >= 10)
{
return 1; // 超时
}
// Step 4: 生成特征文件到CharBuffer2
param[0] = 0x02; // Buffer2
AS608_SendCommand(PS_GenChar, param, 1);
ack = AS608_ReceivePacket(buffer, &length);
if (ack != 0 || buffer[9] != ACK_SUCCESS)
{
return 1; // 错误
}
// Step 5: 合并特征到模板
AS608_SendCommand(PS_RegModel, NULL, 0);
ack = AS608_ReceivePacket(buffer, &length);
if (ack != 0 || buffer[9] != ACK_SUCCESS)
{
return 1; // 错误
}
// Step 6: 存储模板到指定ID
uint8_t store_param[3] = {0x01, (page_id >> 8) & 0xFF, page_id & 0xFF};
AS608_SendCommand(PS_StoreChar, store_param, 3);
ack = AS608_ReceivePacket(buffer, &length);
if (ack != 0 || buffer[9] != ACK_SUCCESS)
{
return 1; // 错误
}
return 0; // 成功
}
// 指纹识别函数
uint8_t AS608_Identify(uint16_t *id)
{
uint8_t ack;
uint8_t buffer[64];
uint16_t length;
uint8_t retry;
// 提示放置手指
OLED_ShowString(2, 1, "Place Finger ");
// Step 1: 获取图像
retry = 0;
do
{
AS608_SendCommand(PS_GetImage, NULL, 0);
ack = AS608_ReceivePacket(buffer, &length);
if (ack == 0 && buffer[9] == ACK_SUCCESS)
{
break;
}
else if (ack == 0 && buffer[9] == 0x02)
{
OLED_ShowString(3, 1, "No Finger ");
}
else
{
OLED_ShowString(3, 1, "GetImage Err ");
}
Delay_ms(500);
retry++;
} while (retry < 10);
if (retry >= 10)
{
return 1; // 超时
}
// Step 2: 生成特征文件到CharBuffer1
uint8_t param[1] = {0x01}; // Buffer1
AS608_SendCommand(PS_GenChar, param, 1);
ack = AS608_ReceivePacket(buffer, &length);
if (ack != 0 || buffer[9] != ACK_SUCCESS)
{
return 1; // 错误
}
// Step 3: 搜索指纹库
uint8_t search_param[6] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x01}; // Buffer1, 起始页0,页数1
AS608_SendCommand(PS_Search, search_param, 6);
ack = AS608_ReceivePacket(buffer, &length);
if (ack != 0 || buffer[9] != ACK_SUCCESS)
{
return 1; // 未找到
}
// 获取匹配到的指纹ID
*id = ((uint16_t)buffer[10] << 8) | buffer[11];
return 0; // 成功
}
关键问题与解决方案
问题描述
在实现过程中,发现程序一直停留在 Place Finger
提示,无法继续。这表明程序可能在等待指纹模块的响应,但未能收到正确的数据。
可能原因
-
USART 通讯问题:
- 波特率设置不正确。
- USART 引脚连接错误。
- USART 接收函数未正确实现。
-
指令包格式错误:
- 指令包中的参数、校验和计算错误。
- 未正确按照 AS608 通讯协议发送和接收数据。
-
指纹模块未正常工作:
- 模块损坏或供电不足。
- 指纹库已满或需要初始化。
解决方案
-
检查 USART 通讯配置:
-
确保波特率设置为 57600,这是 AS608 的默认波特率。
-
检查 USART2 初始化代码,确保配置正确。
c// 配置 USART2 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 57600; // AS608默认波特率 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);
-
确认引脚连接:
- PA2(USART2 TX) ==> AS608 RXD
- PA3(USART2 RX) ==> AS608 TXD
-
-
改进接收函数
AS608_ReceiveByte
:-
增加超时时间,避免因等待时间过短导致接收失败。
cuint8_t AS608_ReceiveByte(void) { uint32_t timeout = 0xFFFFF; // 增加超时时间 while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET) { if (--timeout == 0) { return 0xFF; // 超时返回 } } return USART_ReceiveData(USART2); }
-
-
检查指令包格式和校验和计算:
-
确保发送的指令包符合 AS608 通讯协议,包括包头、地址、包标识、包长度、指令码、参数、校验和。
-
修改
AS608_SendCommand
函数中的校验和计算方式:c// 计算校验和 checksum = packet[6] + packet[7] + packet[8]; for (uint16_t i = 9; i < 10 + parameter_length; i++) { checksum += packet[i]; }
-
-
增加调试信息:
-
在关键步骤中,通过 OLED 显示接收到的确认码,便于判断问题所在。
cOLED_ShowString(3, 1, "Ack: "); OLED_ShowHexNum(3, 6, buffer[9], 2);
-
-
增加超时和错误处理机制:
-
在等待指纹输入和接收模块响应时,增加重试次数,避免程序陷入死循环。
cuint8_t retry = 0; do { // 发送指令并接收响应 // ... retry++; } while (retry < 10);
-
-
检查指纹模块工作状态:
-
确认指纹模块的供电电压为 3.3V,且电源足够稳定。
-
如果指纹库已满,尝试清空指纹库:
cuint8_t AS608_EmptyLibrary(void) { uint8_t buffer[32]; uint16_t length; AS608_SendCommand(PS_Empty, NULL, 0); uint8_t ack = AS608_ReceivePacket(buffer, &length); if (ack == 0 && buffer[9] == ACK_SUCCESS) { return 0; // 成功 } else { return 1; // 失败 } }
-
-
使用最小化测试代码进行验证:
-
编写简单的测试程序,仅发送
PS_GetImage
指令,查看是否能收到正确的响应。cint main(void) { // 初始化外设 OLED_Init(); AS608_Init(); OLED_ShowString(1, 1, "AS608 Test:"); while(1) { OLED_ShowString(2, 1, "Testing... "); AS608_SendCommand(PS_GetImage, NULL, 0); uint8_t buffer[32]; uint16_t length; uint8_t ack = AS608_ReceivePacket(buffer, &length); if (ack == 0) { OLED_ShowString(3, 1, "Ack: "); OLED_ShowHexNum(3, 6, buffer[9], 2); if (buffer[9] == ACK_SUCCESS) { OLED_ShowString(4, 1, "GetImage OK "); } else { OLED_ShowString(4, 1, "GetImage Fail"); } } else { OLED_ShowString(3, 1, "No Response "); } Delay_s(2); } }
-
总结
通过对硬件连接、USART 通讯配置、指令包格式、接收函数以及错误处理机制的逐一检查和改进,成功实现了 AS608 指纹模块在 STM32 上的指纹录入和识别功能。
在调试过程中,遇到类似的问题时,应从硬件和软件两个方面入手,逐步排查。同时,增加调试信息和错误处理机制,可以大大提高调试效率。
注意事项:
- 确保指纹模块的电源供电稳定。
- 遵循 AS608 通讯协议,正确组建指令包和解析响应包。
- USART 接收函数需要考虑超时和异常情况。
参考资料
- AS608 指纹模块数据手册
- STM32F10x 标准外设库参考手册
- UART 通讯协议和调试方法