基于STM32与RC522的RFID考勤系统单片机程序全解析
2026/9/9 12:04:57 网站建设 项目流程

简介:基于RFID考勤系统设计的单片机程序是一份完整的嵌入式实战资料,面向单片机初学者、嵌入式开发人员以及物联网相关专业学生,帮助理解射频识别读卡、防碰撞算法、信息处理与嵌入式硬件设计的协同实现。资源共34个文件,约116KB,以C源文件、头文件、Keil工程配置、hex固件、列表文件、中间目标文件等构成,其中C与头文件便于阅读修改,hex文件可直接烧录验证,备份和工程辅助文件可用于参考与恢复。已有1220人学习下载。项目基于RC522射频模块与LCD12864液晶屏,覆盖IC卡注册、注销、打卡时间记录与显示等考勤核心环节;程序结构清晰,对理解射频识别标签读写机制、单片机与射频模块接口设计、ALOHA等防碰撞算法的工程落地具有直接参考价值。通过研读源码与工程配置,读者可快速搭建同类考勤系统并进行二次开发。 做考勤系统,是很多人接触RFID的第一个完整项目,这话我到现在依然觉得不夸张。不管是学生时代的课程设计、毕业设计,还是公司里给前台做一套内部打卡机,核心逻辑一脉相承:RFID读卡器拿到卡号,单片机判断合法性,再结合时钟模块记录时间,最后把结果打到屏幕上。这个项目门槛不高,但真要做到稳定可靠、不丢卡、不误报,里面的细节远比你想象的多。这篇文章我就把基于RFID考勤系统的单片机程序,从方案选型、硬件搭建到代码实现、调试排障,完整拆开讲一遍。项目默认用 STM32F103 + RC522 这套经典组合作为主线,兼容性很强,看完你完全可以照着搭出一台能实际运行的考勤原型机。适合正在做课设、毕设,或者想入门RFID和单片机的嵌入式开发者参考。

1. 项目需求与方案选型

1.1 考勤系统到底需要什么功能

做项目第一步永远是先把需求钉死,不然写代码一定会被自己反复推翻。一套最小可用考勤系统,至少要包含这几件事:

  • 卡片识别:RFID读卡器能稳定读出卡片唯一编号。
  • 合法性校验:判断当前卡片是否在预先录入的白名单里。
  • 打卡时间记录:需要实时时钟,记录“谁在什么时间打了卡”。
  • 结果反馈:显示器和蜂鸣器结合,告诉用户打卡成功还是失败。
  • 防重复打卡:同一张卡短时间内重复刷,系统要能识别并拒绝,不然就成了刷存在感。
  • 掉电存储:考勤记录不能一断电就清零,必须存到非易失性存储里。

很多刚接触的人容易上来就写RFID驱动,或者先调屏幕,这是顺序误区。正确做法是先列出功能清单,再反推硬件选型和程序模块划分。比如你如果连“防重复打卡”这个需求都没确认,写程序时可能就不会做时间窗判断,后期返工成本很高。

1.2 器件选型背后的取舍逻辑

RFID读卡方案上,我优先推荐RC522,它工作在13.56MHz高频段,走ISO14443A协议,读卡距离大概3到6厘米,非常贴近桌面打卡应用场景。市面上也有便宜的125kHz ID卡模块,读卡距离远一些,ID卡本身也很廉价,但ID卡几乎没有加密能力,属于“可轻易复制”的类型。如果是做演示和课设,RC522配M1卡是最通用的组合;如果是要正式商用,建议直接上CPU卡或国密卡,程序接口差别不大,但安全性完全不是一个量级。

单片机选型上,STM32F103C8T6是我在这类项目里用得最多的芯片。原因很实际:主频72MHz跑RC522毫无压力,硬件SPI、硬件I2C都有,调试工具链成熟,板子还便宜。有人会问能不能用51单片机,答案是能,但不推荐。RC522通信本身是SPI协议,51没有硬件SPI,需要用普通GPIO模拟时序,写起来不说多难,至少多了一部分调试成本。如果手里只有51开发板,注意电平问题,RC522是3.3V逻辑器件,别直接接5V引脚,否则容易烧模块。

其他外设选型我一般这样配:时钟用DS1302,便宜且资料多,如果对精度有要求直接换DS3231,温度漂移小很多;存储用AT24C02或AT24C64,用来存白名单和考勤记录;显示用0.96寸OLED(I2C接口,只要两个IO),比1602液晶省脚且显示信息量更大。这套组合下来总成本在40块以内,学生党完全能承受。

1.3 系统整体工作流程

把需求转化为程序流程图,大致是这样的逻辑:

系统上电后初始化所有外设,读取白名单和考勤记录索引,然后进入待机状态。当有人把卡放到读卡区,RC522触发寻卡成功,程序立即停止待机循环,转去执行防冲突算法拿到完整卡号。拿到卡号后先比对白名单,不在名单里直接提示“陌生卡”;在名单里再判断这次刷卡和上次刷卡间隔,如果小于设定时间窗(比如5分钟),提示“已打卡”并拒绝记录;如果间隔正常,就读取RTC时间,把“卡号 + 时间 + 结果”写入EEPROM,同时更新OLED显示,蜂鸣器短响一声表示成功。处理完毕,程序回到待机状态,等待下一次刷卡。

这个流程看着简单,但真正写代码时,你会发现每一步都有不少细节要处理,比如RC522寻卡函数的返回码判断、EEPROM磨损均衡、时钟芯片的BCD码转换等等。下面我把硬件先搭出来,再逐个环节拆解代码。

2. 硬件电路搭建与关键细节

2.1 硬件清单

动手接线之前,先把物料表列清楚。我这里给的是最常用的配置,所有器件都可以在常见电商平台买到。

器件型号/规格作用
主控板STM32F103C8T6最小系统板核心控制
射频读卡模块RC52213.56MHz非接触式读卡
卡片M1卡 / NTAG215存储卡号信息
实时时钟DS1302产生时间戳
存储芯片AT24C02 或 AT24C64保存白名单和考勤记录
显示模块0.96寸OLED SSD1306显示时间、状态
蜂鸣器有源蜂鸣器模块提示成功/失败
电源5V适配器 + AMS1117-3.3稳压为主控和RC522供电
按键轻触开关 x2系统设置、卡片录入

2.2 RC522与单片机的接线

RC522接口是SPI,模块上一共8个引脚:SDA(片选)、SCK(时钟)、MOSI(主出从入)、MISO(主入从出)、IRQ(中断请求)、RST(复位)、GND、VCC。注意这个模块的VCC是3.3V,千万别直接上5V。

我使用的接线方式如下,以STM32F103C8T6的SPI1为例:

RC522引脚STM32引脚
SDAPA4(软件片选)
SCKPA5
MOSIPA7
MISOPA6
RSTPB0
IRQ不接(轮询方式)
GNDGND
VCC3.3V

IRQ引脚在这里可以不接,程序用轮询方式检查读卡状态,对于考勤这种低频触发场景完全够用,还省一个引脚。接线的核心原则是:线越短越好,杜邦线长度控制在20厘米以内,超过了会影响SPI时钟边沿质量,造成读卡不稳定。如果现场空间受限,可以把SPI时钟频率降下来,后面调试章节细说。

2.3 供电与布局注意事项

RFID读卡模块在工作时会瞬间拉高电流,特别是寻卡瞬间,如果电源纹波大,单片机容易复位。我的做法是给系统板单独用AMS1117-3.3稳压供电,RC522和主控共用一个3.3V电压域,外部5V只能进开发板的电源输入端。在RC522的VCC和GND之间加一个100uF电解电容并一个104瓷片电容,效果很明显,至少能解决一半的“读卡过程中单片机重启”问题。

天线线圈的布局也值得注意。RC522的天线区上方不要放金属物体,包括金属桌面板、金属外壳,会显著缩短读卡距离。单片机晶振和RC522天线保持至少3厘米距离,天线附近也不要走长排线。如果发现读卡距离只有1厘米出头,先怀疑天线被遮挡或者供电不足,别急着换模块。

3. 单片机程序设计与实现

3.1 主程序状态机架构

考勤程序如果全部塞在while循环里,功能一多就会乱套。我习惯用状态机组织整个程序逻辑,把系统划分为几个清晰状态:上电初始化、待机扫描、卡片处理、结果显示。

typedef enum { SYS_INIT, SYS_IDLE, CARD_DETECTED, CARD_PROCESS, SHOW_RESULT } SystemState; SystemState state = SYS_INIT; while (1) { switch (state) { case SYS_INIT: System_Init(); state = SYS_IDLE; break; case SYS_IDLE: if (RC522_CheckCard()) { state = CARD_DETECTED; } break; case CARD_DETECTED: UID uid; if (RC522_ReadUID(&uid)) { state = CARD_PROCESS; } else { state = SYS_IDLE; } break; case CARD_PROCESS: attendance_result_t result = Attendance_Process(uid); display_show(result); buzzer_feedback(result); state = SYS_IDLE; break; default: state = SYS_IDLE; break; } }

状态机的核心好处是每个状态职责单一,排查问题非常直观。比如发现屏幕卡死,直接看显示相关状态;发现读卡不响应,就只查IDLE和DETECTED这两个状态之间的跳转条件。对于这种小型项目,状态机不会增加多少代码量,但可维护性提升很明显。

3.2 RC522底层驱动与SPI通信

要用好RC522,先要理解它和单片机之间的SPI通信。RMFRC522数据手册里寄存器很多,但实际项目里你只需要掌握几个关键函数:

  • 写RC522寄存器:用于下发命令和配置。
  • 读RC522寄存器:用于获取状态和数据。
  • 发送命令帧:比如0x52寻卡、0x93防冲突。
  • 读取返回数据:拿到卡序列号。

SPI初始化建议把时钟频率设置在100kHz到1MHz之间。RC522数据手册说最高支持10MHz,但实际用杜邦线连接时,跑到几MHz就非常容易出错,特别是环境有干扰时。我实测比较稳的速率是500kHz,一次寻卡加防冲突的流程不到50毫秒,对考勤来说完全够快。

void RC522_WriteByte(uint8_t reg, uint8_t value) { CS_LOW(); SPI_ExchangeByte((reg << 1) & 0x7E); // 写命令,地址左移一位 SPI_ExchangeByte(value); CS_HIGH(); } uint8_t RC522_ReadByte(uint8_t reg) { uint8_t val; CS_LOW(); SPI_ExchangeByte(((reg << 1) & 0x7E) | 0x80); // 读命令,最高位置1 val = SPI_ExchangeByte(0x00); CS_HIGH(); return val; }

读卡的核心命令流程是寻卡 -> 防冲突 -> 选卡。寻卡发送PCD_TRANSCEIVE命令和0x52字节,相当于在射频场里喊一声“谁在场”;有卡片响应后,执行防冲突循环,拿到4字节的卡序列号(UID);最后选卡锁定这张卡。这三个步骤缺一不可,尤其防冲突这一步,很多人省略,结果两张卡同时进场时程序就乱了。

3.3 卡号识别与防重复打卡逻辑

拿到卡号之后,程序要做两件事:合法性判断和时间窗判断。

合法性判断最简单的实现是维护一张白名单数组:

const uint32_t valid_card_list[] = { 0xA1B2C3D4, 0x12345678, 0xABCDEF01 };

把读到的UID转成32位整数后,遍历这个数组。这种做法在白名单数量少的情况下简单可靠,但每次加卡都要重新编译烧录程序,灵活性差。更好的方案是把白名单存在AT24C04里,增加一个“读卡授权”模式:按下设置键进入授权状态,刷入新卡就把UID写入EEPROM,这样管理员不需要懂单片机也能维护名单。

防重复打卡逻辑用时间窗实现。系统保存每个卡号最后一次成功打卡的时间戳,每次合法刷卡后,取当前时间与上次时间做差值,如果小于设定值就拒绝本次打卡。这个逻辑要放在“打卡成功”记录之前执行,否则会出现边界问题,比如5分钟内第一次打卡成功但重复刷仍提示成功。

3.4 时间记录与EEPROM存储

记录格式我建议固定为:卡号(4字节) + 时间戳(4字节,Unix时间) + 状态(1字节)。每条记录9字节。如果用AT24C64(8KB),可以存大约900条记录,对多数考勤场景够用了。

EEPROM写入有两个坑要避免。第一是写入地址必须按页对齐,AT24C02的页大小是8字节,一次页写最多8字节,跨页写会出错。第二是EEPROM寿命大约10万次擦写,如果固定写同一个地址,用不了多久就废了。简单做法是用一个“当前写入地址”变量,每写一条记录就往后偏移,写满了再回到起点覆盖最老数据,实现一个简单的环形缓冲。这个指针变量也存到EEPROM尾部固定地址,避免掉电复位后不知道写到哪里。

RTC时间读取方面,DS1302通过三个线操作,数据格式是BCD码,读取后需要转换成十进制再用于显示和存储。建议在系统初始化时先判断RTC是否走时正常,如果发现时间异常(比如年份读出来是0),就在OLED上提示用户设置时间,避免记录了一堆“1970年”的乌龙数据。

3.5 核心代码示例

下面是我在考勤项目里的主处理函数,去掉了平台相关细节,保留完整逻辑:

attendance_result_t Attendance_Process(UID uid) { // 步骤1: 查找白名单 uint8_t allow = 0; for (int i = 0; i < white_list_count; i++) { if (memcmp(uid.bytes, white_list[i].uid, 4) == 0) { allow = 1; break; } } if (!allow) { return RESULT_DENIED; } // 步骤2: 检查重复打卡 uint32_t now = RTC_GetUnixTime(); uint32_t last = EEPROM_ReadLastTime(uid.bytes); if ((now - last) < CHECK_INTERVAL_SEC) { return RESULT_REPEATED; } // 步骤3: 写入考勤记录 uint8_t record[9]; memcpy(record, uid.bytes, 4); record[4] = (now >> 24) & 0xFF; record[5] = (now >> 16) & 0xFF; record[6] = (now >> 8) & 0xFF; record[7] = now & 0xFF; record[8] = 0x01; // 状态:正常 EEPROM_RingWrite(record, sizeof(record)); return RESULT_SUCCESS; }

这段代码把合法性校验、时间窗判断、存储串起来了。实际项目里还需要在步骤2之前加一个“白名单不在本机”的处理分支,但核心思想是一致的。

3.6 数据安全与防复制设计

很多人做考勤项目时会忽略一个问题:普通M1卡的UID是可以被读写设备复制和改写的。如果整个考勤系统只校验UID,那别人拿一个读卡器就能复制所有卡。应对措施有两个层次:

第一,如果只是课程设计,可以在PPT和报告里说明“本设计适用于演示环境,正式环境需配合加密卡”。第二,更进一步的方案是,不把白名单判定放在单片机端的UID比对,而是要求M1卡在某个特定扇区写入本系统自定义的加密数据。读卡的时候先读扇区密码验证,再读取该扇区内容与系统预设值比对,双因子校验。这样即使有人复制了UID,也读不出扇区里的密码和数据,复制出来的卡依然无效。

这个思路对于面向实际部署的考勤项目非常重要,建议写入你的设计文档里,答辩时会是加分项。

4. 常见问题与调试实录

4.1 常见问题速查表

这部分是我自己反复踩过、也帮别人排查过的问题汇总。

现象可能原因解决办法
读不到任何卡RC522供电不足、SPI速率过高降SPI到500kHz,检查3.3V电源稳定性
能读卡但UID随机变化接线过长、接触不良缩短杜邦线,用示波器查SPI信号质量
程序上电后就乱跑电源纹波大导致复位在电源引脚加100uF和104去耦电容
蜂鸣器长响不止GPIO配置错误或驱动电流不足检查蜂鸣器是低电平触发还是高电平触发
打卡时间全是0DS1302初始化失败或电池没电检查电池电压,重新初始化RTC
掉电后打卡记录丢失EEPROM写入时掉电、地址未做环形缓冲加掉电检测,写入完成后再更新地址指针
电脑读卡软件提示“数据连接错误”串口被占用、波特率不匹配或USB转串口芯片驱动未装检查设备管理器识别情况,确认波特率与软件设置一致
读卡距离只有1厘米天线被金属遮挡、天线线圈变形清理天线区域,避免金属桌面直贴模块

这里单独说一下“数据连接错误”的排查。很多初学者通过串口把单片机连到PC端考勤管理软件时,软件提示连接错误,第一时间会怀疑程序。但80%的情况是USB转串口驱动没装好、串口号选择错误,或者软件里波特率与固件实际波特率不一致。建议先在电脑上用串口助手发一个测试字符串,如果能收到回显,再打开管理软件。这个排查顺序能少走一半弯路。

4.2 实测中遇到的三个典型问题

第一个问题是SPI通信乱码。我最初用杜邦线把RC522接到STM32核心板,SPI速率配到2MHz,结果寻卡成功率只有一半,经常读到错误的CRC。折腾半天后发现是杜邦线太长加上模块VCC走了一条很细的飞线。后来把电源线加粗、SPI速率降到500kHz,问题立刻消失。RC522的SPI抗干扰能力没有想象中那么强,实际项目一定要留足余量。

第二个问题是刷卡偶尔连触发两次。现象是刷一次卡,蜂鸣器响两声,记录出现两条。原因是卡片离开读卡区时射频场电离导致再次触发寻卡。解决办法是在记录写入后增加一个5秒的冷却标志,冷却时间内即使读到同一张卡也不处理,只更新显示。这个冷却标志和防重复打卡时间窗是两个不同概念,前者是硬件防抖,后者是业务逻辑。

第三个问题是批量记录写入EEPROM时偶发坏数据。排查后发现是页写入跨页导致的,我使用AT24C64时没注意它页大小是32字节,数据一旦跨页就出错。修复方式是把记录缓冲区强制按页大小对齐,并在写前检测当前页剩余空间,不够就先去下一页。这个坑写EEPROM项目时非常常见,建议直接用一个通用的PageWrite函数封装掉。

4.3 提升稳定性的几条经验

做完这套系统,我有几个稳定性层面的经验分享。

上电初始化阶段别急着读卡,加一个500毫秒延时让RC522内部晶振稳定。很多“上电后第一次刷卡总失败”的问题,都是初始化时序不够导致的。我习惯先读RC522版本号寄存器,如果读出来是0x92或0x91,才认为模块正常;如果不是,就在屏幕上打印错误码,方便现场定位。

程序里加一个独立看门狗,特别是系统脱离开发板、用电池独立供电的场景。如果外接干扰导致程序跑飞,看门狗能自动复位,保证考勤机长期运行不罢工。喂狗的位置放在主循环状态机每次切换时,不要在某个具体函数里喂,否则某个状态卡死时看门狗形同虚设。

卡片处理和显示刷新分离。OLED刷新一帧要几十毫秒,如果每次读卡都在同一段代码里做完整屏重绘,会导致读卡响应变慢、感应区域附近有其他卡片时误读概率变高。用缓存刷新策略,只在结果变化时刷新屏幕,平时显示时间时每秒钟刷新一次即可。

5. 项目落地与后续扩展

一套能用的考勤原型机到这里就算完成了。但如果想让这个项目真正落地,而不是停留在实验台上,有几个扩展方向值得花时间做。

第一个方向是上位机管理软件。单片机端的EEPROM存储空间有限,查询、统计报表这些操作也不适合在单片机上做。你可以加一个USB转串口,把考勤记录通过串口协议上传到PC,写一个Python脚本或Qt界面读取记录、生成Excel出勤表。很多管理软件提示“数据连接错误”,其实就是上位机和下位机之间的协议没对齐,建议先设计一个简单的帧格式,比如帧头 + 卡号 + 时间 + 校验,两边都按这个格式解析。

第二个方向是联网化。在STM32F103上挂一个ESP8266或ESP32模块,通过串口把考勤记录推送到HTTP接口,这样就形成了一个考勤云平台。配合微信小程序,管理员能直接在手机上查看谁来了谁没来。这个方向在答辩和实际商用中都很加分,但项目复杂度会高一个台阶,适合在基础程序跑通之后再慢慢加。

第三个方向是功耗优化。如果考勤机要装到没有插座的地方,可以用电池供电,程序里加入待机模式,平时单片机睡眠,刷卡时通过RC522的中断唤醒或者定时唤醒巡卡。STM32F103进入到待机模式电流可以降到微安级,配备一块2000mAh锂电池能扛很长时间。

程序本身的模块化也很重要。把RFID驱动、RTC驱动、OLED显示、EEPROM存储拆成独立模块,每个模块只暴露几个接口函数。这样后续换芯片平台,只需要重写底层驱动,上层考勤逻辑几乎不用动。我目前维护的好几个项目都是这样组织的,模块之间编译互不干扰,测试也能单独验证,效率比大而全的单一文件高很多。

最后唠叨一点实际项目里的体会。RFID考勤系统难的点从来不是RFID读卡本身,而是如何把“读卡这一个动作”安全、可靠、可追溯地变成一条考勤记录。RC522寻卡流程就那几行代码,真正的工程量在时间处理、存储管理、异常恢复这些容易被表面忽视的地方。如果你做完这个项目,能讲清楚EEPROM为什么需要磨损均衡、状态机为什么比顺序逻辑稳定、为什么要用双因子校验而不是只认UID,那这个课设或毕设的技术深度已经完全合格了。

做这个项目时我最大的一个教训是:硬件电路还没验证稳定就开始写业务代码,结果每次读卡出错都分不清是驱动问题还是逻辑问题。正确顺序应当是先点亮OLED、再让RC522能稳定读到卡号、最后才开始写考勤流程。前两步基础打牢了,后面基本就是一路顺风。希望这篇内容能帮你把弯路走直。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询