Raspberry-Pi-Projekte: Einfaches Text-LCD

Prof. Jürgen Plate, Christian Steiner, Serhat Erbas, Nguyen Duc Khanh

Raspberry Pi: Einfaches Text-LCD mit I2C-Interface

Allgemeines

Einfache Text-Display mit zwei bis vier Zeile zu je 20 bis 40 Zeichen und HD44780 Controller werden meist parallel angeschlossen. Dazu werden vier oder 8 Datenleitungen und drei Steuerleitungen benötigt. So belegt ein solches Display minimal sieben GPIO_Ports. Deshalb wird hier auf ein Display mit angebauten I2C-Interface beschrieben, das so nur zwei Leitungen braucht. Die Wahl fiel auf das SainSmart IIC/I2C/TWI Serial 2004 20x4 LCD, das über Amazon bestellt wurde. Es gibt aber auch Steckplatinen ohne Display, die ähnliche Funktionaität besitzen.

Schaltung

Auf der Display-Rückseite sitzt eine winzige Platine, die mit einem I2C-Baustein bestückt ist. Das folgende Bild zeigt einen Stromlaufplan fur eine Platine mit einem LCD mit HD44780-Controller und I2C-Portexpander. In diesem Projekt wurde darauf verzichtet, eine Platine zu entwerfen, da das fertige Moduls kostengünstiger war. Die Schaltung ist sehr einfach, es werden nur ein LCD mit HD44780-Controller, ein Portexpander PCF8574T in SMD-Ausführung, einPotentiometer 10 kOhm und ggf. Jumper für die LED-Hintergrundbeleuchtung benötigt.

Um das LCD Display an den Raspberry Pi anzuschliessen sind nur vier Leitungen notwendig:

Dieses Display wird bei Amazon für den Adruino beworben, es funktioniert aber auch wunderfein mit dem Raspberry Pi. Wichtigste Änderung ist hier die niedrigere Betriebsspannung des GPIO von 3.3 Volt. Sobald das Display mit Strom versorgt wird, geht die Hintergrundbeleuchtung an, aber es ist keinerlei Anzeige zu sehen - was normal ist. Da das LCD-Modul nur mit 3,3 Volt versorgt wird, ist der Textkontrast etwas schwach. Dieser läßt sich aber mit einem kleinen Schraubendreher auf der Rückseite des Moduls am blauen Potenziometer einstellen. Bei zu hohen Kontrast kann mn sogar die Positionen der einzelen Zeichematrizen erkennen.

Software

Um die I2C-Adresse des LCD zu finden, kann der Befehl i2cdetect -y 1 verwendet werden, wozu Root-Berechtigung nötig ist. In diesem Fall ist die I2C-Adresse 0x3F. Für die Programmierung des LCD wurde die Programmiersprache Python verwendet. Es ist auch möglich, das LCD in C oder einer anderen Programmierprache zu betreiben. Um auf dem Raspberry Pi den I2C-Bus verwenden zu können, muss er frei geschatet sein (siehe Raspberry Pi: I2C-Konfiguration und -Programmierung). Der nachfolgende Code-Ausschnitt zeigt ausschnittsweise den I2C-Treiber von Python für grundlegende Funktionen:

import smbus
from time import sleep

class i2c_device:
    def __init__(self, addr, port=1):
        self.addr = addr
        self.bus = smbus.SMBus(port)

    # Write a single command
    def write_cmd(self, cmd):
        self.bus.write_byte(self.addr, cmd)
        sleep(0.0001)

    # Write a command and argument
    def write_cmd_arg(self, cmd, data):
        self.bus.write_byte_data(self.addr, cmd, data)
        sleep(0.0001)

    # Write a block of data
    def write_block_data(self, cmd, data):
        self.bus.write_block_data(self.addr, cmd, data)
        sleep(0.0001)

    # Read a single byte
    def read(self):
        return self.bus.read_byte(self.addr)

    # Read 
    def read_data(self, cmd):
        return self.bus.read_byte_data(self.addr, cmd)

    # Read a block of data
    def read_block_data(self, cmd):
        return self.bus.read_block_data(self.addr, cmd)

Das nachfolgende Bild zeigt das Display-Register des HD44780. Zu beachten ist, dass die zweite Zeile des Displays, der dritten Zeile im Register entspricht. Für den Lineshift-Befehl müssen dann die entsprechenden Werte verwendet werden.

Der LCD-Treiber beinhaltet alle Kommandos die das LCD verwendet. Die Kommandos können dazu im Datenblatt des HD44780 nachgelesen werden. Es gibt z. B. Funktionen, um einen Text am Display anzuzeigen oder auch den Inhalt zu löschen. Näheres kann dem Datenblatt des HD44780 entnommen werden. Der LCD-Treiber verwendet dabei den bereits angesprochenen I2C-Treiber. Er wird mittels import lcddriver in eigene Python-Programme eingebunden.

import i2c_lib
from time import sleep

# LCD Address
ADDRESS = 0x3f

# commands
LCD_CLEARDISPLAY = 0x01
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80

# flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00

# flags for display on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00

# flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00

# flags for function set
LCD_8BITMODE = 0x10
LCD_4BITMODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5x10DOTS = 0x04
LCD_5x8DOTS = 0x00

# flags for backlight control
LCD_BACKLIGHT = 0x08
LCD_NOBACKLIGHT = 0x00

En = 0b00000100 # Enable bit
Rw = 0b00000010 # Read/Write bit
Rs = 0b00000001 # Register select bit

class lcd:
    #initializes objects and lcd
    def __init__(self):
        self.lcd_device = i2c_lib.i2c_device(ADDRESS)
        self.lcd_write(0x03)
        self.lcd_write(0x03)
        self.lcd_write(0x03)
        self.lcd_write(0x02)
        
        self.lcd_write(LCD_FUNCTIONSET | LCD_2LINE | LCD_5x8DOTS | LCD_4BITMODE)
        self.lcd_write(LCD_DISPLAYCONTROL | LCD_DISPLAYON)
        self.lcd_write(LCD_CLEARDISPLAY)   
        self.lcd_write(LCD_ENTRYMODESET | LCD_ENTRYLEFT)
        sleep(0.2)   

    # clocks EN to latch command
    def lcd_strobe(self, data):
        self.lcd_device.write_cmd(data | En | LCD_BACKLIGHT)
        sleep(.0005)
        self.lcd_device.write_cmd(((data & ~En) | LCD_BACKLIGHT))
        sleep(.0001)   

    def lcd_write_four_bits(self, data):
        self.lcd_device.write_cmd(data | LCD_BACKLIGHT)
        self.lcd_strobe(data)

# write a command to lcd
    def lcd_write(self, cmd, mode=0):
        self.lcd_write_four_bits(mode | (cmd & 0xF0))
        self.lcd_write_four_bits(mode | ((cmd << 4) & 0xF0))

# put string function
    def lcd_display_string(self, string, line, offset):
        if line == 1:
            #0x80 | 0x00 = 0x80
            #self.lcd_write(0x80 + offset)
            self.lcd_write((LCD_SETDDRAMADDR | 0x00) + offset)
        if line == 2:
            #0x80 | 0x40 = 0xC0
            #self.lcd_write(0xC0 + offset)
            self.lcd_write((LCD_SETDDRAMADDR | 0x40) + offset)
        if line == 3:
            #0x80 | 0x14 = 0x94
            #self.lcd_write(0x94 + offset)
            self.lcd_write((LCD_SETDDRAMADDR | 0x14) + offset)
        if line == 4:
            #0x80 | 0x54 = D4
            #self.lcd_write(0xD4 + offset)
            self.lcd_write((LCD_SETDDRAMADDR | 0x54) + offset)

        for char in string:
            self.lcd_write(ord(char), Rs)

# clear lcd and set to home
    def lcd_clear(self):
        self.lcd_write(LCD_CLEARDISPLAY)
        self.lcd_write(LCD_RETURNHOME)

Beispiel 1: Textausgabe per Kommandozeile

Der anzuzeigende Text, die Zeile und der Zeilenversatz werden als Kommandozeilenparameter an das Python-Programm übergeben. Zuerst kommt der auszugebene String, dann die gewünscht Zeile (beginnend bei 1) und zum Schluss der Offset in der Zeile (beginnend bei 0).

import lcddriver
import sys

lcd = lcddriver.lcd()
try : 
    lcd.lcd_display_string(str(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3]))
except:
    print("Usage: \"Output-String\" \"Line in decimal\" \"Offset in decimal\" ")
    print("Example: \"Hello World!\" 1 0")

Beispiel 2:Aktuelle Systemzeit

Dieses Skript gibt die aktuelle Systemzeit vom Raspberry Pi aus. Befindet sich der RasPi in einem Netz mit Zeitserver, wird die Systemzeit durch den Zeitserver aktualisiert. Dieses Skript läuft solange bis das Raspberry Pi ausgeschaltet wird.
import lcddriver
import threading
from datetime import datetime
from time import sleep

threads = []

class DisplayTimeThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.running = True

    
    def run(self):
        while self.running:
            try: 
                self.printTime()
                sleep(0.25)
            except:
                self.running = False

    
    def printTime(self):
        lcd.lcd_display_string(datetime.now().strftime('%b %d'), 2, 7)
        lcd.lcd_display_string(datetime.now().strftime('%H:%M:%S'), 3, 6)

    
#END DisplayTimeThread(threading.Thread):

#MAIN PROGRAM   
lcd = lcddriver.lcd()
lcd.lcd_clear()

timeThread = DisplayTimeThread()
threads.append(timeThread)
timeThread.start()

for t in threads:
    t.join()

Links


Copyright © Hochschule München, FK 04, Prof. Jürgen Plate und die Autoren
Letzte Aktualisierung: