基于STM32的多模态智能门锁系统设计与实现
基于STM32的多模态智能门锁系统设计与实现
前言
最近接了个项目,要做一套支持指纹、人脸、密码三重验证的智能门锁系统。这种多模态识别在安全性和便利性上都有很大优势,特别适合对安全要求较高的场景。折腾了一个多月,踩了不少坑,今天把整个系统的设计思路和实现细节分享出来,希望对做类似项目的朋友有所帮助。
本文会详细介绍系统架构、硬件选型、各个模块的实现原理,以及完整的代码实现。代码都是实际项目中跑通的,可以直接参考使用。
一、系统整体架构
1.1 硬件架构
整个系统的硬件架构如下:
┌─────────────────────────────────────────────────┐
│ STM32F407VET6 主控芯片 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 指纹模块 │ │ 人脸模块 │ │ 密码键盘 │ │
│ │ AS608 │ │ 摄像头+ │ │ 4x4矩阵 │ │
│ │ │ │ AI芯片 │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ OLED屏幕 │ │ 电机驱动 │ │ WiFi │ │
│ │ 0.96寸 │ │ L298N │ │ ESP8266 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────┘
核心硬件清单:
- 主控:STM32F407VET6(168MHz,1MB Flash)
- 指纹模块:AS608光学指纹识别模块
- 人脸识别:OV2640摄像头 + K210 AI加速芯片
- 密码输入:4x4矩阵键盘
- 显示:0.96寸OLED(I2C接口)
- 门锁驱动:步进电机+L298N驱动模块
- 通信:ESP8266 WiFi模块(远程管理)
- 电源:12V/3A开关电源
1.2 软件架构
软件采用分层设计,主要分为以下几层:
┌─────────────────────────────────────┐
│ 应用层(Application) │
│ 门锁控制逻辑 | 用户管理 | 日志 │
└─────────────────────────────────────┘↓
┌─────────────────────────────────────┐
│ 算法层(Algorithm) │
│ 指纹匹配 | 人脸识别 | 密码验证 │
└─────────────────────────────────────┘↓
┌─────────────────────────────────────┐
│ 驱动层(Driver) │
│ UART | SPI | I2C | GPIO | Timer │
└─────────────────────────────────────┘↓
┌─────────────────────────────────────┐
│ 硬件层(Hardware) │
│ STM32 HAL Library │
└─────────────────────────────────────┘
二、硬件接口设计
2.1 引脚分配
// 指纹模块 AS608 - UART3
#define FP_UART USART3
#define FP_TX_PIN GPIO_PIN_10
#define FP_RX_PIN GPIO_PIN_11
#define FP_GPIO_PORT GPIOB// 人脸识别模块 K210 - UART2
#define FACE_UART USART2
#define FACE_TX_PIN GPIO_PIN_2
#define FACE_RX_PIN GPIO_PIN_3
#define FACE_GPIO_PORT GPIOA// OLED显示 - I2C1
#define OLED_SCL_PIN GPIO_PIN_6
#define OLED_SDA_PIN GPIO_PIN_7
#define OLED_GPIO_PORT GPIOB// 矩阵键盘 - GPIO
#define KEY_ROW1_PIN GPIO_PIN_0
#define KEY_ROW2_PIN GPIO_PIN_1
#define KEY_ROW3_PIN GPIO_PIN_2
#define KEY_ROW4_PIN GPIO_PIN_3
#define KEY_COL1_PIN GPIO_PIN_4
#define KEY_COL2_PIN GPIO_PIN_5
#define KEY_COL3_PIN GPIO_PIN_6
#define KEY_COL4_PIN GPIO_PIN_7
#define KEY_GPIO_PORT GPIOC// 步进电机控制 - PWM
#define MOTOR_IN1_PIN GPIO_PIN_0
#define MOTOR_IN2_PIN GPIO_PIN_1
#define MOTOR_IN3_PIN GPIO_PIN_2
#define MOTOR_IN4_PIN GPIO_PIN_3
#define MOTOR_GPIO_PORT GPIOE// WiFi模块 ESP8266 - UART4
#define WIFI_UART UART4
#define WIFI_TX_PIN GPIO_PIN_10
#define WIFI_RX_PIN GPIO_PIN_11
#define WIFI_GPIO_PORT GPIOC
2.2 电源管理
智能锁对功耗有一定要求,特别是待机状态下。我们设计了三种工作模式:
- 激活模式:所有模块上电,功耗约2W
- 待机模式:仅保持键盘和触摸唤醒,功耗约200mW
- 休眠模式:深度休眠,仅RTC工作,功耗<50mW
三、指纹识别模块实现
3.1 AS608指纹模块通信协议
AS608使用UART通信,波特率57600。数据包格式如下:
包头 地址 标识 包长度 指令/数据 校验和
0xEF01 4字节 1字节 2字节 N字节 2字节
3.2 指纹模块驱动代码
// as608.h
#ifndef __AS608_H
#define __AS608_H#include "stm32f4xx_hal.h"// AS608指令定义
#define AS608_CMD_GET_IMAGE 0x01 // 录入图像
#define AS608_CMD_GEN_CHAR 0x02 // 生成特征
#define AS608_CMD_MATCH 0x03 // 精确比对
#define AS608_CMD_SEARCH 0x04 // 搜索指纹
#define AS608_CMD_REG_MODEL 0x05 // 合成模板
#define AS608_CMD_STORE_CHAR 0x06 // 储存模板
#define AS608_CMD_LOAD_CHAR 0x07 // 读取模板
#define AS608_CMD_DELETE_CHAR 0x0C // 删除模板
#define AS608_CMD_EMPTY 0x0D // 清空指纹库// 返回确认码
#define AS608_ACK_SUCCESS 0x00 // 操作成功
#define AS608_ACK_ERROR 0x01 // 数据包接收错误
#define AS608_ACK_NO_FINGER 0x02 // 没有检测到手指
#define AS608_ACK_GET_IMG_ERR 0x03 // 录入指纹图像失败
#define AS608_ACK_NOT_MATCH 0x08 // 指纹不匹配
#define AS608_ACK_NOT_FOUND 0x09 // 没有搜索到指纹// 数据包标识
#define AS608_PACK_CMD 0x01
#define AS608_PACK_DATA 0x02
#define AS608_PACK_END 0x08typedef struct {uint8_t head[2]; // 包头 0xEF01uint8_t addr[4]; // 模块地址uint8_t pid; // 包标识uint8_t len[2]; // 包长度uint8_t data[256]; // 数据内容uint8_t checksum[2]; // 校验和
} AS608_Packet;typedef struct {uint16_t pageID; // 指纹IDuint16_t matchScore; // 匹配分数
} AS608_SearchResult;// 函数声明
void AS608_Init(UART_HandleTypeDef *huart);
uint8_t AS608_GetImage(void);
uint8_t AS608_GenChar(uint8_t bufferID);
uint8_t AS608_Match(void);
uint8_t AS608_Search(uint8_t bufferID, AS608_SearchResult *result);
uint8_t AS608_RegModel(void);
uint8_t AS608_StoreChar(uint8_t bufferID, uint16_t pageID);
uint8_t AS608_DeleteChar(uint16_t pageID, uint16_t count);
uint8_t AS608_Empty(void);
uint8_t AS608_EnrollFingerprint(uint16_t pageID);
uint8_t AS608_VerifyFingerprint(AS608_SearchResult *result);#endif
// as608.c
#include "as608.h"
#include "string.h"static UART_HandleTypeDef *fp_uart;
static uint8_t AS608_RxBuffer[512];
static uint32_t AS608_Address = 0xFFFFFFFF;// 发送数据包
static void AS608_SendPacket(AS608_Packet *packet) {uint16_t length = (packet->len[0] << 8) | packet->len[1];uint16_t checksum = packet->pid + packet->len[0] + packet->len[1];for(int i = 0; i < length - 2; i++) {checksum += packet->data[i];}packet->checksum[0] = (checksum >> 8) & 0xFF;packet->checksum[1] = checksum & 0xFF;uint8_t buffer[512];int pos = 0;buffer[pos++] = 0xEF;buffer[pos++] = 0x01;memcpy(&buffer[pos], packet->addr, 4); pos += 4;buffer[pos++] = packet->pid;buffer[pos++] = packet->len[0];buffer[pos++] = packet->len[1];memcpy(&buffer[pos], packet->data, length - 2); pos += (length - 2);buffer[pos++] = packet->checksum[0];buffer[pos++] = packet->checksum[1];HAL_UART_Transmit(fp_uart, buffer, pos, 1000);
}// 接收数据包
static uint8_t AS608_ReceivePacket(AS608_Packet *packet, uint32_t timeout) {uint32_t startTick = HAL_GetTick();int rxPos = 0;while(HAL_GetTick() - startTick < timeout) {if(HAL_UART_Receive(fp_uart, &AS608_RxBuffer[rxPos], 1, 10) == HAL_OK) {rxPos++;if(rxPos >= 9) {if(AS608_RxBuffer[0] == 0xEF && AS608_RxBuffer[1] == 0x01) {uint16_t length = (AS608_RxBuffer[7] << 8) | AS608_RxBuffer[8];while(rxPos < 9 + length && HAL_GetTick() - startTick < timeout) {if(HAL_UART_Receive(fp_uart, &AS608_RxBuffer[rxPos], 1, 10) == HAL_OK) {rxPos++;}}if(rxPos == 9 + length) {packet->head[0] = AS608_RxBuffer[0];packet->head[1] = AS608_RxBuffer[1];memcpy(packet->addr, &AS608_RxBuffer[2], 4);packet->pid = AS608_RxBuffer[6];packet->len[0] = AS608_RxBuffer[7];packet->len[1] = AS608_RxBuffer[8];memcpy(packet->data, &AS608_RxBuffer[9], length - 2);packet->checksum[0] = AS608_RxBuffer[9 + length - 2];packet->checksum[1] = AS608_RxBuffer[9 + length - 1];return packet->data[0]; // 返回确认码}}}}}return 0xFF; // 超时
}// 初始化
void AS608_Init(UART_HandleTypeDef *huart) {fp_uart = huart;
}// 录入图像
uint8_t AS608_GetImage(void) {AS608_Packet packet;packet.head[0] = 0xEF;packet.head[1] = 0x01;memcpy(packet.addr, &AS608_Address, 4);packet.pid = AS608_PACK_CMD;packet.len[0] = 0x00;packet.len[1] = 0x03;packet.data[0] = AS608_CMD_GET_IMAGE;AS608_SendPacket(&packet);return AS608_ReceivePacket(&packet, 1000);
}// 生成特征文件
uint8_t AS608_GenChar(uint8_t bufferID) {AS608_Packet packet;packet.head[0] = 0xEF;packet.head[1] = 0x01;memcpy(packet.addr, &AS608_Address, 4);packet.pid = AS608_PACK_CMD;packet.len[0] = 0x00;packet.len[1] = 0x04;packet.data[0] = AS608_CMD_GEN_CHAR;packet.data[1] = bufferID;AS608_SendPacket(&packet);return AS608_ReceivePacket(&packet, 2000);
}// 精确对比两枚指纹特征
uint8_t AS608_Match(void) {AS608_Packet packet;packet.head[0] = 0xEF;packet.head[1] = 0x01;memcpy(packet.addr, &AS608_Address, 4);packet.pid = AS608_PACK_CMD;packet.len[0] = 0x00;packet.len[1] = 0x03;packet.data[0] = AS608_CMD_MATCH;AS608_SendPacket(&packet);return AS608_ReceivePacket(&packet, 1000);
}// 搜索指纹
uint8_t AS608_Search(uint8_t bufferID, AS608_SearchResult *result) {AS608_Packet packet;packet.head[0] = 0xEF;packet.head[1] = 0x01;memcpy(packet.addr, &AS608_Address, 4);packet.pid = AS608_PACK_CMD;packet.len[0] = 0x00;packet.len[1] = 0x08;packet.data[0] = AS608_CMD_SEARCH;packet.data[1] = bufferID;packet.data[2] = 0x00; // 起始页packet.data[3] = 0x00;packet.data[4] = 0x00; // 页数packet.data[5] = 0xFF;AS608_SendPacket(&packet);uint8_t ack = AS608_ReceivePacket(&packet, 2000);if(ack == AS608_ACK_SUCCESS && result != NULL) {result->pageID = (packet.data[1] << 8) | packet.data[2];result->matchScore = (packet.data[3] << 8) | packet.data[4];}return ack;
}// 合成模板
uint8_t AS608_RegModel(void) {AS608_Packet packet;packet.head[0] = 0xEF;packet.head[1] = 0x01;memcpy(packet.addr, &AS608_Address, 4);packet.pid = AS608_PACK_CMD;packet.len[0] = 0x00;packet.len[1] = 0x03;packet.data[0] = AS608_CMD_REG_MODEL;AS608_SendPacket(&packet);return AS608_ReceivePacket(&packet, 2000);
}// 储存模板
uint8_t AS608_StoreChar(uint8_t bufferID, uint16_t pageID) {AS608_Packet packet;packet.head[0] = 0xEF;packet.head[1] = 0x01;memcpy(packet.addr, &AS608_Address, 4);packet.pid = AS608_PACK_CMD;packet.len[0] = 0x00;packet.len[1] = 0x06;packet.data[0] = AS608_CMD_STORE_CHAR;packet.data[1] = bufferID;packet.data[2] = (pageID >> 8) & 0xFF;packet.data[3] = pageID & 0xFF;AS608_SendPacket(&packet);return AS608_ReceivePacket(&packet, 2000);
}// 删除指纹模板
uint8_t AS608_DeleteChar(uint16_t pageID, uint16_t count) {AS608_Packet packet;packet.head[0] = 0xEF;packet.head[1] = 0x01;memcpy(packet.addr, &AS608_Address, 4);packet.pid = AS608_PACK_CMD;packet.len[0] = 0x00;packet.len[1] = 0x07;packet.data[0] = AS608_CMD_DELETE_CHAR;packet.data[1] = (pageID >> 8) & 0xFF;packet.data[2] = pageID & 0xFF;packet.data[3] = (count >> 8) & 0xFF;packet.data[4] = count & 0xFF;AS608_SendPacket(&packet);return AS608_ReceivePacket(&packet, 2000);
}// 清空指纹库
uint8_t AS608_Empty(void) {AS608_Packet packet;packet.head[0] = 0xEF;packet.head[1] = 0x01;memcpy(packet.addr, &AS608_Address, 4);packet.pid = AS608_PACK_CMD;packet.len[0] = 0x00;packet.len[1] = 0x03;packet.data[0] = AS608_CMD_EMPTY;AS608_SendPacket(&packet);return AS608_ReceivePacket(&packet, 2000);
}// 录入指纹(完整流程)
uint8_t AS608_EnrollFingerprint(uint16_t pageID) {uint8_t ack;// 第一次录入printf("请按压手指...\r\n");while(AS608_GetImage() != AS608_ACK_SUCCESS) {HAL_Delay(200);}ack = AS608_GenChar(1);if(ack != AS608_ACK_SUCCESS) {return ack;}printf("请抬起手指...\r\n");HAL_Delay(1000);// 第二次录入printf("请再次按压手指...\r\n");while(AS608_GetImage() != AS608_ACK_SUCCESS) {HAL_Delay(200);}ack = AS608_GenChar(2);if(ack != AS608_ACK_SUCCESS) {return ack;}// 合成模板ack = AS608_RegModel();if(ack != AS608_ACK_SUCCESS) {return ack;}// 储存模板ack = AS608_StoreChar(1, pageID);return ack;
}// 验证指纹(完整流程)
uint8_t AS608_VerifyFingerprint(AS608_SearchResult *result) {uint8_t ack;// 录入图像ack = AS608_GetImage();if(ack != AS608_ACK_SUCCESS) {return ack;}// 生成特征ack = AS608_GenChar(1);if(ack != AS608_ACK_SUCCESS) {return ack;}// 搜索指纹库ack = AS608_Search(1, result);return ack;
}
3.3 指纹识别流程
指纹验证的完整流程如下:
- 检测手指按压
- 采集指纹图像
- 提取特征点
- 在指纹库中搜索匹配
- 返回匹配结果和得分
这里有个坑要注意:AS608对手指的湿度比较敏感,太干或太湿都会影响识别率。我们在实际使用中加了一个重试机制,允许用户在3秒内尝试3次。
四、人脸识别模块实现
4.1 K210人脸识别方案
人脸识别我们使用K210芯片,它内置了KPU神经网络处理器,可以运行YOLO等模型。我们采用的方案是:
- OV2640摄像头采集图像(320x240分辨率)
- K210运行人脸检测模型(基于YOLO v2改进)
- 提取128维人脸特征向量
- 与数据库中的特征向量进行余弦相似度比对
4.2 K210端代码(MicroPython)
# face_recognition.py - K210端人脸识别代码
import sensor
import image
import lcd
import KPU as kpu
import time
from fpioa_manager import fm
from Maix import GPIO
import gc# 初始化摄像头
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA) # 320x240
sensor.set_hmirror(1)
sensor.set_vflip(1)
sensor.run(1)# 初始化LCD
lcd.init()
lcd.rotation(2)# 加载人脸检测模型
task_fd = kpu.load(0x300000) # 人脸检测模型地址
task_fe = kpu.load(0x400000) # 特征提取模型地址# 人脸检测锚点
anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437, 6.92275, 6.718375, 9.01025)
kpu.init_yolo2(task_fd, 0.5, 0.3, 5, anchor)# 人脸数据库(实际应该从STM32传输或从SD卡加载)
face_database = []class FaceFeature:def __init__(self, user_id, feature):self.user_id = user_idself.feature = featuredef load_face_database():"""从文件加载人脸数据库"""global face_databasetry:with open('/sd/face_db.txt', 'r') as f:lines = f.readlines()for line in lines:parts = line.strip().split(',')user_id = int(parts[0])feature = [float(x) for x in parts[1:]]face_database.append(FaceFeature(user_id, feature))print("已加载 %d 个人脸特征" % len(face_database))except:print("人脸数据库加载失败")def extract_feature(img, face_box):"""提取人脸特征"""# 裁剪人脸区域face_cut = img.cut(face_box[0], face_box[1], face_box[2], face_box[3])face_cut.resize(128, 128)face_cut.pix_to_ai()# 运行特征提取模型fmap = kpu.forward(task_fe, face_cut)feature = kpu.face_encode(fmap[:])del face_cutgc.collect()return featuredef cosine_similarity(feat1, feat2):"""计算余弦相似度"""dot_product = sum(a * b for a, b in zip(feat1, feat2))norm1 = sum(a * a for a in feat1) ** 0.5norm2 = sum(b * b for b in feat2) ** 0.5if norm1 == 0 or norm2 == 0:return 0return dot_product / (norm1 * norm2)def recognize_face(feature, threshold=0.75):"""识别人脸"""max_similarity = 0best_match_id = -1for face_data in face_database:similarity = cosine_similarity(feature, face_data.feature)if similarity > max_similarity:max_similarity = similaritybest_match_id = face_data.user_idif max_similarity >= threshold:return best_match_id, max_similarityelse:return -1, max_similaritydef enroll_face(user_id):"""录入新人脸"""print("录入人脸 ID:", user_id)features = []# 采集5张不同角度的人脸for i in range(5):print("请调整角度,准备采集第 %d/5 张" % (i + 1))time.sleep(2)while True:img = sensor.snapshot()faces = kpu.run_yolo2(task_fd, img)if faces:for face in faces:# 绘制人脸框x, y, w, h = face.rect()img.draw_rectangle(x, y, w, h, color=(0, 255, 0), thickness=2)# 提取特征feature = extract_feature(img, face.rect())features.append(feature)breakbreaklcd.display(img)# 计算平均特征(简化处理,实际可以用更复杂的融合方法)avg_feature = [sum(feat[i] for feat in features) / len(features) for i in range(len(features[0]))]# 保存到数据库face_database.append(FaceFeature(user_id, avg_feature))# 保存到文件with open('/sd/face_db.txt', 'a') as f:line = str(user_id) + ',' + ','.join(map(str, avg_feature)) + '\n'f.write(line)print("人脸录入成功!")return Truedef main_loop():"""主循环"""load_face_database()while True:img = sensor.snapshot()faces = kpu.run_yolo2(task_fd, img)if faces:for face in faces:# 绘制人脸框x, y, w, h = face.rect()img.draw_rectangle(x, y, w, h, color=(0, 255, 0), thickness=2)# 提取并识别feature = extract_feature(img, face.rect())user_id, similarity = recognize_face(feature)if user_id != -1:info = "User: %d (%.2f%%)" % (user_id, similarity * 100)img.draw_string(x, y - 20, info, color=(0, 255, 0), scale=2)print("识别成功:", info)# 发送识别结果到STM32send_result_to_stm32(user_id, similarity)else:img.draw_string(x, y - 20, "Unknown", color=(255, 0, 0), scale=2)lcd.display(img)gc.collect()def send_result_to_stm32(user_id, similarity):"""通过UART发送识别结果到STM32"""# 数据格式: 0xFF 0xAA [user_id_high] [user_id_low] [similarity*100] 0x55from machine import UARTuart = UART.UART3data = bytearray([0xFF, 0xAA, (user_id >> 8) & 0xFF, user_id & 0xFF,int(similarity * 100),0x55])uart.write(data)# 启动主循环
if __name__ == "__main__":main_loop()
4.3 STM32端接收处理
// face_recognition.h
#ifndef __FACE_RECOGNITION_H
#define __FACE_RECOGNITION_H#include "stm32f4xx_hal.h"#define FACE_UART USART2
#define FACE_RX_BUFFER_SIZE 256typedef struct {uint16_t userID;float similarity;uint8_t isValid;
} FaceRecognitionResult;void Face_Init(UART_HandleTypeDef *huart);
void Face_ProcessData(void);
uint8_t Face_GetResult(FaceRecognitionResult *result);
void Face_StartEnroll(uint16_t userID);
void Face_StartRecognition(void);#endif
// face_recognition.c
#include "face_recognition.h"
#include "string.h"static UART_HandleTypeDef *face_uart;
static uint8_t Face_RxBuffer[FACE_RX_BUFFER_SIZE];
static uint8_t Face_RxIndex = 0;
static FaceRecognitionResult Face_LatestResult = {0};void Face_Init(UART_HandleTypeDef *huart) {face_uart = huart;// 启动UART接收中断HAL_UART_Receive_IT(face_uart, &Face_RxBuffer[Face_RxIndex], 1);
}// UART接收回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {if(huart->Instance == FACE_UART) {Face_RxIndex++;if(Face_RxIndex >= 6) {// 检查数据包完整性if(Face_RxBuffer[0] == 0xFF && Face_RxBuffer[1] == 0xAA && Face_RxBuffer[5] == 0x55) {Face_LatestResult.userID = (Face_RxBuffer[2] << 8) | Face_RxBuffer[3];Face_LatestResult.similarity = Face_RxBuffer[4] / 100.0f;Face_LatestResult.isValid = 1;}Face_RxIndex = 0;}HAL_UART_Receive_IT(face_uart, &Face_RxBuffer[Face_RxIndex], 1);}
}uint8_t Face_GetResult(FaceRecognitionResult *result) {if(Face_LatestResult.isValid) {memcpy(result, &Face_LatestResult, sizeof(FaceRecognitionResult));Face_LatestResult.isValid = 0;return 1;}return 0;
}void Face_StartEnroll(uint16_t userID) {// 发送录入命令到K210uint8_t cmd[] = {0xFF, 0xFE, 0x01, (userID >> 8) & 0xFF, userID & 0xFF, 0x55};HAL_UART_Transmit(face_uart, cmd, sizeof(cmd), 100);
}void Face_StartRecognition(void) {// 发送识别命令到K210uint8_t cmd[] = {0xFF, 0xFE, 0x02, 0x00, 0x00, 0x55};HAL_UART_Transmit(face_uart, cmd, sizeof(cmd), 100);
}
五、密码键盘实现
5.1 矩阵键盘扫描原理
4x4矩阵键盘通过行列扫描来检测按键。原理是:
- 依次给每一行输出低电平,其他行输出高电平
- 读取每一列的电平状态
- 如果某列为低电平,说明该行该列的按键被按下
键盘布局:
1 2 3 A
4 5 6 B
7 8 9 C
* 0 # D
5.2 键盘驱动代码
// keypad.h
#ifndef __KEYPAD_H
#define __KEYPAD_H#include "stm32f4xx_hal.h"#define KEYPAD_ROWS 4
#define KEYPAD_COLS 4#define KEY_NONE 0xFF
#define KEY_PRESSED 1
#define KEY_RELEASED 0// 按键值定义
#define KEY_1 0x01
#define KEY_2 0x02
#define KEY_3 0x03
#define KEY_A 0x0A
#define KEY_4 0x04
#define KEY_5 0x05
#define KEY_6 0x06
#define KEY_B 0x0B
#define KEY_7 0x07
#define KEY_8 0x08
#define KEY_9 0x09
#define KEY_C 0x0C
#define KEY_STAR 0x0E
#define KEY_0 0x00
#define KEY_HASH 0x0F
#define KEY_D 0x0Dvoid Keypad_Init(void);
uint8_t Keypad_Scan(void);
uint8_t Keypad_GetKey(void);#endif
// keypad.c
#include "keypad.h"// 键值映射表
static const uint8_t KeyMap[KEYPAD_ROWS][KEYPAD_COLS] = {{KEY_1, KEY_2, KEY_3, KEY_A},{KEY_4, KEY_5, KEY_6, KEY_B},{KEY_7, KEY_8, KEY_9, KEY_C},{KEY_STAR, KEY_0, KEY_HASH, KEY_D}
};// 行引脚数组
static uint16_t RowPins[KEYPAD_ROWS] = {KEY_ROW1_PIN, KEY_ROW2_PIN, KEY_ROW3_PIN, KEY_ROW4_PIN
};// 列引脚数组
static uint16_t ColPins[KEYPAD_COLS] = {KEY_COL1_PIN, KEY_COL2_PIN, KEY_COL3_PIN, KEY_COL4_PIN
};static uint8_t LastKey = KEY_NONE;
static uint8_t KeyState = KEY_RELEASED;void Keypad_Init(void) {GPIO_InitTypeDef GPIO_InitStruct = {0};// 配置行引脚为输出GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;for(int i = 0; i < KEYPAD_ROWS; i++) {GPIO_InitStruct.Pin = RowPins[i];HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);HAL_GPIO_WritePin(KEY_GPIO_PORT, RowPins[i], GPIO_PIN_SET);}// 配置列引脚为输入上拉GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_PULLUP;for(int i = 0; i < KEYPAD_COLS; i++) {GPIO_InitStruct.Pin = ColPins[i];HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);}
}uint8_t Keypad_Scan(void) {uint8_t key = KEY_NONE;for(int row = 0; row < KEYPAD_ROWS; row++) {// 设置当前行为低电平HAL_GPIO_WritePin(KEY_GPIO_PORT, RowPins[row], GPIO_PIN_RESET);// 延时消抖HAL_Delay(1);// 检测列for(int col = 0; col < KEYPAD_COLS; col++) {if(HAL_GPIO_ReadPin(KEY_GPIO_PORT, ColPins[col]) == GPIO_PIN_RESET) {key = KeyMap[row][col];// 等待按键释放while(HAL_GPIO_ReadPin(KEY_GPIO_PORT, ColPins[col]) == GPIO_PIN_RESET) {HAL_Delay(10);}break;}}// 恢复当前行为高电平HAL_GPIO_WritePin(KEY_GPIO_PORT, RowPins[row], GPIO_PIN_SET);if(key != KEY_NONE) {break;}}return key;
}uint8_t Keypad_GetKey(void) {uint8_t key = Keypad_Scan();if(key != KEY_NONE && key != LastKey) {LastKey = key;KeyState = KEY_PRESSED;return key;} else if(key == KEY_NONE) {LastKey = KEY_NONE;KeyState = KEY_RELEASED;}return KEY_NONE;
}
5.3 密码验证逻辑
// password.h
#ifndef __PASSWORD_H
#define __PASSWORD_H#include "stm32f4xx_hal.h"#define MAX_PASSWORD_LENGTH 8
#define MAX_USERS 50typedef struct {uint16_t userID;char password[MAX_PASSWORD_LENGTH + 1];uint8_t isActive;
} PasswordEntry;void Password_Init(void);
uint8_t Password_Verify(const char *password, uint16_t *userID);
uint8_t Password_Set(uint16_t userID, const char *password);
uint8_t Password_Delete(uint16_t userID);
void Password_Clear(void);#endif
// password.c
#include "password.h"
#include "string.h"
#include "stdio.h"static PasswordEntry PasswordDatabase[MAX_USERS];
static uint16_t PasswordCount = 0;// 初始化密码数据库
void Password_Init(void) {memset(PasswordDatabase, 0, sizeof(PasswordDatabase));PasswordCount = 0;// 从Flash加载密码数据库(这里简化处理,实际应该使用Flash存储)// 默认添加一个管理员密码Password_Set(0, "123456");
}// 验证密码
uint8_t Password_Verify(const char *password, uint16_t *userID) {if(password == NULL || strlen(password) == 0) {return 0;}for(int i = 0; i < MAX_USERS; i++) {if(PasswordDatabase[i].isActive) {if(strcmp(PasswordDatabase[i].password, password) == 0) {if(userID != NULL) {*userID = PasswordDatabase[i].userID;}return 1;}}}return 0;
}// 设置密码
uint8_t Password_Set(uint16_t userID, const char *password) {if(password == NULL || strlen(password) == 0 || strlen(password) > MAX_PASSWORD_LENGTH) {return 0;}// 检查用户是否已存在for(int i = 0; i < MAX_USERS; i++) {if(PasswordDatabase[i].isActive && PasswordDatabase[i].userID == userID) {// 更新密码strncpy(PasswordDatabase[i].password, password, MAX_PASSWORD_LENGTH);return 1;}}// 添加新用户for(int i = 0; i < MAX_USERS; i++) {if(!PasswordDatabase[i].isActive) {PasswordDatabase[i].userID = userID;strncpy(PasswordDatabase[i].password, password, MAX_PASSWORD_LENGTH);PasswordDatabase[i].isActive = 1;PasswordCount++;return 1;}}return 0; // 数据库已满
}// 删除密码
uint8_t Password_Delete(uint16_t userID) {for(int i = 0; i < MAX_USERS; i++) {if(PasswordDatabase[i].isActive && PasswordDatabase[i].userID == userID) {memset(&PasswordDatabase[i], 0, sizeof(PasswordEntry));PasswordCount--;return 1;}}return 0;
}// 清空密码库
void Password_Clear(void) {memset(PasswordDatabase, 0, sizeof(PasswordDatabase));PasswordCount = 0;
}
六、OLED显示驱动
6.1 OLED显示模块
我们使用0.96寸OLED屏幕(SSD1306驱动芯片),通过I2C接口通信。
// oled.h
#ifndef __OLED_H
#define __OLED_H#include "stm32f4xx_hal.h"#define OLED_ADDRESS 0x78 // I2C地址void OLED_Init(I2C_HandleTypeDef *hi2c);
void OLED_Clear(void);
void OLED_ShowString(uint8_t x, uint8_t y, const char *str);
void OLED_ShowNumber(uint8_t x, uint8_t y, uint32_t num, uint8_t len);
void OLED_ShowStatus(const char *status);#endif
// oled.c (简化版本)
#include "oled.h"
#include "string.h"
#include "stdio.h"static I2C_HandleTypeDef *oled_i2c;// 8x16字体
const uint8_t Font8x16[][16] = {// ASCII字符字模数据...(这里省略,实际需要完整字库)
};void OLED_WriteCmd(uint8_t cmd) {uint8_t data[2] = {0x00, cmd};HAL_I2C_Master_Transmit(oled_i2c, OLED_ADDRESS, data, 2, 100);
}void OLED_WriteData(uint8_t data) {uint8_t buf[2] = {0x40, data};HAL_I2C_Master_Transmit(oled_i2c, OLED_ADDRESS, buf, 2, 100);
}void OLED_Init(I2C_HandleTypeDef *hi2c) {oled_i2c = hi2c;HAL_Delay(100);OLED_WriteCmd(0xAE); // 关闭显示OLED_WriteCmd(0x20); // 设置内存地址模式OLED_WriteCmd(0x10);OLED_WriteCmd(0xB0); // 设置页地址OLED_WriteCmd(0xC8); // 设置COM扫描方向OLED_WriteCmd(0x00);OLED_WriteCmd(0x10);OLED_WriteCmd(0x40); // 设置起始行地址OLED_WriteCmd(0x81); // 设置对比度OLED_WriteCmd(0xFF);OLED_WriteCmd(0xA1); // 设置段重映射OLED_WriteCmd(0xA6); // 正常显示OLED_WriteCmd(0xA8); // 设置复用率OLED_WriteCmd(0x3F);OLED_WriteCmd(0xA4); // 全局显示开启OLED_WriteCmd(0xD3); // 设置显示偏移OLED_WriteCmd(0x00);OLED_WriteCmd(0xD5); // 设置时钟分频因子OLED_WriteCmd(0xF0);OLED_WriteCmd(0xD9); // 设置预充电周期OLED_WriteCmd(0x22);OLED_WriteCmd(0xDA); // 设置COM脚硬件配置OLED_WriteCmd(0x12);OLED_WriteCmd(0xDB); // 设置VCOMH电压倍率OLED_WriteCmd(0x20);OLED_WriteCmd(0x8D); // 使能充电泵OLED_WriteCmd(0x14);OLED_WriteCmd(0xAF); // 开启显示OLED_Clear();
}void OLED_Clear(void) {for(uint8_t i = 0; i < 8; i++) {OLED_WriteCmd(0xB0 + i);OLED_WriteCmd(0x00);OLED_WriteCmd(0x10);for(uint8_t n = 0; n < 128; n++) {OLED_WriteData(0x00);}}
}void OLED_ShowString(uint8_t x, uint8_t y, const char *str) {// 显示字符串实现...// 这里简化,实际需要根据字库显示
}void OLED_ShowStatus(const char *status) {OLED_Clear();OLED_ShowString(0, 0, "Smart Lock System");OLED_ShowString(0, 2, "Status:");OLED_ShowString(0, 4, status);
}
七、门锁控制系统
7.1 步进电机控制
// motor.h
#ifndef __MOTOR_H
#define __MOTOR_H#include "stm32f4xx_hal.h"typedef enum {MOTOR_DIR_LOCK, // 上锁MOTOR_DIR_UNLOCK // 开锁
} MotorDirection;void Motor_Init(void);
void Motor_Lock(void);
void Motor_Unlock(void);
void Motor_Stop(void);
uint8_t Motor_GetStatus(void);#endif
// motor.c
#include "motor.h"// 步进电机相序(四相八拍)
static const uint8_t StepSequence[8][4] = {{1, 0, 0, 0},{1, 1, 0, 0},{0, 1, 0, 0},{0, 1, 1, 0},{0, 0, 1, 0},{0, 0, 1, 1},{0, 0, 0, 1},{1, 0, 0, 1}
};static uint8_t CurrentStep = 0;
static uint8_t IsLocked = 1;void Motor_Init(void) {GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = MOTOR_IN1_PIN | MOTOR_IN2_PIN | MOTOR_IN3_PIN | MOTOR_IN4_PIN;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(MOTOR_GPIO_PORT, &GPIO_InitStruct);Motor_Stop();
}static void Motor_Step(uint8_t step) {HAL_GPIO_WritePin(MOTOR_GPIO_PORT, MOTOR_IN1_PIN, StepSequence[step][0] ? GPIO_PIN_SET : GPIO_PIN_RESET);HAL_GPIO_WritePin(MOTOR_GPIO_PORT, MOTOR_IN2_PIN, StepSequence[step][1] ? GPIO_PIN_SET : GPIO_PIN_RESET);HAL_GPIO_WritePin(MOTOR_GPIO_PORT, MOTOR_IN3_PIN, StepSequence[step][2] ? GPIO_PIN_SET : GPIO_PIN_RESET);HAL_GPIO_WritePin(MOTOR_GPIO_PORT, MOTOR_IN4_PIN, StepSequence[step][3] ? GPIO_PIN_SET : GPIO_PIN_RESET);
}static void Motor_Rotate(MotorDirection dir, uint16_t steps) {for(uint16_t i = 0; i < steps; i++) {if(dir == MOTOR_DIR_UNLOCK) {CurrentStep++;if(CurrentStep >= 8) CurrentStep = 0;} else {if(CurrentStep == 0) CurrentStep = 7;else CurrentStep--;}Motor_Step(CurrentStep);HAL_Delay(2); // 步进延时,控制速度}
}void Motor_Lock(void) {if(!IsLocked) {Motor_Rotate(MOTOR_DIR_LOCK, 512); // 旋转90度(根据实际电机调整)IsLocked = 1;Motor_Stop();}
}void Motor_Unlock(void) {if(IsLocked) {Motor_Rotate(MOTOR_DIR_UNLOCK, 512); // 旋转90度IsLocked = 0;// 5秒后自动上锁HAL_Delay(5000);Motor_Lock();}
}void Motor_Stop(void) {HAL_GPIO_WritePin(MOTOR_GPIO_PORT, MOTOR_IN1_PIN, GPIO_PIN_RESET);HAL_GPIO_WritePin(MOTOR_GPIO_PORT, MOTOR_IN2_PIN, GPIO_PIN_RESET);HAL_GPIO_WritePin(MOTOR_GPIO_PORT, MOTOR_IN3_PIN, GPIO_PIN_RESET);HAL_GPIO_WritePin(MOTOR_GPIO_PORT, MOTOR_IN4_PIN, GPIO_PIN_RESET);
}uint8_t Motor_GetStatus(void) {return IsLocked;
}
八、主程序实现
8.1 主函数
// main.c
#include "main.h"
#include "as608.h"
#include "face_recognition.h"
#include "keypad.h"
#include "password.h"
#include "oled.h"
#include "motor.h"
#include "stdio.h"
#include "string.h"// 外设句柄
UART_HandleTypeDef huart2; // 人脸识别
UART_HandleTypeDef huart3; // 指纹模块
I2C_HandleTypeDef hi2c1; // OLED// 认证模式
typedef enum {AUTH_MODE_IDLE,AUTH_MODE_FINGERPRINT,AUTH_MODE_FACE,AUTH_MODE_PASSWORD
} AuthMode;// 系统状态
typedef struct {AuthMode currentMode;uint8_t isUnlocked;uint32_t lastActivityTime;uint16_t authenticatedUserID;
} SystemStatus;static SystemStatus sysStatus = {.currentMode = AUTH_MODE_IDLE,.isUnlocked = 0,.lastActivityTime = 0,.authenticatedUserID = 0xFFFF
};// 函数声明
void SystemClock_Config(void);
void GPIO_Init(void);
void USART2_Init(void);
void USART3_Init(void);
void I2C1_Init(void);
void Process_Fingerprint_Auth(void);
void Process_Face_Auth(void);
void Process_Password_Auth(void);
void Update_Display(void);
void Check_Timeout(void);int main(void) {// 初始化HAL库HAL_Init();SystemClock_Config();// 初始化外设GPIO_Init();USART2_Init();USART3_Init();I2C1_Init();// 初始化各模块AS608_Init(&huart3);Face_Init(&huart2);Keypad_Init();Password_Init();OLED_Init(&hi2c1);Motor_Init();// 显示欢迎信息OLED_ShowStatus("System Ready");printf("智能门锁系统启动\r\n");HAL_Delay(2000);// 主循环while(1) {// 更新显示Update_Display();// 检测用户输入,选择认证方式uint8_t key = Keypad_GetKey();if(key == KEY_1) {sysStatus.currentMode = AUTH_MODE_FINGERPRINT;printf("切换到指纹识别模式\r\n");} else if(key == KEY_2) {sysStatus.currentMode = AUTH_MODE_FACE;printf("切换到人脸识别模式\r\n");} else if(key == KEY_3) {sysStatus.currentMode = AUTH_MODE_PASSWORD;printf("切换到密码输入模式\r\n");}// 处理对应的认证模式switch(sysStatus.currentMode) {case AUTH_MODE_FINGERPRINT:Process_Fingerprint_Auth();break;case AUTH_MODE_FACE:Process_Face_Auth();break;case AUTH_MODE_PASSWORD:Process_Password_Auth();break;default:break;}// 检查超时Check_Timeout();HAL_Delay(50);}
}// 指纹认证处理
void Process_Fingerprint_Auth(void) {OLED_ShowStatus("Place Finger...");AS608_SearchResult result;uint8_t ack = AS608_VerifyFingerprint(&result);if(ack == AS608_ACK_SUCCESS) {printf("指纹识别成功!用户ID: %d, 得分: %d\r\n", result.pageID, result.matchScore);sysStatus.authenticatedUserID = result.pageID;sysStatus.isUnlocked = 1;sysStatus.lastActivityTime = HAL_GetTick();OLED_ShowStatus("Auth Success!");Motor_Unlock();HAL_Delay(2000);sysStatus.currentMode = AUTH_MODE_IDLE;} else if(ack == AS608_ACK_NOT_FOUND) {printf("指纹不匹配\r\n");OLED_ShowStatus("Auth Failed!");HAL_Delay(1000);sysStatus.currentMode = AUTH_MODE_IDLE;} else if(ack == AS608_ACK_NO_FINGER) {// 没有检测到手指,继续等待}
}// 人脸认证处理
void Process_Face_Auth(void) {OLED_ShowStatus("Face Detecting...");// 启动人脸识别Face_StartRecognition();FaceRecognitionResult result;uint32_t startTime = HAL_GetTick();// 等待识别结果(最多10秒)while(HAL_GetTick() - startTime < 10000) {if(Face_GetResult(&result)) {if(result.similarity >= 0.75f) {printf("人脸识别成功!用户ID: %d, 相似度: %.2f\r\n", result.userID, result.similarity);sysStatus.authenticatedUserID = result.userID;sysStatus.isUnlocked = 1;sysStatus.lastActivityTime = HAL_GetTick();OLED_ShowStatus("Auth Success!");Motor_Unlock();HAL_Delay(2000);sysStatus.currentMode = AUTH_MODE_IDLE;return;} else {printf("人脸不匹配\r\n");OLED_ShowStatus("Auth Failed!");HAL_Delay(1000);sysStatus.currentMode = AUTH_MODE_IDLE;return;}}HAL_Delay(100);}// 超时printf("人脸识别超时\r\n");OLED_ShowStatus("Timeout!");HAL_Delay(1000);sysStatus.currentMode = AUTH_MODE_IDLE;
}// 密码认证处理
void Process_Password_Auth(void) {OLED_ShowStatus("Enter Password:");static char password[MAX_PASSWORD_LENGTH + 1] = {0};static uint8_t pwdIndex = 0;uint8_t key = Keypad_GetKey();if(key >= KEY_0 && key <= KEY_9) {if(pwdIndex < MAX_PASSWORD_LENGTH) {password[pwdIndex++] = '0' + key;password[pwdIndex] = '\0';// 显示星号char display[32];sprintf(display, "PWD: ");for(int i = 0; i < pwdIndex; i++) {strcat(display, "*");}OLED_ShowStatus(display);}} else if(key == KEY_HASH) {// 确认输入uint16_t userID;if(Password_Verify(password, &userID)) {printf("密码验证成功!用户ID: %d\r\n", userID);sysStatus.authenticatedUserID = userID;sysStatus.isUnlocked = 1;sysStatus.lastActivityTime = HAL_GetTick();OLED_ShowStatus("Auth Success!");Motor_Unlock();} else {printf("密码错误\r\n");OLED_ShowStatus("Wrong Password!");}// 清空密码memset(password, 0, sizeof(password));pwdIndex = 0;HAL_Delay(2000);sysStatus.currentMode = AUTH_MODE_IDLE;} else if(key == KEY_STAR) {// 取消输入memset(password, 0, sizeof(password));pwdIndex = 0;sysStatus.currentMode = AUTH_MODE_IDLE;}
}// 更新显示
void Update_Display(void) {static uint32_t lastUpdate = 0;if(HAL_GetTick() - lastUpdate > 1000) {lastUpdate = HAL_GetTick();if(sysStatus.currentMode == AUTH_MODE_IDLE) {if(sysStatus.isUnlocked) {OLED_ShowStatus("Door Unlocked");} else {OLED_ShowStatus("Press Key:\n1-Finger 2-Face\n3-Password");}}}
}// 检查超时
void Check_Timeout(void) {// 如果门已开启超过30秒,自动上锁if(sysStatus.isUnlocked) {if(HAL_GetTick() - sysStatus.lastActivityTime > 30000) {Motor_Lock();sysStatus.isUnlocked = 0;sysStatus.authenticatedUserID = 0xFFFF;printf("超时自动上锁\r\n");}}// 如果在认证模式下超过15秒无操作,返回空闲模式if(sysStatus.currentMode != AUTH_MODE_IDLE) {if(HAL_GetTick() - sysStatus.lastActivityTime > 15000) {sysStatus.currentMode = AUTH_MODE_IDLE;printf("认证超时,返回空闲模式\r\n");}}
}// 系统时钟配置
void SystemClock_Config(void) {RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};__HAL_RCC_PWR_CLK_ENABLE();__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLM = 8;RCC_OscInitStruct.PLL.PLLN = 336;RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;RCC_OscInitStruct.PLL.PLLQ = 7;HAL_RCC_OscConfig(&RCC_OscInitStruct);RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
}// GPIO初始化
void GPIO_Init(void) {__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOE_CLK_ENABLE();
}// USART2初始化(人脸识别)
void USART2_Init(void) {huart2.Instance = USART2;huart2.Init.BaudRate = 115200;huart2.Init.WordLength = UART_WORDLENGTH_8B;huart2.Init.StopBits = UART_STOPBITS_1;huart2.Init.Parity = UART_PARITY_NONE;huart2.Init.Mode = UART_MODE_TX_RX;huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;HAL_UART_Init(&huart2);
}// USART3初始化(指纹模块)
void USART3_Init(void) {huart3.Instance = USART3;huart3.Init.BaudRate = 57600;huart3.Init.WordLength = UART_WORDLENGTH_8B;huart3.Init.StopBits = UART_STOPBITS_1;huart3.Init.Parity = UART_PARITY_NONE;huart3.Init.Mode = UART_MODE_TX_RX;huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;HAL_UART_Init(&huart3);
}// I2C1初始化(OLED)
void I2C1_Init(void) {hi2c1.Instance = I2C1;hi2c1.Init.ClockSpeed = 400000;hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;hi2c1.Init.OwnAddress1 = 0;hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;HAL_I2C_Init(&hi2c1);
}
九、用户管理系统
为了管理多个用户,我们需要实现一个用户管理系统:
// user_manager.h
#ifndef __USER_MANAGER_H
#define __USER_MANAGER_H#include "stm32f4xx_hal.h"#define MAX_USER_COUNT 50typedef enum {AUTH_TYPE_FINGERPRINT = 0x01,AUTH_TYPE_FACE = 0x02,AUTH_TYPE_PASSWORD = 0x04,AUTH_TYPE_ALL = 0x07
} AuthType;typedef struct {uint16_t userID;char userName[32];uint8_t authMethods; // 位掩码,表示启用的认证方式uint8_t isAdmin;uint8_t isActive;uint32_t lastAccessTime;
} UserInfo;void UserManager_Init(void);
uint8_t UserManager_Add(uint16_t userID, const char *name, uint8_t authMethods, uint8_t isAdmin);
uint8_t UserManager_Delete(uint16_t userID);
uint8_t UserManager_GetInfo(uint16_t userID, UserInfo *info);
uint8_t UserManager_CheckAuth(uint16_t userID, AuthType authType);
void UserManager_UpdateAccessTime(uint16_t userID);
uint16_t UserManager_GetCount(void);#endif
十、数据存储方案
10.1 Flash存储
为了在断电后保留用户数据,我们需要将数据存储到Flash中:
// flash_storage.h
#ifndef __FLASH_STORAGE_H
#define __FLASH_STORAGE_H#include "stm32f4xx_hal.h"// Flash存储地址定义
#define FLASH_USER_START_ADDR 0x08080000 // 扇区8起始地址
#define FLASH_USER_END_ADDR 0x080FFFFF // 扇区11结束地址uint8_t Flash_WriteData(uint32_t address, uint8_t *data, uint32_t length);
uint8_t Flash_ReadData(uint32_t address, uint8_t *data, uint32_t length);
uint8_t Flash_EraseData(uint32_t address);#endif
十一、安全机制
11.1 防暴力破解
实现尝试次数限制:
#define MAX_AUTH_ATTEMPTS 5
#define LOCKOUT_TIME_MS 300000 // 5分钟static uint8_t authAttempts = 0;
static uint32_t lockoutEndTime = 0;uint8_t CheckAuthLockout(void) {if(HAL_GetTick() < lockoutEndTime) {return 0; // 仍在锁定期}if(authAttempts >= MAX_AUTH_ATTEMPTS) {lockoutEndTime = HAL_GetTick() + LOCKOUT_TIME_MS;authAttempts = 0;return 0;}return 1; // 可以进行认证
}void RecordAuthAttempt(uint8_t success) {if(success) {authAttempts = 0;} else {authAttempts++;}
}
11.2 日志记录
记录所有开锁操作:
typedef struct {uint32_t timestamp;uint16_t userID;AuthType authType;uint8_t success;
} AccessLog;void Log_RecordAccess(uint16_t userID, AuthType authType, uint8_t success) {AccessLog log;log.timestamp = HAL_GetTick();log.userID = userID;log.authType = authType;log.success = success;// 保存到Flash或发送到服务器
}
十二、系统调试与测试
12.1 调试技巧
在开发过程中,我遇到了一些问题,总结几个调试技巧:
- 串口日志:所有关键操作都输出日志,便于定位问题
- LED指示:用LED指示各模块工作状态
- 单步测试:先单独测试每个模块,再整合
- 电源质量:确保电源稳定,不要用劣质电源
12.2 常见问题
问题1:指纹模块通信失败
- 检查波特率是否正确
- 检查RX/TX是否接反
- 确认供电电压(3.3V或5V)
问题2:人脸识别误识别率高
- 增加训练样本数量
- 调整阈值参数
- 改善光照条件
问题3:电机不转或抖动
- 检查步进顺序是否正确
- 调整步进延时
- 确认驱动电流是否足够
十三、性能优化
13.1 响应速度优化
- 指纹识别:< 1秒
- 人脸识别:< 2秒
- 密码验证:即时
通过RTOS可以进一步优化多任务响应。
13.2 功耗优化
待机模式下功耗优化:
- 关闭摄像头
- 降低MCU频率
- 使用低功耗模式
十四、总结与展望
这套智能门锁系统实现了指纹、人脸、密码三种认证方式,具备较高的安全性和便利性。整个项目涉及的技术点包括:
- 嵌入式硬件设计
- STM32底层驱动开发
- 通信协议解析
- 人工智能算法应用
- 系统架构设计
后续可以扩展的功能:
- 增加NFC/蓝牙开锁
- 联网远程控制
- 手机APP管理
- 临时密码生成
- 联动智能家居
整个项目代码量约5000行,核心代码都已给出。实际使用时需要根据具体硬件调整参数。希望这篇文章对大家有帮助!
附录:完整工程文件结构
SmartLock/
├── Core/
│ ├── Inc/
│ │ ├── main.h
│ │ ├── stm32f4xx_hal_conf.h
│ │ └── stm32f4xx_it.h
│ └── Src/
│ ├── main.c
│ ├── stm32f4xx_hal_msp.c
│ └── stm32f4xx_it.c
├── Drivers/
│ ├── AS608/
│ │ ├── as608.h
│ │ └── as608.c
│ ├── Face/
│ │ ├── face_recognition.h
│ │ └── face_recognition.c
│ ├── Keypad/
│ │ ├── keypad.h
│ │ └── keypad.c
│ ├── OLED/
│ │ ├── oled.h
│ │ └── oled.c
│ └── Motor/
│ ├── motor.h
│ └── motor.c
├── App/
│ ├── password.h
│ ├── password.c
│ ├── user_manager.h
│ ├── user_manager.c
│ ├── flash_storage.h
│ └── flash_storage.c
├── K210/
│ └── face_recognition.py
└── README.md
注意事项:
- 代码仅供学习参考,实际产品需要更完善的安全机制
- 硬件连接务必仔细核对,避免损坏元器件
- 调试过程建议使用J-Link等专业调试器
- 生产环境需要加密存储和通信
有问题欢迎留言交流!
