In diesem Tutorial möchte ich das SD Card Shield mit RealTimeClock für den Mikrocontroller Wemos D1 Mini vorstellen.
Auf der Vorderseite des Shields befindet sich der Kartenslot für die SD-Karte. Des Weiteren befindet sich auch hier ein kleiner Text, welcher auf die Pinbelegung hinweist (D1-SCL / D2-SDA).
Auf der Unterseite des Shield befindet sich die Halterung für eine Knopfzelle.
Bezug
Das Shield kann über ebay.de* bestellt werden. Auf amazon.de* gibt es das Shield leider nur ohne RTC DS1307 Chip (stand 05.07.2018).
Für das Shield wird eine SD Card im Micro SD Card Format benötigt. Für die Beispiele wollte ich eine SD Card mit 16 GB nutzen jedoch wurde diese nicht erkannt somit musste ich auf eine 2 GB Karte wechseln, welche reibungslos erkannt wurde. Eine 2 GB SD-Karte gibt es Bsp. bei amazon.de* oder ebay.de* für wenig Geld.
Damit die RTC DS1307 die eingestellte Zeit nach einem Stromausfall behält, kann dieses Shield um eine Batterie im Format CR1220 ergänzt werden. Diese Batterie gibt es im Blister pack auch bei amazon.de* und bei ebay.de*.
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
Aufbau & Schaltung
Das SD Card Shield mit RealTimeClock belegt folgende Pins des Wemos D1 Mini:
SD Card
Pin | Funktion |
---|---|
D5 | CLK |
D6 | MISO |
D7 | MOSI |
D8 | CS |
Quelle: Micro SD Card Shield [WEMOS Electronics]
RTC DS1307
Pin | Funktion |
---|---|
D1 | SCL |
D2 | SDA |
Somit verbleiben zur freien Programmierung noch weitere 3 digitale Pins und 1 analoger Pin.
Quellcode
Auf der Wikiseite zum original Shield findet man einen Link zu einem GitHub Repository wo diverse Anwendungsfälle mit einem Quellcode (*.ino Datei) hinterlegt wurde.
Ich möchte mich hier auf das lesen und schreiben einer Datei beschränken, da ich dieses für ein paralleles Projekt benötige.
Beispiel – Daten auf der SD Card speichern
#include <SD.h> #define CS D8 void setup() { Serial.begin(9600); // CS-Pin als Ausgang konfigurieren pinMode(CS, OUTPUT); // SD-Karte initialisieren Serial.print("\r\nInitialisiere SD-Karte..."); if (!SD.begin(CS)) { Serial.println(" fehlgeschlagen!"); // Setup-Funktion verlassen return; } Serial.println(" fertig."); //Versucht die Datei "text.txt" zu im Modus FILE_WRITE zu öffnen. //Es gibt noch den Modus FILE_READ für nur lese Zugriff. //Wenn kein Modus angegeben wurde dann wird die Datei immer im FILE_READ Modus geöffnet. //Wenn die Datei "text.txt" nicht gefunden wurde dann wird diese erzeugt. File target = SD.open("text.txt", FILE_WRITE); //Wenn das File existiert dann.... if (target) { Serial.print("Datei 'text.txt' gefunden."); //Eine neue Zeile an die Datei anhängen. //Diese Zeile wird an das Ende der Datei angehängt. target.println("Test, Test. 1, 2, 3."); //Nach dem Schreiben nicht vergessen den Zugriff //auf die Datei zu schließen. target.close(); } else { //Wenn die Datei nicht geöffnet werden kann, dann soll eine Fehlermeldung //ausgegeben werden. //Ein möglicher Fehler kann sein dass, die Datei bereits durch einen anderen //Service geöffnet wurde. Serial.println("Fehler beim Öffnen von text.txt."); } } void loop() { /* Bleibt in diesem Beispiel leer. */ }
Beispiel – RTC DS1307
Das Shield verfügt wie schon erwähnt über eine RealTimeClock DS1307. Die RTC DS1307 habe ich bereits im Tutorial Arduino Lektion 27: Daten loggen mit dem Logging Shield behandelt.
#include "Wire.h" #define DS1307_I2C_ADDRESS 0x68 /* * Array mit den Wochentagbezeichnungen. * Die erste Stelle ist 0 da die Wochentage bei 1 für Sonntag beginnen. * Hier hätte man im Quellcode an der passenden Stelle auch den Zähler * um 1 verringern können jedoch wird darunter die lesbarkeit leiden. */ String daysOfWeek[8] = {"","Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"}; //Konvertiert eine normale Dezimalzahl zu einer binärcodierten Dezimalzahl. byte decToBcd(byte val){ return( (val/10*16) + (val%10) ); } //Konvertiert Binärcodierte Dezimalzahl zu einer //normalen Dezimalzahl. byte bcdToDec(byte val){ return( (val/16*10) + (val%16) ); } void setup(){ Wire.begin(); Serial.begin(9600); /* * Um die Uhrzeit & das Datum initial zu setzen, muss die * nachfolgende Zeile auskommentiert werden. * * Parameter der Funktion * setDS1307 - Sekunden, Minuten, Stunden, Tag der Woche, Tag im Monat, Monat, Jahr * Der "Tag der Woche" ist im Englischen Format d.h. Sonntag = 1, Montag = 2 usw. * Der Wert für das Jahr wird im 2 stelligen Bereich eingegeben, * dieser Code ist also noch für ca. 82 Jahre funktionsfähig. */ //setDS1307(00,21,8,6,6,7,18); } void setDS1307(byte second, byte minute, byte hour, byte dayOfWeek, byte dayOfMonth, byte month, byte year){ Wire.beginTransmission(DS1307_I2C_ADDRESS); Wire.write(0); Wire.write(decToBcd(second)); // setzt die Sekunden Wire.write(decToBcd(minute)); // setzt die Minuten Wire.write(decToBcd(hour)); // setzt die Stunden Wire.write(decToBcd(dayOfWeek)); // setzt den Wert für den Tag der Woche Wire.write(decToBcd(dayOfMonth)); // setzt den Wert für den Tag im Monat Wire.write(decToBcd(month)); // setzt den Monat Wire.write(decToBcd(year)); // setzt das Jahr Wire.endTransmission(); } void readDS1307time(byte *second, byte *minute, byte *hour, byte *dayOfWeek, byte *dayOfMonth, byte *month, byte *year){ Wire.beginTransmission(DS1307_I2C_ADDRESS); Wire.write(0); Wire.endTransmission(); Wire.requestFrom(DS1307_I2C_ADDRESS, 7); //Anfordern der ersten 7 Datenbyte vom DS1307 *second = bcdToDec(Wire.read() & 0x7f); *minute = bcdToDec(Wire.read()); *hour = bcdToDec(Wire.read() & 0x3f); *dayOfWeek = bcdToDec(Wire.read()); *dayOfMonth = bcdToDec(Wire.read()); *month = bcdToDec(Wire.read()); *year = bcdToDec(Wire.read()); } void displayTime(){ byte second, minute, hour, dayOfWeek, dayOfMonth, month, year; readDS1307time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year); //Ausgeben des Zeitstempels auf dem seriellen Ausgang. Serial.print(daysOfWeek[dayOfWeek]); Serial.print(" "); Serial.print(dayOfMonth, DEC); Serial.print("."); Serial.print(month, DEC); Serial.print("."); Serial.print(year, DEC); Serial.print(" "); Serial.print(hour<10?"0":""); Serial.print(hour, DEC); Serial.print(":"); Serial.print(minute<10?"0":""); Serial.print(minute, DEC); Serial.print(":"); Serial.print(second<10?"0":""); Serial.print(second, DEC); Serial.println(" "); } void loop(){ displayTime(); delay(1000); //kleine Pause von 1 Sekunde }
Beispiel – DHT11 Sensordaten mit Zeitstempel loggen
Da die Shields für den Wemos D1 mini untereinander kompatibel und vor allem Stapelbar sind möchte ich nun ein kleines Beispiel mit dem DHT11 Shield und dem SD Card Shield zeigen. Zusätzlich werde ich hier die RTC DS1307 welche auf Shield verbaut wurde verwenden, um einen Zeitstempel zu speichern.
Für dieses Beispiel verwende ich:
- Dual Base Shield,
- Datalogging Shield,
- DHT11 Shield,
- Wemos D1 mini
#include <SD.h> #include "Wire.h" #include "DHT.h" //DHT Bibliothek //Pin an welchem der DHT11 Sensor angeschlossen ist. //Beim DHT11 Shield ist es der digitale Pin D4. #define DHTPIN D4 //Festlegen welcher Typ von DHT Sensor verwendet wird. #define DHTTYPE DHT11 //Initialisieren des Sensors mit dem Anschluss und dem Typ DHT dht(DHTPIN, DHTTYPE); #define CS D8 #define DS1307_I2C_ADDRESS 0x68 String daysOfWeek[8] = {"","Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"}; //Konvertiert eine normale Dezimalzahl zu einer binärcodierten Dezimalzahl. byte decToBcd(byte val){ return( (val/10*16) + (val%10) ); } //Konvertiert Binärcodierte Dezimalzahl zu einer //normalen Dezimalzahl. byte bcdToDec(byte val){ return( (val/16*10) + (val%16) ); } void setup(){ Wire.begin(); Serial.begin(9600); pinMode(CS, OUTPUT); dht.begin(); //DHT Kommunikation beginnen. if (!SD.begin(CS)) { Serial.println("Verbindung zur SD Card fehlgeschlagen!"); // Setup-Funktion verlassen return; } setDS1307(00,41,11,6,6,7,18); } void storeData(String timestamp, float luftfeuchtigkeit, float temperaturC, float temperaturF){ String csvRow = getFormatedRow(timestamp, luftfeuchtigkeit, temperaturC, temperaturF,"","",";"); writeToFile(csvRow, "data.csv"); //String htmlTableRow = getFormatedRow(timestamp, luftfeuchtigkeit, temperaturC, temperaturF,"<tr><td>","</td></tr>","</td><td>"); //writeToFile(csvRow, "htmldata.html"); } String getFormatedRow(String timestamp, float luftfeuchtigkeit, float temperaturC, float temperaturF, String first, String last, String separator){ String row = first; row += timestamp; row += separator; row += luftfeuchtigkeit; row += separator; row += temperaturC; row += separator; row += temperaturF; row += last; return row; } void writeToFile(String data, String filename){ File dataFile = SD.open("data.csv", FILE_WRITE); Serial.println(data); if (dataFile) { dataFile.println(data); dataFile.close(); }else { Serial.print("Fehler beim öffnen der Datei ["); Serial.println(filename); Serial.println("]"); } } void setDS1307(byte second, byte minute, byte hour, byte dayOfWeek, byte dayOfMonth, byte month, byte year){ Wire.beginTransmission(DS1307_I2C_ADDRESS); Wire.write(0); Wire.write(decToBcd(second)); // setzt die Sekunden Wire.write(decToBcd(minute)); // setzt die Minuten Wire.write(decToBcd(hour)); // setzt die Stunden Wire.write(decToBcd(dayOfWeek)); // setzt den Wert für den Tag der Woche Wire.write(decToBcd(dayOfMonth)); // setzt den Wert für den Tag im Monat Wire.write(decToBcd(month)); // setzt den Monat Wire.write(decToBcd(year)); // setzt das Jahr Wire.endTransmission(); } void readDS1307time(byte *second, byte *minute, byte *hour, byte *dayOfWeek, byte *dayOfMonth, byte *month, byte *year){ Wire.beginTransmission(DS1307_I2C_ADDRESS); Wire.write(0); Wire.endTransmission(); Wire.requestFrom(DS1307_I2C_ADDRESS, 7); //Anfordern der ersten 7 Datenbyte vom DS1307 *second = bcdToDec(Wire.read() & 0x7f); *minute = bcdToDec(Wire.read()); *hour = bcdToDec(Wire.read() & 0x3f); *dayOfWeek = bcdToDec(Wire.read()); *dayOfMonth = bcdToDec(Wire.read()); *month = bcdToDec(Wire.read()); *year = bcdToDec(Wire.read()); } String getTimestamp(){ byte second, minute, hour, dayOfWeek, dayOfMonth, month, year; readDS1307time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year); String timestamp = ""; //Ausgeben des Zeitstempels auf dem seriellen Ausgang. timestamp += daysOfWeek[dayOfWeek]; timestamp +=" "; timestamp += String(dayOfMonth, DEC); timestamp += "."; timestamp += String(month, DEC); timestamp += "."; timestamp += String(year, DEC); timestamp += " "; timestamp += (hour<10?"0":""); timestamp += String(hour, DEC); timestamp += ":"; timestamp += (minute<10?"0":""); timestamp += String(minute, DEC); timestamp += ":"; timestamp += (second<10?"0":""); timestamp += String(second, DEC); return timestamp; } void loop(){ String timestamp = getTimestamp(); delay(2000); float luftfeuchtigkeit = dht.readHumidity(); float temperaturC = dht.readTemperature(); float temperaturF = dht.readTemperature(true); //Prüfen ob die Werte des DHT11 Sensors erfolgreich gelesen wurden. if (isnan(luftfeuchtigkeit) || isnan(temperaturC) || isnan(temperaturF)) { Serial.println("Fehler beim lesen von Daten."); return; } storeData(timestamp, luftfeuchtigkeit, temperaturC, temperaturF); delay(2500); }
Hallo Stefan,
sehr schöner Sketch, den ich auch als Einsteiger gut nachvollziehen kann.
Bei den Definitionen am Anfang verwendest du keine “RTClib.h” Lib, sondern sprichst die RTC direkt über die Adresse an (#define DS1307_I2C_ADDRESS 0x68). Wie kann bei dem Sketch das Logfile mit einem Timestamp als Dateiname erzeugt werden?
Mit besten Grüßen
Thomas
Wer Probleme mit diesem Modulen hat sollte das mal prüfen:
Bei den gängigen Modulen dieser Bauart funktioniert die Pufferung der Datum-/Zeiteinstellung nicht. Sie funktionieren, solange sie mit 5V über Vcc versorgt werden. Bei Unterbrechung der Stromzufuhr und nach dem Wiederanschließen starten sie wieder mit Sonntag 1.1.2000.
Auf der Platine gibt es eine Kombination aus Widerstand (2MOhm) und Diode, die Vbat auf definiertes Potential ziehen soll, wenn keine Batterie eingelegt ist (siehe Datenblatt DS1307).
Die Diode ist bei den gängigen China-Modulen (DS1307 + SD-Kartenslot, auch bei dem aus Ihrer Abbildung) verkehrt herum eingebaut!!! Bei Modulen ohne SD-Karte ist sie allerdings richtig eingebaut.
Ich hab’s kaum glauben können, aber es ist so. Die Batterie entlädt sich. Bei der ersten Batterie, die ich eingelegt habe, habe ich im Laufe der Untersuchungen nur noch 1,9V gemessen. Ok, die Batterie war nicht neu und lag schon eine Weile herum. Daraufhin habe ich eine neue gekauft und eingelegt.
Es funktionierte wieder nicht. Batterie entnommen und Spannung gemessen: 2,7V, das bei einer neu gekauften Batterie mit 3V Nennspannung??
Im Internet habe ich dann einen Hinweis gefunden, daß die besagte Diode defekt sein könne (https://sharedinventions.com/?p=663). Daraufhin habe ich mir das Modul angeschaut und mit einem funktionierenden (ohne SD-Karte!) verglichen und festgestellt, daß die Diode verkehrt herum eingelötet ist.
Diode (und Widerstand) entfernt, da ich das Modul nicht ohne Batterie betreiben will. Alles ok, funktioniert so wie es soll!
Alle Module dieser Bauart bei Ebay haben laut Photo diesen Fehler (Photos mit Gammaverschiebung aufhellen dann sieht man die Leiterbahnen).