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.

Connections diagram.



The code.

I found very useful information here.
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).

Thanks for reading.

Marek Karcz, 11/16/2013

No comments:

Post a Comment

Reset And Panic Buttons, Buttons Debouncing

Reset And Panic Buttons, Buttons Debouncing The monitor program in my home brew computer has a nice debug feature. The NMI (Non-Maskab...