STM32指纹密码锁开发实战:AS608模块对接、GSM报警与矩阵键盘设计

发布时间:2026/9/16 1:09:51
STM32指纹密码锁开发实战:AS608模块对接、GSM报警与矩阵键盘设计 简介这套STM32指纹密码锁程序是一套完整的嵌入式安全系统方案主要面向智能锁开发者、电子设计竞赛学生以及物联网爱好者将AS608指纹识别、GSM远程报警、密码管理与蜂鸣器本地告警集于一体适用于家庭门禁、办公安防等场景。资源包共101个文件以44个.h头文件和43个.c源文件为主对应STM32标准外设库驱动、AS608指纹通信、GSM模块AT指令控制等核心代码另有工程配置与编译脚本整体仅339KB轻量便于直接移植。目前已有576人学习下载适合用于STM32项目实战与课程设计参考。代码基于Keil工程组织模块划分清晰完整呈现了从指纹录入、密码校验、异常触发到短信报警的整个链路用户拿到后既可烧录到开发板验证功能也可参照其状态机与中断设计扩展自己的智能锁应用综合了解生物识别与远程告警的嵌入式实现方式。1. 一套 STM32 指纹密码锁的完整方案长什么样把 AS608 指纹模块、矩阵键盘、GSM 短信报警和电磁锁控制塞进一块 STM32 里是这个项目标题背后的真实需求门禁不只认指纹还得留密码后路异常时得能拨号报警。这类系统在实验室门禁、宿舍柜锁、毕业设计展品里是最常见的一套组合难点不在单个模块而在把三个外设的时序、电源、错误处理整合进一个主循环里。AS608 负责指纹录入与比对矩阵键盘承担密码输入GSM 模块常见 SIM800C / SIM900A 系列在连续验证失败或锁体被撬时发短信给预设号码。STM32 在这里的角色不是算力担当而是调度中枢串口 1 接指纹、串口 2 接 GSM、GPIO 扫描键盘、继电器控制锁舌。程序规模不大但牵扯到串口分包、Flash 存储、AT 指令时序这三类基本功恰好是嵌入式岗笔试和面试最爱考的东西。这篇不照着原工程一行行读而是把如果你要从零搭一套的完整路径讲清楚AS608 怎么发送指令、密码存哪、GSM 怎样发短信、三个外设怎么在一个循环里和谐共处、现场调试盯哪些点。适合正在调 AS608 但协议不熟的人也适合想抄一套健壮状态机做门禁项目的工程师。2. AS608 指纹模块与 STM32 的串口协议对接2.1 AS608 的指令包结构AS608 用的是自定义串口协议不是 Modbus也不是简单的发 0x01 返回 0x00。它要求设备地址 包标识 长度 指令 参数 校验和并且校验和是从包标识异或到最后一个参数。初次调这个模块的人十有八九是栽在校验和算错或应答帧长度解析错上。指令包格式固定为包头F5 55 AA命令码占 1 字节随后是参数最后 1 字节是校验和从包标识异或到参数末尾。应答帧结构相同但中间夹着确认码00表示成功01表示收包错误0B表示指纹库已满这类语义。下面这段是发送一条完整指令并接收应答的最小 C 实现按 AS608 手册的通用格式封装#define AS608_HEAD1 0xF5 #define AS608_HEAD2 0x55 #define AS608_HEAD3 0xAA #define AS608_ADDR 0xFFFFFFFF uint8_t as608_send_cmd(uint8_t cmd, uint8_t *params, uint8_t len) { uint8_t buf[32]; uint8_t sum 0, i; uint8_t resp[16]; uint16_t timeout 500; buf[0] AS608_HEAD1; buf[1] AS608_HEAD2; buf[2] AS608_HEAD3; buf[3] 0x01; // 包标识命令包固定 0x01 buf[4] (len 3) 0xFF; // 长度 命令码 参数 校验和 buf[5] cmd; sum buf[3] ^ buf[4] ^ buf[5]; // 校验起始包标识异或到命令码 for (i 0; i len; i) { buf[6 i] params[i]; sum ^ params[i]; } buf[6 len] sum; uart_dma_send(USART1, buf, 7 len); // 等待应答帧这里用超时接收 if (uart_recv_timeout(USART1, resp, 12, timeout) ! 0) { return 0xFF; // 超时模块没响应 } return resp[9]; // 应答帧第 10 字节是确认码 }广播地址0xFFFFFFFF在代码里没有参与报文拼接因为 AS608 默认帧格式里地址被省略实际是无地址模式所以包标识后直接是长度。长度字段计算的是从命令码到校验和的字节数也就是len 3。确认码的位置需要拿逻辑分析仪抓一帧真实应答确认不同批次模块的应答帧确认码在第几个字节略有差异这是踩坑点——建议上电后先发0x01读版本号打印整帧 12 个字节十六进制值再定位偏移。2.2 指纹录入六步指令序列指纹录入不是发一条指令就完事而是六步操作收集图像、生成特征、生成模板、存储模板、再次收集图像、合并模板。AS608 的固件把中间状态保存在模块内部缓冲区MCU 侧看到的是一串状态机跳转。指令码含义参数典型应答0x01读版本号无确认码 版本字符串0x02收集指纹图像无00 成功 / 01 收包错误0x05生成特征存缓冲区BufferID1 或 200 成功 / 0B 指纹太干或太湿0x06生成模板BufferID 1, 200 成功 / 0B 合并失败0x0A存储模板BufferID, PageID00 成功 / 0B 库满0x0B删除模板PageID, 数量00 成功 / 0B 地址错误0x0C清空指纹库无00 成功0x13搜索指纹BufferID, 起始页, 页数00 页码 得分录入流程代码示意uint8_t enroll_finger(uint16_t page_id) { uint8_t p1[2]; if (as608_send_cmd(0x02, NULL, 0) ! 0x00) return 1; // 按第一次手指 if (as608_send_cmd(0x05, (uint8_t[]){0x01}, 1) ! 0x00) return 2; // 生成特征到缓冲区1 delay_ms(800); // 提示换手指 if (as608_send_cmd(0x02, NULL, 0) ! 0x00) return 3; // 按第二次手指 if (as608_send_cmd(0x05, (uint8_t[]){0x02}, 1) ! 0x00) return 4; // 生成特征到缓冲区2 if (as608_send_cmd(0x06, NULL, 0) ! 0x00) return 5; // 合成模板 p1[0] 0x01; // 模板存在缓冲区1 p1[1] page_id 0xFF; if (as608_send_cmd(0x0A, p1, 2) ! 0x00) return 6; // 存入指纹库 page_id return 0; }参数数组里BufferID固定 1 或 2PageID是指纹库的槽位号0299 是常用范围。这里有个容易忽略的细节0x05的第二个参数重试次数在很多手册版本里存在但实际固件不一定解析发送时只传 BufferID 反而最稳。如果第二次按手指时模块在 5 秒内没等到指纹图像0x05会返回超时错误码代码里需要给操作者明确的请重新按提示不能直接当失败结束。2.3 指纹搜索与识别状态机识别比录入简单采集图像 - 生成特征 - 搜索。搜索是0x13参数里带起始页和搜索页数通常填 0 和 300 表示全库搜索。uint16_t search_finger(void) { uint8_t params[4]; if (as608_send_cmd(0x02, NULL, 0) ! 0x00) return 0xFFFF; // 没按手指 if (as608_send_cmd(0x05, (uint8_t[]){0x01}, 1) ! 0x00) return 0xFFFF; params[0] 0x01; // 用缓冲区1的特征搜索 params[1] 0x00; // 起始页 params[2] 0x01; // 页数高字节 params[3] 0x2C; // 300 页0x012C if (as608_send_cmd(0x13, params, 4) 0x00) { // 确认码后面两字节是找到的 PageID return read_resp_page_id(); } return 0xFFFF; }建议把识别流程放到定时器中断里做非阻塞轮询主循环每 200ms 发一次0x02采集指令模块收到后会自动检测手指按压。这样手指放上去不用等软件触发体验接近即按即开。搜索耗时取决于库容量300 枚全库搜大约 500ms期间不能发其他 AS608 指令否则模块会回错误包。库超过 100 枚时识别率会因特征碰撞下降这时候应该按分组搜索来优化而不是一味加 PageID。3. 密码管理矩阵键盘输入、EEPROM 存储与防暴力尝试3.1 密码存哪内部 Flash 模拟 EEPROM指纹库在 AS608 内部但密码得自己存。常见做法三段选一外挂 AT24C02、用 STM32 内部 Flash 的最后两页模拟 EEPROM、直接写死在代码里。写死最省事但没法改AT24C02 要多拉一根 I2C内部 Flash 方案零成本且断电不丢中容量型号STM32F103RCT6的最后 1MB 空间擦写寿命约 1 万次对门锁这种不频繁改密的场景绰绰有余。内部 Flash 写入需要先擦除整页再写且写入过程中不能有中断所以要把存密码封装成一个关中断函数#define PASSWORD_ADDR 0x080FF800 // 最后一页起始地址 #define PASSWORD_LEN 6 // 6 位密码BCD 码存储 void save_password(uint8_t *pwd) { uint32_t temp[8]; uint8_t i; for (i 0; i 6; i) { temp[i] pwd[i]; } __disable_irq(); FLASH_Unlock(); FLASH_ErasePage(PASSWORD_ADDR); for (i 0; i 6; i) { FLASH_ProgramHalfWord(PASSWORD_ADDR i * 2, temp[i]); } FLASH_Lock(); __enable_irq(); }Flash 页大小对不同型号不一样F103 是 1KB 或 2KB 一页地址要对齐到页边界。这里用半字写入读出时直接*(uint8_t*)PASSWORD_ADDR按字节取。存的不是 ASC 码而是 BCD 码键盘1存0x01省空间也方便比对。密码校验成功后建议把 Flag 位 已修改密码 顺便写进同一页这样重启后能区分是首次上电还是已初始化避免每次开机都强制回默认密码123456。Flash 模拟 EEPROM 有个替代方案直接外挂 AT24C02。如果你不差那两块钱AT24C02 更省心读写都是字节级操作还不用关中断。选型逻辑很简单——批量生产选 Flash 方案省物料自己调试用 AT24C02 省头发。3.2 矩阵键盘扫描与输入状态机4x4 矩阵键盘的扫描用行列反转法先所有行输出低、读列有按键时得到列号再所有列输出低、读行得到行号。关键在去抖——机械键盘按下瞬间会有 5~20ms 的抖动扫描间隔 20ms 读两次一致才算有效。uint8_t key_scan(void) { uint8_t col, row, key 0xFF; uint16_t t1, t2; // 行输出低列上拉输入 set_rows_output_low(); col read_cols(); if (col 0xFF) return 0xFF; // 无按键 // 第一次读出列延时去抖 delay_ms(20); t1 read_cols(); if (t1 ! col) return 0xFF; // 反转列输出低行上拉输入 set_cols_output_low(); row read_rows(); delay_ms(20); t2 read_rows(); if (t2 ! row) return 0xFF; key decode_key(row, col); // 查表映射到 0~15 // 等待按键释放防止连发 while (read_cols() ! 0xFF || read_rows() ! 0xFF); return key; }释放检测一定要做否则一次按键会被执行好几次。输入密码时用状态机空闲、输入中、校验、已开锁。输入中每按一位存进缓冲区按#确认按*退格。超时 10 秒没按完自动清空缓冲区回到空闲这个超时细节是决定体感的关键——很多人直接调阻塞式while等按键导致指纹验证和密码输入互相卡死。正确做法是把键盘扫描放进 10ms 定时中断主循环只消费按键事件队列。这样指纹搜索的 500ms 阻塞期间密码输入也不会丢键。3.3 防暴力尝试错误锁定与掉电保持密码锁最怕穷举。6 位数密码全空间 100 万次不做限制的话脚本跑一夜就开了。常见策略连续错 3 次锁 30 秒错 5 次触发 GSM 报警。锁定状态要存 Flash否则断电重上电锁定就失效——这等于没锁。uint8_t check_pwd_lock(void) { uint8_t err_cnt; uint32_t lock_until; err_cnt read_flash_byte(ERR_CNT_ADDR); lock_until read_flash_word(LOCK_TIME_ADDR); if (err_cnt 5) { if (get_rtc_sec() lock_until) return 1; // 仍在锁定期 else { clear_lock_flags(); // 到期自动解锁 return 0; } } return 0; }锁定期按 RTC 秒数算如果板子上没有 RTC 芯片用系统定时器的 tick 累计也行但断电后 tick 归零锁定时间就得重新从 Flash 读 锁定时刻 锁定秒数 来推算。更稳妥的做法是掉电前把锁定到期时间算好写进 Flash而不是只存锁定标记。还要注意 Flash 写入次数错误计数每错一次就写一次 Flash暴力尝试时可能在几分钟内把这一页 Flash 写穿所以错误计数只存 RAM等到状态变化比如触发了锁定才落到 Flash。4. GSM 报警链路SIM800C 短信触发与 STM32 状态联动4.1 SIM800C 的接线与串口时序SIM800C 是 2G 模块2G 退网后部分区域搜不到网这是选型时要先确认的点。模块串口电平是 2.8V直接接 STM32 的 3.3V TX 可能烧模块稳妥做法是串一个 1K 电阻分压STM32 的 RX 接模块 TX 则可以直接连因为模块 2.8V 输出高于 STM32 的低电平阈值。模块上电后不能立刻发 AT 指令——它要等网络注册冷启动最慢能到 5~10 秒。初始化序列按下表逐步来每步都要等到明确应答再走下一步指令预期应答说明ATOK串口通断检测发 3 次没回应查电平ATCPIN?CPIN: READYSIM 卡识别ATCSQCSQ: 15,0信号强度第一个数大于 10 才稳ATCMGF1OK切文本模式ATCSCSGSMOK字符集中文短信需要切 UCS2信号强度CSQ: 99,0表示无信号这种情况发短信必失败要在代码里打印出来而不是闷头重试。GSM 模块在发短信时电流峰值能到 2A电源必须用独立 LDO 或 DC-DC 供电和 STM32 共用 3.3V 会直接把单片机拉复位——这是现场最多发的故障。4.2 AT 指令拼接与短信发送发送短信是拼字符串的过程核心是ATCMGS13800138000\r\n后跟短信内容再发0x1A结束。STM32 侧用 sprintf 拼好号码和内容经 UART2 发出void send_sms(const char *phone, const char *msg) { char cmd[64]; uint16_t len; sprintf(cmd, ATCMGS\%s\\r\n, phone); uart_send_string(UART2, cmd); delay_ms(300); // 等模块返回 uart_send_string(UART2, msg); uart_send_string(UART2, \r\n); uart_send_byte(UART2, 0x1A); // CtrlZ 结束 // 等待 CMGS: xxx 应答超时 10s wait_sms_ack(10000); }delay_ms(300)是等模块返回提示符不同固件版本等待时间不同保守起见串口收到字符再发内容更稳。短信内容如果含中文要先ATCSCSUCS2然后内容转 UCS2 十六进制编码每个汉字占 4 个字符。报警短信一般建议纯英文字母和数字省去编码转换的麻烦比如ALARM: DOOR OPEN!。反正门卫看号码就能知道是哪扇门。主循环里发短信必须加正在发送标志位不能在短信发送过程中再去读 AS608 或键盘串口 DMA 缓冲会互相覆盖。更规范的做法是开一个发送状态机IDLE - SENDING - WAIT_ACK - DONE主循环每个周期检查状态。4.3 报警触发条件连续失败、撬锁检测、超时未关门报警不能只在密码错时响门锁场景至少三类连续指纹/密码验证失败达阈值防尾随、锁体被撬干簧管/震动传感器输入、开门超时未关门磁检测。把这些事件统一包成一个alarm_trigger(type)void alarm_trigger(uint8_t type) { static uint8_t last_report_ts 0; uint8_t now get_tick_sec(); // 同一事件 60 秒内只报一次防短信轰炸 if (now - last_report_ts 60) return; last_report_ts now; switch (type) { case ALARM_PWD_FAIL: send_sms(PHONE_OWNER, ALARM: PWD FAIL 5 TIMES); break; case ALARM_TAMPER: send_sms(PHONE_OWNER, ALARM: TAMPER DETECT!); break; } }短信报警的最小间隔 60 秒是经验值运营商对同一号码短时间发大量短信会触发风控停机报警逻辑宁可漏发一次也不能让人工卡被锁。GSM 发送期间如果主控被打断模块可能卡在未知状态下次发 AT 指令前应补一发 0x10 字符CtrlP取消当前会话作复位。事件日志建议在报警的同时通过串口输出[EVENT] tamper at 12:03:22到调试口现场排查时日志和短信能对上号才敢下手改逻辑。5. 指纹与密码的权限合并开锁仲裁、超时回锁与掉电保持5.1 三种开锁方式的优先级与互斥一套完整的门锁开锁入口至少三路指纹匹配、密码校验、管理员指纹强制开锁。优先级上管理员指纹 普通指纹 密码。管理员指纹比如 PageID 0 和 1不仅要开锁还要允许进入添加指纹菜单普通指纹只能开锁。这个通过 PageID 区间划分uint8_t evaluate_access(uint8_t page_id) { if (page_id 1 || page_id 2) return ACCESS_ADMIN; if (page_id 100) return ACCESS_USER; return ACCESS_DENIED; }校验逻辑统一进一个access_ctrl()函数不接受外部散乱的 if 嵌套。主循环里定时器设置的标志位驱动指纹模块返回匹配成功、密码状态机走到确认态、管理员指令下发——都会调用这个函数但最终结果都是写入同一个全局变量current_access。之后只由lock_control()按这个变量决定继电器动作。5.2 开锁超时回锁与继电器控制继电器吸合让电磁锁断电或通电开锁取决于锁型这里必须处理两个问题继电器不能一直吸着锁也不能开了不管。开锁动作后延时 5 秒自动回锁——这个 5 秒用定时器计数不能用delay_ms(5000)阻塞主循环否则这 5 秒内有人按指纹、输密码全部无效。void lock_control(void) { static uint16_t unlock_cnt 0; if (current_access ! ACCESS_NONE) { relay_on(); unlock_cnt 500; // 500 * 10ms 5 秒 current_access ACCESS_NONE; } if (unlock_cnt 0) { unlock_cnt--; if (unlock_cnt 0) relay_off(); } }电磁锁的开锁信号是脉冲式的继电器持续吸合会让线圈发热甚至烧毁所以开锁后继电器最多吸合 5 秒之后不管有没有人进来都断电。门磁传感器干簧管接一个 GPIO 外部中断检测到开门状态持续超过 30 秒触发ALARM_OPEN_TIMEOUT——这是防止门被掩住后忘了关的关键兜底。5.3 独立看门狗与异常复位恢复恶意输入、静电、电源毛刺都可能让 STM32 跑飞。指纹锁这类安防设备必须开 IWDG 独立看门狗喂狗只能放在主循环空闲位置。注意别在中断里喂狗——如果主循环已经卡死中断还能跑的话看门狗就形同虚设。void main_loop(void) { while (1) { process_key_event(); // 键盘事件 poll_as608(); // 非阻塞指纹轮询 check_alarm_inputs(); // 撬锁 / 门磁检测 update_rtc_tick(); IWDG_ReloadCounter(); // 只有走到这里才喂狗 } }掉电保持除了前面说的密码存 Flash管理员指纹的允许录入标志也要掉电复位——重新上电后自动回到安全状态需要重新验证管理员才能进入配置菜单。否则有人拆开外壳断电重启就能绕过密码锁定那就闹笑话了。6. 现场调试AS608 不识别、GSM 无响应、按键乱码的定位手法6.1 AS608 不应答先抓物理层再抓协议层模块不回应九成是接线问题而不是代码问题。先拿万用表量模块 VCC 和 GND确认供电在 3.6~5V 之间且纹波小于 100mV再把 STM32 的串口 TX、RX 和模块交叉对接确认没接成同向。物理层没问题后用 USB-TTL 直接连 AS608电脑端发一帧F5 55 AA 01 03 01 03读版本号看有没有返回值。这一招能把问题精确隔离在模块本身还是单片机侧。AS608 这部分常见的误诊是把模块返回的 FF FF FF FF当成无响应其实那是模块在忙或帧头解析失败。抓串口数据的正确姿势是逻辑分析仪挂在 TX 上波特率设 57600看发送帧是否完整。确认码返回0x01表示收包错误基本都是校验和算错用 printf 把每一帧逐字节打印[TX] F5 55 AA ... sum0x3D和手册对一遍马上能发现。6.2 GSM 收不到短信信号 vs 供电优先级GSM 的网络注册和短信收发是两个独立问题。先发ATCSQ看信号强度第一个返回数低于 10 说明在室内/地下室换天线或移到窗边。信号没问题但短信发不出用串口抓模块返回CMS ERROR: 512是 SIM 卡欠费或没开通短信CMS ERROR: 513是短信中心号码没配。模块所有指令都返回 ERROR则大概率是串口电平不对或供电电流不够。实测很常遇到的一种怪现象GSM 模块单独测试正常一接 STM32 就重启。这是共地不良或电源纹波过大。给模块供电用独立的LM2596或MP1584降压模块输入侧和主控板用同一个 12V 电源但压降路径分开同时把模块的VDD_EXT通过 100uF 电容对地滤波。6.3 矩阵键盘乱码按键抖动和行列反转时序键盘按键每次按下去输出两个不同键值是先消抖后反转的时序写错了——特别注意行列反转时必须先停掉 GPIO 的输出模式再切换为输入模式否则瞬间短路会把按键值读成0x00软件上多等 1ms 再做第二次读取就稳了。矩阵键盘有鬼键按下某个键相邻两个键同时触发检查行列二极管有没有焊反。如果是买的成品键盘模块短路时测一下各行列的默认电平状态再改列映射表。调试建议在工程里开放一个debug.c把串口打印、AS608 帧日志、GSM AT 指令回显、键盘按键队列全部打上时间戳DEBUG_LEVEL宏控制打印量。现场问题只要把串口日志拉回来按时间线把事件对上号80% 的故障不用拆机就能定位。最后留一根调试串口的排针在主板上这个习惯能省掉大量来回烧固件的时间。本文还有配套的精品资源点击获取