Skip to content

- Programming for Final Project, 1-2

This page is the process of Programming for my final Project.

Notes:
When installing "Board Manager esp32" in the Arduino IDE, selecting version 2.0.8 caused an RFID-related compile error, so Please install "Board Manager esp32" version 2.0.7 or lower. (As of 2-May-2023)

1-2: RFID2 + SSR + LCD

Next, I generated an additional program in ChatGPT (GPT-4) that "displays text on the LCD depending on the situation, retrieves the exact time from the NTP server via a Wifi connection, and counts the time while the registrant's card is being authenticated.

alt text

  • What I used
    • Items used in 1-1, and add LCD Module (AE-AQM1602A)
    • Library:
      • Wire (for I2C communication)
      • RFID_MFRC522v2 (for RFID reader)
      • Wifi (for wireless connectivity)
      • time (time-related processing)

Main instructions for ChatGPT (GPT-4)

  • modify the original code generated in 1-1 by adding the LCD used for I2C and displaying the UID read.
  • The original code must be adhered to and additional modifications must be made without changing the structure as much as possible.
  • The board used is XIAO ESP32C3.
  • Do not use the "LiquidCrystal_I2C" library from LCD, as it does not work with ESP32.
  • Connect via Wifi Network and get the exact time from the NTP server.
  • Count the elapsed time after the card is authenticated and display the elapsed time when the card is removed.

The completed code is as follows.

Programming code

#include <Wire.h>
#include <MFRC522v2.h>
#include <MFRC522DriverI2C.h>
#include <MFRC522Debug.h>
#include <WiFi.h>
#include <time.h>

const uint8_t RFID_Address = 0x28;
const uint8_t LCD_ADDRESS = 0x3E;

MFRC522DriverI2C driver{RFID_Address, Wire};
MFRC522 mfrc522{driver};

const char* registeredUids[] = {
  "5E4ABC3E",
  "DEE6B43E"
};

const char* registeredUserNames[] = {
  "HIROE TAKEDA",
  "KAZUNARI TAKEDA"
};

const uint8_t ssr = 8;

enum CardStatus {
  NO_CARD,
  REGISTERED_CARD,
  UNREGISTERED_CARD
};

CardStatus currentStatus = NO_CARD;

char lastUid[9] = "";
char lastUserName[17] = "";
bool countTime = false;
time_t cardAuthStartTime;

bool uidMatches(MFRC522::Uid *uid1, const char *uid2) {
  if (uid1->size != 4) {
    return false;
  }

  for (uint8_t i = 0; i < uid1->size; i++) {
    char buf[3] = {uid2[i * 2], uid2[i * 2 + 1], '\0'};
    uint8_t uid2Byte = strtol(buf, nullptr, 16);
    if (uid1->uidByte[i] != uid2Byte) {
      return false;
    }
  }
  return true;
}

const char* formatUid(MFRC522::Uid *uid) {
  static char buf[9];
  for (uint8_t i = 0; i < uid->size; i++) {
    snprintf(&buf[i * 2], 3, "%02X", uid->uidByte[i]);
  }
  return buf;
}

void printCardDetails(MFRC522::Uid *uid) {
  Serial.print("Card UID: ");
  for (uint8_t i = 0; i < uid->size; i++) {
    if (uid->uidByte[i] < 0x10) {
      Serial.print("0");
    }
    Serial.print(uid->uidByte[i], HEX);
  }
  Serial.println();
}

void setup() {
  Serial.begin(115200);
  while (!Serial);

  Wire.begin();

  mfrc522.PCD_Init();
  MFRC522Debug::PCD_DumpVersionToSerial(mfrc522, Serial);
  Serial.println(F("Scan PICC to see UID, SAK, type, and data blocks..."));

  pinMode(ssr, OUTPUT);

  init_LCD();

  const char* ssid = "your wifi id"; // Input your wifi id
  const char* password = "your wifi password"; // Inpur your wifi password
  connectToWifi(ssid, password);
  obtainTime();
}

void updateLCD(const char* line1, const char* line2) {
  writeCommand(0x01); // Clear Display

  // Write line1
  for (int i = 0; line1[i] != '\0'; i++) {
    writeCommand(0x80 + i);
    writeData(line1[i]);
  }

  // Write line2
  for (int i = 0; line2[i] != '\0'; i++) {
    writeCommand(0x40 + 0x80 + i);
    writeData(line2[i]);
  }
}

void loop() {
  bool cardPresent = mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial();
  CardStatus newStatus;

  if (cardPresent) {
    printCardDetails(&(mfrc522.uid));

    bool isRegistered = false;

    for (uint8_t i = 0; i < sizeof(registeredUids) / sizeof(registeredUids[0]); i++) {
      if (uidMatches(&(mfrc522.uid), registeredUids[i])) {
        isRegistered = true;
        strncpy(lastUid, registeredUids[i], sizeof(lastUid));
        strncpy(lastUserName, registeredUserNames[i], sizeof(lastUserName));
        break;
      }
    }

    if (isRegistered) {
      digitalWrite(ssr, HIGH);
      Serial.println("Access granted.");
      newStatus = REGISTERED_CARD;

      while (mfrc522.PICC_IsNewCardPresent()) {
        delay(100);
      }
    } else {
      digitalWrite(ssr, LOW);
      Serial.println("Access denied.");
      newStatus = UNREGISTERED_CARD;
    }
  } else {
    digitalWrite(ssr, LOW);
    newStatus = NO_CARD;
  }

  char welcomeMessage[17] = "Welcome FabLab"; // Moved outside the switch statement

  if (newStatus != currentStatus) {
    currentStatus = newStatus;

    switch (currentStatus) {
      case NO_CARD:
        if (countTime) {
          time_t elapsedTime = time(nullptr) - cardAuthStartTime;
          unsigned long hours = elapsedTime / 3600;
          unsigned long minutes = (elapsedTime % 3600) / 60;
          unsigned long seconds = elapsedTime % 60;
          char elapsedTimeStr[17];
          snprintf(elapsedTimeStr, sizeof(elapsedTimeStr), "%luh %lum %lus", hours, minutes, seconds);
          updateLCD(elapsedTimeStr, lastUserName);
          countTime = false;
        } else {
          updateLCD("Put your", "members card");
          delay(2000); // Display "ERROR" for 2 seconds
        }
        break;
      case REGISTERED_CARD:
        updateLCD(welcomeMessage, lastUserName);
        cardAuthStartTime = time(nullptr); // Start counting the elapsed time
        countTime = true;
        break;
      case UNREGISTERED_CARD:
        updateLCD("Card is not", "authorized");
        delay(2000); // Display "Card is not authorized" for 2 seconds
        break;
    }
  }
}

void updateLCD(const char* message) {
  writeCommand(0x01); // Clear Display
  for (int i = 0; message[i] != '\0'; i++) {
    writeCommand(0x80 + i);
    writeData(message[i]);
  }
}

void writeData(byte t_data) {
  Wire.beginTransmission(LCD_ADDRESS);
  Wire.write(0x40);
  Wire.write(t_data);
  Wire.endTransmission();
  delay(1);
}

void writeCommand(byte t_command) {
  Wire.beginTransmission(LCD_ADDRESS);
  Wire.write(0x00);
  Wire.write(t_command);
  Wire.endTransmission();
  delay(10);
}

void init_LCD() {
  delay(100);
  writeCommand(0x38); // FUNCTION SET
  delay(20);
  writeCommand(0x39); // IS=1
  delay(20);
  writeCommand(0x14); // INT OSC FREQUENCY
  delay(20);
  writeCommand(0x7A); // CONTRAST SET 0,1,2,3
  delay(20);
  writeCommand(0x54); // CONTRAST SET 4,5
  delay(20);
  writeCommand(0x6C); // F0LLOWER CONTROL
  delay(20);
  writeCommand(0x38); // IS=0
  delay(20);
  writeCommand(0x0C); // Display ON
  delay(20);
  writeCommand(0x01); // Clear Display
  delay(20);
  writeCommand(0x06); // Entry Mode Set
  delay(20);
}

void connectToWifi(const char* ssid, const char* password) {
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("Connected to WiFi.");
}

void obtainTime() {
  configTime(0, 0, "pool.ntp.org");
  Serial.print("Obtaining time from NTP server...");
  while (time(nullptr) < 1619999999) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" Time obtained!");
}

The relevant codes for LCD

The relevant code to control LCD are described below:

  • LCD_ADDRESS definition:
    • This code defines the I2C address of the LCD. The address specified here is used to communicate from the microcontroller to the LCD.
const uint8_t LCD_ADDRESS = 0x3E; // Define the I2C address for the LCD
  • updateLCD function (2 line version):
    • This function is used to display text on the first and second lines of the LCD. Passing a string to be displayed as an argument causes the string to be displayed on each line. The writeCommand and writeData functions used here are described below and are responsible for sending commands to the LCD and writing data, respectively.
void updateLCD(const char* line1, const char* line2) {
  writeCommand(0x01); // Clear Display

  // Write line1
  for (int i = 0; line1[i] != '\0'; i++) {
    writeCommand(0x80 + i); // Set cursor to the appropriate location
    writeData(line1[i]); // Send data (character) to be displayed
  }

  // Write line2
  for (int i = 0; line2[i] != '\0'; i++) {
    writeCommand(0x40 + 0x80 + i); // Set cursor to the appropriate location on second line
    writeData(line2[i]); // Send data (character) to be displayed
  }
}
  • update LCD function (1 line version):
    • This function is used to display text on the first line of the LCD. Passing a string to be displayed as an argument causes the string to be displayed on the first line of the LCD.
void updateLCD(const char* message) {
  writeCommand(0x01); // Clear Display
  for (int i = 0; message[i] != '\0'; i++) {
    writeCommand(0x80 + i); // Set cursor to the appropriate location
    writeData(message[i]); // Send data (character)
  } 
}    
  • writeData function:
    • This function is used to write data to the LCD. This function uses beginTransmission in the Wire library to initiate I2C communication, then calls write twice to send data. The first write indicates to the LCD that what is to be sent next is data, and the next write is the actual data to be sent. Finally, endTransmission is called to be end I2C communication.
void writeData(byte t_data) {
  Wire.beginTransmission(LCD_ADDRESS); // Begin I2C transmission to the LCD
  Wire.write(0x40); // Specify that what follows is data
  Wire.write(t_data); // Write the actual data (character) to the LCD
  Wire.endTransmission(); // End I2C transmission
  delay(1); 
}
  • writeCommand function:
    • This function is used to send a command to the LCD.
void writeCommand(byte t_command) {
  Wire.beginTransmission(LCD_ADDRESS); // Begin I2C transmission to the LCD
  Wire.write(0x00); // Specify that what follows is a command
  Wire.write(t_command); // Write the actual command to the LCD
  Wire.endTransmission(); // End I2C transmission
  delay(10); 
}
  • init_LCD function:
    • This function is used to initialize the LCD and sends a series of commands necessary for the LCD to operate properly.
    • This sequence of processing allows the LCD to begin operating in a given state and to properly display the text that follows. An appropriate delay is also placed after each command, which allows time for the LCD to process each command.
void init_LCD() {
  delay(100); // Initial delay to allow LCD to power up
  writeCommand(0x38); // Set LCD function (data length, number of lines, character font)
  delay(20); // Allow command to be processed
  writeCommand(0x39); // Extend function set (bias selection, instruction table)
  delay(20);
  writeCommand(0x14); // INT OSC FREQUENCY
  delay(20);
  writeCommand(0x7A); // Contrast set (low byte)
  delay(20);
  writeCommand(0x54); // Contrast set (high byte)
  delay(20);
  writeCommand(0x6C); // Follower control (amplifier ratio)
  delay(20);
  writeCommand(0x38); // Function set (switch back to basic instruction set)
  delay(20);
  writeCommand(0x0C); // Display ON
  delay(20);
  writeCommand(0x01); // Clear Display
  delay(20);
  writeCommand(0x06); // Entry Mode Set
  delay(20);
}

Datasheet of LCD Module (AE-AQM1602A)

alt text

I2C address of LCD, "0x3E", is refer in Datasheet.
alt text

Upload

  • Wired device, then I open Arduino IDE, choose "board ESP32C3". (Tools > Board > ESP32 Arduino > XIAO_ESP32C3) .Port is "/dev/cu.usbmodem1101" in this time, Then Upload the code.

I verified that the device works as programmed!!