MAKER Pi Pico #2 – Sensordaten auf einer SD-Card speichern

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.

MAKER Pi Pico - schreiben von Daten auf die SD-Karte
Dieses Video ansehen auf YouTube.

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.

MAKER Pi Pico
MAKER Pi Pico

Benötigte Ressourcen zum Nachbau

Möchtest du die Beispiele aus dem Beitrag nachbauen, so benötigst du folgende Ressourcen:

MAKER Pi Pico mit Micro SD-Card
MAKER Pi Pico mit Micro SD-Card

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.

MAKER Pi Pico mit DHT11 Sensor und Micro SD-Karte
MAKER Pi Pico mit DHT11 Sensor und Micro SD-Karte

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 ModeSPI Mode
GP10CLKSCK
GP11CMDSDI / MOSI
GP12DAT0SDO / MISO
GP13DAT1X
GP14DAT2X
GP15CD/DAT3CSn
Pinout des SD-Card Adapters am MAKER Pi Pico

Auf der Seite Maker Pi Pico Datasheet findest du weitere technische Daten zum MAKER Pi Pico.

Anschluss eines SD-Card Adapters an den Raspberry Pi Pico
Anschluss eines SD-Card Adapters an den Raspberry 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.

Lesen eines Zeitstempels von einer Webseite.

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)
    

Video – schreiben der DHT11 Sensorwerte in eine CSV Datei auf der SD-Karte

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.