I2C LCD Demo with Onewire Temperature Sensor
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.
- 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
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
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".
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.
''' 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=d4_pin self.pins=d5_pin self.pins=d6_pin self.pins=d7_pin self.pins=rs_pin self.pins=rw_pin self.pins=en_pin self.pins=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=4 # D4 Pin self.pins=5 # D5 Pin self.pins=6 # D6 Pin self.pins=7 # D7 Pin self.pins=0 # RS Pin self.pins=1 # RW Pin self.pins=2 # EN Pin self.pins=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=4 # D4 Pin self.pins=5 # D5 Pin self.pins=6 # D6 Pin self.pins=7 # D7 Pin self.pins=0 # RS Pin self.pins=1 # RW Pin self.pins=2 # EN Pin self.pins=3 # Backlight Pin else: # self.pins is already initialized to this, but broken out here for clarity 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 # 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)
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.