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:
- Protokollierung von Arbeitspaketen zur Abrechnung von Dienstleistungen: Es werden pro Kunde oder auch Arbeitspaket/Kunde eine RFID-Karte mit dem Edding beschrieben. Immer wenn für einen Kunden gearbeitet wird, wird die Karte des Kunden aufgelegt. Später am PC wird ausgewertet, wieviele Minuten die Kundenkarte aufgelegt war, um so die Stunden minutengenau abzurechnen.
- Zugangskontrolle und Zeiterfassung: Dies erfordert 2 Schaltungen, eine ist außen vor der Tür angebracht ("kommt") und eine ist innen vor der Türangebracht ("geht"), die zu den Büros führt. Im Source oder auf der SD sind die UIDs hinterlegt, die Zugangsberechtigung haben. Der Tag muss an die Schaltung gehalten werden, um Zutritt zu erlangen: hier wird ein Relais ausgelöst, dass den Türöffner betätigt. Nun kann man sehen, wann ein Mitarbeiter gekommen und gegangen ist und wielange er da war. So kann man Kernzeitverletzungen, Überstunden etc. ausweisen. Oder man schickt dem Mitarbeiter eine e-mail/SMS, wenn er seine maximale Arbeitszeit überschritten hat und jetzt Feierabend machen muss.
- Öffnungsmechanismus: Nur eine Karte mit der richtigen UID öffnet die Tür oder das Schloss (Erweiterung mit Relais oder Servo-Motor). Gleichzeitig wird protokolliert, wer wann zugegriffen hat.
- Schachuhr, die protokolloert, wielange jeder Zug gedauert hat: statt bei Zugwechsel einen Knopf zu drücken legen die Spieler ihre RFID-Karte auf. So kann auch gleich überprüft werden, dass die beiden richtigen gegeneinander spielen.
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:/
Beispiel-Source mit den wichtigsten Funktionen
Ich habe die Beispiel-Datei mal ein wenig angepasst und die wichtigsten Funktionen darin beispielsweise aufgerufen:- Verbinden mit SPI2
- Root-Directory listen
- Ein Verzeichnis /Dir2 anlegen
- Eine Datei test.txt anlegen und mit einem Text beschreiben
- Das Dir2-Directory listen (inkl. Dateidatum und Größe, nun mit test.txt)
- Die test.txt Datei auslesen und den Inhalt ausgeben
- Die test.txt Datei löschen
- Das Dir2-Directory listen (nun wieder leer)
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.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.
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.
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.