I<+>2<+>C-Displays mit dem ESP32 ansteuern

Aus LinuxUser 08/2023

I<+>2<+>C-Displays mit dem ESP32 ansteuern

© Olena Yakobchuk / 123RF.com

Wo geht's hin?

Beim Bau einer ESP32-basierten Zugzielanzeige für eine Spur-G-Gartenbahn stellen sich viele interessante Herausforderungen.

Eine Zugzielanzeige zu bauen, klingt erst einmal gar nicht so kompliziert. Wie so häufig steckt der Teufel aber im Detail. LGB-Modellbahnen für den Garten sind üblicherweise wetterfest und recht groß. Der Maßstab beträgt für die sogenannte Spur G satte 1:22,5. Hier handelt es sich also um recht geräumige Modelle. Damit der Zielanzeiger realistisch wirkt, verwenden wir kleine I2C-Displays [1].

Bei I2C haben die Geräte am Bus eindeutige Adressen und lassen sich deshalb leicht ansteuern. Die von uns genutzten 0,91 Zoll kleinen Displays bringen allerdings eine unveränderliche feste Adresse mit. Somit ist es unmöglich, zwei dieser Displays an einem I2C-Bus zu betreiben. Die erste Idee zur Lösung des Problems war, einen einfachen I2C-Multiplexer zu verwenden, der aus einem Bus zwei macht. Das hätte aber mehr Platz für die Elektronik erfordert.

Nach einigen Tagen des Grübelns und Probierens kristallisierte sich heraus, dass es am einfachsten ist, mit zwei unabhängigen I2C-Schnittstellen zu arbeiten – eine für jedes Display. Hier stellte sich allerdings heraus, dass sich die C-Bibliotheken aus der Arduino-Welt als reichlich starrsinnig und unflexibel – weil hartkodiert – erweisen. Interessanterweise ist die I2C-Implementierung von MicroPython im Gegensatz dazu recht flexibel, und auch die Bibliothek fürs Display arbeitet ohne Probleme mit unterschiedlichen Schnittstellen.

In der Zielkonfiguration steckt die Elektronik im Inneren einer Lokomotive, sodass sich am Programm nichts mehr ändern lässt. Daher ist es wichtig, eine Möglichkeit zu schaffen, die Anzeigen von außen zu konfigurieren. Hier kommt der ESP32 ins Spiel: Er bringt alle Komponenten mit, um einen Access Point mit Webserver bereitzustellen, über den Sie die Daten für die Displays eingeben.

Wir verwenden in diesem Projekt ein ESP32 NodeMCU Development Board [2], das eine einfache Möglichkeit zum Programmieren des ESP32 bereitstellt. Üblicherweise steht in der Gartenbahn auch genug Platz zur Verfügung, um etwas größere Hardware unterzubringen. Falls Platz für Sie ein Thema ist, können Sie auch einen ESP32 ohne das Drumherum verwenden und über eine externe Programmierschnittstelle mit der nötigen Software versorgen.

Abbildung 1 zeigt unseren Aufbau. Um Platz zu sparen, verzichtet das ESP32-Dev-Board auf die sonst übliche Stiftleiste. Um einen gewissen Schutz gegen Witterung zu gewährleisten, empfiehlt es sich, die Bauteile vor dem Einbau mit einem Schrumpfschlauch zu umhüllen. Alternativ könnte man die komplette Platine auch mit Sprühplastik behandeln. Das hat allerdings den Nachteil, dass es nach der Behandlung fast unmöglich ist, noch etwas an das Board anzulöten.

Abbildung 1: Das ESP-Dev-Board mit Displays und einem Taster zur Richtungswahl.

Abbildung 1: Das ESP-Dev-Board mit Displays und einem Taster zur Richtungswahl.

Schaltplan

Zum Schaltplan (Abbildung 2) gibt es nur wenig zu sagen. Die beiden Displays sind mit den entsprechenden Pins verbunden, das Dev-Board versorgt sie mit 3,3 Volt Betriebsspannung. Den erforderlichen Strom bezieht der Testaufbau über die USB-Schnittstelle. Wenn man die Schaltung in einem Modell verbaut, gilt es, die 5 Volt Betriebsspannung über den entsprechenden Pin von extern an das Dev-Board zu führen.

Abbildung 2: Der Schaltplan f&uuml;r die Zugzielanzeige.

Abbildung 2: Der Schaltplan für die Zugzielanzeige.

Die I2C-Schnittstelle dient dazu, Mikrocontroller und deren Peripherie innerhalb von Geräten zu verbinden. Daher sollten die Leitungen nicht allzu lang ausfallen. Die Praxis zeigt allerdings, dass I2C auch bei Kabellängen von über einem Meter das Signal noch sauber überträgt.

Programm

Wie eingangs erwähnt, verwenden wir für unser Projekt MicroPython als Programmiersprache. Wie Sie es auf dem ESP32 installieren und verwenden, zeigt der Artikel “Kleine Schlange” [3] aus RPG 06/2022. Alle Dateien, die wir im Folgenden besprechen, finden Sie im Download-Bereich zu diesem Artikel [4].

Das Programm gliedert sich zur besseren Übersicht in mehrere Dateien auf. Zusätzlich gibt es noch das File data.txt (Listing 1), mit den Texten für die Displays. Neben den Programmen sind noch einige zusätzliche Bibliotheken vorhanden, die zum Ansteuern des Displays dienen. Schauen wir uns diese als Erstes ein wenig genauer an.

Listing 1

data.txt

Frankfurt
Hamburg

Die Bibliothek ssd1306.py [5] benötigen wir, um unsere kleinen Displays anzusteuern. Allerdings stellt sie von Haus aus nur sehr kleine Texte auf den Displays dar. Dieses Problem lässt sich leicht mit der Bibliothek writer.py [6] lösen. Sie erlaubt es, Texte in einem beliebigen Font in variabler Schriftgröße darzustellen. Die Fonts müssen Sie dabei in einem speziellen Format auf dem MicroPython-Gerät ablegen, in unserem Fall in der Datei freesans31.py. Möchten Sie einen anderen Font verwenden, finden Sie eine Anleitung dazu auf Github [7]. Hier muss man etwas tüfteln, um ans Ziel zu gelangen. Beachten Sie, dass Sie die Bibliotheken manuell auf das MicroPython-Gerät kopieren müssen.

Als Nächstes sehen wir uns die Datei config.py (Listing 2) genauer an. Wie der Name vermuten lässt, enthält sie die Einstellungen für die Zugzielanzeige. Sie umfassen neben der Display-Auflösung die verwendeten Pins für die zwei I2C-Schnittstellen. Sollten Sie ein anderes ESP32-Modul einsetzen, können Sie diese Pins also zentral ändern und müssen nicht im Code nach den passenden Stellen suchen. Bei den Einstellungen für den WLAN-Access-Point sollten Sie im Hinterkopf behalten, dass sich der Webserver erreichen lässt, sobald der ESP32 Spannung erhält. Das WLAN-Passwort ist also der einzige Zugriffsschutz für die Displays, wählen Sie also ein sicheres.

Listing 2

config.py

OLED_WIDTH = 128
OLED_HEIGHT = 32
I2C_1_SDA = 33
I2C_1_SCL = 32
I2C_2_SDA = 26
I2C_2_SCL = 25
FILENAME = 'data.txt'
INPUT = 14
PASS = 'Train'
SSID = 'Train'

Alle für die Anzeige auf den Displays zuständigen Funktionen fasst die Datei display.py (Listing 3) zusammen. Der Code aktiviert die zwei I2C-Schnittstellen, liest die Texte aus der Datei data.txt und stellt sie auf den beiden Displays dar. Die Variable direction steuert dabei, welche Zeile aus der Datei auf den Displays erscheint.

Aktuell arbeitet das Programm nur mir zwei Texten, die sich über den Eingang cfg.INPUT wechseln lassen. Der Eingang ist dazu gedacht, von einer Steuerplatine der Lok verwendet zu werden (Open-Collector-Ausgang). Der Taster, den wir hier zum Test angeschlossen haben, kann prellen, sodass die Anzeige beim Umschalten eventuell flackert.

Listing 3

display.py

import config as cfg
from machine import SoftI2C,Pin
from writer import Writer
import freesans31
import ssd1306
class Display:
  def show(direction):
    i2c1 = SoftI2C(scl=Pin(cfg.I2C_1_SCL), sda=Pin(cfg.I2C_1_SDA))
    i2c2 = SoftI2C(scl=Pin(cfg.I2C_2_SCL), sda=Pin(cfg.I2C_2_SDA))
    oled1 = ssd1306.SSD1306_I2C(cfg.OLED_WIDTH, cfg.OLED_HEIGHT, i2c1)
    oled2 = ssd1306.SSD1306_I2C(cfg.OLED_WIDTH, cfg.OLED_HEIGHT, i2c2)
    file = open(cfg.FILENAME)
    lines=file.read()
    file.close()
    line=lines.split("\n")
    wri1 = Writer(oled1, freesans31)
    wri2 = Writer(oled2, freesans31)
    Writer.set_textpos(oled1, 0, 0)
    Writer.set_textpos(oled2, 0, 0)
    wri1.printstring(line[direction])
    wri2.printstring(line[direction])
    oled1.show()
    oled2.show()

Kommen wir jetzt zur Datei, die alle Abläufe für den Zielanzeiger steuert. Angelehnt an die englische Bezeichnung Train Direction Indicator lautet der Dateiname tdi.py (Listing 4). Im Kern startet das Programm einen Access Point und einen einfach aufgebauten Webserver ohne Bibliotheken. Letzterer erlaubt es, die Einträge in der Datei data.txt zu ändern. Zum Testen starten Sie die Datei tdi.py über die Python-IDE Thonny. Wenn Sie möchten, dass das Programm beim Einschalten automatisch mitlädt, speichern Sie es unter dem Namen main.py.

Um Zugriff auf den Webserver zu erlangen, verbinden Sie sich zunächst mit dem Access Point des ESP32. Die Zugangsdaten legen Sie in der Datei config.py unter SSID und PASS fest. Nachdem die Verbindung zum ESP32 steht, öffnen Sie einen Webbrowser und geben die IP-Adresse des Webservers ein, etwa http://192.168.4.1. Jetzt sollten Sie die Webseite zum Ändern der Einträge sehen (Abbildung 3).

Abbildung 3: Der rudiment&auml;re Webserver erlaubt es, Eintr&auml;ge in der <code>data.txt</code> zu &auml;ndern. Dieses File enth&auml;lt die im Display angezeigten Begriffe.

Abbildung 3: Der rudimentäre Webserver erlaubt es, Einträge in der data.txt zu ändern. Dieses File enthält die im Display angezeigten Begriffe.

Listing 4

tdiv2.py

from machine import Pin
import config as cfg
import display
from time import sleep
import network
import socket
accesspoint = network.WLAN(network.AP_IF)
accesspoint.active(True)
accesspoint.config(password=cfg.PASS,essid=cfg.SSID)
while accesspoint.active() == False:
  sleep(1)
print('Connection successful')
accesspoint.ifconfig(('192.168.4.1' , '255.255.255.0' , '192.168.4.1' , '192.168.4.1'))
print(accesspoint.ifconfig())
def handle_interrupt(pin):
  d = display.Display
  d.show(switch.value())
switch = Pin(cfg.INPUT, Pin.IN, Pin.PULL_UP)
switch.irq(trigger=3, handler=handle_interrupt)
html = """<!DOCTYPE html>
<html>
  <head> <title>Train Config</title> </head>
  <body>
   <form action="/" method=get>
    Destination 1: <input name ='0' type='text'><br>
    Destination 2: <input name ='1' type='text'><br>
    <input type='submit' value='Submit'>
   </form>
  </body>
</html>
"""
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.bind(addr)
s.listen(1)
print('listening on', addr)
handle_interrupt(cfg.INPUT)
while(True):
  conn, addr = s.accept()
  print('client connected from', addr)
  request = conn.recv(1024)
  request = str(request)
  p=""
  i=request.find ('?') + 1
  print(i)
  if (i !=0 ):
    while(request[i]!=' '):
      if (request[i]=='\\'): break
      p+=request[i]
      i+=1
      param = p.split('&')
      dest1 = param[0].split('=')[1]
      dest2 = param[1].split('=')[1]
      if (dest1!=''):
        f = open("data.txt", "w")
        f.write(dest1+"\n")
        f.write(dest2+"\n")
        f.close()
  d = display.Display
  d.show(1)
  conn.send('HTTP/1.1 200 OK\n')
  conn.send('Content-Type: text/html\n')
  conn.send('Connection: close\n\n')
  conn.send(html)
  conn.close()

Tipps

Wenn Sie beginnen, das Programm an Ihre Bedürfnisse anzupassen, kann es hin und wieder zu kleinen Problemen kommen. Manchmal verabschiedet sich das Programm etwa mit der Ausrede EADDRINUSE. Der Grund dafür liegt darin, dass trotz sauberen Beenden des Programms noch Teile davon in den Untiefen des ESP32 laufen und die WLAN-Schnittstelle blockieren. Hier hilft tatsächlich nur ein Hardware-Reset, den Sie über den Taster auf dem Dev-Board ausführen. Interessanterweise ist der Reset-Taster auf vielen Dev-Boards mit EN (Enable) beschriftet.

Sehr selten tritt das recht nervige Problem auf, dass das Programm nicht die aktuell gespeicherten Dateien verwendet. Sollte das bei Ihnen der Fall sein, hilft auch hier im Zweifelsfall ein Hardware-Reset. Sollte das nicht der Fall sein, beenden Sie die IDE, trennen den ESP32 kurz vom Strom, und stecken ihn danach wieder an. Jetzt starten Sie die IDE erneut, damit alles wieder so läuft wie gewohnt.

Fazit

Dieses Projekt zeigt sehr gut, dass man nicht zwangsläufig C oder Assembler als Programmiersprache für Mikrocontroller verwenden muss. Es gibt viele Anwendungen, bei denen man mit MicroPython schon sehr weit kommt. Kommt es allerdings auf Geschwindigkeit oder präzises Timing an, schneidet MicroPython eher schlecht ab. Oftmals erweisen sich die MicroPython-Bibliotheken sogar als flexibler als die in anderen Programmiersprachen. Beispielhaft seien hier die Softwareimplementierungen der I2C- und SPI-Schnittstellen genannt. Unsere Zugzielanzeige bietet Bastlern viel Raum für zusätzliche Erweiterungen. Der ESP32 hat selbst beim Betrieb mit MicroPython noch ausreichend CPU-Reserven, um einiges an Erweiterungen bedienen zu können. (tle)

Der Autor

Martin Mohr hat die komplette Entwicklung der modernen Computertechnik live miterlebt. Nach dem Studium entwickelte er überwiegend Java-Applikationen. Mit dem Raspberry Pi erwachte seine alte Liebe zur Elektronik wieder.

Infos

  1. 0,91-Zoll-I2C-Display: https://www.amazon.de/gp/product/B07BDFXFRK/

  2. ESP32 NodeMCU Development Board: https://www.amazon.de/AZDelivery-unverl%C3%B6tet-Development-Nachfolgermodell-inklusive/dp/B08BTWJGFX/

  3. MicroPython auf dem ESP32: Martin Mohr, “Kleine Schlange”, RPG 06/2022, S. 26, https://www.raspi-geek.de/47908

  4. Dateien zum Artikel: https://www.linux-user.de/dl/49271

  5. Display-Bibliothek: https://github.com/stlehmann/micropython-ssd1306

  6. Bibliothek zur Textdarstellung: https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py

  7. Erzeugen von Fonts für writer.py: https://github.com/peterhinch/micropython-font-to-py

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDF
LinuxUser 08/2023 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben