Using LCD HD44780 on Raspberry Pi – part 4: I2C driver

sample1The time has come to add I2C driver.  At last !

I will start with I2C introduction, next we will look at the wiring. After that we will do some coding.

Earlier parts:

I2C

What is i2c ? Check wikipedia if you need details. For our needs lets call it a network for electronic devices. Each device got its own address. Its using one data line and one clock line.
We will have one master (Raspi) and lots of slaves (for now lcd).

First we need to enable it. Look at point 5 here or read below:

  1. open file /etc/modprobe.d/raspi-blacklist.conf and before blacklist i2c-bcm2708 put a #
  2. edit /etc/modules and add i2c-dev
  3. install i2c-tools
    sudo apt-get install i2c-tools
  4. add user pi to i2c group
    sudo adduser pi i2c
  5. reboot your raspi
  6. install python library
    sudo apt-get install python-smbus

Now our master is alive ! But what about slaves ?

We will be using common 8-bit expander PCF 8574. Lets look at its outputs:

pcf8574

PCF8574P

A0, A1, A2 address pins.
P0 – P7 data pins.
GND, Vcc power.
And SDA, SCL i2c bus.

Address pins allow us to select chip address. Part of address is hard coded into chip and we are able to select 3 bits. It give us lots of combinations. Ok maybe not a lot but 7 for one type of chip. If you look into spec you will see that 8574 has a few types (letter A, P and so on). It describe different package and ability to set different address bits. All in all it gives us many possibilities.

Lets look differently at the data pins.

 P7  P6  P5  P4  P3  P2  P1  P0
128  64  32  16   8   4   2   1

To control pins we are sending (or reading) a word.

Wiring – test

Lets start with something simple. We will connect chip to Raspberry just to see it is detected.
i2c_wire_test

Raspberry          PCF8574
    GND <---------> A0
    GND <---------> A1
    GND <---------> A2
    GND <---------> GND
    +5V <---------> Vcc
    SDA <---------> SDA
    SCL <---------> SCL

Now login into Raspberry via ssh and type (if you have an A version replace 1 with 0).

i2cdetect -y 1

Output should look like this:

pi@raspberrypi ~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

What does it say ? It telling us that something is on address 0x20 and something unknown at address 0x3B. My PCF is on 0x20. And this unknown thing is audio device – read more here.

Wiring – lcd

Time to plug LCD. We will be using 4-bit mode. So 4 pins for data, one for E and one for RS. It gives us 2 pins for something else, like to control backlight. From now we will be using external DC power supply.

LCD                                           PCF8574
 1 -------- GND                     GND ----- A0   Vcc ---- +5V 
 2 -------- +5V                     GND ----- A1   SDA ---- SDA on RPi
 3 --/\/\ [potentiometer]           GND ----- A2   SCL ---- SCL on RPi
       \--- GND                   LCD11 ----- P0   INT 
 4 [RS]---- P4                    LCD12 ----- P1   P7
 5 -------- GND                   LCD13 ----- P2   P6
 6 [E]----- P5                    LCD14 ----- P3   P5 ----- LCD6
 7                                  GND ----- GND  P4 ----- LCD4
 8
 9
10
11 [DB4]--- P0
12 [DB5]--- P1
13 [DB6]--- P2
14 [DB7]--- P3
15 --/\/\ [potentiometer]
       \--- +5V
16 -------- GND

Screen from Fritzing

i2c_lcd_2_16

If everything is correct, you will see working backlight, squares on front and

i2cdetect -y 1

will still work.

Coding – driver

What our driver needs to know ? I2c port, address and pin layout. Because we have only 8 pins to use will will implement only 4-bit LCD mode.

I2C driver will work differently then gpio one. In gpio we were setting each line separatly  and sending E signal. Here we are sending whole packet at once as a word.

Create a file i2c.py in drivers package and copy:


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

import time
import smbus
from base import BaseDriver


class I2C(BaseDriver):
    def __init__(self, address, port):
        &quot;&quot;&quot;set address port, pins
        :param address:
        :param port:
        &quot;&quot;&quot;
        self.address = address
        self.port = port
        self.mode = 8
        self.PINS = {
            'RS': 4,
            'E': 5,
            'DB4': 0,
            'DB5': 1,
            'DB6': 2,
            'DB7': 3
        }
        self.bus = smbus.SMBus(port)

    def init(self):
        &quot;&quot;&quot;recalculate pins to values&quot;&quot;&quot;
        pins = {}
        for k in self.PINS:
            pins[k] = int(pow(2, self.PINS[k]))
        self.PINS = pins

    def cmd(self, c):
        &quot;&quot;&quot;
        Send command to lcd
        :param c:
        &quot;&quot;&quot;
        if self.mode == 8:
            self.send(c &amp; 0x0F)
        else:
            self.send(c &gt;&gt; 4)
            self.send(c &amp; 0x0F)

    def shutdown(self):
        pass

    def send(self, c):
        &quot;&quot;&quot;
        Send E signal
        &quot;&quot;&quot;
        self.write(c | self.PINS['E'])
        time.sleep(0.005)
        self.write(c &amp; (0xFF - self.PINS['E']))

    def write(self, byte):
        &quot;&quot;&quot;
        Write data to bus
        :param byte:
        &quot;&quot;&quot;
        self.bus.write_byte(self.address, byte)

    def char(self, c):
        &quot;&quot;&quot;
        Send char
        :param c:
        &quot;&quot;&quot;
        c = ord(c)
        if self.mode == 8:
            self.send(self.PINS['RS'] | (c &amp; 0x0F))
        else:
            self.send(self.PINS['RS'] | (c &gt;&gt; 4))
            self.send(self.PINS['RS'] | (c &amp; 0x0F))

    def set_mode(self, m):
        &quot;&quot;&quot;
        Set working mode (4/8-bit)
        :param m:
        &quot;&quot;&quot;
        self.mode = m

What have we here ?

Function __init__ set pins according to wiring, sets I2C address and port. It also initialize a smbus class. Next in function init we are doing small cheat, we translate pins to their values.

Pins      E  RS  P3  P2  P1  P0   
Bits      1   1   1   1   1   1 
Values   32  16   8   4   2   1

If we would like to send E & P1, that means command 3, we will send 0x22 (34) via I2C.

Function cmd sends command to LCD. In 8-bit mode it takes only 4 bits and pass them to send, and in 4-bit mode it takes 4-low bits, pass them and 4-high bits and pass them too. Its similar to char function but char add RS pin.

Function send first add E value and send it then clears E. Sending word to LCD is done in write function.

Coding – test it !

Time to see if it works. open main.py and add

from charlcd.drivers.i2c import I2C

Now you can use new driver like this:


l1 = lcd.CharLCD(16, 2, I2C(0x20, 1))
l1.init()
l1.string('Second blarg !!')
l1.position(lcd.LINE1)
l1.string(&quot;second line&quot;)

I2C(0x20, 1)  – 0x20 is a i2c address (as seen by i2cdetect)

But what if have different wiring ? No problem !


d = I2C(0x20, 1)
d.PINS = {..}
l1 = lcd.CharLCD(16, 2, d)

And that’s all for this part.

Bonus, two LCDs, one by GPIO and second by I2C:

mess

Advertisements

One comment

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