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

Dieses Projekt setzt auf dem letzten Projekt RC522-RFID-Kartenleser und OLED-Display (128x64 px, 0.96") an STM32 auf, das wiederum auf das vorletzte Projekt OLED-Display (128x64 px, 0.96") an STM32 betreiben aufsetzt.

Indem wir nun auch einen SD-Kartenleser anschließen, mit dem wir die RFID-Lese-Vorgänge auf MicroSD-Card protokollieren, schließen wir das Gesamtprojekt ab.

Immer, wenn ein RFID-Tag angehalten wird, soll eine Log-Datei Datum, Uhrzeit (auf die Minute genau) und RIFD-UID in eine Log-Datei auf der SD-Karte schreiben.

Die auf der SD-Karte gespeicherten Daten können wir dann am PC auswerten. Ein paar Anwendungsideen:

Micro SD-Kartenleser


Die MicroSD Card Adapter Module sind per SPI anzubinden, nehmen Micro-SD-Karten auf und kosten nur wenige Euro - wie immer direkt aus China mit langer Wartezeit günstiger. Die noch nötigen MicroSD-Karten bekommt man mittlerweile in jedem besseren Supermarkt.

Meine Version ist von Catalex, aber Prinzip sind die Module alle baugleich. Mein Verkäufer preist das Modul so an: The module (MicroSD Card Adapter) is a Micro SD card reader module, and the SPI interface via the file system driver, microcontroller system to complete the MicroSD card read and write files. Arduino users can directly use the Arduino IDE comes with an SD card to complete the library card initialization and read-write Module features are as follows: 1. Support Micro SD Card, Micro SDHC card (high-speed card) 2. the level conversion circuit board that can interface level is 5V or 3.3V 3. power supply is 4.5V ~ 5.5V, 3.3V voltage regulator circuit board 4. Communication interface is a standard SPI interface 5. 4 M2 screw positioning holes for easy installation Control Interface: A total of six pins (GND, VCC, MISO, MOSI, SCK, CS), GND to ground, VCC is the power supply, MISO, MOSI, SCK is the SPI bus, CS is the chip select signal pin; 3.3V regulator circuit: LDO regulator output 3.3V as level converter chip, Micro SD card supply; Level conversion circuit: Micro SD card into the direction of signals into 3.3V, MicroSD card toward the direction of the control interface MISO signal is also converted to 3.3V, general AVR microcontroller system can read the signal; Micro SD card connector: yes since the bomb deck for easy card insertion and removal. Positioning holes: 4 M2 screws positioning hole diameter of 2.2mm, the module is easy to install positioning, to achieve inter-module combination; Zu beachten ist, das es die Module schon lange gibt und sie für die 5V des Arduino Uno ausgelget sind. Auf Ihnen ist deswegen ein Spannungswandler verbaut, der aus den 5V des Arduino 3.3V macht, die der Chip braucht. Legen wir also nur 3.3V an das Modul an, bekommt der Chip nur 2.5V durch die Spannungswandlung. Wir müssen also die 5V Schiene der Blue-Pill anschließen, damit der Chip korrekt mit 3.3V versorgt wird. Alternativ könnte man natürlich auch den Sppanungswandler-Chip (AMS 1117) auslöten und wenn das breite Lötpad unten ist, dort GND direkt anlöten und oben rechts die 3.3V. Das würde ich aber nur tun, wenn ich keine 5V zur Verfügung hätte, also wenn ich den STM32 mit 3.3V über VIN versorge.

Anschluss des SD Card Readers an den STM32

Normalerweise könnte man den SD-Card Reader parallel zum RFID-Read am SPI-Bus 1 laufen lassen und man müsste nur zwei SS-Leitungen (SDA bei RFID-Reader und CS beim SD-Reader) anschließen, die man beim SPI-Init übergibt und es sollte funktionieren, dass beide Geräte am selben Bus hängen. "Sollte"! Denn ein Modul allein funktioniert einwandfrei, auch funktioniert der SD-Reader, wenn die RFID-Platine eingesteckt ist. Aber das RFID-Modul funktioniert nicht, sobald der SD-Card-Reader eingesteckt ist.

Der zweite SPI-Bus des STM32 wäre noch frei und das eine Möglichkeit, dass sich die beiden am SPI-Bus hängenden Module nicht ins Gehege kommen (was sie therotisch nicht sollten, praktisch aber tun, evtl. ist in einem Modul das SPI-Protokoll allzu egoistisch implementiert?).

Uns bleibt also nichts weiteres übrig, als den zweiten SPI-Port des STM32 zu benutzen. Die Pins entnehmen wir wieder dem GPIO-Pinout des STM32. Der 2. SPI-Bus beim STM32 auf PB12 bis PB15:



Auf dem Board heißt der SS-Pin CS (Client Select). Der Rest wird wie bezeichnet angeschlossen: SD-Card-Modul STM32 Kabelfarbe auf Foto SDA SS2 (PB12) blau SCK SCK2 (PB13) orange MOSI MOSI2 (PB15) grau MISO MISO2 (PB14) lila VCC 5V GND GND

Library einbinden und testen


Normalerweise würde ich ja die SD-Karten-Lib von Arduino / Sparkfun, die über eine gute Dokumentation verfügt empfehlen. Diese ist mittlerweile auch zur Standard-Library "geadelt" worden.

Und ich würde sie auch verwenden, wenn ich sonst nichts am SPI-Bus hängen hätte. Auf den RFID-Reader kann ich in diesem Projekt aber nicht verzichten und so kommt diese Library hier in diesem Fall in Frage.


Stattdessen gehe ich auf Library SdFat by Bill Greiman zurück, auf der die obere Library basiert.

Die alte Version hat den Vorteil, dass hier noch in die Breite gedacht wurde und sich z. B. auch ein Beispiel (namens STM32Test) findet, dass aich mit dem STM32 beschäftigt und noch dazu mit dem 2. SPI-Port

Auch diese hat eine Dokumentation, die man im Library-Folder der SdFat-Library im Extras/Html-Ordner findet, bei mir ist das z. B. file:///D:/ARDUINO/libraries/SdFat/extras/html/index.html

Beispiel-Source mit den wichtigsten Funktionen

Ich habe die Beispiel-Datei mal ein wenig angepasst und die wichtigsten Funktionen darin beispielsweise aufgerufen: Die Ergebnisse werden über den seriellen Port ausgegeben: FreeStack: 15756 ------sd2 root------- tyrian/ Dir2/ odroid/ roms/ downloads/ romart/ data/ duke3d/ python/ mp3/ pics/ mods/ WIFI.TXT --------------------- Writing to test.txt...done. ------sd2 Dir2------- 2000-01-01 01:00:00 18 test.txt --------------------- test.txt: testing 1, 2, 3. --------------------- deleting test.txt...done. ------sd2 Dir2------- --------------------- Done

Beispiel-Source SPI2 / Grundfunktionen

sd-sdfat-spi2-stm32.ino (klicken, um diesen Abschnitt aufzuklappen)
/* * Example use of two SPI ports on an STM32 board. * Note SPI speed is limited to 18 MHz. */ #include <SPI.h> #include "SdFat.h" // #include "FreeStack.h" // set ENABLE_EXTENDED_TRANSFER_CLASS non-zero to use faster EX classes // Use second SPI port SPIClass SPI_2(2); // leitet ein Objekt SPI_2 von der SPI-Klasse für den SPI Port 2 (2) ab SdFat sd2(&SPI_2); // wir benutzen SdFat mit SPI_2 und instanziieren und ein sd2-Objekt, mit dem wir arbeiten // SdFatEX sd2(&SPI_2); // Die Karte ist 32 GB groß und FAT32 formatiert, dafür reicht das normale FAT const uint8_t SD2_CS = PB12; // chip select for sd2 / SPI2 /*const uint8_t BUF_DIM = 100; uint8_t buf[BUF_DIM]; const uint32_t FILE_SIZE = 1000000; const uint16_t NWRITE = FILE_SIZE/BUF_DIM; */ File myFile; //------------------------------------------------------------------------------ // print error msg, any SD error codes, and halt. // store messages in flash #define errorExit(msg) errorHalt(F(msg)) #define initError(msg) initErrorHalt(F(msg)) //------------------------------------------------------------------------------ void setup() { Serial.begin(115200); delay(1000); //while (!Serial) ; // auf Serial warten /* // fill buffer with known data for (size_t i = 0; i < sizeof(buf); i++) { buf[i] = i; }*/ /* Serial.print(F("FreeStack: ")); Serial.println(FreeStack()); */ // initialize the second card if (!sd2.begin(SD2_CS, SD_SCK_MHZ(18))) { sd2.initError("sd2:"); } // create Dir2 on sd2 if it does not exist if (!sd2.exists("/Dir2")) { if (!sd2.mkdir("/Dir2")) { sd2.errorExit("sd2.mkdir"); } } // list root directory Serial.println(F("------sd2 root-------")); sd2.ls(); // make /Dir2 the default directory for sd2 if (!sd2.chdir("/Dir2")) { sd2.errorExit("sd2.chdir"); } // in /Dir2 eine etwas in eine test.txt-Datei schreiben (ANHÄNGEN, wird bei jedem Durchlauf länger) Serial.println(F("---------------------")); // open the file. note that only one file can be open at a time, // so you have to close this one before opening another. myFile = sd2.open("test.txt", FILE_WRITE); // if the file opened okay, write to it: if (myFile) { Serial.print("Writing to test.txt..."); myFile.println("testing 1, 2, 3."); // close the file: myFile.close(); Serial.println("done."); } else { // if the file didn't open, print an error: Serial.println("error opening test.txt"); } // list current directory (Dir2) Serial.println(F("------sd2 Dir2-------")); //sd2.ls(); sd2.ls("/Dir2", LS_DATE | LS_SIZE); // LS_R | recursive Serial.println(F("---------------------")); // test.txt lesen und inhalt ausgeben // re-open the file for reading: myFile = sd2.open("test.txt"); if (myFile) { Serial.println("test.txt:"); // read from the file until there's nothing else in it: while (myFile.available()) { Serial.write(myFile.read()); } // close the file: myFile.close(); } else { // if the file didn't open, print an error: Serial.println("error opening test.txt"); } Serial.println(F("---------------------")); Serial.print("deleting test.txt..."); // remove test.txt from /Dir2 directory of sd2 if (sd2.exists("test.txt")) { if (!sd2.remove("test.txt")) { sd2.errorExit("remove test.txt"); } } Serial.println("done."); // list current directory (Dir2) Serial.println(F("------sd2 Dir2-------")); //sd2.ls(); sd2.ls("/Dir2", LS_DATE | LS_SIZE); // LS_R | recursive Serial.println(F("---------------------")); Serial.println(F("Done")); } //------------------------------------------------------------------------------ void loop() {} // loop bleibt leer, wir wollen das nur 1x ausführen.

Der Source hatte einige, überflüssige Zeilen. Die stehen auskommentiert im Code, sollten sie doch noch einmal wichtig werden. Die orignialen Kommentare (englisch) habe ich durch eigene (deutsch) erweitert, um den Code noch verständlicher zu machen.

Die "Magie" SPI2 nutzen zu können, liegt daran, dass wir die Klasse mit einem beliebigen SPI-Objekt benutzen können, indem wir dessen Zeiger (Pointer) übergeben. SPIClass SPI_2(2); // leitet ein Objekt SPI_2 von der SPI-Klasse für den SPI Port 2 (2) ab SdFat sd2(&SPI_2); // wir benutzen SdFat mit SPI_2 und instanziieren ein sd2-Objekt, mit dem wir dann arbeiten Bill Greiman, der Autor der Library schreibt dazu in seinem Readme / Changelog (Updated 28 Dec 2018):
The Arduino SdFat library provides read/write access to FAT16/FAT32 file systems on SD/SDHC flash cards.

SdFat requires Arduino 1.6x or greater.

Key changes:

Support for multiple SPI ports now uses a pointer to a SPIClass object.

See the STM32Test example.
Das ist genau die Flexibilität, die wir benötigen, um den RFID-Reader an SPI1 und den SD-Karten-Leser an SPI2 parallel benutzen zu können.

Erweiterung der RFID-Reader-Anwendung

Nun ist der Weg frei, unser Projekt um eine Protokollierung auf SD-Karte zu erweitern und damit zu vollenden.

Dazu wollen wir im Verzeichnis /logs/rfid jeweils Ordner mit dem Jahr, darunter Ordner mit dem Monat und darunter Log-Dateien für jeden Tag anlegen, die alle Logeinträge für den betreffenden Tag enthalten. So bringen wir etwas Ordnung in die Sache, haben nicht tausende Dateien in einem Ordner und keine gigabytegroße Dateien. Was wann an einem Tag los war, lässt sich so auch sehr schnell finden.

Die Dateinamen der Logfiles selbst sollen eindeutig sein und folgendermaßen aufgebaut sein rfid-[jahr]-[monat]-[tag].log. Zu finden im Verzeichnis /logs/rfid/[jahr]/[monat]/[tag].

Jeder Logeintrag (jede Zeile im Logfile) soll das Datum und die Uhrzeit (auf die Minute genau) beinhalten im Format "[jahr]-[monat]-[tag] [stunde]:[minute]" gefolgt von den 4 Bytes der UID im Hex-Format. Eine Zeile könnte also so aussehen: 2020-02-14 17:09 0d 0c c0 01 123456789012345678901234567890 Jede Zeile ist mit Zeilenendemarkierung (CR LF) also 30 Bytes lang. Erst wenn eine neue Minute angebrochen ist, wird eine neue Zeile geschrieben. Für einen 8-Stunden-Arbeitstag würde das also 8 Stunden mal 60 Minuten gleich 480 Einträge mal 30 Bytes = 14400 Bytes Speicherplatz pro Tag, wenn ständig irgendeine Karte aufliegt. Da reicht eine 32 GB Speicherkarte eine laaange Zeit.

Die geloggte Variablen werden immer mit gleichen Länge weggeschrieben, also mit führenden Nullen bei Stunde und Minute und bei der UID, so dass Import und Auswertung z. B. in Excel aufgrund der festen Grenzen einfach und fehlerfest ist.



Beim Kompilieren des untenstehenden Codes zeigt sich, dass die 3 Libraries doch reichlich Speicher in Anspruch nehmen. Ein 64 KB-STM32 reicht für unseren Sketch nicht mehr aus, von einem Arduino ganz zu schweigen: Der Sketch verwendet 74364 Bytes (56%) des Programmspeicherplatzes. Das Maximum sind 131072 Bytes. Globale Variablen verwenden 6992 Bytes (34%) des dynamischen Speichers, 13488 Bytes für lokale Variablen verbleiben. Das Maximum sind 20480 Bytes. Beim Austesten ist mir aufgefallen, dass die RFID-Karte vor dem Reader immer etwas hin und herbewegt werden muss, um wiederholt erkannt zu werden (Anwendung Zeitmessung aufliegende Karte). In einer Orientierung reicht es aber auch, die im 30° Winkel schräg gegen den Leser zu lehnen, um immer wieder erkannt zu werden, ohne bewegt zu werden. Das müsste man beim Entwurf eines Gehäuses bedenken.

Die Debug-Ausgabe auf der seriellen Konsole sieht wie folgt aus: Compile-DateTime: Feb 15 2020 12:02:44 2020-02-15 12:02:44 Sat, 2020-02-15 12:02:44 20200215120244 20200215 120244 RTC-DateTime: Sat, 2020-02-15 12:02:40 RTC-Uhrzeit auf Kompilierungszeit gesetzt. SD-Root: 2000-01-01 01:00:00 0 logs/ 2000-01-01 01:00:00 0 rfid/ 2000-01-01 01:00:00 0 2020/ 2000-01-01 01:00:00 0 02/ 2000-01-01 01:00:00 435 rfid-2020-02-15.log RFID Tag found at 12:02:44 RFID Chip-Typ: MIFARE 1KB UID of the tag is: 0x 0D 0C C0 01 Ergänze Log rfid-2020-02-15.log mit 2020-02-15 12:02 0d 0c c0 01 RFID Tag found at 12:02:49 RFID Chip-Typ: MIFARE 1KB UID of the tag is: 0x 0D 0C C0 01 RFID Tag found at 12:02:58 RFID Chip-Typ: MIFARE 1KB UID of the tag is: 0x 0D 0C C0 01 RFID Tag found at 12:03:04 RFID Chip-Typ: MIFARE 1KB UID of the tag is: 0x 0D 0C C0 01 Ergänze Log rfid-2020-02-15.log mit 2020-02-15 12:03 0d 0c c0 01 RFID Tag found at 12:03:09 RFID Chip-Typ: MIFARE 1KB UID of the tag is: 0x 0D 0C C0 01 Als erstes wird wie schon im Projekt zuvor die Zeit ausgegeben. Danach erfolgt eine rekursive Ausgabe der Verzeichnisse der SD-Karte. Jedes mal, wenn ein RFID-Chip erkannt wird, erfolgt eine Ausgabe dessen Typs, UID und der genauen Zeit. Die UID wird auch auf dem OLED für 5 Sekunden angezeigt.

Wenn die UID oder die Minute anders ist als die zuletzt geloggte, dann wird das Logfile um eine Zeile ergänzt in dem oben angegebenen Format. Auch das wird auf dem ser. Port ausgegeben.

Source-Code

DateTime.h und RTCInterface.h bleiben gleich und können aus dem letzten Projekt übernommen werden.

Defines.h wird um SD-Elemente erweitert auf: #define PinPowerLED PC13 // grüne LED onBoard #define PinPowerLED PC13 // grüne LED onBoard #define PinSCL PB6 // I2C-Bus für OLED #define PinSDA PB7 #define OLED_RESET 4 #define RFID_SS_PIN PA4 // MFRC522 #define RFID_RST_PIN PB0 // MFRC522 #define SD2_CS_PIN PB12 // SD-Reader chip select for sd2 / SPI2 rfid-oled-stm32.ino wird zu rfid-oled-sd-stm32.ino und erweitert auf:

rfid-oled-sd-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 (SPI1) #include "SdFat.h" // für SD-Kartenleser (auf SPI2) // 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(RFID_SS_PIN, RFID_RST_PIN); // RC522-RFID-Reader instanziieren // MFRC522::MIFARE_Key rfidKey; // RIFD-Schlüsselspeicher reservieren byte rfidUID[4]; // 4 Bytes für die RFID-UID SPIClass SPI_2(2); // leitet ein Objekt SPI_2 von der SPI-Klasse für den SPI Port 2 (2) ab SdFat sd2(&SPI_2); // wir benutzen SdFat mit SPI_2 und instanziieren ein sd2-Objekt, mit dem wir dann arbeiten File myFile; void setup() { char buf[25]; // zum zusammenbasteln der Ausgaben Serial.begin(115200); SPI.begin(); // SPI bus starten SPI.beginTransaction(RFID_SS_PIN); 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 if (!sd2.begin(SD2_CS_PIN, SD_SCK_MHZ(18))) { Serial.println (F("SD-Kartenleser nicht bereit.")); } else { Serial.println (F("SD-Root:")); sd2.ls("/", LS_DATE | LS_SIZE | LS_R); } } int rfidLog(byte *UID) { char fname [31]; char zeile [31]; char buf[20]; rtcdat = getRtcDateTime(rtclock); // aktuelle Zeit holen // Log-Verzeichnis anlegen, falls nicht vorhanden if (!sd2.exists("/logs")) { if (!sd2.mkdir("/logs")) return -10; } sd2.chdir ("/logs"); if (!sd2.exists("rfid")) { if (!sd2.mkdir("rfid")) return -20; } sd2.chdir ("rfid"); snprintf (buf, 5, "%04d", rtcdat.year); if (!sd2.exists(buf)) { if (!sd2.mkdir(buf)) return -30; } sd2.chdir (buf); snprintf (buf, 3, "%02d", rtcdat.month); if (!sd2.exists(buf)) { if (!sd2.mkdir(buf)) return -40; } sd2.chdir (buf); snprintf (fname, 30, "rfid-%04d-%02d-%02d.log", rtcdat.year, rtcdat.month, rtcdat.day); snprintf (zeile, 30, "%04d-%02d-%02d %02d:%02d %02x %02x %02x %02x\r\n", rtcdat.year, rtcdat.month, rtcdat.day, rtcdat.hour, rtcdat.minute, UID[0], UID[1], UID[2], UID[3]); Serial.print("Ergänze Log "); Serial.print(F(fname)); Serial.print(F(" mit ")); Serial.println(zeile); myFile = sd2.open(fname, FILE_WRITE); if (myFile) { myFile.print(zeile); // close the file: myFile.close(); } else { // if the file didn't open, print an error: Serial.print(F("Konnte nicht in ")); Serial.print(fname); Serial.println(F("schreiben")); } } 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 uint32_t thisUIDRead = 0; uint32_t lastUIDLogged = 0; uint16_t thisTimeRead = 0; uint16_t lastTimeLogged = 0; 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()) { // neues Tag liegt an if (rfid.PICC_ReadCardSerial()) { // Tag lesen Serial.print(F("\nRFID Tag found at ")); snprintf (buf, 20, "%02d:%02d:%02d", rtcdat.hour, rtcdat.minute, rtcdat.second); Serial.println(buf); 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(); thisUIDRead = rfidUID[0]*16777216 + rfidUID[1]*65536 + rfidUID[2]*256 + rfidUID[3]; thisTimeRead = rtcdat.hour*60 + rtcdat.minute; // wenn eine neue Minute angebrochen ist oder sich die UID verändert hat, loggen if (thisTimeRead != lastTimeLogged || thisUIDRead != lastUIDLogged) { rfidLog (rfidUID); //rtcDat muss nicht uebergeben werden, ist global lastTimeLogged = thisTimeRead; lastUIDLogged = thisUIDRead; } 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); } }

Der Source-Code sollte wie immer durch die Kommentare selbsterklärend sein.

Beispiel Auswertung Logdatei=SUMME(E2:E39)

Für die eingangs erwähnte Anwendung "Protokollierung von Arbeitspaketen zur Abrechnung von Dienstleistungen" wird die entstandene Logdatei SD:/logs/rfid/2020/02/rfid-2020-02-15.log am PC nach Excel importiert (Spalte A markieren, Text in Spalten, feste Breite, nur den Trenner zwischen Zeit und UID beibehalten). Dann kann man mit ein paar Excel-Funktionen die einzelnen Minuten zählen, die für die jeweiligen Kunden angefallen sind (=ZÄHLENWENN($B2;D$1)) und summieren (=SUMME(E2:E39)), um sie dann einfach einen Tag abrechnen können.

Will man einen ganzen Monat summieren, dann kopiert man einfach alle Dateien, die in einem Monat angefallen sind, zu einer großen zusammen und importiert die. Et Voila ist die Monatsabrechnung fertig.



Damit ist dieses Projekt abschlossen. Mit der Kombination OLED-Display zur Anzeige, RFID-Reader zum Lesen von RFID-Tags und SD-Reader zum Schreiben von Logfiles hat man eine Kombination an Hardware, mit denen sich viele Anwendungen realisieren lassen... es muss nur der Source angepasst werden.

Abgeschlossen bis auf eine Kleinigkeit. Ein gescheites, selbst entworfenes und mit dem 3D-Drucker gedrucktes Gehäuse darf natürlich auch nicht fehlen.