Programmieren 1 mit Python

Jörg Thomaschewski

Willkommen zum Script Programmieren 1 (mit Python) für Studierende der Medientechnik an der Hochschule Emden/Leer. Aber vielleicht helfen diese Inhalte ja auch anderen Studierenden und Interessierten. Ich bin Prof. Dr. Jörg Thomaschewski. Weitere Informationen zu studentischen Projekten und meiner Forschung finden Sie auf meiner Webseite www.thomaschewski.de.

Für das Melden von Tippfehlern und Anregungen bin ich immer dankbar. Bitte senden Sie diese direkt an joerg.thomaschewski@hs-emden-leer.de. Vielen Dank!

Nützliche Hinweise

  • Sehr gut ist die eingebaute Suche, die über die Taste F aktiviert werden kann.
  • Sourcecode kann 'oben rechts' über den Copy-Button in einem Sourcecode-Block direkt kopiert werden.

Änderungshistorie

Damit die Studierenden mögliche Änderungen und Ergänzungen während des Semesters nachvollziehen können, werden hier inhaltliche Änderungen notiert.

  • [2024-09-13] Kapitel 1 - 4 sind einsatzbereit. Kapitel 5 ergibt sich im laufenden Semester auch aufgrund der studentischen Interessen.
  • [2024-09-16] Tippfehler und favicon korrigiert.
  • [2024-09-25] Tippfehler und "Schaltpläne" (Fritzing) in Kap. 3.4, 3.5 und 3.6 korrigiert.
  • [2024-10-02] Kap. 3.2 LED dimmen: Sourcecode in CamelCase geschrieben, "Schaltplan" und "Foto" korrigiert, Tabelle mit den Operatoren umgeschrieben
  • [2024-10-15] Viele Tippfehler korrigiert und in Kap. 3.2 Sourcecode zum Schalten mit dem Drehschalter eingefügt.
  • [2024-10-16] Kleine Änderungen im Sourcecode Kap. 3 auf CamelCase.
  • [2024-10-19] Kap. 3.3 Taster als Schalter mit Prellschutz vieles neu!
  • [2024-10-23] Kap. 3.3, 3.4, 3.7, 4.4, 4.5, 4.6, 4.9 kleinere Ergänzungen & Kap. 4.1 umfangreicht überarbeitet, Kap. 4.8, 4.10 eingefügt.
  • [2024-10-30] Kap. 4.1-4.4 überarbeitet, insbesondere 4.2 for-Schleife über ein Dictionary und 4.4 print
  • [2024-11-01] Kap. 3.8 Webserver ist neu
  • [2024-11-06] Kap. 3.8 Webserver Status erklärt, Kap. 4.10 ergänzt
  • [2024-11-13] Ein paar Änderungen in Kap. 4.10
  • [2024-11-20] Ein paar Änderungen in Kap. 4.5
  • [2024-11-27] Ein paar Änderungen in Kap. 4.6 - 4.10 und 4.11 ist neu
  • [2024-11-04] Minimale Fehlerverbesserungen

Offene Aufgabe

  • Wie werden Programme ausgeführt -> Zeichnung einfügen (s. Tafelbild 16.10.24)
  • In 4.2 eine Abbildung key-value einfügen -> s. Screenshot + Kommentare in der VL 06.11.24
  • In 4.5 eine Abbildung zum Aufruf des Konstruktors einbauen -> s. Screenshot + Kommentare in der VL 06.11.24

Danksagung

Ein besonderer Dank geht an Jörg Strick, der viele Jahre und viel Energie aufbrachte, um mich für Python und die Verbindung mit "Elektronik" zu begeistern. Danke!

1 Einleitung

Wenn wir ans Programmieren denken, dann denkt man normalerweise an einen Computer und einen großen Monitor mit vielen Zeilen Sourcecode. Ich möchte in diesem Modul einen anderen Weg gehen und wir beginnen Programmieren 1 mit der Programmiersprache Python auf einem Mikrocontroller. Wir verwenden also ein Elektronik-Set und erlernen die ersten Programmierschritte durch das Nachvollziehen und Verändern vorgegebener praktischer Anwendungen

Die Veranstaltung folgt dem Zitat von Jörg Strick "Hardware macht glücklich" und versucht über motivierende Anwendungen einen spielerischen Einstieg in die Programmierung zu finden.

Wieso Programmieren?

Programme steuern heutzutage die Welt, egal ob es uns gefällt oder nicht. Aber warum sollten gerade Sie Programmieren lernen? Hier sind einige Gründe, die sowohl erfahrene als auch neue Studierende motivieren können.

  1. Problemlösungsfähigkeiten entwickeln
    Programmieren lehrt uns, Probleme strukturiert und logisch anzugehen. Unabhängig davon, ob Sie ein einfaches Script schreiben oder eine komplexe Anwendung entwickeln – Sie lernen, analytisch zu denken und kreative Lösungen zu finden.

  2. Kreativität entfalten
    Programmieren ist nicht nur eine technische, sondern auch eine kreative Tätigkeit. Es bietet Ihnen die Möglichkeit, Ihre Ideen in die Tat umzusetzen und z.B. digitale Medien zu erschaffen, sei es ein spannendes Spiel oder eine innovative App.

  3. Berufliche Chancen verbessern
    In nahezu jedem Berufsfeld sind Programmierkenntnisse mittlerweile von Vorteil. Besonders in der Medientechnik können Sie mit Programmierfähigkeiten Ihre Chancen auf dem Arbeitsmarkt deutlich erhöhen und sich von anderen abheben.

  4. Verständnis für Technologie
    Die Welt wird zunehmend von Technologie bestimmt. Wer versteht, wie diese Technologien funktionieren, kann aktiv an der Gestaltung der digitalen Zukunft teilnehmen. Dies gilt vielleicht sogar besonders für die Arbeit mit Microcontrollern wie dem Raspberry Pi Pico und elektronischen Komponenten, mit denen man sehr viele praktische Anwendungen entwickeln kann.

  5. Projekte realisieren
    Stellen Sie sich vor, Sie könnten Ihre eigenen Projekte umsetzen, ohne auf externe Entwickler angewiesen zu sein. Mit Programmierkenntnissen können Sie Ihre eigenen Ideen realisieren, von der Planung bis zur fertigen Anwendung.

  6. Gemeinschaft und Zusammenarbeit
    Programmieren bringt Menschen zusammen. Es gibt zahlreiche Communities, in denen Sie sich austauschen, voneinander lernen und gemeinsam an Projekten arbeiten können. Dies fördert nicht nur Ihr technisches Wissen, sondern auch Ihre Teamfähigkeit.

  7. Spaß und Herausforderung
    Ja, Programmieren kann herausfordernd sein, aber gerade das macht den Reiz aus. Die Erfolgserlebnisse, wenn ein Programm funktioniert oder eine schwierige Aufgabe gelöst ist, sind unbezahlbar. Und wer weiß, vielleicht entdecken Sie eine neue Leidenschaft!

Egal, ob Sie schon Programmiererfahrung haben oder ganz neu anfangen – Programmieren bietet für jeden etwas. Es ist eine Reise, die sowohl Ihre berufliche als auch persönliche Entwicklung bereichern kann, wenn man sich darauf einlässt.

Für mich als Lehrender bedeutet dies, dass ich eine sehr heterogene Interessensgruppe vor mir habe und versuchen muss, allen Beteiligten eine Herausforderung zu geben.

Diese Veranstaltung richte ich an diejenigen, die ohne Vorwissen motiviert und aktiv teilnehmen. "Aktiv" bedeutet dabei, sich auch selbständig mit dem Inhalt auseinanderzusetzen - also zu studieren.

Unser Unterschied zur klassischen Programmierveranstaltung

Nehmen wir uns ein beliebiges Programmierbuch für Anfänger:innen. Zuerst lernen wir Syntax und Syntax und Syntax. Sehr viel Theorie, bevor es erste sichtbare Ergebnisse gibt. Es ist also wie "Vokabeln lernen" einer neuen Sprache. Wenn man es kann, kann es Spaß machen und bis dahin ist es ein steiniger Weg.

Ich möchte (erstmalig) anders vorgehen: Wir nehmen uns kleine Elektronik-Projekte und verwenden vorgefertigte Scripte. Wir erfreuen uns an den funktionierenden Projekten und lernen dann (wenn wir die Projekte erweitern) die Syntax. Auch dieser Weg wird nicht ohne Stolpersteine sein. Nicht immer wird gleich alles auf Anhieb funktionieren. Und dann kommt es darauf an, wie kollegial Sie genau mit diesen Situationen umgehen.

Was ist Programmieren?

Zur Beantwortung der Frage "Was ist Programmieren?" gibt es schon sehr gutes Material. So schauen wir uns einfach dieses Video an: https://www.youtube.com/watch?v=3_Goj0_DJfA

2 Inbetriebnahme und Basisinfos

Gliederung

2.1 Inbetriebnahme
2.2 Thonny
2.3 ChatGPT und andere KI

Was Sie hier erwartet

Nach dem Durcharbeiten dieses Kapitels haben Sie den Raspberry Pi Pico erfolgreich angeschlossen, kennen die sehr einfache Entwicklungsumgebung "Thonny" und sind in der Lage eigene Programme (Scripte) zu erstellen. Und Sie kennen "den guten Umgang mit ChatGPT".

2.1 Inbetriebnahme

Unser Elektronik Set besteht aus mehreren Komponenten. Wir beginnen aber ganz einfach nur mit dem Microcontrollerboard, unserem Raspberry Pi Pico W bzw. dem Raspberry Pi Pico WH.

Die Basis: Raspberry Pi Pico W

Der Raspberry Pi Pico (= Pi Pico) ist ein kostengünstiges aber vielseitiges Microcontrollerboard mit dem Microcontroller RP2040, der sich ideal für Einsteigerprojekte eignet. Der Pi Pico ermöglicht einen schnellen Einstieg in die Welt der Medienelektronik und kann zur Steuerung von Sensoren, Displays und anderen Komponenten genutzt werden. Die Programmierung erfolgt in Python, genauer gesagt in MicroPython, einer abgespeckten Version von Python.

Die Dokumentation des Pi Pico findet sich hier

Nerd-Wissen Microcontroller RP2040

Hier der Link zum Microcontroller RP2040

Es gibt den Pi Pico in verschiedenen Ausführungen.

  • Es gibt den Raspberry Pi Pico
    Ohne WLAN und ohne Bluetooth. Er ist zwar günstiger, aber ohne WLAN ist man schon sehr eingeschränkt.
  • Es gibt den Raspberry Pi Pico W
    Mit WLAN und mit Bluetooth. Dieser ist in der folgenden Abbildung zu sehen.
  • Es gibt den Raspberry Pi Pico WH
    Das Board hat noch Header installiert, z.B. in den drei Löchern oberhalb des WLAN-Chips.

Microcontrollerboard Raspberry Pi Pico W

Unser Board hat insgesamt 40 "Pins". Einige Pins dienen zur Spannungsversorgung, aber die meisten Pins sind programmierbar, sogenannte GPIO-Pins. GPIO steht für "General Purpose Input/Output". Es sind vielseitig einsetzbare Pins, die sowohl als Eingänge als auch als Ausgänge konfiguriert werden können, um verschiedene elektronische Komponenten zu steuern oder Daten von Sensoren zu lesen.

Spannungsversorgung Raspberry Pi Pico W

Für die erste Anwendung brauchen wir noch nicht mehr über das Board zu wissen, denn wir wollen zunächst die interne LED zum Blinken bringen.

Bei der eingebauten LED müssen wir uns erstmal um nichts weiter kümmern. Sie ist richtig angeschlossen und hat einen passenden Vorwiderstand.

Erstinstallation

Das Ziel dieses Abschnitts ist es, dass wir die Thonny Programmierumgebung installiert haben, den Pi Pico mit dem Computer verbunden haben und die interne LED anschalten. Wir brauchen dazu einen Computer/Laptop mit Microsoft Windows, MacOS oder Linux für die Programmierumgebung "Thonny IDE".

Unser Computer mit dem USB-Anschluss und der Programmierumgebung "Thonny IDE" ermöglicht es uns, dass wir Programme schreiben können und diese anschließend auf den Pi Pico laden können. Und dann anschließend können wir dem Pi Pico (ebenfalls über Thonny auf unserem Computer) mitteilen das Programm auszuführen.

Nerd-Wissen Schnellerer Einstieg

Wem es hier zu langsam geht, der kann folgendes Video nutzen: https://www.youtube.com/watch?v=_ouzuI_ZPLs

Sie können dann auch in das Thonny-Terminalfelster help() eingeben und sich anschauen, welche Umgebung installiert ist.

Unter https://docs.micropython.org/en/latest/ finden Sie alles zu MicoPthon.

Wer bei der Installation unsicher ist, sollte die Installation im ersten Praktikumstermin vornehmen.

  1. Das Microcontrollerboard wird auf das Experimentierboard (engl. Breadboard) gesteckt, und USB-Leitung wird schon an den Computer aber noch nicht an den Pi Pico angeschlossen).

    Breadboard

  2. Thonny herunterladen https://thonny.org/, installieren und starten.

    Thonny Einrichten

    Nach dem Start sollte es so aussehen.

    Thonny Einrichten

  3. Button BOOTSEL gedrückt halten und Microcontoller mittels USB mit dem Computer verbinden. Geduld haben, es kann 1-2 Minuten dauern und solange BOOTSEL gedrückt halten.

    BOOTSEL

    Wenn es erfolgreich war, dann erscheint ein neues "Laufwerk", wie abgebildet.

    Thonny Einrichten

  4. Nun in Thonny unter Werkzeuge -> Optionen im Tab Interpreter den Interpreter aus der Liste auswählen MicroPython (RP2040).

    Thonny Einrichten

    Thonny Einrichten

  5. Wenn dies erreicht ist, gibt es unten rechts einen Link "MicroPython installieren oder aktualisieren", den wir anklicken. Dann öffnet sich ein neues Fenster und wir müssen das "Target volume" entsprechend dem Laufwerk einstellen. Danach bei "variant" den Eintrag Raspberry Pi Pico W / Pico WH auswählen.

    Thonny Einrichten

    Thonny Einrichten

    Nun auf installieren drücken und warten, bis die Installation abgeschlossen ist. Sobald "done" unten links erscheint, können wir das Fenster schließen.

    Thonny Einrichten

  6. Jetzt sehen wir ein leeres Thonny-Fenster und müssen Thonny neu starten.

    Thonny Einrichten

  7. Nach dem Neustart den COM-Port angeben, sofern dieser nicht automatisch gefunden wurde.

    Der Raspberry Pi Pico kommuniziert über einen sogenannten COM-Port mit dem Computer. Oder anders gesagt, der Computer stellt einen COM-Port für den Pi Pico zur Verfügung. Bei mir ist es an einem Computer COM 3 und an einem anderen Laptop COM 6, bei Ihnen kann es ein anderer COM-Port sein.

    ROTE SCHRIFT in der Kommandozeile: In meinem Fall wurde der COM-Port nicht automatisch gefunden und es erscheint eine rote Schrift in dem unteren Teil von Thonny.

    Thonny Einrichten

    Dann muss man in Thonny unter Werkzeuge -> Optionen im Tab Interpreter den Port auswählen und das Fenster mit "ok" verlassen.

    Thonny Einrichten

    So sollte es am Ende aussehen! Ansonsten ist etwas schiefgelaufen.

    Thonny Einrichten

Das erste Script schreiben und ausführen

Schreiben Sie von Hand diese Zeilen in die Thonny Programmierumgebung und drücken Sie anschließend im Menü den grünen Pfeil, der das Progamm auf den Pi Pico lädt und ausführt.

Thonny Einrichten

Jetzt sollte die LED leuchten und es ist ein wichtiger Schritt geschafft!

Erfolgreich eingerichtet

led.value(1) - led.value(0)

Wenn in einem Script steht, dass ein Ausgang auf "1" (siehe led.value(1) im Script) geändert wird, dann bedeutet dies, dass an dem zugehörigen Pin die Spannung von 0 Volt auf 3,3 Volt geändert wird.

2.2 Thonny

Hier Infos zu den Basisfunktionalitäten der Thonny-Oberfläche

Thonny Einrichten

Basis-Wissen Verbindungsinfo

In der Thonny-Oberfläche ist unten rechts eine Info-Zeile. Diese Zeile ist sehr wichtig und enthält die Information, welches Python wo ausgeführt wird..

Wenn offensichtlich keine Verbindung zum Pi Pico besteht, dann den roten "Stop"-Button in der Menüleiste klicken, damit die Verbindung erneut probiert wird.

Das erste Script abspeichern

Wir werden nun viele kleine Scripte erstellen und es gibt immer zwei Arten, die Scripte abzuspeichern: auf dem Computer/Laptop oder auf dem Pi Pico.

Wenn man ein Script auf dem Pi Pico unter dem Namen main.py speichert, dann wird es ausgeführt, sobald der Pi Pico mit Strom versorgt wird. Wenn man dem Script einen anderen Namen gibt, dann wird es abgespeichert, aber nicht direkt ausgeführt.

Basis-Wissen Script speichern

Sie können also den Pi Pico zu Hause programmieren und die Scripte darauf speichern und dann in der Hochschule die Programmierung daran fortsetzen.

Geben Sie den Scripten eindeutige, aussagekräftige Namen. Das erste Script möchte ich nun bei mir im Verzeichnis Prog1-PiPico unter dem Namen 1-interneLED.py auf dem Laptop speichern. Bei Datei -> Speichern unter erscheint ein Fenster mit der Auswahl "Dieser Computer" und "Raspberry Pi Pico".

Aber bevor wir das Script speichern, fehlt noch etwas sehr Wichtiges: ein Kommentarheader im Script. Kommentarheader sind absolut wichtig und Abgaben ohne Kommenatarheader werden nicht anerkannt!

Speichern

Regeln

  • Speichern Sie kein Script ohne einen Kommentarheader mit den minimalen Daten
    • "Was macht das Script",
    • "Wer hat es erstellt" und
    • "Von wann ist das letzte Update".
  • Verwenden Sie stets aussagekräftige Namen für die Dateien.

2.3 ChatGPT und andere KI

Wie einfach es ist, schlechten Sourcecode zu schreiben, möchte ich an diesem kleinen Beispiel zeigen. Anschließend soll KI (genauer gesagt ChatGPT) genutzt werden, denn die gute Nutzung ist sehr vorteilhaft, während eine schlechte Nutzung in Sackgassen führt; und am Ende muss man von vorne neu von Hand anfangen.

Hinweis: Einrückungen

In Python sind Einrückungen total wichtig, da das Programm daran erkennt, was Teil einer Schleife, Anweisung oder Funktion ist. In Thonny verwenden wir die Tab-Taste für eine Einrückung.

Wir wollen die interne LED nun blinken lassen.

Hier das erste schlechte Script, das zwar funktioniert, aber nicht erweiterbar ist. Genutzt wird eine Endlosschleife (while), die den eingerückten Codeblock immer wieder erneut ausführt.

# Interne LED blinken lassen - schlecht 1
# J. Thomaschewski, 12.08.2024
from machine import Pin
from time import sleep
led = Pin('LED', Pin.OUT)
while True:
    led.on()
    sleep(0.5)
    led.off()
    sleep(0.5)

Nun das zweite schlechte Script, das ebenso funktioniert, aber ebenfalls nicht erweiterbar ist. Statt led.on() und led.off() verwenden wir die Funktionalität led.toggle().

1
2
3
4
5
6
7
8
# Interne LED blinken lassen - schlecht 2
# J. Thomaschewski, 12.08.2024
from machine import Pin
from time import sleep
led = Pin('LED', Pin.OUT)
while True:
    led.toggle()
    sleep(2)

Schlecht deshalb, da wir eine while-Schleife nutzen, die endlos läuft und wir das Programm somit nicht erweitern können. Die gute Lösung enthält eine Definition einer Funktion, die über einen Timer gesteuert wird. Hier soll uns der Sourcecode noch nicht interessieren, sondern nur die Tatsache, dass es gute und schlechte Ansätze in der Programmierung gibt (und dass ChatGPT oftmals zu den schlechten Versionen greift).

1
2
3
4
5
6
7
8
9
# Interne LED blinken lassen - gut
# J. Thomaschewski, 12.08.2024
from machine import Pin, Timer
led = Pin('LED', Pin.OUT)

def blink(timer):
    led.toggle()

Timer().init(freq=5, callback=blink)

Nutzung von KI am Beispiel von ChatGPT

Ich gebe eine deutliche Empfehlung für die Nutzung von Künstlicher Intelligenz (KI).

Zunächst verweise ich auf die Regeln der Hochschule zur Nutzung der KI, die in einem Positionspapier zusammengefasst sind und derzeit hier zu finden sind.

ChatGPT programmieren lassen

Hier schauen wir uns ChatGPT-Ergebnisse an.

Fragestellung an ChatGPT 4o am 12.08.2024

Erstelle mir ein MicroPython-Script für die Nutzung durch den Raspberry Pi Pico. Die interne LED soll blinken.

Antwort

from machine import Pin
import time

# Die interne LED des Raspberry Pi Pico ist an Pin 25 angeschlossen
led = Pin(25, Pin.OUT)

while True:
    led.value(1)  # LED einschalten
    time.sleep(0.5)  # 500 Millisekunden warten
    led.value(0)  # LED ausschalten
    time.sleep(0.5)  # 500 Millisekunden warten

WICHTIG: Die Antwort von ChatGPT ist zwingend um einen Kommentarheader zu ergänzen und lautet dann für die Nutzung durch uns z.B. wie folgt

# Interne LED blinken lassen - gut
# Script erstellt von ChatGPT 4o
# J. Thomaschewski, 12.08.2024
from machine import Pin
import time

# Die interne LED des Raspberry Pi Pico ist an Pin 25 angeschlossen
led = Pin(25, Pin.OUT)

while True:
    led.value(1)  # LED einschalten
    time.sleep(0.5)  # 500 Millisekunden warten
    led.value(0)  # LED ausschalten
    time.sleep(0.5)  # 500 Millisekunden warten

Es gibt die drei Varianten

  1. Sourcecode erstellt von ChatGPT 4o
    Dies ist zu nutzen, wenn ChatGPT den Hauptteil erstellt hat und nur minimale Änderungen stattgefunden haben.

  2. Sourcecode mit Hilfe von ChatGPT 4o
    Hier muss der Sourcecode und die Struktur einen erheblichen Eigenanteil aufweisen. Ansonsten ist die Variante A. zu wählen.

  3. XYZ wurde von ChatGPT 4o erstellt
    Wenn ein eindeutiger Teil von ChatGPT erstellt wurde, dann würde ich dies immer genau benennen.

Die Verwendung von KI-Tools ist in dieser Veranstaltung ausdrücklich erlaubt, sofern eine Kennzeichnung vorhanden ist.

Hinweis: Probleme mit ChatGPT

Das Problem ist bei ChatGPT oftmals nicht der schlechte Sourcecode, sondern die mangelnde Erweiterbarkeit. Man fängt mit einem kleinen Stückchen Sourcecode an und lässt es sich erstellen. Dann kommen die nächsten und nächsten Wünsche und irgendwann bekommt es ChatGPT nicht mehr hin. Aber man selbst bekommt es dann auch nicht mehr hin und muss von Hand von vorne anfangen.

Lassen Sie sich also immer nur ein Stückchen helfen!

ChatGPT kommentieren lassen

Eine sehr gute Verwendung ist die Verwendung von ChatGPT als "Kommentator".

Fragestellung an ChatGPT 4o am 12.08.2024

Hinweis: Ich studiere im 1. Semester und habe keine Ahnung von Programmierung.
Kommentiere mir das Script (MicroPython für Raspberry Pi Pico) so ausführlich, sodass ich es gut verstehe.

from machine import Pin
from time import sleep
led = Pin('LED', Pin.OUT)
while True:
    led.toggle()
    sleep(2)

Antwort

# Importieren der notwendigen Module
from machine import Pin  # Das Modul 'machine' enthält Klassen zur Steuerung der Hardware des Raspberry Pi Pico. Hier wird speziell die Klasse 'Pin' importiert.

from time import sleep   # Das Modul 'time' enthält Funktionen zur Zeitsteuerung. Hier wird speziell die Funktion 'sleep' importiert.

# Initialisieren der LED
led = Pin('LED', Pin.OUT)  # Ein Pin-Objekt namens 'led' wird erstellt. Der Parameter 'LED' steht für den integrierten LED-Pin des Raspberry Pi Pico. 'Pin.OUT' bedeutet, dass dieser Pin als Ausgang konfiguriert wird.

# Endlosschleife
while True:  # Dies ist eine Endlosschleife, die ununterbrochen läuft.
    led.toggle()  # Die Methode 'toggle()' wechselt den Zustand der LED. Wenn sie an ist, wird sie ausgeschaltet und umgekehrt.
    sleep(2)      # Die Funktion 'sleep(2)' pausiert das Programm für 2 Sekunden, bevor die Schleife erneut durchlaufen wird.

Zusammenfassung

  • Sie haben die Erstinstallation vorgenommen und die interne LED anschalten können. Damit sind Sie einsatzbereit für die kommenden Aufgaben.
  • Sie können Scripte in Thonny schreiben und abspeichern, sowohl auf einem lokalen Computer als auch auf dem Pi Pico Board.
  • Sie wissen, dass Sie bei mir unbedingt einen Kommentarheader verwenden müssen.
  • Sie wissen, wie Sie ChatGPT oder andere KI-Tools nutzen können und dass die Nutzung zwingend im Programmheader angegeben werden muss.

3 Raspberry Pi Pico W Anwendungen

Gliederung

3.1 LED blinken
3.2 LED dimmen
3.3 LED schalten
3.4 I2C Display
3.5 Licht Sensor BH 1750
3.6 Temperatursensor BMP 280
3.7 WLAN
3.8 Webserver

Generelles Vorgehen

Hier stellen wir die Pi Pico-Anwendungen vor, die wir realisieren wollen. Jedes Unterkapitel hat folgende Struktur

  • Die Hardware- und Elektronikgrundlagen: Alle neuen Hardwarekomponenten werden kurz vorgestellt.

  • Die Anwendungen: Wir zeigen die Anwendungen mit vorgegebenen Beispiel-Scripten.

    Das Vorgehen bei allen Anwendungen ist immer

    1. Zuerst die Schaltung aufbauen

    2. Pi Pico per USB mit dem Computer verbinden

    3. Thonny starten und das Script kopieren/erstellen

    4. Programm ausführen

  • Python: Letztendlich wollen wir die Anwendungen ausprobieren UND die Programmierung verstehen. In diesem Abschnitt wird auf die Python-Programmierung eingegangen. Oftmals wird nicht alles besprochen, sondern es wird mit einfacher Syntax begonnen und wir steigern uns langsam. Am Anfang verstehen wir also noch nicht alle Zeilen im Script, sondern nehmen vieles als gegeben hin.

Raspberry Pi Pico Pinout

Die Übersicht über die Pinbelegung des Raspberry Pi Pico hier als PDF ist wichtig, da wir zwei Arten von Pins unterscheiden: Die Pins des grünen Raspberry Pi Pico Boards (1 - 40), die wir als Anschlüsse für die Bauteile (z.B. eine externe LED) benötigen und die logischen Pins GPIO (abgekürzt GP) des Microcontrollers, die wir bei der Programmierung angeben müssen.

Schauen wir uns den Pin unten rechts an: Pin 21 entspricht GPIO 16.

Raspberry Pi Pico W Pinout

Raspberry Pi Pico W Pinout

Die Grafik sollten Sie (ausgedruckt) immer dabeihaben.

Breadboard

Der Raspberry Pi Pico wird auf ein sogenanntes Breadboard gesteckt, sodass Sie weitere Bauteile ohne zu löten dazustecken können. Dies kann in der Praxis dann so aussehen:

Drehschalter

Hier sehen wir, wie die einzelnen Steckpunkte miteinander verbunden sind. Oben und unten verlaufen jeweils zwei "Leitungen" für Plus und Masse. Im Hauptbereich verlaufen kurze "Leitungen", sodass der Pi Pico eingesteckt werden kann.

Breadboard

3.1 LEDs blinken

Die interne LED haben wir schon blinken lassen, aber nun wollen wir mit externen LEDs beginnen.

Die Hardware- und Elektronikgrundlagen

Wir benötigen immer das Microcontrollerboard Raspberry Pi Pico W auf dem Breadboard und die USB-Anschlussleitung sowie Jumper-Leitungen, die man für die Verkabelung auf dem Breadboard benötigt.

Wir benötigen für diese drei Anwendungen außerdem:

  • 2 Widerstände (220 Ohm)

  • LEDs (rot, grün)

Widerstand und LED

Basis-Wissen LED

Eine LED (Light Emitting Diode; dt.: Leuchtdiode) ist ein Halbleiterbauelement, das Licht erzeugt, wenn elektrischer Strom in Durchlassrichtung fließt. Daher ist beim Anschluss auf die Polarität zu achten.

Die Polarität erkennt man daran, dass die längere Anschlussleitung (Anode) an Plus und die kürzere (Kathode) an Minus angeschlossen wird. (Einfach zu merken: Plus hat einen Strich mehr als Minus und ist somit länger.) Wenn die Anschlüsse gekürzt wurden, geht es wie folgt: Die Kathode wird oft durch eine abgeflachte Seite am Gehäuse markiert. Und wenn man von der Seite in die LED reinschaut, ist Minus innen an den dickeren Bereich (den Reflektor) anzuschließen.

Typische Betriebsspannungen für LEDs liegen zwischen 2 und 3 Volt, abhängig von der Farbe. Somit kann eine LED nicht direkt an eine 3,3 V oder 5 V-Spannung angeschlossen werden, sondern es muss ein Widerstand in Reihe geschaltet werden.

Basis-Wissen Widerstand

Ein Widerstand wird oft als sogenannter Vorwiderstand eingesetzt und dient dann dazu, die Spannung an einem elektrischen Bauteil zu verringern. In unserer Schaltung benötigen wir den Widerstand vor der LED.

Der Formelbuchstabe für einen Widerstand ist R (=resistor) und der Wert wird in Ohm (Ω) angegeben. Wir benötigen hier 220 Ω Widerstände. Um den richtigen Widerstand zu finden, muss man die Farbkodierung für 220 Ω kennen, die sich noch leicht unterscheidet, je nachdem ob 4 oder 5 Ringe (oder 6 Ringe) aufgedruckt sind.

Die Anwendungen

Wir starten mit drei Anwendungen. Zunächst lassen wir eine rote LED blinken. Dann lassen wir eine rote und eine grüne LED unabhängig voneinander blinken und drittens sollen eine rote und eine grüne LED abwechselnd blinken.

Eine rote LED blinken lassen

Externe LED blinken Externe LED blinken

  • Pin 38 an Masse (engl. Ground = GND)

  • Pin 21 (= GPIO 16) an den Widerstand

  • Widerstand an Plus der LED

  • Minus der LED an Masse

Wir haben mit led = Pin('LED', Pin.OUT) die interne LED aktiviert. Die einzige Änderung ist nun, dass wir led = Pin(16, Pin.OUT) schreiben und das GPIO 16 auf dem physikalischen Pin 21 liegt.

Pin ungleich Pin

Dies ist eine wichtige Warnung zum "Pin-Problem"!

  • Wenn ich schreibe Pin 38 an Masse (engl. Ground = GND), dann ist damit der physikalische Pin auf "grünen" Pi Pico Board gemeint!
  • Ebenso, wenn ich schreibe Pin 21 (= GPIO 16) an den Widerstand. Aber mit der Bezeichnung GPIO 16 habe ich schon die Zahl 16 angegeben, die dann im Script als "Pin" verwendet wird.

Dies müssen alle verstanden haben, BEVOR Sie die erste Schaltung in Betrieb nehmen. Ansonsten gerne nochmal nachfragen.

Das richtige "Plus 3,3 V verwenden

Noch eine Warnung: Ohne diese Vorsichtsmaßnahme könnte Ihr Board leicht zerstört werden!

Wenn wir 3,3 Volt auf dem Breadboard benötigen, dann nehmen wir nur und ausschließlich den Pin 36 mit der Bezeichnung "3V3(Out)".

Hier ist das Script. Bitte mit Copy&Paste in Thonny einfügen und nach Änderungen auch den Kommentarheader ändern.

# Externe LED blinken lassen
# J. Thomaschewski, 12.08.2024
from machine import Pin, Timer

# GPIO 16 ist auf Pin 21
led = Pin(16, Pin.OUT) 

def blink(timer):
    led.toggle()

Timer().init(freq=5, callback=blink)

Eine rote und eine grüne LED blinken lassen

Wir erweitern das Script so, dass nun zwei LEDs blinken sollen. Dafür nutzen wir zusätzlich GPIO 15 an Pin 20. Der Pi Pico hat insgesamt 26 GPIOs und theoretisch könnte man somit 26 LEDs unabhängig voneinander blinken lassen. In der Praxis muss man beachten, welchen Strom das Pi Pico Board maximal verkraften kann.

Externe LED blinken

# Externe LED blinken lassen
# J. Thomaschewski, 12.08.2024
from machine import Pin, Timer

# GPIO 16 ist auf Pin 21 (LED rot)
# GPIO 15 ist auf Pin 20 (LED grün)
ledRed   = Pin(16, Pin.OUT)
ledGreen = Pin(15, Pin.OUT)

def blinkRed(timer):
    ledRed.toggle()

def blinkGreen(timer):
    ledGreen.toggle()   


Timer().init(freq=5, callback=blinkRed)
Timer().init(freq=2, callback=blinkGreen)

Eine rote und eine grüne LED abwechselnd blinken lassen

Der Aufbau bleibt gleich und wir ändern lediglich das Script, sodass die rote und die grüne LED nun abwechselnd blinken.

# Externe LED blinken lassen
# J. Thomaschewski, 12.08.2024
from machine import Pin, Timer

# GPIO 16 ist auf Pin 21 (LED rot)
# GPIO 15 ist auf Pin 20 (LED grün)
ledRed   = Pin(16, Pin.OUT)
ledGreen = Pin(15, Pin.OUT)

def blink(timer):
    ledRed.toggle()
    ledGreen.value(not ledRed.value())    

Timer().init(freq=2, callback=blink)

Python

Nun wollen wir Python verstehen. Wir beginnen mit einem sehr einfachen Script, bei dem die interne LED angeschaltet wurde. Sie können die Zeilen in Thonny einfügen und laufen lassen. Die "restliche Schaltung" kann so bleiben und wird nicht genutzt.

1
2
3
4
5
6
7
8
# Bibliotheken laden
from machine import Pin

# Initialisierung der Onboard-LED
led = Pin('LED', Pin.OUT)

# LED einschalten
led.on()

Python verstehen

Script verstehen

Was ist im Script fest vorgegeben und was kann man ändern wie man möchte?

Lösungen

Für die gewünschte Funktionalität ist bis auf led alles vorgegeben. led ist ein frei gewählter Variablenname und kann beliebig umbenannt werden. Aber achten Sie auf einen sinnvollen Namen und machen Sie es nicht wie in diesem Beispiel 😄.

1
2
3
4
5
6
7
8
# Bibliotheken laden
from machine import Pin

# Initialisierung der Onboard-LED
hans = Pin('LED', Pin.OUT)

# LED einschalten
hans.on()

Folgende Fragen wollen wir nun beantworten:

  1. Welche erste Syntax können wir erkennen?
  2. Woher weiß man, wie die Klasse "Pin" verwendet werden kann?
  3. Was kann alles in einer Variable gespeichert werden?
  4. Welche Regeln gibt es für die Benennung von Variablen?

1. Erste Syntax

Syntax Erklärung
# Kommentar
from bibliothek import Klasse Eine Klasse wird genutzt
led = Pin('LED', Pin.OUT) Variablenname = Wert

2. Die Klasse "Pin"

Unter https://docs.micropython.org/ sind alle Bibliotheken und die darin enthaltenen Klassen dokumentiert. Was wir suchen, ist unter https://docs.micropython.org/en/latest/library/machine.Pin.html#machine.Pin zu finden.

Wir können die Dokumentation jetzt noch nicht verstehen und konzentrieren uns auf die für uns derzeit notwendigen Teile.

Syntax Erklärung
Pin.OUT Pin wird als Ausgabe festgelegt. Beispielsweise, weil eine LED daran angeschlossen ist.
Pin.IN Pin wird als Eingabe festgelegt. Beispielsweise, weil ein Helligkeitssensor daran angeschlossen ist.
.on(), .high(), .value(1) Schaltet den Ausgang auf 3,3 V, also an
.off(), .low(), .value(0) Schaltet den Ausgang auf 0 V, also aus

Nerd-Wissen Signal statt Pin

In unserem Beispiel einer einfachen LED wird "Signal" statt "Pin" empfohlen. Bei den einfachen Aufgaben ist es mir an dieser Stelle aber egal. Siehe https://docs.micropython.org/en/latest/library/machine.Signal.html.

3. Was kann alles in einer Variable gespeichert werden?

Typ Beschreibung Beispiel
Integer Ganze Zahlen, z.B. 42 breite = 42
Float Gleitkommazahlen, z.B. 3.14 temperatur = 3.14
String Text, z.B. "Hallo Welt" begruessung = "Hallo Welt"
Boolean Wahrheitswerte, z.B. True oder False istAktiv = True
List Listen von Werten, z.B. [1, 2, 3] zahlen = [1, 2, 3]
Dictionary Schlüssel-Wert-Paare, z.B. {"key": "value"} person = {"name": "Alice"}
Tuple Unveränderliche Listen, z.B. (1, 2, 3) koordinaten = (1, 2, 3)
Set Mengen von eindeutigen Werten, z.B. {1, 2, 3} einzigartigeZahlen = {1, 2, 3}
Object Instanzen von Klassen oder andere Objekte led = Pin()

4. Regeln für die Benennung von Variablen

Bei der Benennung von Variablen in Python sollten bestimmte Regeln und Konventionen beachtet werden, um den Code lesbar, verständlich und fehlerfrei zu halten:

  1. Buchstaben, Ziffern und Unterstriche: Variablennamen dürfen nur Buchstaben (a-z, A-Z), Ziffern (0-9) und Unterstriche (_) enthalten. Allerdings darf ein Variablenname nicht mit einer Ziffer beginnen. Beispiele: breite, tiefe1.

  2. Keine reservierten Schlüsselwörter: Vermeiden Sie die Verwendung von Python-Schlüsselwörtern (z.B. if, else, while), da diese spezielle Bedeutungen haben und zu Syntaxfehlern führen können. Eine Liste der reservierten Schlüsselwörter finden Sie in der Python-Dokumentation.

  3. Keine deutschen Umlaute und Leerzeichen: Verwenden Sie keine deutschen Umlaute (ä, ö, ü) oder Leerzeichen in Variablennamen, da diese zu Kompatibilitätsproblemen führen können. Stattdessen sollten Sie ae, oe, ue verwenden. Beispiel: maxHoehe statt maxHöhe.

  4. Aussagekräftige Namen: Wählen Sie Namen, die den Inhalt oder Zweck der Variablen beschreiben. Dies erleichtert das Verständnis des Codes. Beispiel: Verwenden Sie temperatur statt temp.

  5. Kleinschreibung: Gemäß der PEP 8-Richtlinie sollten Variablennamen in Python normalerweise in Kleinbuchstaben mit Unterstrichen geschrieben werden, z.B. max_temperatur. Da jedoch in vielen Programmiersprachen die CamelCase-Schreibweise bevorzugt wird, wollen wir auch in diesem Kurs CamelCase verwenden. Beispiel: maxTemperatur statt max_temperatur.

  6. Konstanten: Für Konstanten, die sich während der Programmausführung nicht ändern sollen, verwenden Sie Großbuchstaben. Beispiel: PI = 3.14159.

  7. Vermeiden Sie einbuchstabige Namen: Außer in Schleifenindizes (z.B. i, j) sollten einbuchstabige Variablennamen vermieden werden, da sie den Code weniger verständlich machen.

Durch die Einhaltung dieser Regeln wird Ihr Code konsistenter und leichter lesbar, was insbesondere bei der Zusammenarbeit in Teams und der Fehlersuche von großem Vorteil ist.

3.2 LEDs dimmen

Bisher waren die LEDs an oder aus. Nun wollen wir LEDs dimmen und dazu benötigen wir einen Trick, denn die Pins können nur "an"/"aus".

Die Hardware- und Elektronikgrundlagen

Wir benötigen für unsere nächsten Anwendungen:

  • Widerstand (220 Ohm)

  • LED (rot)

  • Drehschalter (Rotary Encoder KY-040)

Die Pins kennen nur die Zustände "an"/"aus", aber es gibt den Trick der Pulsweitenmodulation (PWM). Hierbei wird der Pin schnell mit einer hohen Frequenz "an"/"aus"-geschaltet.

Der Duty Cycle (Einschaltdauer) ist der Prozentsatz der Zeit, in der das Signal in einem Zyklus "ein" ist.

  • Ein 100% Duty Cycle (Einschaltdauer) bedeutet, dass das Signal die ganze Zeit "ein" ist (volle Leistung).

  • Ein 0% Duty Cycle (Einschaltdauer) bedeutet, dass das Signal die ganze Zeit "aus" ist (keine Leistung).

  • Ein 80% Duty Cycle (Einschaltdauer) bedeutet, dass das Signal 80% der Zeit "ein" und 20% der Zeit "aus" ist.

Die Einschaltdauer bezieht sich also auf den prozentualen Anteil der Zeit, in der der Strom eingeschaltet ist, im Vergleich zur Gesamtzeit des "an"/"aus"-Zyklus. Im folgenden Diagramm ist ein 80% Duty Cycle dargestellt, der die LED somit auch nur mit 80% der Leistung versorgt.

PWM

Die Anwendungen

Wir haben hier drei Anwendungen: zuerst lassen wir die rote LED blinken, aber dabei nicht ausgehen, sondern reduzieren die Helligkeit. Danach soll die rote LED langsam heller werden. Anschließend verwenden wir den Drehschalter, um die Helligkeit der LED einzustellen.

Eine rote LED blinken lassen (100% - 20%)

Wir verwenden folgendes Setup (wie zuvor)

Externe LED blinken

  • Pin 38 an Masse

  • Pin 21 (= GPIO 16) an den Widerstand

  • Widerstand an Plus der LED

  • Minus der LED an Masse

# Externe LED blinken lassen 100% zu 20%
# J. Thomaschewski, 12.08.2024

from machine import Pin, PWM, Timer

# GPIO 16 auf Pin 21 ist nun ein PWM-Ausgang mit 1000 Hz
led = PWM(Pin(16))
led.freq(1000)  

# Variablen zur Steuerung der Einschaltdauer (Duty Cycle)
dutyCycleHigh = 65535  # 100% Leistung
dutyCycleLow  = 13107  # 20% Leistung (65535 * 0.2)
state = False            # Startzustand der LED "aus"

def blink(timer):
    global state         # globale Variable wird übernommen
    if state:
        led.duty_u16(dutyCycleLow)  # Setze den Duty Cycle auf 20%
    else:
        led.duty_u16(dutyCycleHigh)  # Setze den Duty Cycle auf 100%
    state = not state     # Wechsel des Zustands False <-> True

Timer().init(freq=2, callback=blink)

Zeile 11: Der Wert 65535 wird verwendet, weil er die maximale Auflösung für den Duty Cycle in der PWM-Implementierung auf dem Raspberry Pi Pico ist. Die Methode duty_u16() erwartet Werte im Bereich von 0 bis 65535, was einer 16-Bit-Auflösung entspricht.

Hinweis: Die Zahl 65535

Bei led.duty_u16 sagt die 16 aus, dass es sich um 16 Bit, also 2 hoch 16 Abstufungen (im Binärsystem) handelt. 2 hoch 16 = 65536. Da wir bei "0" beginnen, ist die größtmögliche Zahl die 65535.

Script verstehen

Was ist im Script fest vorgegeben und was kann man ändern wie man möchte?

Eine rote LED wird langsam heller

Der Aufbau bleibt gleich und wir ändern lediglich das Script

# LED wird langsam heller
# J. Thomaschewski, 13.08.2024

from machine import Pin, PWM
from time import sleep_ms

# GPIO 16 auf Pin 21 ist nun ein PWM-Ausgang mit 1000 Hz
led = PWM(Pin(16))
led.freq(1000)

# Starte mit einem Duty Cycle von 0
duty = 0

# Erhöhe die Helligkeit der LED schrittweise über 3 Sekunden
while duty <= 65535:
    led.duty_u16(duty)  # Setze den Duty Cycle direkt auf den aktuellen Wert
    duty += 65   # Erhöhe den Duty Cycle in angepassten Schritten
    sleep_ms(3)  # Wartezeit von 3 Millisekunden (3 Sekunden / ca. 1000 Schritte)

# LED bleibt auf maximaler Helligkeit

Sourcecode verstehen

Schreiben Sie den Sourcecode so um, dass die LED zuerst 100% hell ist und dann in ca. 3 Sekunden dunkel wird.

Langsamer heller

An welchen beiden Stellen können Sie einstellen, dass die LED nur langsam heller wird?

Drehschalter als Input

Bisher haben wir die GPIO-Pins als Output-Pins für LEDs verwendet. Aber nun wollen wir Daten einlesen. Im konkreten Fall wollen wir Eingabewerte des Drehschalters (Bezeichnung KY-040) einlesen.

Drehschalter Drehschalter

Das hier gezeigte Script basiert auf dem Sourcecode von Elektronik Kompendium und wurde nur leicht abgeändert.

Pinbelegung für Raspberry Pi Pico und KY-040

Raspberry Pi Pico KY-040
Verbindung zu Masse GND
Pin 36 +
Pin 22 - GPIO 17 SW
Pin 24 - GPIO 18 DT
Pin 25 - GPIO 19 CLK

Zunächst muss die Bibliothek "rotary" von Elektronik Kompendium heruntergeladen und unter dem Namen rotary.py auf dem Pi Pico gespeichert werden.

# LED wird mit Drehschalter geändert
# Original von Elektronik Kompendium 
# https://www.elektronik-kompendium.de/sites/raspberry-pi/2703141.htm
# angepasst von J. Thomaschewski, 13.08.2024

from machine import Pin, PWM
from rotary import Rotary

# GPIOs festlegen (LED auf GPIO 16)
sw  = 17 # Taster (Schalter)
dt  = 18 # Datenleitung für Drehen
clk = 19 # Taktleitung für Drehen

# Einstellungen
steps   = 10        # in 10 Schritten änderbar
stepMax = 60000     # 60000 weil nah am Maximum 65535
step    = int(stepMax / steps)

# Initialiserung Drehschalter
rotary  = Rotary(dt, clk, sw)
value   = int(stepMax / 6)

# Initialisierung PWM und LED
led = PWM(Pin(16))
led.freq(1000)
led.duty_u16(value)

# Funktion
def rotaryChanged(change):
    global value
    global step
    global stepMax
    if change == Rotary.ROT_CW:
        value = value + step
        print('Rechts (', value, ')')
    elif change == Rotary.ROT_CCW:
        value = value - step
        print('Links (', value, ')')

    # Korrektur, wenn Wert außerhalb des Bereichs
    if value < 0:
        value = 0
    elif value > stepMax:
        value = 65535

    led.duty_u16(value)  # Helligkeit der LED einstellen

# Überprüft ständig, ob der Drehschalter gedreht wird
rotary.add_handler(rotaryChanged)

Script verstehen

Was ist im Script fest vorgegeben und was kann man ändern wie man möchte?

Taster zum Ein- und Ausschalten nutzen
# LED wird mit Drehschalter geändert und kann geschaltet werden
# Original von Elektronik Kompendium 
# Idee des Ein-/Ausschaltens von Justus Winkler; Umsetzung ChatGPT 
# https://www.elektronik-kompendium.de/sites/raspberry-pi/2703141.htm
# angepasst von J. Thomaschewski, 13.08.2024

from machine import Pin, PWM
from rotary import Rotary
import time

# GPIOs festlegen (LED auf GPIO 16)
sw  = 17  # Taster (Schalter)
dt  = 18  # Datenleitung für Drehen
clk = 19  # Taktleitung für Drehen

# Einstellungen
steps   = 10        # in 10 Schritten änderbar
stepMax = 60000     # 60000 weil nah am Maximum 65535
step    = int(stepMax / steps)

# Initialiserung Drehschalter
rotary  = Rotary(dt, clk, sw)  
value   = int(stepMax / 6)

# Initialisierung PWM und LED
led = PWM(Pin(16))
led.freq(1000)
led.duty_u16(value)

# Variable für den Zustand der LED (an/aus)
ledOn = True

# Initialisierung des Tasters (Schalter)
switch = Pin(sw, Pin.IN, Pin.PULL_UP)

# Funktion für das Drehen des Encoders
def rotaryChanged(change):
    global value
    global step
    global stepMax
    if change == Rotary.ROT_CW:
        value += step
        print('Rechts (', value, ')')
    elif change == Rotary.ROT_CCW:
        value -= step
        print('Links (', value, ')')

    # Korrektur, wenn Wert außerhalb des Bereichs
    if value < 0:
        value = 0
    elif value > stepMax:
        value = 65535

    # Helligkeit der LED anpassen
    if ledOn:  # Nur wenn die LED an ist, die Helligkeit ändern
        led.duty_u16(value)

# Überprüft ständig, ob der Drehschalter gedreht wird
rotary.add_handler(rotaryChanged)

# Funktion für das Drücken des Drehschalter (LED an/aus schalten)
def checkSwitch():
    global ledOn
    if switch.value() == 0:  # Taster gedrückt (aktiv low)
        ledOn = not ledOn  # Zustand der LED umschalten
        if ledOn:
            led.duty_u16(value)  # LED mit aktueller Helligkeit anschalten
            print("LED an")
        else:
            led.duty_u16(0)  # LED ausschalten
            print("LED aus")
        time.sleep(0.2)  # Entprellzeit, um mehrfaches Registrieren zu vermeiden

# Endlos-Schleife, um den Schalter zu überprüfen
while True:
    checkSwitch()  # Überprüfen, ob der Schalter gedrückt wurde

Neu sind:

  • Zeile 9: import time

  • Zeile 30: LED soll am Anfang an sein

  • Zeile 55: Helligkeit nur bei eingeschalteter LED einstellen

  • Alle Zeilen ab Zeile 61

Python

Folgende Fragen wollen wir nun beantworten:

  1. Welche Python Befehle sind in den bisherigen Scripten vorhanden?
  2. Welche Operatoren gibt es in Python?
  3. Welche sind Aufrufe aus Teilen der Bibliothek?

1. Welche Python-Befehle sind in den bisherigen Scripten vorhanden?

Befehl Erklärung
from *bibliothek* import *Klasse* Importiert die Klasse aus der Bibliothek
print(...) Gibt eine Nachricht auf der Konsole aus
def Definiert eine Funktion
if Bedingte Anweisung
elif Weitere Bedingte Anweisung
else Alternative Bedingte Anweisung
while Schleife, die solange eine Bedingung erfüllt ist, wiederholt wird
global Deklariert eine Variable als global
True, False Wahrheitswerte

2. Welche Operatoren gibt es in Python?

A. Rechenoperatoren

Operator Erklärung Beispiel
= Zuweisung a = 5
+ Addition a + b
- Subtraktion a - b
* Multiplikation a * b
** Potenzierung a ** b
/ Division a / b
% Modulus (Restwert einer Division) a % b (z.B. 10 % 3 ergibt 1)
// Ganzzahlige Division a // b (z.B. 10 // 3 ergibt 3)
+= Addition und Zuweisung a += 3 (z.B. a = 2; a += 3 ergibt 5)
-= Subtraktion und Zuweisung a -= 2
*= Multiplikation und Zuweisung a *= 2
**= Potenzierung und Zuweisung a **= 2
/= Division und Zuweisung a /= 2
%= Modulus und Zuweisung a %= 3
//= Ganzzahlige Division und Zuweisung a //= 2

B. Vergleichsoperatoren (die als Ergebnis True / False liefern)

Operator Erklärung Beispiel
== Gleichheit a == b
!= Ungleichheit a != b
< Kleiner als a < b
> Größer als a > b
<= Kleiner oder gleich a <= b
>= Größer oder gleich a >= b
and Logisches UND a and b
or Logisches ODER a or b
not Logisches NICHT not a

Weitere Details zu diesen Operatoren finden Sie in der offiziellen Python-Dokumentation zu den Operatoren.

3. Welches sind Aufrufe aus Teilen der Bibliothek?

Aufruf Erklärung
Pin(...) Initialisiert einen GPIO-Pin
PWM(...) Initialisiert PWM auf einem GPIO-Pin
Timer().init(...) Initialisiert einen Timer mit einer Frequenz und Callback
sleep_ms(...) Pausiert die Ausführung für eine bestimmte Anzahl Millisekunden
Rotary(...) Initialisiert den Rotary-Encoder
rotary.add_handler(...) Fügt einen Handler für den Rotary-Encoder hinzu
.freq(1000) Setzt die Frequenz auf 1000 Hz
.duty_u16(value) Setzt den Duty Cycle auf den angegebenen Wert
.ROT_CW Drehrichtung im Uhrzeigersinn
.ROT_CCW Drehrichtung gegen den Uhrzeigersinn

3.3 LEDs schalten

Jetzt möchten wir die LED ein- und ausschalten können.

Die Hardware- und Elektronikgrundlagen

Wir benötigen für unsere nächsten Anwendungen:

  • Widerstand (220 Ohm)

  • LED (rot)

  • Taster

Basis-Wissen Unterschied Taster und Schalter

Ein Taster schließt den Stromkreis nur, solange er gedrückt wird, und kehrt nach Loslassen in seine Ausgangsposition zurück, wie bei einer Türklingel oder Tastaturtasten. Ein Schalter hingegen bleibt in der gewählten Position (ein/aus), bis er erneut betätigt wird, und schaltet den Stromkreis dauerhaft ein oder aus, wie bei einem einfachen Lichtschalter.

Die Einbauposition des Tasters ist sehr wichtig. Man muss von vorne auf die beiden Anschlüsse schauen können!

Taster

Die Anwendungen

Wir haben zwei Anwendungen, aber in unterschiedlichen "Qualitätsstufen". Zunächst soll die LED angehen, wenn der Taster gedrückt ist und ausgehen, wenn der Taster nicht mehr gedrückt ist. Anschließend wollen wir den Taster über die Programmierung als Schalter verwenden: einmal drücken = an und erneut drücken = aus.

Eine rote LED mit einem Taster steuern - 1

Taster

  • Pin 38 an Masse

  • Pin 36 an Plus

  • Pin 21 (= GPIO 16) an den Widerstand

  • Widerstand an Plus der LED

  • Minus der LED an Masse

  • Pin 20 (GPIO 15) an den Taster

  • Andere Seite des Tasters an Plus

# LED mit Taster - 1
# Datei 3.3a-Taster.py
# J. Thomaschewski, 14.08.2024
from machine import Pin

led    = Pin(16, Pin.OUT)
taster = Pin(15, Pin.IN, Pin.PULL_DOWN)

while True:
    if taster.value() == 1: # Ermittelt automatisch, wenn der Taster gedrückt wird.
        led.on()
    else:
        led.off()

Wenn der Taster gedrückt wird, dann bekommt den Wert taster.value()=1 und in der if-Bedingung steht dann 1=1, was True ist.

Script verstehen

Bauen Sie das Script so um, dass der Taster die LED aus"tastet".

Eine rote LED mit einem Taster steuern - 2

Der Aufbau bleibt gleich und wir ändern lediglich das Script. Aber wir wollen uns eine weitere Lösung ansehen, die didaktisch interessant ist. Dazu wiederholen wir noch mal folgendes led.value(1) = led.high() = led.on() und led.value(0) = led.low() = led.off().

# LED mit Taster - 2
# Original Sourcecode von Rui Santos
# https://RandomNerdTutorials.com/raspberry-pi-pico-outputs-inputs-micropython/
# Datei 3.3b-Taster.py
# angepasst von J. Thomaschewski, 14.08.2024

from machine import Pin

led    = Pin(16, Pin.OUT)
taster = Pin(15, Pin.IN, Pin.PULL_DOWN)

while True:
  led.value(taster.value())

Hier ist die Logik interessant. Wir haben also wieder eine While-Schleife, die als Endlosschleife funktioniert.

  • taster.value() = 0, wenn nicht gedrückt => Dann steht da: led.value(0), wenn wir den Wert 0 von taster.value() einsetzen!

  • taster.value() = 1, wenn gedrückt => Dann steht da: led.value(1), wenn wir den Wert 1 von taster.value() einsetzen!

So wird gerne programmiert und es "sieht schlimm aus, ist es aber nicht".

Den Taster als Schalter verwenden - Zustand speichern

Der Aufbau bleibt gleich und wir ändern lediglich das Script. Beim einmaligen drücken des Tasters soll die LED angeschaltet werden und beim erneuten Drücken ausgeschaltet werden.

Hier lernen wir zunächst einen wichtigen "Trick" der Programmierung kennen!

# Taster mit "Zustand"
# Datei 3.3c-TasterZustand.py
# J. Thomaschewski, 19.10.2024
from machine import Pin

taster = Pin(15, Pin.IN, Pin.PULL_DOWN)
led    = Pin(16, Pin.OUT)

ledStatus = False                   # Hier wird der Antfangszustand festgelegt.

while True:

    if taster.value() == 1:         # Kennen wir schon: wenn der Taster gedrückt wird, dann ist taster.value=1

        ledStatus = not ledStatus   # Hier wird der (Anfangs-)Zustand geändert.
        print(ledStatus)

Mit diesem Script können wir also den Status bei jedem Tastendruck ändern.

Manchmal funktioniert es und manchmal nicht. Das Problem ist der Taster, da die mechanischen Kontakte mehrfach auslösen können. Dies nennt man "prellen". Wir verwenden einen einfachen Workarround, denn das Prellen entsteht beim Tasten in einem Zeitraum von wenigen Millisekunden. Also bauen wir eine kleine zeitliche Verzögerung ein.

Den Taster als Schalter verwenden - mit Prellschutz

Der Aufbau bleibt gleich und wir ändern lediglich das Script.

# Taster mit "Zustand"
# Datei 3.3d-TasterZustandPrellschutz.py
# J. Thomaschewski, 19.10.2024
from machine import Pin
from time import sleep

taster = Pin(15, Pin.IN, Pin.PULL_DOWN)
led    = Pin(16, Pin.OUT)

ledStatus = False

while True:

    if taster.value() == 1:

        ledStatus = not ledStatus
        print(ledStatus)
        sleep(0.2)      # Prellschutz: erst nach 0.2 sec wird die if-Abfrage wiederholt.

Der Operator not ist für das Wechseln des Status sehr wichtig!

Den Taster als Schalter verwenden - LEDs schalten

Das Script für das Ein- und Ausschalten der LED sieht damit wie folgt aus:

# LED mit Taster als Schalter (mit Prellschutz)
# Datei 3.3e-TasterLED.py
# J. Thomaschewski, 19.10.2024
from machine import Pin
from time import sleep

taster = Pin(15, Pin.IN, Pin.PULL_DOWN)
led    = Pin(16, Pin.OUT)

ledStatus = False

while True:

    if taster.value() == 1:

        ledStatus = not ledStatus
        print(ledStatus)

        if ledStatus == True:
            led.on()
        else:
            led.off()

        sleep(0.2)      # Prellschutz: erst nach 0.2 sec wird die if-Abfrage wiederholt.

Wenn man zu lange drückt, dann fängt die LED an zu blinken. Logisch, oder? Soll dies nicht vorkommen, so fügen wir noch eine while-Schleife hinzu, die einfach nur wartet, bis man die Taster losgelassen hat.

# LED mit Taster als Schalter (mit Prellschutz)
# Datei 3.3f-TasterLED-vers2.py
# J. Thomaschewski, 19.10.2024
from machine import Pin
from time import sleep

taster = Pin(15, Pin.IN, Pin.PULL_DOWN)
led    = Pin(16, Pin.OUT)

ledStatus = False

while True:

    if taster.value() == 1:

        ledStatus = not ledStatus
        print(ledStatus)

        if ledStatus == True:
            led.on()
        else:
            led.off()

        sleep(0.2)      # Prellschutz: erst nach 0.2 sec wird die if-Abfrage wiederholt.
        while taster.value() == 1:  # Schleife läuft, bis der Taster loegelassen wurde.
            pass

Scripte sind didaktische Beispiele!

Dieses Script (wie auch alle anderen bisher gezeigten Scripte) eignet sich nicht für den produktiven Einsatz in irgendwelchen kritischen Systemen. Der hier vorgenommene Prellschutz ist "viel zu einfach gedacht", aber didaktisch leicht vermittelbar und eine Lösung für unsere einfachen Anwendungen.

Python

Wir müssen nachsehen, was taster = Pin(15, Pin.IN, Pin.PULL_DOWN) macht. Die Dokumentation gibt es hier: https://docs.micropython.org/en/latest/.

Aufgabe

Erstellen sie ein Script für eine Fußgängerampel. WICHTIG: vor der Programmierung überlegen sie sich die Logik, die sie programmieren möchten und dann gehen sie in kleinen Schritten vor!

3.4 I2C und Display steuern

Wir können mit dem Pi Pico noch so viel mehr steuern. Die LEDs waren ja nur ein einfacher Anfang. Spannend wird auch, dass wir ein Display sehr einfach einbinden können.

Die Hardware- und Elektronikgrundlagen

Wir benötigen für unsere nächsten Anwendungen:

  • Taster

  • OLED Display SSD 1306

    OLED Display SSD 1306

Basis-Wissen OLED-Display

Das OLED Display SSD1306 wird in Elektronikprojekten oft verwendet. Es ist günstig, klein, hat einen hohen Kontrast und einen relativ niedrigen Stromverbrauch, da nur die beleuchteten Pixel Strom verbrauchen. Das Display basiert auf der OLED-Technologie (Organic Light Emitting Diode), bei der jede Pixel einzeln leuchtet.

  • Auflösung: 128x64 Pixel, Schwarz-Weiß
  • Schnittstellen: I2C oder SPI (wir verwenden die I2C-Version)
  • Betriebsspannung: 3,3V oder 5V (wir verwenden die 3,3V-Version)

Die Datenübertragung findet per I2C statt. Für den I2C-Betrieb werden in der Regel vier Pins verwendet:

  • VCC: Versorgungsspannung (3,3V oder 5V)

  • GND: Erdung

  • SCL: I2C Clock (Signal für die Synchronisation der Datenübertragung)

  • SDA: I2C Data (Signal für die Datenübertragung)

Basis-Wissen I2C

I2C (Inter-Integrated Circuit) ist eine weit verbreitete serielle Kommunikation in Elektronikprojekten und ermöglicht die Verbindung zwischen einem Mikrocontroller und verschiedenen Peripheriegeräten über nur zwei Datenleitungen, was die Verkabelung vereinfacht.

I2C verwendet für jeden Gerätetyp eine eindeutige Adresse, um unterschiedliche Geräte an denselben Leitungen anzuschließen und trotzdem zu unterscheiden.

Die Anwendungen

Zunächst nehmen wir das Display in Betrieb (natürlich mit einem Hello World) und dazu benötigen wir die eindeutige I2C-Adresse des OLED-Displays.

I2C-Scanner zur Emittlung der Adresse

Jetzt kann man im Pinout auch erkennen, welche Pins sich für SDA bzw. SCL eignen. Wir nehmen Pin 21 als SDA und Pin 22 als SCL

OLED Display

Der Aufbau sieht wir folgt aus:

OLED Display

OLED Display

Somit haben wir folgende Anschlüsse

  • Pin 38 an Masse

  • Pin 36 an Plus

  • Pin 21 (= GPIO 16, gelb) an OLED-Display SDA

  • Pin 22 (= GPIO 17, grün) an OLED-Display SCL

  • OLED-Display mit Plus und Masse verbinden

# I2C Scanner
# Original Sourcecode von Rui Santos
# https://randomnerdtutorials.com/raspberry-pi-pico-ssd1306-oled-micropython/
# angepasst von J. Thomaschewski, 14.08.2024
from machine import Pin, I2C

i2c = I2C(id=0, scl=Pin(17), sda=Pin(16), freq=10000)

print('I2C SCANNER')
devices = i2c.scan()

if len(devices) == 0:
    print("No i2c device !")

else:
    print('i2c devices found:', len(devices))

    for device in devices:
        print("I2C hexadecimal address: ", hex(device))

Die Ausgabe lautet bei unserem OLED-Display:

I2C SCANNER

i2c devices found: 1

I2C hexadecimal address:  0x3c

Damit haben wir die Adresse für das OLED-Display ermittelt: 0x3c

OLED-Display "Hello World"

Zunächst benötigen wir die Bibliothek für das OLED-Display.

Achtung: es gibt unterschiedliche Bibliotheken für das SSD1306-OLED-Display, auch wenn diese den identischen Dateinamen ssd1306.py verwenden. Hier mal eine Liste nach kurzer Suche gefundenen Bibliotheken.

  • Adafruit MicroPython SSD1306: Diese Bibliothek stammt vom Hersteller Adafruit ist eine der Standardbibliotheken für SSD1306-Displays unter MicroPython. Sie wird nicht mehr aktiv weiterentwickelt. Die Nachfolge-Bibliotheken von Adafruit eignen sich sehr gut für umfangreiche Projekte, ist aber für die Beispiele hier zu komplex.

    • stlehmann/micropython-ssd1306: Diese Bibliothek ist ein Fork der ursprünglichen MicroPython-SSD1306-Treiber und bietet eine Installation über upip, den MicroPython-Paketmanager. Sie hat eine ähnliche API wie die Originalbibliothek von Adafruit.
  • Rui Santos MicroPython SSD1306: Diese Bibliothek wurde von Rui Santos als Teil eines MicroPython-Tutorials entwickelt. Sie bietet grundlegende Funktionen zur Steuerung von SSD1306-Displays über I2C. Die Bibliothek ist auf Einfachheit ausgelegt, was sie ideal für Anfänger*innen macht.

  • FelixSchladt/Micropython-SSD1306-GFX: Diese Bibliothek bietet zusätzliche GFX-Funktionen, die es ermöglichen, geometrische Formen wie Kreise, Rechtecke und Linien auf dem Display zu zeichnen.

  • TimHanewich/MicroPython-SSD1306: Diese Bibliothek ist darauf ausgelegt, Bitmap-Grafiken auf dem SSD1306-Display anzuzeigen. Sie enthält Funktionen, um Bilder in das entsprechende Bitmuster für die Anzeige auf dem OLED zu konvertieren. Die Beispielseite ist sehr interessant!

Merke: es ist sehr zu empfehlen, bei eigenen Programmierungen zunächst einmal zu recherchieren, welche Bibliotheken es gibt und wie die Bibliothek zum Projekt passt.

Wir verwenden hier die Bibliothek von TimHanewich/MicroPython-SSD1306. Somit ist diese Datei ssd1306.py in Thonny einzufügen und auf dem Pi Pico unter dem Namen ssd1306.py abzuspeichern.

Folgende Methoden lassen sich in der Bibliothek aufrufen.

olde.fill(0)

Anschließend diesen Sourcecode ganz normal in Thonny kopieren.

# OLED-Display (I2C) mit Hello World
# J. Thomaschewski, 16.08.2024

from machine import Pin, SoftI2C
from ssd1306 import SSD1306_I2C  # Import der TimHanewich SSD1306 I2C-Klasse

# Initialisierung der I2C-Pins 
i2c = SoftI2C(scl=Pin(17), sda=Pin(16))

# Initialisierung des OLED-Displays mit expliziter I2C-Adresse
oled = SSD1306_I2C(128, 64, i2c, addr=0x3C)

# In der Klammer: Text, x-Startwert, y-Startwert von oben links
oled.fill(0)  # Bildschirm löschen
oled.text('Hello, World 1!', 0, 0)
oled.text('Hello, World 2!', 0, 10)
oled.text('Hello, World 3!', 0, 20)

oled.show()  # Aktualisiere das Display, um den Text anzuzeigen

Ist es nicht wundervoll einfach!?

Ein schönes, etwas komplexeres OLED-Display Beispiel von Tim Hanewich

Nun verwenden wir das hier angegebene Beispiel.

# OLED-Display (I2C) Beispiel
# Basieren auf Sourcecode von Tim Hanewich
# https://github.com/TimHanewich/MicroPython-SSD1306/tree/master
# leicht abgeändert von J. Thomaschewski, 16.08.2024

from machine import Pin, SoftI2C
from time import sleep
from ssd1306 import SSD1306_I2C
import framebuf

# create I2C interface
i2c = SoftI2C(scl=Pin(17), sda=Pin(16))
print(i2c.scan()) # 0x3c is the I2C address of the SSD1306. As an integer, 60.

oled = SSD1306_I2C(128, 64, i2c, addr=0x3C)

# fill in a single pixel
oled.fill(0)
oled.pixel(0, 0, 1) # turn on pixel at (0, 0) (top left)
oled.pixel(64, 0, 1) # turn on pixel at (64, 0), middle top
oled.pixel(127, 63, 1) # turn on pixel at (127, 63) (absolute bottom right on my 128x64 display)
oled.show() # show the update
sleep(1)

# display text
oled.fill(0)
oled.text("Hello, world!", 0, 0) # print text "Hello, world!" at position (0, 0) (top left)
oled.show()
sleep(1)


# Load smiley face image and display
# The below bytearray is a buffer representation of a 32x32 smiley face image. This is explained in this documentation below - continue reading!
oled.fill(0)
smiley = bytearray(b'\x00?\xfc\x00\x00\xff\xff\x00\x03\xff\xff\xc0\x07\xe0\x07\xe0\x0f\x80\x01\xf0\x1f\x00\x00\xf8>\x00\x00|<\x00\x00<x\x00\x00\x1epx\x1e\x0e\xf0x\x1e\x0f\xe0x\x1e\x07\xe0x\x1e\x07\xe0\x00\x00\x07\xe0\x00\x00\x07\xe0\x00\x00\x07\xe1\xc0\x03\x87\xe1\xc0\x03\x87\xe1\xc0\x03\x87\xe1\xe0\x07\x87\xe0\xf0\x0f\x07\xf0\xf8\x1f\x0fp\x7f\xfe\x0ex?\xfc\x1e<\x0f\xf0<>\x00\x00|\x1f\x00\x00\xf8\x0f\x80\x01\xf0\x07\xe0\x07\xe0\x03\xff\xff\xc0\x00\xff\xff\x00\x00?\xfc\x00')
fb = framebuf.FrameBuffer(smiley, 32, 32, framebuf.MONO_HLSB) # load the 32x32 image binary data in to a FrameBuffer
oled.blit(fb, 0, 0) # project or "copy" the loaded smiley image FrameBuffer into the OLED display at position 0,0 (the top left of the smiley will touch the top left corner of the display)
oled.show()
sleep(1)


# load buffer
oled.fill(0)
hello_world_buf = bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x0f\x80\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x01\xff?\xc0\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x01\xe7\xff\xc0\x00\x00\x00\x04\x00\x00\x00\xe0\x00\x00\x00\x00\x01\xc7\xf9\xc0\x00\x00\x10\x0e\x00\x00\x00\xe0\x00\x00\x00\x00\x01\xc7\xe1\xc0\x00\x008\x0e\x00\x00\x00\xe1\x00\x00\x00\x00\x01\xc3\xc1\xc0\x00\x008\x0e\x00\x00\x00\xe3\x80\x00\x00\x00\x01\xc0\x01\xc0\x00\x008\x0e\x00\x00\x00\xe3\x80\x00\x00\x00\x01\xc0\x03\xc0\x00\x00x\x0e\x00\x00\x00\xe3\x80\x00\x00\x00\x01\xc0\x03\x80\x00\x00p\x0e\x00\x00\x00\xe3\x80\x00\x00\x00\x01\xc0\x07\x80\x00\x00p\x0e\x01\xfc\x00\xe3\x80\x00\x00\x00\x01\xe0\x0f\x80\x00\x00p\x0f\xc3\xfe\x00\xe3\x80\x00\x00\x00\x00\xf0?\x00\x00\x00\xf7\xff\xe7\xfe\x00\xe3\x80\x07\x00\x00\x00\x7f\xfe\x00\x00\x00\xef\xff\xef\x8e\x00\xe3\x80\x0f\x80\x00\x00?\xfc\x00\x00\x00\xe7\xfe\x0f\x0e\x00\xe3\x80\x1f\x80\x00\x00\x1f\xf0\x00\x00\x00\xe0\x1c\x1e\x1e\x00\xe3\x80?\x80\x00\x00\x0f\x00\x00\x00\x00\xe0\x1c\x1f\xfe\x00\xe3\x80\x7f\x80\x00\x00\x00\x00\x00\x00\x00\xe0<\x1f\xfc\x00\xe3\x80\xf3\x80\x00\x00\x00\x00\x00\x00\x01\xe08\x1f\xf8\x00\xe3\x81\xe3\x80\x00\x00\x00\x00\x00\x00\x01\xc08\x0f\x00\x00\xe3\x81\xe3\x80\x00\x00\x00\x00\x00\x00\x01\xc08\x07\xe0\x00\xe3\x81\xc3\x80\x00\x00\x00\x00\x00\x00\x01\xc08\x03\xfc@\xe3\x81\xc3\x80\x00\x00\x00\x00\x00\x00\x01\xc08\x01\xff\xe0c\x81\xc7\x80\x00 \x00\x00\x00\x00\x01\xc0x\x00?\xe0\x01\x81\xcf\x00\x00p\x00\x00\x00\x00\x01\xc0x\x00\x07\xe0\x00\x01\xff\x00\x00p\x00\x00\x00\x00\x00\xc00\x00\x00\x00\x00\x01\xff\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x00\x00\xf0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\xf0\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x01\xf0\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x01\xf0\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x01\xf0\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe0\x01\xf0\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\x01\xf0\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x01\xc0\x01\xf0\x03\x80\x00\x00\x00\x00\x00\x04\x00\x00\x00\x0e\x01\xc0\x01\xf0\x03\x80\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x1e\x01\xc0\x01\xf0\x03\x80\x00\x00\x00\x00\x0c\x0e\x00\x0c\x00\x1e\x01\xc0\x03\xf0\x03\x80\x00\x00\x18\x00\x1e\x1e\x00\x1f\x00\xfc\x01\xc0\x03\xf0\x03\x80\x00\x00<\x00>\x1c\x00\x7f\x81\xf8\x01\xc0\x03\xf0\x03\x80\x00\x00\x1c\x00~<\x00\xff\x83\xf8\x01\xc0\x03\xf0\x03\x80\x00\x00\x1e\x00~8\x01\xfb\x83\xe0\x01\xc0\x07\xe0\x03\x80\x00\x00\x0e\x00~8\x03\xe3\x83\xc0\x01\xc0\x1f\xe0\x03\x80\x00\x00\x0f\x00\xfex\x07\xc3\x83\xc0\x01\xc0?\xe0\x03\x80\x00\x00\x07\x00\xefp\x0f\x03\x83\x80\x01\xc0\x7f\xe0\x03\x80\x00\x00\x07\x81\xe7\xf0\x0f\x03\x87\x80\x01\xc0\xff\xe0\x03\x80\x00\x00\x03\x83\xc7\xe0\x0e\x07\x87\x00\x01\xc1\xef\xe0\x03\x80\x00\x00\x03\xc3\xc7\xe0\x1e\x07\x07\x00\x01\xc3\xee\xe0\x01\x80\x00\x00\x01\xe7\x87\xc0\x1c\x0f\x07\x00\x01\xc3\xce\xe0\x00\x00\x00\x00\x01\xef\x87\xc0\x1c\x0f\x07\x00\x01\xc3\x8e\xe0\x00\x00\x00\x00\x00\xfe\x07\xc0\x1c\x1e\x07\x00\x01\xc7\x8e\xe0\x00\x00\x00\x00\x00~\x03\x80\x1e>\x07\x00\x01\xc7\x1e\xe0\x00\x00\x00\x00\x00>\x00\x00\x0f\xfc\x07\x00\x01\xc7<\xe0\x00\x00\x00\x00\x00\x1c\x00\x00\x0f\xf8\x03\x00\x01\xc7|`\x01\x00\x00\x00\x00\x00\x00\x00\x07\xf0\x00\x00\x01\xc7\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc7\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')

import framebuf
hello_world_img = framebuf.FrameBuffer(hello_world_buf, 128, 64, framebuf.MONO_HLSB) # load as an image 128 pixels wide, 64 pixels high
oled.blit(hello_world_img, 0, 0) # write the 128x64 image to the SSD-1306 display, positioned in the top left corner
oled.show() # show on the display

OLED-Display Taster zählen

Wir erweitern das Script. Es soll gezählt werden, wie oft der Taster gedrückt wurde.

Wir brauchen also zusätzlich

  • Pin 20 (GPIO 15) an den Taster

  • Andere Seite des Tasters an Plus

OLED Display

# OLED-Display (I2C) mit Hello World
# J. Thomaschewski, 16.08.2024

from machine import Pin, SoftI2C
from time import sleep
from ssd1306 import SSD1306_I2C  # Import der TimHanewich SSD1306 I2C-Klasse

# Initialisierung der I2C-Pins 
i2c = SoftI2C(scl=Pin(17), sda=Pin(16))

# Initialisierung des OLED-Displays mit expliziter I2C-Adresse
oled = SSD1306_I2C(128, 64, i2c, addr=0x3C)

# Taster initialisieren (GPIO 15)
taster = Pin(15, Pin.IN, Pin.PULL_DOWN)

# Zähler für die Anzahl der Tasterdrücke
counter = 0

# Funktion zum Aktualisieren des Displays
def updateDisplay():
    oled.fill(0)  # Bildschirm löschen
    oled.text('Anzahl ermitteln', 0, 0)
    oled.text('Counter: {}'.format(counter), 20, 30)
    oled.show()

# Funktion, die beim Tasterdruck aufgerufen wird
def countButtonPress(pin):       
    global counter
    sleep(0.1)  # Entprellung des Tasters durch kurze Verzögerung
    if pin.value() == 1:  # Überprüfen, ob der Taster nach der Verzögerung noch gedrückt ist
        counter += 1
        updateDisplay()
        print('Taster gedrückt! Zähler: {}'.format(counter))

# Interrupt-Handler für den Taster einrichten
taster.irq(trigger=Pin.IRQ_RISING, handler=countButtonPress)

# Initiale Anzeige
updateDisplay()

Python

Wir wollen jetzt das Script analysieren. Zunächst wurden die bisherigen Kommentarzeilen gelöscht und Zeilen zu Blöcken anders zusammengefasst. Ein Sourcecode ist immer ähnlich aufgebaut.

# Zuerst werden alle Bibliotheken geladen
from machine import Pin, SoftI2C
from time import sleep
from ssd1306 import SSD1306_I2C

# Jetzt wird alles initialisiert und Variablen gesetzt
i2c = SoftI2C(scl=Pin(17), sda=Pin(16))
oled = SSD1306_I2C(128, 64, i2c, addr=0x3C)
taster = Pin(15, Pin.IN, Pin.PULL_DOWN)
counter = 0

# Nun werden die Funktionen definiert
def updateDisplay():
    oled.fill(0)  
    oled.text('Anzahl ermitteln', 0, 0)
    oled.text('Counter: {}'.format(counter), 20, 30)
    oled.show()

def countButtonPress(pin):       
    global counter
    sleep(0.1)  
    if pin.value() == 1:  
        counter += 1
        updateDisplay()
        print('Taster gedrückt! Zähler: {}'.format(counter))

##### Hier beginnt das eigentliche Hauptprogramm #####
taster.irq(trigger=Pin.IRQ_RISING, handler=countButtonPress)
updateDisplay() # Initiale Anzeige

Diese oben dargestellte Reihenfolge findet sich in jeder guten Programmierung.

Nerd-Wissen Mit ChatGPT die ssd1306-Bibliothek erweitern

Mit Hilfe von ChatGPT habe ich die Bibliothek ssd1306.py in der Klasse um folgende Methoden ergänzt, damit auch große Schrift dargestellt werden kann. Damit die ursprüngliche Bibliothek erhalten bleibt, wird die neue Bibliothek unter ssd1306big.py auf dem Pi Pico abgespeichert.

# Diese Klasse suchen
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
    self.i2c = i2c
    self.addr = addr
    self.temp = bytearray(2)
    self.write_list = [b"\x40", None]  # Co=0, D/C#=1
    super().__init__(width, height, external_vcc)

def write_cmd(self, cmd):
    self.temp[0] = 0x80  # Co=1, D/C#=0
    self.temp[1] = cmd
    self.i2c.writeto(self.addr, self.temp)

def write_data(self, buf):
    self.write_list[1] = buf
    self.i2c.writevto(self.addr, self.write_list)

# Hier kommen die neuen Methoden hinzu
def big_text(self, string, x, y, col=1):
    for i, char in enumerate(string):
        self.draw_big_char(char, x + i*16, y, col)

def draw_big_char(self, char, x, y, col=1):
    char_buf = bytearray(8)
    fb = framebuf.FrameBuffer(char_buf, 8, 8, framebuf.MONO_VLSB)
    fb.text(char, 0, 0, col)
    for j in range(8):
        for i in range(8):
            pixel = fb.pixel(i, j)
            if pixel:
                self.pixel(x + i*2, y + j*2, col)
                self.pixel(x + i*2 + 1, y + j*2, col)
                self.pixel(x + i*2, y + j*2 + 1, col)
                self.pixel(x + i*2 + 1, y + j*2 + 1, col)

# Alle anderen Zeilen bleiben unverändert.

Mit folgendem Script kann man es dann ausprobieren

# OLED-Display (I2C) Taster zählen mit großem Text
# J. Thomaschewski, 16.08.2024

from machine import Pin, SoftI2C
from time import sleep
from ssd1306big import SSD1306_I2C  # Import der TimHanewich SSD1306 I2C-Klasse

# Initialisierung des I2C-Busses für das OLED-Display
i2c = SoftI2C(scl=Pin(17), sda=Pin(16))

# Initialisierung des OLED-Displays mit expliziter I2C-Adresse
oled = SSD1306_I2C(128, 64, i2c, addr=0x3C)

# Taster initialisieren (GPIO 15)
taster = Pin(15, Pin.IN, Pin.PULL_DOWN)

# Zähler für die Anzahl der Tasterdrücke
counter = 0

# Funktion zum Aktualisieren des Displays
def updateDisplay():
    oled.fill(0)  # Bildschirm löschen
    oled.big_text('Count:', 10, 0)
    oled.big_text(str(counter), 50, 40)
    oled.show()

# Funktion, die beim Tasterdruck aufgerufen wird
def countButtonPress(pin):       
    global counter
    sleep(0.2)  # Entprellung des Tasters durch kurze Verzögerung
    if pin.value() == 1:  # Überprüfen, ob der Taster nach der Verzögerung noch gedrückt ist
        counter += 1
        updateDisplay()
        print('Taster gedrückt! Zähler: {}'.format(counter))

# Interrupt-Handler für den Taster einrichten
taster.irq(trigger=Pin.IRQ_RISING, handler=countButtonPress)

# Initiale Anzeige
updateDisplay()

OLED Display SSD 1306

Insgesamt habe ich dafür weniger als 20 Minuten benötigt, musste aber ChatGPT mehrere Fehlerhinweise geben, da die Displaydarstellung zunächst gedreht war. Man muss also schon wissen, wo die Fehler liegen (zumindest bei ChatGPT 4o, August 2024).

Ohne ChatGPT wäre ich nicht auf die Idee gekommen, die Bibliothek zu ändern, da diese "kleine" Änderung auch für mich sehr viele Stunden Arbeit bedeutet hätte.

ChatGPT ist in der Programmierung ein sehr mächtiges Werkzeug und es besteht die Gefahr umfangreichen Sourcecode zu erzeugen, den man nicht mehr im Griff hat (der irgendwo fehlerhaft ist oder sich nicht mehr erweitern lässt).

3.5 Licht Sensor BH 1750

Jetzt wollen wir die Helligkeit mit dem Lichtsensor BH 1750 messen.

Die Hardware- und Elektronikgrundlagen

Wir benötigen für unsere nächsten Anwendungen:

  • OLED Display SSD 1306

    OLED Display SSD 1306

  • Lichtsensor BH 1750

    Lichtsensor BH 1750

Basis-Wissen Lichtsensor BH1750

Der Lichtsensor BH1750 ist guter und kostengünstiger Lichtsensor mit einer direkten Ausgabe der Lichtintensität in Lux, ohne dass eine zusätzliche Kalibrierung erforderlich ist.

  • Messbereich: 1 - 65535 Lux
  • Schnittstelle: I2C
  • Betriebsspannung: 3,3V oder 5V (wir verwenden die 3,3V-Version)

Die Datenübertragung findet per I2C statt. Für den I2C-Betrieb werden in der Regel vier Pins verwendet:

  • VCC: Versorgungsspannung (3,3V oder 5V)

  • GND: Erdung

  • SCL: I2C Clock (Signal für die Synchronisation der Datenübertragung)

  • SDA: I2C Data (Signal für die Datenübertragung)

Die Anwendungen

Zunächst nehmen wir nur den Lichtsensor in Betrieb und dann erweitern wir das Script, sodass die Ausgabe nicht im Thonny-Kommandofenster erfolgt, sondern auf dem Display.

Inbetriebnahme des Lichtsensors

Der Aufbau sieht wir folgt aus:

OLED Display

OLED Display

Somit haben wir die bisherigen OLED-Anschlüsse, ergänzgt um den Lichtsensor.

  • Pin 38 an Masse

  • Pin 36 an Plus

  • Pin 21 (= GPIO 16, gelb) an OLED-Display SDA

  • Pin 22 (= GPIO 17, grün) an OLED-Display SCL

  • OLED-Display mit Plus und Masse verbinden

  • Lichtsensor BH 1750 mit Plus und Masse verbinden

  • Lichtsensor SDA an an OLED-Display SDA (gelb)

  • Lichtsensor SCL an an OLED-Display SCL (grün)

Wir haben wieder die Schritte

  1. Adresse ermitteln mit dem I2C-Scanner. Ergebnis: 0x23

  2. Bibliothek installieren. Auch hier gibt es verschiedene Bibliotheken und es ist nicht leicht eine gute Bibliothek zu finden (siehe z.B. diese Suchergebnisse: https://github.com/search?q=Micropython+bh1750&type=repositories&p=1)

    Wir verwenden das Beispiel von Rui Santos https://randomnerdtutorials.com/raspberry-pi-pico-bh1750-micropython/

    Link zur Bibliothek von Tim Pelling, die aber auf der Originalseite nicht mehr vorhanden ist und daher von Rui Santos heruntergeladen wird. Abspeichern auf dem Pi Pico unter bh1750.py.

# Anzeige der Helligkeit (Sensor BH1750) im Terminal
# BH1750-Basis von Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-bh1750-micropython/
# i2c-Zeile angepasst von J. Thomaschewski, 16.08.2024

from machine import Pin, SoftI2C
from bh1750 import BH1750
import time

# Initialize I2C communication (GPIO angepasst)
i2c = SoftI2C(scl=Pin(17), sda=Pin(16), freq=400000)

# Create BH1750 object
lightSensor = BH1750(bus=i2c, addr=0x23)

try:
    # Read lux every 2 seconds
    while True:
        lux = lightSensor.luminance(BH1750.CONT_HIRES_1)
        print("Luminance: {:.2f} lux".format(lux))
        time.sleep(2)

except Exception as e:
    # Handle any exceptions during sensor reading
    print("An error occurred:", e)
Die Ergebnisse werden in der Thonny-Kommandozeile angezeigt.

Ausgabe des Lichtsensors auf dem OLED-Display

Nun wollen wir die Helligkeit auf dem OLED-Display anzeigen.

# Anzeige der Helligkeit (Sensor BH1750) auf OLED-Display
# BH1750-Basis von Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-bh1750-micropython/
# Erweitert um Display-Anzeige J. Thomaschewski, 16.08.2024

from machine import Pin, SoftI2C
from bh1750 import BH1750
from ssd1306 import SSD1306_I2C  # Import der OLED-Bibliothek
from time import sleep

# Initialisierung der I2C-Pins für den BH1750 und das OLED-Display
i2c = SoftI2C(scl=Pin(17), sda=Pin(16), freq=400000)

# Initialisierung des BH1750-Sensors
lightSensor = BH1750(bus=i2c, addr=0x23)

# Initialisierung des OLED-Displays (128x64 Pixel)
oled = SSD1306_I2C(128, 64, i2c)

try:
    # Lux-Wert alle 2 Sekunden auslesen und auf dem OLED-Display anzeigen
    while True:
        lux = lightSensor.luminance(BH1750.CONT_HIRES_1)
        print("Luminance: {:.2f} lux".format(lux))

        # OLED-Display löschen
        oled.fill(0)
        oled.text("Helligkeit", 15, 24)
        oled.text("{:.2f} lux".format(lux), 15, 40)
        oled.show()

        sleep(2)

except Exception as e:
    # Fehlerbehandlung
    print("An error occurred:", e)

Python

Zunächst eine Kleinigkeit.

Import Erklärung Aufruf
import time Importiert time komplett time.sleep(2)
from time import sleep Importiert nur sleep sleep(2)

Fehlerbehandlung in Python: try - except

In Python wird eine Fehlerbehandlung mit den Schlüsselwörtern try und except durchgeführt. Damit können wir auf Fehler in einem Programm reagieren, anstatt dass das Programm bei einem Fehler einfach abstürzt.

Funktionsweise

  • try: Der Code, der in den try-Block geschrieben wird, ist der Teil des Programms, der möglicherweise einen Fehler verursachen könnte. Python versucht, diesen Code auszuführen.

    In diesem Block wird der Hauptteil des Codes ausgeführt, der den Lux-Wert vom Lichtsensor liest und ihn auf dem OLED-Display anzeigt. Dieser Code wird in einer Schleife ausgeführt, um kontinuierlich den Helligkeitswert zu aktualisieren.

  • except: Falls im try-Block ein Fehler auftritt, wird der except-Block ausgeführt. Hier kann definiert werden, was im Fehlerfall passieren soll, z.B. eine Fehlermeldung ausgeben oder das Programm in einen sicheren Zustand versetzen.

    Falls während der Ausführung des Codes im try-Block ein Fehler auftritt (z.B. ein Problem mit der Verbindung zum Sensor oder Display), wird der except-Block aktiviert. Das Programm beendet sich damit kontrolliert. In diesem Fall wird nur eine einfache Nachricht ausgegeben, damit das Programm an dieser Stelle nicht zu lang wird. Anstelle einer einfachen Ausgabe könnte auch ein Eintrag in eine Log-Datei geschrieben werden und eine E-Mail (oder eine sonstige Warnung) verschickt werden.

Beispiel im Kontext

In dem Script wird try - except folgendermaßen verwendet:

try:
    # Lux-Wert alle 2 Sekunden auslesen und auf dem OLED-Display anzeigen
    while True:
        lux = lightSensor.luminance(BH1750.CONT_HIRES_1)
        print("Luminance: {:.2f} lux".format(lux))

        # OLED-Display löschen
        oled.fill(0)
        oled.text("Helligkeit", 15, 24)
        oled.text("{:.2f} lux".format(lux), 15, 40)
        oled.show()

        sleep(2)

except Exception as e:
    # Fehlerbehandlung
    print("An error occurred:", e)

3.6 Temperatur und Luftdrucksensor BMP 280

Jetzt wollen wir die Temperatur und den Luftdruck mit dem Sensor BMP 280 messen. Und wir haben "das gleiche Spiel wie zuvor".

Die Hardware- und Elektronikgrundlagen

Wir benötigen für unsere nächsten Anwendungen:

  • OLED Display SSD 1306

    OLED Display SSD 1306

  • Sensor BMP 280

    BMP 280

Basis-Wissen Temperatursensor BMP280

Der BMP280 ist ein Sensor zur Messung von Temperatur und Luftdruck. Er ist kompakt, präzise und eignet sich gut für Wetterstationen und Höhenmessungen. Es gibt noch einen ähnlichen Sensor BME280, der Temperatur, Luftfeuchtigkeit und Luftdruck mist.

  • Messbereich:
    • Temperatur: -40°C bis +85°C (±1.0°C)
    • Luftdruck: 300 hPa bis 1100 hPa (±1 hPa, entspricht etwa ±8.4 Meter in der Höhe)
  • Schnittstelle: I2C oder SPI (wir verwenden die I2C-Version)
  • Betriebsspannung: 1,8V bis 3,6V (wir verwenden die 3,3V-Version)

Die Datenübertragung findet per I2C statt. Für den I2C-Betrieb werden in der Regel vier Pins verwendet:

  • VCC: Versorgungsspannung (3,3V oder 5V)

  • GND: Erdung

  • SCL: I2C Clock (Signal für die Synchronisation der Datenübertragung)

  • SDA: I2C Data (Signal für die Datenübertragung)

Die Anwendungen

Zunächst nehmen wir nur den Temperatur- und Luftdrucksensor in Betrieb und dann erweitern wir das Script, sodass die Ausgabe nicht im Thonny-Kommandofenster erfolgt, sondern auf dem Display.

Sensoren und Display

Es gibt sehr viele I2C-Sensoren und Displays aller Art.

Nerd-Wissen I2C

Eine sehr gute Info zum I2C findet sich bei Wikipedia.

Inbetriebnahme des Lichtsensors

Der Aufbau sieht wir folgt aus:

OLED Display

OLED Display

Somit haben wir die bisherigen OLED-Anschlüsse, ergänzgt um den Lichtsensor.

  • Pin 38 an Masse

  • Pin 36 an Plus

  • Pin 21 (= GPIO 16, gelb) an OLED-Display SDA

  • Pin 22 (= GPIO 17, grün) an OLED-Display SCL

  • OLED-Display mit Plus und Masse verbinden

  • Temperatursensor BMP 280 mit Plus und Masse verbinden

  • BMP 280 SDA an an OLED-Display SDA (gelb)

  • BMP 280 SCL an an OLED-Display SCL (grün)

Wir haben wieder die Schritte

  1. Adresse ermitteln mit dem I2C-Scanner. Ergebnis: 0x76

  2. Bibliothek installieren. Wir verwenden die Bibliothek und das Beispiel von David Stellvan.

    Link zur Bibliothek von David Stellvan. Abspeichern auf dem Pi Pico unter bmp280.py.

# Anzeige von Temperatur und Luftdruck (Sensor BMP 280) im Terminal
# BMP280-Basis der Bibliothek von David Stellvan
# https://github.com/dafvid/micropython-bmp280
# Angepasst von J. Thomaschewski, 16.08.2024

from machine import Pin, SoftI2C
from bmp280 import *

# Initialize I2C communication (GPIO angepasst)
i2c = SoftI2C(scl=Pin(17), sda=Pin(16), freq=400000)
bmp = BMP280(i2c)

print(bmp.temperature)
print(bmp.pressure)
Die Ergebnisse werden in der Thonny-Kommandozeile angezeigt.

Übung

In dem Beispiel werden die Daten nur einmal angezeigt. Bauen Sie eine einfache Schleife (gerne ohne try-execpt) wie im Beispiel zum Lichtsensor, damit die Werte alle 2 Sekunden ausgegeben werden.

Ausgabe des Lichtsensors auf dem OLED-Display

Nun wollen wir die Helligkeit auf dem OLED-Display anzeigen.

# Anzeige von Temperatur und Luftdruck (Sensor BMP 280) im Terminal und auf OLED-Display
# BMP280-Basis von David Stellvan
# https://github.com/dafvid/micropython-bmp280
# Angepasst von J. Thomaschewski, 16.08.2024

from machine import Pin, SoftI2C
from bmp280 import *
from ssd1306 import SSD1306_I2C  # Import der OLED-Bibliothek
from time import sleep

# Initialize I2C communication (GPIO angepasst)
i2c = SoftI2C(scl=Pin(17), sda=Pin(16), freq=400000)
bmp = BMP280(i2c)

# Initialisierung des OLED-Displays (128x64 Pixel)
oled = SSD1306_I2C(128, 64, i2c)

while True:
    # Messwerte erfassen
    temperature = bmp.temperature
    pressure = bmp.pressure / 100  # hPa

    # Ausgabe im Terminal
    print("Temperatur: {:.2f}°C".format(temperature))
    print("Luftdruck:  {:.2f} hPa".format(pressure))

    # Anzeige auf dem OLED-Display
    oled.fill(0)  # Bildschirm löschen
    oled.text("Temperatur", 0, 8)
    oled.text("{:.2f} C".format(temperature), 0, 20)
    oled.text("Luftdruck", 0, 40)
    oled.text("{:.2f} hPa".format(pressure), 0, 52)
    oled.show()  # Display aktualisieren

    sleep(2)

Damit verlassen wir das Thema der Sensoren und gehen zu einem weiteren spannenden Thema: Eine WLAN-Verbindung zum Pi Pico herstellen.

3.7 WLAN

Der Raspberry Pi Pico W hat ein WLAN (WIFI) und Bluetooth-Chip auf der Platine. Und dieses WLAN wollen wir nun nutzen.

Die Hardware- und Elektronikgrundlagen

Wir verwenden den Raspberry Pi Pico W (Version 1). Er hat den WLAN Chip von Infineon CYW43439 verbaut und nutzt somit den WLAN-Standard IEEE 802.11b/g/n (von 2009) auf 2,4 GHz. Der alte WLAN-Standard hat den Vorteil, auch älteren WLAN-Routern problemlos zu funktionieren.

Wir benötigen für unsere nächsten Anwendungen den Pi Pico auf dem Breadboard und das OLED Display SSD 1306, angeschlossen wie zuvor.

Die Anwendungen

Zunächst wollen wir nur die WLAN-Verbindung herstellen, bevor wir dann im nächsten Schritt einige Anwendungen realisieren, die Internetdienste verwenden.

Inbetriebnahme der WLAN-Verbindung

Hier nun ein einfaches Script zu WLAN-Verbindung. Dieses Script wollen wir in Zukunft nutzen und um entsprechende Dienste erweitern.

In Zeile 9 muss die SSID und das PASSWORD durch die aktuellen Zugangsdaten ersetzt werden!

# WLAN-Verbindung herstellen
# J. Thomaschewski, 17.08.2024

import network

# WLAN aktivieren und verbinden. SSID und PASSWORD ersetzen
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect("SSID", "PASSWORD") # SSID und PASSWORD ersetzen

# Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    pass

# Verbindung erfolgreich
print("WLAN verbunden, IP-Adresse:", wlan.ifconfig()[0])

Wenn die WLAN-Verbindung nicht funktioniert...

Meistens liegt es daran, dass die SSID oder das PASSWORD falsch geschrieben wurden. Wenn man aber gaaaanz sicher ist, dass das Problem woanders liegt, dann kommen hier drei Scripte als Abhilfe.

1. WLAN Netzwerke anzeigen

Es kann ja sein, dass das gewünschte Netzwerk beim Pi Pico nicht ankommt.

# Vorhandene WLAN-Netzwerke ermitteln
# J. Thomaschewski, 16.08.2024

import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

networks = wlan.scan()

print("Vorhandene WLAN-Netzwerke:")
for network_info in networks:
    print(network_info)
Das gewünschte Netzwerk muss aufgeführt sein, damit man sich damit verbinden kann. Wenn das Netzwerk nicht aufgeführt ist, dann ist es entweder zu weit entfernt sein (Funkwellen kommen nicht an) oder ausgeschaltet oder es hat einen anderen Namen.

2. MAC Adresse des eigene Pi Pico anzeigen

Jeder Funkchip hat eine eindeutige MAC-Adresse. Und viele WLAN-Router zeigen in ihrer Weboberfläche an, wenn ein Gerät versucht hat sich erfolglos anzumelden.

# Eigene MAC-Adresse ermitteln
# J. Thomaschewski, 17.08.2024

import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

mac = wlan.config('mac')

mac_address = ':'.join(['{:02x}'.format(b) for b in mac])
print("WiFi MAC-Adresse:", mac_address)
3. Ausführliches Script zur WLAN-Verbindung

Hier kommt ein ausführliches Script zur Herstellung einer WLAN-Verbindung mit Ausgabe der aktuellen Statusinformationen. Dieses Script ist mit Hilfe von ChatGPT entstanden, da meine Verbindungen stehts mit meinem einfachen Script funktioniert haben.

# WLAN-Verbindung herstellen
# Erstellt von ChatGPT
# J. Thomaschewski, 17.08.2024

import network
import time

# WLAN-Verbindungsdaten
SSID = "XXXXXXXXXXX"      # Ersetze durch deinen WLAN-Namen (SSID)
PASSWORD = "XXXXXXXXXXXX"  # Ersetze durch dein WLAN-Passwort

def connect_to_wifi(ssid, password):
    # WLAN-Station-Interface initialisieren
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    # WLAN-Verbindung herstellen
    if not wlan.isconnected():
        print("Versuche, eine Verbindung zu WLAN herzustellen...")
        wlan.connect(ssid, password)

        # Warte, bis die Verbindung hergestellt ist
        for attempt in range(20):  # 20 Versuche, jeweils 0,5 Sekunden
            if wlan.isconnected():
                print("WLAN verbunden!")
                print("IP-Adresse:", wlan.ifconfig()[0])
                return wlan
            else:
                print("Verbindung wird aufgebaut, Versuch", attempt + 1)
                time.sleep(0.5)

        # Wenn die Verbindung nicht hergestellt wurde
        if not wlan.isconnected():
            print("Fehler: Verbindung zu WLAN konnte nicht hergestellt werden.")
            print("Überprüfe SSID und Passwort.")
            return None
    else:
        print("Bereits mit WLAN verbunden.")
        print("IP-Adresse:", wlan.ifconfig()[0])
        return wlan

# Verbindung zu WLAN herstellen
wlan = connect_to_wifi(SSID, PASSWORD)

# Überprüfen, ob die Verbindung erfolgreich war
if wlan is None:
    print("WLAN-Verbindung konnte nicht hergestellt werden.")
else:
    print("WLAN-Verbindung erfolgreich.")

# Scriptende
print("Script beendet.")

WLAN-Verbindung trennen

Da der Pi Pico die ganze Zeit mit dem WLAN verbunden bleibt, brauchen wir für unsere weiteren "Experimente" auch ein Script, um die Verbindung zu trennen.

# WLAN-Verbindung trennen
# J. Thomaschewski, 17.08.2024

import network

wlan = network.WLAN(network.STA_IF)

# Überprüfen, ob eine Verbindung besteht
if wlan.isconnected():
    # WLAN-Verbindung trennen
    wlan.disconnect()
    print("WLAN-Verbindung getrennt.")
else:
    print("Keine aktive WLAN-Verbindung.")

Wenn wir jetzt die Verbindung getrennt haben, können wir das nächste Script ausprobieren.

WLAN-Verbindung auf dem Display anzeigen lassen

Hier eine einfache Kombination aus der WLAN-Verbindung und unserem früheren OLED-Display-Script.

# WLAN-Verbindung herstellen und IP-Adresse auf dem OLED-Display anzeigen
# J. Thomaschewski, 17.08.2024

import network
from machine import Pin, SoftI2C
from ssd1306 import SSD1306_I2C  

# Initialisierung I2C-Pins und OLED-Display
i2c = SoftI2C(scl=Pin(17), sda=Pin(16))
oled = SSD1306_I2C(128, 64, i2c, addr=0x3C)

# WLAN-Station-Interface initialisieren und aktivieren
wlan = network.WLAN(network.STA_IF)
wlan.active(True)

# Verbindung herstellen und warten, bis die Verbindung erfolgreich ist
oled.fill(0)
oled.text("Verbinde WLAN", 0, 0)
oled.show()

wlan.connect("SSID", "PASSWORD")

# Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    pass

# Verbindung erfolgreich
ip_address = wlan.ifconfig()[0]
print("WLAN verbunden, IP-Adresse:", ip_address)

# IP-Adresse auf dem OLED-Display anzeigen
oled.fill(0)  # Bildschirm löschen
oled.text("WLAN verbunden!", 0, 16)
oled.text("IP:", 0, 32)
oled.text(ip_address, 0, 48)
oled.show()  # Display aktualisieren

Wie man leicht erkennt, ist das Script weder sehr komplex noch sehr einfallsreich. Ich habe einfach beide Scripte "WLAN-Verbindung" und "OLED-Display" recht trivial miteinander verbunden.

Wetterdienst nutzen

Mit unserer WLAN-Verbindung und dem Display ergeben sich nun unendliche Möglichkeiten.

Ich habe das letzte Script in ChatGPT4o hochgeladen und gebeten einen Wetterdienst ohne API zu finden und das Script anzupassen. Natürlich waren auch diesmal weitere Interaktionen mit ChatGPT notwendig, aber am Ende kam dieses übersichtliche Script heraus.

# WLAN-Verbindung zum Wetterdienst
# Basis von J. Thomaschewski, erweitert mittels ChatGPT 4o
# J. Thomaschewski, 17.08.2024

import network
import urequests as requests
from machine import Pin, SoftI2C
from ssd1306 import SSD1306_I2C  

# Initialisierung I2C-Pins und OLED-Display
i2c = SoftI2C(scl=Pin(17), sda=Pin(16))
oled = SSD1306_I2C(128, 64, i2c, addr=0x3C)

# WLAN-Verbindungsdaten
SSID     = "SSID"     # Ersetze durch WLAN-Namen (SSID)
PASSWORD = "PASSWORD" # Ersetze durch WLAN-Passwort

# Standortdaten
CITY = "Emden"

# WLAN-Verbindung herstellen
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(SSID, PASSWORD)
while not wlan.isconnected():
    pass


# Wetterdaten von wttr.in abrufen
def get_weather(city):
    url = "http://wttr.in/{}?format=%t+%C".format(city)  # %t für Temperatur, %C für Bedingungen
    response = requests.get(url)
    data = response.text.strip()

    # Ersetzen des °-Zeichens durch " Grad"
    data = data.replace("°C", " Grad")
    return data

# Abrufen und Anzeigen der Wetterdaten auf dem OLED-Display
try:
    weather_data = get_weather(CITY)

    oled.fill(0)  # Bildschirm löschen
    oled.text("Wetter in {}".format(CITY), 0, 16)
    oled.text(weather_data, 0, 32)
    oled.show()  # Display aktualisieren

except Exception as e:
    oled.fill(0)  # Bildschirm löschen
    oled.text("Fehler beim", 0, 16)
    oled.text("Abrufen der", 0, 32)
    oled.text("Daten", 0, 48)
    oled.show()  # Display aktualisieren
    print("Fehler beim Abrufen der Wetterdaten:", e)

# WLAN-Verbindung trennen (optional)
wlan.disconnect()

Wetterstation

Nerd-Wissen Pi Pico als Webserver

Man kann den Pi Pico nicht nur als Web-Client, sondern auch als Webserver betreiben. Darauf werden wir aber im Rahmen des Moduls nicht eingehen.

Python

In den Scripten auf dieser Seite gibt es einige neue Python-Themen, von denen hier wlan.ifconfig()[0] als Syntax erklärt werden soll.

Umgang mit wlan.ifconfig()

Der Befehl wlan.ifconfig() liefert ein Array (auch bekannt als Tupel in Python), das aus vier Elementen besteht. Diese Elemente enthalten wichtige Netzwerkinformationen, wie die IP-Adresse und die Subnetzmaske, die das Gerät im Netzwerk verwendet.

Ein Array ist eine geordnete Sammlung von Werten, auf die man über ihren Index (ihre Position in der Liste) zugreifen kann.

Die Zählung der Indizes beginnt bei 0, was bedeutet, dass das erste Element des Arrays den Index 0 hat, das zweite den Index 1 und so weiter. Um auf einen spezifischen Wert aus dem Array, das von wlan.ifconfig() zurückgegeben wird, zuzugreifen, verwendet man die eckigen Klammern [] und den entsprechenden Index.

Zum Beispiel: - wlan.ifconfig()[0] liefert die IP-Adresse. - wlan.ifconfig()[1] liefert die Subnetzmaske.

Parameter Erklärung
IP-Adresse Die aktuelle IP-Adresse des Geräts im Netzwerk
Subnetzmaske Die Subnetzmaske, die definiert, welcher Teil der IP-Adresse das Netzwerk repräsentiert
Gateway-Adresse Die IP-Adresse des Routers oder Gateways, das den Zugang zu anderen Netzwerken ermöglicht (z.B. dem Internet)
DNS-Server-Adresse Die IP-Adresse des DNS-Servers, der zur Namensauflösung verwendet wird (Übersetzung von Domainnamen in IP-Adressen)

3.8 Webserver

Als Ausgangsbasis nehmen wir die Verbindung mit dem Netzwerk wie zuvor. Wenn wir einen Webserver bauen, benötigen wir natürlich zunächst eine Verbindung zum Router/Netzwerk.

# WLAN-Verbindung herstellen
# J. Thomaschewski, 17.08.2024

import network

# WLAN aktivieren und verbinden. SSID und PASSWORD ersetzen
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect("SSID", "PASSWORD") # SSID und PASSWORD ersetzen

# Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    pass

# Verbindung erfolgreich
print("WLAN verbunden, IP-Adresse:", wlan.ifconfig()[0])

WLAN-Zugangsdaten auslagern

Damit die Zugangsdaten des WLANs (SSID und Passwort) nicht direkt im Quellcode stehen, möchten wir diese in eine separate Datei wlanzugangsdaten.py ausgelagert. Diese Datei muss auf dem Pi Pico gespeichert werden. Der Code kann dann darauf zugreifen, um die Verbindung herzustellen, ohne die Daten im Hauptprogramm anpassen zu müssen. Die wlanzugangsdaten.py-Datei könnte z. B. so aussehen:

# Datei wlanzugangsdaten.py
ssid = "DeinSSID"
passwd = "DeinPasswort"

Und die Datei zur Erstellung einer Wlan-Verbindung ändert sich in Zeile 6 und Zeile 11 wie folgt

# WLAN-Verbindung herstellen, Zugangsdaten auslagern
# Datei: 3-8a-WLAN-Passworddatei.py
# J. Thomaschewski, 01.11.2024

import network
import wlanzugangsdaten

# WLAN aktivieren und verbinden. SSID und PASSWORD ersetzen
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(wlanzugangsdaten.ssid, wlanzugangsdaten.passwd) # SSID und PASSWORD ersetzen

# Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    pass

# Verbindung erfolgreich
print("WLAN verbunden, IP-Adresse:", wlan.ifconfig()[0])

Ein einfacher Webserver

Um einen einfachen Webserver zu erstellen, benötigen wir Sockets (siehe Zeilen 28-33). Ein Socket ist eine Schnittstelle, die eine Netzwerkverbindung ermöglicht. Er erlaubt unserem Webserver, mit Browsern zu kommunizieren. Über den Socket kann der Server Anfragen empfangen und Antworten zurücksenden.

# Ein einfacher Webserver - Client schaltet interen LED ein/aus
# 3-8b-Webserver-LED.py
# J. Thomaschewski, 17.08.2024
import network
import socket
from machine import Pin
import time
import wlanzugangsdaten  # Zugang für das WLAN ausgelagert

led = Pin('LED', Pin.OUT)

# LED Anfangszustand
state = "aus"

# WLAN-Verbindung herstellen
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(wlanzugangsdaten.ssid, wlanzugangsdaten.passwd)

# Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    print('Versuche Wlan-Verbindung herzustellen')
    time.sleep(1)

print(f'Dies im Browser eingeben: http://{wlan.ifconfig()[0]}')

# Socket einrichten und lauschen
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen()

# Hauptschleife (a) Empfang von Daten, (b) Daten verarbeiten und (c) erstellen der HTML-Seite
while True:
    # (a) Empfang von Daten
    conn, addr = s.accept()
    request = conn.recv(1024)
    request = str(request)
    request = request.split()[1]
    print('Anfrage:', request)

    # (b) Daten verarbeiten
    if request == '/lighton?':
        print("LED an")
        led.value(1)
        state = "an"
    elif request == '/lightoff?':
        print("LED aus")            
        led.value(0)
        state = 'aus'

    # (c) erstellen der HTML-Seite
    response = f"""
        <!DOCTYPE html>
        <html>
        <body>
            <h2>Die LED ist: {state}</h2>
            <form action="./lighton">
                <input type="submit" value="Licht an" />
            </form>
            <br>
            <form action="./lightoff">
                <input type="submit" value="Licht aus" />
            </form>
        </body>
        </html>
        """

    # HTTP-Antwort senden und Verbindung schließen
    conn.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
    conn.send(response)
    conn.close()

Hauptschleife zur Verarbeitung der Anfragen

Der folgende Code beschreibt die Hauptschleife des Webservers, die aus drei wichtigen Blöcken besteht: (a) Empfang von Daten, (b) Datenverarbeitung und (c) Erstellen der HTML-Seite.

while True:
    # (a) Empfang von Daten
    conn, addr = s.accept()  # Eine Verbindung vom Client wird akzeptiert
    request = conn.recv(1024)  # Die Anfrage des Clients wird empfangen
    request = str(request)  # Die Anfrage des Clienst wird in einen String umgewandelt
    request = request.split()[1]  # Der relevante Teil der Anfrage wird abgesplittet
    print('Anfrage:', request)

    # (b) Daten verarbeiten
    if request == '/lighton?':  # Prüfen, ob der Pfad "lighton" ist
        print("LED an")
        led.value(1)  # LED einschalten
        state = "an"
    elif request == '/lightoff?':  # Prüfen, ob der Pfad "lightoff" ist
        print("LED aus")            
        led.value(0)  # LED ausschalten
        state = 'aus'

    # (c) Erstellen der HTML-Seite
    response = f"""
        <!DOCTYPE html>
        <html>
        <body>
            <h2>Die LED ist: {state}</h2>
            <form action="./lighton">
                <input type="submit" value="Licht an" />
            </form>
            <br>
            <form action="./lightoff">
                <input type="submit" value="Licht aus" />
            </form>
        </body>
        </html>
        """

    # HTTP-Antwort senden und Verbindung schließen
    conn.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
    conn.send(response)
    conn.close()

(a) Empfang von Daten

  • conn, addr = s.accept(): Der Server akzeptiert eine Verbindung vom Client (z. B. einem Webbrowser).

  • request = conn.recv(1024): Der Server speichert in der Variablen request die Anfrage des Clients, die bis zu 1024 Bytes umfassen kann.

  • request = str(request): Die Anfrage des Clienst ist ein Byte-Object und wird in einen String umgewandelt.

  • request = request.split()[1]: In der Anfrage (Request Line) steht nach einem Leerzeichen an der 2. Position entweder /lighton oder /lightoff. Eine typische Request Line sieht wie folgt aus:
    "GET /lighton HTTP/1.1\r\nHIER FOLGEN WEITERE ZEILEN AUS DEM HTTP-HEADER\r\n\r\n"

(b) Daten verarbeiten

  • Der extrahierte Pfad wird überprüft:

    • Wenn der Pfad /lighton lautet, schaltet der Server die LED ein (led.value(1)) und setzt den Status auf "an".

    • Wenn der Pfad /lightoff lautet, schaltet der Server die LED aus (led.value(0)) und setzt den Status auf "aus".

(c) Erstellen der HTML-Seite

  • Der Server erstellt eine HTML-Seite mit dem aktuellen LED-Status ({state}).

  • Zwei Schaltflächen ermöglichen es die LED einzuschalten (/lighton) oder auszuschalten (/lightoff).

  • Der HTML-Code wird als response gespeichert, um später an den Client gesendet zu werden.

Nach diesen Schritten sendet der Server die HTML-Antwort an den Client und schließt die Verbindung, sodass die Seite im Browser angezeigt werden kann.

Verbesserter WLAN-Verbindungsaufbau

Der bisherige Verbindungsaufbau war nicht ideal.

1
2
3
4
5
6
# Warten, bis die Verbindung hergestellt ist
while not wlan.isconnected():
    pass

# Verbindung erfolgreich
print("WLAN verbunden, IP-Adresse:", wlan.ifconfig()[0])

Ein besserer Ansatz mit einem Timeout könnte so aussehen:

# Warten, bis die Verbindung hergestellt ist
connectionTimeout = 10
while connectionTimeout > 0:
    if wlan.status() == 3:
        break
    connectionTimeout -= 1
    print('Warte auf WLAN-Verbindung...')
    time.sleep(1)

# Überprüfen, ob die Verbindung erfolgreich war
if wlan.status() == 3:
    print("WLAN verbunden, IP-Adresse:", wlan.ifconfig()[0])
else:
    raise RuntimeError('Netzwerkverbindung konnte nicht hergestellt werden')

Dieser Code wartet auf die WLAN-Verbindung, bis ein Timeout abgelaufen ist, und gibt eine Fehlermeldung aus, wenn die Verbindung nicht erfolgreich hergestellt werden konnte.

Übung

Beschreiben Sie, wie der Timeout funktioniert.

Der WLAN-Status kann folgende Werte annehmen

Status-Code Konstante Beschreibung
0 STAT_IDLE Keine Verbindung und keine Aktivität.
1 STAT_CONNECTING Verbindung wird hergestellt.
-3 STAT_WRONG_PASSWORD Verbindung fehlgeschlagen aufgrund eines falschen Passworts.
-2 STAT_NO_AP_FOUND Verbindung fehlgeschlagen, da kein Access Point gefunden wurde.
-1 STAT_CONNECT_FAIL Verbindung aus anderen Gründen fehlgeschlagen.
3 STAT_GOT_IP Verbindung erfolgreich hergestellt und IP-Adresse erhalten.

Wie es weitergeht...

Sie haben hier nun einen Einstieg in den Raspberry Pi Pico bekommen und nun konzentrieren wir uns im nächten Kapitel auf die Python-Syntax, damit sie diesen Einstieg alleine ausbauen können.

4 Python Grundlagen

Gliederung

4.1 Syntax und Variablen
4.2 Schleifen
4.3 Bedingte Anweisungen
4.4 Ein- und Ausgabe
4.5 Funktionen, Klassen und Methoden
4.6 Bibliotheken
4.7 Daten speichern
4.8 Verzeichnisse bearbeiten
4.9 Übersicht MicroPython-spezifische Funktionen
4.10 Dies und Das
4.11 Dies und Das 2

Generelles Vorgehen in diesem Kapitel

In diesem Kapitel werden wir uns systematisch mit den grundlegenden Konzepten der Programmierung am Beispiel von Python bzw. MicoPython vertraut machen. Die gewählte Kapitelstruktur bildet eine Metaebene, die Ihnen hilft, die Kernprinzipien einer Programmiersprache strukturiert zu erlernen und zu verstehen.

Wie wir vorgehen

Unsere Reise durch die Python-Grundlagen beginnt mit den wesentlichen Bausteinen, auf denen alle Programme aufbauen: der Syntax und den Variablen (Kapitel 4.1). Hier lernen Sie, wie Daten in einem Programm zur weiteren Verarbeitung zwischengespeichert werden können.

Im nächsten Schritt untersuchen wir die Schleifen (Kapitel 4.2), die es ermöglichen, Anweisungen mehrfach auszuführen. Schleifen sind ein zentrales Element jeder Programmiersprache, da sie es ermöglichen, wiederkehrende Aufgaben effizient zu bewältigen.

Danach beschäftigen wir uns mit den Bedingten Anweisungen (Kapitel 4.3), die es dem Programm ermöglichen, Entscheidungen zu treffen und verschiedene Pfade basierend auf bestimmten Bedingungen zu verfolgen. Bedingte Anweisungen sind in jeder Programmiersprache unverzichtbar, um komplexe Entscheidungslogiken zu realisieren.

Anschließend betrachten wir die Ein- und Ausgabe (Kapitel 4.4), ein zentraler Aspekt jeder Programmiersprache. Hier erfahren Sie, wie Programme mit der Außenwelt interagieren, sei es durch das Erfassen von Benutzereingaben oder das Anzeigen von Ergebnissen.

Ein weiteres zentrales Thema sind Funktionen, Klassen und Methoden (Kapitel 4.5). Diese Konzepte ermöglichen die Strukturierung und Wiederverwendung von Code, was die Effizienz und Lesbarkeit Ihrer Programme erheblich verbessert.

Im Kapitel Bibliotheken (Kapitel 4.6) lernen Sie, wie Sie wiederverwendbare Code-Bausteine in Bibliotheken auslagern und in Ihren Projekten verwenden können. Sie erstellen eine eigene Bibliothek und erfahren, wie Sie externe Bibliotheken einbinden.

Im Kapitel Daten speichern (Kapitel 4.7) lernen Sie, wie Sie Daten dauerhaft speichern und verwalten können, was insbesondere für Anwendungen wichtig ist, die Informationen über einen längeren Zeitraum hinweg benötigen.

Abschließend erhalten Sie in MicroPython-spezifische Funktionen (Kapitel 4.8) eine Übersicht über die Besonderheiten und spezifischen Funktionen von MicroPython, die für die Arbeit mit Hardware und eingebetteten Systemen relevant sind.

Was zeichnet eine Programmiersprache aus?

Eine Programmiersprache ist ein Werkzeug, das es uns ermöglicht, Anweisungen an einen Computer zu geben, um bestimmte Aufgaben zu erfüllen. Programmiersprachen wie Python, PHP, Java oder JavaScript ermöglichen es, logische Abläufe zu erstellen (notwendig sind hierfür Variablen), Entscheidungen zu treffen (z.B. durch bedingte Anweisungen), wiederholende Aufgaben durchzuführen (z.B. mittels Schleifen) und Daten zu verarbeiten und zu speichern.

Im Gegensatz dazu sind Nicht-Programmiersprachen wie HTML, CSS oder Markdown statisch und deklarativ. Sie beschreiben, wie etwas dargestellt wird, aber sie enthalten keine Logik oder Anweisungen zur Verarbeitung von Daten. HTML, zum Beispiel, dient zur Strukturierung von Inhalten auf einer Webseite, aber es führt keine Berechnungen durch und trifft keine Entscheidungen.

Beispiele für Programmiersprachen

  • Python
  • PHP
  • Java
  • JavaScript

Beispiele für Nicht-Programmiersprachen

  • HTML (HyperText Markup Language)
  • CSS (Cascading Style Sheets)
  • Markdown

4.1 Syntax und Variablen

In diesem Kapitel erlernen Sie die grundlegende Syntax von Python sowie den Umgang mit Variablen und verschiedenen Datentypen.

Übersicht Allgemeine Syntax

In diesem Abschnitt erhalten Sie einen Überblick über die grundlegenden Syntaxelemente in Python, die die Regeln für das Schreiben und Strukturieren von Code darstellen.

Syntax Beschreibung Beispiel
Kommentar Ein Kommentar im Code, der nicht ausgeführt wird # Dies ist ein Kommentar
Zuweisung Zuweisung eines Wertes zu einer Variablen x = 5
Ausdruck (Expression) Eine Kombination aus Werten, Variablen und Operatoren y = x + 2 oder status = not status
Funktionsaufruf Aufruf einer Funktion mit oder ohne Argumente. Es gibt drei Arten von Funktionen:
(a) interne Funktionen
(b) externe Funktionen
(c) eigene Funktionen erstellt mit def tolleFunktion()

print("Hallo Welt")
sleep(2)
tolleFunktion()
Doppelpunkt : Verwendet nach Schlüsselwörtern wie if, for, def, etc. if x > 0:
Einrückung (Indentation) Einrücken von Code-Blöcken zur Strukturierung siehe Beispiel unter der Tabelle

Einrückungen in Python sind entscheidend, da sie die Struktur des Codes bestimmen. Jeder Codeblock, der beispielsweise zu einer Bedingung (if) oder einer Schleife (z.B. while oder for) gehört, muss eingerückt sein. Ein typisches Beispiel:

if x > 0:
    print("x ist positiv")
    y = x + 5

Auch hatten wir schon die Operatoren, die an dieser Stelle der Vorllständigkeit halber wiederholt werden.

A. Rechenoperatoren

Operator Erklärung Beispiel
= Zuweisung a = 5
+ Addition a + b
- Subtraktion a - b
* Multiplikation a * b
** Potenzierung a ** b
/ Division a / b
% Modulus (Restwert einer Division) a % b (z.B. 10 % 3 ergibt 1)
// Ganzzahlige Division a // b (z.B. 10 // 3 ergibt 3)
+= Addition und Zuweisung a += 3 (z.B. a = 2; a += 3 ergibt 5)
-= Subtraktion und Zuweisung a -= 2
*= Multiplikation und Zuweisung a *= 2
**= Potenzierung und Zuweisung a **= 2
/= Division und Zuweisung a /= 2
%= Modulus und Zuweisung a %= 3
//= Ganzzahlige Division und Zuweisung a //= 2

Was ist das Ergebnis?

zahl = 5
zahl += 10
print(zahl)

B. Vergleichsoperatoren (die als Ergebnis True / False liefern)

Operator Erklärung Beispiel
== Gleichheit a == b
!= Ungleichheit a != b
< Kleiner als a < b
> Größer als a > b
<= Kleiner oder gleich a <= b
>= Größer oder gleich a >= b
and Logisches UND a and b
or Logisches ODER a or b
not Logisches NICHT not a

Vergleichsoperatoren sind sehr wichtig, da damit if-Bedingungen und Schleifen (for, while) gesteuert werden.

if a == b:
    print("a un b sind gleich")

Die Operatoren and und or sind sehr wichtig, wenn man mehr als einen Vergleich benötigt.

Beispiel für die Verwendung von and und or

a = 5
b = 10
c = 15

# Verwendung von and: Beide Bedingungen müssen wahr sein
if a < b and b < c:
    print("a ist kleiner als b und b ist kleiner als c")

# Verwendung von or: Mindestens eine Bedingung muss wahr sein
if a > b or b < c:
    print("Mindestens eine der beiden Bedingungen ist wahr")

Logiktabelle für and

a b a and b
False False False
False True False
True False False
True True True

Logiktabelle für or

a b a or b
False False False
False True True
True False True
True True True

Logiktabelle für not

a not a
False True
True False

Weitere Details zu diesen Operatoren finden Sie in der offiziellen Python-Dokumentation zu den Operatoren.

Übersicht Variablentypen

Eine typische Variable mit einer Wertzuweisung ist breite = 42.

Regeln für die Benennung von Variablen

  1. Buchstaben, Ziffern und Unterstriche: Variablennamen dürfen nur Buchstaben (a-z, A-Z), Ziffern (0-9) und Unterstriche (_) enthalten. Allerdings darf ein Variablenname nicht mit einer Ziffer beginnen. Beispiele: breite, tiefe1.

  2. Keine reservierten Schlüsselwörter: Vermeiden Sie die Verwendung von Python-Schlüsselwörtern (z.B. if, else, while), da diese spezielle Bedeutungen haben und zu Syntaxfehlern führen können. Eine Liste der reservierten Schlüsselwörter finden Sie in der Python-Dokumentation.

  3. Keine deutschen Umlaute und Leerzeichen: Verwenden Sie keine deutschen Umlaute (ä, ö, ü) oder Leerzeichen in Variablennamen, da diese zu Kompatibilitätsproblemen führen können. Stattdessen sollten Sie ae, oe, ue verwenden. Beispiel: maxHoehe statt maxHöhe.

  4. Aussagekräftige Namen: Wählen Sie Namen, die den Inhalt oder Zweck der Variablen beschreiben. Dies erleichtert das Verständnis des Codes. Beispiel: Verwenden Sie temperatur statt temp.

  5. Kleinschreibung: Gemäß der PEP 8-Richtlinie sollten Variablennamen in Python normalerweise in Kleinbuchstaben mit Unterstrichen geschrieben werden, z.B. max_temperatur. Da jedoch in vielen Programmiersprachen die CamelCase-Schreibweise bevorzugt wird, wollen wir auch in diesem Kurs CamelCase verwenden. Beispiel: maxTemperatur statt max_temperatur.

  6. Konstanten: Für Konstanten, die sich während der Programmausführung nicht ändern sollen, verwenden Sie Großbuchstaben. Beispiel: PI = 3.14159.

  7. Vermeiden Sie einbuchstabige Namen: Außer in Schleifenindizes (z.B. i, j) sollten einbuchstabige Variablennamen vermieden werden, da sie den Code weniger verständlich machen.

Folgende Variablentypen können in Python zur Speicherung und Verarbeitung von Daten verwendet werden.

Was kann alles in einer Variable gespeichert werden?

Typ Beschreibung Beispiel
Integer
int()
Ganze Zahlen, z.B. 42
ist uns schon bekannt
breite = 42
Float
float()
Gleitkommazahlen, z.B. 3.14
ist uns schon bekannt
temperatur = 3.14
String
str()
Text, z.B. "Hallo Welt"
ist uns schon bekannt
begruessung = "Hallo Welt"
Boolean
bool()
Wahrheitswerte, z.B. True oder False
ist uns schon bekannt
istAktiv = True
List
list()
Listen von Werten, z.B. [1, 2, 3]
schauen wir uns gleich an
zahlen = [1, 2, 3]
Dictionary
dict()
Schlüssel-Wert-Paare, z.B. {"key": "value"}
schauen wir uns gleich an
person = {"name": "Alice"}
Tuple
tuple()
Unveränderliche Listen, z.B. (1, 2, 3)
wird in diesem Semester nicht betrachtet
koordinaten = (1, 2, 3)
Set
set()
Mengen von eindeutigen Werten, z.B. {1, 2, 3}
wird in diesem Semester nicht betrachtet
einzigartigeZahlen = {1, 2, 3}
Object
object()
Instanzen von Klassen oder andere Objekte
wird später erklärt
led = Pin()

Beispielsweise kann mit int() eine Kommazahl in einen Integer umgewandelt werden. Mit diesen angegebenen Funktionen kann also eine Umwandlung stattfinden. Wichtig sind für uns zunächst einmal int(), float(), str() und bool().

# Float-Zahl als Ausgangspunkt
floatZahl = 42.58

# Umwandeln der Float-Zahl in eine Ganzzahl
ganzZahl = int(floatZahl)
print(ganzZahl)

Wir schauen uns nun die Variablentypen List und Dictionary an

Typ Beschreibung Beispiel
List Listen von Werten, z.B. [1, 2, 3] zahlen = [1, 2, 3]
Dictionary Schlüssel-Wert-Paare, z.B. {"key": "value"} person = {"name": "Alice"}

Wann verwendet man List?

Listen werden verwendet, wenn man eine geordnete Sammlung von änderbaren Elementen benötigen (die sich ändern lassen). Sie eignen sich gut für Sequenzen von ähnlichen Objekten, auf die man in der Reihenfolge ihres Einfügens zugreifen möchten. Listen werden in anderen Programmiersprachen (indizierte) Arrays genannt.

Beispiele für Listen

zahlen = [1, 2, 3, 4, 5]
gemischt = [1, "Hallo", 3.14, True]
verschachtelt = [1, [2, 3], ["a", "b", "c"]]

Wie man eine List füllt und Werte ausliest

  • Füllen:
    meineListe = [1, 2, 3]
    meineListe.append(4)
    
  • Auslesen:
    erstesElement = meineLliste[0]
    

Wann verwendet man ein Dictionary?

Dictionaries sind ideal, wenn Sie eine ungeordnete Sammlung von Schlüssel-Wert-Paaren benötigen. Sie sind besonders nützlich, wenn Sie Daten nach einem eindeutigen Schlüssel suchen oder speichern müssen. Dictionaries werden in anderen Programmiersprachen (assoziative) Arrays genannt.

Beispiele für Dictionaries

person = {"name": "Alice", "ort": "Wunderland"}
gemischt = {"zahl": 42, "text": "Hallo", "istAktiv": True}
verschachtelt = {"person": {"name": "Bob", "alter": 25}, "adresse": {"stadt": "Berlin", "plz": 10115}}

Wie man ein Dictionary füllt und Werte ausliest

  • Füllen:
    meinDictionary = {"name": "Alice", "alter": 9}
    meinDictionary["ort"] = "Wunderland"
    
  • Auslesen:
    name  = meindictionary["name"]   # damit enthält die Variable name einen String
    alter = meindictionary["alter"]  # damit enthält die Variable alter einen Integer
    

Pi Pico Script

Wir nehmen nur das Pi Pico-Board und lassen die interne LED blinken.

Pi Pico

Ursprüngliches Script

# Interne LED blinken lassen 
# J. Thomaschewski, 12.08.2024
from machine import Pin
from time import sleep
led = Pin('LED', Pin.OUT)
while True:
    led.on()
    sleep(0.5)
    led.off()
    sleep(0.5)

Erweitert mit einer List

# Interne LED blinken lassen 
# J. Thomaschewski, 12.08.2024
from machine import Pin
from time import sleep

data = ['LED', 0.5, 1.2]

led = Pin(data[0], Pin.OUT)
while True:
    led.on()
    sleep(data[1])
    led.off()
    sleep(data[2])

Aufgabe

  • Welche Vorteile und welche Nachteile hat diese Schreibweise?
  • Nutzen Sie nun ein Dictionary statt einer List
  • Welche Vorteile und welche Nachteile hat dies dann? Was gefällt ihnen besser?

4.2 Schleife

In diesem Kapitel lernen Sie die Schleifenstrukturen in Python kennen.

Übersicht Schleifenarten

In Python gibt es zwei Haupttypen von Schleifen, um Codeblöcke wiederholt auszuführen: for-Schleifen und while-Schleifen.

Schleifenart Beschreibung
while-Schleife Wiederholt eine Sequenz von Anweisungen, solange eine Bedingung erfüllt ist. Eine while-Schleife ist gut, wenn die Anzahl der Schleifendurchläufe nicht klar ist.
for-Schleife Wiederholt eine Sequenz von Anweisungen für jedes Element einer Sammlung (z.B. Liste, Tupel, String). Eine for-Schleife ist gut, wenn die Anzahl der Schleifendurchläufe definiert ist.

Die while-Schleife kennen wir schon.

# Interne LED blinken lassen - schlecht 1
# J. Thomaschewski, 12.08.2024
from machine import Pin
from time import sleep
led = Pin('LED', Pin.OUT)
while True:
    led.on()
    sleep(0.5)
    led.off()
    sleep(0.5)

Verwendung der while-Schleife

Eine while-Schleife wird verwendet, wenn Sie eine Schleife benötigen, die solange läuft, bis eine bestimmte Bedingung nicht mehr erfüllt ist oder bis eine bestimmte Bedingung endlich erfüllt ist.

Beispiele für while-Schleifen

  1. Endlosschleife

    while True:
        print("Dies läuft unendlich...")
    

  2. Einfaches Beispiel

    1
    2
    3
    4
    x = 0
    while x < 5:
        print(x)
        x += 1
    
    Diese Problemstellung kann man besser mit einer for-Schleife lösen, da die Anzahl der Durchläufe klar ist.

Die while-Schleife verwendet man, wenn eine Endlosschleife benötigt wird oder wenn unklar ist, wie lange es dauert, bis der Zustand True erreicht wird (beispielsweise bei der unbeschränkten Möglichkeit ein Passwort richtig einzugeben).

Verwendung der for-Schleife

Eine for-Schleife wird verwendet, wenn wir eine festgelegte Anzahl von Iterationen durchlaufen möchten oder über eine Sammlung von Elementen iterieren müssen.

Auch bei Schleifen die richtige Einrückung entscheidend. Jeder Codeblock, der zu einer Schleife gehört, muss entsprechend eingerückt werden, um die Struktur des Programms korrekt darzustellen.

Beispiele für for-Schleifen

  1. Einfaches Beispiel

    Das i ist hier eine Laufvariable, die nach nach dem Beenden der for-Schleife den letzten wert beibehält (also i=4)

    for i in range(5):
        print("Durchlauf:", i)
    
    Ergebnis:
    Durchlauf: 0
    Durchlauf: 1
    Durchlauf: 2
    Durchlauf: 3
    Durchlauf: 4

  2. Iteration über eine Liste

    Hier ist nun farbe die Laufvariable

    1
    2
    3
    farben = ["rot", "grün", "blau"]
    for farbe in farben:
       print(farbe)
    
    Ergebnis:
    rot
    grüne
    blau

  3. Iteration über ein Dictionary - Keys verwenden

    1
    2
    3
    4
    # Ein dictionary besteht aus Bezeichner-Werte-Paaren (engl.: key-value)
    person = {"name": "Alice", "ort": "Wunderland"}
    for key in person:
        print(key)
    
    Ergebnis:
    name
    ort

  4. Iteration über ein Dictionary - Values verwenden

    1
    2
    3
    4
    # Ein dictionary besteht aus Bezeichner-Werte-Paaren (engl.: key-value)
    person = {"name": "Alice", "ort": "Wunderland"}
    for value in person.values():
        print(value)
    
    Ergebnis:
    Alice
    Wunderland

  5. Iteration über ein Dictionary - Keys und Values verwenden

    1
    2
    3
    4
    # Ein dictionary besteht aus Bezeichner-Werte-Paaren (engl.: key-value)
    person = {"name": "Alice", "ort": "Wunderland"}
    for key,value in person.items():
        print(key, value)
    
    Ergebnis:
    name Alice
    ort Wunderland

  6. Verschachtelte Schleifen

    1
    2
    3
    for i in range(3):
        for j in range(2):
            print(i, j)
    

    Die Ausgabe lautet:
    0 0
    0 1
    1 0
    1 1
    2 0
    2 1

Aufgabe verschachtelte Schleife

Wie lautet die Ausgabe nun?

1
2
3
for i in range(2):
    for j in range(3):
        print(i, j)

Verwendung von break und continue

In Python können Sie die Anweisungen break und continue verwenden, um den Fluss einer Schleife zu steuern:

  • break: Bricht die Schleife vollständig ab und fährt mit dem nächsten Codeblock nach der Schleife fort.
  • continue: Überspringt den aktuellen Schleifendurchlauf und beginnt mit dem nächsten Durchlauf.

Beispiel für break und continue

1
2
3
4
5
6
for i in range(10):
    if i == 3:
        continue  # Überspringt den Rest dieses Schleifendurchlaufs
    if i == 7:
        break  # Bricht die Schleife ab
    print(i)

Klausurrelevant

In der Prüfung müssen Sie in der Lage sein, sowohl for-Schleifen als auch while-Schleifen anzuwenden und auch zu verstehen, wie Sie break und continue sinnvoll einsetzen.

Pi Pico Script

Wir nehmen das Pi Pico-Board und lassen die interne LED blinken, wobei die Blinkdauer mithilfe einer Schleife und Listen variiert wird.

# LED-Morsen: SOS und Name "Emden"
# J. Thomaschewski, 04.09.2024
from machine import Pin
from time import sleep

led = Pin('LED', Pin.OUT)

# Morsen für SOS (kurz-kurz-kurz, lang-lang-lang, kurz-kurz-kurz)
sos = [0.2, 0.2, 0.2, 0.6, 0.6, 0.6, 0.2, 0.2, 0.2]

# Morsen für "Emden" (lang-lang, kurz-lang-lang-lang, lang-lang-lang, kurz-lang-lang, kurz-lang-lang-lang)
emden = [0.6, 0.6, 0.2, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.2, 0.6, 0.6, 0.6, 0.2, 0.6, 0.6, 0.6]

# SOS morsen
for duration in sos:
    led.on()
    sleep(duration)
    led.off()
    sleep(0.2)  # kurze Pause zwischen den Signalen

sleep(3)  # Pause zwischen den Wörtern

# Name "Emden" morsen
for duration in emden:
    led.on()
    sleep(duration)
    led.off()
    sleep(0.2)  # kurze Pause zwischen den Signalen

In diesem Code wird die Variable duration in den for-Schleifen verwendet, um die Dauer (in Sekunden) zu speichern, die die LED bei jedem Schritt ein- bzw. ausgeschaltet bleibt. Das Wort duration wurde gewählt, damit klar wird, dass es sich um eine Zeitdauer handelt.

Die Schleife for duration in sos: bedeutet, dass die Variable duration nacheinander die Werte aus der Liste sos annimmt, also erst 0.2, dann 0.2, dann 0.2 usw. Für jeden dieser Werte wird die LED für die entsprechende Dauer eingeschaltet, dann eine kurze Pause gemacht, und der Vorgang wird für das nächste Element in der Liste wiederholt.

Es ist wichtig zu verstehen, dass der Name duration nur eine Hilfsvariable ist. Man könnte diese Variable auch i, step oder einen anderen beliebigen Namen nennen. Der Name sollte jedoch sinnvoll gewählt werden, um den Code lesbarer zu machen. Wenn Sie duration verwenden, ist sofort klar, dass es sich um die Dauer der LED-Leuchtdauer handelt. Wenn Sie stattdessen i oder step verwenden würden, wäre die Bedeutung weniger offensichtlich, aber der Code würde genauso funktionieren.

Aufgabe (Klausurniveau)

Schreiben Sie ein Script, das SOS morsen lässt, indem Sie ein dictionary verwenden, um die Morsezeichen für die Buchstaben S und O zu speichern. Verwenden Sie das dictionary, um die LED entsprechend blinken zu lassen.

Lösung
# SOS Morsen mit Dictionary
# J. Thomaschewski, 04.09.2024
from machine import Pin
from time import sleep

led = Pin('LED', Pin.OUT)

# Dictionary mit Morsezeichen für S und O
morseCode = {
    'S': [0.2, 0.2, 0.2],  # Kurz-Kurz-Kurz
    'O': [0.6, 0.6, 0.6]   # Lang-Lang-Lang
}

# SOS Morsen
sos = ['S', 'O', 'S']

for buchstabe in sos:
    for duration in morseCode[buchstabe]:
        led.on()
        sleep(duration)
        led.off()
        sleep(0.2)  # kurze Pause zwischen den Signalen
    sleep(0.6)  # Pause zwischen den Buchstaben

Aufgabe (Klausurniveau)

Schreiben Sie ein Script, das die LED 10 Mal mit abnehmender Geschwindigkeit blinken lässt.

Lösungen
# LED 10 Mal mit abnehmender Geschwindigkeit blinken lassen
# J. Thomaschewski, 04.09.2024
from machine import Pin
from time import sleep

led = Pin('LED', Pin.OUT)

delay = 0.1  # Startverzögerung

for i in range(10):
    led.on()
    sleep(delay)
    led.off()
    sleep(delay)
    delay += 0.2  # Verzögerung bei jedem Durchlauf erhöhen

Aufgabe Bewegen auf dem Display

  1. Schreiben Sie ein Script, das "Moin" auf dem Display zeigt.
  2. Nun soll "Moin" auf dem Display von oben nach unten wandern. Verwenden Sie dafür eine for-Schleife

4.3 Bedingte Anweisungen

In diesem Kapitel lernen Sie die bedingten Anweisungen, also unterschiedliche Codeblöcke basierend auf bestimmten Bedingungen auszuführen.

Übersicht Bedingte Anweisungen

Bedingte Anweisungen ermöglichen es einem Programm, Entscheidungen zu treffen und verschiedene Pfade basierend auf den gegebenen Bedingungen zu verfolgen. In Python werden bedingte Anweisungen hauptsächlich mit den Schlüsselwörtern if, elif und else umgesetzt.

Anweisung Beschreibung
if Führt einen Codeblock aus, wenn eine Bedingung erfüllt ist.
elif Fügt eine weitere Bedingung hinzu, die überprüft wird, wenn die vorherige if-Bedingung nicht erfüllt ist.
else Führt einen Codeblock aus, wenn keine der vorherigen Bedingungen erfüllt ist.

Verwendung der if-Anweisung

Die if-Anweisung wird verwendet, um Code nur dann auszuführen, wenn eine bestimmte Bedingung wahr (True) ist.

Beispiel für eine einfache if-Anweisung

1
2
3
x = 10
if x > 5:
    print("x ist größer als 5")

Verwendung von elif und else

Zusätzlich zur if-Anweisung können Sie elif (kurz für "else if") verwenden, um zusätzliche Bedingungen zu prüfen, und else, um einen Codeblock auszuführen, wenn keine der vorherigen Bedingungen wahr ist.

Beispiele für if, elif und else

  1. Einfaches Beispiel mit if, elif und else:

    1
    2
    3
    4
    5
    6
    7
    x = 10
    if x > 10:
        print("x ist größer als 10")
    elif x == 10:
        print("x ist genau 10")
    else:
        print("x ist kleiner als 10")
    
  2. Mehrere elif-Bedingungen

    wetter = "regnerisch"
    
    if wetter == "sonnig":
        print("Nimm deine Sonnenbrille mit!")
    elif wetter == "regnerisch":
        print("Nimm deinen Regenschirm mit!")
    elif wetter == "schneit":
        print("Zieh dich warm an!")
    else:
        print("Genieße das Wetter!")
    

Verschachtelte Bedingte Anweisungen

Es ist möglich, if-Anweisungen innerhalb anderer if-Anweisungen zu verschachteln, um komplexere Logiken zu erstellen.

Beispiel für verschachtelte if-Anweisungen

1
2
3
4
5
6
7
x = 15
if x > 10:
    print("x ist größer als 10")
    if x > 20:
        print("x ist auch größer als 20")
    else:
        print("x ist nicht größer als 20")

Verwendung von Vergleichs- und Logikoperatoren

Bedingte Anweisungen verwenden Vergleichsoperatoren (wie ==, !=, >, <, >=, <=) und Logikoperatoren (wie and, or, not), um Bedingungen zu formulieren.

Beispiele für Vergleichs- und Logikoperatoren

  1. Vergleichsoperatoren
1
2
3
4
5
6
a = 10
b = 20
if a < b:
    print("a ist kleiner als b")
if a == 10:
    print("a ist genau 10")
  1. Logikoperatoren
1
2
3
4
5
6
7
alter = 25
geschlecht = "männlich"

if alter > 18 and geschlecht == "männlich":
    print("Sie sind ein erwachsener Mann.")
if alter > 18 or geschlecht == "männlich":
    print("Sie sind erwachsen oder männlich oder beides.")

Pi Pico Script

Wir nehmen das Pi Pico-Board und lassen die interne LED abhängig von einer Bedingung blinken.

# LED blinkt abhängig von einer Bedingung
# J. Thomaschewski, 04.09.2024
from machine import Pin
from time import sleep

led = Pin('LED', Pin.OUT)
blinkAnzahl = 5

if blinkAnzahl > 0:
    for i in range(blinkAnzahl):
        led.on()
        sleep(0.5)
        led.off()
        sleep(0.5)
else:
    print("Fertig")

In diesem Beispiel wird die Anzahl der Blinkvorgänge durch die Bedingung if blinkAnzahl > 0: gesteuert. Nur wenn die Bedingung erfüllt ist, blinkt die LED die angegebene Anzahl von Malen.

Wann wird der Text "fertig" ausgegeben?

4.4 Ein- und Ausgabe

Hier erfahren Sie, wie Programme mit der Außenwelt kommunizieren, indem sie Eingaben verarbeiten und Ausgaben generieren.

  • Beim Pi Pico erfolgt die Eingabe typischerweise über Sensoren, Taster oder durch Daten aus dem Netzwerk (WLAN, Bluetooth). Aber auch eine Eingabe durch ein Touch-Display ist möglich. Wenn der Pi Pico als Webserver läuft, dann können Daten auch über einen Webclient (Browser) geschickt werden.

  • Die Ausgabe erfolgt über Displays oder LEDs oder durch das Senden von Daten.

Wir simulieren hier die Eingabe von Daten (die über einen Browser geschickt werden könnten), indem wir diese Daten in Thonny eingeben, während der Pi Pico angeschlossen ist.

Übersicht Ein- und Ausgabe

Die Ein- und Ausgabe ermöglicht es einem Programm, mit der Außenwelt zu interagieren. In Python erfolgt die Eingabe typischerweise über die input()-Funktion, während die Ausgabe über die print()-Funktion oder durch die Steuerung von Hardware wie LEDs oder Displays erfolgt.

In Python erfolgt die grundlegende Eingabe über die input()-Funktion, die es ermöglicht, Benutzereingaben zu erfassen. Die Ausgabe erfolgt in der Regel über die print()-Funktion, die Daten auf der Konsole (in Thonny) anzeigt.

Ein- und Ausgabebefehle Beschreibung
input() Ermöglicht die Eingabe von Daten durch den Benutzer.
print() Gibt Daten auf der Konsole oder in Thonny aus.
Pin() Steuert Hardware wie LEDs und ermöglicht so die Ausgabe von Signalen. Aber auch die Eingabe über Sensoren wird damit ermöglicht.
recv() Empfängt Daten über das Netzwerk (z.B. von einem Webclient).
send() Sendet Daten über das Netzwerk (z.B. an einen Webclient).

Beispiele zu Verwendung von print()

# 1. Einfache Ausgabe
print("Hallo, Welt!")
# Ausgabe: Hallo, Welt!

# 2. Ausgabe mit Variablen
name = "Ute"
print("Hallo,", name)
# Ausgabe: Hallo, Ute

# 3. Ausgabe mit mehreren Argumenten
temperatur = 22.5
print("Temperatur:", temperatur, "°C")
# Ausgabe: Temperatur: 22.5 °C

# 4. String-Formatierung mit f-Strings
alter = 22
stadt = "Berlin"
print(f"{name} ist {alter} Jahre alt und lebt in {stadt}.")
# Ausgabe: Ute ist 22 Jahre alt und lebt in Berlin.

# 5. Ausgabe einer Berechnung mit gerundeten Nachkommastellen
ergebnis = 10 / 3
print(f"Das Ergebnis von 10 / 3 mit zwei Nachkommastellen: {ergebnis:.2f}")
# Ausgabe: Das Ergebnis von 10 / 3 mit zwei Nachkommastellen: 3.33

# 6. String-Formatierung mit format()
name = "Anna"
alter = 30
print("Name: {}, Alter: {}".format(name, alter))
# Ausgabe: Name: Anna, Alter: 30

# 7. Ausgabe einer Berechnung mit gerundeten Nachkommastellen
ergebnis = 10 / 3
print("Das Ergebnis von 10 / 3 mit zwei Nachkommastellen: {:.2f}".format(ergebnis))
# Ausgabe: Das Ergebnis von 10 / 3 mit zwei Nachkommastellen: 3.33

# 8. Python-Besonderheit multiplizieren im print (bitte nicht verwenden)
anzahl = 5
print(anzahl * "Miau ")
# Ausgabe: Miau Miau Miau Miau Miau 

Beispiel: Einfache Eingabe und Ausgabe

1
2
3
4
5
# Eingabe des Benutzers
name = input("Wie heißen Sie? ")

# Ausgabe der Eingabe
print("Hallo,", name)

In diesem Beispiel wird der Benutzer nach seinem Namen gefragt, und die Eingabe wird anschließend auf der Konsole ausgegeben.

Warning

MicroPython unterstützt nur den ASCII-Zeichensatz und bei einem Umlaut in der Eingabe erscheint in der Thonny-Kommandozeile: "WARNING: MicroPython ignores non-ascii characters of the input".

Einfache Ein- und Ausgabe mit dem Pi Pico

Um Daten vom Pi Pico auszugeben, können wir eine LED steuern oder Daten auf einem angeschlossenen Display anzeigen. Die Eingabe kann beispielsweise durch das Drücken eines Tasters erfolgen.

Beispiel: LED-Steuerung basierend auf Benutzereingaben

# LED ein- und ausschalten basierend auf Benutzereingaben
# Hierbei muss der Pi Pico mit Thonny verbunden bleiben
# J. Thomaschewski, 04.09.2024
from machine import Pin

# LED-Pin definieren
led = Pin('LED', Pin.OUT)

# Eingabe des Benutzers
eingabe = input("LED ein (1) oder aus (0)? ")

# Steuerung der LED basierend auf der Eingabe
if eingabe == "1":
    led.on()
    print("LED ist an.")
elif eingabe == "0":
    led.off()
    print("LED ist aus.")
else:
    print("Ungültige Eingabe.")

In diesem Beispiel wird die LED am Pi Pico basierend auf der Benutzereingabe ein- oder ausgeschaltet. Der Benutzer gibt 1 ein, um die LED einzuschalten, oder 0, um sie auszuschalten.

Eingabe und Ausgabe über das Netzwerk

Der Pi Pico kann Daten über das Netzwerk empfangen und senden, wenn er als Webserver oder Client verwendet wird. Dies ermöglicht beispielsweise die Fernsteuerung über einen Webbrowser.

Einfaches Beispiel: Daten über das Netzwerk empfangen und verarbeiten

Hier simulieren wir eine einfache Server-Client-Interaktion, bei der der Pi Pico als Server fungiert und Daten von einem Webclient (Browser) empfängt.

import network
import socket
import time

# WLAN-Zugangsdaten
SSID = 'Ihr_WLAN_Name'
PASSWORD = 'Ihr_WLAN_Passwort'

# WLAN verbinden
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(SSID, PASSWORD)

# Warten, bis das WLAN verbunden ist
while not wlan.isconnected():
    print("Verbindung zum WLAN wird hergestellt...")
    time.sleep(1)

print("WLAN verbunden:", wlan.ifconfig())

# Webserver starten
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
server = socket.socket()
server.bind(addr)
server.listen(1)
print("Listening on", addr)

# Server wartet auf Verbindungen
while True:
    client, client_addr = server.accept()
    print("Client verbunden von", client_addr)

    # Daten vom Client empfangen
    request = client.recv(1024)
    print("Request:", request)

    # Antwort an den Client senden
    response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nHallo von Pi Pico!"
    client.send(response)
    client.close()

In diesem Beispiel fungiert der Pi Pico als einfacher Webserver, der Anfragen von einem Browser entgegennimmt und darauf antwortet. Dies zeigt, wie der Pi Pico Daten aus dem Netzwerk empfangen und verarbeiten kann.

Besseres Beispiel: Daten über das Netzwerk empfangen und verarbeiten

# Webserver zur Steuerung einer LED am Pi Pico
# J. Thomaschewski, 04.09.2024
import network
import socket
from machine import Pin

# LED-Pin definieren
led = Pin('LED', Pin.OUT)

# WLAN verbinden
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('WLAN-SSID', 'WLAN-PASSWORD')

# Warten auf die Verbindung
while not wlan.isconnected():
    pass

print('Netzwerkverbindung hergestellt')
print('IP-Adresse:', wlan.ifconfig()[0])

# Server starten
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
server = socket.socket()
server.bind(addr)
server.listen(1)
print('Listening on', addr)

# Server läuft und wartet auf Anfragen
while True:
    client, client_addr = server.accept()
    print('Client verbunden von', client_addr)

    # Anfrage vom Client empfangen
    request = client.recv(1024)
    request = str(request)
    print('Request:', request)

    # LED ein- oder ausschalten basierend auf der Anfrage
    if '/led_on' in request:
        led.on()
        response = "LED ist AN"
    elif '/led_off' in request:
        led.off()
        response = "LED ist AUS"
    else:
        response = "Unbekannter Befehl"

    # Antwort an den Client senden
    client.send("HTTP/1.1 200 OK\r\n")
    client.send("Content-Type: text/html\r\n\r\n")
    client.send("<html><body><h1>{}</h1></body></html>".format(response))
    client.close()

4.5 Funktionen, Klassen und Methoden

In diesem Kapitel lernen Sie, wie Sie Funktionen, Klassen und Methoden in Python verwenden, um Ihren Code besser zu strukturieren und wiederverwendbare Komponenten zu erstellen.

Übersicht Funktionen, Klassen und Methoden

Funktionen, Klassen und Methoden sind grundlegende Bausteine in Python, die es ermöglichen, Code zu strukturieren, wiederverwendbar zu machen und in kleinere, handhabbare Einheiten zu zerlegen.

Konzept Beschreibung
Funktion Eine wiederverwendbare Einheit von Code, die eine bestimmte Aufgabe ausführt.
Methode Eine Funktion, die innerhalb einer Klasse definiert ist.
Attribut Eine Variable, die in einer Klasse definiert wird und die Daten eines Objekts speichert.
Klasse Eine Vorlage zur Erzeugung von Objekten, die Variablen (Attribute) und Funktionen (Methoden) enthält.
Instanz Ein konkretes Objekt, das aus einer Klasse erzeugt wurde. Jede Instanz hat ihre eigenen Attributwerte.
Konstruktor Die Methode, die beim Instanzieren automatisch aufgerufen wird.

Spezifischere Anweisungen und Eigenschaften

Diese Tabelle beschreibt spezifischere Anweisungen und Eigenschaften, die im Zusammenhang mit Funktionen, Klassen und Methoden in Python verwendet werden.

Anweisung/Eigenschaft Beschreibung
class Schlüsselwort zur Definition einer Klasse.
def Schlüsselwort zur Definition einer Funktion oder Methode.
__init__ Der Konstruktor einer Klasse, der aufgerufen wird, wenn ein neues Objekt erstellt wird.
self Ermöglicht den Zugriff auf Attribute und andere Methoden der Klasse.
return Gibt einen Wert aus einer Funktion oder Methode zurück.

Funktionen

Funktionen sind Blöcke von wiederverwendbarem Code, die eine bestimmte Aufgabe ausführen. Funktionen helfen dabei, Code übersichtlich zu halten und die Wiederholung von Code zu vermeiden.

Beispiel: Einfache Funktion

1
2
3
4
5
6
7
# Einfache Funktion
# J. Thomaschewski, 04.09.2024
def begruessung(name):
    print("Hallo,", name)

# Aufruf der Funktion
begruessung("Jörg")

In diesem Beispiel wird eine Funktion begruessung() definiert, die eine Begrüßung für den übergebenen Namen ausgibt.

Parameter und Rückgabewerte

Funktionen können Parameter annehmen und Werte zurückgeben.

1
2
3
4
5
6
7
8
# Funktion mit zwei Parametern und return
# J. Thomaschewski, 04.09.2024
def addiere(a, b):
    return a + b

# Aufruf der Funktion
ergebnis = addiere(5, 3)
print("Das Ergebnis ist:", ergebnis)

Hier nimmt die Funktion addiere() zwei Parameter a und b und gibt ihre Summe zurück.

Klassen

Klassen sind Vorlagen zur Erstellung von Objekten. Sie kombinieren Daten (Attribute) und Funktionen (Methoden), die auf diesen Daten operieren.

Beispiel: Einfache Klasse - Ausgabe über print in der Methode

# Klasse Baum mit Hauptprogramm - print in Methode
# J. Thomaschewski, 04.09.2024
class Baum:
    def __init__(self, art, hoehe):
        self.art = art
        self.hoehe = hoehe

    def beschreibung(self):
        print(f"Baum: {self.art}, Höhe: {self.hoehe} Meter")

# Hauptprogramm
meinBaum = Baum("Eiche", 20)
meinBaum.beschreibung()

In diesem Beispiel wird eine Klasse Baum definiert, die die Attribute art und hoehe besitzt. Die Methode beschreibung() gibt die Details des Baumes aus, indem sie die Art und die Höhe des Baumes anzeigt.

Die Ausgabe erfolgt direkt in der Methode über den print-Befehl.

Beispiel: Einfache Klasse - Ausgabe über print im Hauptprogramm

# Klasse Baum mit Hauptprogramm - return
# J. Thomaschewski, 04.09.2024
class Baum:
    def __init__(self, art, hoehe):
        self.art = art
        self.hoehe = hoehe

    def beschreibung(self):
        return f"Baum: {self.art}, Höhe: {self.hoehe} Meter"

# Hauptprogramm
meinBaum = Baum("Eiche", 20)

beschreibung = meinBaum.beschreibung()
print(beschreibung)

Der String wird hier in der Methode mit return an das Hauptprogramm übergeben. Wenn eine Übergabe aus der MEthode erfolgt, dann muss diese im Hauptprogramm auch entgegengenommen werden. Dies erfolgt mit beschreibung =. Die Ausgabe erfolgt dann im Hauptprogramm über den print-Befehl.

Beispiel: Einfache Klasse - Der Baum wächst ja noch...

Im Konstrktor sollen nur "Werte" übergeben werden, die sich im Objekt nicht ändern. Die Höhe kann sich ja bei einer Eiche ber die Zeit noch ändern und ist damit kein gutes Klassenattribut.

# Klasse Baum mit Hauptprogramm - return
# J. Thomaschewski, 04.09.2024
class Baum:
    def __init__(self, art):
        self.art = art


    def beschreibung(self, hoehe):
        return f"Baum: {self.art}, Höhe: {hoehe} Meter"

# Hauptprogramm
meinBaum = Baum("Eiche")

beschreibung = meinBaum.beschreibung(20)
print(beschreibung)

Der String wird hier in der Methode mit return an das Hauptprogramm übergeben. Wenn eine Übergabe aus der MEthode erfolgt, dann muss diese im Hauptprogramm auch entgegengenommen werden. Dies erfolgt mit beschreibung =. Die Ausgabe erfolgt dann im Hauptprogramm über den print-Befehl.

Konstruktoren

Der Konstruktor __init__ wird aufgerufen, wenn eine neue Instanz der Klasse erstellt wird. Er initialisiert die Attribute des Objekts.

Methoden

Methoden sind Funktionen, die innerhalb einer Klasse definiert sind und auf Objekte dieser Klasse angewendet werden. Sie können auf die Attribute des Objekts zugreifen und diese manipulieren.

Beispiel: Methode zur Berechnung

# Klasse Kreis mit Hauptprogramm
# J. Thomaschewski, 04.09.2024
class Kreis:
    def __init__(self, radius):
        self.radius = radius

    def flaeche(self):
        return 3.14159 * (self.radius ** 2)

# Erstellen eines Objekts der Klasse und Aufruf der Methode
meinKreis = Kreis(5)
print("Die Fläche des Kreises ist:", meinKreis.flaeche())

In diesem Beispiel wird die Methode flaeche() verwendet, um die Fläche eines Kreises zu berechnen, basierend auf dem Radius, der beim Erstellen des Objekts angegeben wurde.

Einfache Aufgabe

Frage den Radius interaktiv über input() ab.

Pi Pico Script: Verwendung von Funktionen und Klassen

In diesem Beispiel kombinieren wir die Verwendung von Funktionen und Klassen, um eine LED am Pi Pico zu steuern.

# LED-Steuerung mit Funktionen und Klassen
# J. Thomaschewski, 04.09.2024
from machine import Pin
from time import sleep

class LedBlinker:
    def __init__(self, pinNumber):
        self.led = Pin(pinNumber, Pin.OUT)

    def blink(self, times, delay):
        for _ in range(times):
            self.led.on()
            sleep(delay)
            self.led.off()
            sleep(delay)

# Erstellen eines LED-Controllers
ledBlinker = LedBlinker('LED')

# LED blinken lassen
ledBlinker.blink(5, 0.5)

In diesem Beispiel wird eine Klasse LedBlinker erstellt, die eine LED steuern kann. Die Methode blink() lässt die LED eine bestimmte Anzahl von Malen blinken.

Wichtiger Hinweis

Die Besonderheit von for _ in diesem Kontext liegt darin, dass der Unterstrich (_) als Platzhalter für eine Variable verwendet wird, deren Wert im Schleifenrumpf nicht benötigt wird. Dies signalisiert, dass die Schleifenvariable ignoriert werden kann.

Aufgabe Funktionen zu Klassen

Nehmen Sie einen Sourcecode aus einem der Kapitel 3.1 - 3.6 und schreiben Sie den Sourcecode so um, dass eine Klasse genutzt wird.

Aufgabe

Erstellen Sie eine Klasse Ampel, die drei LEDs (rot, gelb, grün) steuert und die Farben entsprechend einer Ampelschaltung (rot -> gelb -> grün) umschaltet. Implementieren Sie eine Methode schalten(), die die Ampelschaltung durchführt.

Lösung
# Ampelsteuerung mit Klassen und Methoden
# Die Steuerung ist aber noch nicht "fertig". Hier nur rot -> grün
# J. Thomaschewski, 04.09.2024
from machine import Pin
from time import sleep

class Ampel:
    def __init__(self, rotPin, gelbPin, gruenPin):
        self.rot = Pin(rotPin, Pin.OUT)
        self.gelb = Pin(gelbPin, Pin.OUT)
        self.gruen = Pin(gruenPin, Pin.OUT)

    def schalten(self):
        self.rot.on()
        sleep(2)
        self.rot.off()
        self.gelb.on()
        sleep(1)
        self.gelb.off()
        self.gruen.on()
        sleep(2)
        self.gruen.off()

# Erstellen eines Ampel-Controllers
ampel = Ampel(15, 14, 13)

# Ampelschaltung durchführen
ampel.schalten()

4.6 Bibliotheken

In diesem Kapitel wollen wir Bibliotheken verwenden, um Code modular und wiederverwendbar zu machen. Bibliotheken ermöglichen es uns, häufig verwendeten Code auszulagern, sodass dieser in mehreren Projekten wiederverwendet werden kann.

Eine Bibliothek ist eine Sammlung von Funktionen, Klassen und anderen Ressourcen. Python verfügt über viele eingebaute Bibliotheken, wie time, machine, und socket, die wir bereits verwendet haben. Darüber hinaus können Sie eigene Bibliotheken erstellen, um Ihren Code übersichtlicher und modularer zu gestalten.

Übersicht Bibliotheken

Befehl Beschreibung
import Importiert eine gesamte Bibliothek oder Modul.
from ... import Importiert spezifische Klassen, Funktionen oder Variablen aus einer Bibliothek oder Modul.
as Verwendet einen Alias für eine importierte Bibliothek oder Modul.

Einfache LED-Bibliothek erstellen und verwenden

In diesem Beispiel erstellen wir eine einfache Bibliothek zur Steuerung einer LED. Diese Bibliothek kann dann in verschiedenen Projekten verwendet werden, ohne dass der Code zur Steuerung der LED jedes Mal neu geschrieben werden muss.

Schritt 1: Erstellen der Bibliothek

Erstellen Sie eine neue Datei mit dem Namen led.py und speichern Sie diese im selben Verzeichnis wie Ihr Hauptscript. In dieser Datei definieren wir eine Klasse LedBlinker, die die Steuerung einer LED übernimmt.

# Eigene LED-Bibliothek
# J. Thomaschewski, 04.09.2024
# led.py
from machine import Pin
from time import sleep

class LedBlinker:
    def __init__(self, pinNumber):
        self.led = Pin(pinNumber, Pin.OUT)

    def blink(self, times, delay):
        for _ in range(times):
            self.led.on()
            sleep(delay)
            self.led.off()
            sleep(delay)

    def on(self):
        self.led.on()

    def off(self):
        self.led.off()

Schritt 2: Verwenden der Bibliothek im Hauptscript

Jetzt können wir die gerade erstellte Bibliothek led.py in unserem Hauptscript verwenden, um eine LED zu steuern.

# Hauptscript: Verwendung der LED-Bibliothek
# J. Thomaschewski, 04.09.2024
from led import LedBlinker

# Erstellen eines LED-Controllers für die eingebaute LED des Pi Pico
ledblinker = LedBlinker('LED')

# LED blinken lassen
ledblinker.blink(5, 0.5)

# LED dauerhaft einschalten
ledblinker.on()

Erklärung

  • Erstellen der Bibliothek: In der Datei led.py haben wir eine Klasse LedBlinker definiert, die Methoden zum Blinken, Ein- und Ausschalten der LED enthält.
  • Verwendung der Bibliothek: Im Hauptscript importieren wir die Klasse LedBlinker aus der Bibliothek led.py und verwenden sie, um die LED zu steuern.

Vorteile der Verwendung von Bibliotheken

  • Wiederverwendbarkeit: Einmal erstellte Bibliotheken können in mehreren Projekten verwendet werden, was die Entwicklungszeit verkürzt und die Konsistenz verbessert.
  • Modularität: Durch die Aufteilung des Codes in Bibliotheken bleibt das Hauptscript übersichtlich und leicht wartbar.
  • Zusammenarbeit: Bibliotheken ermöglichen es mehreren Entwicklern, an verschiedenen Teilen eines Projekts zu arbeiten, ohne sich gegenseitig in die Quere zu kommen.

4.7 Daten speichern

In diesem Kapitel lernen Sie, wie Sie Daten in Python dauerhaft speichern und verwalten können. Das Speichern von Daten ist ein grundlegender Bestandteil vieler Anwendungen, insbesondere wenn Informationen über längere Zeiträume hinweg verfügbar sein müssen. Wir werden uns mit verschiedenen Methoden des Speicherns von Daten befassen, angefangen von einfachen Textdateien bis hin zu komplexeren Datenformaten wie JSON.

Übersicht Daten speichern

Daten können in verschiedenen Formaten und auf unterschiedliche Arten gespeichert werden. Je nach Anwendungsfall können Sie einfache Textdateien verwenden, um Daten im Klartext zu speichern, oder komplexere Strukturen wie JSON, die sich gut für das Speichern von strukturierten Daten eignen.

Methode Beschreibung
Textdateien Speichert Daten im Klartext. Geeignet für einfache, unstrukturierte Daten.
CSV-Dateien Speichert tabellarische Daten im CSV-Format (Comma-Separated Values).
JSON-Dateien Speichert strukturierte Daten im JSON-Format (JavaScript Object Notation).
Binäre Dateien Speichert Daten in einem binären Format, das nicht direkt lesbar ist.
Datenbanken (SQLite) Verwaltet große Datenmengen strukturiert und bietet erweiterte Abfragemöglichkeiten.

Spezifischere Anweisungen und Methoden

Diese Tabelle beschreibt spezifischere Anweisungen und Methoden, die im Zusammenhang mit dem Speichern von Daten in Python verwendet werden.

Anweisung/Methode Beschreibung
open() Öffnet eine Datei zum Lesen ("r"), Schreiben ("w"), Anhängen ("a") oder als Binärdatei ("b").
write() Schreibt Daten in eine geöffnete Datei.
read() Liest Daten aus einer geöffneten Datei.
json.dump() Schreibt ein Python-Objekt (z.B. Dictionary) in eine JSON-Datei.
json.load() Liest Daten aus einer JSON-Datei und wandelt sie in ein Python-Objekt um.
csv.writer() Schreibt Daten in eine CSV-Datei.
csv.reader() Liest Daten aus einer CSV-Datei.
wb Modus zum Öffnen einer Datei zum Schreiben im Binärformat.
rb Modus zum Öffnen einer Datei zum Lesen im Binärformat.
with Sorgt dafür, dass Dateien ordnungsgemäß geöffnet und geschlossen werden, auch wenn ein Fehler auftritt.

Speichern in Textdateien

Eine der einfachsten Methoden, Daten zu speichern, ist die Verwendung von Textdateien. Textdateien sind besonders nützlich, wenn Sie einfache Daten speichern möchten, die keinen speziellen Formatierungsbedarf haben.

Beispiel: Schreiben und Lesen einer Textdatei

# Schreiben und lesen aus einer Textdatei
# J. Thomaschewski, 04.09.2024

# Schreiben in eine Textdatei
with open("daten.txt", "w") as datei:
    datei.write("Dies ist eine gespeicherte Nachricht.\n")

# Lesen aus einer Textdatei
with open("daten.txt", "r") as datei:
    inhalt = datei.read()
    print(inhalt)

In diesem Beispiel wird eine Textdatei erstellt und eine einfache Nachricht darin gespeichert. Anschließend wird der Inhalt der Datei gelesen und auf der Konsole ausgegeben.

Schauen Sie nach: auf dem Pi Pico liegt nun eine Datei daten.txt.

Speichern in JSON-Dateien

JSON (JavaScript Object Notation) ist ein weit verbreitetes Format zum Speichern und Übertragen von strukturierten Daten. Es ist ideal für das Speichern von Daten, die in einem hierarchischen Format vorliegen, wie z.B. Dictionaries oder Listen.

Beispiel: Speichern und Laden von Daten im JSON-Format

# Schreiben und lesen im JSON-Format
# J. Thomaschewski, 04.09.2024
import json

# Daten als Dictionary
daten = {
    "name": "Ute",
    "alter": 22,
    "stadt": "Emden"
}

# Schreiben der Daten in eine JSON-Datei
with open("daten.json", "w") as jsonDatei:
    json.dump(daten, jsonDatei)

# Lesen der Daten aus einer JSON-Datei
with open("daten.json", "r") as jsonDatei:
    geladeneDaten = json.load(jsonDatei)
    print(geladeneDaten)

In diesem Beispiel werden Daten in einer JSON-Datei gespeichert und anschließend wieder aus der Datei gelesen. JSON ist besonders nützlich, wenn Sie komplexere Datenstrukturen wie Dictionaries speichern möchten.

Speichern in CSV-Dateien (nicht in Micro-Python!)

CSV-Dateien (Comma-Separated Values) sind nützlich, um tabellarische Daten zu speichern. Jede Zeile einer CSV-Datei stellt eine Datenzeile dar, und die einzelnen Werte sind durch Kommas getrennt.

Folgendes Beispiel können wir in MicroPython nicht probieren, da es keine Bibliothek csv gibt. Hier könnten wir eine "richtige" Python-Umgebung mit dem Editor "Visual Studio Code" verwenden.

Beispiel: Speichern und Lesen von Daten im CSV-Format

# Schreiben und lesen - csv
# J. Thomaschewski, 04.09.2024
import csv

# Daten als Liste von Listen
daten = [
    ["Name", "Alter", "Stadt"],
    ["Micha", 25, "Emden"],
    ["Anna", 23, "Berlin"]
]

# Schreiben der Daten in eine CSV-Datei
with open("daten.csv", "w", newline="") as csvDatei:
    writer = csv.writer(csvDatei)
    writer.writerows(daten)

# Lesen der Daten aus einer CSV-Datei
with open("daten.csv", "r") as csvDatei:
    reader = csv.reader(csvDatei)
    for zeile in reader:
        print(zeile)

In diesem Beispiel werden tabellarische Daten in einer CSV-Datei gespeichert und anschließend wieder aus der Datei gelesen.

Pi Pico Script: Speichern von Sensordaten

In diesem Beispiel speichern wir Sensordaten, die vom BMP280-Sensor auf dem Pi Pico erfasst werden, in einer Textdatei.

# Sensordaten speichern (BMP280)
# J. Thomaschewski, 04.09.2024
from machine import Pin, SoftI2C
from bmp280 import BMP280
from time import sleep

# Initialize I2C communication (GPIO angepasst)
i2c = SoftI2C(scl=Pin(17), sda=Pin(16), freq=400000)
bmp = BMP280(i2c)

with open("bmp280Sensordaten.txt", "w") as datei:
    for _ in range(10):
        # Messwerte erfassen
        temperature = bmp.temperature
        pressure = bmp.pressure / 100  # hPa

        # Speichern der Sensordaten in der Textdatei
        datei.write(f"Temperatur: {temperature:.2f}°C, Luftdruck: {pressure:.2f} hPa\n")
        sleep(2)

print("Sensordaten wurden gespeichert.")

In diesem Beispiel werden die Temperatur- und Luftdruckwerte zehnmal erfasst und in einer Textdatei gespeichert. Jede Zeile der Datei enthält die Messwerte für Temperatur und Luftdruck.

Aufgabe

Schreiben Sie ein Script, das die Sensordaten des BMP280-Sensors in einer JSON-Datei speichert und anschließend wieder ausliest. Speichern Sie mindestens drei verschiedene Messungen in der Datei.

Lösung
# Loesung der Aufgabe BMP280-Sensors - JSON
# J. Thomaschewski, 04.09.2024
import json
from machine import Pin, SoftI2C
from bmp280 import BMP280
from time import sleep

# Initialize I2C communication
i2c = SoftI2C(scl=Pin(17), sda=Pin(16), freq=400000)
bmp = BMP280(i2c)
daten = []

# Erfassen von Sensordaten
for _ in range(3):
    temperature = bmp.temperature
    pressure = bmp.pressure / 100  # hPa
    daten.append({"Temperatur": temperature, "Luftdruck": pressure})
    sleep(2)

# Speichern der Daten in einer JSON-Datei
with open("bmp280Sensordaten.json", "w") as jsonDatei:
    json.dump(daten, jsonDatei)

# Lesen der Daten aus der JSON-Datei
with open("bmp280Sensordaten.json", "r") as jsonDatei:
    geladeneDaten = json.load(jsonDatei)
    print("Geladene Sensordaten:", geladeneDaten)

4.8 Verzeichnisse bearbeiten mit os in MicroPython

In diesem Abschnitt lernen Sie, wie Sie in MicroPython mithilfe des Moduls os Verzeichnisse erstellen, löschen und verwalten können. Diese Funktionalitäten sind wichtig, wenn Sie Dateien auf einem Gerät organisieren und strukturieren möchten.

Übersicht Verzeichnisverwaltung

Das Modul os bietet in MicroPython grundlegende Funktionen zur Verwaltung von Dateien und Verzeichnissen. Einige der wichtigsten Funktionen sind:

Methode Beschreibung
os.mkdir() Erstellt ein neues Verzeichnis.
os.rmdir() Löscht ein Verzeichnis (wenn es leer ist).
os.listdir() Listet die Inhalte eines Verzeichnisses auf.
os.remove() Löscht eine Datei.
os.rename() Benennt eine Datei oder ein Verzeichnis um.

Es gibt weitere Methoden wie os.stat(), os.statvfs() und os.sync(), die Informationen über das Dateisystem ausgeben. Und es gibt Methoden wie os.mount() und os.umount(), die beim Einbinden von SD-Karten oder anderen externen Speichermedien nützlich sind.

Verzeichnis erstellen mit os.mkdir()

Um ein Verzeichnis zu erstellen, verwenden Sie die Methode os.mkdir(). Diese Methode nimmt den Pfad des zu erstellenden Verzeichnisses als Argument.

Beispiel: Erstellen eines neuen Verzeichnisses

1
2
3
4
5
6
7
8
# Erstellen eines neuen Verzeichnisses
# J. Thomaschewski, 23.10.2024
import os

# Neues Verzeichnis erstellen
os.mkdir("testverzeichnis")

print("Verzeichnis wurde erstellt.")

In diesem Beispiel wird ein neues Verzeichnis mit dem Namen testverzeichnis erstellt. Wenn Sie das Verzeichnis auflisten, sollte es im Dateisystem erscheinen.

Verzeichnisinhalt auflisten mit os.listdir()

Um die Inhalte eines Verzeichnisses aufzulisten, verwenden Sie die Methode os.listdir(). Diese Methode gibt eine Liste aller Dateien und Verzeichnisse im angegebenen Pfad zurück.

Beispiel: Inhalte eines Verzeichnisses auflisten

1
2
3
4
5
6
7
# Auflisten des Inhalts eines Verzeichnisses
# J. Thomaschewski, 23.10.2024
import os

# Inhalte des aktuellen Verzeichnisses auflisten
inhalt = os.listdir()
print("Verzeichnisinhalt:", inhalt)

In diesem Beispiel wird der Inhalt des aktuellen Verzeichnisses aufgelistet. Es werden alle Dateien und Verzeichnisse ausgegeben, die sich im aktuellen Verzeichnis befinden.

Verzeichnis löschen mit os.rmdir()

Wenn Sie ein Verzeichnis löschen möchten, können Sie die Methode os.rmdir() verwenden. Beachten Sie, dass das Verzeichnis leer sein muss, bevor es gelöscht werden kann.

Beispiel: Löschen eines Verzeichnisses

1
2
3
4
5
6
7
8
# Löschen eines Verzeichnisses
# J. Thomaschewski, 23.10.2024
import os

# Verzeichnis löschen
os.rmdir("testverzeichnis")

print("Verzeichnis wurde gelöscht.")

In diesem Beispiel wird das Verzeichnis testverzeichnis gelöscht. Wenn das Verzeichnis nicht leer ist, tritt ein Fehler auf.

Dateien umbenennen mit os.rename()

Die Methode os.rename() wird verwendet, um eine Datei oder ein Verzeichnis umzubenennen. Diese Methode erfordert zwei Argumente: den alten Namen und den neuen Namen.

Beispiel: Datei umbenennen

1
2
3
4
5
6
7
8
# Datei umbenennen
# J. Thomaschewski, 23.10.2024
import os

# Datei umbenennen
os.rename("altedatei.txt", "neuedatei.txt")

print("Datei wurde umbenannt.")

In diesem Beispiel wird die Datei .txt in .txt umbenannt.

Dateien löschen mit os.remove()

Um eine Datei zu löschen, verwenden Sie die Methode os.remove(). Diese Methode löscht die angegebene Datei aus dem Dateisystem.

Beispiel: Datei löschen

1
2
3
4
5
6
7
8
# Datei löschen
# J. Thomaschewski, 23.10.2024
import os

# Datei löschen
os.remove("neuedatei.txt")

print("Datei wurde gelöscht.")

In diesem Beispiel wird die Datei neuedatei.txt gelöscht.

4.9 Übersicht MicroPython-spezifische Funktionen

In diesem Kapitel erhalten Sie einen Überblick über die speziellen Funktionen und Bibliotheken, die in MicroPython verfügbar sind. MicroPython ist eine speziell für Mikrocontroller optimierte Version von Python, die einige Unterschiede und Erweiterungen gegenüber der Standard-Python-Implementierung aufweist. Diese spezifischen Funktionen sind entscheidend für die Interaktion mit Hardware und die Entwicklung eingebetteter Systeme.

Übersicht MicroPython-spezifische Funktionen

MicroPython bietet eine Reihe von Funktionen und Bibliotheken, die speziell für die Arbeit mit Mikrocontrollern entwickelt wurden. Diese Funktionen ermöglichen den direkten Zugriff auf Hardwarekomponenten wie GPIO-Pins, I2C, SPI und vieles mehr.

Funktion/Bibliothek Beschreibung
machine Die zentrale Bibliothek für den Zugriff auf Hardwarekomponenten wie GPIO, Timer, PWM, I2C, SPI, UART und mehr.
Pin Klasse zur Steuerung der GPIO-Pins (General Purpose Input/Output) zum Einlesen von Sensorwerten oder Steuern von LEDs.
I2C Schnittstelle für die Kommunikation mit I2C-Geräten wie Sensoren und Displays.
SPI Schnittstelle für die Kommunikation mit SPI-Geräten, die eine höhere Geschwindigkeit und Flexibilität bieten.
UART Serielle Schnittstelle für die Kommunikation mit anderen Geräten über UART (Universal Asynchronous Receiver-Transmitter).
PWM Ermöglicht die Steuerung der Pulsweitenmodulation, um analoge Signale wie die Helligkeit einer LED zu simulieren.
ADC Analog-Digital-Wandler, der es ermöglicht, analoge Signale (z.B. von einem Potentiometer) in digitale Werte umzuwandeln.
Timer Verwaltet Timer, die zeitgesteuerte Ereignisse im Programm auslösen können, wie z.B. das Blinken einer LED.
sleep Verzögert die Ausführung des Programms für eine bestimmte Zeit, nützlich für Timing und Pausen.
reset Startet den Mikrocontroller neu. Nützlich, um den Zustand zu resetten oder das Programm von vorne zu starten.

Verwendung von MicroPython-spezifischen Funktionen

MicroPython-Funktionen und -Bibliotheken sind speziell für Mikrocontroller ausgelegt. Sie bieten direkten Zugriff auf die Hardware und ermöglichen es, effizient mit verschiedenen Sensoren, Aktoren und Kommunikationsschnittstellen zu arbeiten.

Wir hatten bereits viele Beispiele mit Pin und PWM, sodass dieses Unterkapitel nur der Vollständigkeit halber aufgeführt ist.

4.10 Dies und Das

Aufgaben

Übung

Erstellen Sie ein Programm zur Berechnung des Duchschnittsalters. Zuerst soll abgefragt werden, wie viele Angaben eingegeben werden (also über wie viele Studierende das Alter ermittelt werden soll). Anschließend soll so oft nach dem Alter gefragt werden und die Angaben sollen in einer Liste gespeichert werden. Und als letztes soll das Durchschnittsalter in einer Funktion ermittelt und über return an das Hauptprogramm übergeben werden.

Einfache Beispiele für Funktionen

1
2
3
4
5
6
def moin():
    print('Hello World!')

moin()
moin()
moin()
1
2
3
4
5
def addNumbers(a, b):
    sum = a + b
    print('Summe: ', sum)

addNumbers(3, 4)
1
2
3
4
5
6
def addNumbers(a, b):
    sum = a + b
    return sum

z = addNumbers(3, 4)
print('Summe: ', z)
1
2
3
4
5
6
7
8
def addNumbers(a, b):
    sum = a + b
    return sum

a = 3
b = 5
z = addNumbers(a, b)
print('Summe: ', z)
1
2
3
4
5
6
7
8
def addnumbers(a, b):
    sum = a + b
    return sum

x = 3
y = 5
z = addnumbers(x, y)
print('Summe: ', z)
1
2
3
4
5
6
7
def addnumbers(a, b):
    return(a + b)

a = 3
b = 5
z = addnumbers(a, b)
print('Summe: ', z)
1
2
3
4
5
6
def addnumbers(a, b):
    return(a + b)

a = 3
b = 5
print('Summe: ', addnumbers(a, b))

Was geht, aber total verrückt ist (bitte so nicht programmieren). Die Variablen a und b werden hier automatisch als globale Variablen verwendet. https://www.python-kurs.eu/python3_global_lokal.php

1
2
3
4
5
6
7
8
9
def addnumbers():
    sum = a + b
    return sum

# Hauptprogramm
a = 3
b = 5
z = addnumbers()
print('Summe: ', z)

Die Funktionen int() und isinstance()

Die Funktion int() in Python dient dazu, Werte in Ganzzahlen umzuwandeln.

Möchte man überprüfen, ob ein Wert schon ein Intager ist, verwendet man die Funktion isinstance().

# Überprüfung, ob eine Zahl ein Integer ist
zahl = 42.5

if isinstance(zahl, int):
    print(f"{zahl} ist ein Integer.")
else:
    print(f"{zahl} ist kein Integer.")

# Nun die Zahl in einen Integer umwandeln
zahl = int(zahl)

Einfache und doppelte Anführungszeichen

In Python können einfache (') und doppelte (") Anführungszeichen verwendet werden, um Stringn zu definieren. Beide Varianten verhalten sich gleich und haben dieselbe Funktionalität. Sie werden oft je nach Bedarf eingesetzt:

  • Einfache Anführungszeichen werden verwendet, wenn doppelte Anführungszeichen innerhalb der String auftreten, da so das Escapen von Zeichen vermieden werden kann.
  • Doppelte Anführungszeichen werden oft verwendet, wenn einfache Anführungszeichen im Text enthalten sind.

Beispiel:

text1 = 'Dies ist ein "Zitat" innerhalb eines Strings.'
text2 = "Hier ist ein Beispiel für ein einfaches 'Zitat'."

Drei Anführungszeichen

Für komplexere Stringn, wie etwa mehrzeilige Strings, können dreifache Anführungszeichen (''' oder """) verwendet werden. Sie sind besonders nützlich, um längere Texte und Dokumentationen (Docstrings) klar zu formatieren.

Python kennt '#' als Zeichen für einzeilige Kommentare. Es gibt aber nichts für mehrzeilige Kommentare (andere Programmiersprachen nutzen dann / ... /). In Python verwendet man dann mehrzeilige Strings (ohne diese einer Variablen zuzuweisen) als "Quasi-Kommentar"

def addiereZahlen(a, b):
    """
    Addiere zwei Zahlen und gebe das Ergebnis zurück.

    Diese Funktion nimmt zwei numerische Eingaben und gibt ihre Summe zurück.
    Sie eignet sich besonders für einfache Berechnungen und wird oft als Beispiel
    für Docstrings verwendet.

    Parameters:
        a (int, float): Die erste Zahl zum Addieren.
        b (int, float): Die zweite Zahl zum Addieren.

    Returns:
        int, float: Die Summe der beiden Zahlen.

    Hinweis:
        Diese Funktion ist nicht für Strings geeignet und erwartet nur numerische Werte.
    """
    return a + b

    # Interaktive Hilfe
    help(addiereZahlen)

Man kann in den Mehrzeilen-String auch Variablen einfügen. Hier zum Beispiel die Variable state.

# (c) Erstellen der HTML-Seite
response = f"""
    <!DOCTYPE html>
    <html>
    <body>
        <h2>Die LED ist: {state}</h2>
        <form action="./lighton">
            <input type="submit" value="Licht an" />
        </form>
        <br>
        <form action="./lightoff">
            <input type="submit" value="Licht aus" />
        </form>
    </body>
    </html>
    """

Anzahl abfragen, wie oft geblinkt werden soll

# Interne LED blinken lassen - mit Benutzereingabe
# J. Thomaschewski, 01.11.2024
from machine import Pin
from time import sleep

blinkCount = int(input("Wie oft soll die LED blinken? "))

led = Pin('LED', Pin.OUT)

for i in range(blinkCount):
    led.on()
    sleep(0.5)
    led.off()
    sleep(0.5)

Hier nun soll die Anzahl in einer Funktion addZahl() um 3 erhöht werden

# Interne LED blinken lassen - mit Benutzereingabe und erhöhter Blinkanzahl
# J. Thomaschewski, 01.11.2024
from machine import Pin
from time import sleep

# Funktion, die die Blinkanzahl um 3 erhöht und zurückgibt
def addZahl(blinkCount):
    return blinkCount + 3

blinkCount = int(input("Wie oft soll die LED blinken? "))
blinkCount = addZahl(blinkCount)

led = Pin('LED', Pin.OUT)

for i in range(blinkCount):
    led.on()
    sleep(0.5)
    led.off()
    sleep(0.5)

Variablenkonvertierung

Code Printergebnis Beschreibung
print(int(5)) 5 Integer in Integer umwandeln
print(int(8.7)) 8 Gleitkommazahl in Integer umwandeln (abschneiden)
print(float(6)) 6.0 Integer in Gleitkommazahl umwandeln
print(str(7)) '7' Integer in String umwandeln
print(str(10.5)) '10.5' Gleitkommazahl in String umwandeln
print(int("2")) 2 String, die eine Zahl enthält, in Integer umwandeln
print(float('4.2')) 4.2 String in Gleitkommazahl umwandeln
print(int("2a")) ValueError: ... Fehlermeldung, da String nicht rein numerisch ist
print(int("a2")) ValueError: ... Fehlermeldung, da String nicht rein numerisch ist

Arbeiten mit Zufallszahlen - Lottozahlen 6 aus 49

Versuchen Sie den Soucecode zu verstehen.

import random

# Erstelle eine Liste von Zahlen von 1 bis 49
zahlenListe = list(range(1, 50))

# Liste zum Speichern der gezogenen Lottozahlen. 
# Die Erstellung einer leeren Liste ist ein wichtiges Konzept!
lottoZahlen = []

# Ziehe 6 verschiedene Zahlen
for _ in range(6):

    # Wähle zufällig eine Zahl aus der Liste
    gezogeneZahl = random.choice(zahlenListe)
    # Füge die gezogene Zahl zur Lottozahlen-Liste hinzu
    lottoZahlen.append(gezogeneZahl)
    # Entferne die gezogene Zahl aus der Liste, damit sie nicht erneut gezogen wird
    zahlenListe.remove(gezogeneZahl)

# Sortiere die gezogenen Lottozahlen zur besseren Übersicht
lottoZahlen.sort()

# Ausgabe der gezogenen Lottozahlen
print("Die gezogenen Lottozahlen sind:", lottoZahlen)
Nerdwissen Eine kleine Knobelaufgabe...

Wie funktioniert dieser Sourcecode?

Wenn man nicht weiterkommt, dann kann man immer konkrete Werte einsetzen (und sich zum Beispiel auch Zufallszahlen ausdenken). Schwierige Zeilen durch das Hinzuziehen der Dokumentation (oder ChatGPT, wobei die Dokumentation für das tiefere Verständnis oft der bessere Weg ist). Im Sourcecode unten hatten wir z.B. range() noch nicht mit drei Argumenten. Die Dokumentation zu range() findet sich hier.

import random

# Programm zur zufälligen Anordnung sortierter Zahlen
print("Wie viele zufällige Zahlen sollen generiert werden?")
anzahlZahlen = int(input("Bitte Anzahl eingeben: "))

# Liste zum Speichern zufällig generierter Zahlen
zahlenListe = []

# Generiere die gewünschte Anzahl zufälliger Zahlen und füge sie zur Liste hinzu
for _ in range(anzahlZahlen):
    zufallsZahl = random.randint(1, 1000)
    zahlenListe.append(zufallsZahl)

# Ausgabe der generierten Zufallszahlen
print("Wir haben folgende Zufallszahlen gezogen:", zahlenListe)

# Eigene Funktion, um die Liste zufällig zu mischen
def customShuffle(liste):
    for i in range(len(liste) - 1, 0, -1):
        j = random.randint(0, i)
        liste[i], liste[j] = liste[j], liste[i]

# Sortier-Check-Variable und Versuchszähler initialisieren
istSortiert = False
anzahlVersuche = 0

# Zufälliges Mischen und Überprüfung, ob die Liste sortiert ist
while not istSortiert:
    # Zufälliges Anordnen der Liste mit customShuffle
    customShuffle(zahlenListe)
    istSortiert = True  # Annahme, dass die Liste sortiert ist

    # Gehe durch die Liste und überprüfe, ob sie wirklich sortiert ist
    for i in range(len(zahlenListe) - 1):
        if zahlenListe[i] > zahlenListe[i + 1]:
            istSortiert = False  # Wenn ein Element größer als das nächste ist, ist die Liste nicht sortiert
            break  # Überprüfung kann abgebrochen werden, Liste ist unsortiert

    # Versuchszähler erhöhen und Liste ausgeben
    anzahlVersuche += 1
    print(f"Versuch {anzahlVersuche}: {zahlenListe}")

# Ausgabe der Anzahl der Versuche, bis die Liste sortiert war
print(f"Die Liste ist sortiert! Anzahl der Versuche: {anzahlVersuche}")
print("Fertig!")

Listen in Strings umwandeln und umgekehrt in MicroPython

In MicroPython, wie auch in Python, ist die Konvertierung zwischen Listen und Zeichenketten eine häufige Aufgabe. Im Folgenden werden Methoden vorgestellt, um Listen in Strings und umgekehrt umzuwandeln.

Liste in String umwandeln

Um eine Liste von Zeichenketten in einen einzelnen String zu konvertieren, verwendet man die Methode join(). Diese Methode verbindet die Elemente der Liste zu einem String, wobei ein spezifisches Trennzeichen zwischen den Elementen eingefügt wird. Im folgenden Beispiel werden die Elemente der Liste wortListe mit einem Leerzeichen verbunden. Das Ergebnis ist der String 'Hallo Welt von MicroPython'.

1
2
3
4
5
6
7
# Liste von Wörtern
wortListe = ['Hallo', 'Welt', 'von', 'MicroPython']

# Verbinden der Liste zu einem String mit Leerzeichen als Trennzeichen
verbundenerString = ' '.join(wortListe)

print(verbundenerString)

Ausgabe:
Hallo Welt von MicroPython

Hinweis: Die Methode join() erwartet, dass alle Elemente der Liste vom Typ str sind. Falls die Liste andere Datentypen enthält, müssen diese zuvor in Strings umgewandelt werden.

# Liste mit verschiedenen Datentypen
gemischteListe = ['Temperatur:', 23, 'Grad']

# Umwandlung aller Elemente in Strings
stringListe = []
for element in gemischteListe:
    element = str(element)
    stringListe.append(element)

# Verbinden der Liste zu einem String mit Leerzeichen als Trennzeichen
verbundenerString = ' '.join(stringListe)

print(verbundenerString)
Ausgabe:
Temperatur: 23 Grad

Nerdwissen for-Schleife in einer Zeile

Folgende Scripte erzeugen die gleiche Ausgabe.

# Liste mit verschiedenen Datentypen
gemischteListe = ['Temperatur:', 23, 'Grad']

# Umwandlung aller Elemente in Strings
stringListe = []
for element in gemischteListe:
    element = str(element)
    stringListe.append(element)

# Verbinden der Liste zu einem String mit Leerzeichen als Trennzeichen
verbundenerString = ' '.join(stringListe)

print(verbundenerString)
# Liste mit verschiedenen Datentypen
gemischteListe = ['Temperatur:', 23, 'Grad']

# Umwandlung aller Elemente in Strings
stringListe = [str(element) for element in gemischteListe]

# Verbinden der Liste zu einem String mit Leerzeichen als Trennzeichen
verbundenerString = ' '.join(stringListe)

print(verbundenerString)

String in Liste umwandeln

Um einen String in eine Liste von Teilstrings zu zerlegen, verwendet man die Methode split(). Diese Methode teilt den String an einem angegebenen Trennzeichen und gibt die Teilstrings als Listenelemente zurück. Wenn kein Trennzeichen an split() übergeben wird, wird standardmäßig an Leerzeichen, Tabulatoren und Zeilenumbrüchen aufgeteilt.

1
2
3
4
5
6
7
# String mit Wörtern
satz = 'MicroPython ist großartig'

# Aufteilen des Strings in eine Liste von Wörtern
wortListe = satz.split(' ')

print(wortListe)

Ausgabe:
['MicroPython', 'ist', 'großartig']

Zeichenkette in Liste von Zeichen umwandeln:

Um einen String in eine Liste von einzelnen Zeichen zu zerlegen, kann die Funktion list() verwendet werden.

Beispiel:

1
2
3
4
5
6
7
# String
wort = 'MicroPython'

# Umwandeln des Strings in eine Liste von Zeichen
zeichenListe = list(wort)

print(zeichenListe)

Ausgabe: ['M', 'i', 'c', 'r', 'o', 'P', 'y', 't', 'h', 'o', 'n']

In diesem Fall wird der String wort in seine einzelnen Zeichen zerlegt und als Liste zurückgegeben.

Diese Methoden ermöglichen eine flexible Handhabung von Strings und Listen in MicroPython und sind nützlich für verschiedene Anwendungsfälle, wie z. B. das Parsen von Eingaben oder das Formatieren von Ausgaben.

Folgende Python-Funktionen haben wir somit kennen gelernt:

Funktion Beschreibung Beispiel Ausgabe
join() Verbindet eine Liste von Strings zu einem einzelnen String mit einem definierten Trennzeichen. ' '.join(['Hallo', 'Welt']) 'Hallo Welt'
split() Zerlegt einen String in eine Liste von Teilstrings anhand eines definierten Trennzeichens. 'MicroPython ist großartig'.split(' ') ['MicroPython', 'ist', 'großartig']
list() Wandelt einen String in eine Liste einzelner Zeichen um. list('MicroPython') ['M', 'i', 'c', 'r', 'o', 'P', 'y', 't', 'h', 'o', 'n']

Aufgabe: Kombination von Zahlenlisten addieren

Übung

Gegeben sind zwei Listen von Zahlen:

NumberA = [1, 2]
NumberB = [5, 6]

Schreibe ein Programm, das jede Zahl aus NumberA mit jeder Zahl aus NumberB kombiniert und die Summe ausgibt. Die Ausgabe soll das Format 1 + 5 = 6 haben.

Erwartete Ausgabe:
1 + 5 = 6
1 + 6 = 7
2 + 5 = 7
2 + 6 = 8

Lösung
1
2
3
4
5
6
NumberA = [1, 2]
NumberB = [5, 6]

for n in NumberA:
    for m in NumberB:
        print(n, " + ", m, " = ", n + m)

Visual Studio statt Thonny

Um von Thonny zu Visual Studio Code (VS Code) für die Programmierung des Raspberry Pi Pico mit MicroPython zu wechseln, folgen Sie diesen Schritten:

  1. VS Code installieren: Visual Studio Code Download.

  2. Pico-W-Go-Erweiterung in VS Code installieren

    • Öffnen Sie VS Code.

    • Klicken Sie auf Erweiterungen (linke Leiste unteres Symbol mit 3+1 Vierecke).

    • Suchen Sie nach "Pico-W-Go" und installieren Sie die Erweiterung.

  3. Projektordner einrichten

    • Öffnen Sie den Ordner in dem die bisherigen .py-Dateien liegen oder erstellen Sie einen neuen Ordner für Ihr Projekt.

    • Öffnen Sie diesen Ordner in VS Code über Datei > Ordner öffnen.

  4. Verbindung zum Raspberry Pi Pico herstellen

    • Pi Pico mit dem Computer verbinden.

    • Pico-W-Go sollte den Pico automatisch erkennen und eine Verbindung herstellen: unten rechts muss dann ein Haken sein.

  5. Code auf den Pico hochladen und ausführen: Klicken Sie mit der rechten Maustaste auf die Datei und wählen Sie "Pi-W-Go > Upload project", um den Code auf den Pico zu übertragen.

  6. Weitere Ressourcen: Run-Button unten links benutzen.

Hier der Link zur offizielle Dokumentation von Pico-W-Go.

PyCharm statt Thonny

Anleitungen auf der offiziellen Dokumentation von MicroPython. Ich habe es bei mir nicht zum Laufen bekommen.

Um von Thonny zu PyCharm für die Programmierung des Raspberry Pi Pico mit MicroPython zu wechseln, folgen Sie diesen Schritten:

  1. PyCharm installieren: PyCharm Community Edition Download. Pycharm ist für Studierende kostenlos.

  2. MicroPython-Plugin in PyCharm installieren:

    • Öffnen Sie PyCharm.

    • Gehen Sie zu Datei > Einstellungen (oder PyCharm > Einstellungen auf macOS).

    • Wählen Sie im linken Menü Plugins aus.

    • Suchen Sie im Marketplace nach "MicroPython" und installieren Sie das Plugin.

    • Starten Sie PyCharm neu, um die Installation abzuschließen.

  3. Projektordner einrichten:

    • Öffnen Sie den Ordner, in dem die bisherigen .py-Dateien liegen, oder erstellen Sie einen neuen Ordner für Ihr Projekt.

    • Öffnen Sie diesen Ordner in PyCharm über Datei > Ordner öffnen.

  4. MicroPython-Unterstützung im Projekt aktivieren

    • Gehen Sie zu Datei > Einstellungen > Sprachen & Frameworks > MicroPython.

    • Aktivieren Sie das Kontrollkästchen MicroPython-Unterstützung aktivieren.

    -...

  5. Code auf den Pico hochladen und ausführen:

    • Klicken Sie mit der rechten Maustaste auf die Datei und wählen Sie Run 'Flash <Dateiname>', um den Code auf den Pico zu übertragen.

    • Die Konsole zeigt den Fortschritt des Uploads und eventuelle Ausgaben Ihres Programms an.

Python Online-Interpreter

Einfache Programmier-Videos

Wer gut programmieren kann, kann es auch ohne das Video schaffen! Hier die Spielregeln: https://www.gruppenspiele-hits.de/wenig-material-spiele/Die-verflixte-Eins.html

Besonderheit: man soll mindestens 51 Punkte insgesamt haben.

https://www.youtube.com/watch?v=21FnnGKSRZo Start bei 2:15 und Ende bei 23:45.

Wenn das Spiel programmiert wurde, dann sind hier folgende Erweiterungen möglich:

  • Anleitung auf deutsch schreiben

  • Für 1 – 3 Spieler:innen erstellen

  • Mit zwei Würfeln würfeln und bei „2“ oder „3“ verliert man die Punkte

Eingebaute Funktionen in Python

Python bietet eine Vielzahl eingebauter Funktionen, die häufige Aufgaben erleichtern. Im Folgenden sind einige dieser Funktionen thematisch gruppiert mit ihren Parametern und Anwendungsbeispielen aufgeführt.

Iteration

Funktion Parameter Beschreibung Beispiel
range() start (optional), stop, step (optional) Erzeugt eine Sequenz von Zahlen von start bis stop (exklusiv) mit dem Schritt step. range(1, 10, 2) erzeugt [1, 3, 5, 7, 9]

Umwandlungen

Funktion Beschreibung Beispiel
int() Wandelt in eine Ganzzahl um. int('10.6') gibt 10 zurück
float() Wandelt in eine Gleitkommazahl um. float('3.14') gibt 3.14 zurück
str() Wandelt in eine Zeichenkette um. str(100) gibt '100' zurück
bool() Wandelt ein Element in einen Booleschen Wert um. bool(1) gibt True zurück
list() Wandelt in eine Liste um. list('abc') gibt ['a', 'b', 'c'] zurück
dict() Erstellt ein Wörterbuch. dict(a=1, b=2) gibt {'a': 1, 'b': 2} zurück
join() Verbindet eine Liste von Strings zu einem String mit einem Trennzeichen. ' '.join(['Hallo', 'Welt']) gibt 'Hallo Welt' zurück
split() Zerlegt einen String anhand eines Trennzeichens in eine Liste von Teilstrings. 'MicroPython ist großartig'.split(' ') gibt ['MicroPython', 'ist', 'großartig'] zurück

Aggregation und Reduktion

Funktion Beschreibung Beispiel
sum() Gibt die Summe der Elemente zurück. sum([1, 2, 3]) gibt 6 zurück
min() Gibt das kleinste Element zurück. min(3, 1, 2) gibt 1 zurück
max() Gibt das größte Element zurück. max(3, 1, 2) gibt 3 zurück
all() Gibt True zurück, wenn alle Elemente wahr sind. all([True, True, False]) gibt False zurück
any() Gibt True zurück, wenn mindestens ein Element wahr ist. any([False, False, True]) gibt True zurück

Sonstige Funktionen

Funktion Beschreibung Beispiel
strip() Löscht Leerzeichen am Anfang und Ende eines Strings, z.B. bei input() eingabe = strip(input())
len() Gibt die Anzahl der Elemente zurück. len([1, 2, 3]) gibt 3 zurück
type() Gibt den Typ eines Elements zurück. type(42) gibt <class 'int'> zurück
abs() Gibt den absoluten Wert einer Zahl zurück. abs(-5) gibt 5 zurück
round() Rundet eine Zahl auf die angegebene Anzahl von Dezimalstellen. round(3.14159, 2) gibt 3.14 zurück
sort() Sortiert eine Liste direkt (in-place), d. h., die ursprüngliche Liste wird verändert. Siehe Beispiel unten
sorted() Gibt eine neue, sortierte Liste zurück, ohne die ursprüngliche Liste zu verändern. Dies ist besser und stabiler! Siehe Beispiel unten
map() Wendet eine Funktion auf jedes Element eines iterierbaren Objekts an. list(map(str, [1, 2, 3])) gibt ['1', '2', '3'] zurück

Beispiel sort() und sorted()

# Verwendung von sort() - verändert die ursprüngliche Liste
meineListe = [5, 3, 8, 1, 2]  # ursprüngliche Liste
meineListe.sort()  # jetzt ist meineListe sortiert.
print("Sortiert (In-Place):", meineListe)

# Rückwärtssortierung mit sort()
meineListe.sort(reverse=True)  # Sortiert die Liste rückwärts in-place
print("Rückwärtssortiert (In-Place):", meineListe)

# Verwendung von sorted() - erzeugt eine neue sortierte Liste
originalListe = [7, 4, 9, 3, 6]
sortierteListe = sorted(originalListe)  # Erstellt eine neue sortierte Liste
print("OriginalListe: ", originalListe)  # Unverändert
print("Sortierte Liste:", sortierteListe)  # Sortiert

# Rückwärtssortierung mit sorted()
rueckwaertsSortierteListe = sorted(originalListe, reverse=True)  # Erstellt eine neue rückwärtssortierte Liste
print("Rückwärtssortierte Liste:", rueckwaertsSortierteListe)

Ausgabe:
Sortiert (In-Place): [1, 2, 3, 5, 8]
Rückwärtssortiert (In-Place): [8, 5, 3, 2, 1]
OriginalListe: [7, 4, 9, 3, 6]
Sortierte Liste: [3, 4, 6, 7, 9]
Rückwärtssortierte Liste: [9, 7, 6, 4, 3]

Nerdwissen map()
Funktion Beschreibung Beispiel
map() Wendet eine Funktion auf jedes Element eines iterierbaren Objekts an. Siehe Beispiel unten

Schauen Sie sich das Beispiel in Ruhe an, denn map() ist richtig gut!

# Eine Funktion, um die Quadrate einer Zahl zu berechnen
def SquareNumber(x):
    return x ** 2

# Liste von Zahlen
numberList = [1, 2, 3, 4, 5]

# Verwenden von map() mit SquareNumber-Funktion
squaredList = list(map(SquareNumber, numberList))

print("OriginalList:", numberList)
print("SquaredList:", squaredList)

Ausgabe:
OriginalList: [1, 2, 3, 4, 5]
SquaredList: [1, 4, 9, 16, 25]

4.10 Dies und Das 2

Model - View - Controler (MVC-Konzept)

Das Model-View-Controller (MVC)-Konzept ist ein bewährtes Entwurfsmuster in der Softwareentwicklung, das Klassen in drei Gruppen unterteilt:

  1. Model (Modell): Das Model ist für die Datenhaltung zuständig. Hierin finden sich Klassen zum Speichern und Abrufen der Daten. Da das Model unabhängig von der Verarbeitung der Daten (Controler) und der Ansicht der Daten (View) ist, kann man in einer guten Programmierung die Datenhaltung ändern, ohne dass dies einen Einfluss auf die Datenverarbeitung bzw. die Darstellung der Daten hat.

  2. View (Ansicht): In den Klassen des Views findet die Darstellung der Anwendung sowie die Interaktionen Darstellung der Benutzerinteraktionen statt.

  3. Controller (Steuerung): Der Controller fungiert als Vermittler zwischen Model und View. Er verarbeitet Benutzereingaben, aktualisiert das Modell entsprechend und sorgt dafür, dass die Ansicht die aktuellen Daten anzeigt.

Durch diese Trennung der Verantwortlichkeiten fördert das MVC-Muster eine klare Strukturierung des Codes, erleichtert die Wartung und ermöglicht eine parallele Entwicklung der einzelnen Komponenten. In Python wird dieses Muster häufig in Web-Frameworks wie Django verwendet, um eine saubere und skalierbare Anwendungsarchitektur zu gewährleisten.

Guter Stil

Hier wird die Textlänge geprüft. Es ist eine sehr schlanke Lösung einer Praktikumsaufgabe

# Textlänge prüfen
# 27.11.2024, Schicke Lösung aus dem Praktikum

from machine import Pin, SoftI2C
from ssd1306 import SSD1306_I2C  # Import der TimHanewich SSD1306 I2C-Klasse

# Initialisierung der I2C-Pins 
i2c = SoftI2C(scl=Pin(17), sda=Pin(16))

# Initialisierung des OLED-Displays mit expliziter I2C-Adresse
oled = SSD1306_I2C(128, 64, i2c, addr=0x3C)

class Eingabe:
        def __init__(self, maxLang):
            self.maxLang = maxLang

        def pruefeText(self, text):
            if len(text) <= self.maxLang:
                return text
            else:
                return "Text ist zu lang!"

# Hauptprogramm
eingabeText = Eingabe(16)

text = input("Gib deinen Text ein: ")

anzeigeText = eingabeText.pruefeText(text)

oled.fill(0)  
oled.text(anzeigeText, 0, 20)
oled.show() 

MVC-Konzept

Zu welchem Bereich des MVC-Konzepts gehört hier die Klasse "Eingabe"?


Gut abgesichert

Es ist empfehlenswert, die Variablenübergabe an eine Methode auf ihren Typ zu überprüfen oder den Typ festzulegen.

Nehmen wir das bereits bekannte Beispiel und ändern es entsprechend ab, indem wir alle übergebenen Parameter entweder überprüfen oder konvertieren.

Hier das Originalbeispiel

# LED-Steuerung mit Funktionen und Klassen
# J. Thomaschewski, 04.09.2024
from machine import Pin
from time import sleep

class LedBlinker:
    def __init__(self, pinNumber):
        self.led = Pin(pinNumber, Pin.OUT)

    def blink(self, times, delay):
        for _ in range(times):
            self.led.on()
            sleep(delay)
            self.led.off()
            sleep(delay)

# Erstellen eines LED-Controllers
ledBlinker = LedBlinker('LED')

# LED blinken lassen
ledBlinker.blink(5, 0.5)

Hier ist ein verbessertes und abgesichertes Beispiel. So werden die Methoden der Klasse vor "Störungen von außen" geschützt.

# "Abgesicherte" LED-Steuerung mit Funktionen und Klassen
# J. Thomaschewski, 27.11.2024
from machine import Pin
from time import sleep

class LedBlinker:
    def __init__(self, pinValue):
        try:
            if isinstance(pinValue, int):
                # Externe LED: pin ist die GPIO-Pin-Nummer
                self.led = Pin(pinValue, Pin.OUT)
            elif pinValue == 'LED':
                # Onboard-LED: String 'LED'
                self.led = Pin('LED', Pin.OUT)
            else:
                raise TypeError("pinValue muss ein int oder der String 'LED' sein.")
        except Exception as e:
            print(f"Fehler bei der Initialisierung: {e}")
            self.led = None  # Setze led auf None, da die Initialisierung fehlgeschlagen ist

    def blink(self, times, delay):
        if self.led is None:
            print("LED wurde nicht korrekt initialisiert.")
            return
        try:
            if not isinstance(times, int):
                raise TypeError("times muss vom Typ int sein.")
            if not isinstance(delay, (int, float)):
                raise TypeError("delay muss vom Typ int oder float sein.")
            for _ in range(times):
                self.led.on()
                sleep(delay)
                self.led.off()
                sleep(delay)
        except Exception as e:
            print(f"Fehler beim Blinken: {e}")

# Hauptprogramm
ledBlinker = LedBlinker('LED')

ledBlinker.blink(5, 0.5)  # schreiben Sie mal ('Hallo', 0.5)
Und plötzlich haben wir sehr viel Sourcecode, aber auch einen sehr guten Sourcecode.


Klassenattribute und Methoden aufrufen

Hier das Originalbeispiel

# LED-Steuerung mit Funktionen und Klassen
# J. Thomaschewski, 04.09.2024
from machine import Pin
from time import sleep

class LedBlinker:
    def __init__(self, pinNumber):
        self.led = Pin(pinNumber, Pin.OUT)

    def blink(self, times, delay):
        for _ in range(times):
            self.led.on()
            sleep(delay)
            self.led.off()
            sleep(delay)

# Erstellen eines LED-Controllers
ledBlinker = LedBlinker('LED')

# LED blinken lassen
ledBlinker.blink(5, 0.5)

Wir wissen, dass wir die Methode blink() aus dem Hauptprogramm aufrufen können und zwar (abstrakt geschrieben) mit objektName.blink(wert1, wert1).

Wir können aber auch Klassenattribute auslesen und sogar neu überschreiben. Schauen wir uns dies mal an:

# LED-Steuerung mit Funktionen und Klassen
# J. Thomaschewski, 27.11.2024
from machine import Pin
from time import sleep

class LedBlinker:
    def __init__(self, pinNumber):
        self.led = Pin(pinNumber, Pin.OUT)

    def blink(self, times, delay):
        for _ in range(times):
            self.led.on()
            sleep(delay)
            self.led.off()
            sleep(delay)

# Erstellen eines LED-Controllers
ledBlinker = LedBlinker('LED')

# Auslesen des Klassen-Attributs
print(ledBlinker.led)

# Noch schlimmer: ich kann das Klassenattribut sogar überschreiben 
# und damit von außen alles kaputt machen. Dazu die folgenden Zeilen auskommentieren!
#ledBlinker.led = Pin(16, Pin.OUT)
#print(ledBlinker.led)

# LED blinken lassen
ledBlinker.blink(5, 0.5)
Und selbst unser gut abgesicherte Script mit try - except bekommt man damit kaputt. Aber man "darf" in Python gemäß Konvention nicht auf Klassen-Attribute aus dem Hauptprogramm zugreifen.

Bei Java, C++, PHP etc. "kann" man darauf nicht zugreifen (sofern die Klasse gut programmiert wurde). Wenn man auf ein "privates" Klassenattribut in Java, C++, PHP etc. zugreift, dann gibt dies eine Fehlermeldung.

Klassen-Attribute in Python

Unterstrich (_): Ein Attributname, der mit einem einzelnen Unterstrich beginnt, z. B. _meinAttribut, signalisiert, dass es sich um ein geschütztes Attribut handelt, auf das nicht direkt von außen zugegriffen werden sollte. Diese Konvention dient hauptsächlich als Hinweis für Entwickler:innen und wird nicht vom Interpreter erzwungen.

Nerd-Wissen zwei Unterstriche

Ein Attributnamezwei Unterstrichen, z. B. __meinAttribut, führt zu Namensveränderung (Name Mangling). Der Interpreter ändert den Namen des Attributs intern, um Kollisionen in Unterklassen zu vermeiden. Dies erschwert den direkten Zugriff von außen, verhindert ihn jedoch nicht vollständig.

Getter und Setter

In der objektorientierten Programmierung ist es oft vorteilhaft, Attribute einer Klasse nicht direkt zugänglich zu machen, sondern den Zugriff über sogenannte Getter- und Setter-Methoden zu steuern. Dies bietet mehrere Vorteile:

  • Datenvalidierung: Bevor ein Attribut ein Wert zugewiesen wird, kann überprüft werden, ob dieser Wert gültig ist und den erwarteten Kriterien entspricht.

  • Kontrollierter Zugriff: Öffentliche Attribute können von überall gelesen und geschrieben werden. Oftmals möchte man jedoch den schreibenden Zugriff einschränken oder an bestimmte Bedingungen knüpfen.

  • Erweiterbarkeit: Durch die Verwendung von Getter- und Setter-Methoden kann später zusätzliche Funktionalität hinzugefügt werden, wie z.B. das Beobachter-Pattern, um Änderungen an Eigenschaften an andere Programmteile weiterzuleiten.

Im folgenden Python-Beispiel wird eine Klasse Student definiert, die diese Prinzipien anwendet.

# Klasse Student mit "privaten Attributen"  und Hauptprogramm
# J. Thomaschewski, 27.11.2024
class Student:
    def __init__(self, name: str, matrNumber: int, phoneNumber: str):
        self._name        = name
        self._matrNumber  = matrNumber
        self._phoneNumber = phoneNumber

    def getMatrNumber(self) -> int:
        return self._matrNumber

    def getPhoneNumber(self) -> str:
        return self._phoneNumber

    def setPhoneNumber(self, newPhoneNumber: str) -> None:
        # Hier könnten Validierungen des neuen Werts erfolgen
        self._phoneNumber = newPhoneNumber

    def returnNameAndMatrikel(self) -> str:
        return(f"{self._name} hat die Matr.-Nr.: {self._matrNumber}")

# Hauptprogramm

# Erstellen eines Student-Objekts
student = Student("Marie", 7203456, "0491-23403")

# Auslesen der Matrikelnummer
matrikelnummer = student.getMatrNumber()
print(f"Matrikelnummer: {matrikelnummer}")

# Auslesen der Telefonnummer
telefonnummer = student.getPhoneNumber()
print(f"Telefonnummer: {telefonnummer}")

# Setzen einer neuen Telefonnummer
neueTelefonnummer = "01234 567890"
student.setPhoneNumber(neueTelefonnummer)

print(f"Neue Telefonnummer: {student.getPhoneNumber()}")

print(student.returnNameAndMatrikel())

Hier ist einige neu, was wir nun im Detail besprechen müssen.

Arbeiten mit zwei Klassen - völlig unabhängig von einander

class Horse:
    def __init__(self, horseType: str):
        self._horseType = horseType

    def ride(self, location: str) -> str:
        return f"Ich habe ein {self._horseType} und reite {location}"


class Student:
    def __init__(self, name: str, matrNumber: int):
        self._name = name
        self._matrNumber = matrNumber
        self._phoneNumber = "keine Angabe"

    def getName(self) -> str:
        return self._name

    def getMatriculationNumber(self) -> int:
        return self._matrNumber

    def setPhoneNumber(self, newPhoneNumber: str) -> None:
        self._phoneNumber = newPhoneNumber

    def getPhoneNumber(self) -> str:
        return self._phoneNumber


# Hauptprogramm
annaHorse = Horse("Deutsches Reitpony")
annaStudent = Student("Anna", 7205555)

# Beispielausgabe
print(annaHorse.ride("im Park"))
print(f"Name des Studenten: {annaStudent.getName()}")
print(f"Matrikelnummer: {annaStudent.getMatriculationNumber()}")
print(f"Telefonnummer vor Änderung: {annaStudent.getPhoneNumber()}")

annaStudent.setPhoneNumber("12345 67890")
print(f"Telefonnummer nach Änderung: {annaStudent.getPhoneNumber()}")