自制智能门锁(NFC+小程序远程开锁)
这是一次真实可复现的智能门锁改造记录。起因很简单:传统门锁太麻烦,而市面上的智能门锁要么太贵、要么不开放。
所以我决定:
- 用 ESP8266 自己做一把
- 支持 NFC 本地刷卡
- 支持 微信小程序远程开锁
- 成本可控、逻辑透明、代码完全可读
一、这个项目能做什么?
在开始之前,先明确目标功能:
-
📡 刷 IC 卡开锁(NFC)
-
📱 微信小程序远程开锁(MQTT)
-
🔁 自动回位关锁(防止舵机空转)
-
⚙️ 非阻塞网络设计,刷卡与联网互不影响
适用场景:老旧门锁、宿舍门、出租屋、工作室内门等。
二、整体方案与架构
先看整体结构,这一步能帮你快速判断这个方案是否适合你。
┌────────┐ NFC ┌──────────┐
IC卡 ────────────────▶ RC522
└────────┘ └────┬─────┘
│ SPI
┌──────────────┐ WiFi + MQTT ┌───▼────────┐ PWM ┌─────────┐
微信小程序 ◀────────────▶ ESP8266 ─────────────▶ MG90S
└──────────────┘ D1 UNO R3 舵机
└────────────┘ └─────────┘
核心思想只有一句话:
ESP8266 作为中枢,NFC 与 MQTT 都只是“开门信号源”。
三、准备
1.硬件
| 硬件 | 说明 | |
|---|---|---|
| D1 UNO R3 | ESP8266 主控(自带 WiFi) | 图示 |
| MFRC-522 | RFID IC 卡模块(⚠️3.3V) | 图示 |
| MG90S | 180° 金属齿轮舵机 | 图示 |
| 杜邦线 | 公对母若干 | 图示 |
⚠️ RC522 只能接 3.3V,接 5V 很容易直接烧模块。
2.硬件连接
这是最容易出问题的一步,直接给出完整对照表。
| D1 UNO R3 | 舵机 | RC522 |
|---|---|---|
| GND | 黑线 | GND |
| 5V | 红线 | — |
| D10 | 黄线(PWM) | — |
| 3.3V | — | VCC |
| D15 | — | RST |
| D14 | — | SDA / SS |
| D11 | — | MOSI |
| D12 | — | MISO |
| D13 | — | SCK |
舵机颜色不统一,以商品说明为准(常见:红=VCC,黑=GND,黄=信号)。 IC卡感应模块的IRQ不用连接
3.软件 IDE
安装IDE
Arduino IDE 下载地址 ,用于将控制代码写入开发板
安装ESP8266程序
安装库

安装串口驱动
开发板与IDE通讯,IDE写入程序到开发板,如果是买的和我同款开发板,可以下载以下地址驱动,如果是其他开发板,一般购买详情页有相关资料的下载地址,可以下载。
通过网盘分享的文件:ch340驱动 链接: https://pan.baidu.com/s/1jEzqaJpXa2Sio2-H7DcaVA?pwd=ngbr 提取码: ngbr
创建远程控制账号
-
打开小程序

-
创建一个锁,然后点击【使用教程】

-
在教程页面复制账号和密码

-
修改代码
const char* mqtt_broker = "mqtt.zkphp.com"; const int mqtt_port = 2883; const char* mqtt_user = "小程序获取的用户名"; const char* mqtt_password = "小程序获取密码";
写入程序
必须完成以上所有步骤。然后用数据线将开发板连接到电脑上。
-
选择开发板

-
选择串口

-
打开串口监视器,然后设置比特率为9600

-
复制完整代码 点击查看完整代码
-
修改代码中WiFi密码
// WiFi 配置
const char* ssid = "WiFi账号";
const char* password = "密码";
-
上传代码

如果在串口监视器中见到以下信息,则表示WiFi和mqtt远程控制连接成功。
点击微信小程序解锁按键,串口监视器中见到以下信息,则表示mqtt远程控制成功。
ic卡贴近感应模块,串口监视器中见到以下信息,则表示IC卡感应模块功能正常
可以将uid添加到代码中,将IC卡设置为授权卡片。代码如下:const uint64_t ALLOWED_IDS[] = {0x03C09EEE, 0x1D193DE0};
完整代码
#include <Servo.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <SPI.h>
#include <MFRC522.h>
// ========== 配置 ==========
const uint64_t ALLOWED_IDS[] = {0x03C09E22};//写死的IC卡
const size_t NUM_ALLOWED = sizeof(ALLOWED_IDS) / sizeof(ALLOWED_IDS[0]);
// WiFi 配置
const char* ssid = "你的WiFi名";
const char* password = "WiFi密码";
// MQTT 配置
const char* mqtt_broker = "mqtt.zkphp.com";
const int mqtt_port = 2883;
const char* mqtt_user = "小程序获取的用户名";
const char* mqtt_password = "小程序获取密码";
// ========== 全局对象 ==========
WiFiClient espClient;
PubSubClient client(espClient);
MFRC522 mfrc522(D14, D15);
Servo doorServo;
const int SERVO_PIN = D10;
char command_topic[128];
// ========== 状态定义 ==========
enum DoorState {
DOOR_IDLE,
DOOR_OPEN,
DOOR_UNAUTHORIZED
};
DoorState doorState = DOOR_UNAUTHORIZED;
unsigned long doorOpenTime = 0;
bool needOpen = false;
#define SERVO_CLOSE 1020
#define SERVO_OPEN 1500
// ========== 计时器变量 (用于非阻塞) ==========
unsigned long lastMqttRetry = 0;
const unsigned long MQTT_RETRY_INTERVAL = 5000; // MQTT重连间隔5秒
unsigned long lastWifiCheck = 0;
const unsigned long WIFI_CHECK_INTERVAL = 1000; // WiFi状态检查间隔1秒
// ========== 函数声明 ==========
void openDoor();
void closeDoor();
void doorServoInit();
void mqttCallback(char* topic, byte* payload, unsigned int length);
void handleNetwork(); // 统一的网络处理函数
void readCard();
// ========== MQTT 回调 ==========
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("收到消息 [");
Serial.print(topic);
Serial.print("] : ");
String msg = "";
for (int i = 0; i < length; i++) {
msg += (char)payload[i];
}
Serial.println(msg);
if (msg == "ON" && doorState != DOOR_OPEN) {
needOpen = true;
}
}
// ========== 网络管理 (核心修改部分) ==========
void handleNetwork() {
unsigned long now = millis();
// 1. 检查 WiFi 连接状态
if (now - lastWifiCheck >= WIFI_CHECK_INTERVAL) {
lastWifiCheck = now;
// 如果 WiFi 未连接,只输出状态,不阻塞。
// ESP8266 在 STATION 模式下会自动在后台尝试重连,不需要在 loop 中频繁调用 begin
if (WiFi.status() != WL_CONNECTED) {
// 可以在这里加一个长时间未连接的判断,例如超过30秒重置WiFi,目前保持简单
// Serial.println("WiFi 连接中...");
return; // WiFi 没好,就不用试 MQTT 了
}
}
// 如果 WiFi 没连上,直接退出,不执行后面 MQTT 逻辑
if (WiFi.status() != WL_CONNECTED) return;
// 2. 检查 MQTT 连接状态
if (!client.connected()) {
// 使用非阻塞计时器,每隔 5 秒尝试一次连接
if (now - lastMqttRetry > MQTT_RETRY_INTERVAL) {
lastMqttRetry = now;
Serial.println("尝试连接 MQTT Broker...");
// client.connect 可能会有短暂阻塞 (约1-2秒超时),但在重试间隔内不会影响 NFC
if (client.connect("door-hook", mqtt_user, mqtt_password)) {
Serial.println("✅ MQTT 连接成功");
client.subscribe(command_topic);
} else {
Serial.print("❌ MQTT 连接失败, rc=");
Serial.print(client.state());
Serial.println(" (将在5秒后重试)");
}
}
} else {
// 只有连接成功时才调用 loop,处理接收到的消息
client.loop();
}
}
// ========== 舵机控制 ==========
void openDoor() {
if (doorState == DOOR_OPEN) return;
Serial.println("open door!!");
doorServo.writeMicroseconds(SERVO_OPEN);
doorOpenTime = millis();
doorState = DOOR_OPEN;
needOpen = false;
}
void closeDoor() {
if (doorState == DOOR_IDLE) return;
Serial.println("close door!!");
doorServo.writeMicroseconds(SERVO_CLOSE);
doorState = DOOR_IDLE;
}
void doorServoInit() {
doorServo.attach(SERVO_PIN);
closeDoor();
}
// ========== RFID 读卡 (保持高响应速度) ==========
void readCard() {
// 如果门正开着或准备开,暂不读卡,避免逻辑冲突
if (needOpen || doorState == DOOR_OPEN) return;
// 这里的检查非常快,如果没有卡立即返回
if (!mfrc522.PICC_IsNewCardPresent()) return;
if (!mfrc522.PICC_ReadCardSerial()) return;
Serial.println("检测到卡片...");
if (mfrc522.uid.size > 8) {
Serial.println("UID too long");
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
return;
}
uint64_t uidInt = 0;
for (byte i = 0; i < mfrc522.uid.size; i++) {
uidInt = (uidInt << 8) | mfrc522.uid.uidByte[i];
}
Serial.print("UID (hex): 0x");
Serial.println(uidInt, HEX);
bool authorized = false;
for (size_t i = 0; i < NUM_ALLOWED; i++) {
if (uidInt == ALLOWED_IDS[i]) {
authorized = true;
break;
}
}
if (authorized) {
Serial.println("✅ 授权通过");
needOpen = true;
} else {
Serial.println("🚫 未授权卡片");
}
// 必须停止当前卡片操作,否则下次循环可能读不到新卡
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
}
// ========== Setup ==========
void setup() {
Serial.begin(9600);
Serial.println("\n\n欢迎启动智能门禁系统!");
// 1. 初始化硬件
SPI.begin();
mfrc522.PCD_Init();
doorServoInit();
// 2. 初始化网络配置
snprintf(command_topic, sizeof(command_topic), "door/switch/%s/status", mqtt_user);
client.setServer(mqtt_broker, mqtt_port);
client.setCallback(mqttCallback);
// 3. 启动 WiFi (只调用一次,交给 ESP 后台处理连接)
// WiFi.mode(WIFI_STA); // 可选,强制为客户端模式
WiFi.begin(ssid, password);
Serial.println("WiFi 开始后台连接...");
}
// ========== 主循环 ==========
void loop() {
// 1. 优先处理读卡 (最高优先级)
readCard();
// 2. 处理开门/关门逻辑
if (needOpen) {
openDoor();
}
if (doorState == DOOR_OPEN && millis() - doorOpenTime > 3000) {
closeDoor();
}
// 3. 处理网络重连与消息 (非阻塞,低优先级)
handleNetwork();
// 4. 让出系统资源 (对 ESP8266 很重要)
yield();
}



