BeagleBone/I2CLCDDemo
Contents
I2C LCD Demo with Onewire Temperature Sensor
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
- 8 Channel Logic Converter $4.95 each and free shipping.
- IIC I2C LCD Module $3.20 each and cheap shipping.
- Sturdy 4 Line LCD $6.90 each and cheap shipping.
- (Optional) Waterproof Temperature Sensor $2 and free shipping. Also uses a 4.7k resistor (there is some wiggle room here).
Logic Converter Wiring
For the logic converter you need to
- Run a wire from a 3.3v pin to the VCC on the low side of the logic converter.
- Run a wire from a 5v pin to the VCC on the high side of the logic converter.
- 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
- Wire the I2C module into 5V and Ground with the high side of the logic converter
- 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)
- 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
- Run a jumper wire from the 3.3v from the VCC low side of the logic converter to the sensor's VCC wire
- Run a jumper wire from ground to the sensor ground wire
- Put a 4.7k resistor from the 3.3V VCC to the data wire
- 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.