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

X-iaowh 发表于 2020-4-3 11:04

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

本帖最后由 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文件夹中



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

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



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



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

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

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

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



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

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



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

    Author: X-iaowh
    Version: 3.3            for ESP32/ESP8266/Arduino...
    修复了1.0版本中的以下问题:
    无法将门禁信息写入EEPROM中
    完善继电器选择模式
    解决运行过程中,擦除按键无法清拆主卡信息的问题
    解决除主卡外,录入的第一门禁卡无法识别的问题
    在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;            //用于保存成功从读卡器读到的数据信息
byte storedCard;             //存储来自EEPROM的一个ID信息
byte readCard;               //存储从RFID模块扫描到ID信息
byte masterCard;             //存储从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数组中

      //检查从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);                        //非编程模式下,远程开门亮的是绿灯和蓝灯,刷卡开门只亮绿灯
}
6 演示
       这是在B站录的视频(录得挺糙的) https://www.bilibili.com/video/BV1g54y1d7DW

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

X-iaowh 发表于 2020-4-15 20:23

接线图长这样,未接继电器,NodeMcu32s接继电器需要注意电平能否驱动,可以考虑加三极管或者选择带有5V电源输出的板子,但是更详细的接线已在某个回答中说明




13675003417 发表于 2020-4-8 16:59

接线能详细说明一下吗?谢谢

X-iaowh 发表于 2020-4-11 22:51

13675003417 发表于 2020-4-8 16:59
接线能详细说明一下吗?谢谢

我明天看下,用Fritzing画一个吧

wrz2000 发表于 2020-4-12 02:21

老哥这个好强

X-iaowh 发表于 2020-4-12 22:28

13675003417 发表于 2020-4-8 16:59
接线能详细说明一下吗?谢谢

今天挤不出时间来,我先口头和你讲下吧
这是我用的ESP32开发板的引脚图,如下

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



X-iaowh 发表于 2020-4-12 22:29

wrz2000 发表于 2020-4-12 02:21
老哥这个好强

谢谢呀,其实实现不难的,主要是Blinker这个平台节约了很多中间的实现过程

13675003417 发表于 2020-4-13 14:05

谢谢:victory:

宇之枫海云 发表于 2020-4-16 11:19

不错啊。但是你只需要加入几行代码就可以实现小爱同学控制了,不是更酷吗:lol:lol:lol

X-iaowh 发表于 2020-4-16 11:36

宇之枫海云 发表于 2020-4-16 11:19
不错啊。但是你只需要加入几行代码就可以实现小爱同学控制了,不是更酷吗 ...

有想过,但是手上没有小爱,另一方面还没学习参考过,点灯开发文档好像有,请赐教哈哈!
页: [1] 2
查看完整版本: 基于Blinker与ESP32的RFID门禁系统 就地、远程双控制