091
[C/C++] ESP32와 MFRC522 연결해서 카드로 LED ON/OFF 하기 본문
01. SPI(Serial Peripheral Interface) & RFID(Radio Frequency Identification)
- SPI란 마이크로컨트롤러와 센서, 디스플레이, RFID 사이에서 빠르고 간단하게 데이터를 주고 받기 위한 통신 규격(프로토콜)으로, 선이 많이 필요한 특징을 가지고 있습니다. 클럭 기반 동기식이며, 마스터가 모든 것을 주도합니다.
• 마스터-슬레이브 : 마스터란 통신을 제어하는 주인 장치를 의미하며, ESP32나 아두이노가 이 역할을 합니다. 클럭을 생성하고 어떤 슬레이브와 통신할지 선택합니다. 슬레이브란 명령을 기다리고 응답하는 장치로 센서, 디스플레이, RFID 등이 이 역할을 합니다.
• SPI의 기본 핀은 MOSI(Master Out Slave In, 마스터가 보내는 데이터), MISO(Master In Slave Out, 슬레이브가 보내는 데이터), SCLK(Serial Clock), SS(Slave Select)이 있으며, 슬레이브 핀은 슬레이브 마다 각각 따로 설정됩니다.
• SPI의 흐름 : 마스터가 SS를 LOW(LOW일 때 활성화)로 해서 슬레이브 선택 -> 클럭 발생 -> MOSI로 데이터 보냄 -> MISO로 응답함(필요 시) -> SS를 HIGH로 해서 선택 해제
- RFID란 무선 주파수(RF)를 이용해서 태그(Tag) 정보를 읽고 쓰는 기술입니다. 태그와 이를 읽는 리더기로 구성되어 있습니다. 그 중 MFRC522를 사용하였습니다.
• MFRC522란 NXP에서 만든 13.56MHz RFID 리더 칩으로, RFID 카드나 키포드와 함께 사용됩니다. RFID 카드나 키포드의 경우 UID가 내장되어 있어 리더기를 통해 이를 읽을 수 있습니다. 4~7 바이트 길이의 고유값으로 사람 식별이나 출입 제어를 할 때 사용됩니다.
02. ESP32와 MFRC522 연결해서 카드로 LED 불 ON/OFF 하기
- 하드웨어 :
MFRC522 RFID Reader | ESP32 |
SDA(Slave Selct) | GPIO 5 |
SCK(Slave Clock) | GPIO 18 |
MOSI | GPIO 23 |
MISO | GPIO 19 |
IRQ | Don't Connect |
GND | GND |
RST(Reset Pin) | GPIO 0 |
VCC(only 3.3V Input) | VCC(3.3V) |
- 소프트웨어 : 가독성을 위해 코드를 잘랐지만 한 코드로 실행해줘야합니다.
#include <SPI.h>
#include <MFRC522.h>
#define LED_PIN 4
#define SS_PIN 5
#define RST_PIN 0
int value = 0;
byte knownCardUID[] = {0xA3, 0x0E, 0xF9, 0x12};
byte knownKeyfobUID[] = {0x53, 0xB5, 0xD1, 0x1C};
//const int ipaddress[4] = {103, 97, 67, 25};
byte nuidPICC[4] = {0, 0, 0, 0}; //읽은 ID를 저장
MFRC522::MIFARE_Key key;
MFRC522 rfid = MFRC522(SS_PIN, RST_PIN);//mfrc 객체 생성
void setup() {
Serial.begin(115200);
Serial.println(F("Initialize System")); //F(): flash 메모리
pinMode(LED_PIN, OUTPUT);
SPI.begin();
rfid.PCD_Init();
Serial.print(F("Reader :"));
rfid.PCD_DumpVersionToSerial();
}
- #include ... : SPI(프로토콜)와 MFRC522(리더 라이브러리)를 사용하기 위해 각각 라이브러리를 임포트해줍니다
- #define ... : 슬레이브 핀, 리셋 핀, led 핀을 각각 매크로 상수로 저장해둡니다.
- int value = 0; ... : digitalWrite()에서 HIGH와 LOW를 저장할 value를 선언하고 UID 데이터 정의를 해둡니다. 나중에 입력된 값이 같은 경우에 memcmp() 함수를 이용하여 인식된 카드와 UID가 일치하는지 확인할 예정입니다.
- byte nuidPICC[4] ... : 읽은 UID 저장용으로 변수선언해두고, 블록 접근을 위한 기본 키와 RFLD 객체를 생성합니다.
• MFRC522::MIFARE_Key key; : 블록 접근을 위한 키를 선언해줍니다. RFID 카드의 특정 블록(메모리 영역)에 접근할 때 필요한 보안 키를 담는 구조체로, MIFARE_Key는 6바이트 길이의 배열로 되어있습니다. 이걸 선언해준 이유는 사용하는 리더기는 MIFARE 카드를 읽습니다. 이 카드 내부에는 블록(섹터) 단위로 나뉜 메모리 공간이 있고 이 데이터를 읽거나 쓰기 위해서는 접근 키가 필요합니다. 보안상 아무 블록이나 못 건들이게 하기 위해서 접근 권한 키를 설정해뒀고 이 키를 이용해서 접근을 허용하게 해줍니다. 하지만 현재 LED를 끄고 키는 경우에는 블록 접근을 할 이유가 없어서 사용되지 않고 있습니다. 블록 접근 키는 보통 교통카드나 학생증의 이름, 잔액 등의 정보를 저장하고 접근할 때 사용됩니다.
- void setup() {...} : pinMode와 rfid 초기화, 리더 정보 출력, 버전 정보 출력을 해줍니다.
• rfid.PCD_Init(); : RFID 리더 모듈을 초기화해주는 코드입니다. SPI 버스를 초기화하고, 모듈 내부 레지스터를 초기화하기 때문에 반드시 작성해줘야 리더기가 작동합니다.
• rfid.PCD_DumpVersionToSerial(); : 리더기의 펌웨어 버전을 시리얼 모니터에 출력합니다. 리더기가 제대로 연결되었는지 확인할 수 있지만 필수는 아닙니다. Firmware Version: 0x92 = v2.0 이런 식으로 출력됩니다.
• F()는 아두이노에서 사용하는 Flash 문자열 매크로 입니다. 원래 문자열은 SRAM에 저장되지만 문자열이 많아져 RAM이 부족해지는 상황을 방지하기 위해 F()로 메모리 사용을 절약해준 것입니다.
void loop() {
digitalWrite(LED_PIN, value);
readRFID();
}
void readRFID(void) {
for (byte i = 0; i < 6; i++) {
key.keyByte[i] = 0xFF;
}
if (!rfid.PICC_IsNewCardPresent())
return;
if (!rfid.PICC_ReadCardSerial())
return;
if (rfid.uid.size == sizeof(knownCardUID)) {
if (memcmp(rfid.uid.uidByte, knownCardUID, rfid.uid.size) == 0) {
Serial.println("Known Card Detected!");
value = HIGH;
}
}
if (rfid.uid.size == sizeof(knownKeyfobUID)) {
if (memcmp(rfid.uid.uidByte, knownKeyfobUID, rfid.uid.size) == 0) {
Serial.println("Known Keyfob Detected!");
value = LOW;
}
}
for (byte i = 0; i < 4; i++) {
nuidPICC[i] = rfid.uid.uidByte[i];
}
Serial.print(F("RFID In dec: "));
printDec(rfid.uid.uidByte, rfid.uid.size);
Serial.println();
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}
void printHex(byte *buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], HEX);
}
}
void printDec(byte *buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], DEC);
}
}
- void loop(){...} : 카드나 키포드를 읽었을 때를 위한 함수와 LED값을 설정하는 함수를 실행합니다.
- void readRFID() { ... } :
• for( byte i = 0; i < 6; i++) {...} : RFID 블록 접근 시 사용할 기본 키를 설정합니다. 이 코드에서는 데이터 블록에 읽거나 쓰진 않지만 기본 인증을 위해 설정해둡니다.
• if()...return; : 새로운 카드가 감지되었나 확인하고 없으면 함수를 종료합니다. 카드가 있으면 UID를 읽기를 시도합니다.
-> rfid.PICC_IsNewCardPresent() : 새로운 카드가 리더 범위 안에 있는지 감지합니다.
-> rfid.PICC_ReadCardSerial() : 카드의 UID를 읽습니다. 읽는 것을 성공할 때 true가 반환됩니다.
• if(rfid.uid.size == sizeof(비교할 카드나 키포드의 uid)){...} : rfid.uid.size(입력된 uid의 크기)가 sizeof(저장된카드나키포드의uid)의 길이와 같다면, memcmp()은 배열 두개를 비교할 때 사용됩니다. memcmp(배열1,배열2,비교할 길이)로 사용하는 것이고 두 배열이 같으면 0을 반환합니다. 다른 경우에는 베열1과 배열2의 처음 바이트를 기준으로 0보다 크고 작은지를 출력합니다.( < 0 와 > 0)
• for( byte i = 0; i < 4; i++) {...} : 사용되지는 않았지만, 들어온 값 UID값을 저장해줍니다.
• 마지막 디버깅을 위한 출력 :
-> rfid.PICC_HaltA() : 카드와의 통신을 종료하면서 슬립 모드로 전환합니다. 이걸 하지 않으면 계속 읽는 상태가 되기 때문에 한 번 읽고 멈추게 하기 위해서는 꼭 호출해야합니다.
-> rfid.PCD_StopCrypto1() : 암호화된 통신을 종료합니다. 카드와 데이터를 읽거나 쓸 때 내부적으로 암호화가 활성화됩니다. 이를 호출해서 암호화된 연결을 종료해야 다음 카드 인식이 원할해집니다.
- void printHex(), void printDec() : 바이트 값을 보기 좋게 출력하기 위해 사용되는 함수입니다. 각각 16진수와 10진수로 출력하는 함수입니다.
'Programming Language > C&C++' 카테고리의 다른 글
[C/C++] ESP32와 DS3231 연결해서 온도에 따라 LED ON/OFF 하기 (0) | 2025.06.01 |
---|---|
[C/C++] ESP32와 DHT11 연결해서 온도에 따라 LED ON/OFF 하기 (0) | 2025.05.19 |
[C/C++] ESP32에서 RTOS 사용하여 3가지 작업 유연하게 처리하기 (0) | 2025.04.13 |
[C/C++] ESP32에서 3가지 작업(LCD,Switch,UART) 반복처리하기 (0) | 2025.04.09 |
[C/C++] ESP32에서 EEPROM 저장하기 (0) | 2025.04.08 |