BeagleBone/I2CLCDDemo

From eLinux.org
Jump to: navigation, search

I2C LCD Demo with Onewire Temperature Sensor

Cardboard mockup of beaglebone weather demo

Introduction

I created this tutorial to show how to use inexpensive components (commonly used arduino components) with the BeagleBone Black. The cost of this project can easily be under $20 USD shipping and all.

If you have come from tinkering with Arduino boards you need to understand that the BBB is much more sensitive than what you are used to. It can't handle the voltages or amounts of current. For this demo we'll be using a logic converter to allow the BBB to communicate with a 5V device over I2C.

For the software side I'm using python.

Hardware


Logic Converter Wiring

For the logic converter you need to

  1. Run a wire from a 3.3v pin to the VCC on the low side of the logic converter.
  2. Run a wire from a 5v pin to the VCC on the high side of the logic converter.
  3. Run a wire from a ground pin to the ground of one side and a jump a wire from the ground of one side to the ground on the other side.

I2C LCD wiring

For the LCD module you need to

  1. Wire the I2C module into 5V and Ground with the high side of the logic converter
  2. Plug SCL and SDA from the I2C module into the first two pins on the logic converter on the high side (for example H0 and H1)
  3. On the other side of the logic converter run wires to the BBB to pins 19(SCL) and 20(SDA)

Temp sensor wiring

For the temperature sensor you need to

  1. Run a jumper wire from the 3.3v from the VCC low side of the logic converter to the sensor's VCC wire
  2. Run a jumper wire from ground to the sensor ground wire
  3. Put a 4.7k resistor from the 3.3V VCC to the data wire
  4. From the data wire, run a wire to the BBB at P9_22

Software

For the 1-Wire temperature sensor, you can follow this tutorial to get the device recognized by the system. For the purposes of this tutorial the address I will be using is "/sys/bus/w1/devices/28-000004d43557/w1_slave" yours will be different. To get your 1-wire device address you should be able to ls /sys/bus/w1/devices/.


You can setup python for working with I2C by following the guide from Adafruit. The main priority is having smbus working. You need to have i2c tools installed to figure out what address the I2C LCD module is at. Mine is on bus 1, at address 0x20.


The particular I2C module I listed in the hardware section had a peculiar wiring that didn't easily work with any I2C LCD libraries. For one the backlight pin toggles the opposite of the PyLCD library. I was able to find the pinout by digging through tons of sites, and finally found a comment on dx.com for a different module which mostly worked, and then found an unpopular Arduino sketch that worked with it, and got it all sorted out from there.

Below is the actual code to make this app run. You'll have to at the very least modify the w1 device address. The first "os path exists" checks to see if the device is loaded, and if not, loads it up and give enough time before continuing. To run the application run "python weather.py".


weather.py

import pylcd
import smbus
from time import *
import os.path
 
#if 1-wire sensor isn't ready
if os.path.exists("/sys/bus/w1/devices/28-000004d43557/w1_slave") is False:
        slots = open("/sys/devices/bone_capemgr.9/slots", "w")
        slots.write("BB-W1:00A0")
        slots.close()
        print "Added 1-Wire Device"
        sleep(2)
        #This is basically the same as "echo BB-W1:00A0 > /sys/devices/bone_capemgr.9/slots"
 
 
class temp:
        def __init__(self):
                a = 1
 
        #celcius
        def temp_c(self):
                w1="/sys/bus/w1/devices/28-000004d43557/w1_slave"
                raw = open(w1, "r").read()
                return str(float(raw.split("t=")[-1])/1000)
        #farenheit
        def temp_f(self):
                ctemp = self.temp_c()
                ftemp = 9.0/5.0 * float(ctemp) + 32.0
                return ftemp
 
while True:
        tempnow = temp()
        ctemp = tempnow.temp_c()
        ftemp = tempnow.temp_f()
        lcd = pylcd.lcd(0x20, 1)
        lcd.lcd_puts("BeagleWeather", 1)
        lcd.lcd_puts("Celsius: "+str(ctemp), 2)
        lcd.lcd_puts("Farenheit: "+str(ftemp), 3)
        lcd.lcd_puts(strftime("%m-%d-%Y %H:%M",localtime()), 4)
        #update every 10 seconds
        sleep(10)


Here is the library. Besides the modified pins from the original source at github, I added if now < today7am or now > today11pm: to turn the backlight off during sleeping hours. It was the easiest place to hack it in, as I didn't want to spend a lot of time on that.


pylcd.py

'''
Copyright (C) 2012 Matthew Skolaut
 
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'''
 
import smbus
from time import *
import datetime
 
# 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 (Use for "LCD2004" board)
        2: top 4 bits of expander are commands bits AND P0-6 P1-5 P2-4
        3: "LCD2004" board where lower 4 are commands, but backlight is pin 3
        '''
        def __init__(self, addr, port, reverse=0, backlight_pin=7, en_pin=4, rw_pin=5, rs_pin=6, d4_pin=0, d5_pin=1, d6_pin=2, d7_pin=3):
                self.reverse = reverse
                self.lcd_device = i2c_device(addr, port)
 
                self.pins=[i for i in range(8)] # Initialize the list
                #self.backlight=1<<7 # Initialize with backlight as on (Change self.backlight to 0 to turn off backlight pin)
                now = datetime.datetime.now()
                today7am = now.replace(hour=7, minute=0, second=0, microsecond=0)
                today11pm = now.replace(hour=23, minute=0, second=0, microsecond=0)
                if now < today7am or now > today11pm:
                        self.backlight=1<<7
                else:
                        backlight_pin=7
                        self.backlight=0
 
                if d7_pin != -1: # Manually set pins, in case we have a different backpack pinout
                        self.pins[0]=d4_pin
                        self.pins[1]=d5_pin
                        self.pins[2]=d6_pin
                        self.pins[3]=d7_pin
                        self.pins[4]=rs_pin
 
                        self.pins[5]=rw_pin
                        self.pins[6]=en_pin
                        self.pins[7]=backlight_pin
 
                elif self.reverse==1: # 1: top 4 bits of expander are commands bits AND P0-4 P1-5 P2-6 (Use for "LCD2004" board)
                        self.pins[0]=4          # D4 Pin
                        self.pins[1]=5          # D5 Pin
                        self.pins[2]=6          # D6 Pin
                        self.pins[3]=7          # D7 Pin
                        self.pins[4]=0          # RS Pin
                        self.pins[5]=1          # RW Pin
                        self.pins[6]=2          # EN Pin
                        self.pins[7]=3          # Backlight Pin
 
                elif self.reverse==2: # 2: top 4 bits of expander are commands bits AND P0-6 P1-5 P2-4
                        self.pins[0]=4          # D4 Pin
                        self.pins[1]=5          # D5 Pin
                        self.pins[2]=6          # D6 Pin
                        self.pins[3]=7          # D7 Pin
                        self.pins[4]=0          # RS Pin
                        self.pins[5]=1          # RW Pin
                        self.pins[6]=2          # EN Pin
                        self.pins[7]=3          # Backlight Pin         
 
                else:
                        # self.pins is already initialized to this, but broken out here for clarity
                        self.pins[0]=0          # D4 Pin
                        self.pins[1]=1          # D5 Pin
                        self.pins[2]=2          # D6 Pin
                        self.pins[3]=3          # D7 Pin
                        self.pins[4]=4          # RS Pin
                        self.pins[5]=5          # RW Pin
                        self.pins[6]=6          # EN Pin
                        self.pins[7]=7          # Backlight Pin
 
 
                # This begins the actual initialization sequence
                self.lcd_device_write(0x03) # Prepare to switch to 4 bit mode
                self.lcd_strobe()
                sleep(0.0005)
                self.lcd_strobe()
                sleep(0.0005)
                self.lcd_strobe()
                sleep(0.0005)
 
                self.lcd_device_write(0x02) # Set 4 bit mode
                self.lcd_strobe()
                sleep(0.0005)
 
 
                # Initialize
                self.lcd_write(0x28) # Set 4 bit, 2 line mode (Multi-line)
                self.lcd_write(0x08) # Hide cursor, don't blink
                self.lcd_write(0x01) # Clear display, move cursor home
                self.lcd_write(0x06) # Move cursor right
                self.lcd_write(0x0C) # Turn on display
#               self.lcd_write(0x0F)
 
 
        # clocks EN to latch command
        def lcd_strobe(self):
                self.lcd_device_write(self.lastcomm | (1<<6), 1) # 1<<6 is the enable pin
                self.lcd_device_write(self.lastcomm,1) # Technically not needed, but included so we can read from the display
 
        # write a command to lcd
        def lcd_write(self, cmd):
                self.lcd_device_write((cmd >> 4)) # Write the first 4 bits (nibble) of the command
                self.lcd_strobe()
                self.lcd_device_write((cmd & 0x0F)) # Write the second nibble of the command
                self.lcd_strobe()
                self.lcd_device_write(0x0) # Technically not needed
 
        # write a character to lcd (or character rom)
        def lcd_write_char(self, charvalue):
                self.lcd_device_write(((1<<4) | (charvalue >> 4))) # Originally this was 0x40
                self.lcd_strobe()
                self.lcd_device_write(((1<<4) | (charvalue & 0x0F))) # Originally this was 0x40
                self.lcd_strobe()
                self.lcd_device_write(0x0)
 
        # put char function
        def lcd_putc(self, char):
                self.lcd_write_char(ord(char))
 
 
        # Do clunky bitshifting to account for strangely wired boards
        # I guarantee there is an easier way of doing this.
        def lcd_device_write(self, commvalue, isstrobe=0):
                tempcomm=commvalue | self.backlight
                outcomm=[0 for i in range(8)]
 
                for a in range(0,8):
                        outcomm[self.pins[a]]=(tempcomm & 1)
                        tempcomm=tempcomm>>1
 
                tempcomm=0
                a=7
                while (a >= 0):
                        tempcomm=(tempcomm<<1)|outcomm[a]
                        a=a-1;
 
                self.lcd_device.write(tempcomm)
                sleep(0.0005) # May be unnecessary, but including to guarantee we don't push data out too fast
 
                # Since we can't trust what we read from the display, we store the last
                # executed command in a property inside the object. This way strobe
                # can add the enable bit & resend it
                if isstrobe==0: # 
                        self.lastcomm=commvalue
 
 
 
 
        # 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)
                sleep(0.005) # This command takes awhile.
                self.lcd_write(0x2)
                sleep(0.005) # This command takes awhile.
 
        # 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)

Notes

It looks like LCDd can be modified to work with this module by taking a glimpse of the source at http://lcdproc.cvs.sourceforge.net/viewvc/lcdproc/lcdproc/server/drivers/hd44780-i2c.c?revision=1.15&view=markup.