
简介这份PDF是一套基于STM32与AS608的指纹考勤机设计方案面向嵌入式学习者和企业信息化管理人员解决传统考勤易出错、效率低的问题。文档从项目需求、预期效果到硬件选型逐项拆解主控选用STM32F103RCT6指纹模块采用AS608并用QtC开发上位机实现指纹ID管理、考勤记录、数据导出、系统登录、账号注册、设备配置、账号管理和日志显示等功能。资源为单个PDF文档约19.17MB适合作为从单片机到上位机完整通信链路的参考范例。正文覆盖前言、硬件选型、Qt上位机界面设计、Keil工程创建、通信协议约定与main.c代码包含开发板特点、USB下载线、AS608指纹模块容量及接口等选型细节和连接方式可直接辅助复现项目。设计总结明确了系统操作流程单片机负责采集指纹上位机负责指纹ID管理、考勤记录与打卡。目前已有277人学习。1. 这套STM32指纹考勤机资料单片机只报ID其余全交给Qt上位机大多数考勤机喜欢把所有逻辑都塞进设备里录指纹、算迟到、统计报表全部在下位机完成结果固件越堆越重改一条规则就要重新烧录。而这份基于STM32F103RCT6和AS608指纹模块的考勤机方案刻意反着来单片机只负责采集指纹、比对、上报一个指纹ID剩下打卡记录、账号管理、日志导出全部放到Qt上位机完成。这个拆分方式的直接收益是改考勤规则不用动硬件串口一开就能看到谁在什么时候打了卡。适合准备做嵌入式结合桌面应用、但不想在下位机上堆业务逻辑的开发者也适合课程设计里想把“上位机单片机”两端都展示出来的场景。资料里包含了完整的AS608驱动、通信协议约定、Qt源码和硬件接线表照着接线就能跑通一套能用的考勤系统。2. 硬件选型与系统分工STM32F103RCT6、AS608和Qt这套组合够用吗2.1 STM32F103RCT6的资源配置够不够用资料里选的是STM32F103RCT6这颗芯片属于F103系列里的大容量型号配备了48KB SRAM、256KB Flash以及多达5个串口、51个通用IO、3个SPI、2个I2C还带USB、CAN和SDIO接口。对这个考勤项目来说资源是明显富余的。实际项目里只用到其中两条串口USART1做调试打印USART2接AS608指纹模块其余外设几乎都在空闲状态。富余资源不是浪费而是给调试阶段留的后路——比如想再加一个蓝牙模块做无线开门、或者用I2C外扩存储基本不用换主控。开发板上还自带了1.4寸TFT-LCD彩屏这一点在调试时很实用。AS608握手成功、指纹库里剩余数量、当前刷指纹命中的ID都可以直接显示在屏幕上不用每次都去串口助手里翻日志。LCD显示不是核心功能但建议一开始就预留驱动后面联调能省很多事。2.2 AS608模块容量、接口与自学习AS608是资料里选定的指纹模块接口是SH1.0-6P排线模块作为UART从设备由主设备发命令控制。指纹容量有50枚、100枚、300枚三档价格对应不同采购前先确认公司实际人数选小了后期只能靠反复删除旧指纹来腾空间。模块自带两个值得注意的特性。第一个是自学习功能识别成功一次模块会把本次采集到的特征值融合进库里已有的模板所以同一个手指用得越多后续识别越稳定反过来说如果录入时手指状态和日常使用差别很大前期确实会出现偶尔识别失败的情况用一段时间后会慢慢变好。第二个是Touch感应脚手指按压到采集面时模块Touch脚会输出高电平下位机可以用这个脚做“是否有手指按压”的判断避免误触发取图命令。模块对干手指和湿手指都有适配正常程度的手汗问题不大但指纹浅或者脱皮严重的人群识别率会明显下降。模块支持安全等级调节安全等级越高匹配越严格误识率低但拒识率升高初期联调建议先用低等级跑通链路后再调到生产等级。2.3 系统分工为什么考勤规则全放上位机这套设计最关键的一点是把“考勤业务”从单片机里摘了出来。下位机只负责三件事握手、录指纹、刷指纹后上报ID。上位机承担的是ID对应哪个员工、几点打的卡、是否迟到、账号权限、日志记录全部业务逻辑放在Qt程序里。这个分工的好处非常实际。其一上位机改动只需要重新编译Qt工程不用对设备端反复烧录其二考勤记录可以存到电脑本地文件甚至数据库里后续要对接工资系统直接在Qt端导出数据即可而不是去单片机里按地址读Flash其三账号管理和权限区分这类功能在单片机上实现很痛苦在Qt里不过是几个对话框和表格模型的事。代价是这套系统不适合移动考勤设备端和上位机必须通过USB串口实线连接才能工作。如果目标是多台考勤机分布式部署那这套架构就不适用需要改成网络方案。表格是硬件接线时最常用到的整理如下硬件型号/规格作用主控板STM32F103RCT6开发板主控、LCD显示、外设接口下载线USB转TTL下载线给STM32烧录程序指纹模块AS608SH1.0-6P接口指纹采集、特征比对通信线杜邦线或模块排线连接USART2与AS608上位机Qt 5.12.6C考勤管理、数据导出3. 串口指令协议拆解从$dev_cmd_lu_ru_zhi_wen到$update_find数据包怎么设计3.1 为什么用“$开头冒号数字”的文本协议资料里约定了一套很直白的文本通信协议。上位机下发指令格式是“$指令名:参数”例如“$dev_cmd_lu_ru_zhi_wen:1”表示录入ID为1的指纹“$dev_cmd_del_zhi_wen:1”表示删除ID为1的指纹。设备上传则用另外几个标记“$update_lu_ru_ok”表示录入成功“$update_lu_ru_error”表示录入失败“$update_find,1,”表示搜索到ID为1的指纹并签到成功。这种文本协议相比二进制协议最大的优势是调试时可读性极高。直接打开串口助手手动敲一条“$dev_cmd_lu_ru_zhi_wen:5”发给设备就能看到下位机的行为日志。出问题时不用拿着协议文档逐个字节对比肉眼就能发现是命令拼写错误还是参数格式不对。下位机侧匹配使用的是C标准库的strstr函数对传入的字符串做子串查找所以只要命令的关键字正确参数部分就能用strlen偏移加atoi转换得到数字。使用时有几个约定需要遵守命令区分大小写参数和命令之间只有一个冒号不能带空格或其他不可见字符每条指令后推荐带回车换行方便Qt端拆包。3.2 三类关键时序录入、删除、刷指纹把协议按功能拆成三张时序表更容易理解。方向命令/返回含义上位机→设备$dev_cmd_lu_ru_zhi_wen:1录入ID为1的指纹设备→上位机$update_lu_ru_ok录入成功设备→上位机$update_lu_ru_error录入失败超时/多次比对失败上位机→设备$dev_cmd_del_zhi_wen:1删除ID为1的指纹设备→上位机$update_find,1,刷指纹命中ID 1签到成功录入时序是流程最长的上位机发送指令后设备端进入“录指纹”状态机要求按压同一根手指两次两次特征比对一致后生成模板并存储最后返回$update_lu_ru_ok。如果10次内没有完成有效按压设备返回$update_lu_ru_error。这个过程最短也要十几秒上位机侧必须做成异步等待不能用阻塞式请求-应答。刷指纹时序正好相反设备端在死循环里不断调用PS_GetImage取图一旦采集到指纹且比对成功立即主动向上位机发送$update_find,ID,。这里没有“上位机先问、设备再答”的交互上位机程序要随时准备接收这条上行报文并解析。删除时序相对简单删除单枚指纹发$dev_cmd_del_zhi_wen:ID即可设备内部调用PS_DeletChar完成。协议里还约定了一个特殊值0xFF00下位机识别到该值时会调用PS_Empty清空整个指纹库这个操作在初次调试换人测试时非常有用。3.3 Qt端串口收发的落地片段Qt端使用QSerialPort类来对接下位机。工程配置文件里需要加一行QT serialport否则编译时找不到串口类。初始化代码一般写在主窗口构造函数里。// Qt端串口初始化MainWindow构造函数中调用 #include QSerialPort QSerialPort *port new QSerialPort(this); port-setPortName(COM3); // 端口名以设备管理器实际枚举为准 port-setBaudRate(57600); // 必须与下位机USART2波特率一致 port-setDataBits(QSerialPort::Data8); port-setParity(QSerialPort::NoParity); port-setStopBits(QSerialPort::OneStop); port-setFlowControl(QSerialPort::NoFlowControl); if (!port-open(QIODevice::ReadWrite)) { ui-log-append(串口打开失败: port-errorString()); } else { connect(port, QSerialPort::readyRead, this, MainWindow::onDataReady); }setPortName这里不能写死。不同电脑上USB转串口枚举出来的COM号不一样应该做一个下拉框在设备配置界面里让用户选择。波特率参数必须和下位机USART2_Init(57600)一致改任何一端都要同步改另一端。接收端要处理的是数据粘包问题。下位机返回的$update_find,1,后面跟着\r\nQt端在readyRead信号里收到的可能是不完整的一帧也可能一次收到多帧。常见做法是维护一个接收缓冲区把readAll()读到的数据追加进去再按换行符逐条切出完整指令。// 接收槽函数按换行拆包交给业务处理 void MainWindow::onDataReady() { QByteArray chunk port-readAll(); rxBuffer.append(chunk); while (rxBuffer.contains(\n)) { int pos rxBuffer.indexOf(\n); QByteArray line rxBuffer.left(pos).trimmed(); rxBuffer.remove(0, pos 1); handleDeviceLine(line); } } void MainWindow::handleDeviceLine(const QByteArray line) { if (line.startsWith($update_find,)) { // 提取指纹ID示例$update_find,3, int id line.mid(13, line.size() - 14).toInt(); appendRecord(id); // 插入考勤记录表 } else if (line.startsWith($update_lu_ru_ok)) { ui-log-append(录入成功); } else if (line.startsWith($update_lu_ru_error)) { ui-log-append(录入失败); } }把trimmed()放在拆包后处理能把末尾的\r去掉避免匹配时被不可见字符干扰。handleDeviceLine里匹配的是指令前缀因为一行内可能有多个字段比如$update_find,1,这串字符的ID部分在逗号之间用mid直接截取比split更直观。当然用QString::split(,)处理也可以个人习惯是在这类固定格式里用索引截取少一次容器分配。4. STM32固件实现与AS608驱动状态机录指纹流程与常见问题排查4.1 初始化顺序为什么先释放PA15再初始化串口main函数开头的初始化顺序是有讲究的。第一行调用JTAG_Set(JTAG_SWD_DISABLE)把PA15从JTAG功能释放为普通IO。很多开发板的PA15默认被JTAG占用如果不释放后面用PA15做按键或者接其他外设时会发现引脚电平不受控制。释放完引脚后才是LED、按键、延时、串口、定时器和指纹状态脚的初始化。// main.c 初始化顺序参考工程结构宏定义按板卡调整 JTAG_Set(JTAG_SWD_DISABLE); // 释放PA15为普通IO LED_Init(); // 板载LED闪烁表示程序在跑 KEY_Init(); // 板载按键预留手动测试入口 delay_init(72); // 72MHz主频对应的延时初始化 USART1_Init(115200); // 串口1调试日志接电脑 TIMER1_Init(72, 20000); // 定时器提供超时计数 USART2_Init(57600); // 串口2接AS608指纹模块 TIMER2_Init(72, 20000); PS_StaGPIO_Init(); // AS608 Touch状态脚输入这段代码里有几个参数要留意。delay_init(72)传的是系统主频72MHz这是F103系列标准配置。USART1_Init(115200)是调试串口接USB转TTL看日志用USART2_Init(57600)才是和指纹模块通信的串口两个波特率不能搞混。TIMER1_Init和TIMER2_Init的第二个参数20000是定时器自动重装载值配合预分频72得到大概200ms的中断周期在录指纹超时判断和LED翻转里都会用到。4.2 AS608底层API与错误码握手、读模板数、读系统参数初始化完成后第一件事是让主控和AS608握手。资料里的实现是while(PS_HandShake(AS608Addr))死循环握手失败就一直打印“连接指纹模块失败”这属于阻塞式等待仅适用于上电自检。如果AS608没供电、接线接反、或者串口波特率不对程序会卡在这个循环里。调试时建议在循环里加一个超时退出比如等待5秒后仍然失败就把错误原因打印出来并进入待机而不是无休止重试。// 握手成功后读取指纹库状态 while (PS_HandShake(AS608Addr)) { printf(连接指纹模块失败....\r\n); delay_ms(1000); } printf(指纹模块连接成功.....\r\n); // 读库中有效模板数 ensure PS_ValidTempleteNum(ValidN); if (ensure ! 0x00) { printf(ensure:%s\r\n, EnsureMessage(ensure)); } // 读模块系统参数 ensure PS_ReadSysPara(AS608Para); if (ensure 0x00) { printf(RemainNum:%d Level:%d\r\n, AS608Para.PS_max - ValidN, AS608Para.PS_level); }PS_ValidTempleteNum返回的是模块内部指纹库的有效模板个数存储在ValidN变量里。PS_ReadSysPara返回系统参数结构体其中PS_max表示模块最大容量PS_level表示当前安全等级。剩余容量用PS_max减去ValidN就能算出来上位机界面如果显示“剩余可录枚数”数据来源就在这里。AS608的API返回值为0x00表示成功非0值时需要使用EnsureMessage函数把错误码转成可读字符串。常见的错误码比如0x07表示图像不清晰0x09表示两次采集的特征不一致这些在联调阶段直接打印出来定位会非常快。4.3 录指纹状态机processnum的0到4录指纹是整个固件里最复杂的部分资料里用了一个基于processnum变量的switch状态机分成五个阶段取第一张图、取第二张图、两次对比、合成模板、存储模板。核心分支如下。// 录指纹状态机processnum从0递增到4 switch (processnum) { case 0: printf(请按下手指.\r\n); ensure PS_GetImage(); // 采集第一张指纹图像 if (ensure 0x00) { ensure PS_GenChar(CharBuffer1); // 生成特征存入CharBuffer1 if (ensure 0x00) { processnum 1; // 成功跳到第二步 } else ShowErrMessage(ensure); } else ShowErrMessage(ensure); break; case 1: printf(再按一次手指.\r\n); ensure PS_GetImage(); // 采集第二张指纹图像 if (ensure 0x00) { ensure PS_GenChar(CharBuffer2); // 生成特征存入CharBuffer2 if (ensure 0x00) { processnum 2; // 成功跳到对比步骤 } else ShowErrMessage(ensure); } else ShowErrMessage(ensure); break; case 2: ensure PS_Match(); // 对比两个特征是否同一手指 if (ensure 0x00) { printf(两次指纹是一样的.\r\n); processnum 3; } else { printf(对比失败重新按手指.\r\n); processnum 0; // 对比失败回到第一步重新采集 } delay_ms(1000); break; case 3: ensure PS_RegModel(); // 由两个特征生成合成模板 if (ensure 0x00) { processnum 4; } else { processnum 0; ShowErrMessage(ensure); } delay_ms(1000); break; case 4: ensure PS_StoreChar(CharBuffer2, ID); // 把模板存储到指定ID if (ensure 0x00) { printf($update_lu_ru_ok,\r\n); PS_ValidTempleteNum(ValidN); // 刷新库中模板数量 return; } else { processnum 0; ShowErrMessage(ensure); } break; }这个状态机有两个边界条件值得注意。第一个是i计数器每次进入case 0或case 1都会自增一旦i超过10说明用户连续10次没有成功按下手指程序直接退出并向上位机返回$update_lu_ru_error。第二个是两次按压的是不是同一根手指只有PS_Match确认一致后才会继续往合成模板方向走否则会退回最开始的状态。实测中经常遇到的“录到最后一步提示失败”大多是因为第二次按压时手指位置和第一次偏差太大。4.4 刷指纹与删除HighSpeedSearch和DeletChar的参数刷指纹的主流程在press_FR函数里。每轮循环调用PS_GetImage判断是否有手指按下一旦取图成功立即生成特征然后调用PS_HighSpeedSearch在指纹库中搜索匹配项。// 刷指纹取图-生成特征-高速搜索 SearchResult seach; ensure PS_GetImage(); // 采集图像 if (ensure 0x00) { ensure PS_GenChar(CharBuffer1); // 生成特征 if (ensure 0x00) { ensure PS_HighSpeedSearch(CharBuffer1, 0, 300, seach); if (ensure 0x00) { printf($update_find,%d,\r\n, seach.pageID); printf(Match ID:%d score:%d\r\n, seach.pageID, seach.mathscore); } else { ShowErrMessage(ensure); } } } delay_ms(1000);PS_HighSpeedSearch的第二个参数0表示从0号页开始搜索第三个参数300是搜索的页数范围。这里有个坑如果AS608是50枚或100枚容量版本300的搜索范围超出模块实际页数不会报错但会影响耗时反过来如果换成300枚版本而搜索范围留的是100ID大于100的指纹永远匹配不上。建议把这个搜索范围设置成模块实际容量值并且配一个宏定义方便不同容量模块切换。删除指纹调用PS_DeletChar(num, 1)第一个参数是待删除的ID第二个参数1表示删除该ID对应的模板。资料代码里约定了一个特殊逻辑如果num大于等于0xFF00则调用PS_Empty清空整个指纹库。4.5 常见问题排查五个具体踩坑记录以下五条都是在实际复现这套考勤机时大概率会遇到的问题按“现象—原因—解决”的顺序记录。1. 上位机发“录入指纹”指令下位机没任何反应。现象串口助手发送$dev_cmd_lu_ru_zhi_wen:1后单片机日志没有任何打印。 原因指令末尾被串口助手自动添加了换行符或回车符strstr仍然能匹配到关键字但atoi在解析数字时遇到换行符导致ID解析成0代码里if(ID 0)分支被跳过。 解决下位机解析命令前先对USART1_RX_BUFFER做一次截断去掉\r和\n或者Qt端发送时不加换行。我一般更喜欢后者发送纯指令更干净。2. AS608握手失败程序卡死。现象串口日志反复打印“连接指纹模块失败....”。 原因模块VCC接到了5V而不是3.3V或者TX和RX两根线接反。AS608对供电电压比较敏感接5V可能导致通信异常。 解决用万用表确认VCC为3.3VPA2接模块RX白线、PA3接模块TX黄线不要靠颜色记位置必须对照接线表逐一核对。3. 录入时总提示“超过10次没有按手指”。现象按下手指后程序没有进入case 0的流程一直超时退出。 原因Touch感应线没接或者接触不良PS_GetImage内部依赖Touch脚判断是否有手指按压。另外如果手指按下去时间太短模块来不及完成图像采集。 解决确认蓝线Touch感应线接到PA6并且用手指完全覆盖采集面保持1秒以上再松手。4. 使用SWD调试时程序突然无法下载。现象代码里放了JTAG_Set(JTAG_SWD_DISABLE)之后第二次烧录失败编译器报找不到设备。 原因该语句把调试口也禁用了还在调试阶段的工程会直接失去调试器连接。 解决工程调试阶段不要过早执行JTAG_Set(JTAG_SWD_DISABLE)等到所有外设测试完确认不再需要调试器时再启用如果已经被禁用按住板卡复位键在下载工具开始擦除瞬间松手通常能救回来。5. 删除指纹后上位机列表还显示旧ID。现象设备端删除成功但Qt考勤记录界面上ID仍然存在。 原因上位机只在添加指纹成功后刷新了本地列表没有处理删除成功后的刷新逻辑。 解决删除指令发送后等待设备返回确认再重新读取本地下发同步指令或者直接让上位机根据删除结果在本地表格中剔除对应ID。5. Qt上位机端实现串口收发、登录与考勤记录导出的编码细节5.1 开发环境搭建Qt 5.12.6安装里的两个坑资料里明确指定了Qt 5.12.6版本安装时有两个环节处理不当会很浪费时间。第一安装时必须断网否则安装向导会强制要求登录Qt账户没有账户的机器根本无法继续。离线安装包本身已经带了所有需要的组件断网装反而更顺畅。第二组件选择界面不需要全选只勾选一个MinGW 32-bit编译器即可。选MinGW而不是MSVC是为了避免后续还要安装Visual Studio和Windows SDKMinGW自带编译链直接就能编译出exe可执行文件。装完之后建工程时编译器路径会自动关联到MinGW。需要注意的一点是Qt 5.12.6的MinGW通常是32位版本生成的exe在64位Windows上可以正常运行但发布时要把Qt安装目录下的mingw32_53文件夹里的dll一起打包。5.2 串口通信封装细节在5.12.6环境里串口通信依赖QtSerialPort模块。除了前面提到的.pro文件加QT serialport界面侧还要把波特率、数据位、停止位这些参数做成可配置项而不是写死在代码里。设备配置界面里至少要有端口号下拉框和波特率输入框默认值填57600。串口打开之后有一个容易忽视的时序问题设备刚上电时AS608模块和串口的初始化需要时间如果上位机立刻发送指令很可能被丢弃。常见做法是在主界面加载完成后先发送一个空行或者握手命令等待设备返回“指纹模块连接成功”后再置位界面控件给设备一个准备窗口。// 设备配置界面应用串口参数并打开 void DeviceConfigDialog::applySettings() { QString portName ui-comPort-currentText(); int baud ui-baudRate-currentText().toInt(); serial.setPortName(portName); serial.setBaudRate(baud); serial.setDataBits(QSerialPort::Data8); serial.setParity(QSerialPort::NoParity); serial.setStopBits(QSerialPort::OneStop); serial.setFlowControl(QSerialPort::NoFlowControl); if (serial.open(QIODevice::ReadWrite)) { ui-statusLabel-setText(串口已打开); } else { ui-statusLabel-setText(串口打开失败); } }串口配置类的QSerialPort对象一般设置为主窗口的成员变量这样多个对话框之间可以共享同一个串口实例避免重复打开导致设备被占用。5.3 考勤记录与CSV导出的实现打卡记录的数据结构推荐用Qt的QList存储自定义结构体每条记录包含指纹ID、打卡时间、打卡状态。每次收到$update_find,ID,就构造一条新记录追加到列表里同时刷新表格控件。时间戳取的是上位机本地时间这是个有意的设计选择。如果从下位机取时间还得给单片机加RTC芯片并且设备断电后时间容易丢失。上位机在电脑上运行系统时钟本身就可靠同时考勤记录和PC时间对齐后续统计工时也更方便。// 导出考勤记录到CSV带BOM避免Excel中文乱码 QFile file(QCoreApplication::applicationDirPath() /attendance_ QDate::currentDate().toString(yyyyMMdd) .csv); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(this, 导出失败, file.errorString()); return; } QTextStream out(file); out.setCodec(UTF-8); out \xEF\xBB\xBF; // UTF-8 BOM out 指纹ID,打卡时间,状态\n; for (const AttendanceRecord rec : records) { out rec.id , rec.time.toString(yyyy-MM-dd hh:mm:ss) , rec.status \n; } file.close();BOM那一行不要省略。Excel打开没有BOM的UTF-8文件时中文表头会直接乱码成“铟纹ID”这样的内容加了BOM后Excel能正确识别UTF-8编码。文件名用当天日期生成每天的考勤记录自动存一个文件月末只需要把这些CSV直接归档或导入Excel汇总。5.4 账号与日志别把权限做在下位机上位机登录界面、账号注册、账号管理这几个功能本质上都是Qt的窗口和数据库表操作。账号表可以放在本地SQLite数据库里Qt自带的驱动不需要额外安装服务端一个文件就能解决。注册界面往用户表插入一条记录登录界面按用户名和密码去校验逻辑都很直接。日志显示窗口在整个联调阶段价值很高。建议把串口收到的所有原始报文都追加到一个QTextEdit里同时带上时间戳。这样做的好处是设备端行为一目了然哪条指令发了、设备回了什么、有没有粘包全都能在日志窗口里对照。实测下来很多通信问题不需要上逻辑分析仪盯着日志图形容就能定位到是哪一端出了问题。6. 联调四步与参数自查先握手、再录入、再打卡的完整验证整个系统跑通的验证路径可以压缩成四步每一步都有明确的预期结果。按这个顺序走出了问题也能立刻知道是哪一层卡的。第一步握手验证。打开上位机选择正确的COM口波特率设置57600点击打开串口。预期日志窗口打印“指纹模块连接成功”并且设备端LCD或串口日志显示“系统正常运行!”。如果这一步没过优先检查AS608的VCC电压和TX/RX接线。第二步录入验证。在上位机设备配置界面选择录入指纹输入ID号如1。设备端提示“请按下手指”先按一次再按一次两次都成功后等待约2秒观察是否收到$update_lu_ru_ok。如果报错查看设备端打印的错误码0x07是图像质量差0x09是两次指纹不一致。第三步删除与清空验证。发送删除指令删除刚才录入的ID确认设备端打印“删除指纹成功”。然后拉到协议里约定的0xFF00清空指令执行一次确认指纹库归零。这一步在生产环境不要随便做建议只在初始调试阶段执行。第四步刷指纹打卡验证。手按已录入的手指上位机考勤记录表格里应立即新增一条打卡记录。再按一个未录入的手指确认不会产生记录。导出CSV打开后确认中文表头正常、打卡时间准确。这四步之外还有一张参数自查表可以在联调过程中随时对照检查项期望值说明USART1波特率115200调试串口USART2波特率57600AS608通信串口AS608 VCC3.3V接5V会通信异常PA2/PA3接线PA2→模块RXPA3→模块TX交叉连接AS608搜索范围按模块容量配置50/100/300对应不同值Qt侧串口参数8/无校验/1停止位与下位机保持一致最后说一个我踩过不止一次的教训资料里的Del_FR函数有一段测试残留函数体内把num直接写死成了1导致任何删除请求都只删1号指纹。这类“写死在测试代码里”的值当时联调多花了一个多小时才定位到。从那以后我每次做完一轮联调都会强制全工程搜一遍写死的数字参数凡是带注释TODO的测试值统一清理再走一遍完整验证流程。这个习惯帮我免掉了很多翻车现场也希望帮到你。本文还有配套的精品资源点击获取