Raspberry Pi und LCD1602 bzw. LCD2004

Manchmal lohnt es sich, auch grundsätzlich bekannte Themen erneut aufzugreifen, sei es als Einführung für junge Programmierer oder Vertiefung für die Fortgeschrittenen. Durch die Aktualisierung des Raspberry Pi OS seit meinen letzten Versuchen mit dem LCD musste ich u.a. auch die Programm-Bibliotheken (Python-Module) neu installieren. Und dabei bin ich im Internet auf eine unübersichtliche Vielzahl von Beschreibungen, Schaltplänen und Beispielprogrammen gestoßen, die heute z.T. nicht mehr funktionieren oder sogar zu Beschädigungen führen können.  Da auch die Programm-Bibliotheken auf der Produktseite und die Beschreibungen im eBook nicht mehr ganz aktuell sind, möchte ich die Grundlagen der Installation und einfache Beispiele hier im Blog aufgreifen.

Verwendete Hardware:

1

Raspberry Pi 3B, 4B oder beliebiger Raspberry Pi

mit aktuellem Raspberry Pi OS (auf Basis Debian Bullseye)

plus Zubehör

1

LCD1602/2004 mit/ohne I2C-Adapter

1

I2C-Adapter 

1

Logic Level ConverterTXS0108E

1

Breadboard, am besten mit Breakout-Kabel/-Adapter
Jumperkabel, 220Ω-Widerstand, 10kΩ-Potentiometer

1

Sensor BME280 (Temperatur, Druck, Feuchtigkeit)


LCD Pinout

Die Pinbelegung der verschiedenen LCDs 1602 und 2004, grün oder blau, ist einheitlich wie dargestellt. Dabei bedeutet 1602 sechzehn Zeichen und zwei Zeilen, 2004 entsprechend jeweils zwanzig Zeichen in vier Zeilen. Aber aufgepasst: Die Zählung Zeile/Reihe (engl. row) und der Spalte (engl. column) beginnt jeweils bei 0. Beispiel lcd.cursor_pos = (1,0) setzt den Cursor in der zweiten Zeile auf die erste Position.

Bibliotheken

Das Wirrwarr unterschiedlicher Bibliotheken, insbesondere auch für die verschiedenen Ausführungen mit und ohne I2C-Adapter haben den Programmierer Danilo Bargen veranlasst, ein schlankes Python-Modul zu schreiben, das alle Varianten berücksichtigt. Wenn man sich in die Thematik einliest, wird man feststellen, dass man nicht unbedingt das Paket von Github herunterladen, entpacken und installieren muss. Denn es hat Eingang gefunden bei PyPi (The Python Package Index (PyPI) is a repository of software for the Python programming language.) und man kann es deshalb direkt auf dem Raspi mit dem Tool pip installieren.

Das Modul heißt RPLCD, der Terminal-Befehl lautet also:

 sudo pip install RPLCD

Während die Methoden für die Bedienung, also das eigentliche Python-Programm, weitgehend identisch sind, muss man beim Importieren und Instanziieren des Objekts lcd beachten, ob man die GPIO-Version oder die I2C-Version des Displays (siehe nachfolgende Bilder) benutzt.

Hier der jeweilige Beginn der Programme …

GPIO-Version:

 from RPi import GPIO
 GPIO.setwarnings(False)
 from RPLCD.gpio import CharLCD
 ##lcd = CharLCD(pin_rs=36, pin_rw=None, pin_e=40, pins_data=[31, 33, 35, 37],
 ##             numbering_mode=GPIO.BOARD,cols=16, rows=2, dotsize=8,
 ##             charmap='A02', auto_linebreaks=True, compat_mode=True)
 
 lcd = CharLCD(pin_rs=16, pin_rw=None, pin_e=21, pins_data=[6, 13, 19, 26],
               numbering_mode=GPIO.BCM,cols=16, rows=2, dotsize=8,
               charmap='A02', auto_linebreaks=True, compat_mode=True)

Dazu zwei Anmerkungen:
1. Auskommentiert ist die (sehr lange) Zeile für GPIO.BOARD, also die physischen Pin-Bezeichnungen von 1 bis 40. Die nicht auskommentierte Zeile zeigt die Instanziierung für GPIO.BCM, also die Pin-Bezeichnung mit den GPIO-Nummern.
2. Selbstverständlich können andere Pins verwendet werden. Ich favorisiere die letzten Pins, die nicht zugleich für die elektronischen Schnittstellen (z.B. I2C, SPI) verwendet werden können. Wichtig ist, dass Schaltung und Pin-Nummern der Instanziierung übereinstimmen.

I2C-Version:

 from RPLCD.i2c import CharLCD
 lcd = CharLCD('PCF8574',0x27)
 
 lcd.write_string('Hello World')

Entsprechend der einfacheren Schaltung mit vier Leitungen für Stromversorgung und I2C-Schnittstelle beschränkt sich auch die Instanziierung auf die Bezeichnung des verwendeten Adapters und die I2C-Adresse.

Allerdings sollte man bei der Hardware nicht auf einen Logic Level Converter (LLC) verzichten. Natürlich war ich neugierig und habe den I2C-Adapter direkt an 3,3 V und 5 V angeschlossen. Bei 3,3 V ist das Display auch bei voll aufgedrehtem Poti sehr schwach. Bei 5 V habe ich an den I2C-Pins 4,88V gemessen, also zu viel für die Pins des Raspberry Pi. The book says: 3,3V max!

Bei mir ist alles heilgeblieben und offensichtlich auch bei den Bloggern, die entsprechende Schaltpläne im Internet veröffentlichen. Aber dringende Empfehlung: LLC wie auf dem folgenden Bild verwenden:

Kurze Anmerkung zum I2C-Adapter: In der Abbildung aus dem eBook soll der I2C-Adapter mit seinen Kontakten gezeigt werden. Tatsächlich wird der Adapter als „backpack“ eingelötet, also unsichtbar unterhalb des LCD. Entscheidend ist, dass die vier Anschlüsse an der gezeigten Seite zugänglich sind.

Bei den folgenden Versuchen werde ich die GPIO-Version mit der GPIO.BCM-Nomenklatur verwenden. Hier zunächst der Schaltplan:

Beispielprogramme

Die verschiedenen Möglichkeiten (Methoden/Funktionen) des Moduls möchte ich mit folgenden Python-Programmen zeigen. Das Programm HelloWorld.py bzw.  sein Äquivalent HelloWorldI2C.py zeigen zunächst den String ‚Hello World‘ in der ersten Zeile an und danach ‚Zweite Zeile‘, dann blinkt das Display, z.B. um Aufmerksamkeit zu erregen, und schließlich wandern die Zeichen nach links aus dem Display. Zum Schluss möchte ich prüfen, ob alle Sonderzeichen angezeigt werden.

 from RPi import GPIO
 GPIO.setwarnings(False)
 from time import sleep
 from RPLCD.gpio import CharLCD
 ##lcd = CharLCD(pin_rs=36, pin_rw=None, pin_e=40, pins_data=[31, 33, 35, 37],
 ##             numbering_mode=GPIO.BOARD,cols=16, rows=2, dotsize=8,
 ##             charmap='A02', auto_linebreaks=True, compat_mode=True)
 
 lcd = CharLCD(pin_rs=16, pin_rw=None, pin_e=21, pins_data=[6, 13, 19, 26],
               numbering_mode=GPIO.BCM,cols=16, rows=2, dotsize=8,
               charmap='A02', auto_linebreaks=True, compat_mode=True)
 
 lcd.clear()
 sleep(1)
 lcd.write_string('Hello World')
 lcd.cursor_pos = (1,0)
 lcd.write_string('Zweite Zeile')
 sleep(3)
 
 for i in range(3):      # Display blinkt im Sekundentakt
     sleep(1)
     lcd.display_enabled=False
     sleep(1)
     lcd.display_enabled=True
 
 sleep(1)
 for i in range(16):     # Text wandert zeichenweise nach links aus
     lcd.shift_display(-1)
     sleep(1)
 
 lcd.home()
 lcd.clear()
 
 # Versuch, Sonderzeichen zu schreiben
 lcd.write_string('!"§$%&/()=?²³<>|°{}[]+-*#,;.:')
 sleep(10)
 
 lcd.close(clear=True)

Bei der Anzeige der Sonderzeichen wurde das Zeichen Grad° gar nicht, die Zeichen § und Hochzahlen ² und ³ nicht richtig angezeigt. Da ich aber bei der Temperaturanzeige °C haben möchte, mache ich Gebrauch von der Möglichkeit, acht eigene Zeichen aus 8x5 Pixeln zu kreieren. Dabei hilft ein Internet-Tool auf Seite https://omerk.github.io/lcdchargen/.

Es macht Sinn, den selbstdefinierten „Character“ im Binärformat zu kopieren und im eigenen Programm wie folgt einzufügen: (Download HelloWorld_create_char.py)

 from RPi import GPIO
 GPIO.setwarnings(False)
 from time import sleep
 from RPLCD.gpio import CharLCD
 ##lcd = CharLCD(pin_rs=36, pin_rw=None, pin_e=40, pins_data=[31, 33, 35, 37],
 ##             numbering_mode=GPIO.BOARD,cols=16, rows=2, dotsize=8,
 ##             charmap='A02', auto_linebreaks=True, compat_mode=True)
 
 lcd = CharLCD(pin_rs=16, pin_rw=None, pin_e=21, pins_data=[6, 13, 19, 26],
               numbering_mode=GPIO.BCM,cols=16, rows=2, dotsize=8,
               charmap='A02', auto_linebreaks=True, compat_mode=True)
 
 lcd.clear()
 sleep(1)
 lcd.write_string('Hello World')
 lcd.cursor_pos = (1,0)
 lcd.write_string('Zweite Zeile')
 sleep(3)
 
 lcd.home()
 lcd.clear()
 
 # Versuch, Sonderzeichen zu schreiben
 lcd.write_string('!"§$%&/()=?²³<>|°{}[]+-*#,;.:')
 sleep(5)
 
 degrees = (    
     0b00111,
     0b00101,
     0b00111,
     0b00000,
     0b00000,
     0b00000,
     0b00000,
     0b00000)
 lcd.create_char(0,degrees)
 
 smiley = (    
     0b00000,
     0b01010,
     0b01010,
     0b00000,
     0b10001,
     0b10001,
     0b01110,
     0b00000)
 lcd.create_char(1,smiley)
 
 para = (    
     0b01111,
     0b10000,
     0b01110,
     0b10001,
     0b01110,
     0b00001,
     0b11110,
     0b00000)
 lcd.create_char(3,para)
 
 lcd.clear()
 sleep(1)
 lcd.write_string('\x00!"\x03$%&/()=?<>|{}[]+-*#,;.: \x01')
 
 sleep(10)
 
 lcd.close(clear=True)

Unter den selbst gewählten Variablennamen degrees, smiley und para werden die Bitmaps als Tupel der Binärzahlen abgespeichert. Mit der Methode
lcd-create_char(arg1, arg2) werden die Zeichen erzeugt. Dabei ist arg1 der Speicherplatz, also eine Zahl zwischen 0 und 7, und arg2 der Variablenname des Tupels.

Angezeigt wird das Zeichen mit der Zeichenfolge \x0z mit z=Speicherplatz (0…7). Dies kann mitten im Text geschehen. Siehe Beispiel: Angezeigt werden die Sonderzeichen der deutschen Tastatur, beginnend mit \x00 = °, \x03=§ zwischen „ und $ sowie der Smiley am Ende.

Im ersten Beispiel hatten wir lcd.shift_display(-1) gesehen, mit der die Zeichen nach links ausgewandert sind (mit +1 nach rechts). Eine Methode/ Funktion, bei der ein längerer Text wie am laufenden Band angezeigt wird, gibt es nicht. Jedoch hat der Modul-Autor diese in seinem Blog Scrolling Text with RPLCD exemplarisch vorgestellt. Diese habe ich weiterentwickelt, damit der rollende Text nicht das gesamte Programm beansprucht. Download scrollingText.py

 from RPi import GPIO
 GPIO.setwarnings(False)
 from time import sleep
 import _thread
 
 from RPLCD.gpio import CharLCD
 ##lcd = CharLCD(pin_rs=36, pin_rw=None, pin_e=40, pins_data=[31, 33, 35, 37],
 ##             numbering_mode=GPIO.BOARD,cols=16, rows=2, dotsize=8,
 ##             charmap='A02', auto_linebreaks=True, compat_mode=True)
 
 lcd = CharLCD(pin_rs=16, pin_rw=None, pin_e=21, pins_data=[6, 13, 19, 26],
               numbering_mode=GPIO.BCM,cols=16, rows=2, dotsize=8,
               charmap='A02', auto_linebreaks=True, compat_mode=True)
 
 lcd.clear()
 sleep(1)
 
 framebuffer = [
     'Hello World!',
     '',
 ]
 line1 = 'scrolling text'
 
 def write_to_lcd(lcd, framebuffer, num_cols):
     """Write the framebuffer out to the specified LCD."""
     lcd.home()
     for row in framebuffer:
         lcd.write_string(row.ljust(num_cols)[:num_cols])
         lcd.write_string('\r\n')
 
 def scroll_left(string, lcd, framebuffer, row, num_cols, delay=0.3):
     s = string
     if (len(s) < num_cols):
         spaces = 3 + num_cols - len(s)
     else:
         spaces = 3
     s = spaces*' ' + s
     while True:
         for i in range(num_cols):
             s = s[1:1+len(s)] + s[0]
             framebuffer[row] = s
             write_to_lcd(lcd, framebuffer, num_cols)
             sleep(delay)
 
 _thread.start_new_thread(scroll_left,(line1, lcd, framebuffer, 1, 16))
 
 for n in range(10):
     print("n = ",n)
     sleep(1)

Das Rollen des Textes geschieht in der selbstdefinierten Funktion scroll_left(). Hier wird die Zeichenkette zunächst um mindestens drei Leerzeichen erweitert. Dann wird in einer Endlosschleife mit while True die Zeichenkette dahingehend verändert, dass das erste Zeichen jeweils vorne abgeschnitten und am Ende wieder eingefügt wird. Die Fachausdrücke dafür heißen „slicing“ und „concatenation“. Damit die while True-Schleife nicht das ganze Programm blockiert, wird die Funktion in einem sogenannten „thread“ ausgeführt. Dazu wird das entsprechende Modul mit import _thread importiert. Am Ende wird die selbstdefinierte Funktion aufgerufen mit

 _thread.start_new_thread(scroll_left,(line1, lcd, framebuffer, 1, 16))

Die Methode start_new_thread hat zwei Argumente: zunächst den Namen der Funktion, die aufgerufen wird, übrigens ohne (), und ein Tupel mit weiteren Parametern (der auszugebende Text, das Objekt lcd, die Zeilennummer und Anzahl der Zeichen pro Zeile).

Weitere Infos zu dem Python-Modul RPLCD von Danilo Bargen unter https://rplcd.readthedocs.io/en/stable/

Temperatur-, Luftdruck- und Feuchtigkeitssensor

Zum Schluss möchte ich die Schaltung um einen kombinierten Temperatur-, Luftdruck- und Feuchtigkeitssensor erweitern und die Werte sowie die Uhrzeit auf dem LCD anzeigen. Auch dafür mache ich mir die Vorarbeiten anderer zunutze und installiere die entsprechende Programm-Bibliothek(en).

Anstelle eines neuen Schaltplans hier die einfache Pinbelegung des BME280:

  • VCC an phys. Pin 1 = 3,3V
  • GND an phys. Pin 9 (oder einen anderen Ground Pin)
  • SDA an phys. Pin 3
  • SCL an phys. Pin 5

Selbstverständlich muss die I2C-Schnittstelle in der Konfiguration aktiviert sein und wir prüfen die I2C-Adresse im Terminal mit dem Befehl

 i2cdetect – y 1

In meinem Fall lautet die Adresse 0x76. Das wird später von Bedeutung sein.

Auf der Produktseite des BME280 finde ich das Modul Adafruit_BME280.py und das Beispielprogramm Adafruit_BME280_Example.py. In der Datei README.md lese ich, dass weitere Programm-Bibliotheken von Adafruit zu installieren sind. Also zunächst im Terminal-Programm:

 git clone https://github.com/adafruit/Adafruit_Python_GPIO.git
 cd Adafruit_Python_GPIO
 sudo python setup.py install

Das Ganze dauert rund eine halbe Minute.

Da das Adafruit-Modul Adafruit_BME280.py als Voreinstellung die I2C-Adresse 0x77 verwendet und man diese nicht wie bei anderen Bibliotheken als Parameter bei der Instanziierung ändern kann, ändere ich die Adresse auf die von mir ermittelte 0x76 (s.o.) und speichere es ab. Im Beispielprogramm schaue ich mir die Funktionen an und übernehme Teile davon in meinem Abschlussprojekt. Wegen der überlangen Zeilen empfehle ich den Download LCD_BME280.py anstelle von „Copy and Paste“.

 from RPi import GPIO
 GPIO.setwarnings(False)
 import time
 from Adafruit_BME280 import *
 # I2C must be activated, check address with i2cdetect - y 1
 # default is 0x77
 # if necessary, change address in module Adafruit_BME280.py
 
 sensor = BME280(t_mode=BME280_OSAMPLE_8, p_mode=BME280_OSAMPLE_8, h_mode=BME280_OSAMPLE_8)
 
 from RPLCD.gpio import CharLCD
 ##lcd = CharLCD(pin_rs=36, pin_rw=None, pin_e=40, pins_data=[31, 33, 35, 37],
 ##             numbering_mode=GPIO.BOARD,cols=16, rows=2, dotsize=8,
 ##             charmap='A02', auto_linebreaks=True, compat_mode=True)
 
 lcd = CharLCD(pin_rs=16, pin_rw=None, pin_e=21, pins_data=[6, 13, 19, 26],
               numbering_mode=GPIO.BCM,cols=16, rows=2, dotsize=8,
               charmap='A02', auto_linebreaks=True, compat_mode=True)
 
 lcd.clear()
 time.sleep(1)
 
 degrees = (    
    0b00111,
    0b00101,
    0b00111,
    0b00000,
    0b00000,
    0b00000,
    0b00000,
    0b00000)
 lcd.create_char(0,degrees)
 
 while True:
    dtg = time.localtime()
     if (dtg.tm_hour<10):
        hh = '0'+str(dtg.tm_hour)
     else:
        hh = str(dtg.tm_hour)
     if (dtg.tm_min<10):
        mm = '0'+str(dtg.tm_min)
     else:
        mm = str(dtg.tm_min)
    hhmm = hh + ':' + mm
    print(hhmm)
    degrees = sensor.read_temperature()
    pascals = sensor.read_pressure()
    hectopascals = pascals / 100
    humidity = sensor.read_humidity()
 
    print('Temp     = {0:0.3f} °C'.format(degrees))
    print('Pressure = {0:0.2f} hPa'.format(hectopascals))
    print('Humidity = {0:0.2f} %'.format(humidity))
     wx='T='+str(round(degrees,1))+'\x00C   '+hhmm+'\r\nP='+str(round(hectopascals))+'hPa H='+str(round(humidity))+'%'
    print(wx)
    lcd.write_string(wx)
    time.sleep(5)
    lcd.clear()

Viel Spaß beim Nachbauen und Anpassen an eigene Wünsche.

DisplaysProjekte für anfängerRaspberry pi

Laisser un commentaire

Tous les commentaires sont modérés avant d'être publiés