이 스케치는 MFRC522 리더를 이용해 MIFARE Classic 카드에 문자열, 정수, 구조체 등 여러 타입의 데이터를 쓰고/읽는 예제입니다. 학습 목적으로 각 단계(챕터)의 코드를 주석으로 보존하며, 변경점에는 [CH2] ~ [CH7] 태그를 달아 차이를 쉽게 확인할 수 있습니다.
구성 요소
- 보드: Arduino (UNO 기준, 다른 보드도 SPI 핀만 맞추면 가능)
- 리더: MFRC522 (RC522)
- 라이브러리:
MFRC522,SPI
배선
SS(SDA)→ D10RST→ D9- 나머지
SCK/MOSI/MISO는 보드의 하드웨어 SPI 핀 사용
시리얼 명령어 (9600 baud)
ws문자열 쓰기: 블록 60에 기본 문자열 기록wi정수 쓰기: 블록 61에 16비트 정수(예: 32767) 기록rs문자열 읽기: 블록 60에서 읽기ri정수 읽기: 블록 61에서 읽기wt구조체 쓰기:TagData를 블록 56–57(연속 두 블록)에 기록rt구조체 읽기: 블록 56–57에서TagData읽기 후 항목별 출력
챕터 구성 (코드 내 주석으로 구분)
- CH2: 기본 쓰기 흐름
- CH3:
checkAuth,writeString헬퍼 추가 및 리팩터링 - CH4: 문자열 읽기
readString - CH5: 정수 쓰기
writeInteger - CH6: 정수 읽기
readInteger - CH7: 구조체
TagData(name/total/payment) 2블록 단위 읽기/쓰기
챕터별 상세 변경
- CH2
- 목적: RC522 태그에 문자열을 직접 쓰는 최소 동작을 확인.
- 주요 변경:
loop()에서w명령을 직접 처리하고, 블록 60 인증 후 고정 문자열을MIFARE_Write로 기록.if (!rc522.PICC_IsNewCardPresent()) return; if (!rc522.PICC_ReadCardSerial()) return; status = rc522.PCD_Authenticate(..., 60, &key, &(rc522.uid)); String name = "clyde's"; name.toCharArray(data, name.length() + 1); status = rc522.MIFARE_Write(60, (byte*)&data, 16);
- CH3
- 목적: 중복 코드를 줄이고 재사용 가능한 기본 구조를 마련.
- 주요 변경:
checkAuth,writeString헬퍼를 추가하고,switch기반 명령 처리로loop()를 리팩터링.switch (cmd.charAt(0)) { case 'w': status = writeString(60, key, "clyde's"); break; } MFRC522::StatusCode checkAuth(int index, MFRC522::MIFARE_Key key) { return rc522.PCD_Authenticate(PICC_CMD_MF_AUTH_KEY_A, index, &key, &(rc522.uid)); }
- CH4
- 목적: 저장된 문자열을 읽어 검증할 수 있는 기능 추가.
- 주요 변경:
readString함수 도입,r명령 및rs서브커맨드를 통해 블록 60을 읽고 시리얼에 출력.case 'r': switch (cmd.charAt(1)) { case 's': status = readString(60, key, s_data); Serial.println(s_data); break; } MFRC522::StatusCode readString(int index, MFRC522::MIFARE_Key key, String& data) { status = checkAuth(index, key); byte buffer[18], length = 18; status = rc522.MIFARE_Read(index, buffer, &length); data = String((char*)buffer); return status; }
- CH5
- 목적: 문자열뿐 아니라 정수 데이터도 카드에 저장·조회 가능하도록 확장.
- 주요 변경:
writeInteger/readInteger,toBytes/toInteger함수 추가,wi·ri명령으로 블록 61에 16비트 정수를 Little Endian으로 입·출력.case 'w': if (cmd.charAt(1) == 'i') { status = writeInteger(61, key, 32767); rc522.PICC_DumpToSerial(&(rc522.uid)); } break; case 'r': if (cmd.charAt(1) == 'i') { status = readInteger(61, key, i_data); Serial.println(i_data); // 32767 출력 } break; MFRC522::StatusCode writeInteger(int index, MFRC522::MIFARE_Key key, int data) { MFRC522::StatusCode status = checkAuth(index, key); byte buffer[16]; memset(buffer, 0x00, sizeof(buffer)); toBytes(buffer, data); // LSB-first 로 2바이트 채움 return rc522.MIFARE_Write(index, buffer, sizeof(buffer)); }
- CH6
- 목적: 읽기 명령을 분리·보강하고 임시 버퍼 관리를 개선.
- 주요 변경:
loop()에서rs/ri처리 시 임시 변수(s_data,i_data)를 사용하고,readInteger/readString성공 시 값을 시리얼에 출력.case 'r': switch (cmd.charAt(1)) { case 's': status = readString(60, key, s_data); Serial.println(s_data); break; case 'i': status = readInteger(61, key, i_data); Serial.println(i_data); break; }
- CH7
- 목적: 구조체 형태의 복합 데이터를 두 블록에 나누어 저장·조회하는 고급 예제 구현.
- 주요 변경:
TagData구조체 정의,writeTagData/readTagData추가,wt·rt명령이 블록 56–57에name/total/payment를 연속 기록하고 다시 읽어 필드별로 출력.
주요 헬퍼 함수
checkAuth: 지정 블록에 접근하기 전에 기본 키 A로 인증을 수행하고 실패 시 오류 메시지를 출력.toBytes/toInteger: 16비트 정수를 리틀엔디언으로 버퍼에 변환하거나 다시 정수로 복원.writeString/readString: 문자열을 16바이트 블록에 기록하거나 해당 블록에서 읽어String으로 반환.writeInteger/readInteger: 정수를 16바이트 버퍼(나머지 0 패딩)에 써 넣고 다시 읽어 복원.writeTagData/readTagData:TagData(name, total, payment)구조체를 32바이트 버퍼에 담아 연속 두 블록에 기록하고 다시 읽어 필드별로 복원.
사용 방법
- Arduino IDE에서 라이브러리(MFRC522) 설치
- 보드와 포트 선택 후 스케치 업로드
- 시리얼 모니터(9600, 줄바꿈 전송)에서 명령 입력
- 카드/태그를 리더에 올려놓은 상태에서
ws,wi,rs,ri,wt,rt실행
- 카드/태그를 리더에 올려놓은 상태에서
참고 사항
- 블록 인덱스(56, 57, 60, 61)는 예시이므로 카드 레이아웃에 맞게 조정 가능합니다.
- 보안 키는 기본값(0xFF)으로 동작하도록 되어 있습니다. 실제 서비스 환경에서는 키를 변경해주세요.
- 카드 종류가
MIFARE Classic이 아닌 경우 동작이 다를 수 있습니다.
데이터 저장 블록 요약
| 블록 | 용도 | 관련 명령 | 기본 데이터 예시 |
|---|---|---|---|
| 56 | TagData 앞 16B |
wt / rt |
이름/총액 일부 |
| 57 | TagData 뒤 16B |
wt / rt |
결제 금액 등 나머지 |
| 60 | 문자열 | ws / rs |
"clyde's" |
| 61 | 16비트 정수 | wi / ri |
32767 → FF 7F |
기초 지식: RFID와 MFRC522
- RFID란?
- 전자기장(13.56MHz, HF)을 이용해 태그와 리더가 비접촉으로 통신하는 기술입니다.
- 태그는 전원(배터리)이 없는 수동형이 보편적이며, 리더의 전자기장으로부터 전력을 유도해 응답합니다.
- MFRC522 리더 칩
- SPI/I2C/UART를 지원하는 13.56MHz용 리더 IC로, MIFARE Classic(1K/4K), Ultralight 등 ISO/IEC 14443 Type A 태그를 지원합니다.
- Arduino 모듈(일명 RC522 보드)은 주로 SPI를 사용하며,
SDA(SS),SCK,MOSI,MISO,RST,3.3V,GND핀을 제공합니다.
- MIFARE Classic 카드
- 제품군: 1K(16섹터, 64블록)과 4K(40섹터, 256블록) 변형이 가장 흔하며, 각 블록은 16바이트 고정 길이를 갖습니다.
- 메모리 구성: 각 섹터의 마지막 블록은
섹터 트레일러로, 키 A/B와 접근 비트를 저장합니다. 접근 비트는 섹터 내 다른 블록에 대한 읽기/쓰기 권한을 정의하므로 임의로 덮어쓰면 섹터 전체가 잠길 수 있습니다. - 인증 흐름: 리더는 UID를 읽은 뒤, 원하는 섹터의 트레일러에 저장된 키 A 또는 키 B 중 하나로 인증해야 데이터 블록에 접근할 수 있습니다. 이 스케치는 학습 목적상 기본 키(
FF FF FF FF FF FF)를 사용합니다. - 데이터 저장 팁: 데이터 블록은 16바이트 단위이므로, 문자열은 널 종료 문자를 고려해 길이를 조정하고, 정수/구조체는 엔디안을 통일해야 합니다. 또한 한 섹터의 마지막 블록은 데이터 저장에 사용하지 않는 것이 안전합니다.
- MIFARE Classic 동작 예시
- 스케치에서는 블록 56~57에 구조체(
TagData)를 저장하고, 블록 60·61에 문자열/정수를 개별로 저장합니다. write*명령을 수행하면 인증 → 데이터 블록 접근 → 쓰기 순서로 진행되며, 이후read*명령으로 동일 블록을 읽어 원복 여부를 확인합니다.
- 스케치에서는 블록 56~57에 구조체(
- MIFARE Classic 메모리 구조(요약)
- 블록 크기: 16바이트. 섹터는 여러 블록으로 구성됩니다(1K 카드 기준 섹터 0~15, 각 섹터 4블록 = 총 64블록).
- 섹터 트레일러: 각 섹터의 마지막 블록은 키A/키B와 접근 비트가 저장되는 특수 블록입니다. 일반 데이터 저장 금지.
- 인증: 데이터 블록 접근 전
키A또는키B로 인증이 필요합니다(본 예제는 기본 키FF FF FF FF FF FF사용).
- UID(고유번호)
- 카드의 고유 식별자로, 안티콜리전 과정을 통해 읽어옵니다. 변경 불가(일부 비정상 카드 제외).
- 데이터 저장 팁
- 16바이트 정렬: 한 블록은 16바이트이므로, 문자열은 널 종료 포함 길이 관리가 필요합니다.
- 엔디안: 본 예제는 정수 2바이트를 리틀엔디언으로 인코딩/디코딩합니다(
toBytes,toInteger). - 민감 정보: 키·개인정보는 카드에 평문 저장하지 않도록 주의하세요. 필요 시 암호화/서명 적용을 고려하세요.
- 제한 사항 및 주의
- 보안 취약점: MIFARE Classic은 알려진 취약점이 있으므로 고보안 용도에는 적합하지 않습니다.
- 전원/거리: RC522는 3.3V 구동이며, 안테나 설계/환경에 따라 인식 거리가 달라집니다(수 cm 수준).
- 권한 비트 손상 주의: 섹터 트레일러를 잘못 쓰면 섹터가 영구 잠길 수 있으니, 트레일러 블록은 건드리지 마세요.