Skip to content

Instantly share code, notes, and snippets.

@ufux
Created July 27, 2013 14:08
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save ufux/6094977 to your computer and use it in GitHub Desktop.
Save ufux/6094977 to your computer and use it in GitHub Desktop.
Created a working driver to drive a LCD 20x4 display based on HD44780U (http://www.exp-tech.de/Displays/I2C-LCD-1602-Module-652.html) via PCF8574 i2c bus expander driven from a raspberrypi.
import smbus
import getopt
import sys
from time import *
from time import gmtime, strftime
# TODO: Factor out all device_write calls to some PCF8574 specific module ...
# will be different with another io expander
# communication from expander to display: high nibble first, then low nibble
# communication via i2c to the PCF 8547: bits are processed from highest to lowest (send P7 bit first)
# General i2c device class so that other devices can be added easily
class i2c_device:
def __init__(self, addr, port):
self.addr = addr
self.bus = smbus.SMBus(port)
def write(self, byte):
self.bus.write_byte(self.addr, byte)
def read(self):
return self.bus.read_byte(self.addr)
def read_nbytes_data(self, data, n): # For sequential reads > 1 byte
return self.bus.read_i2c_block_data(self.addr, data, n)
class ioexpander:
def __init__(self):
pass
class lcd:
#initializes objects and lcd
# LCD 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 on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00
# Flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 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
'''
new pinout:
----------
0x80 P7 - - D7
0x40 P6 - - D6
0x20 P5 - - D5
0x10 P4 - - D4
-----------
0x08 P3 - - BL Backlight ???
0x04 P2 - - EN Starts Data read/write
0x02 P1 - - RW low: write, high: read
0x01 P0 - - RS Register Select: 0: Instruction Register (IR) (AC when read), 1: data register (DR)
'''
def __init__(self, addr, port, withBacklight=True, withOneTimeInit=False):
'''
device writes!
crosscheck also http://www.monkeyboard.org/tutorials/81-display/70-usb-serial-to-hd44780-lcd
here a sequence is listed
'''
self.displayshift = (self.LCD_CURSORMOVE |
self.LCD_MOVERIGHT)
self.displaymode = (self.LCD_ENTRYLEFT |
self.LCD_ENTRYSHIFTDECREMENT)
self.displaycontrol = (self.LCD_DISPLAYON |
self.LCD_CURSOROFF |
self.LCD_BLINKOFF)
if withBacklight:
self.blFlag=self.LCD_BACKLIGHT
else:
self.blFlag=self.LCD_NOBACKLIGHT
self.lcd_device = i2c_device(addr, port)
# we can initialize the display only once after it had been powered on
if(withOneTimeInit):
self.lcd_device.write(0x20)
self.lcd_strobe()
sleep(0.0100) # TODO: Not clear if we have to wait that long
self.lcd_write(self.LCD_FUNCTIONSET | self.LCD_4BITMODE | self.LCD_2LINE | self.LCD_5x8DOTS) # 0x28
self.lcd_write(self.LCD_DISPLAYCONTROL | self.displaycontrol) # 0x08 + 0x4 = 0x0C
self.lcd_write(self.LCD_ENTRYMODESET | self.displaymode) # 0x06
self.lcd_write(self.LCD_CLEARDISPLAY) # 0x01
self.lcd_write(self.LCD_CURSORSHIFT | self.displayshift) # 0x14
self.lcd_write(self.LCD_RETURNHOME)
# clocks EN to latch command
def lcd_strobe(self):
self.lcd_device.write((self.lcd_device.read() | self.EN | self.blFlag)) # | 0b0000 0100 # set "EN" high
self.lcd_device.write(( (self.lcd_device.read() | self.blFlag) & 0xFB)) # & 0b1111 1011 # set "EN" low
# write data to lcd in 4 bit mode, 2 nibbles
# high nibble is sent first
def lcd_write(self, cmd):
#write high nibble first
self.lcd_device.write( (cmd & 0xF0) | self.blFlag )
hi= self.lcd_device.read()
self.lcd_strobe()
# write low nibble second ...
self.lcd_device.write( (cmd << 4) | self.blFlag )
lo= self.lcd_device.read()
self.lcd_strobe()
self.lcd_device.write(self.blFlag)
# write a character to lcd (or character rom) 0x09: backlight | RS=DR
# works as expected
def lcd_write_char(self, charvalue):
controlFlag = self.blFlag | self.RS
# write high nibble
self.lcd_device.write((controlFlag | (charvalue & 0xF0)))
self.lcd_strobe()
# write low nibble
self.lcd_device.write((controlFlag | (charvalue << 4)))
self.lcd_strobe()
self.lcd_device.write(self.blFlag)
# put char function
def lcd_putc(self, char):
self.lcd_write_char(ord(char))
def _setDDRAMAdress(self, line, col):
# we write to the Data Display RAM (DDRAM)
# TODO: Factor line offsets for other display organizations; this is for 20x4 only
if line == 1:
self.lcd_write(self.LCD_SETDDRAMADDR | (0x00 + col) )
if line == 2:
self.lcd_write(self.LCD_SETDDRAMADDR | (0x40 + col) )
if line == 3:
self.lcd_write(self.LCD_SETDDRAMADDR | (0x14 + col) )
if line == 4:
self.lcd_write(self.LCD_SETDDRAMADDR | (0x54 + col) )
# put string function
def lcd_puts(self, string, line):
self._setDDRAMAdress(line, 0)
for char in string:
self.lcd_putc(char)
# clear lcd and set to home
def lcd_clear(self):
# self.lcd_write(0x10)
self.lcd_write(self.LCD_CLEARDISPLAY)
# self.lcd_write(0x20)
self.lcd_write(self.LCD_RETURNHOME)
# add custom characters (0 - 7)
def lcd_load_custon_chars(self, fontdata):
self.lcd_device.bus.write(0x40);
for char in fontdata:
for line in char:
self.lcd_write_char(line)
# Let them know how it works
def usage():
print 'Usage: lcdui.py --init --debug --backlightoff'
# Handle the command line arguments
def main():
initFlag=False
debug=False
backlight=True
try:
opts, args = getopt.getopt(sys.argv[1:],"idb",["init","debug","backlightoff"])
except getopt.GetoptError:
usage()
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
usage()
sys.exit()
elif opt in ("-i", "--init"):
initFlag = True
elif opt in ("-d", "--debug"):
debug = True
elif opt in ("-b", "--backlightoff"):
backlight = False
if initFlag:
print "Doing initial init ..."
else:
print "Skipping init ..."
device = lcd(0x27,1,backlight, initFlag)
device.lcd_puts("01234567890123456789",1)
device.lcd_puts("012345 Zeile 2 56789",2)
device.lcd_puts("012345 Zeile 3 56789",3)
device.lcd_puts(strftime("%Y-%m-%d %H:%M:%S", gmtime()),4)
sleep(3)
device.lcd_clear()
device.lcd_puts(" Simple Clock ",1)
while True:
device.lcd_puts(strftime("%Y-%m-%d %H:%M:%S ", gmtime()),3)
sleep(1)
if __name__ == '__main__':
main()
@tdack
Copy link

tdack commented Sep 22, 2014

Thankyou!! This thing was doing my head in.

Your code works as is on a BeagleBone Black too.

@vi
Copy link

vi commented Sep 5, 2016

Note: this code fails to work in my setup (C.H.I.P + this display and adapter), although driver from this repository does work.

Changed to both drives: 2'nd port instead of 1'st, i2c address 0x3f.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment