RFID LabRFID
Lecteur RFID RC522
Lisez un badge, comprenez son UID et ses blocs, puis construisez un contrôle d’accès. Le laboratoire vous montre ce que le lecteur voit avant que vous ne câbliez quoi que ce soit.
En bref
| Puce | NXP MFRC522, lecteur et écrivain 13,56 MHz (ISO/IEC 14443 A, cartes MIFARE) |
|---|---|
| Interface | SPI sur les modules courants (la puce sait aussi faire I²C et UART) |
| Alimentation | 3,3 V (2,5 à 3,3 V). Ne branche jamais le VCC sur 5 V |
| Portée | quelques centimètres, selon l’antenne et le badge |
| Cartes lues | MIFARE Classic 1K/4K, MIFARE Ultralight, NTAG (autocollants et porte-clés NFC) |
| Souvent vendu avec | une carte blanche et un porte-clés MIFARE Classic 1K |
13,56 MHz, pas 125 kHz. Les badges à 125 kHz (EM4100) ne sont pas lus par le RC522. Il faut un lecteur 125 kHz, comme le RDM6300.
Ce guide vous fait passer du premier badge lu à un contrôle d’accès complet, avec un badge virtuel pour comprendre ce que le lecteur voit avant même de câbler.
Câblage
| Broche RC522 | ESP32 (30 broches) | Arduino Uno / Nano |
|---|---|---|
| SDA (SS / CS) | GPIO 5 | 10 |
| SCK | GPIO 18 | 13 |
| MOSI | GPIO 23 | 11 |
| MISO | GPIO 19 | 12 |
| IRQ | non connectée | non connectée |
| GND | GND | GND |
| RST | GPIO 22 | 9 |
| 3.3V | 3V3 | 3,3 V |
- Le RC522 se branche en 3,3 V. Sur une Uno ou une Nano, utilise la sortie 3,3 V (elle fournit assez de courant : la puce consomme une vingtaine de milliampères).
- Sur une Uno, les lignes SPI sont au niveau 5 V : ajoutez un diviseur ou un convertisseur de niveau si vous voulez protéger la puce.
- La broche
IRQreste libre : la bibliothèque interroge le lecteur, elle n’a pas besoin de l’interruption. - Garde des fils courts (10 cm environ) pour le SPI, surtout au-dessus de 4 MHz.
Les GPIO 21 et 22 sont le bus I²C par défaut de l’ESP32. Si vous ajoutez un écran OLED, placez RST ailleurs (le projet Badge Cloneur utilise le GPIO 27).
SPI
Le SPI utilise quatre fils : une horloge (SCK), une ligne du maître vers l’esclave (MOSI), une ligne de retour (MISO) et une ligne de sélection (SS, à l’état bas pour parler au RC522). Plusieurs périphériques peuvent partager SCK, MOSI et MISO ; chacun a son propre SS.
| Bus ESP32 | SCK | MISO | MOSI | SS |
|---|---|---|---|---|
| VSPI (défaut) | GPIO 18 | GPIO 19 | GPIO 23 | GPIO 5 |
| HSPI | GPIO 14 | GPIO 12 | GPIO 13 | GPIO 15 |
La bibliothèque MFRC522 utilise l’objet global SPI. Choisir « HSPI » revient donc à passer les broches HSPI à SPI.begin(SCK, MISO, MOSI, SS) ; le configurateur plus bas fait ce réglage pour vous. Le GPIO 12 (MISO du HSPI) est une broche de démarrage de l’ESP32 : si le montage la force à l’état haut au moment du reset, la carte peut refuser de démarrer.
Premier badge
Choisissez un badge virtuel : vous voyez ce que le lecteur en tire (UID, type, blocs) et ce que le moniteur série affiche avec rc522_uid_read.ino. Le résultat « autorisé » dépend de l’UID que vous déclarez.
Moniteur série (115 200 bauds)
- UID
- –
- Taille de l’UID
- –
- Type (bibliothèque)
- –
- SAK / ATQA
- –
- Mémoire
- –
/*
* Premier badge — Kaihatsu Lab
* Lecteur RC522 (SPI) sur ESP32 : affiche l'UID et le type de chaque carte présentée.
* Bibliothèque : MFRC522 (gestionnaire de bibliothèques de l'IDE Arduino).
*/
#include <SPI.h>
#include <MFRC522.h>
// Broches ESP32 (VSPI). Adaptez-les à votre câblage.
const uint8_t PIN_SCK = 18;
const uint8_t PIN_MISO = 19;
const uint8_t PIN_MOSI = 23;
const uint8_t PIN_SS = 5;
const uint8_t PIN_RST = 22;
MFRC522 rfid(PIN_SS, PIN_RST);
void setup() {
Serial.begin(115200);
SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_SS);
rfid.PCD_Init();
delay(50);
rfid.PCD_DumpVersionToSerial(); // 0x91 ou 0x92 : puce reconnue. 0x00 ou 0xFF : problème de câblage.
Serial.println(F("Approche une carte du lecteur..."));
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;
Serial.print(F("UID :"));
for (byte i = 0; i < rfid.uid.size; i++) {
Serial.print(rfid.uid.uidByte[i] < 0x10 ? " 0" : " ");
Serial.print(rfid.uid.uidByte[i], HEX);
}
Serial.println();
MFRC522::PICC_Type type = rfid.PICC_GetType(rfid.uid.sak);
Serial.print(F("Type : "));
Serial.println(rfid.PICC_GetTypeName(type));
rfid.PICC_HaltA(); // carte en veille : elle ne sera relue qu'après avoir été retirée puis représentée
rfid.PCD_StopCrypto1();
}
Le lecteur répond « version 0x91 ou 0x92 » quand la puce est bien câblée. Toute autre valeur (0x00, 0xFF) signale un problème de câblage ou d’alimentation : voir le dépannage.
UID
L’UID (Unique IDentifier) est le numéro de série de la carte : 4, 7 ou 10 octets selon la famille. Les MIFARE Classic ont en général 4 octets ; les NTAG et les Ultralight en ont 7. Le lecteur le lit sans clé, à chaque présentation.
L’UID n’est pas un secret. Il se lit sans authentification et des cartes à UID modifiable existent. Pour un accès qui compte, utilise-le comme identifiant, pas comme mot de passe : ajoute une donnée secrète protégée par clé, ou un contrôle côté serveur.
Formateur d'UID
MIFARE
| MIFARE Classic 1K | |
|---|---|
| Mémoire | 1 024 octets (1K) ou 4 096 octets (4K) |
| Organisation | 16 secteurs de 4 blocs (1K), 16 octets par bloc |
| Bloc 0 | fabricant : UID, BCC, SAK, ATQA (lecture seule sur une carte normale) |
| Dernier bloc de chaque secteur | « sector trailer » : clé A (6 octets), bits d’accès (4), clé B (6) |
| Clé d’usine | FF FF FF FF FF FF |
| Famille Ultralight / NTAG | |
|---|---|
| Ultralight, NTAG213 | pages de 4 octets (NTAG213 : 45 pages, soit 180 octets dont 144 utilisables) |
| Protection | pas de clés MIFARE Classic : mot de passe 32 bits en option (NTAG) ; le code d’écriture est différent |
Carte des blocs (1K)
Clique sur un bloc.
Lecture
Pour lire un bloc de données d’une carte MIFARE Classic, il faut d’abord s’authentifier sur le secteur avec la clé A ou B, puis appeler MIFARE_Read. Une carte neuve accepte la clé d’usine. Le code suivant lit le bloc 4 et l’affiche en hexadécimal et en texte.
/*
* Lecture d'un bloc MIFARE Classic — Kaihatsu Lab
* Authentification avec la clé A d'usine (FF FF FF FF FF FF), puis lecture du bloc 4
* affiché en hexadécimal et en texte.
*/
#include <SPI.h>
#include <MFRC522.h>
const uint8_t PIN_SS = 5;
const uint8_t PIN_RST = 22;
const byte BLOCK = 4; // secteur 1 : blocs 4, 5 et 6 pour les données, le 7 est la « trailer »
MFRC522 rfid(PIN_SS, PIN_RST);
MFRC522::MIFARE_Key key;
void setup() {
Serial.begin(115200);
SPI.begin(18, 19, 23, PIN_SS); // SCK, MISO, MOSI, SS
rfid.PCD_Init();
for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF; // clé d'usine
Serial.println(F("Approche une carte MIFARE Classic..."));
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;
MFRC522::StatusCode status = rfid.PCD_Authenticate(
MFRC522::PICC_CMD_MF_AUTH_KEY_A, BLOCK, &key, &(rfid.uid));
if (status != MFRC522::STATUS_OK) {
Serial.print(F("Authentification refusee : "));
Serial.println(rfid.GetStatusCodeName(status));
} else {
byte buffer[18]; // 16 octets de données + 2 octets de CRC
byte size = sizeof(buffer);
status = rfid.MIFARE_Read(BLOCK, buffer, &size);
if (status == MFRC522::STATUS_OK) {
Serial.print(F("Bloc "));
Serial.print(BLOCK);
Serial.print(F(" :"));
for (byte i = 0; i < 16; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], HEX);
}
Serial.print(F(" | "));
for (byte i = 0; i < 16; i++) {
Serial.print(buffer[i] >= 32 && buffer[i] < 127 ? (char)buffer[i] : '.');
}
Serial.println();
} else {
Serial.print(F("Lecture impossible : "));
Serial.println(rfid.GetStatusCodeName(status));
}
}
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1(); // à appeler après toute authentification
}
MIFARE_Readrenvoie 18 octets : 16 de données et 2 de CRC.PCD_StopCrypto1()désactive le chiffrement après l’authentification. Sans lui, la carte suivante peut échouer.- Pour une carte Ultralight ou NTAG, il n’y a pas d’authentification : utilise
MIFARE_Readsur une page, sans clé.
Écriture
Écrire suit le même chemin : authentification, puis MIFARE_Write avec exactement 16 octets. Ce code écrit « KAIHATSU LAB » dans le bloc 4 et relit le bloc pour vérifier.
Attention. N’écris jamais dans le bloc 0 ni dans un sector trailer (blocs 3, 7, 11, 15...). Une erreur dans le trailer peut rendre le secteur illisible pour toujours. Le code refuse ces blocs.
/*
* Écriture d'un bloc MIFARE Classic — Kaihatsu Lab
* Écrit un texte de 16 caractères maximum dans le bloc 4, puis relit le bloc pour vérifier.
*
* ATTENTION : n'écris jamais dans le bloc 0 ni dans un « sector trailer » (blocs 3, 7, 11, 15...).
* Le trailer contient les clés et les droits d'accès : une erreur peut rendre le secteur définitivement illisible.
*/
#include <SPI.h>
#include <MFRC522.h>
const uint8_t PIN_SS = 5;
const uint8_t PIN_RST = 22;
const byte BLOCK = 4;
const char TEXT[] = "KAIHATSU LAB"; // 16 caractères maximum
MFRC522 rfid(PIN_SS, PIN_RST);
MFRC522::MIFARE_Key key;
void setup() {
Serial.begin(115200);
SPI.begin(18, 19, 23, PIN_SS);
rfid.PCD_Init();
for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF;
if (BLOCK == 0 || (BLOCK % 4) == 3) {
Serial.println(F("Bloc interdit : choisis un bloc de donnees (4, 5, 6, 8, 9, 10...)."));
while (true) delay(1000);
}
Serial.println(F("Approche une carte MIFARE Classic pour ecrire..."));
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;
MFRC522::StatusCode status = rfid.PCD_Authenticate(
MFRC522::PICC_CMD_MF_AUTH_KEY_A, BLOCK, &key, &(rfid.uid));
if (status == MFRC522::STATUS_OK) {
byte data[16] = {0};
size_t n = strlen(TEXT);
if (n > 16) n = 16;
memcpy(data, TEXT, n);
status = rfid.MIFARE_Write(BLOCK, data, 16);
if (status == MFRC522::STATUS_OK) {
byte check[18];
byte size = sizeof(check);
if (rfid.MIFARE_Read(BLOCK, check, &size) == MFRC522::STATUS_OK && memcmp(check, data, 16) == 0) {
Serial.println(F("Ecriture verifiee."));
} else {
Serial.println(F("Ecriture faite, mais la relecture ne correspond pas."));
}
} else {
Serial.print(F("Ecriture impossible : "));
Serial.println(rfid.GetStatusCodeName(status));
}
} else {
Serial.print(F("Authentification refusee : "));
Serial.println(rfid.GetStatusCodeName(status));
}
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}
Sur une NTAG ou une Ultralight, l’écriture se fait par page de 4 octets avec MIFARE_Ultralight_Write.
Authentification
| Paramètre de PCD_Authenticate | Valeur |
|---|---|
| Type de clé | PICC_CMD_MF_AUTH_KEY_A ou PICC_CMD_MF_AUTH_KEY_B |
| Bloc | n’importe quel bloc du secteur visé : l’authentification vaut pour tout le secteur |
| Clé | structure MIFARE_Key de 6 octets |
| UID | l’UID de la carte, tel que lu (rfid.uid) |
- Chaque secteur a ses propres clés A et B, stockées dans son trailer. Un système d’accès sérieux les change et garde la clé B secrète.
- Les bits d’accès décident ce que chaque clé peut faire (lire, écrire, incrémenter, changer les clés). La configuration d’usine autorise la clé A ou B à lire et écrire les blocs de données.
- « Authentification refusée » veut presque toujours dire : la carte a déjà été configurée avec d’autres clés.
Personnalisation
Règle les broches, choisis le bus SPI, puis récupère un sketch prêt à téléverser.
Configurateur
// Le code apparaît ici.Dépannage
| Symptôme | Cause probable | Solution |
|---|---|---|
| Version 0x00 ou 0xFF au démarrage | Câblage, alimentation ou SS/RST inversés | Vérifie 3,3 V et GND, puis SCK, MOSI, MISO et SS. Teste avec des fils courts. |
| Aucune carte détectée | Badge 125 kHz, carte trop loin ou antenne masquée | Utilise un badge 13,56 MHz (MIFARE, NTAG) à 1 ou 2 cm de l’antenne, à plat. |
| La carte n’est lue qu’une fois | PICC_HaltA() l’a mise en veille | C’est voulu : retire la carte puis représente-la. Retire l’appel pour relire en continu. |
| Authentification refusée | Clés différentes de celles d’usine | Utilise les bonnes clés A ou B du secteur, ou essaie une carte neuve. |
| Écriture impossible | Bloc 0, trailer ou droits d’accès restrictifs | Écris dans un bloc de données (4, 5, 6, 8...) d’un secteur en configuration d’usine. |
| L’UID change à chaque lecture | Téléphone ou carte bancaire (UID aléatoire) | Ces appareils masquent leur UID. Utilise un badge ou un autocollant NFC classique. |
| Redémarrages de l’ESP32 à la lecture | Alimentation instable | Alimente par une source correcte, ajoute un condensateur de 10 à 100 µF sur le 3,3 V du module. |
| Ne compile pas | Bibliothèque absente ou mauvaise carte | Installe « MFRC522 » et sélectionne bien la carte ESP32 dans le menu Outils. |
Bibliothèques
| Bibliothèque | À retenir | À choisir pour |
|---|---|---|
| MFRC522 | La bibliothèque de référence, très répandue, avec de nombreux exemples. Elle utilise l’objet global SPI. | Tous les exemples de ce guide et le projet Badge Cloneur |
| MFRC522v2 | Réécriture qui sépare le pilote (SPI, I²C) de la logique. Plus souple pour plusieurs lecteurs. | Plusieurs RC522 sur un même montage, ou SPI personnalisé |
Les deux s’installent depuis le gestionnaire de bibliothèques de l’IDE Arduino. Ne les installe pas ensemble : elles partagent des noms de fichiers.
Projets
Projet interactif : serrure à badge
RC522, écran SSD1306 et servomoteur. Passe un badge virtuel : l’écran affiche le même texte que le vrai montage, et le servo tourne si l’UID est autorisé (ici 04 A3 7B 2C).
Passer un badge :
En attente d’un badge.
/*
* Serrure à badge — Kaihatsu Lab
* RC522 (SPI) + écran OLED SSD1306 (I2C) + servomoteur sur ESP32.
* Un badge autorisé affiche « ACCES OK » et ouvre le servo trois secondes.
*
* Bibliothèques : MFRC522, Adafruit GFX, Adafruit SSD1306, ESP32Servo.
*/
#include <SPI.h>
#include <MFRC522.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP32Servo.h>
// RC522 (SPI) : SCK 18, MISO 19, MOSI 23
const uint8_t PIN_SS = 5;
const uint8_t PIN_RST = 27; // pas 22 : le GPIO 22 est déjà le SCL de l'écran
// OLED (I2C) : SDA 21, SCL 22
#define SCREEN_W 128
#define SCREEN_H 64
#define OLED_ADDR 0x3C
// Servo
const uint8_t PIN_SERVO = 13;
const int ANGLE_CLOSED = 0;
const int ANGLE_OPEN = 90;
const uint32_t OPEN_MS = 3000;
// Badges autorisés (UID de 4 octets) : ajoute une ligne par badge
const byte AUTHORIZED[][4] = {
{0x04, 0xA3, 0x7B, 0x2C},
};
const uint8_t AUTHORIZED_COUNT = sizeof(AUTHORIZED) / sizeof(AUTHORIZED[0]);
MFRC522 rfid(PIN_SS, PIN_RST);
Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, -1);
Servo door;
bool isAuthorized() {
if (rfid.uid.size != 4) return false;
for (uint8_t i = 0; i < AUTHORIZED_COUNT; i++) {
if (memcmp(rfid.uid.uidByte, AUTHORIZED[i], 4) == 0) return true;
}
return false;
}
String uidToString() {
String s;
for (byte i = 0; i < rfid.uid.size; i++) {
if (rfid.uid.uidByte[i] < 0x10) s += '0';
s += String(rfid.uid.uidByte[i], HEX);
if (i < rfid.uid.size - 1) s += ' ';
}
s.toUpperCase();
return s;
}
void screen(const char* title, const String& uid, const char* result) {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.print(title);
display.setCursor(0, 14);
display.print(uid);
display.setTextSize(2);
display.setCursor(0, 40);
display.print(result);
display.display();
}
void setup() {
Serial.begin(115200);
SPI.begin(18, 19, 23, PIN_SS);
rfid.PCD_Init();
Wire.begin(21, 22);
display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
door.setPeriodHertz(50);
door.attach(PIN_SERVO, 500, 2400); // impulsions min et max en microsecondes
door.write(ANGLE_CLOSED);
screen("BADGE", "", "");
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;
String uid = uidToString();
bool ok = isAuthorized();
screen("BADGE", uid, ok ? "ACCES OK" : "REFUSE");
if (ok) {
door.write(ANGLE_OPEN);
delay(OPEN_MS);
door.write(ANGLE_CLOSED);
} else {
delay(1500);
}
screen("BADGE", "", "");
rfid.PICC_HaltA();
rfid.PCD_StopCrypto1();
}
Idées à construire
- Pointeuse : enregistre chaque UID avec l’heure sur une carte microSD.
- Boîte à souvenirs : un autocollant NFC déclenche une musique ou une lumière.
- Compteur de passages avec un écran OLED et le nom de chaque badge.
- Clé de démarrage pour un projet : rien ne se lance sans le bon badge.
Le PCB et les composants pour reproduire les projets arriveront avec la boutique.