In diesem Beitrag möchte ich dir zeigen, wie du Sensordaten am MAKER Pi Pico mit dem SD-Card Adapter auf einer entsprechenden Micro SD-Card speichern kannst.
Im letzten Beitrag habe ich dir gezeigt wie du Sensordaten an den IoT Service ThingSpeak senden kannst, wenn du aber einmal keine WiFi Verbindung hast oder aber dein eigenes Dashboard erstellen möchtest dann kannst du mit diesem Feature deine Daten sicher zwischenspeichern.
Den MAKER Pi Pico selber, habe ich dir bereits im Beitrag Maker Pi Pico von Cytron vorgestellt.
Benötigte Ressourcen zum Nachbau
Möchtest du die Beispiele aus dem Beitrag nachbauen, so benötigst du folgende Ressourcen:
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!
Schaltung & Aufbau
In diesem Beitrag verwende ich den DHT11 Sensor mit Grove Schnittstelle. Der DHT11 Sensor hat den Vorteil das diese zwei Werte liefert (Temperatur, rel. Luftfeuchtigkeit), jedoch den Nachteil das dieser nicht zuverlässig Werte liefert bzw. das Auslesen nicht zuverlässig funktioniert.
Pinout des SD-Card Adapters
Der MAKER Pi Pico hat einen SD-Card Adapter onBoard d.h. wir müssen uns nicht zusätzlich ein Modul besorgen und ggf. umständlich anschließen.
Wenn du “nur” den Raspberry Pi Pico verwenden möchtest dann gebe ich dir hier das Pinout des SD-Card Adapters.
Raspberry Pi Pico GPIO | SD Mode | SPI Mode |
---|---|---|
GP10 | CLK | SCK |
GP11 | CMD | SDI / MOSI |
GP12 | DAT0 | SDO / MISO |
GP13 | DAT1 | X |
GP14 | DAT2 | X |
GP15 | CD/DAT3 | CSn |
Auf der Seite Maker Pi Pico Datasheet findest du weitere technische Daten zum MAKER Pi Pico.
In meinem Fall brauche ich nur den DHT11 Sensor anschließen und das Micro USB Kabel anschließen und bin mit dem Aufbau für diesen Beitrag fertig.
Programmieren des SD-Card Adapters am MAKER Pi Pico
Für die nachfolgenden Beispiele verwende ich das Tutorial “Write Read Data to SD Card Using Maker Pi Pico and CircuitPython” als Basis. Dieses Tutorial ist zwar in Englisch, aber durch die recht einfache Skriptsprache CircuitPython kann man den Quellcode gut lesen und verstehen.
Mounten einer SD-Card & schreiben einer Zeile in eine Textdatei
Zunächst wollen wir eine SD-Karte mounten quasi einbinden und in eine Datei eine Textzeile schreiben.
from board import * from time import * import busio import sdcardio import storage # eine Pause von 1 Sekunde sleep(1) # definieren der Pins der SD-Card spi = busio.SPI(GP10, MOSI=GP11, MISO=GP12) cs = GP15 sd = sdcardio.SDCard(spi, cs) # einbinden der SD Karte vfs = storage.VfsFat(sd) storage.mount(vfs, '/sd') # öffnen der Datei pico.txt zum schreiben # wenn diese Datei nicht existiert dann # wird diese zuvor erstellt with open("/sd/pico.txt", "w") as file: # schreiben einer Zeile in die Datei file.write("Hello, world!") # schreiben eines Zeilenumbruchs file.write("\r\n")
lesen von Dateien einer SD-Card
Da wir nun Daten auf die SD-Karte geschrieben haben, möchten wir diese ggf. auch auslesen.
Im Beitrag Python #10: Dateiverarbeitung habe ich dir gezeigt wie man mit Dateien & Verzeichnisse in Python arbeitet. Dieses können wir auf die leicht abgewandelte Skriptsprache CircuitPython anwenden.
from board import * from time import * import busio import sdcardio import storage # eine Pause von 1 Sekunde sleep(1) # definieren der Pins der SD-Card spi = busio.SPI(GP10, MOSI=GP11, MISO=GP12) cs = GP15 sd = sdcardio.SDCard(spi, cs) # einbinden der SD Karte vfs = storage.VfsFat(sd) storage.mount(vfs, '/sd') # schreiben von 3 Einträgen in die Datei "greeting.txt" # durch den Parameter "a" (a - append / anhängen ) # wird beim nächsten Start des Programmes die Datei # NICHT überschrieben sondern 3 zusätzliche Einträge hinzugefügt for i in range(3): # Datei "greeting.txt" zum schreiben öffnen, die Daten werden # an das Ende der Datei geschrieben with open("/sd/greeting.txt", "a") as file: # schreiben einer Zeile in die Datei file.write("Hello World!") # schreiben eines Zeilenumbruchs file.write("\r\n") # lesen der zuvor geschriebenen Daten von der SD Karte with open("/sd/greeting.txt", "r") as file: for line in file: print(line)
Ausgabe auf der Konsole
Auf der Konsole werden nun die zuvor geschriebenen Daten angezeigt.
code.py Ausgabe:
Hello World!
Hello World!
Hello World!
Sollte das Programm jedoch mehrfach gestartet werden, so werden je Start 3 weiteren Datenzeilen hinzugefügt.
Schreiben von Sensordaten auf der SD-Karte
Möchte man Sensordaten schreiben so empfiehlt es sich diese Strukturiert zu schreiben. Man kann hierfür das JSON Format, XML oder auch das recht einfache CSV Format wählen.
Da wir lediglich die 4 Werte,
- Index,
- Zeitstempel,
- Temperatur,
- rel. Luftfeuchtigkeit
schreiben möchten, reicht für diesen Fall das CSV Format aus. (Mit den anderen beiden Formaten werde ich mich gesondert auf meinem Blog befassen.)
Was ist das CSV Format?
Das CSV Format ist wie erwähnt das einfachste Format. Die Daten werden dabei mit einem definierten Symbol getrennt in einer Zeile gespeichert. Eine Zeile endet immer mit einem Zeilenumbruch “\r\n”.
1;2021-08-22 13:30;13;52
2;2021-08-22 13:31;15;49
Das Symbol zum Trennen von Daten innerhalb einer Zeile ist normalerweise das Semikolon. Aber es kann auch jedes andere Symbol verwendet werden. Ein Problem tritt jedoch auf wenn dieses Symbol innerhalb eines Textes in der Zeile vorkommt, dann kann ein Parser schon an seine grenzen stoßen.
Daten im CSV Format schreiben & lesen
Wollen wir zunächst ein paar Daten im CSV Format schreiben und lesen.
for i in range(3): with open("/sd/date.txt", "a") as file: # schreiben einer CSV Datenzeile in die Datei file.write(str(i)) # Trenner der CSV Datei file.write(";") file.write("2021-08-22 13:3"+str(i)) file.write(";") file.write(str(24)) file.write(";") file.write(str(48)) # schreiben eines Zeilenumbruchs file.write("\r\n") # lesen der zuvor geschriebenen Daten von der SD Karte with open("/sd/date.txt", "r") as file: for line in file: single_line = line.strip() # entpacken einer Zeile in die Variablen index, timestamp, temp, humi = single_line.split(";") # ausgeben der Daten auf der Konsole print("Index:", str(index)) print("Zeitstempel:", str(timestamp)) print("Temperatur:", str(temp)) print("rel. Luftfeuchtigkeit:", str(humi))
Ausgabe auf der Konsole
Auf der Konsole werden nun 3 Blöcke ausgegeben mit den zuvor gespeicherten Daten.
code.py Ausgabe:
Index: 0
Zeitstempel: 2021-08-22 13:30
Temperatur: 24
rel. Luftfeuchtigkeit: 48
Index: 1
Zeitstempel: 2021-08-22 13:31
Temperatur: 24
rel. Luftfeuchtigkeit: 48
Index: 2
Zeitstempel: 2021-08-22 13:32
Temperatur: 24
rel. Luftfeuchtigkeit: 48
Zeitstempel für die Sensordaten
Der MAKER Pi Pico verfügt über ein paar sehr nützliche Features aber eine RealTimeClock ist (bisher) nicht verbaut somit müsste man über die Pins ein solches Modul zusätzlich anschließen oder aber über einen aufgesteckten ESP01 und einer WiFi Verbindung von einem NTP Server die Zeitstempel holen.
In diesem Beispiel möchte ich einen Zeitstempel von einem kleinen PHP-Skript auf einer meiner Subdomains lesen (https://zeitstempel.draeger-it.blog/). Der Vorteil ist, dass ich das Format gleich definieren kann und somit der Code im Mu-Editor recht übersichtlich bleibt. Für diese Lösung benötigst du ein aktive WiFi Verbindung zu einem lokalen WLAN Netzwerk.
Aufbau einer WiFi Verbindung und laden des Zeitstempels von der Webseite
Wie du am MAKER Pi Pico mit dem ESP01 eine WiFi Verbindung zu deinem WLAN Netzwerk aufbaust habe ich dir im Beitrag Maker Pi Pico von Cytron bereits gezeigt.
Hier möchte ich dir lediglich das fertige Programm zum lesen eines Zeitstempels zeigen. Dieser Beitrag soll sich hautpsächlich darum drehen wie du nun die Sensordaten mit eben diesem Zeitstempel auf einer SD-Card im CSV Format speicherst.
import time import board import adafruit_dht import busio import adafruit_requests as requests import adafruit_espatcontrol.adafruit_espatcontrol_socket as socket from adafruit_espatcontrol import adafruit_espatcontrol secrets = { "ssid" : "FRITZBox7590GI24", "password" : "abc" } timestamp_url = "http://zeitstempel.draeger-it.blog/" RX = board.GP17 TX = board.GP16 uart = busio.UART(TX, RX, receiver_buffer_size=2048) esp = adafruit_espatcontrol.ESP_ATcontrol(uart, 115200, debug=False) requests.set_socket(socket, esp) print("Resetting ESP module") esp.soft_reset() # Aufbau der WiFi Verbindung while not esp.is_connected: print("Connecting...") esp.connect(secrets) print("lesen des Zeitstempels von ", timestamp_url) # Endlosschleife... while True: try: r = requests.get(timestamp_url) print("Zeitstempel:", r.text) time.sleep(2) except: print("Fehler beim lesen des Zeitstempels von", timestamp_url)
In diesem kurzen Video zeige ich dir nun die Ausführung des oben gezeigten Programmes. (Das Passwort zu meinem WiFi-Netzwerk habe ich hier mit einem schwarzen Balken unkenntlich gemacht.)
Auf der Konsole sieht man den gelesenen Zeitstempel sowie ab und zu das nicht erfolgreich gelesen werden konnte. Ich denke das liegt hier vielmehr an einem Timeout der Verbindung, welcher zu kurz gewählt wurde. Hier kann man sich aber Abhilfe schaffen und ggf. eine kleine Schleife von 10 Durchläufen erzeugen und somit 10-mal probieren einen gültigen Zeitstempel zu laden.
schreiben der Sensordaten im CSV Format
Da wir nun wissen wie wir Daten auf die SD-Card schreiben und lesen, sowie einen Zeitstempel haben ist der nächste Schritt die Sensordaten auszulesen und diese Daten auf die SD-Card zu schreiben.
Damit unser Index (die erste Spalte in der CSV Datei) fortlaufend geschrieben wird, laden wir die CSV Datei beim starten des Mikrocontrollers und speichern und die Anzahl der Zeilen dieser Datei in einer Variable “index”.
import time import board import adafruit_dht import busio import adafruit_requests as requests import adafruit_espatcontrol.adafruit_espatcontrol_socket as socket from adafruit_espatcontrol import adafruit_espatcontrol import adafruit_dht import sdcardio import storage secrets = { "ssid" : "FRITZBox7590GI24", "password" : "abc" } timestamp_url = "http://zeitstempel.draeger-it.blog/" RX = board.GP17 TX = board.GP16 uart = busio.UART(TX, RX, receiver_buffer_size=2048) esp = adafruit_espatcontrol.ESP_ATcontrol(uart, 115200, debug=False) requests.set_socket(socket, esp) # initialisieren eines DHT11 Sensors am GP27 dhtDevice = adafruit_dht.DHT11(board.GP27) # Zähler für den Index innerhalb der CSV Datei index = 0 # Dateiname für die Sensordaten csv_filename = "/sd/measurements.csv" # definieren der Pins der SD-Card spi = busio.SPI(board.GP10, MOSI=board.GP11, MISO=board.GP12) cs = board.GP15 sd = sdcardio.SDCard(spi, cs) # einbinden der SD Karte vfs = storage.VfsFat(sd) storage.mount(vfs, '/sd') # lesen der Sensorwerte des DHT Sensors def read_dht_values(): result = {} # Schleife von 0 bis 9 for i in range(9): try: # lesen der Sensorwerte result["temp"] = dhtDevice.temperature result["humi"] = dhtDevice.humidity # Wenn die Temperatur ODER die rel. Luftfeuchtigkeit nicht vom Typ None ist dann, # soll die aeussere Schleife verlassen werden. if(result["temp"] is not None or result["humi"] is not None): break; else: # Wenn die Daten nicht gelesen werden konnten, dann eine kleine # Pause von 2 Sekunden einlegen. time.sleep(2) except RuntimeError as error: print(error.args[0]) except Exception as error: # Im Fall eines Schwerwiegenden Fehlers, so wird das Programm beendet. dhtDevice.exit() raise error return result def setup(): # Zugriff auf die Globale Variable "index" global index print("Setup") # Reset des ESP01 Modules esp.soft_reset() # Aufbau der WiFi Verbindung while not esp.is_connected: print("Verbindung zu", secrets["ssid"],"wird aufgebaut...") esp.connect(secrets) try: index = sum(1 for line in open(csv_filename)) print("Datei", csv_filename,"enthaelt", str(index), "Zeilen") except: print("Datei wurde nicht gefunden.") index = 0 # lesen des Zeitstempels von der Webseite def read_timestamp(): timestamp = "-undefined-" for i in range(10): try: r = requests.get(timestamp_url) timestamp = r.text # Wenn der Code bis hier funktioniert hat, # dann kann die aeussere Schleife verlassen werden. break; except: pass time.sleep(1) return timestamp def loop(): global index # lesen des Zeitstempels timestamp = read_timestamp() # lesen der Sensordaten dht_values = read_dht_values() # incrementieren des Indexes index = index + 1 # Aufbau der CSV Datenzeile csv_line = str(index) + ";" + timestamp + ";" + str(dht_values["temp"]) + ";" + str(dht_values["humi"]) # Ausgeben der CSV Datenzeile auf der Komandozeile print(csv_line) # schreiben der CSV Datenzeile in die Datei with open(csv_filename, "a") as file: file.write(csv_line) # hinzufügen eines Zeilenumbruchs am Zeilenende file.write("\r\n") # einmaliges Ausführen der Funktion "setup" setup() # Endlosschleife, welche die Funktion "loop" ausführt while True: loop() # eine Pause von 1 Sekunde time.sleep(1)