RC522-RFID-Kartenleser und OLED-Display (128x64 px, 0.96") an STM32 betreiben

Dieses Projekt setzt auf dem letzten Projekt OLED-Display (128x64 px, 0.96") an STM32 betreiben auf und erweitert es um einen RFID (NFC) Kartenleser-Modul RC522 mit 13.56 MHz. Ich empfehle direkt ein Set zu kaufen, in dem eine Karte (in Scheckkartengröße) oder ein Tag (Schlüsselanhänger) oder gleich beides, vorhanden ist, damit man auch etwas zum Testen hat.

Das Modul ist eine kostengünstige Möglichkeit in das Thema RFID, also kontaktlose Datenübertragung, mit einem Arduino (oder hier: einer STM32 Blue Pill) einzusteigen. Wer mit diesem 10 Euro Projekt herausfindet, dass ihn das Thema interessiert, der kann später in ein Proxmark3 Easy Modul oder ähnliches investieren, das weitere Frequenzen und Funktionen (z. B. Clonen bestimmter Chiptypen) bietet, aber das wird evtl. mal Thema eines anderen Blogs.

RFID Grundlagen

RFID ist die englischsprachige Abkürzung für radio-frequency identification, also die Identifikation mittels Radiowellen. Eine weitere Abkürzung, die in diesem Themenberech immer wieder auftritt ist NFC (Near Field Communication), also der Nahfeldkommunikation.


Die Begriffe RFID und NFC kann man nicht genau trennen. Ihre Grenzen verwischen und das eine meint irgendwie auch das andere. Die Technologie ist die folgende: ein winziger Chip ist an eine Spule (Antenne) angeschlossen, in die von einem Lese/Schreibgerät ein elektromagnetisches Feld induziert wird. Dadurch bekommt der Chip Strom - er braucht also keine eigene Batterie.


Durch vorher festgelegte Änderungen der Frequenz des Feldes (Protokoll) können nun Befehle geformt werden, die der RFID-Chip erkennt und ausführt. Etwa der Befehl, die eigenen ID preiszugeben. Der Chip entnimmt in einem bestimmten Muster Strom aus dem induzierten Feld und schächt es damit, was der Leser/Schreiber wiederum messen kann. Auf die Feldstärkeschwankungen ist die ID sozusagen aufmoduliert.

So können Leser/Schreiber und RFID-Chip miteinander kommunizieren. Der Leser sendet Befehle und der RFID-Chip antwortet entsprechend. Im einfachsten Fall kennt der RFID-Chip nur den Befehl, seine ID zu senden, die er in z. B. 4 Bytes gespeichert hat. Sobald er Strom bekommt, "schickt" er seine ID heraus. Diese einfachen System laufen meist mit einer Träger-Frequenz von 125 kHz und können für Zugangssysteme verwendet werden. Dabei wird einfach geprüft, ob die gesendete ID zutrittsberechtigt ist und je nachdem bleibt die Tür verschlossen oder wird geöffnet. Protokolliert man gleichzeitig noch die Zeit, hat man eine Stechuhr und weiß, wann ein Mitarbeiter gekommen und wann gegangen ist.

Es gibt aber auch leistungsfähigere Chips mit eigenem Speicher. Zum Beispiel den NXP Mifare 1K, der über 1 KByte Speicher verfügt (Frequenz: 13.56 MHz). Dieser ist in Sektoren und Blöcke unterteilt. Außer der (normalerweise unveränderlichen) ID können hier weitere Daten abgelegt werden und später wieder abgerufen werden. Dafür kennt der RFID-Chip weitere Befehle.


So kann man weitere Anwendungen realisieren, z. B. eine Kaffeautomaten-Guthabenkarte, die man mit einem Betrag aufladen kann und dann am Kaffeeautomaten den Kaffee bezahlen kann. Hier sollte man sich allerdings Gedanken um die Sicherheit machen. Einfach eine "1000" für 10 Euro auf die Karte beim Aufladen zu schreiben und dann bei jedem ausgegebenen Kaffee um "100" (Der Kaffee soll je Tasse 1 Euro kosten) zu verringern und bei nicht ausreichendem Guthaben die Kafeeausgabe zu verweigern, hört sich einfach und logisch an.

Aber wenn jemand einen RFID-Leser/Schreiber hat, dann könnte er die Aufladung einfach faken und einen beliebigen Betrag auf die Karte schreiben. Am sichersten ist natürlich, nur die ID zu übertragen und die Beträge außerhalb der Karte zu speichern, was Manipulationsversuche an der Karte nutzlos macht. Oder zumindestens die Beträge kryptografisch so zu verschlüsseln, dass eine Fälschung nicht so einfach möglich ist. Das Thema Sicherheit ist auch bei RFID ein Thema, das zugegebenermaßen sehr komplex ist, dass man dennoch nicht vernachlässigen sollte und an das man spätestens vor Einführung gedacht haben sollte, möchte man später keine bösen Überraschungen erleben mit Sicherheitslücken, die dann nicht mehr so einfach aus der Welt zu schaffen sind. In diesem Projekt geht es aber erstmal nur um die RFID-Technik und das Thema Sicherheit werde ich beiseite lassen, es würde den Rahmen sprengen.

Gebräuchliche Frequenzen für RFID-Chips sind: Stand der Technik ist moment HF. UHF und SHF sind noch wenig verbreitet, bieten aber aufgrund der höheren Übertragungsraten die Möglichkeit mehr Daten z. B. beim Vorbeigehen zu übertragen (bevor der Tag wieder ausserhalb der Reichweite ist). Die technischen Protokolle für UHF und SHF sind wesentlich komplizierter als bei HF.

Das RC522 Modul


Auf unserem RC522 Modul befindet sich ein Chip des Typs RC522 von NXP Semiconductors (früher einmal Philips), der mit einer HF-Frequenz von 13.56 MHz arbeitet. Es ist darum ideal für RFID-Chips des Types Mifare vom selben Hersteller mit selber Frequenz.

Der Verkäufer des Moduls nennt folgende technischen Daten: RC522 is applied to the highly integrated read and write 13.56MHz contactless communication card chip, NXP launched by the company for the "table" application of a low-voltage, low-cost, small size of the non-contact card chip to read and write, smart meters and portable handheld devices developed better choice. The MF RC522 use of advanced modulation and demodulation concept completely integrated in all types of 13.56MHz passive contactless communication methods and protocols. 14443A compatible transponder signals. The digital part of to handle the ISO14443A frames and error detection. In addition, support rapid CRYPTO1 encryption algorithm, terminology validation products. MFRC522 support series of high-speed non-contact communication, two-way data transmission rate up to 424kbit/s. As new members of the 13.56MHz reader card series of highly integrated chip family, MF RC522 MF RC500 MF RC530 There are a lot of similarities, but also have many of the characteristics and differences. Communication between it and the host SPI mode helps to reduce the connection narrow PCB board volume, reduce costs. RFID module: The MF522-AN module the the original Philips MFRC522 chip design circuit card reader, easy to use, low cost, and applies to the user equipment development, the reader and the development of advanced applications, the need for the user RF card terminal design/production. This module can be directly loaded into the various reader molds. Utilizes a voltage of 3.3V, through the SPI interface simple few lines directly with any user CPU motherboard connected communication can ensure that the module is stable and reliable work, distance card reader. Electrical parameters: Operating current: 13-26mA/DC 3.3V Idle current: 10-13mA/DC 3.3V Sleep current: < 80uA Peak current: < 30mA Operating Frequency: 13.56MHz Supported card types: S50, S70 Ultralight, Pro, DESFire Product physical characteristics: size: 40mm×60mm Environmental Operating temperature: -20-80 degrees Celsius Environmental Storage Temperature: -40-85 degrees Celsius Relative humidity: relative humidity 5% -95% Module interfaces SPI Parameter Data transfer rate: maximum 10Mbit/s Dem RC522 Chip, der das gesamte Protokoll abbildet und die ganze Arbeit macht, stehen auf der Platine noch ein paar Komponenten zur Seite:

Der NXP RC522 Chip

Da der RC522 Chip alles erledigt lohnt sich auch ein Blick in das Datenblatt MFRC522. Die wichtigsten Dinge daraus möchte ich hier noch einmal aufführen:
The MFRC522 supports all variants of the MIFARE Mini, MIFARE 1K, MIFARE 4K, MIFARE Ultralight, MIFARE DESFire EV1 and MIFARE Plus RF identification protocols. The MFRC522 is a highly integrated reader/writer IC for contactless communication at 13.56 MHz. The MFRC522 reader supports ISO/IEC 14443 A/MIFARE and NTAG. The MFRC522 supports MF1xxS20, MF1xxS70 and MF1xxS50 products. The MFRC522 supports contactless communication and uses MIFARE higher transfer speeds up to 848 kBd in both directions. The following host interfaces are provided: • Serial Peripheral Interface (SPI) • Serial UART (similar to RS232 with voltage levels dependant on pin voltage supply) • I2C-bus interface Features: • Typical operating distance in Read/Write mode up to 50 mm depending on the antenna size and tuning • Supports MF1xxS20, MF1xxS70 and MF1xxS50 encryption in Read/Write mode • Supports ISO/IEC 14443 A higher transfer speed communication up to 848 kBd • SPI up to 10 Mbit/s • I2C-bus interface up to 400 kBd in Fast mode, up to 3400 kBd in High-speed mode • RS232 Serial UART up to 1228.8 kBd • FIFO buffer handles 64 byte send and receive • Flexible interrupt modes • Programmable timer • Internal oscillator for connection to 27.12 MHz quartz crystal • 2.5 V to 3.3 V power supply • CRC coprocessor Kurzum: Der MFRC522-Chip / Reader liest alle 13.56 MHz Mifare Classic Tags, als da wären: Interface des Chips ist SPI als auch I2C Bus und läuft auf der 3.3 Volt-Schiene. Das Modul hat aber nur den SPI-Bus herausgeführt, so dass wir diesen nehmen müssen.

Desweiteren gibt das Datasheet natürlich Auskunft darüber, wie SPI und I2C und weitere Protkolle anzusprechen sind und welche Befehle es gibt. Die Befehle sind vielleicht noch interessant: Command bin dec Action Idle 0000 0 no action, cancels current command execution Mem 0001 1 stores 25 bytes into the internal buffer Generate RandomID 0010 2 generates a 10-byte random ID number CalcCRC 0011 3 activates the CRC coprocessor or performs a self test Transmit 0100 4 transmits data from the FIFO buffer NoCmdChange 0111 7 no command change, can be used to modify the CommandReg register bits without affecting the command, for example, the PowerDown bit Receive 1000 8 activates the receiver circuits Transceive 1100 12 transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission - 1101 13 reserved for future use MFAuthent 1110 14 performs the MIFARE standard authentication as a reader SoftReset 1111 15 resets the MFRC522 Mir fällt auf, dass die Befehle (dezimal) 5, 6, 9, 10 und 11 fehlen. Ich nehme an, auch sie sind für zukünftige Verwendung vorbehalten. Die genauen Parameter für Befehle wie MFAuthent sind dem Datenblatt zu entnehmen.

Wir werden uns einer Library bedienen, die für uns die Low-Level-Befehle bereits implementiert hat und bei der wir nur noch High-Level-Befehle absetzen müssen. Nur, wenn ich keine passende finden sollte, werde ich meine eigene Libary hierzu schreiben und dort die Low-Level-Befehle verwenden.

Anschluss des RC522 Moduls an den STM32

Das Modul bietet einen SPI-Bus über die Pins SDA (SS), SCK, MOSI, MISO. Außerdem hat das Modul Pins für Spannungsversorgung (3.3V und GND) und eine Reset-Leitung RST).

Wir entnehmen dem GPIO-Pinout des STM32, dass der 1. SPI-Bus beim STM32 auf PA7 bis PA4 liegt:



SS ist hier mit SDA gleichzusetzen. Für die Reset-Leitung benutzen wir den benachbarte Pin PB0, so dass die Verkabelung wie folgt aussieht:



RC522-Modul STM32 Kabelfarbe auf Foto SDA SS1 (PA4) grün SCK SCK1 PA5) lila MOSI MOSI1 (PB7) gelb MISO MISO1 (PB6) orange IRQ - GND GND braun RST PB0 weiß 3.3V 3.3V rot

Library zur Ansteuerung installieren und testen


Als nächstes benötigen wir eine Library zur Ansteuerung des RFID-Readers. Meine Suche im Blibliotheksmanager brachte für den Suchbegriff RC522 nur eine Library, nämlich die MFRC522 by GithubCommunity, also habe ich die natürlich zuerst versucht.

Ich war mir nicht ganz sicher, ob sie auch auf dem STM32 funktionieren würde, doch nach Angabe der verwendeten Pins funktionierte das Beispiel ReadNUID am Anhieb.

Source-Code ReadNUID.ino

An den fett gedruckten Stellen habe ich Anpassungen vorgenommen.

ReadNUID.ino (klicken, um diesen Abschnitt aufzuklappen)
/* * -------------------------------------------------------------------------------------------------------------------- * Example sketch/program showing how to read new NUID from a PICC to serial. * -------------------------------------------------------------------------------------------------------------------- * This is a MFRC522 library example; for further details and other examples see: https://github.com/miguelbalboa/rfid * * Example sketch/program showing how to the read data from a PICC (that is: a RFID Tag or Card) using a MFRC522 based RFID * Reader on the Arduino SPI interface. * * When the Arduino and the MFRC522 module are connected (see the pin layout below), load this sketch into Arduino IDE * then verify/compile and upload it. To see the output: use Tools, Serial Monitor of the IDE (hit Ctrl+Shft+M). When * you present a PICC (that is: a RFID Tag or Card) at reading distance of the MFRC522 Reader/PCD, the serial output * will show the type, and the NUID if a new card has been detected. Note: you may see "Timeout in communication" messages * when removing the PICC from reading distance too early. * * @license Released into the public domain. * * Typical pin layout used: * ----------------------------------------------------------------------------------------- * MFRC522 Arduino Arduino Arduino Arduino Arduino * Reader/PCD Uno/101 Mega Nano v3 Leonardo/Micro Pro Micro * Signal Pin Pin Pin Pin Pin Pin * ----------------------------------------------------------------------------------------- * RST/Reset RST 9 5 D9 RESET/ICSP-5 RST * SPI SS SDA(SS) 10 53 D10 10 10 * SPI MOSI MOSI 11 / ICSP-4 51 D11 ICSP-4 16 * SPI MISO MISO 12 / ICSP-1 50 D12 ICSP-1 14 * SPI SCK SCK 13 / ICSP-3 52 D13 ICSP-3 15 */ #include <SPI.h> #include <MFRC522.h> #define SS_PIN PA4 // 10 #define RST_PIN PB0 // 9 MFRC522 rfid(SS_PIN, RST_PIN); // Instance of the class MFRC522::MIFARE_Key key; // Init array that will store new NUID byte nuidPICC[4]; void setup() { Serial.begin(115200); SPI.begin(); // Init SPI bus rfid.PCD_Init(); // Init MFRC522 for (byte i = 0; i < 6; i++) { key.keyByte[i] = 0xFF; } delay (1000); // auf serial port warten. Serial.println(F("This code scan the MIFARE Classsic NUID.")); Serial.print(F("Using the following key:")); printHex(key.keyByte, MFRC522::MF_KEY_SIZE); Serial.println(); } void loop() { // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle. if ( ! rfid.PICC_IsNewCardPresent()) return; // Verify if the NUID has been readed if ( ! rfid.PICC_ReadCardSerial()) return; Serial.print(F("PICC type: ")); MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak); Serial.println(rfid.PICC_GetTypeName(piccType)); // Check is the PICC of Classic MIFARE type if (piccType != MFRC522::PICC_TYPE_MIFARE_MINI && piccType != MFRC522::PICC_TYPE_MIFARE_1K && piccType != MFRC522::PICC_TYPE_MIFARE_4K) { Serial.println(F("Your tag is not of type MIFARE Classic.")); return; } if (rfid.uid.uidByte[0] != nuidPICC[0] || rfid.uid.uidByte[1] != nuidPICC[1] || rfid.uid.uidByte[2] != nuidPICC[2] || rfid.uid.uidByte[3] != nuidPICC[3] ) { Serial.println(F("A new card has been detected.")); // Store NUID into nuidPICC array for (byte i = 0; i < 4; i++) { nuidPICC[i] = rfid.uid.uidByte[i]; } Serial.println(F("The NUID tag is:")); Serial.print(F("In hex: ")); printHex(rfid.uid.uidByte, rfid.uid.size); Serial.println(); Serial.print(F("In dec: ")); printDec(rfid.uid.uidByte, rfid.uid.size); Serial.println(); } else Serial.println(F("Card read previously.")); // Halt PICC rfid.PICC_HaltA(); // Stop encryption on PCD rfid.PCD_StopCrypto1(); } /** * Helper routine to dump a byte array as hex values to Serial. */ void printHex(byte *buffer, byte bufferSize) { for (byte i = 0; i < bufferSize; i++) { Serial.print(buffer[i] < 0x10 ? " 0" : " "); Serial.print(buffer[i], HEX); } } /** * Helper routine to dump a byte array as dec values to Serial. */ void printDec(byte *buffer, byte bufferSize) { for (byte i = 0; i < bufferSize; i++) { Serial.print(buffer[i] < 0x10 ? " 0" : " "); Serial.print(buffer[i], DEC); } }

Für den STM32 sind keine Pins angegeben, an denen das Modul angeschlossen werden soll. Aber die Standard-SPI-Bus-Pins des STM32 funktionieren wunderbar, wie das Ergebnis zeigt. Die Definitionen für SS_PIN und RST_PIN habe ich entsprechend dem Anschluss angegeben.

Die Ausgabe auf dem seriellen Port sieht dann entsprechend so aus: This code scan the MIFARE Classsic NUID. Using the following key: FF FF FF FF FF FF PICC type: MIFARE 1KB A new card has been detected. The NUID tag is: In hex: D5 C0 9F F0 In dec: 213 192 159 240 PICC type: MIFARE 1KB Card read previously. PICC type: MIFARE 1KB A new card has been detected. The NUID tag is: In hex: CC 14 C8 73 In dec: 204 20 200 115 PICC type: MIFARE 1KB A new card has been detected. The NUID tag is: In hex: D5 C0 9F F0 In dec: 213 192 159 240 Dabei habe ich zuerst Tag1 vor den Reader gehalten (A new card has been detected). Dann nochmal Tag1 (Card read previously), dann Tag2 (new card) und wieder Tag1 (new card).

Kurze Erklärung des Sources: Im Loop Teil wird dann wiederholt Es gibt noch mehr Beispiele, in die man sich ggf. einarbeiten kann, um weitere Funktionen des Readers zu nutzen: AccessControl ChangeUID Dumpinfo firmware_check FixBrickedUID MifareClassicValueBlock MinimalIinterrupt Ntag216_AUTH ReadAndWrite ReadNUID ReadUidMultiReader RFID-Cloner rfid_default_keys rfid_read_personal_data rfid_write_personal_data

Speicheraufbau des NXP Mifare S50 1K Classic RFID-Chips

Mit dem Beispiel Dumpinfo etwa kann man die gesamten Daten auf einem Tag lesen und ausgeben. Dazu muss man den Tag ungefähr eine Sekunde lang an den Reader halten, damit alle Daten vom Tag übertragen werden können: Firmware Version: 0x92 = v2.0 Scan PICC to see UID, SAK, type, and data blocks... Card UID: D5 C0 9F F0 Card SAK: 08 PICC type: MIFARE 1KB Sector Block 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 AccessBits 15 63 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 62 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 14 59 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 58 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 57 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 56 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 13 55 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 53 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 52 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 12 51 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 49 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 11 49 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 46 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 45 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 44 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 10 41 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 9 39 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 38 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 36 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 8 35 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 7 31 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 29 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 6 27 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 26 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 25 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 24 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 5 23 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 21 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 4 19 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 17 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 16 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 3 15 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 2 11 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 9 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 1 7 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 4 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 0 3 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF [ 0 0 1 ] 2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 1 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 0 0 0 ] 0 D5 C0 9F F0 7A 08 04 00 02 73 07 3A 49 CE DB 1D [ 0 0 0 ] Key A (5 Bytes) Access Bits Key B (6 Bytes) UID (4 B) Herstellerdaten Ich habe eine Mifare 1K Classic S50 Schlüsselanhänger Tag (gibt es auch als Blanko-Karte) an den Reader gehalten, ein sehr verbreiteter RFID-Chip mit 1024 (1K) Bytes Speicher. Diese sind in 64 (0-63) Blocks je 16 Bytes unterteilt, die wiederum zu je 4 Blocks in 16 Sektoren (0-15) organisiert sind.

Den Dump sollte man von unten nach oben lesen. Sektor 0, Block 0 ist (normalerweise) nur lesbar und enthält die sogenannten Herstellerdaten (Manufacturer block) und hier als erste 4 Bytes die UID des Chips. Über den Rest der Bedeutungen schweigt sich das Datenblatt aus, aber es ist davon auszugehen, dass hier Dinge wie Chip-Typ, Versionsnummer, Herstellungsdatum etc. abgelegt sind.

Aber auch sonst stehen einem nicht der gesamter Speicher der Karte zur Verfügung. Denn der Block am Ende eines jeden Sektors ist mit dem Schlüssel A, dem Schlüssel B und den Zugriffsbits belegt abgelegt. So bleiben nur 64 Blöcke minus 16 Blöcke für Schlüssel minus 1 Block für Herstellerdaten gleich 47 freie Blöcke zu 16 Bytes gleich 752 Bytes zur freien Verfügbarkeit zum Speichern von Daten.

Auch hier ist uns das Datenblatt zum MF1S50-Chip von NXP eine große Hilfe. Dort steht drin, auf welche Art man sich gegenüber dem Chip authentifizieren muss, will man einen Block lesen - kleiner Tipp: dazu dienen die Schlüssel.

Sicher ist das Protokoll für alle NXP Mifare Chips ähnlich. Dennoch muss man für je jeden Chip das Datenblatt studieren, um den Zugriff richtig zu implementieren. Zum Glück nimmt uns die Library diesen Teil der Arbeit ab und wir müssen uns nur den Teil aus den Beispielen herausfischen, der uns interessiert. Wer ein tiefergehendes Verständnis über die RFID-Chips und die Übertragungsprotkolle hat, dem steht natürlich frei, die Datenblätter genauer durchzulesen.

Vielleicht nur soviel: Es gibt pro Sektor auf dem RFID-Chip zwei Keys: A und B. Für Key A und B gibt es je einen Satz Access-Bits, die angeben, was für Zugriffe mit den jeweiligen Keys erlaubt sind. Ohne die richtigen Keys kann man normalerweise nicht auf die Daten in einem Sektor zugreifen. Normalerweise deshalb, weil etwa Mifare classic Sicherheitslücken aufweisen, die ausgenutzt weren können, um dies zu umgehen. Der Default Key, mit dem die Karten und Tags vom Werk her ausgeliefert werden ist 0x FF FF FF FF FF FF. Der Herausgeber der Kartenanwendung denkt sich dann Keys aus (pro Anwendung oder auch pro Benutzer) und vergibt die Zugriffsrechte, so dass sie nicht von jedem x-Beliebigen einfach so gelesen werden können. Die UID ist hingegen immer auslesbar.

Die NXP Mifare S70 (Ev1 4K Classic), die der RC522-Reader auch lesen kann, hat übrigens eine andere Speicheraufteilung: Sektor 0-31: 32 Sektoren à 4 blocks à 16 Bytes = 32 x 4 x 16 = 2048 Bytes Sektor 31-39: 8 Sektoren à 16 blocks à 16 Bytes = 8 x 16 x 16 = 2048 Bytes Von den 4096 brutto müssen noch abgezogen werden: Sektor 0-31: 32 Sektoren à 1 Schlüsselblock à 16 Bytes = 32 x 16 = 512 Bytes Sektor 31-39: 8 Sektoren à 1 Schlüsselblock à 16 Bytes = 8 x 16 = 128 Bytes Block 0 ist unbeschreibbar: 16 Bytes Verwaltungsbytes: 656 Bytes Bleiben also noch 3440 Bytes, in denen man frei Daten speichern kann. Die unterschiedlichen Sektorgrößen sind auf den ersten Blick vielleicht ein wenig verwirrend, bieten aber den Vorteil, dass die ersten 32 Sektoren das gleiche Format haben wie eine S50 (1K) mit 16 Sektoren zu 4 Blöcken und eine imaginäre S60 (2K) mit 32 Sektoren zu 4 Blöcken, wobei es die S60 wohl nie gab.

Für unser Projekt wollen wir nur die UID auslesen und für ein paar Sekunden auf dem OLED anzeigen. Wurde ein RFID-Tag gelesen, wird die kleine Sekunden Anzeige mit Auslesezeit und UID des letzten Tags ersetzt.

Source-Code

Der Source-Code dazu sieht wie folgt aus. DateTime.h und RTCInterface.h bleiben gleich und können aus dem letzten Projekt übernommen werden.

Defines.h wird um RFID-Elemente erweitert auf:

defines.h (klicken, um diesen Abschnitt aufzuklappen)
#define PinPowerLED PC13 // grüne LED onBoard #define PinSCL PB6 // I2C-Bus für OLED #define PinSDA PB7 #define OLED_RESET 4 #define SS_PIN PA4 // MFRC522 #define RST_PIN PB0 // MFRC522

oled-stm32.ino wird zu rfid-oled-stm32.ino und erweitert auf:

rfid-oled-stm32.ino (klicken, um diesen Abschnitt aufzuklappen)
//////////////////////////////////////////////////////// // (C) 2020 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// #include <libmaple/libmaple.h> #include <libmaple/bkp.h> #include <RTClock.h> // uses https://github.com/rogerclarkmelbourne/Arduino_STM32/tree/master/STM32F1/libraries/RTClock #include "Defines.h" #include "DateTime.h" #include "RTCInterface.h" #include <Wire.h> // I2C #include <Adafruit_GFX.h> // für das OLED #include <Adafruit_SSD1306_STM32.h> // für das OLED #include <SPI.h> // SPI #include <MFRC522.h> // für den RC522-RFID-Reader // den genauen 32768 Hertz-Timer für die Uhr benutzen (LSE), // auch ist nur der LSE durch VBat abgesichert RTClock rtclock (RTCSEL_LSE); // globale Variablen ////////////// DateTime rtcdat; // RTC-Zeit Adafruit_SSD1306 oled(OLED_RESET); // OLED-Objekt MFRC522 rfid(SS_PIN, RST_PIN); // RC522-RFID-Reader instanziieren // für UID nicht nötig MFRC522::MIFARE_Key rfidKey; // RIFD-Schlüsselspeicher reservieren byte rfidUID[4]; // 4 Bytes für die RFID-UID void setup() { char buf[25]; // zum zusammenbasteln der Ausgaben Serial.begin(115200); SPI.begin(); // SPI bus starten rfid.PCD_Init(); // MFRC522-Library initialisieren /* for (byte i = 0; i < 6; i++) { // rfidKey mit 0xFFFFFFFFFFFF (Default Key) vorbelegen rfidKey.keyByte[i] = 0xFF; } */ oled.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3D (for the 128x64) oled.clearDisplay(); oled.display(); // evtl. zufälliges Muster aus Display löschen pinMode(PinPowerLED, OUTPUT); digitalWrite (PinPowerLED, LOW); // grüne OnBoard-LED aus delay (500); delay(1000); // auf die ser. Schnittstelle warten Serial.println (F("Compile-DateTime:")); Serial.println (__DATE__); Serial.println (__TIME__); DateTime compdat; compdat = getCompileDateTime(); storeDateTimeInString (compdat, buf, false); // Compile-Datum ausgeben Serial.println (buf); storeDateTimeInString (compdat, buf, true); // Compile-Datum mit Wochentag Serial.println (buf); uint64_t ts64; ts64 = getDateTimeAsUint64(compdat); // Compile-Datum und Zeit als Zahl Serial.println (ts64); uint32_t ts32; ts32 = getDateAsUint32(compdat); // Compile-Datum als Zahl Serial.println (ts32); ts32 = getTimeAsUint32(compdat); // Compile-Zeit als Zahl Serial.println (ts32); Serial.println (F("\nRTC-DateTime:")); rtcdat = getRtcDateTime(rtclock); storeDateTimeInString (rtcdat, buf, true); // RTC-Datum ausgeben Serial.println (buf); if ( getDateTimeAsUint64(rtcdat) < getDateTimeAsUint64(compdat)) { // RTC-Zeit aus der Vergangenheit, kann nicht stimmen // Kopilierungszeit in Echtzeituhr speichern setRtcDateTime (rtclock, compdat); // könnte eigentlich nochmal 10 Sekunden drauf Serial.println (F("RTC-Uhrzeit auf Kompilierungszeit gesetzt.")); // ansonsten mit der Zeit in der RTC weitermachen } // Für jede Uhr individuell, muss nach jedem völligen Stromausfall (auch VBat weg oder leer) neu gesetzt werden // calibrateRtc(1.04); // RTC geht um 1.04 Sekunden am Tag zu schnell // calibrateRtc(-8.0); // RTC geht um 8 Sekunden am Tag zu langsam } void loop() { DateTime rtcdat; char buf[25]; byte swHour = 0; byte swMin = 0; byte swSec = 0; uint32_t swStart = 0; uint32_t dur = 0; boolean swRuns = false; char rfidReadTime[10]; // speichert Zeit, wann letzter RFID-Tag gelesen boolean rfidRead = false; // speichert, ob schon ein RFID-Tag gelesen time_t tt; // Sekunden seit 01.01.1970 while (1) { if ( Serial.available() > 14 ) { // 20190515-091017 // setzen der Uhrzeit per Ser-Schnittstelle möglich machen DateTime serdat; for (byte i = 0; i < 15; i++) { buf[i] = Serial.read(); } Serial.flush(); buf [15] = 0; sscanf (buf, "%4d%2d%2d-%2d%2d%2d", &serdat.year, &serdat.month, &serdat.day, &serdat.hour, &serdat.minute, &serdat.second); setRtcDateTime (rtclock, serdat); } //tt = rtclock.getTime(); //Serial.println(tt); rtcdat = getRtcDateTime(rtclock); //storeDateTimeInString (rtcdat, buf, true); // RTC-Datum mit Wochentag //Serial.println (buf); // RFID-Leser überprüfen if (rfid.PICC_IsNewCardPresent()) { // Tag liegt an Serial.println(F("\nNew RFID Tag")); if (rfid.PICC_ReadCardSerial()) { // Tag lesen digitalWrite (PinPowerLED, LOW); // während des Lese-Vorgangs grüne Power-LED an // Chip-Typ lesen und ausgeben MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak); Serial.print(F("RFID Chip-Typ: ")); Serial.println(rfid.PICC_GetTypeName(piccType)); // Ist der Chip-Typ mit der Library kompatibel? if (piccType != MFRC522::PICC_TYPE_MIFARE_MINI || piccType != MFRC522::PICC_TYPE_MIFARE_1K || piccType != MFRC522::PICC_TYPE_MIFARE_4K) { // RFID-UID übertragen for (byte i = 0; i < 4; i++) { rfidUID[i] = rfid.uid.uidByte[i]; } Serial.print(F("UID of the tag is: 0x ")); for (int i=0; i<4; i++) { snprintf (buf, 20, "%02X ", rfidUID[i]); Serial.print(buf); } Serial.println(); rfidRead=true; snprintf (rfidReadTime, 9, "%02d:%02d", rtcdat.hour, rtcdat.minute); delay (100); // LED ein wenig an lassen, sonst sieht man nichts digitalWrite (PinPowerLED, HIGH); // RFID für kurze Zeit groß auf OLED anzeigen oled.clearDisplay(); oled.setCursor(0,0); oled.setTextSize(4); snprintf (buf, 20, "%02X %02X", rfidUID[0], rfidUID[1]); oled.print(buf); oled.setCursor(0,32); snprintf (buf, 20, "%02X %02X", rfidUID[2], rfidUID[3]); oled.print(buf); oled.display(); delay(5000); } else { Serial.println(F("Chip-Typ inkompatibel.")); } //Serial.println(F("Tag konnte nicht korrekt gelesen werden.")); } } digitalWrite (PinPowerLED, HIGH); // LED wieder aus // OLED STANDARDAUSGABE ////////////////// oled.clearDisplay(); oled.setTextSize(1); oled.setTextColor(WHITE); // Datum oled.setCursor(0,0); snprintf (buf, 20, "%s, %02d.%02d.%04d", wochentage[rtcdat.weekday-1], rtcdat.day, rtcdat.month, rtcdat.year); oled.print(buf); // Uhrzeit oled.setCursor(0,12); oled.setTextSize(4); snprintf (buf, 20, "%02d:%02d", rtcdat.hour, rtcdat.minute); oled.print(buf); // Sekundenbalken oled.fillRect(3,45, rtcdat.second*2, 6, WHITE); // Sekunden oled.setCursor(0,55); oled.setTextSize(1); if (rfidRead) { // wenn einen Tag gelesen, in der Sekundenzeile Info dazu anzeigen snprintf (buf, 20, "%s %02X %02X %02X %02X", rfidReadTime, rfidUID[0], rfidUID[1], rfidUID[2], rfidUID[3] ); oled.println(buf); } else { snprintf (buf, 20, "%2d Sekunden", rtcdat.second); oled.println(buf); } oled.display(); // ohne display() keine Anzeige // digitalWrite (PinPowerLED, !digitalRead(PinPowerLED)); // ggf. PowerLED blinken lassen delay(200); } }

Die großzügig verteilten Kommentare sollten klarmachen, um was es jeweils geht und wie der Code funktioniert.

Eine Momentaufnahme der Schaltung und der OLED-Anzeige:



Und hier noch ein Video, das die Funktionsweise demonstriert:



Im nächsten Teil des Projektes wollen wir es um einen SD-Karten-Leser/Schreiber erweitern, um damit zu protokollieren, wann welche Karte aufgelegt wurde.