基于Blinker与ESP32的RFID门禁系统 就地、远程双控制-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 8039|回复: 13

[分享] 基于Blinker与ESP32的RFID门禁系统 就地、远程双控制

[复制链接]
发表于 2020-4-3 11:04 | 显示全部楼层 |阅读模式
本帖最后由 X-iaowh 于 2020-4-4 16:40 编辑

1 项目描述
1.1 项目说明
       项目主要使用乐信的ESP32开发板作为控制器,搭配RFID-RC522模块及电磁门锁等制作了一个门禁系统,实现用主卡进行门禁卡信息录入或删除,用已录入的门禁卡实现开门的动作。后增加了点灯科技的Blianker APP的使用,构建了一个简单物联网项目,以此现实远程通过MQTT通信达到远程控制的目的,或者在同一网络下时,用局域网通信控制开门动作。由此,项目除了实现用门禁卡开门的功能外,还可实现以下的功能。例如,当有钟点工来打扫卫生或者家里有亲戚/客人来访刚好不在家的时候,就可以通过远程开门,让他们进入住所。此外,如果是熟人到访,自己又窝在被窝/沙发上不想起身时,就可以用APP开个门,就非常方便。
       目前项目还未实现门禁卡信息记录及上传云端的功能,如果后面时间充裕,将修改补充此文档,如有朋友已经有过类似的开发经历,望指导赐教。

也可以直接看放在B站的视频链接,来了解这个门禁系统:https://www.bilibili.com/video/BV1g54y1d7DW


1.2 流程图   
       程序主要功能现实的流程图如下,不包含远程控制部分,如有些许错误,欢迎指出

主要功能流程图

主要功能流程图

2 前期准备
2.1 软件
    · Arduino IDE
       可在Arduino官网进行下载最新版本:https://www.arduino.cc/en/Main/Software
       如果下载的是可执行文件版本(.exe)直接安装即可,如果是压缩包(.zip),解压即可运行
       详细安装教程可参考社区:Arduino IDE安装教程 教程提供百度云安装方式,比官网下载来的快

    · Blinker APP安装
       Blinker APP 安装地址:https://www.diandeng.tech/doc/app-download
       自行选择Android或iOS版本进行下载安装,iOS也可在应用商店直接搜索到

    · Blinker 库文件(blinker-library-master.zip)
       blinker-library-master.zip文件下载地址:https://www.diandeng.tech/doc/sdk-download
       选择Arduino(C++) SDK下载即可,将下载好的库文件压缩包解压安装到"我的电脑->文档->Arduino->libraries"文件夹中

    · RFID 库文件
       Arduino RFID Library for MFRC522下载地址:https://github.com/miguelbalboa/rfid
       点击红色框内的"Clone or download"后选择"Download ZIP"解压于libraries文件夹中

RFID下载提示

RFID下载提示


    · 安装ESP32 SDK
       使用代理直接在Arduino IDE进行安装,或使用论坛中的压缩包进行安装,直接运行并解压即可-->文件及安装教程

2.2 硬件
    · ESP32开发板
       个人本次使用的ESP32开发板是UNO D1 R32或Node MCU开发板,如下图所示

UNO D1 R32开发板

UNO D1 R32开发板

Node MCU开发板

Node MCU开发板


    · MFRC522射频模块
       MFRC522套装包括RFID-RC522模块、标准S50空白卡、S50异型卡及排针,如下图所示

RFID-RC522模块

RFID-RC522模块


    · 继电器
       继电器所采用的是可选择触发方式的继电器

触发方式可选 继电器

触发方式可选 继电器
    · 电磁门锁
       电磁门锁采用12V小型电磁门锁,方便开关电源供电,实际项目时可根据需要选择,如下图所示

电磁门锁

电磁门锁
    · 其他
       LED灯、220Ω电阻、按键、面包板、杜邦线、电源等...

3 电气接线
       电气接线目前使用的板子的UNO D1 R32,程序对应的也是UNO D1 R32的引脚,而在Fritzing官方社区中找不到此开发板,暂且不放Fritzing的接线图,放一张实物接线图,后期有时间会将Frizing的接线图放上来,实物接线图如下

UNO D1 R32的接线图

UNO D1 R32的接线图


       其中MFRC522采用的是SPI通信,按照SPI的通信方式接就可以了,至于从机选择SS/SDA以及RST可以进行自定义,按钮上拉输入的方式,可直接在程序中进行设置,需要主要的MFRC522一般采用3.3V供电。

4 Blinker APP 界面设置
       Blinker App 的简单应用及界面设置可以参考论坛的Blinker 快速接入指南 ,最终设置结果如下

门禁系统APP端远程控制界面

门禁系统APP端远程控制界面


5 Arduino 程序
       以下程序尽可能注释清楚了,如果有疑问,大部分应该可以在注释中得到解决
[mw_shl_code=arduino,true]/*
    基于RFID的门禁系统
    程序流程:
    识别卡片ID,判断是否为管理员卡片,
    是管理员ID,则再次读卡,
    如果为管理员卡片,则退出
    如果为已知ID则从存储器删除,为未知ID则写入存储器钟中;
    如果第一次读卡不是管理员ID,
    若为已知ID,则开门,若为为止ID,则不允许通过;

    Author: X-iaowh
    Version: 3.3            for ESP32/ESP8266/Arduino...
    修复了1.0版本中的以下问题:
    [1] 无法将门禁信息写入EEPROM中
    [2] 完善继电器选择模式
    [3] 解决运行过程中,擦除按键无法清拆主卡信息的问题
    [4] 解决除主卡外,录入的第一门禁卡无法识别的问题
    在2.0版本基础上增加远程控制

    Date: 2020-04-02
*/
#define BLINKER_WIFI        //  定义ESP32开发板为WiFi的连接方式,通信方式的选择必须定义在头文件之前

#include<EEPROM.h>          //  在EEPROM中读写射频卡的ID信息,以保证断电数据不丢失
#include<SPI.h>             //  RC522模块采用SPI通信协议
#include<MFRC522.h>         //  读取射频卡的类库
#include<Blinker.h>         //  导入Blinker头文件,以便使用Blinker APP进行远程控制

// 本项目采用继电器对电磁门锁进行控制

/*
    为了观察硬件的运行状态,使用一些LED灯、一个控制电磁门锁的继电器以及一个用来擦除所有数据的按钮
    使用共阳极的LED灯,写入HIGH将关灯
    若使用共阴极的LED灯,写入HIGH将开灯
    如果使用共阴极的LED灯,注释掉 #define COMMOM_ANODE
    使用低电平触发模式的继电器,注释掉 #define HIGH_RELAY后,OPEN将为开门
*/
#define HIGH_RELAY

#ifdef HIGH_RELAY
#define OPEN HIGH
#define CLOSE LOW
#else
#define OPEN LOW
#define CLOSE HIGH
#endif

//#define COMMOM_ANODE

#ifdef COMMOM_ANODE
#define LED_ON LOW
#define LED_OFF HIGH
#else
#define LED_ON HIGH
#define LED_OFF LOW
#endif

#define size 1304                 //  EEPROM的操作空间的字节数

#define redLed 14                //  简单的交互界面采用LED灯作为基础,在此设置LED灯连接的引脚
#define greenLed 27
#define blueLed 16

#define relay 26                 //  设置继电器输出引脚
#define wipeB 25                 //  设置数据清楚按键的引脚

bool programMode = false;       //  初始化编程为False,即默认不进入program Mode

uint8_t successRead;            //  用于保存成功从读卡器读到的数据信息 [uint8_t为无符号的8位数,unsigned char]
byte storedCard[4];             //  存储来自EEPROM的一个ID信息
byte readCard[4];               //  存储从RFID模块扫描到ID信息
byte masterCard[4];             //  存储从EEPROM读取到的管理员的ID信息

//  创建一个RFID_RC522对象实例
#define SS_PIN 5
#define RST_PIN 13
MFRC522 mfrc522(SS_PIN, RST_PIN);

//  WiFi连接的账号与密码设置
char auth[] = "填入Blinker APP中获取到的设备密钥";               //  填入Blinker APP中获取到的设备密钥
char ssid[] = "接入的WiFi热点名称";                             //  接入的WiFi热点名称
char pswd[] = "所接入的WiFi密码";                               //  所接入的WiFi密码

//  新建一个按钮组件对象Button_OpenDoor,用于进行远程开门
BlinkerButton Button_OpenDoor("btn-abc");                      //   对象Button_OpenDoor所对应得按键为APP上编号为 btn-abc 的按键

///////////////////////////////////  初始化  ///////////////////////////////////
void setup()
{
    //  Arduino的引脚模式设置
    pinMode(redLed, OUTPUT);
    pinMode(greenLed, OUTPUT);
    pinMode(blueLed, OUTPUT);
    pinMode(relay,OUTPUT);
    pinMode(wipeB, INPUT_PULLUP);           //  上拉输入设置

    //  设置初始的交互界面,以及注意继电器的在复位或接通Arduino电源的动作
    digitalWrite(relay, CLOSE);              //  确保门锁处于关闭的状态
    digitalWrite(redLed, LED_OFF);          //  保证redLed关闭
    digitalWrite(greenLed, LED_OFF);        //  保证greenLed关闭
    digitalWrite(blueLed, LED_OFF);         //  保证blueLed关闭

    //  通讯协议初始化
    Serial.begin(115200);                     //  用于调试时查看程序的运行状态
    SPI.begin();                              //  MFRC522使用SPI通信协议
    BLINKER_DEBUG.stream(Serial);             //  在串口打印Blinker的调试信息
    Blinker.begin(auth, ssid, pswd);          //  Blinker的初始化
    Button_OpenDoor.attach(OpenDoor);         //  按下远程开门键调用OpenDoor()函数

    //  初始化MFRC522硬件
    mfrc522.PCD_Init();  

    //  设置天线增益至最大,可以提高射频读卡器的感应距离
    //  mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_max);

    Serial.print(F("门禁管理系统 Version: 0.1"));               //  出于调试目的
    ShowReaderDetails();                                       //  用于显示射频读卡器的详细信息,函数实现置于主程序后

    //  擦除数据 ———— 在设置(setup)运行时按下擦除按钮(wipeB),则会擦除EEPROM中的数据
    if (digitalRead(wipeB) == LOW)                            //  按钮通过上拉电阻接地,所以当按钮按下时为低电平
    {
        digitalWrite(redLed, LED_ON);                         //  点亮红灯以提醒用正在准备擦除数据

        Serial.println(F("擦除按钮已按下!"));               
        Serial.println(F("你有10秒钟的时间松开按钮,以取消擦除操作"));
        Serial.println(F("这将删除所有的记录信息,且不可撤销"));

        bool buttonState = monitorWipeButton(10000);          //  给用户留足时间来取消操作,monitorWipeButton()函数实现在主程序之后

        if(buttonState == true && digitalRead(wipeB) == LOW)            //  如果擦除按键持续按下10秒,则擦除EEPROM中的数据
        {
            Serial.println(F("开始擦除EEPROM中的数据"));
            
            EEPROM.begin(size);
            for(uint16_t x = 1280; x < size; x = x +1)          //  擦除EEPROM数据的循环程序,此处将原来的EEPROM.length()直接更改为size
                                                                //  至于从1280开始是因为ESP32模拟出EEPROM中的0-1279的空间被用作Blinker的自动控制
            {
                if(EEPROM.read(x) == 0)
                {
                    //  什么都不做,因为数据已清除,继续读取下一个地址的数据,以节约时间并且减少EEPRM的写入次数
                }
                else
                {
                  EEPROM.write(x, 0);
                }
            }
            EEPROM.end();
            
            //  数据清楚完成的交互指示
            Serial.println(F("EEPROM中的数据已清楚完毕"));
            digitalWrite(redLed, LED_OFF);
            Blinker.delay(200);
            digitalWrite(redLed, LED_ON);
            Blinker.delay(200);
            digitalWrite(redLed, LED_OFF);
            Blinker.delay(200);
            digitalWrite(redLed, LED_ON);
            Blinker.delay(200);
            digitalWrite(redLed, LED_OFF);
        }
        else
        {
            Serial.println(F("数据清除已取消"));            //  10秒内松开清楚按钮wipeB,则取消EEPROM数据清楚操作
            digitalWrite(redLed, LED_OFF);
        }
    }   


    //  检查主卡ID信息是否已经录入,没有录入则让用户选择一张主卡录入信息
    //  同样适用于重新设置一张主卡ID
    //  同时可以保存其他的EEPROM的记录信息,只要不将“143”写入地址1中
    //  在EEPROM的地址1281中写入“143”,则认为已经有主卡的ID信息
    EEPROM.begin(size);
    if (EEPROM.read(1281) != 143)
    {
        Serial.println(F("暂未定义主卡"));
        Serial.println(F("请扫描一张射频卡作为主卡"));
        do
        {
            successRead = getID();          //  如果成功从读卡器中读到信息则置1,否则置0,直到读取到射频卡信息;getID()函数实现位于主程序之后
            digitalWrite(blueLed, LED_ON);
            Blinker.delay(200);
            digitalWrite(blueLed, LED_OFF);
            Blinker.delay(200);
        } while (!successRead);

        //  将读取到的射频卡信息进行存储,并设置为主卡
        EEPROM.begin(size);
        for (uint8_t i = 0; i < 4; i++)         //  读取射频卡的4位数据
        {
            EEPROM.write( 1282+i, readCard );           //  将射频卡的信息写入EEPROM中
        }
        EEPROM.write(1281, 143);
        Serial.println(F("已完成主卡定义"));
        EEPROM.end();
    }

    //  打印出已保存于EEPROM中的主卡的ID信息
    Serial.println(F("----------------------"));
    Serial.println(F("主卡ID信息如下"));
   
    EEPROM.begin(size);
    for (uint8_t i = 0; i < 4; i++)
    {
        masterCard = EEPROM.read(i + 1282);
        Serial.print(masterCard, HEX);           //  以十六进制打印主卡的ID信息
    }

    //  门禁系统准备就绪用户交互展示
    Serial.println("");
    Serial.println(F("----------------------"));
    Serial.println(F("已准备就绪,等待射频卡扫描"));
    cycleLeds();            //  当所有设置准备就绪后,通过此循环闪烁LED提示用户,cycleLeds()函数实现位于主程序之后
}


///////////////////////////////////  主循环  ///////////////////////////////////
void loop()
{   
    Blinker.run();                      //  启动远程连接服务,保证设备在线
    do
    {
        successRead = getID();          //  如果成功从读卡器中读到信息则置1,否则置0,直到读取到射频卡信息;getID()函数实现位于主程序之后

        //  当在设备运行时按下擦除按键10秒,将擦除EEPROM中的主卡信息
        if (digitalRead(wipeB) == LOW)
        {
            //  当按下擦除按键后,正常的操作流程被打断,通过闪烁红色的LED灯来警告用户
            digitalWrite(redLed, LED_ON);
            digitalWrite(greenLed, LED_OFF);
            digitalWrite(blueLed, LED_OFF);

            //  打印串口调试信息
            Serial.println(F("擦除按钮已按下!"));               
            Serial.println(F("主卡信息将在10秒后被擦除!"));

            bool buttonState = monitorWipeButton(10000);          //  给用户留足时间来取消操作,monitorWipeButton()函数实现在主程序之后

            //  擦除按键按下10秒后,擦除EEPROM中的主卡设定信息
            if (buttonState == 1 && digitalRead(wipeB) == LOW)
            {   
                EEPROM.begin(size);
                EEPROM.write(1281, 0);
                EEPROM.end();
                Serial.println(F("主卡信息已从设备中擦除"));
                Serial.println(F("请按复位键(reset)以重新设置所需要的主卡"));
                while(1);
            }
            Serial.println(F("擦除主卡的操作已被取消"));
        }

        //  当擦除按键没有按下时
        if (programMode)            //  当前为添加新卡或删除旧卡的programMode时
        {
            cycleLeds();            //  programMode时,循环闪烁红绿蓝灯以等待读取一张新的射频卡;cycleLeds()函数实现位于主程序之后
        }
        else
        {
            normalModeOn();         // 一般读卡模式,蓝灯常亮,其他灯全灭;normalModeOn()函数实现位于主程序之后
        }

    } while (!successRead);         // 程序将持续读取射频卡,直到成功读取到射频卡
   
    //  programMode下添加新卡或删除旧卡
    if (programMode)
    {
        //  编程模式下,首先检测是否为主卡再次扫描,如果是,则退出编辑模式
        if ( isMaster(readCard) )           //  isMaster()函数实现位于主程序之后
        {
            Serial.println(F("扫描到的为主卡"));
            Serial.println(F("退出编辑模式(Program Mode)"));
            Serial.println(F("----------------------"));
            programMode = false;
            return;
        }
        
        //  若不是主卡,则进行添加或删除卡片信息操作
        else
        {
            if ( findID(readCard) )         //  如果检测到的是已知的射频卡,则删除;findID()函数实现位于主程序之后
            {
                Serial.println(F("这是一张已知的门禁卡,删除中..."));
                deleteID(readCard);         //  deleteID()函数实现位于主程序之后
                Serial.println(F("----------------------"));
                Serial.println(F("可继续添加或移除门禁卡,或使用主卡退出编辑模式"));
            }
            else                            //  若扫描到位置卡片,则添加卡片信息
            {
                Serial.println(F("这是一张未知的门禁卡,添加中..."));
                writeID(readCard);         //  writeID()函数实现位于主程序之后
                Serial.println(F("----------------------"));
                Serial.println(F("可继续添加或移除门禁卡,或使用主卡退出编辑模式"));
            }
        }
    }
   
    //  使用主卡进入编辑模式(programMode),或者对门禁卡判断,进行开锁动作
    else
    {
        //  扫描到的门禁卡为主卡,则进入编辑模式
        if ( isMaster(readCard) )           
        {   EEPROM.begin(size);
            programMode = true;
            Serial.println(F("管理员你好,将进入编辑模式"));
            uint8_t count = EEPROM.read(1280);         //  读取EEPROM用于存储用户数量的地址0中的信息
            Serial.print(F("当前系统中有 "));     
            Serial.print(count);
            Serial.print(F(" 条门禁卡信息"));
            Serial.println("");
            Serial.println(F("你可以扫描一张门禁卡以添加到系统中,或从系统中移除"));
            Serial.println(F("再次扫描主卡可退出编辑模式"));
            Serial.println(F("----------------------"));
        }
        
        //  若扫描到的不是主卡,则系统进行开锁判断
        else
        {
            if ( findID(readCard) )         // 如果是系统已知的门禁卡,则开锁
            {   
                Serial.println(F("门锁已打开,欢迎"));
                granted(3000);              //  设置锁打开的时间3秒,granted()函数实现位于主函数之后
            }
            else            //  扫描到的是系统未录入的门禁卡
            {
                Serial.println(F("此门禁卡未录入!!!"));
                denied();                   //  denied()函数现实位于主函数之后
            }
        }
    }
}


///////////////////////////////////////    函 数 现 实 部 分     ///////////////////////////////////////



//////////////////////////////////////               门禁判断              //////////////////////////////////////


//////////////////////////////////////   门禁允许通过函数 granted() 实现   //////////////////////////////////////
void granted( uint16_t setDelay )
{   
    //  设置指示灯,保持绿灯亮
    digitalWrite(blueLed, LED_OFF);
    digitalWrite(redLed, LED_OFF);
    digitalWrite(greenLed, LED_ON);

    digitalWrite(relay, OPEN);           //  继电器触发,以此开门
    Blinker.delay(setDelay);                    //  设置继电器通电时间,即锁舌回缩时间
    digitalWrite(relay, CLOSE);          //  继电器复位,锁舌复位,关门

    Blinker.delay(1000);                        //  保持绿灯亮1秒钟时间
}

//////////////////////////////////////   门禁拒绝通过函数 denied() 实现   //////////////////////////////////////
void denied()
{
    digitalWrite(redLed, LED_ON);
    digitalWrite(greenLed, LED_OFF);
    digitalWrite(blueLed, LED_OFF);
    Blinker.delay(1000);
}


//////////////////////////////////////            RFID卡信息获取           //////////////////////////////////////


//////////////////////////////////////     射频卡ID读取函数getID()实现     //////////////////////////////////////
uint8_t getID()
{
    //  读取射频卡准备
    if( ! mfrc522.PICC_IsNewCardPresent() )         //  如果放置一张新的射频卡到读卡器则继续getID()函数
    {
        return 0;
    }
    if( ! mfrc522.PICC_ReadCardSerial() )           //  当一张射频卡放置于读卡器上后,读取串号并继续getID()函数

    //  默认射频卡信息为4位
    //  读取射频卡的ID串号
    Serial.print(F("门禁卡ID信息:"));
    for (uint8_t i = 0; i < 4; i++)
    {
        readCard = mfrc522.uid.uidByte;
        Serial.print(readCard, HEX);
    }
    Serial.println("");
    mfrc522.PICC_HaltA();                           //  停止读取
   
    return 1;
}

/////////////////////////////////////   读卡器详情展示函数ShowReaderDetails()实现   //////////////////////////////////////
void ShowReaderDetails()
{
    //  获取RC522软件版本
    byte v = mfrc522.PCD_ReadRegister(mfrc522.VersionReg);
    Serial.print(F("RC522的软件版本为0x"));
    Serial.print(v, HEX);
    if (v == 0x91)
    {
        Serial.println(F(" = v1.0"));
    }
    else if (v == 0x92)
    {
        Serial.println(F(" = v2.0"));
    }
    else
    {
        Serial.println(F(",此版本未知"));
    }

    //  如果v的返回值是0x00或0xFF,说明通讯失败
    if (v == 0x00 || v == 0xFF)
    {
        Serial.println(F("警告:通信失败,请检查RC522是否成功连接?"));
        Serial.println(F("系统停止:请检查连接..."));

        //  系统暂停时的交互界面,亮红灯
        digitalWrite(redLed, LED_ON);
        digitalWrite(greenLed, LED_OFF);
        digitalWrite(blueLed, LED_OFF);
        while(true);            //  系统暂停
    }
}


//////////////////////////////////////               灯光提示函数               //////////////////////////////////////


//////////////////////////////////////     编程模式循环灯函数cycleLeds()实现     //////////////////////////////////////
void cycleLeds()
{   
    Blinker.run();                          // 在这个循环加入,为了确保设备在编程也不中断,删除的话编程模时会导致设备中断,有兴趣可以试试
    digitalWrite(redLed, LED_OFF);
    digitalWrite(greenLed, LED_ON);
    digitalWrite(blueLed, LED_OFF);
    Blinker.delay(200);
    digitalWrite(redLed, LED_OFF);
    digitalWrite(greenLed, LED_OFF);
    digitalWrite(blueLed, LED_ON);
    Blinker.delay(200);
    digitalWrite(redLed, LED_ON);
    digitalWrite(greenLed, LED_OFF);
    digitalWrite(blueLed, LED_OFF);
    Blinker.delay(200);
}

//////////////////////////////////////   正常模式提示灯函数normalModeOn()实现    //////////////////////////////////////
void normalModeOn()
{   
    Blinker.run();                           // 在这个循环加入,为了确保设备不中断,删除的会导致设备中断,有兴趣可以试试
    digitalWrite(redLed, LED_OFF);
    digitalWrite(greenLed, LED_OFF);
    digitalWrite(blueLed, LED_ON);
    digitalWrite(relay, CLOSE);              // 继电器复位,以保证门锁闭合
}


//////////////////////////////////////             EEPROM操作部分              //////////////////////////////////////


//////////////////////////////////////    读EEPROM中ID函数readID()函数实现     //////////////////////////////////////
void readID(uint8_t number)
{   
    EEPROM.begin(size);
    uint8_t start = ( number * 4 ) + 1282;             //  找到ID的起始地址
    for (uint8_t i = 0; i < 4; i++)
    {
        storedCard = EEPROM.read(start + i);
    }
}

//////////////////////////////////////    添加新ID到EEPROM的writeID()函数实现     //////////////////////////////////////
void writeID( byte a[] )
{   
    if ( ! findID(a) )                          //  写入ID前先判断EEPROM中是否有保存此门禁卡ID,findID()的函数实现在后面
    {   
        EEPROM.begin(size);
        uint8_t num = EEPROM.read(1280);          //  EEPROM的地址0用于存放门禁卡ID的数量
        uint8_t start = ( num * 4 ) + 1286;       //  找到用于写入新卡的位置,+6而不是+4,因为0存放数量,1存放主卡标记,往后4个地址存放主卡ID
        
        //  增加系统中的一个门禁卡数量
        num++;                                 
        EEPROM.write(1280, num);

        //  在EEPROM中写入门禁卡的ID信息
        for (uint8_t i = 0; i < 4; i++)
        {
            EEPROM.write(start + i, a);      
        }
        EEPROM.end();
        successWrite();                         //  successWrite()的函数实现在后面
        Serial.println(F("已成功在门禁系统中添加新的门禁卡ID"));
    }
    else
    {
        failedWrite();
        Serial.println(F("失败:此门禁卡ID错误或EEPROM损坏"));
    }
}

//////////////////////////////////////    从EEPROM中移除已有ID的deleteID()函数实现     //////////////////////////////////////
void deleteID( byte a[] )
{   
    if ( ! findID(a) )
    {
        failedWrite();
        Serial.println(F("失败:此门禁卡ID错误或EEPROM损坏"));
    }
    else
    {
        EEPROM.begin(size);
        uint8_t num = EEPROM.read(1280);               //  EEPROM的地址0用于存放门禁卡ID的数量
        uint8_t slot;                               //  找出卡的槽号
        uint8_t start;      //  = (num * 4) + 1286;    //  找到下一个卡槽的开始地址
        uint8_t looping;                            //  移位时,所需要的循环重复的次数
        uint8_t j;                                  //  覆盖地址时用到            
        uint8_t count = EEPROM.read(1280);
        slot = findIDSLOT(a);                       //  找到所要删除的门禁卡的槽号,findIDSLOT()函数实现在后面
        start = ( slot * 4 ) + 1282;
        looping = (( num - slot) * 4);              //  移位循环的次数
        
        //  系统中已知门禁卡数量减1
        num--;                                      
        EEPROM.write(1280, num);

        //  将所要移除的门禁卡ID后的ID往前移动4位
        for (uint8_t j = 0; j < looping; j++)
        {
            EEPROM.write(start + j, EEPROM.read(start + j + 4));
        }
        //  将位于最后的门禁卡ID的4位信息清0
        for (uint8_t i = 0; i < 4; i++)
        {
            EEPROM.write(start + j + i, 0);
        }
        successDelete();                            //  successDelete()函数实现位于后面
        Serial.println(F("已成功将此门禁卡ID从系统中移除"));
        EEPROM.end();
    }
}

//////////////////////////////////////    与EEPROM中ID进行比较的字节比较checkTwo()函数实现     //////////////////////////////////////
bool checkTwo(byte a[], byte b[])
{
    for (uint8_t i = 0; i < 4; i++)
    {   
        //  如果数组中有一位不相同,则返回false
        if (a != b)
        {
            return false;
        }
    }
    return true;
}

//////////////////////////////////////            寻找指定门禁卡卡槽位置的findIDSLOT()函数实现             //////////////////////////////////////
uint8_t findIDSLOT( byte find[] )
{
    EEPROM.begin(size);
    uint8_t count = EEPROM.read(1280);             //  读取系统中的门禁卡数量
    for (uint8_t i = 1; i <= count; i++)
    {
        readID(i);                              //  从系统中读取指定序号的门禁ID,并保存在storedCard[4]数组中

        //  检查从EEPROM中读取到并保存在storedCard数组中的ID信息是否与find数组中的ID信息相同
        if ( checkTwo(find, storedCard) )
        {
            return i;                           // 返回卡槽位置
        }
    }
}

//////////////////////////////////////          在EEPROM中寻找指定门禁ID的findID()函数实现           //////////////////////////////////////
bool findID( byte find[] )
{   
    EEPROM.begin(size);
    uint8_t count = EEPROM.read(1280);             //  读取系统中的门禁卡数量

    //  判断EEPROM中是否有此门禁卡的ID信息
    for (uint8_t i = 0; i <= count; i++)        //  原先此处位 uint8_t i = 1; i < count; i++ ,这样导致第一张录入的门禁卡信息无法识别
    {
        readID(i);
        if (checkTwo(find, storedCard))
        {
            return true;
        }
    }
    return false;
}

//////////////////////////////////////      成功将门禁卡信息写入EEPROM的successWrite()函数实现       //////////////////////////////////////
void successWrite()
{   
    digitalWrite(redLed, LED_OFF);  
    digitalWrite(greenLed, LED_OFF);  
    digitalWrite(blueLed, LED_OFF);   
    Blinker.delay(200);

    digitalWrite(greenLed, LED_ON);   
    Blinker.delay(200);
    digitalWrite(greenLed, LED_OFF);  
    Blinker.delay(200);

    digitalWrite(greenLed, LED_ON);   
    Blinker.delay(200);
    digitalWrite(greenLed, LED_OFF);  
    Blinker.delay(200);
    digitalWrite(greenLed, LED_ON);   
    Blinker.delay(200);
}

//////////////////////////////////////      门禁卡信息写入EEPROM失败的failedWrite()函数实现        //////////////////////////////////////
void failedWrite()
{
    digitalWrite(blueLed, LED_OFF);
    digitalWrite(redLed, LED_OFF);
    digitalWrite(greenLed, LED_OFF);
    Blinker.delay(200);

    digitalWrite(redLed, LED_ON);
    Blinker.delay(200);
    digitalWrite(redLed, LED_OFF);
    Blinker.delay(200);

    digitalWrite(redLed, LED_ON);
    Blinker.delay(200);
    digitalWrite(redLed, LED_OFF);
    Blinker.delay(200);
    digitalWrite(redLed, LED_ON);
}

//////////////////////////////////////      成功将门禁卡信息移除EEPROM的successDelete()函数实现       //////////////////////////////////////
void successDelete()
{
    digitalWrite(blueLed, LED_OFF);
    digitalWrite(redLed, LED_OFF);
    digitalWrite(greenLed, LED_OFF);
    Blinker.delay(200);
    digitalWrite(blueLed, LED_ON);
    Blinker.delay(200);
    digitalWrite(blueLed, LED_OFF);
    Blinker.delay(200);
    digitalWrite(blueLed, LED_ON);
    Blinker.delay(200);
    digitalWrite(blueLed, LED_OFF);
    Blinker.delay(200);
    digitalWrite(blueLed, LED_ON);
    Blinker.delay(200);
}


/////////////////////////////////////////////////////////    其他   //////////////////////////////////////////////////////////////
///////////////////////////////////              检查是否位主卡的isMaster()函数实现             ///////////////////////////////////
bool isMaster( byte test[] )
{
    return checkTwo(masterCard, test);
}

///////////////////////////////////          擦除按钮计时的monitorWipeButton()函数实现         ///////////////////////////////////
bool monitorWipeButton(uint32_t interval)
{
    uint32_t now = (uint32_t) millis();
    while((uint32_t) millis() - now < interval)
    {
        //  每半秒检查一次按键状态
        if (((uint32_t) millis() % 500 ) == 0)
        {
            if (digitalRead(wipeB) != LOW)
            {
                return false;
            }
        }
    }
    return true;
}

///////////////////////////////////              远程开门的OpenDoor()函数实现             ///////////////////////////////////
void OpenDoor(const String & state)
{
    Blinker.vibrate();                                      //  手机的震感反馈函数
    BLINKER_LOG("get Button state:", state);                //  在串口显示按键状态
    digitalWrite(relay, OPEN);
    digitalWrite(greenLed, LED_ON);
    Blinker.delay(3000);                                    //  在远程控制下,要把所有的delay()函数更改为Blinker.delay()
    digitalWrite(relay, CLOSE);
    digitalWrite(greenLed, LED_OFF);                        //  非编程模式下,远程开门亮的是绿灯和蓝灯,刷卡开门只亮绿灯
}[/mw_shl_code]

6 演示
       这是在B站录的视频(录得挺糙的) https://www.bilibili.com/video/BV1g54y1d7DW

7 注意事项
       在实际开发过程遇到很多问题,也查阅挺多资料,后期有时间会将查阅的资料以及注意事项在此更新

 楼主| 发表于 2020-4-15 20:23 | 显示全部楼层
接线图长这样,未接继电器,NodeMcu32s接继电器需要注意电平能否驱动,可以考虑加三极管或者选择带有5V电源输出的板子,但是更详细的接线已在某个回答中说明

接线图.png


发表于 2020-4-8 16:59 | 显示全部楼层
接线能详细说明一下吗?谢谢
 楼主| 发表于 2020-4-11 22:51 | 显示全部楼层
13675003417 发表于 2020-4-8 16:59
接线能详细说明一下吗?谢谢

我明天看下,用Fritzing画一个吧
发表于 2020-4-12 02:21 | 显示全部楼层
老哥这个好强
 楼主| 发表于 2020-4-12 22:28 | 显示全部楼层
13675003417 发表于 2020-4-8 16:59
接线能详细说明一下吗?谢谢

今天挤不出时间来,我先口头和你讲下吧
这是我用的ESP32开发板的引脚图,如下
ESP32引脚图.png
然后,射频读卡器一共有8个引脚,分别为SDA、SCK、MOSI、MISO、RQ、GND、RST、3.3V,
[1] 3.3V和GND分别为读卡器的电源和地
[2] SDA(SS)是从机选择,可以自行设定,我这里设置为第5引脚
[3] RST为复位引脚,也可自行设定,我程序中设置为第13引脚
[4] 读卡器的SCK、MOSI、MISO这是SPI通信的引脚,从我开发板的引脚图可以看到,应分别对应开发板的18、23、19引脚
上面这是读卡器的连接线,其他的就是LED灯、继电器和按钮的接线,这应该没有什么难度,你有问题再留言,我最近尽量抽时间附上Fritzing的接线图



 楼主| 发表于 2020-4-12 22:29 | 显示全部楼层

谢谢呀,其实实现不难的,主要是Blinker这个平台节约了很多中间的实现过程
发表于 2020-4-16 11:19 | 显示全部楼层
不错啊。但是你只需要加入几行代码就可以实现小爱同学控制了,不是更酷吗
 楼主| 发表于 2020-4-16 11:36 | 显示全部楼层
宇之枫海云 发表于 2020-4-16 11:19
不错啊。但是你只需要加入几行代码就可以实现小爱同学控制了,不是更酷吗 ...

有想过,但是手上没有小爱,另一方面还没学习参考过,点灯开发文档好像有,请赐教哈哈!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-11-28 14:40 , Processed in 0.128766 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表