Using LCD HD44780 on Raspberry Pi – part 2: drivers

lcd_20_tutorial_2If we look at our code from part one we will see that more than half functions are used to communicate between Raspi and lcd. In this tutorial we will separate low level function from user functions and create a driver class. Why ? If we connect later our LCD by i2c we will just add new driver and it will work.

All function used to communicate directly with display will be moved to separate driver class, a GPIO driver class. Such basic function are _write and _write4. But in our case it is not so simple, all around a code we have some GPIO.output(self.PINS[‘E’], 1) lines, like in _send(self) function. We will do something about it, but lets start from small things.

Current CharLCD class will be refereed as user driver or user class, and new one as driver or low level driver.

Lets get to work

In package charlcd add another package drivers and create file gpio.py. Into this file we will move all low level functions.
But lets start from changing __init__ in lcd.py. Add another parameter driver after height and in body assign it to self.driver.

In driver create Gpio(object) class and into  __init__(self) move pins definitions (self.PINS and self.DATA_PINS) from lcd.py. Next move pins set up (small for loop) from init(self) to driver init(self). And add self.driver.init() in place of loop.

Now copy functions cmd, cmd4, shutdown, char  into driver and in user driver replace bodies with calls to driver. For example cmd in lcd.py will change from

      def cmd(self, c):
        GPIO.output(self.PINS['RS'], 0)
        self._write(c)

into

      def cmd(self, c):
        self.driver.cmd(c)

Do the same for other copied functions.

Next move _send, _write4 and _write from user class into driver class.

If you done everything correctly IDE should mark that you may safety delete import RPi.GPIO as GPIO from lcd.py because we moved all gpio references.

Its alive !

We done some changes and test script won’t run anymore. Lets revive it.

Open testlcd.py and change it according to this:

#!/usr/bin/python
# -*- coding: utf-8 -*-
__author__ = 'kosci'

import RPi.GPIO as GPIO
from charlcd import lcd
from charlcd.drivers.gpio import Gpio

GPIO.setmode(GPIO.BCM)

l = lcd.CharLCD(20, 4, Gpio())
l.init()
l.string('Blarg !')
l.cmd(lcd.LCD_LINES[1])
l.string('  Grarg !')
l.cmd(lcd.LCD_LINES[2])
l.string(' ALIVE !!!!')

Now run script and you shall see writings on the lcd. If you see any errors try to resolve them. It will be good exercise but don’t worry, at the end of article is fully working code.

Now look at line

l = lcd.CharLCD(20, 4, Gpio())

We are sending driver directly. But now we can create instance, rearrange pin configuration and pass it.

drv = Gpio()
drv.PINS = {...}
l = lcd.CharLCD(20, 4, drv)

Refactoring

Now we done separation but code may be tweaked a little. Lets look at lcd.py and functions cmd and cmd4. All that’s left is call to driver. Its just a simple call, so replace all self.cmd with self.driver.cmd and all self.cmd4 with self.driver.cmd4. After that remove those functions.

But our code won’t work anymore 🙂 Time to do something about cursor position. First replace old and add new const at the beginning of lcd.py file (between import and class declaration).

LINE0 = 0x80
LINE1 = 0xC0
LINE2 = 0x94
LINE3 = 0xD4
LCD_LINES = [
    LINE0, LINE1, LINE2, LINE3
]

And add new function

     def position(self, pos):
        self.driver.cmd(pos)

We defined address of each line and are able to move cursor to any position. How ? Just use (in testlcd.py)

l.position(lcd.LINE1 + 2)

instead of

l.cmd(lcd.LCD_LINES[1]

This will put cursor on second position on second line. Also change other references to cursor position.

From now on our code is alive, again !

Now some beautification, in gpio.py remove all _ from the beginning of the functions and fix references. And we are done ! Clean and nice code. Problems will come later.. but shh! We have plenty of time before.

Final code

File structure:

file_structure_v2

gpio.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
__author__ = 'kosci'

import RPi.GPIO as GPIO
import time


class Gpio(object):
    def __init__(self):
        self.PINS = {
            'RS': 25, #0 ->instruction, 1->data
            'E': 24,
            'DB4': 22,
            'DB5': 23,
            'DB6': 27,
            'DB7': 17
        }
        self.DATA_PINS = [
            'DB4',
            'DB5',
            'DB6',
            'DB7'
        ]

    def init(self):
         #setup pins
        for p in self.PINS:
            GPIO.setup(self.PINS[p], GPIO.OUT)
            GPIO.output(self.PINS[p], 0)

    def cmd(self, c):
        GPIO.output(self.PINS['RS'], 0)
        self.write(c)

    def cmd4(self, c):
        GPIO.output(self.PINS['RS'], 0)
        self.write4(c)

    def shutdown(self):
        GPIO.cleanup()

    def send(self):
        GPIO.output(self.PINS['E'], 1)
        time.sleep(0.005)
        GPIO.output(self.PINS['E'], 0)

    def write4(self, c):
        for i in self.DATA_PINS:
            t = c & 0x01
            GPIO.output(self.PINS[i], t)
            c >>= 1
        self.send()

    def write(self, c):
        data = (c >> 4)
        for i in self.DATA_PINS:
            t = data & 0x01
            GPIO.output(self.PINS[i], t)
            data >>= 1
        self.send()

        data = (c & 0x0F)
        for i in self.DATA_PINS:
            t = data & 0x01
            GPIO.output(self.PINS[i], t)
            data >>= 1
        self.send()

    def char(self, c):
        GPIO.output(self.PINS['RS'], 1)
        self.write(ord(c))

lcd.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
__author__ = 'kosci'

import time

LINE0 = 0x80
LINE1 = 0xC0
LINE2 = 0x94
LINE3 = 0xD4
LCD_LINES = [
    LINE0, LINE1, LINE2, LINE3
]


class CharLCD (object):
    def __init__(self, width, height, driver):
        self.width = width
        self.height = height
        self.c_w = 0
        self.c_h = 0
        self.driver = driver

    def init(self):
        self.driver.init()

        self.driver.cmd4(3)
        time.sleep(0.05)
        self.driver.cmd4(3)
        time.sleep(0.05)
        self.driver.cmd4(3)
        time.sleep(0.05)

        self.driver.cmd4(2)
        self.driver.cmd(0x28)
        self.driver.cmd(0x08)
        self.driver.cmd(0x01)
        self.driver.cmd(0x06)
        self.driver.cmd(0x0D)

    def shutdown(self):
        self.driver.shutdown()

    def string(self, s):
        for l in s:
            self.char(l)

    def char(self, c):
        self.driver.char(c)

    def position(self, pos):
        self.driver.cmd(pos)

    def stream_char(self, c):
        self.char(c)
        self.c_w += 1
        if self.c_w >= self.width:
            self.c_w = 0
            self._inc_c_w()

    def stream_string(self, s):
        for c in s:
            self.stream_char(c)

    def _inc_c_w(self):
        self.c_h += 1
        if self.c_h >= self.height:
            self.c_h = 0
        self.driver.cmd(LCD_LINES[self.c_h])

testlcd.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
__author__ = 'kosci'

import RPi.GPIO as GPIO
from charlcd import lcd
from charlcd.drivers.gpio import Gpio

GPIO.setmode(GPIO.BCM)

l = lcd.CharLCD(20, 4, Gpio())
l.init()
l.string('Blarg !')
l.position(lcd.LINE1 + 2)
l.string('Grarg !')
l.position(lcd.LINE2 + 4)
l.string('ALIVE !!!!')
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s