💡 划重点
- 用 ESP32 做主控,PN532 读 NFC 卡或手机模拟卡,OLED 显示签到结果,按键用于手动补签,LED 和蜂鸣器做反馈,联网后通过飞书 webhook 实时上报。整机成本控制在 80 元内,适合自习馆、合作社、共享办公区做轻量签到。
需求洞察
目标用户是自习馆经营者、合作社管理员,以及每天到店自习的学生和自由职业者。真实痛点是:纸质签到易代签、人工登记打断学习氛围、会员到店数据难统计、续费提醒缺少依据。NFC 打卡不用掏手机扫码,卡片或手机碰一下即可,安静、快速、可追溯。对经营者来说,飞书表格或群机器人能实时收到签到记录,后续可做时长统计和会员运营。这个项目硬件便宜、部署简单,值得作为自习馆数字化第一步。
方案设计
感知层用 PN532 读取 NFC 卡 UID,支持 Mifare 卡和手机模拟卡;按键用于忘带卡时手动补签。控制层由 ESP32 完成 UID 去重、60 秒防重复、白名单判断和联网调度。执行层用 OLED 显示“签到成功/请刷卡”,LED 闪亮、蜂鸣器短响提示。日志与联网层通过 WiFi 将 UID、时间、设备编号发送到飞书 webhook,同时串口输出调试日志。后续可加 SD 卡或云端数据库,实现入馆/离馆双记录。
物料清单
| 物料 | 数量 | 用途 | 估算单价 | 合计 |
|---|---|---|---|---|
| ESP32 开发板 | 1 | 主控与联网 | 18 元 | 18 元 |
| PN532 NFC 模块 | 1 | 读卡 | 28 元 | 28 元 |
| 0.96 寸 OLED | 1 | 显示状态 | 9 元 | 9 元 |
| 轻触按键 | 1 | 手动补签 | 0.5 元 | 0.5 元 |
| LED 与 220Ω 电阻 | 1 套 | 成功提示 | 0.5 元 | 0.5 元 |
| 有源蜂鸣器 | 1 | 声音反馈 | 1.5 元 | 1.5 元 |
| 杜邦线、洞洞板 | 1 套 | 接线与固定 | 6 元 | 6 元 |
| USB 数据线 | 1 | 供电与烧录 | 5 元 | 5 元 |
| NFC 白卡 | 2 | 会员卡 | 2 元 | 4 元 |
| 合计 | 72.5 元 |
接线与结构
PN532 的 SDA 接 GPIO21,SCL 接 GPIO22;OLED 同 I2C 总线,地址 0x3C,PN532 地址 0x24,不冲突。LED 接 GPIO2,串 220Ω 电阻。按键一端接 GPIO4,另一端接 GND,使用内部上拉。蜂鸣器接 GPIO5。微调说明:增加 PN532 复位脚到 GPIO32,上电更稳定;IRQ 不接,用轮询读取。PN532 必须拨到 I2C 模式,供电 3.3V,逻辑电平不要接 5V。外壳可用纸盒或亚克力,PN532 线圈朝外,OLED 开窗,按键和 LED 露出。
核心代码
#include <Wire.h>
#include <Adafruit_PN532.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <HTTPClient.h>
#define SDA_PIN 21
#define SCL_PIN 22
#define LED_PIN 2
#define KEY_PIN 4
#define BUZZER_PIN 5
#define PN532_RST 32
Adafruit_SSD1306 oled(128, 64, &Wire;, -1);
const char* ssid = "自习馆WiFi";
const char* pass = "密码";
const char* webhook = "飞书机器人地址";
String lastUid = "";
unsigned long lastTime = 0;
void show(String a, String b) {
oled.clearDisplay();
oled.setTextSize(1);
oled.setCursor(0, 0);
oled.println(a);
oled.setCursor(0, 24);
oled.println(b);
oled.display();
}
void sendFeishu(String uid) {
if (WiFi.status() != WL_CONNECTED) return;
HTTPClient http;
http.begin(webhook);
http.addHeader("Content-Type", "application/json");
String body = "{\"msg_type\":\"text\",\"content\":{\"text\":\"NFC签到 UID:" + uid + "\"}}";
http.POST(body);
http.end();
}
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
Wire.begin(SDA_PIN, SCL_PIN);
show("NFC签到", "启动中");
nfc.begin();
nfc.SAMConfig();
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) delay(500);
show("NFC签到", "请刷卡");
}
void loop() {
uint8_t uid[7];
uint8_t len;
if (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &len;, 100)) {
String nowUid = "";
for (uint8_t i = 0; i < len; i++) {
if (uid[i] < 16) nowUid += "0";
nowUid += String(uid[i], HEX);
}
if (nowUid != lastUid || millis() - lastTime > 60000) {
lastUid = nowUid;
lastTime = millis();
digitalWrite(LED_PIN, HIGH);
tone(BUZZER_PIN, 1800, 120);
show("签到成功", nowUid);
sendFeishu(nowUid);
delay(800);
digitalWrite(LED_PIN, LOW);
show("NFC签到", "请刷卡");
}
}
if (digitalRead(KEY_PIN) == LOW) {
show("手动补签", lastUid);
sendFeishu(lastUid);
delay(500);
}
}
调试步骤
先不接 PN532,只烧录 I2C 扫描程序,确认 OLED 地址 0x3C 和 PN532 地址 0x24。再检查 PN532 拨码是否为 I2C,供电是否 3.3V,复位脚是否接 GPIO32。第三步串口查看刷卡 UID,若读不到卡,靠近线圈并检查天线是否被金属遮挡。第四步测试 OLED 显示、LED 和蜂鸣器。第五步配置 WiFi 和飞书 webhook,先发测试消息,确认机器人关键词或签名设置正确。最后联调:同一张卡 60 秒内只上报一次,按键可补签,断电重启后自动重连。