
简介围绕STM32C8T6与AS608指纹模块展开的识别工程适合嵌入式课程设计、毕业设计及刚接触串口外设的开发者用于打通指纹模块供电、串口收发、指纹录入与比对、状态引脚检测等环节。压缩包共211个文件、约7.04MB以35个C源码和37个头文件为主体配套Keil工程文件、汇编启动文件、HEX烧录文件以及编译产生的中间文件与映射文件保留较完整的工程组织方式。已有1914人学习反映该案例在同类实践中具有一定参考面。读者可据此了解AS608的Vi、Tx、Rx、GND、WAK、Vt等引脚定义与TTL串口连接思路参考模块控制协议完成指纹采集、特征存储和比对流程并可用于对照协议帧格式、指令包与应答状态梳理指纹模板管理、删除与清空等常用操作借助现成工程可快速编译验证减少环境搭建和底层驱动排查时间也便于移植到其他STM32F103C8T6最小系统板。1. 从一枚指纹到一把锁这套方案要解决的是什么STM32C8T6 加 AS608 的指纹识别组合几乎是嵌入式课设和毕设里被问得最多的一套硬件搭配。真正动手做下去的人很快会发现难点并不在于「让模块认出指纹」而在于认出来之后那一整套连锁动作开锁、记考勤、删用户、断电不丢数据、误触不误开。这套方案要交付的就是这样一条完整链路——C8T6 做主控AS608 负责采集与比对OLED 承担交互继电器驱动锁体AT24C02 保存用户表。它适合两类人一类是要交一份能演示、能答辩的课设或毕设的学生另一类是要做小批量门禁、考勤样机需要一套能直接改的底层框架的从业者。后面的内容按架构、协议、状态机、避坑、验收的顺序展开每一段都对应到能写进代码或写进报告的具体做法。2. 总体架构主控、指纹模组、交互与执行的四层拆分先把硬件和职责定下来后面的代码才有地方放。这一章解决两个问题C8T6 这点资源够不够用以及四个部分之间到底谁指挥谁。2.1 选型与资源盘点64KB Flash 和 20KB SRAM 怎么分配STM32F103C8T6 是 Cortex-M3 内核主频 72MHz64KB Flash、20KB SRAM3 路 USART、2 路 SPI、2 路 I2C、37 个 GPIO。这套参数放在指纹项目里刚好卡在「够用但不宽裕」的位置所以每一项都要提前算清楚不要等到编译报region RAM overflowed才回头删功能。我一般按下面的方式分配资源占用内容估算FlashHAL 库 业务代码 字库2836KBFlash预留数据区日志、参数备份8KBSRAM栈 堆1.5KBSRAMOLED 显存128×64/81024BSRAM串口环形缓冲512BSRAM业务结构体与临时缓冲约 2KBSSD1306 的显存是 1024 字节这一项经常被忽略如果还要做中文字库的取模显示Flash 至少再准备 610KB。想省掉这部分就把常用汉字做成 16×16 点阵的小字库只取提示语用到的那几十个字。AS608 是光学式指纹模块TTL 串口电平默认波特率 57600模板容量常见版本是 300 枚比对时间在 1 秒以内模块内部自带特征提取和比对能力。这句话是整套方案的地基比对是在模块内部完成的MCU 只负责发指令、收结果、做决策。很多人一开始把它当成纯图像传感器想着自己在上位机做匹配算法那完全是另一条技术路线成本和难度都翻好几倍。供电是选型里最容易被轻视的一环。AS608 峰值电流能到 120mA 左右加上 OLED 和继电器线圈整板 3.3V 轨的余量要给足。通常的做法是 5V 输入后经一片 500mA 以上的 LDO 降到 3.3V模块电源脚旁边并 100µF 电解加 0.1µF 瓷片继电器单独用一路三极管或光耦驱动线圈两端反并一只续流二极管。这几处省下来的钱最后都会在「为什么偶尔搜不到手指」上还回去。引脚分配建议固定成一张表贴在工程目录里改板时照抄功能外设引脚备注AS608 RXUSART2_TXPA2交叉连接AS608 TXUSART2_RXPA3中断或 DMA 接收OLED SCLI2C1PB6与 EEPROM 共用OLED SDAI2C1PB7上拉 4.7KAT24C02I2C1PB6/PB7器件地址 0x50按键 13GPIO 输入PA4/PA5/PA6内部上拉低有效蜂鸣器GPIO 或 TIM PWMPB1有源蜂鸣器直接推继电器GPIO 输出PB0经三极管驱动调试口USART1PA9/PA10打印日志I2C 总线上挂两个器件时地址不能撞SSD1306 是 0x3C 或 0x3DAT24C02 是 0x50一般不会冲突但要注意上拉电阻只装一组别两个模块各带一组并联成 2.2K。2.2 模块职责与数据流从按键到开锁的一条主线把系统切成四层每层只对自己那一摊负责出问题时能快速定位采集层AS608负责成像、特征提取、模板存储、1:N 搜索和 1:1 比对。控制层C8T6负责指令封装、超时重试、状态机流转、权限判断。交互层OLED、三个按键、蜂鸣器负责提示「请按手指」「录入成功」「比对失败」。执行层继电器加电磁锁负责在判定通过后吸合固定时长。数据流是一条单向主线按键触发 → 状态机切换 → 调用指纹指令 → 串口收发 → AS608 返回确认码 → 状态机决策 → 驱动 OLED 和继电器。这条线里没有环所以用前后台结构就够了不必上 RTOS。主循环长这样int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART2_UART_Init(); MX_I2C1_Init(); oled_init(); user_table_load(); /* 从 AT24C02 读用户表带 CRC 校验 */ if (as_init() ! 0) { /* 握手读系统参数确认波特率对得上 */ oled_show_str(0, 0, AS608 ERR); while (1) { } /* 起不来就停在这方便现场判断 */ } oled_show_idle(); /* 待机界面请按手指 */ for (;;) { key_event_t k key_scan(); /* 10ms 消抖返回短按/长按/无 */ if (k ! KEY_NONE) { fsm_on_key(k); /* 交给状态机分发 */ } fsm_tick(); /* 超时、蜂鸣器节拍、屏幕局部刷新 */ if (uart_frame_ready()) { /* 中断里只置标志 */ as_ack_t ack; if (uart_take_frame(ack)) { fsm_on_ack(ack); /* 解析和业务都在主循环 */ } } } }这里的取舍很明确串口中断里只做「收字节、拼帧、置标志」解析和业务全部扔回主循环。中断里一旦调用HAL_Delay或者写 OLED轻则丢字节重则整机卡死。fsm_tick()用HAL_GetTick()做非阻塞计时负责「请按手指」倒计时、开锁 3 秒后自动回落、蜂鸣器响几声这类事不要用HAL_Delay硬等。再看状态机的状态集合最小可用版本是六个IDLE待机、ENROLL_1录入第一枚、ENROLL_2录入第二枚、MATCH比对中、UNLOCK已开锁、ADMIN管理菜单。每个状态只处理三类事件按键、指纹应答、超时。状态迁移写成switch-case就够了不要设计得太花能一眼看懂是给别人答辩时最大的善意。3. 通信协议落地AS608 指令帧在 STM32 上的收发实现这一章是全篇最需要照着敲的部分。AS608 的协议本身不复杂但字段定义很讲究一旦理解偏了表现就是「一直返回收包错误」而且从现象上完全看不出是哪一步错的。3.1 指令帧结构拆解与校验和计算一帧完整的命令包由六段组成字段字节数取值说明包头20xEF 0x01固定模块地址40xFFFFFFFF默认广播地址包标识10x01 命令 / 0x02 数据 / 0x07 应答 / 0x08 结束区分帧类型包长度2高字节在前指令 参数 校验和不含包头和地址指令与参数n指令码 参数例如 0x01 采图校验和2高字节在前从包标识开始累加不是从 0xEF 开始有三个地方最容易理解错我按血泪经验的顺序排第一包长度的口径。它只算「指令 1 字节 参数字节数 校验和 2 字节」前面那 6 个字节包头 2 地址 4不算。第二校验和的起点。它是从包标识那个字节开始一直加到最后一个参数字节逐字节累加后取低 16 位。第三所有 16 位字段都是高字节在前别按小端习惯填。拿「读有效模板数量」举例指令码 0x1D 无参数整帧是EF 01 FF FF FF FF 01 00 04 1D 00 22包长度 0x0004 指令 1 参数 0 校验和 2校验和 0x0022 0x01 0x00 0x04 0x1D。手算一遍比调试半天快得多建议把几个常用指令的完整帧写在纸上贴到显示器边上。组装函数直接按字段填注意校验和的累加范围/* 组装一条命令帧buf 至少 80 字节返回总长度 */ uint16_t as_build(uint8_t *buf, uint8_t pid, uint8_t cmd, const uint8_t *param, uint16_t plen) { uint16_t len plen 3; /* 指令1 参数plen 校验和2 */ uint16_t i 0, sum 0; buf[i] 0xEF; /* 包头 */ buf[i] 0x01; buf[i] 0xFF; /* 默认地址 0xFFFFFFFF */ buf[i] 0xFF; buf[i] 0xFF; buf[i] 0xFF; buf[i] pid; /* 包标识0x01 命令 */ buf[i] len 8; /* 包长度高字节在前 */ buf[i] len 0xFF; buf[i] cmd; /* 指令码 */ for (uint16_t k 0; k plen; k) { buf[i] param[k]; /* 参数 */ } for (uint16_t k 6; k i; k) { sum buf[k]; /* 从包标识开始累加跳过前 6 字节 */ } buf[i] sum 8; /* 校验和高字节在前 */ buf[i] sum 0xFF; return i; }参数pid一般传 0x01命令包plen是参数字节数没有参数就传 0 和NULL。这个函数不做长度检查调用方保证buf够大、plen不超过 64因为 AS608 单帧参数不会太长64 字节的余量足够。3.2 串口收发DMA 空闲中断与逐字节中断的取舍AS608 的应答长度不固定成功时短读模板列表时长用HAL_UART_Receive_IT定长接收会很别扭得反复重启接收。省事的做法是 DMA 空闲中断一帧数据收完触发一次回调长度由硬件给出。#define RX_BUF_SZ 128 static uint8_t dma_buf[RX_BUF_SZ]; static uint8_t ring[512]; static volatile uint16_t ring_w 0; /* 空闲中断回调一次拿到本帧全部字节搬进环形缓冲 */ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t size) { if (huart-Instance USART2) { for (uint16_t i 0; i size; i) { ring[ring_w % sizeof(ring)] dma_buf[i]; } /* 必须重启接收否则只能收到第一帧 */ HAL_UARTEx_ReceiveToIdle_DMA(huart2, dma_buf, RX_BUF_SZ); } }HAL_UARTEx_RxEventCallback需要 HAL 库版本较新F1 系列 1.8 以上老工程里没有这个函数就退回逐字节中断在HAL_UART_RxCpltCallback里存一个字节、重新调HAL_UART_Receive_IT然后喂给下面的解析状态机。两种方式的差别只是入口不同解析逻辑完全一样。解析用状态机一个字节进、一帧出不依赖任何缓冲区长度假设typedef enum { S_HEAD0, S_HEAD1, S_ADDR, S_PID, S_LEN_H, S_LEN_L, S_BODY } parse_st_t; static parse_st_t st S_HEAD0; static uint8_t tmp[72]; static uint16_t body_len, idx, addr_cnt; /* 每收到一个字节调一次返回 1 表示收完整一帧且校验通过 */ uint8_t as_parse_byte(uint8_t b, as_ack_t *ack) { switch (st) { case S_HEAD0: if (b 0xEF) st S_HEAD1; break; case S_HEAD1: st (b 0x01) ? S_ADDR : S_HEAD0; break; case S_ADDR: /* 跳过 4 字节地址 */ if (addr_cnt 4) { addr_cnt 0; st S_PID; } break; case S_PID: tmp[0] b; st S_LEN_H; break; /* 包标识 */ case S_LEN_H: tmp[1] b; st S_LEN_L; break; case S_LEN_L: tmp[2] b; body_len ((uint16_t)tmp[1] 8) | tmp[2]; if (body_len 3 || body_len sizeof(tmp) - 3) { st S_HEAD0; /* 长度非法重新找包头 */ break; } idx 0; st S_BODY; break; case S_BODY: tmp[3 idx] b; if (idx body_len) { st S_HEAD0; return as_check(tmp, body_len, ack); /* 校验并拷贝出去 */ } break; } return 0; }这段代码的两个关键点是长度非法时立刻回到找包头的状态防止一个错误长度把后续所有帧带偏tmp要开到 72 字节以上因为应答最长会带模板列表。3.3 指令封装统一成一个 as_call 接口协议层往上就该收口了。业务代码不应该关心帧怎么拼、校验和怎么算它只想「发一条命令告诉我成没成」。所以封装一个统一入口把组帧、发送、等待、超时重试、清残留全包进去/* 发命令并等应答。param 可为 NULL返回确认码0x00 表示成功 AS_ERR_TIMEOUT(0x100) 表示超时AS_ERR_CHECK(0x101) 表示校验失败 */ uint16_t as_call(uint8_t cmd, const uint8_t *param, uint16_t plen, uint8_t *out, uint16_t *out_len) { uint8_t tx[80]; uint16_t n as_build(tx, 0x01, cmd, param, plen); for (uint8_t retry 0; retry 3; retry) { ring_reset(); /* 丢掉上一轮残留避免错位 */ HAL_UART_Transmit(huart2, tx, n, 100); uint32_t t0 HAL_GetTick(); while (HAL_GetTick() - t0 800) { /* 单次超时 800ms */ as_ack_t ack; if (ring_pop_frame(ack)) { if (ack.pid ! 0x07) continue; /* 只要应答包 */ if (out out_len) { *out_len ack.len - 3; /* 去掉指令和校验和 */ memcpy(out, ack.data 1, *out_len); } return ack.data[0]; /* 第一个参数字节是确认码 */ } } } return AS_ERR_TIMEOUT; }确认码是理解整套业务的分水岭常用几个必须背下来0x00 成功0x01 收包错误0x02 传感器上无手指0x03 录入失败0x06 指纹不匹配0x07 特征合并失败0x09 搜索无匹配0x0A 合并失败0x0B 访问越界0x10 删除失败0x11 清空失败0x15 无有效原图0x18 Flash 读写错误0x1F 库满。业务状态机就是围着这些码做分支0x02 和 0x09 是完全不同的两件事前者是没按手指后者是按了但不认识提示语和重试策略都不一样。4. 业务状态机录入、比对、删除三条流程的工程化写法协议层通了之后真正决定项目能不能用的是这三条流程写得够不够扎实。一个课设级别的演示和一个能连续跑一天的样机差别基本都在这里。4.1 录入流程两次采集与模板合并AS608 的录入不是按一次就行标准流程是「采图 → 生成特征存 Buffer1 → 抬手指 → 采图 → 生成特征存 Buffer2 → 合并 → 存库」。中间那步抬手指的等待很多人直接省略结果两次采的是同一幅图合并出来照样能存但识别率明显偏低这就是典型的「功能看着对、用起来发飘」。/* 录入一枚指纹到 page_id返回 0 成功其余为 AS608 确认码 */ uint16_t as_enroll(uint16_t page_id) { uint16_t r; r as_call(0x01, NULL, 0, NULL, NULL); /* 采第一枚图 */ if (r) return r; r as_call(0x02, (uint8_t[]){1}, 1, NULL, NULL); /* 生成特征 → Buffer1 */ if (r) return r; oled_show_str(0, 3, 抬起再按); /* 等手指离开一直采图直到返回 0x02无手指再继续 */ for (uint32_t t HAL_GetTick(); HAL_GetTick() - t 3000; ) { if (as_call(0x01, NULL, 0, NULL, NULL) 0x02) break; HAL_Delay(100); } r as_call(0x01, NULL, 0, NULL, NULL); /* 采第二枚图 */ if (r) return r; r as_call(0x02, (uint8_t[]){2}, 1, NULL, NULL); /* 生成特征 → Buffer2 */ if (r) return r; r as_call(0x05, NULL, 0, NULL, NULL); /* 合并两枚特征 */ if (r) return r; /* storeBufferID1PageID 两字节高字节在前 */ uint8_t p[3] { 1, (uint8_t)(page_id 8), (uint8_t)(page_id 0xFF) }; return as_call(0x06, p, 3, NULL, NULL); }几个参数要说清0x02的 BufferID 只用 1 或 2容量是两枚特征0x05合并时不带参数它固定合并 Buffer1 和 Buffer20x06的三个参数依次是 BufferID、PageID 高字节、PageID 低字节。page_id的取值范围由模块容量决定300 枚版本就是 0299超出会返回 0x0B 访问越界。还有个工程细节整个录入过程中OLED 要一直显示当前步骤。录入是少数需要用户配合的操作如果屏幕停在「请按手指」不动用户会以为卡死然后开始乱按两次采集的位置和角度就全乱了。通常的做法是把「第 1 次」「抬起再按」「第 2 次」「成功」四个提示做出来加起来也就几十行代码。4.2 比对与开锁搜索模式和匹配模式的取舍AS608 有两种比对方式用错地方是常见的设计失误。匹配0x03是 1:1需要你先指定一个模板号模块拿当前特征和那个模板比。适合「卡号 指纹」的双因子场景或者管理员验证自己。搜索0x04是 1:N从指定号段里挨个比返回命中的模板号和得分。适合门禁和考勤这种「按一下就知道你是谁」的场景。/* 1:N 搜索命中返回 0 并回填 page_id 和 score */ uint16_t as_search(uint16_t *page_id, uint16_t *score) { uint8_t buf[8]; uint16_t len 0; uint16_t r; r as_call(0x01, NULL, 0, NULL, NULL); /* 采图 */ if (r) return r; r as_call(0x02, (uint8_t[]){1}, 1, NULL, NULL); /* 生成特征 */ if (r) return r; /* 参数BufferID1起始页 0x0000数量 0x012C(300) */ uint8_t p[5] { 1, 0x00, 0x00, 0x01, 0x2C }; r as_call(0x04, p, 5, buf, len); if (r 0x00 len 4) { *page_id ((uint16_t)buf[0] 8) | buf[1]; /* 命中的模板号 */ *score ((uint16_t)buf[2] 8) | buf[3]; /* 匹配得分 */ } return r; }这段里的 0x012C 就是 300如果模块是 1000 枚版本就改成 0x03E8。搜索范围建议不要一上来就全库扫用户表里记着已用的最大模板号把搜索范围收窄到0 ~ max_used比对时间能明显下降。得分这个值很多方案里白白浪费了。它的含义是匹配相似度分数越高越像。正常录入的同一根手指得分通常在 100 以上把阈值设在 50 左右比较稳妥。门禁场景可以设得高一点减少误开考勤场景可以松一点减少拒识。把这个分数显示在 OLED 第二行答辩时是很有说服力的细节。安全等级是另一个可调项。AS608 支持 15 级默认 3 级等级越高越严格。设置方式是通过系统参数指令写入参数号 4具体参数号对着手册核一遍再写死不同固件版本可能有差异。实际上大多数项目保持默认就行只有现场反馈「别人手指也能开」时才需要往上调。开锁动作本身要有兜底继电器吸合后必须有自动回落不能靠用户再按一次。用一个软件定时器UNLOCK状态进入时记下时间戳fsm_tick()里判断超过 3000ms 就拉低并回到IDLE。这个兜底不做一旦程序在某处卡住锁就一直开着了。4.3 存储与索引管理模板号、用户表与掉电保护AS608 内部存的是指纹模板但「这个模板号对应谁、是普通用户还是管理员」这类信息必须存在 MCU 侧否则没法做权限和删除。用 AT24C02 就够了256 字节按每条 16 字节算能放十几条记录够课程设计和小样机使用。#define USER_MAGIC 0x5A5A typedef struct { uint16_t magic; /* 0x5A5A 表示本区数据有效 */ uint16_t page_id; /* AS608 模板号0 ~ 299 */ uint8_t role; /* 0 普通用户1 管理员 */ uint8_t used; /* 1 已占用0 空槽 */ uint8_t code[8]; /* 简短编号ASCII可为空 */ uint16_t crc; /* 前面所有字节的 CRC16 */ } user_slot_t;magic和crc这两个字段是后悔药。新片子第一次上电时 EEPROM 里全是 0xFF如果不做校验程序会把 0xFFFF 当成模板号去搜索行为完全不可预期。加载时先看 magic再看 crc任何一项不通过就当空表处理并重新初始化。写入策略用双区 A/B 轮换每次保存整表写到另一区写完再更新区标志。好处是写到一半掉电另一区还是完整的。AT24C02 还有页写限制一次最多写 8 字节跨页会回卷覆盖所以驱动里要按页边界拆包每包写完延时 5ms 等内部烧写完成这个 5ms 省不得。删除是配套的另一半。删一个用户要做两件事调0x0C删除模块里的模板参数是 PageID 和数量再把用户表里对应槽位的used清 0。只删一边就会出现「表里没有但库里能开」或者「库里有但号段复用不了」的情况。模板号建议用「最小可用号」策略删除时不清空号段录入时从 0 开始找第一个used 0的槽位填入这样号段能自然回收不会越用越散。5. 避坑与排查指纹方案最容易翻车的五个地方调试期遇到的问题八成集中在这五条上。按「现象 → 原因 → 解决」的格式列出来遇到时对号入座比漫无目的地改代码快。5.1 供电与链路上的三个坑现象同一根已录入的手指十次里有两三次搜不到换手指也一样。原因通常不在算法而在供电。AS608 内部有红外补光和图像处理电路采图瞬间电流会突然上升如果 3.3V 轨的 LDO 余量不足或者走线太长、去耦不够模块电压会被拉低采出来的图像质量下降特征提取自然不稳定。 解决把模块单独接一路 3.3V、500mA 以上的 LDO电源脚就近并 100µF 加 0.1µF串口线控制在 20cm 以内。用示波器抓一下采图瞬间的 3.3V 波形毛刺超过 200mV 就要重做供电。现象串口一直在返回 0x01 收包错误换了几条指令都一样。原因包长度或校验和算错了而且是最典型的两种错法——把包头和地址算进了长度或者校验和从 0xEF 开始累加。模块收到长度不对的帧直接回「收包错误」不会告诉你错在哪。 解决用串口助手手动发一帧已知正确的指令比如读模板数量确认能收到 0x00再回头对照as_build逐字段比。这个方法比在代码里打一百行日志都快。现象第一帧能收到之后就没反应了必须复位才好。原因DMA 或中断接收只启动了一次收完一帧没有重新挂上去也有可能是环形缓冲被写满后没有覆盖策略。 解决在接收完成回调的最后一行无条件重启接收这件事没有例外。缓冲区读写指针用无符号数取模读端在帧校验失败时主动清空缓冲防止坏帧卡住整个队列。5.2 流程与数据上的两个坑现象录入提示成功但马上比对却认不出来。原因一是漏了store特征只合并在 Buffer 里没写进模块 Flash掉电就没了二是抬手指那步被省略两次采的其实是同一幅图合并出来的模板质量很差。 解决检查store的返回值是否为 0x00不要只看合并成功录入流程里加上「等 0x02 无手指再继续」的等待逻辑OLED 同步提示「抬起再按」。这两处改完识别率通常会有肉眼可见的提升。现象掉电重启后用户表乱码或者某些用户突然变成管理员。原因EEPROM 写入没有等内部烧写完成或者结构体没做版本号和校验新增字段后旧数据被按新结构解析。 解决加 magic 加 CRC16加载时校验失败就整表重置并重新初始化结构体第一个字段留一个版本号以后改结构时做兼容判断。另外每次写完整表才更新区标志保证任意时刻拔电都还有一份完整的旧数据。顺带说一个不是坑但经常被问的点AS608 的指纹模板是加密存在模块 Flash 里的取不出来也导不到别的模块。所以换模块等于所有用户要重新录入这一点在做备份方案时要提前跟使用方讲清楚别等交付了才发现。6. 验收与答辩怎么证明这套指纹系统真的能交付做完了不等于能交付尤其是要答辩的时候评委看的不是你有没有跑通而是你能不能证明它在异常情况下也不会出事。我一般会准备一张验收表六条用例全部跑一遍才算完用例操作通过标准录入同一手指按两次中间抬起返回 0x00OLED 提示成功用户表新增一条比对通过已录入手指按一次1 秒内识别得分 ≥ 50继电器吸合 3 秒后自动回落比对拒绝未录入手指按一次返回 0x09不开锁蜂鸣器两短声界面提示「未注册」删除管理员删指定模板号该手指再比对返回 0x09用户表对应槽位used变 0掉电录入后直接断电再上电CRC 校验通过原有用户仍能开锁表项不丢压测已录入手指连续比对 100 次成功率不低于 98%无死机、无花屏、无锁死不开答辩演示的脚本也要排。开场先用三十秒讲架构AS608 负责采集和比对C8T6 负责协议和状态机OLED 和继电器是输出。这句话讲清楚后面很多问题都不会被问。然后按「录入 → 比对通过 → 未注册拒绝 → 删除 → 断电重启」的顺序走一遍每一步都让评委看到屏幕提示和继电器动作。断电重启这一条建议主动演示它能直接证明你的存储方案是可靠的比口头解释有用得多。被问到的问题大概率集中在几个方向提前准备好答案为什么不用 RTOS——前后台加状态机已经够中断里只做收字节长耗时操作全在主循环不存在实时性瓶颈比对为什么在模块里做——AS608 自带 DSP特征提取和比对是固件完成的MCU 拿到的只是结果码和得分误识率怎么保证——靠安全等级和得分阈值两道闸加上 1:N 搜索范围收窄如果两个人手指都被认成同一个人怎么办——把得分阈值调高同时记录每笔开锁的模板号和得分出现异常能追溯。最后说个我自己的习惯。带这类项目这些年我每次在交付或者答辩前一晚都会把上面那张表从头到尾跑一遍一条都不跳哪怕代码当天根本没改。有一次就是靠这个习惯发现断电重启后用户表没加载成功——白天测的时候一直是热复位从来没真正断过电。从那以后我每次收尾都强制走一遍这六条用例也希望这套流程能帮到你。本文还有配套的精品资源点击获取