零知ESP32--RC522NFC考勤打卡系统

项目概述

ESP32 NFC 考勤打卡系统是一个基于 ESP32 微控制器、RC522 RFID 读卡模块和 ST7789 TFT 彩屏的智能考勤解决方案。系统支持 NFC 卡片刷卡签到/签退、实时时钟显示、Wi-Fi 联网、Web 管理后台、卡片注册管理、考勤记录查询等完整功能,适用于中小企业、实验室、兴趣小组等场景的日常考勤管理。本项目通过 SPI 总线共享技术,RC522 读卡模块和 ST7789 显示屏共用同一 SPI 总线协同工作。考勤数据和卡片信息通过 ESP32 的 NVS(Non-Volatile Storage)持久化存储,断电不丢失。配套的 Web 管理界面提供卡片管理、实时考勤状态查看等操作。

项目亮点

  • 稳定可靠的 SPI 总线共享方案:RC522 与 ST7789 共用 SPI 总线,通过 SPI.end()/SPI.begin() 复位机制解决冲突
  • NVS 持久化存储:卡片信息和打卡记录全部保存在 ESP32 的 NVS 分区,无需外部存储芯片
  • Web 管理后台:内建 Web 服务器,提供美观的响应式管理界面,支持卡片增删改查和实时考勤状态
  • NTP 自动校时:连接 Wi-Fi 后自动从阿里云 NTP 服务器同步北京时间
  • 去重打卡逻辑:同一张卡每天只能签到一次、签退一次,避免重复打卡
  • 刷卡注册模式:管理员模式下可直接刷卡注册新卡,注册完成后自动退出管理模式
  • 彩屏实时反馈:刷卡成功/失败/重复打卡时,屏幕显示对应动画和提示信息
  • Web API 鉴权保护:写接口采用 X-Auth-Token 头部鉴权,防止未授权访问

项目难点及解决方案

难点一:SPI 总线共享冲突

RC522 和 ST7789 共用 ESP32 的 SPI 总线(SCK=GPIO18, MOSI=GPIO23, MISO=GPIO19)。TFT_eSPI 库初始化后会将 SPI 频率设置为屏幕的高速模式(40MHz),而 RC522 只能工作在较低频率(约 5MHz),且 SPI Mode 不同。直接调用 RC522 读寄存器会返回 0x00。

解决方案:

在 tft.init() 之后、rfid.PCD_Init() 之前,执行 SPI.end() 再 SPI.begin() 复位 SPI 总线,使 RC522 能从默认低速模式重新初始化。MFRC522 库内部使用 beginTransaction() 自行管理 SPI 配置,每次读卡时自动适配 RC522 的工作频率。

难点二:NVS 持久化的指针悬空问题

早期版本使用 #define NVS_CARD_KEY(i) (String("c") + (i)).c_str() 获取 NVS 键名,但 String 对象是临时变量,表达式结束后立即被销毁,导致返回的 const char* 指针指向已释放的内存,Preferences 读取时造成 IllegalInstruction 崩溃。

解决方案:

改用静态 char 缓冲区 + snprintf 固定写入键名,返回静态存储区的指针,生命周期与程序相同,不会悬空:

cpp 复制代码
static char nvsKeyBuf[8];

static const char* nvsCardKey(int i) {

  snprintf(nvsKeyBuf, sizeof(nvsKeyBuf), "c%d", i);

  return nvsKeyBuf;

}

难点三:JavaScript 模板字符串转义

Web 管理界面的 HTML/JS 代码由 C++ 的 F() 宏包裹的 String 拼接而成。中文引号或 JS 模板字符串中的单引号容易与 C++ 字符串定界符冲突,导致编译错误或 JS 语法错误。例如原版 onclick 中的 '+'c.uid+'' 被当成了字符串字面量而非变量。

解决方案:

采用 data-* 属性 + querySelectorAll 事件委托模式替代内联 onclick 中的模板字符串拼接,避免多层引号嵌套的转义问题。同时移除 F() 字符串中所有的 JS 行注释 //,因为 F() 拼接不保留换行,会导致后续代码被注释掉。

目录

一、硬件系统部分

[1.1 硬件清单](#1.1 硬件清单)

[1.2 接线方案](#1.2 接线方案)

[1.3 硬件连接图](#1.3 硬件连接图)

[1.4 实物连接图](#1.4 实物连接图)

二、软件架构设计

[2.1 系统初始化 (setup)](#2.1 系统初始化 (setup))

[2.2 主循环逻辑 (loop)](#2.2 主循环逻辑 (loop))

三、代码拆分讲解

[3.1 数据结构设计](#3.1 数据结构设计)

[3.2 NVS 持久化](#3.2 NVS 持久化)

[3.3 考勤处理逻辑](#3.3 考勤处理逻辑)

[3.4 Web 管理界面](#3.4 Web 管理界面)

[3.5 屏幕显示与蜂鸣器](#3.5 屏幕显示与蜂鸣器)

四、操作过程及数据展示

[4.1 操作步骤](#4.1 操作步骤)

[4.2 演示视频](#4.2 演示视频)

五、技术原理

[5.1 工作原理](#5.1 工作原理)

[5.2 工作模式配置](#5.2 工作模式配置)

六、常见问题指引

[Q1: RC522 读取寄存器始终返回 0x00](#Q1: RC522 读取寄存器始终返回 0x00)

[Q2: 烧录后出现 IllegalInstruction 或 Guru Meditation 错误](#Q2: 烧录后出现 IllegalInstruction 或 Guru Meditation 错误)

[Q3: Web 管理页面无法访问](#Q3: Web 管理页面无法访问)

[Q4: Web 接口返回 401 Unauthorized](#Q4: Web 接口返回 401 Unauthorized)

[Q5: 打卡记录丢失](#Q5: 打卡记录丢失)

[Q6: 删除卡片时提示 "Not found"](#Q6: 删除卡片时提示 "Not found")


一、硬件系统部分

1.1 硬件清单

构建本系统需要以下硬件组件:

|------------|-----------------|------------|--------------------------|
| 序号 | 组件 | 数量 | 备注 |
| 1 | 零知ESP32 开发板 | 1 | 写入代码 |
| 2 | 零知ESP32扩展板 | 1 | 方便接线 |
| 3 | RC522 RFID 读卡模块 | 1 | MFRC522 芯片,13.56MHz |
| 4 | ST7789 TFT 显示屏 | 1 | 240×240 分辨率,SPI 接口 |
| 5 | 无源蜂鸣器 | 1 | 5V 有源或无源均可(无源音色更丰富) |
| 6 | NFC 标签/IC卡 | 若干 | Mifare Classic 1K S50 兼容 |
| 7 | 杜邦线 | 若干 | 母对母、公对母各若干 |
| 8 | Micro USB 数据线 | 1 | 供电和程序烧录 |

1.2 接线方案

以下是各模块与 ESP32 的详细接线对照表:

RC522 读卡模块接线

|------------------|--------------------|----------------|
| RC522 引脚 | ESP32 GPIO | 说明 |
| SDA (SS) | GPIO5 | SPI 片选 |
| SCK | GPIO18 | SPI 时钟线(与屏幕共用) |
| MOSI | GPIO23 | SPI 数据线(与屏幕共用) |
| MISO | GPIO19 | SPI 数据线(主机输入) |
| RST | GPIO27 | RC522 复位 |
| VCC | 3.3V | 供电(不可接 5V) |
| GND | GND | 共地 |

蜂鸣器接线

|---------------|--------------------|
| 蜂鸣器引脚 | ESP32 GPIO |
| 正极 (+) | GPIO25 |
| 负极 (-) | GND |

1.3 硬件连接图

1.4 实物连接图

二、软件架构设计

系统软件遵循 初始化 (setup) → 主循环 (loop) 的经典嵌入式架构。整体代码分为七大模块:初始化模块、SPI 总线管理模块、NFC 读卡模块、显示模块、Web 服务器模块、NVS 持久化模块以及蜂鸣器模块。

2.1 系统初始化 (setup)

系统启动时,setup() 函数按照严格的先后顺序执行以下初始化步骤:

  • 蜂鸣器初始化:调用 setupBuzzer(),根据 ESP32版本兼容性配置 LEDC PWM 通道
  • 屏幕初始化:调用 tft.init() + setRotation(3),显示启动动画
  • SPI 总线初始化:调用 SPI.begin() 启用 SPI,拉高 RC522 片选引脚避免误选中
  • SPI 复位(关键修复):先 SPI.end() 再 SPI.begin(),清除 TFT_eSPI 设置的高速 SPI 配置
  • RC522 初始化:调用 rfid.PCD_Init(),读取版本寄存器验证通信是否正常
  • NVS 数据加载:调用 loadCardsFromNVS() 和 loadRecordsFromNVS() 读取持久化的卡片和记录
  • Wi-Fi 连接:调用 connectWiFi(),最多尝试 20 秒
  • NTP 校时:如果 Wi-Fi 连接成功,调用 syncNTP() 同步网络时间
  • Web 服务器:调用 setupWebServer() 注册 HTTP 路由并启动
  • 显示就绪界面:showReadyScreen() + playStartupSound()

2.2 主循环逻辑 (loop)

loop() 函数是系统的主循环,每秒执行数十次,主要处理以下任务:

  • Web 请求处理:server.handleClient() 处理 HTTP 请求
  • 时钟刷新:updateReadyClock() 每秒局部刷新屏幕上的时间显示
  • Wi-Fi 状态检测:每 30 秒检查一次 Wi-Fi 连接状态,断开则自动重连
  • 异步音效播放:管理模式触发的提示音由主循环异步播放,避免阻塞 Web 处理线程
  • 管理模式超时检测:30 秒无操作自动退出管理模式
  • 刷卡检测:MFRC522 库的 PICC_IsNewCardPresent() + PICC_ReadCardSerial() 检测 NFC 卡片
  • 防抖处理:3 秒内重复刷卡被忽略
  • 考勤处理:已注册卡执行签到/签退逻辑;未注册卡在管理模式下自动注册,否则显示未知卡

三、代码拆分讲解

以下按照功能模块逐一讲解核心代码的设计思路和关键实现。

3.1 数据结构设计

系统定义了两种核心数据结构:

cpp 复制代码
struct CardInfo {

  char uid[CARD_UID_LEN];  // 卡片 UID,如 "C942C66D"

  char name[32];           // 持卡人姓名

  bool registered;          // 是否已注册

};



struct AttendanceRecord {

  char uid[CARD_UID_LEN];  // 卡片 UID

  char name[32];           // 持卡人姓名

  char timeStr[32];        // 打卡时间 "YYYY-MM-DD HH:MM:SS"

  bool checkedIn;          // true=签到, false=签退

};

固定数组存储避免了动态内存分配带来的碎片问题,MAX_CARDS=50 和 MAX_RECORDS=100 满足中小规模考勤需求。

3.2 NVS 持久化

Preferences 库将数据存储在 ESP32 的 NVS 分区中。卡片和记录分别以 "c0"-"c49" 和 "r0"-"r99" 为键,值为 "UID|NAME|1" 格式的管道分隔字符串。总条数分别存储于 "ccount" 和 "rcount" 键中。

首次启动时(NVS 无数据),loadCardsFromNVS() 会写入三张默认演示卡片(Zhang San, Li Si, Wang Wu)以便快速验证系统功能。

3.3 考勤处理逻辑

handleAttendance() 函数实现了完整的考勤处理流程:

  • 获取当前时间字符串和日期字符串(NTP 同步的北京时间)
  • 遍历所有记录,判断本卡今日是否已签到/已签退
  • 规则:未签到则签到,已签到则签退;若已签到又试图签到则判为重复打卡
  • 记录存储采用环形覆盖策略:达 MAX_RECORDS 上限时整体前移,新记录追加到末尾
  • 每次考勤操作后自动保存到 NVS,确保断电不丢失

3.4 Web 管理界面

generateDashboardHTML() 函数动态生成完整的 Web 管理页面,包含卡片管理、考勤记录、今日出勤状态三大功能模块。前端通过 fetch API 与后端 REST 接口通信:

  • GET /api/cards - 获取已注册卡片列表
  • GET /api/records - 获取考勤记录
  • POST /api/cards - 添加新卡片(需鉴权)
  • POST /api/cards/delete - 删除卡片(需鉴权)
  • POST /api/cards/rename - 重命名卡片(需鉴权)
  • GET /api/admin - 启用管理模式(需鉴权)
  • GET /api/admin/newcard - 轮询新注册卡通知

3.5 屏幕显示与蜂鸣器

系统通过 ST7789 彩屏提供丰富的视觉反馈:就绪界面显示 Wi-Fi 状态、IP 地址和实时时钟;打卡成功显示绿色签到动画或橙色签退动画;错误时显示红色错误提示。蜂鸣器提供四种不同的提示音:开机音、成功音、错误音和重复打卡的三短音。

四、操作过程及数据展示

4.1 操作步骤

首次使用

步骤 1:按照接线方案连接硬件,确保所有模块供电正常。

步骤 2:修改 ESP32-NFC.ino 中的 Wi-Fi 凭据(WIFI_SSID 和 WIFI_PASSWORD)。

步骤 3:修改 AUTH_TOKEN 为一个随机字符串,用于保护 Web 写接口。

步骤 4:上传代码到 ESP32,打开串口监视器(115200bps)观察启动日志。

步骤 5:确认 "RC522 固件版本" 和 "Wi-Fi OK" 信息,系统就绪后屏幕显示 "Tap Your Card"。

刷卡打卡(签到/签退)

将已注册的 NFC 卡片贴近 RC522 天线区域。系统将自动识别:若今日未签到则执行签到(屏幕显示绿色 "CHECK IN");若今日已签到则执行签退(屏幕显示橙色 "CHECK OUT")。若当日已签到和签退完毕,再次刷卡显示 "Already Done" 提示。每次有效打卡均伴有蜂鸣器提示音。

注册新卡片(管理模式)

方法一(管理员模式刷卡注册):

在 Web 管理页面点击 "管理模式" 按钮,然后在 30 秒内将新卡片贴近 RC522,系统自动注册并弹出改名对话框。注册完成后自动退出管理模式。

方法二(Web 页面手动添加):

在 "注册卡片管理" 区域输入卡片 UID(如 A1B2C3D4)和持卡人姓名,点击 "添加" 按钮。卡片 UID 可通过串口监视器在刷卡时获得。

查看考勤数据

在浏览器中访问 ESP32 的 IP 地址(http://192.168.x.x)。管理界面包含四个统计卡片(注册卡片数、打卡记录数、今日签到数、今日签退数)、卡片管理表格、今日出勤状态(未签到/未签退列表)以及考勤记录表格。页面每 10 秒自动刷新打卡记录。

删除卡片

在 "注册卡片管理" 表格中找到目标卡片,点击 "删除" 按钮,确认后即可删除。删除采用 swap-and-decrement 策略,确保卡片容量不泄漏。

4.2 演示视频

零知ESP32--NFC考勤打卡系统

五、技术原理

5.1 工作原理

系统的工作流程可以概括为:读卡 → 识别 → 处理 → 反馈。当 NFC 卡片靠近 RC522 天线时,RC522 通过 13.56MHz 射频场与卡片通信,读取卡片 UID(唯一标识符)。ESP32 通过 SPI 总线读取该 UID,然后在已注册卡片列表中查找匹配项。如果找到匹配项,系统根据当前时间和历史打卡记录判定是签到还是签退,并记录打卡信息。如果未找到匹配项,系统根据是否处于管理模式决定是自动注册还是显示未知卡错误。

打卡记录包含卡号、持卡人姓名、打卡时间(格式 YYYY-MM-DD HH:MM:SS)和打卡类型(签到/签退)。所有记录保存至 NVS 持久化存储,并通过 Web 接口向用户展示。

5.2 工作模式配置

普通模式(默认)

上电即进入此模式。屏幕显示就绪界面(Wi-Fi 状态、IP 地址、实时时钟),底部显示 "Tap Your Card"。已注册的卡片刷卡可正常签到/签退,未注册的卡片将被忽略并显示 "Unknown Card" 错误。

管理模式

通过 Web 管理页面 "启用管理模式" 按钮(或直接调用 GET /api/admin)激活。屏幕显示紫色管理界面,顶部有 "A" 标志。在 30 秒内刷卡可将新卡片自动注册到系统,注册完成后自动退出管理模式返回就绪界面。如果 30 秒内无刷卡,自动超时退出。

自检模式(调试用)

取消代码头部的 // #define TEST_RC522_ONLY 注释并重新上传,固件将跳过屏幕/Web/NVS 等所有模块,只反复读取 RC522 版本号并尝试读卡。此模式用于单独排查 RC522 硬件接线问题,排除 SPI 总线共享冲突因素。

SPI 总线共享机制

ST7789 和 RC522 共用 SPI 总线是本次项目最大的技术挑战。两者的 SPI 协议差异如下:

|------------|-------------------------|-------------------------|
| 参数 | ST7789 (屏幕) | RC522 (读卡器) |
| SPI Mode | Mode 3 (CPOL=1, CPHA=1) | Mode 0 (CPOL=0, CPHA=0) |
| 最大频率 | 40 MHz | ~5 MHz |
| 数据长度 | 8/9/16/18 bit 可变 | 8 bit 固定 |
| 片选逻辑 | 低电平有效 (CS=GPIO15) | 低电平有效 (SS=GPIO5) |

MFRC522 库在每次 SPI 事务开始时调用 beginTransaction() 动态设置 RC522 所需的工作参数,事务结束后调用 endTransaction() 释放总线。这使得两个设备可以在同一总线上共存而互不干扰。核心修复在于初始化阶段执行 SPI.end() 再 SPI.begin() 复位总线,让 RC522 的 PCD_Init() 能在干净的 SPI 环境下配置自己的寄存器。

六、常见问题指引

Q1: RC522 读取寄存器始终返回 0x00

这是最常见的问题,通常由 SPI 总线冲突引起。排查步骤:

  • 启用 TEST_RC522_ONLY 模式验证 RC522 硬件是否正常(排除屏幕干扰)
  • 确认接线:SDA=GPIO5, SCK=GPIO18, MOSI=GPIO23, MISO=GPIO19, RST=GPIO27
  • 确认 RC522 供电为 3.3V(不能接 5V)
  • 检查 MISO 线是否漏接(漏接会始终读回 0x00)
  • 检查所有 GND 是否共地
  • 尝试更换杜邦线或 RC522 模块

Q2: 烧录后出现 IllegalInstruction 或 Guru Meditation 错误

此错误通常由以下原因导致:

  • 更改分区表后未擦除旧数据:切换分区方案(如 huge_app → default)后,需使用 esptool erase-flash 擦除整个 flash,然后重新烧录
  • NVS 指针悬空:确保 nvsCardKey() 和 nvsRecordKey() 使用静态缓冲区返回 const char*,而非临时 String 对象的 c_str()
  • 命令:esptool.py --port COMx erase_flash(Windows 下端口号请根据实际情况修改)

Q3: Web 管理页面无法访问

排查步骤:

  • 确认 ESP32 已成功连接 Wi-Fi(屏幕显示 Wi-Fi: OK 和 IP 地址)
  • 确认电脑/手机与 ESP32 在同一局域网
  • 在浏览器中直接输入 IP 地址(不要加 https://)
  • 检查串口日志中是否有 Web 服务器启动的相关信息

Q4: Web 接口返回 401 Unauthorized

写接口(添加/删除卡片、管理模式)需要鉴权。请确保请求头中包含 X-Auth-Token,其值等于代码中 AUTH_TOKEN 常量的值。浏览器管理页面已自动处理鉴权,不需要额外操作。如果使用 curl 或 Postman 等工具调用 API,请添加 -H "X-Auth-Token: change-this-token" 请求头。

******Q5:******打卡记录丢失

NVS 存储有容量限制(约 15KB)。系统最多存储 100 条打卡记录,超出时旧记录会被覆盖。如有长期归档需求,建议通过 Web API 定期导出数据到外部服务器。在代码中设置 SERVER_HOST 可启用自动上传功能。

Q6: 删除卡片时提示 "Not found"

删除请求的 UID 必须与存储的 UID 完全一致(包括大小写)。系统存储时自动将 UID 转换为大写,因此删除时也需传入大写 UID。Web 管理页面已处理此逻辑,手动调用 API 时需自行转换。

相关推荐
炸膛坦客7 小时前
常见代码问题与解答:(二)因外部晶振频率改变导致 MCU 锁死的原因及处理方法
单片机·嵌入式硬件
小灰灰搞电子7 小时前
C++ boost::range 详解:基于最新版本的现代范围处理指南
开发语言·c++
王燕龙(大卫)7 小时前
fastdds笔记
网络·c++·笔记
脱胎换骨-军哥8 小时前
C++ 与 Go 生成质量比拼,谁是分布式主力语言
c++·分布式·golang
快乐星空Maker8 小时前
C++【生存游戏】开发:荒岛往事 第三期
开发语言·c++·游戏·编程语言
郝学胜-神的一滴8 小时前
Qt 高级编程 035:无边框窗口阴影以及圆角双特效实现
开发语言·c++·qt·程序人生·用户界面
AA陈超8 小时前
006 T03 — 蓝图操作指南
c++·游戏·架构·ue5·github·虚幻引擎
小保CPP8 小时前
OCR C++ Tesseract从OpenCV中获取图片
c++·人工智能·opencv·ocr·模式识别·光学字符识别
ShiMetaPi8 小时前
ShiMetaPi Pico-G1:百元级黑光全彩AI ISP开发板
人工智能·嵌入式硬件·pico·isp