ESP32 und Micro Python

In dieser Lerneinheit geht es darum, den von der Firma Espressiv entwickelten 32-Bit Mikrocontroller ESP32 etwas näher kennen zu lernen. Dieser Mikrocontroller ist heute eine wichtige HW-Komponente im grossen IoT-Markt (Internet of Things). 
Für die Programmierung der einzelnen Anwendungen, soll die einfach zu erlernende und für Mikrocontroller optimierte Sprache Micro Python verwendet werden.

Was ist IoT

Bei IoT (Internet of Things) oder wie es auch heisst, IoE (Internet of Everything) geht es darum, mit Sensoren meist pysikalische Grössen wie Temperatur, Feuchtigkeit, Luftdruck, CO2-Gehalt von Luft oder auch Bilder von Kameras usw zu erfassen und diese an eine zentrale Stelle (IoT Platform) zur Verarbeitung zu senden und daraus Schlüsse für eine Verbesserung der Situation zu erzielen.  
Der Begriff "Smart" fasst je nach Anwendungsgebiet die intelligente Erfassung von Daten und deren Verarbeitung zusammen.

Python

Python ist eine objektorientierte Hochsprache, die vergleichsweise einfach zu erlernen ist. Python-Code ist leicht verständlich und sehr kompakt. Die Sprache ist ziemlich jung – sie wurde Anfang der 90er Jahre von Guido van Rossum entwickelt. Anscheinend ist er ein Fan von Monty Python, denn daher stammt der Name. 
Micro Python ist weitgehend kompatibel mit Python 3 und für den Betrieb auf einem Mikrocontroller optimiert. Unter Hobby-Elektronikern und Ingenieuren hat sich Micro Python hauptsächlich durch seine Verwendung auf dem Raspberry Pi oder dem ESP32 verbreitet.

Aber Micro Python hat auch Nachteile: Die Programme werden interpretiert und nicht kompiliert. Dies macht sich in der Geschwindigkeit bemerkbar. Python ist deutlich langsamer als C++. Darüber hinaus ist es schwieriger, Fehler zu finden. Syntaxfehler werden erst erkannt, wenn der Interpreter die entsprechende Zeile erreicht. Auf der anderen Seite ist die Fehlersuche auch einfacher weil der Code Zeile für Zeile interpretiert wird.

Interpreter vs Compiler

\begin{tabularx}{\textwidth}{lXX} &Interpreter&Compiler\\ Zeitpunkt der Quellcode-Übersetzung&während der Laufzeit der SW&vor der Ausführung der SW\\\midrule Vorgehensweise bei der Übersetzung&Zeile für Zeile&immer gesamter Source-Code\\ Anzeige von Codefehlern&nach jeder Zeile& nach kompletter Compilierung\\ Übersetzungsgeschwindigkeit&hoch&niedrig\\ Typische Sprachen&Python, PHP, Perl&C, C++\\ \end{tabularx}

\begin{tabularx}{\textwidth}{lXX} &Vorteil&Nachteil\\\midrule Interpreter&einfacher Eintwicklungsprozess (Debugging)&ineffizienter Übersetzungsprozess, langsam\\ Compiler&übermittelt dem Prozessor den kompletten ausführbaren Maschinencode& jegliche Anpassungen am Code erfordern Neuübersetzung\\ \end{tabularx}

Objektorientiert

HW und IDE

Hardware ESP32-Pico-Kit/V4.1

Espressif ist ein führender Hersteller von Chips für die IoT Branche. Die Chips sind unter dem Namen ESP32 in verschiedenen Ausführungen im Markt verfügbar. 
Beschreibung unter: Espressif
Von Espressiv aber auch von anderen Herstellern, sind verschiedene Development-Kits verfügbar:
Espressif Dev-Kits
Heltec

Beispiel eines Dev-Kits:
ESP32 Pico V3: Chip Serie
Pico: SiP (System in Package) Serie

Thonny IDE für MicroPython und Python

1. Windows: Treiber für UART-USB installieren und Thonny IDE unter thonny.org downloaden und installieren.

2. MicroPython-Firmware auf das Development-Board laden. Diese kann direkt über "Einstellungen" in Thonny ins Flash-Memory geladen werden. Wenn die entsprechende Version für das Board nicht verfügbar ist, kann es unter: MicroPython Firmware geladen werden. Dazu wie folgt vorgehen:

1. esp.idf Powershell installieren
2. PC neu starten
3. Firmware ESP32-C6 downloaden (.bin File)
4. ESP32 DevKit anschliessen an USB-USART Schnittstelle
5. Im Gerätemanager unter COM Ports prüfen, welcher Port zugewiesen wurde.
6. Powershell starten und Flash-Speicher löschen mit Eingabe:
esptool.py --chip esp32c6 --port COM3 erase_flash
7. Verzeichnis wechseln mit cd C:\Users\AZbinden\Downloads (Verzeichnis wo das .bin-File gespeichert ist)
8. Firmware flashen mit Befehl:
esptool.py --chip esp32c6 --port COM3 --baud 460800 write_flash -z 0x0 ESP32_GENERIC_C6-20240602-v1.24.0.bin

-------------
Info: WebREPL ist eine Abkürzung für "Web-based Remote Programming Environment for MicroPython." Damit kann über Internet mit einem Browser Code direkt im Controller geschrieben und ausgeführt werden

Materialliste für Praktikum

Hello World

MicroPython unterscheidet verschiedene Blöcke durch Einrückung. Es ist nicht erforderlich, geschweifte Klammeren ("{}") oder ähnliches zu verwenden. Dies ist einer der wichtigsten Unterschiede zu den meisten anderen Sprachen wie C. Der Vorteil dieser Methode ist, dass man praktisch zu einem gewissen Maß an Programmstruktur gezwungen wird. 
#Blöcke durch Einrücken; Eingabe direkt in der Shell von Thonny
if True:
  print ("Answer")
  print ("True")
else:
 print ("Answer")
  print ("False") # Unterschiedliche Einzüge--> Error

Code für Hello World

Praktischer Aufbau

Simulation mit Wokwi

Grundlagen microPython

print()

Über die print()-Anweisungen können Informationen auf das Terminal ausgegeben werden.
Die print()-Anweisung führt standardmäßig einen Zeilenwechsel aus. Mit end = '' '' kann dieser unterdrückt werden.

print("hello", end=" ")
print("world")

führt zu hello world
Zeichenfolgen können durch Komma getrennt werden.
print("hello", "world!")
Operatoren
print("3*4=")
print(3*4) #Ausgabe 3*4=12

Schliessen Sie das ESP32-Pico-Kit an Ihren PC an und geben Sie den Code in ein neues Programmfenster ein und klicken Sie auf Run. Die Datei muss dabei nicht auf dem ESP32 gespeichert werden. Probieren Sie es aus!

Zeitsteuerung

#Modul time
import time
#feste Verzögerungszeit:
time.sleep(seconds) #oder
time.sleep_ms(Milliseconds)
#nur sleep-Befehl importieren:
from time import sleep
#Benutzung mit:
sleep(seconds)
Der Nachteil dieser Funktionen ist, dass sie blockierend arbeiten. Das heißt, der Controller kann während der Wartezeit keine anderen Aufgaben ausführen, da er mit dem Zählen von Prozessorzyklen ausgelastet ist. Als Alternative bietet sich die Verwendung von Interrupts oder andern Programmiertechniken an. 
Für Zeitabfragen stehen die beiden folgenden Routinen zur Verfügung:
time.ticks_ms()
time.ticks_us()
sie geben die aktuelle Systemlaufzeit in Milli- bzw. Mikrosekunden an. Eine klassische Anwendung ist die Messung von Programmlaufzeiten.
# runtime.py
# Messung Laufzeit für Math-Berechnung 
import time
import math
while(True):
    start = time.ticks_us()
    #einmal mit x ausführen
    #und einmal ohne x ausführen
    x = math.exp(math.sin(22.5))
    stop = time.ticks_us()
    print(stop-start)

Pin lesen

# read_pin_25.py
from machine import Pin
from time import sleep
#Objekt pin_in erstellen I/O Pin 25 mit Pull_Down
pin_in = Pin(25, Pin.IN) #für interner Pulldown: Pin.PULL_DOWN
while(True):
    print(pin_in())
    sleep(1)

PWM

Ansteuerung eines Servos

PWM mit microPython

ADC

Spannungen präzise erfassen

In einem ersten Anwendungsbeispiel soll die Auswertung der Analog-Digital-Konverter unter MicroPython verdeutlicht werden. Hierzu wird die von einem Potentiometer erzeugte analoge Spannung erfasst und in der Konsole angezeigt. Die Pins 25-34 sollten als universelle ADC-Eingänge bevorzugt eingesetzt werden. Andere ADC-Eingänge sind mit wichtigen Doppelfunktionen belegt, so dass diese nur verwendet werden sollten, wenn die anderen Kanäle bereits belegt sind.

Im folgenden kommt der Analog-Eingangspin 34 zum Einsatz. Das folgende Programm zeigt die einfachste Möglichkeit zum Auslesen des ESP-32-ADCs:

 # ADC_atten_11db.py
        from machine import ADC, Pin
        from time import sleep
        adc = ADC(Pin(34))  # create ADC object on ADC pin
        adc.atten(ADC.ATTN_11DB)
        while True:
          print(adc.read())   # read value, 0-1024
          sleep(1)

Der zusätzlich verwendete 1-kOhm-Widerstand dient lediglich zum Schutz des ESP- Chips. Falls im Fehlerfall einmal eine etwas zu hohe Spannung am Potentiometer anliegen sollte, wird der maximale Strom begrenzt. Hierdurch kann eventuell eine Beschädigung des Chips vermieden werden.

Für verschiedene Spannungseinstellungen am Potentiometer werden Werte zwischen 0 und 4095 ausgegeben. Dies entspricht der Auflösung des ADCs von 12 Bit: = 4095. Wenn man die Spannungen allerdings mit einem externen Multimeter nachmisst, stellt man fest, dass der Wert 4095 bereits bei einer Spannung von einem Volt erreicht wird.

Bei Verwendung der Standardkonfiguration müssen die Eingangsspannungen am ADC-Pin also zwischen 0,0 V und 1,0 V liegen. Für alle Spannungen über 1,0 V wird der Wert 4095 angezeigt. Um diesen nutzbaren Spannungsbereich zu vergrößern, kann ein interner Spannungsteiler aktiviert werden.

Über die Befehle

können die internen Dämpfungen aktiviert werden. Die Abschwächung von 0 dB ist die Standardeinstellung. Auch die Auflösung ist konfigurierbar. Mögliche Optionen sind:

ADC.WIDTH_9BIT 9-Bit-Daten
ADC.WIDTH_10BIT 10-Bit-Daten
ADC.WIDTH_11BIT 11-Bit-Daten
ADC.WIDTH_12BIT 12-Bit-Daten (Standardkonfiguration)

Wichtiger Hinweis: Beachten Sie, dass die absolute maximale Nennspannung für die Eingangspins 3,6 V beträgt. Höhere Spannungen können den ESP32 beschädigen!

Nach dem Laden des Programms werden die Messwerte in der Konsole angezeigt.

Spannungsmessung

An einen ADC-Eingang des ESP32-Chips dürfen nur Spannungen von maximal 3,3 V angelegt werden. Höhere Spannungen können zur Zerstörung des gesamten Controllers führen. Mit einem Spannungsteiler ist der Messbereich aber nahezu beliebig erweiterbar. Die folgende Abbildung zeigt eine Schaltung zur Messung von Eingangsspannungen von bis zu ca. 30 V. Mit einem Spannungsteiler aus einem 100-kOhm- und einem 10-kOhm-Widerstand wird die Eingangsspannung auf 1/11 ihres Werts reduziert:

Ua = R1/(R1+R2) = 10 kOhm / 110 kOhm = 1/11

In elektronischen Schaltungen und Systemen liegen die Spannungen meist unterhalb von 30 V. Der Messbereich der Schaltung oben ist also für viele Anwendungen ausrei- chend. Spannungen über 50 V dürfen zudem ohnehin nur mit sicherheitsgeprüften Messgeräten gemessen werden.

Die im Schaltbild eingezeichneten Schottky-Dioden (SD1 und SD2) sind für die eigentliche Messung nicht erforderlich. Sie schützen lediglich die Eingänge des ESP-Chips vor Überlastung im Fehlerfall. Sobald die Spannung am Controller-Eingang die Werte 0 V unter- bzw. 3,3 V überschreitet, werden die Dioden leitend und verhindern so eventuelle Überspannungen am ADC.

Der Einfluss des Spannungsteilers ist natürlich in der Software zu berücksichtigen. Hierfür ist der Kalibrationsfaktor cal zuständig, über den der Faktor aus dem Spannungsteiler berechnet wird.

 # Voltmeter_30V.py
        from machine import ADC, Pin
        from time import sleep
        adc = ADC(Pin(34))  # create ADC object on ADC pin
        adc.atten(ADC.ATTN_11DB)
        Vref=3.30           # internal reference voltage
        R1=100              # for voltage divider
        R2=10               # for voltage divide
        cal=1*(R1+R2)/R2    # calibration factor
        while True:
          output="V = {:5.1f} V"
          print(output.format(adc.read()*Vref/4095*cal))
          sleep(1)

Bei einem Vergleich der Messwerte mit einem hochwertigen kalibrierten Multimeter zeigt sich die bereits genannte Nichtlinearität des ESP32-ADC. Der Fehler kann mehr als ein Volt betragen. Das folgende Programm liefert wesentlich präzisere Werte:

# Voltmeter_30V_lin.py
from machine import ADC, Pin 
from time import sleep

adc = ADC(Pin(34)) # create ADC object on ADC pin 
adc.atten(ADC.ATTN_11DB)
Vref=3.30 # internal reference voltage 
R1=100 # for voltage divider
R2=10 # for voltage divide 
cal=1*(R1+R2)/R2 # calibration factor

def ReadVoltage():
  ADC=adc.read()
  return -0.000000000009824*pow(ADC,3)+0.000000016557283*pow(ADC,2)+0.00085459
  6860691*ADC+0.065440348345433;

while True:
   output="V = {:5.1f} V" 
   print(output.format(ReadVoltage()*cal)) 
   sleep(1)

Die hier verwendeten Konstanten im Programm in Form des cal-Faktors und der Referenzspannung werden als Software-Kalibrationsparameter bezeichnet. Alternativ könnte eine Hardwarekalibration mit einem Präzisionspotentiometer oder einem Spindeltrimmer durchgeführt werden. Spezielle für die Anwendung entwickelte Trimmer sind sehr präzise justierbar und weisen durch ihre geschlossene Bauform eine gute Langzeitstabilität auf. Beim Vergleich mit einem kalibrierten Messgerät wird der Abgleichtrimmer so lange justiert, bis das ESP32-Voltmeter den korrekten Wert anzeigt. Eine Änderung der Parameter im Programm ist dann nicht mehr erforderlich. So kann bei speziellen Umweltbedingungen (z. B. bei extremen Temperaturen) eine Nachkalibrierung ohne Programmänderung vorgenommen werden.

Ein Nachteil der Hardware-Kalibration ist jedoch, dass zusätzliche Bauelemente erforderlich sind. Potentiometer und insbesondere Spindeltrimmer sind vergleichsweise teuer und zeigen zudem immer gewisse Driften. Umwelteinflüsse führen zu Veränderungen der elektrischen Parameter, sodass sich die Langzeitstabilität des Messgeräts verschlechtert. Wählt man für das Potentiometer einen Gesamtwiderstand von 1 Kiloohm, kann der Messwert um etwa 5 % nachjustiert werden. Größere Widerstandswerte liefern zwar einen weiteren Einstellbereich, die Justierpräzision wird dann aber geringer.

Display

Geht es darum, eine Uhrzeit oder die Messdaten von Klimasensoren anzuzeigen, dann ist es von Vorteil, ein eigenes kleines Display an den Controller anzuschließen. Ein weit verbreiteter Typ ist das SSD1306. Diese Anzeigeeinheit verfügt bei einer Größe von nur 0,96 Zoll (ca. 2,5 cm) über eine Auflösung von 128 x 64 Pixel.

MicroPython bringt standardmäßig einen Treiber für diese Displayversion mit. Dieser wird bereits beim Upload des Dateisystems auf den ESP32 mit geladen. Der Treiber ermöglicht sowohl die Darstellung von Texten und numerischen Daten als auch die Anzeige von ein- fachen Grafiken. Das SSD1306-Display ist mit internem RAM und einem eigenen Oszillator ausgestattet. Dadurch kann es ohne weitere externe Komponenten betrieben werden. Darüber hinaus verfügt es über eine Helligkeitsregelung mit 256 einstellbaren Stufen.

Die einfachsten Versionen von SSD1306-Boards verfügen über lediglich vier Pins. Dies ist ausreichend, um das Display über den I2C-Bus anzusteuern. Andere Varianten weisen Reset-Pins oder eine zusätzliche SPI-Schnittstelle auf.

Das folgende Skript gibt eine Textnachricht und ein einfaches Grafikelement in Form eines Rahmens auf das Display aus:

# SSD1306_DEMO.py
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
i2c=I2C(-1,scl=Pin(22),sda=Pin(21))
# I2C Pin assignment
oled_width=128
oled_height=64
oled = SSD1306_I2C(oled_width, oled_height, i2c)
lin_hight = 9
col_width = 8

def text_write(text,lin, col):
        oled.text(text,col*col_width,lin*lin_hight)
        oled.fill(0)
        text_write("MicroPython", 1, 2)
        text_write("for", 3, 6)
        text_write("ESP32", 5, 5)
        oled.rect(5, 5, 116, 52, 1)
        oled.show()

Für die Ansteuerung des Displays müssen die erforderlichen Module importiert werden. Wie bereits erwähnt, sind die Libraries „machine“ und „SSD1306“ im Normalfall bereits als Standardbibliotheken vorhanden. Falls erforderlich, kann jedoch auch eine ssd1306. py-Datei separat auf das Board hochgeladen werden. Die Pin-Deklaration für den I2C-Bus erfolgt über:

         i2c = I2C (-1, scl = Pin (22), sda = Pin (21))

Die Anzahl der Pixel des angeschlossenen Moduls wird mit folgenden Variablen

         oled_width = 128
oled_height = 64
erfasst. Der Parameter "-1" gibt an, dass das verwendete Modul weder über Reset- noch über Interrupt-Pins verfügt. Mit diesen Angaben kann ein SSD1306_I2C-Objekt mit dem Namen oled erstellt werden. Hier werden die zuvor definierten Daten übernommen:
oled = ssd1306.SSD1306_I2C (oled_width, oled_height, i2c)
Damit ist das Display betriebsbereit. Über die Funktion "text()" werden Informationen auf die Anzeige ausgegeben. Mit der Methode show() wird die Anzeige aktualisiert. Die text()- Funktion akzeptiert die folgenden Argumente:
• Nachricht (Typ String)
• X-Position und Y-Position des Textfeldes in Pixeleinheiten
• Optionale Textfarbe: 0 = schwarz (dunkel) und 1 = weiß (hell)
Die folgende Anweisung gibt eine Meldung in weißer bzw. blauer Farbe auf dunklen Hinter- grund aus. Der Text beginnt an der Position x = 0 und y = 0:
oled.text (‚MicroPython!', 0, 0)
Die show()-Methode lässt die Änderungen auf dem Display sichtbar werden. Die Library enthält zudem noch weitere nützliche Methoden. Die Funktion fill(1) erzeugt einen voll- ständig weißen Bildschirm, d. h. alle Pixel leuchten. Mit oled.fill (0) werden alle Pixel auf schwarz, bzw. dunkel gesetzt.
Die pixel()-Methode erlaubt grafische Darstellungen. Sie akzeptiert die folgenden Argu- mente:
• X-Koordinate: Pixelposition horizontal • Y-Koordinate: Pixelposition vertikal
• Pixelfarbe: 0 = schwarz, 1 = weiß
Ein einzelnes weißes Pixel in der oberen linken Ecke kann damit so erzeugt werden: oled.pixel (0, 0, 1)
Mit
oled.invert (True).
werden die OLED-Farben invertiert. Dabei wird weiß zu schwarz und umgekehrt.
Um zu den ursprünglichen Farben zurückzukehren, kann oled.invert (False) verwendet werden.

Grafische Darstellungen

Neben den Pixel-Anweisungen sind auch noch weitere Grafik-Befehle verfügbar. Horizontale und vertikale Linien können mit .hline () bzw. .vline () gezeichnet werden. Dabei werden die XY-Startposition sowie die Linienlänge und -farbe angegeben.

Das folgende Programm zeichnet beispielsweise konzentrische rechteckige Rahmen auf das Display:

# SSD1306_frames.py
        from machine import Pin, I2C
        import ssd1306
        i2c = I2C(-1,scl=Pin(22),sda=Pin(21))
        oled_width = 128
        oled_height = 64
        oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
        for n in [0,5,10,15,20,25]:
          oled.hline(n, n, oled_width-1-2*n, 1-2*n)
          oled.hline(n, oled_height-1-n, oled_width-1-2*n, 1-2*n)
          oled.vline(n, n, oled_height-1-2*n, 1-2*n)
          oled.vline(oled_width-1-n, n, oled_height-2*n, 1-2*n)
          oled.show()

Diagonalen werden über .line (x1, y1, x2, y2, c) zwischen zwei festgelegten Punkten (x1, y1) und (x2, y2) gezogen. Der Parameter c steuert die Farbe der gezeichneten Linie. Für einfache Grafiken können Bitmaps Pixel für Pixel in den Displaypuffer geschrieben werden. Das folgende Programm zeigt ein entsprechendes Beispiel:

# SSD1306_bitmap_DEMO.py
        from machine import Pin, I2C
        import ssd1306
        import urandom
        i2c = I2C(-1, scl=Pin(22), sda=Pin(21))
        oled_width = 128
        oled_height = 64
        oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
        # frame
        oled.hline(0, 0, oled_width-1, 1)
        oled.hline(0, oled_height-1, oled_width-1, 1)
        oled.vline(0, 0, oled_height-1, 1)
        oled.vline(oled_width-1, 0, oled_height, 1)
        oled.show()
        ICON = [
            [0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
            [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
            [0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
            [0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
            [0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0],
            [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0],
            [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0],
            [0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
]
        for n in range(12):
          xofs = urandom.randint(1, oled_width-12)
          yofs = urandom.randint(1, oled_height-12)
          for y, row in enumerate(ICON):
            for x, c in enumerate(row):
              oled.pixel(x+xofs, y+yofs, c)
oled.show()

OLED Display als Datenplotter

Neben Bitmaps lassen sich auf dem OLED-Display auch Messdaten graphisch darstellen. Damit ist man nicht mehr auf die Plotter-Funktion in Thonny angewiesen, sondern kann auch Stand-alone-Geräte mit Grafikausgabe aufbauen.

# Rolling_ECG_display.py
        from machine import Pin, ADC, I2C
        from time import sleep
        import ssd1306
        i2c = I2C(-1, scl=Pin(22), sda=Pin(21))
        oled_width = 128
        oled_height = 64
        oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
        pot = ADC(Pin(34))
        pot.atten(ADC.ATTN_11DB)       #Full range: 3.3v
        vMax = 3.4
        dotPos_old = int(oled_height/2)
        while True:
          pot_value = pot.read()
          voltage = 0.000816*pot_value + 0.037822
          # print(voltage)
          dotPos_new = int(voltage/vMax*oled_height)
          oled.line(0, dotPos_new, 0, dotPos_old, 1)
          oled.scroll(1, 0)
          oled.line(0, dotPos_new, 0, dotPos_old, 0)
          dotPos_old = dotPos_new
          oled.pixel(0, int(oled_height/2), 1)
          oled.show()

Für die Messwert-Aufnahme kann wieder ein Potentiometer an den ADC-Eingang 34 an- geschlossen werden. Nach dem Start des Programms werden die Span- nungswerte kontinuierlich auf dem Display dargestellt.

Ermöglicht wird dies durch die integrierte Scroll-Funktion. Über die Anweisung

        oled.scroll(1, 0)

wird der gesamte Bildschirminhalt um ein Pixel verschoben. Wenn man nicht einzelne Pixel aufzeichnen will, sondern eine durchlaufende Kurve, dann muss man die Punkte über Linien verbinden. Hierzu sind zwei Variablen erforderlich:

und

dotPos_old

dotPos_new

Damit wird eine Linie zwischen dem aktuellen und dem letzten Messwert gezogen. Dann wird das Display um ein Pixel verschoben. Schließlich wird die neue Position an den Zwischenspeicher übergeben:

         dotPos_old = dotPos_new

und das Spiel beginnt von vorne. Die folgende Abbildung zeigt ein Beispiel für kontinuierlich veränderte Potentiometer-Werte:

Interrupts

Interrupts (Quelle: Elektor)

Sensoren

Feuchtigkeits und Temperatursensor

DHT11/22

Digitaler Temperatursensor DS18B20
  • Betriebsspannung – 3.0V bis 5.5V
  • Messbarer Temperaturbereich von -55 °C bis +125 °C
  • Toleranz – ±0.5 °C
  • Auflösung des Thermometers von 9 bis 12bit
  • jeder Sensor hat einen eindeutigen und einmaligen 64bit Code auf dem onboard ROM
from machine import Pin
from onewire import OneWire
from ds18x20 import DS18X20
from time import sleep
#digitaler Temperatursensor DS18B20 am GPIO 4 angeschlossen
pin = Pin(4)
#erstellen eines Objektes zum Zugriff auf die / den Temperatursensor
tempSensor = DS18X20(OneWire(pin))
#suchen der Sensoren
sensors = tempSensor.scan()
#ausgeben der Liste der gefundenen Sensoren
print('gefundende(r) Sensor / Sensoren: ', len(sensors))
#Wenn min. 1 Sensor gefunden wurde dann...
if(sensors):
  #Ausgabe der Tabellenüberschriften
  print("Sensor\t\t\t|Celsius\t|Fahrenheit\t|Kelvin")
  #eine Endlosschleife starten
  while True:
    #empfangen und umwandeln des Sensorwertes
    tempSensor.convert_temp()
    #eine kleine Pause von 0,5 Sekunden
    sleep(0.5)
    #Anzahl der Nachkommastellen für das runden der Temperaturwerte
    digits = 2
    
    #Für jeden gefundenen Sensor mache...
    for sensor in sensors:
      #lesen und runden des Temperaturwertes in Grad Celsius
      tempCelsius = round(tempSensor.read_temp(sensor),digits)
      #berechnen des Temperaturwertes in Grad Fahrenheit aus Grad Celsius
      tempFahrenheit = round((tempCelsius * 9/5)+32,digits)
      #berechnen des Temperaturwertes in Grad Kelvin aus Grad Celsius
      tempKelvin = round(tempCelsius + 273.15, digits)
      #Ausgeben der Temperaturwerte
      print(sensor, "\t|", tempCelsius,"\t|", tempFahrenheit,"\t|", tempKelvin )
      #eine kleine Pause von 2 Sekunden
    sleep(2)
else:
  #Wenn keine Temperatursensoren vom Typ DS18B20 gefunden wurden dann soll eine Fehlermeldung ausgegeben werden.
  print("Es wurden keine Sensoren vom Typ DS18B20 gefunden!")

Daten des Sensors auf SD-Card speichern inkl. Zeitstempel

Kapazitiver Touch-Sensor (Quelle: Elektor)

Dok Espressiv

Berührungssensoren sind die Klassiker der Sensortechnik. In den 1970er und 80er Jahren durften sie an keinem Fernsehgerät und an keiner Stereoanlage fehlen. Sie ersetzten die bekannten Bedienungsknöpfe und erlaubten es, durch bloße Berührung Programme zu wechseln oder die Lautstärke zu ändern. Aber auch die Bildschirme moderner Smartphones sind im Prinzip Touch-Sensoren, die es erlauben, auf Tastaturen oder mechanische Bedienungselemente zu verzichten.

Ein konventioneller Berührungssensor besteht aus metallischen Elektroden. Gelegentlich sind diese mit einer dünnen Schutzoberfläche abgedeckt. Wird der Sensor mit einem Finger berührt, kann dies anhand einer Kapazitätsänderung der Elektrode festgestellt werden. Um die Touch-Sensoren des ESP32 nutzen zu können, muss die TouchPad-Klasse im Maschinenmodul geladen werden. Danach steht die Methode TouchPad.read() zur Verfügung:

from machine import TouchPad, Pin
t=TouchPad(Pin(14))
t.read() # Returns a smaller number when touched

TouchPad.read() gibt eine relative Kapazitätsänderung zurück. Ohne Berührung von am ESP angeschlossenen Sensorflächen werden große Zahlen, d. h. typischerweise Werte größer als 800 zurückgegeben. Wenn eine aktive Elektrode berührt wird, fallen die Werte auf unter 200 ab.

Der ESP32 stellt zehn TouchPad-Eingänge zur Verfügung. Diese finden sich an den Pins: 0, 2, 4, 12, 13, 14, 15, 27, 32 und 33. Mit dem folgendem Programm kann die Funktion der berührungsempfindlichen Eingänge überprüft werden. Mit dem Plotter von Thonny können die Werte der Sensoren überprüft werden.

# touch_test.py
from machine import TouchPad, Pin
from time import sleep
from machine import TouchPad, Pin

t1 = TouchPad(Pin(2))
t2 = TouchPad(Pin(4))

while True:
  cap1 = t1.read()
  print(cap1, end=" ")
  cap2 = t2.read()
  print(cap2)
  sleep(1)
andreasZbinden