Using HD44780 on Raspberry Pi – part 5: 40×4 (direct access)

This time we will add support for Char LCD with size 40×4. Such display is a special case, when we look at the internal schema we will see that its in fact two-in-one lcd. It has two lcds, one lcd goes on top and second on bottom.
It’s because one HD 44780 can display maximum 80 chars and this is 40×2. If we want 160 chars we must double this.
This way we have two 40×2 lcds. For our purpose lets call such display a twin-lcd.

Download source

Older parts

Pinouts

My display has pins on the left side. Pinouts goes like this:

40x4_pins

1 – DB7
2 – DB6
3 – DB5
4 – DB4
5 – DB3
6 – DB2
7 – DB1
8 – DB0
9 – E1
10 – R/W
11 – RS
12 – V0
13 – Vss (-)
14 – Vdd (+)
15 – E2
16 – nc
17 – A
18 – K

It is different than pinouts in 16×2 or 20×4. Important thing to notice is second enable signal. We have two lcds so we need two E lines. Other lines are same for both.

Wiring GPIO

I used wiring from previous lcd 20×4 and added one additional wire for E2

LCD13 [VSS] ------------ GND
LCD14 [VDD] ------------ +5V
LCD12 [V0] ------/\/\/\ [potentiometer]
                   \---- GND
LCD11 [RS] ------------- GPIO 25
LCD10 [R/W] ------------ GND
LCD9  [E1] ------------- GPIO 24
LCD15 [E2] ------------- GPIO 10
LCD4  [DB4] ------------ GPIO 22
LCD3  [DB5] ------------ GPIO 23
LCD2  [DB6] ------------ GPIO 27
LCD1  [DB7] ------------ GPIO 17
LCD17 [A] ------/\/\/\ [potentiometer]
                   \---- +5V
LCD18 [K] -------------- GND

Driver GPIO

First we will use GPIO. Open gpio.py and search for pins dictionary.

Change it, add key E2, so driver will know about second enable signal, I’m calling it E2.

It’s start value is None, meaning no connection. This imply that we need to change init loop, add check for None in pins.

We can easily enable pin E2 by telling driver that we want it:

drv = Gpio()
drv.pins['E2'] = 10

This was simple.

We also need to change send function, add parameter enable, it will tell us which line to use. Three functions cmd, write and char are gateway into driver. We need to add enable parameter to them and pass it deeper.

After all that gpio.py should look like this:


#!/usr/bin/python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO #pylint: disable=I0011,F0401
import time
from charlcd.drivers.base import BaseDriver


class Gpio(BaseDriver):
    """Gpio LCD driver"""
    def __init__(self):
        """pins in 4bit mode"""
        self.pins = {
            'RS': 25, 
            'E': 24,
            'E2': None,
            'DB4': 22,
            'DB5': 23,
            'DB6': 27,
            'DB7': 17
        }
        self.data_pins = [
            'DB4',
            'DB5',
            'DB6',
            'DB7'
        ]
        self.mode = 8

    def init(self):
        """Set gpio pins"""
        for pin in self.pins:
            if self.pins[pin] is not None:
                GPIO.setup(self.pins[pin], GPIO.OUT)
                GPIO.output(self.pins[pin], 0)

    def cmd(self, char, enable=0):
        """Write output as command. Sets RS to 0
        Args:
            char: hex to write
        """
        GPIO.output(self.pins['RS'], 0)
        self.write(char, enable)

    def shutdown(self):
        """Clean GPIO pins"""
        GPIO.cleanup()

    def send(self, enable=0):
        """Send E signal"""
        if enable == 0:
            pin = self.pins['E']
        elif enable == 1:
            pin = self.pins['E2']

        if pin is None:
            raise IndexError("Wrong enable index")

        GPIO.output(pin, 1)
        time.sleep(0.005)
        GPIO.output(pin, 0)

    def _write8(self, char, enable=0):
        """Write 8 bits"""
        self._write(char)
        self.send(enable)

    def _write4(self, char, enable=0):
        """Prepare and write 4/4 bits"""
        data = (char >> 4)
        self._write(data)
        self.send(enable)

        data = (char & 0x0F)
        self._write(data)
        self.send(enable)

    def _write(self, data):
        """Write to gpio"""
        for i in self.data_pins:
            value = data & 0x01
            GPIO.output(self.pins[i], value)
            data >>= 1

    def write(self, char, enable=0):
        """Write char to lcd
        Args:
            char: hex char to write
        """
        if self.mode == 4:
            self._write4(char, enable)
        else:
            self._write8(char, enable)

    def char(self, char, enable=0):
        """Write char to lcd
        Args:
            char: char to write
        """
        GPIO.output(self.pins['RS'], 1)
        self.write(ord(char), enable)

    def set_mode(self, mode):
        """Set lcd mode
        Args:
            mode: 4 | 8 bit mode
        """
        self.mode = mode


Driver done.

Initialization in user driver

First modify abstract class in file lcd.py . Our user driver need to know about twin-lcd type. Small if in __init__ and we are done:

self.twin_lcd = False # are we using 40x4 = 2* 20x4 lcd ?
if self.driver.pins['E2'] is not None:
    self.twin_lcd = True

Small helper to check if we are using  twin-lcd:

def is_twin(self):
    """returns true if we are using twin lcd"""
    return self.twin_lcd

We are checking if E2 is set and if yes consider this as twin case.

What we want next is ability to initialize twin lcd and normal lcd. Time to rebuild init function. I split it into 2 functions.

Old init  calls _init with first enable line. And in case of twin lcd call second time with E2. This way we will initialize both displays


    def init(self):
        """Inits lcd display, send commands"""
        self._init(0)
        if self.is_twin():
            #time.sleep(1)
            self._init(1)

    def _init(self, enable):
        """subroutine to init lcd"""
        self.driver.init()
        self.driver.cmd(3, enable)
        time.sleep(0.05)
        self.driver.cmd(3, enable)
        time.sleep(0.05)
        self.driver.cmd(3, enable)
        time.sleep(0.05)
        self.driver.cmd(2, enable)
        self.driver.set_mode(4)
        self.driver.cmd(0x28, enable)
        self.driver.cmd(0x08, enable)
        self.driver.cmd(0x01, enable)
        self.driver.cmd(0x06, enable)
        self.driver.cmd(12 +
                        (self.cursor_visible * 2) +
                        (self.cursor_blink * 1), enable)

Direct

Open lcd_direct.py, what we need to do is to send proper enable line to cmd and char functions

There are two lines that require out attention:

self.driver.char(char)
self.driver.cmd(pos)

Now what is a proper E line ? Our lcds in lcd are stacked one on other, so our E line depends on y position. First two (0,1) goes to E and (2, 3) goes to E2. Good news is that we are tracking position, so simple helper is what we need:

    def _get_enable(self):
        """get proper enable line"""
        if self.is_twin() and self.current_pos['y'] > 1:
            enable = 1
        else:
            enable = 0

        return enable

Change char call to

self.driver.char(char, self._get_enable())

And finally we need send enable parameter to cmd. Few modifications and we have:


    def _position(self, pos, enable=0):
        """Set cursor position
        Args:
            pos: hex address for new cursor position
        """
        self.driver.cmd(pos, enable)

    def set_xy(self, pos_x, pos_y):
        """Set cursor position by X Y. it recalculate position to hex address
        Args:
            pos_x: x position
            pos_y: y position
        """
        lcd.CharLCD.set_xy(self, pos_x, pos_y)
        self._position(lcd.LCD_LINES[pos_y] + pos_x, self._get_enable())

Demo script

Open demos/lcd_direct.py and add new function:


def main_test_3():
    """test 3 - lcd 40x4"""
    drv = Gpio()
    drv.pins['E2'] = 10
    drv.pins['E'] = 24
    lcd_1 = lcd.CharLCD(40, 4, drv, 0, 0)
    lcd_1.init()
    lcd_1.write('-First blarg1 !')
    lcd_1.write('-Second blarg2 !', 0, 1)
    lcd_1.write('-Third blarg3 !', 0, 2)
    lcd_1.write('-Fourth blarg4 !', 0, 3)

And when you run it you will see (or at least I saw) something like this:

40x4_first

Ups… its almost right, almost…

Addressing

Why? What went wrong?

Lcd 20×4 has following line address:

Line 1 – 0x80
Line 2 – 0xC0
Line 3 – 0x94
Line 4 – 0xD4

But in 40×4 we get:

Line 1      Line 3
Line 2      Line 4
Line 1      Line 3
Line 2      Line 4

See the problem ?

Until now we had line addresses defined as const. Now we need to create function that will distinct what to return for required line. We are lucky because only one place in lcd_direct.py is using this const:


self._position(lcd.LCD_LINES[pos_y] + pos_x, self._get_enable())

Go back to our abstract class in lcd.py and add this function:


    def get_line_address(self, pos_y):
        """Return start hex address for line
        Args:
            pos_y: line number
        """
        if pos_y >= self.height or pos_y < 0:
            raise IndexError

        if not self.is_twin() or pos_y < 2:
            return LCD_LINES[pos_y]

        return LCD_LINES[pos_y - 2]

Next replace one line from lcd_direct.py with:


        self._position(
            self.get_line_address(pos_y) + pos_x,
            self._get_enable()
        )

When you run demo it will work as we want. Direct input completed.

Virtual direct

Lets quickly check if virtual lcds are working. Add new demo function (into lcd_virtual_direct.py):


def main_test6():
    """test 3 - lcd 40x4 -&amp;amp;gt; 20x4 + 20x4"""
    drv = Gpio()
    drv.pins['E2'] = 10
    drv.pins['E'] = 24
    lcd_1 = lcd.CharLCD(40, 4, drv, 0, 0)
    vlcd_1 = vlcd.CharLCD(20, 4)
    vlcd_1.add_display(0, 0, lcd_1)

    vlcd_2 = vlcd.CharLCD(20, 4)
    vlcd_2.add_display(0, 0, lcd_1, 20)

    vlcd_1.init()
    vlcd_2.init()

    vlcd_1.write('-First blarg1 !')
    vlcd_1.write('-Second blarg2 !', 0, 1)
    vlcd_1.write('-Third blarg3 !', 0, 2)
    vlcd_1.write('-Fourth blarg4 !', 0, 3)

    vlcd_2.write('-First blarg1 !')
    vlcd_2.write('-Second blarg2 !', 0, 1)
    vlcd_2.write('-Third blarg3 !', 0, 2)
    vlcd_2.write('-Fourth blarg4 !', 0, 3)

When you run this function… it will crash 🙂  Reason ? I forgot to write init() function in direct ! We done it with buffered but not with direct!

As the reason the lcds in vlcd are not initialized with vlcd init(). Open lcd_virtual_direct.py and paste after __init__:


    def init(self):
        """Buffer init"""
        if self.initialized:
            return
        if len(self.displays) == 0:
            raise ValueError("Add lcd before init vlcd")

        for display in self.displays:
            display['lcd'].init()
        self.initialized = True

And move (from buffered)

self.active_display = None
self.initialized = False

To parent __init__ function.

Now demo works !

Stop! Jenkins time!

This time all test failed 🙂 I didn’t update Null driver, quick functions fixes, add pins array and test are ok.

I done very important mistake, didn’t update BaseDriver and this gave me lots of

Arguments number differs from overridden method

I updated this abstract class as it should be but this in result gave same error for i2c driver. Lets leave it for now.

Summary

In this part we have:

  • add support for twin-lcd 40×4 in direct mode
  • update gpio driver
  • update BaseDriver
  • update Null driver

Download source

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