Saturday, November 16, 2013

Raspberry Pi, I2C LCD screen and Safe Power Down button.

The
Raspberry Pi, just sitting there, asking to be played with...

As I
looked over my existing projects I noticed this sad little lone R-Pi
sitting on my desk. Some time ago I bought it. Played with it a bit,
installed NOOB image on the SD card, connected I2C LCD text display
to it and a shutdown switch and played a bit with GPIO programming in
Python. I want to make a car trip computer out of this R-Pi. You
know, a computer that will read car's ECU via OBD2 port and display
information in real time, like the average and current gas usage,
trip time and length, how many miles left on the remaining fuel and
such. In the future perhaps I could add a camera and trip log. This
may be a cool project. I commute to work 15 miles, so there will be
plenty opportunity to test the toy.

I
remembered that this LCD screen, connected to GPIO pins via some
prototyping wire (female connectors at both ends) gave me a lot of
trouble related to poor contact, so I decided to upgrade it and make
a dedicated connector with wires soldered to it. I also need a keypad
for the project, but since I did not buy or make one yet, I just
connected a LCD and a shutdown switch at this time.

The
LCD takes just 4 wires – 5V, GND, SDA and SCL
(I2C and power, Pi's GPIO pins #2, 6, 3, 5 respectively). The
unfortunate thing is that the R-Pi's logic is 3.3V, however this
display's power voltage requirement and logic is 5V. I tried to power
it from Pi's 3.3V pin hoping that it has some built in tolerance,
however the display would not work then. Therefore I powered the
screen from Pi's 5V pin with a bit of scare that I might damage the
Pi's GPIO port, however this seems to work fine. It looks like the
device accepts 3.3V logic, just needs to be powered by 5V to operate.
I guess it should be OK as long as I am not sending the input to R-Pi
in the 5V level. Since LCD is an output device, this is not the case.
The temporary connection I made previously suffered from poor contact
and I often had voltage drops that caused information loss on the
display, even with the 5V power connected to the device. With the new
connector things look much better and the display is finally
reliable.

It presents the driver in Python for LCD display that is in turn driven by
PCF8574 expander chip, which is basically an I2C hardware driver for
the LCD display.

It is
easy to implement and start programming your LCD screen in Python
right away (assuming you already installed and configured your GPIO
libraries).

The
actual driver code listed below, lcddriver.py was taken from above web site at the time I made the project:

import smbus

from time import *

# 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 lcd:

#initializes
objects and lcd

'''

Reverse Codes:

0: lower 4 bits of
expander are commands bits

1: top 4 bits of
expander are commands bits AND P0-4 P1-5 P2-6

2: top 4 bits of
expander are commands bits AND P0-6 P1-5 P2-4

'''

def __init__(self,
addr, port, reverse=0):

self.reverse =
reverse

self.lcd_device =
i2c_device(addr, port)

if self.reverse:

self.lcd_device.write(0x30)

self.lcd_strobe()

sleep(0.0005)

self.lcd_strobe()

sleep(0.0005)

self.lcd_strobe()

sleep(0.0005)

self.lcd_device.write(0x20)

self.lcd_strobe()

sleep(0.0005)

else:

self.lcd_device.write(0x03)

self.lcd_strobe()

sleep(0.0005)

self.lcd_strobe()

sleep(0.0005)

self.lcd_strobe()

sleep(0.0005)

self.lcd_device.write(0x02)

self.lcd_strobe()

sleep(0.0005)

self.lcd_write(0x28)

self.lcd_write(0x08)

self.lcd_write(0x01)

self.lcd_write(0x06)

self.lcd_write(0x0C)

self.lcd_write(0x0F)

# clocks EN to
latch command

def
lcd_strobe(self):

if self.reverse ==
1:

self.lcd_device.write((self.lcd_device.read()
| 0x04))

self.lcd_device.write((self.lcd_device.read()
& 0xFB))

if self.reverse ==
2:

self.lcd_device.write((self.lcd_device.read()
| 0x01))

self.lcd_device.write((self.lcd_device.read()
& 0xFE))

else:

self.lcd_device.write((self.lcd_device.read()
| 0x10))

self.lcd_device.write((self.lcd_device.read()
& 0xEF))

# write a command
to lcd

def lcd_write(self,
cmd):

if self.reverse:

self.lcd_device.write((cmd
>> 4)<<4)

self.lcd_strobe()

self.lcd_device.write((cmd
& 0x0F)<<4)

self.lcd_strobe()

self.lcd_device.write(0x0)

else:

self.lcd_device.write((cmd
>> 4))

self.lcd_strobe()

self.lcd_device.write((cmd
& 0x0F))

self.lcd_strobe()

self.lcd_device.write(0x0)

# write a character
to lcd (or character rom)

def
lcd_write_char(self, charvalue):

if self.reverse ==
1:

self.lcd_device.write((0x01
| (charvalue >> 4)<<4))

self.lcd_strobe()

self.lcd_device.write((0x01
| (charvalue & 0x0F)<<4))

self.lcd_strobe()

self.lcd_device.write(0x0)

if self.reverse ==
2:

self.lcd_device.write((0x04
| (charvalue >> 4)<<4))

self.lcd_strobe()

self.lcd_device.write((0x04
| (charvalue & 0x0F)<<4))

self.lcd_strobe()

self.lcd_device.write(0x0)

else:

self.lcd_device.write((0x40
| (charvalue >> 4)))

self.lcd_strobe()

self.lcd_device.write((0x40
| (charvalue & 0x0F)))

self.lcd_strobe()

self.lcd_device.write(0x0)

# put char function

def lcd_putc(self,
char):

self.lcd_write_char(ord(char))

# put string
function

def lcd_puts(self,
string, line):

if line == 1:

self.lcd_write(0x80)

if line == 2:

self.lcd_write(0xC0)

if line == 3:

self.lcd_write(0x94)

if line == 4:

self.lcd_write(0xD4)

for char in
string:

self.lcd_putc(char)

# clear lcd and set
to home

def
lcd_clear(self):

self.lcd_write(0x1)

self.lcd_write(0x2)

# 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)

Example
of use, the script that displays welcome banner, hello.py:

import
lcddriver

from
time import *

lcd
= lcddriver.lcd()

lcd.lcd_clear()

lcd.lcd_display_string("*------------------*",1);

lcd.lcd_display_string("|System
is running.|",2)

lcd.lcd_display_string("*------------------*",3);

I
modified few start-up and shutdown handling scripts to run the
scripts that display information on the LCD screen. This way I have
neat messages that you can see on the pictures when the system is
booted up and ready to use and also when I press the shutdown button,
I get the information displayed about the shutdown progress. Since
there is only one display and there are multiple scripts or programs
that would like to write data to it, the proper way to do it would be
to create some sort of a server that would take the requests from
clients and relay them to the LCD screen. For now however I write
them directly to the LCD screen since this was meant as a demo and
proof of operation.

E.g:
the script displaying welcome banner, hello.py, was added in
script /etc/rc.local at the end:

cd
~pi/src/py/i2c/lcd/hello

python
hello.py &

cd
-

The
script that polls GPIO pin #18 for low state, then shuts down the
R-Pi and displays adequate messages to LCD screen, safeoff.py:

import
lcddriver

from
time import *

import
RPi.GPIO as GPIO

import
os

import
time

GPIO.setmode(GPIO.BCM)

GPIO.setup(18,
GPIO.IN,pull_up_down=GPIO.PUD_UP)

lcd
= lcddriver.lcd()

while
True:

if(GPIO.input(18) ==
0):

lcd.lcd_clear()

lcd.lcd_display_string("Shutting
down...",1)

os.system("sudo
shutdown -h now")

break

time.sleep(2)

progress
= "/"

while
True:

lcd.lcd_display_string(progress,2)

progress=progress+"/"

time.sleep(1);

was
added to /etc/rc.local script as well:

cd
~pi/src/py/i2c/lcd/safeoff

python
safeoff.py &

cd
-

The
script that displays the final message, systemoffmsg.py:

import
lcddriver

lcd
= lcddriver.lcd()

lcd.lcd_clear()

lcd.lcd_display_string("System
is halted.",1)

And
the script that displays the system restart message, sysrestmsg.py:

import
lcddriver

lcd
= lcddriver.lcd()

lcd.lcd_clear()

lcd.lcd_display_string("System
will restart.",1)

lcd.lcd_display_string("Wait
for welcome",2)

lcd.lcd_display_string("screen...",3)

were
added to /etc/init.d/halt and /etc/init.d/reboot
respectively:

log_action_msg
"Will now halt"

python
~pi/src/py/i2c/lcd/safeoff/sysoffmsg.py >/dev/null 2>&1

halt -d -f
$netdown $poweroff $hddown

}

and

do_stop
() {

# Message
should end with a newline since kFreeBSD may

# print more
stuff (see #323749)

log_action_msg
"Will now restart"

python
~pi/src/py/i2c/lcd/safeoff/sysrestmsg.py

reboot -d -f
-i

}

Pictures.

Image
1: R-Pi with I2C LCD and shutdown switch connected before it was put
in the case.

Image
2: Detailed view of GPIO connector.

Image
3: Detailed view of I2C LCD connector.

Image
4: R-Pi in case with LCD attached on top with the rubber bands.

Image
5: R-Pi, side view.

Image
6: R-Pi, back side view, a bit of creativity with a Lego (C) block
supporting the LCD screen.

Image
7: ...and the SD card side view.

Image
8: Welcome banner displayed on LCD after R-Pi boot-up.

Image
9: Testing shutdown button.

Image
10: Shutdown progress displayed.

Image
11: It is now safe to power off your R-Pi.

Well, this is it. Nothing much, but perhaps this article will help somebody doing first steps in R-Pi exploration or looking for the information related to the topic of connecting LCD or making a safe power-off switch for R-Pi's embedded use (with no keyboard and HDMI display).