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. Raspberry Pi, I2C LCD screen and Safe Power Down buttonI 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)Raspberry Pi, I2C LCD screen and Safe Power Down button schematic
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.

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

Detailed view of GPIO connector.

                                                 Image 2: Detailed view of GPIO connector.

Detailed view of I2C LCD connector.

                                                          Image 3: Detailed view of I2C LCD connector.

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

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

R-Pi, side view.

                                                                                Image 5: R-Pi, side view.

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

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

...and the SD card side view.

                                                    Image 7: …and the SD card side view.

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

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

Testing shutdown button.

                                                         Image 9: Testing shutdown button.

Shutdown progress displayed.

                                               Image 10: Shutdown progress displayed.

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

                                           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.

Source: Raspberry Pi, I2C LCD screen and Safe Power Down button


About The Author

Ibrar Ayyub

I am an experienced technical writer holding a Master's degree in computer science from BZU Multan, Pakistan University. With a background spanning various industries, particularly in home automation and engineering, I have honed my skills in crafting clear and concise content. Proficient in leveraging infographics and diagrams, I strive to simplify complex concepts for readers. My strength lies in thorough research and presenting information in a structured and logical format.

Follow Us:
LinkedinTwitter

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top