Commit 42cc8304 authored by julric's avatar julric

added standalone software for Adafruit LCD and AirPi

parent 09b3c302
#!/usr/bin/python
# Python library for Adafruit RGB-backlit LCD plate for Raspberry Pi.
# Written by Adafruit Industries. MIT license.
# This is essentially a complete rewrite, but the calling syntax
# and constants are based on code from lrvick and LiquidCrystal.
# lrvic - https://github.com/lrvick/raspi-hd44780/blob/master/hd44780.py
# LiquidCrystal - https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/LiquidCrystal.cpp
from Adafruit_I2C import Adafruit_I2C
from time import sleep
class Adafruit_CharLCDPlate(Adafruit_I2C):
# ----------------------------------------------------------------------
# Constants
# Port expander registers
MCP23017_IOCON_BANK0 = 0x0A # IOCON when Bank 0 active
MCP23017_IOCON_BANK1 = 0x15 # IOCON when Bank 1 active
# These are register addresses when in Bank 1 only:
MCP23017_GPIOA = 0x09
MCP23017_IODIRB = 0x10
MCP23017_GPIOB = 0x19
# Port expander input pin definitions
SELECT = 0
RIGHT = 1
DOWN = 2
UP = 3
LEFT = 4
# LED colors
OFF = 0x00
RED = 0x01
GREEN = 0x02
BLUE = 0x04
YELLOW = RED + GREEN
TEAL = GREEN + BLUE
VIOLET = RED + BLUE
WHITE = RED + GREEN + BLUE
ON = RED + GREEN + BLUE
# LCD Commands
LCD_CLEARDISPLAY = 0x01
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80
# Flags for display on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00
# Flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00
# Flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00
# ----------------------------------------------------------------------
# Constructor
def __init__(self, busnum=-1, addr=0x20, debug=False):
self.i2c = Adafruit_I2C(addr, busnum, debug)
# I2C is relatively slow. MCP output port states are cached
# so we don't need to constantly poll-and-change bit states.
self.porta, self.portb, self.ddrb = 0, 0, 0b00010000
# Set MCP23017 IOCON register to Bank 0 with sequential operation.
# If chip is already set for Bank 0, this will just write to OLATB,
# which won't seriously bother anything on the plate right now
# (blue backlight LED will come on, but that's done in the next
# step anyway).
self.i2c.bus.write_byte_data(
self.i2c.address, self.MCP23017_IOCON_BANK1, 0)
# Brute force reload ALL registers to known state. This also
# sets up all the input pins, pull-ups, etc. for the Pi Plate.
self.i2c.bus.write_i2c_block_data(
self.i2c.address, 0,
[ 0b00111111, # IODIRA R+G LEDs=outputs, buttons=inputs
self.ddrb , # IODIRB LCD D7=input, Blue LED=output
0b00111111, # IPOLA Invert polarity on button inputs
0b00000000, # IPOLB
0b00000000, # GPINTENA Disable interrupt-on-change
0b00000000, # GPINTENB
0b00000000, # DEFVALA
0b00000000, # DEFVALB
0b00000000, # INTCONA
0b00000000, # INTCONB
0b00000000, # IOCON
0b00000000, # IOCON
0b00111111, # GPPUA Enable pull-ups on buttons
0b00000000, # GPPUB
0b00000000, # INTFA
0b00000000, # INTFB
0b00000000, # INTCAPA
0b00000000, # INTCAPB
self.porta, # GPIOA
self.portb, # GPIOB
self.porta, # OLATA 0 on all outputs; side effect of
self.portb ]) # OLATB turning on R+G+B backlight LEDs.
# Switch to Bank 1 and disable sequential operation.
# From this point forward, the register addresses do NOT match
# the list immediately above. Instead, use the constants defined
# at the start of the class. Also, the address register will no
# longer increment automatically after this -- multi-byte
# operations must be broken down into single-byte calls.
self.i2c.bus.write_byte_data(
self.i2c.address, self.MCP23017_IOCON_BANK0, 0b10100000)
self.displayshift = (self.LCD_CURSORMOVE |
self.LCD_MOVERIGHT)
self.displaymode = (self.LCD_ENTRYLEFT |
self.LCD_ENTRYSHIFTDECREMENT)
self.displaycontrol = (self.LCD_DISPLAYON |
self.LCD_CURSOROFF |
self.LCD_BLINKOFF)
self.write(0x33) # Init
self.write(0x32) # Init
self.write(0x28) # 2 line 5x8 matrix
self.write(self.LCD_CLEARDISPLAY)
self.write(self.LCD_CURSORSHIFT | self.displayshift)
self.write(self.LCD_ENTRYMODESET | self.displaymode)
self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol)
self.write(self.LCD_RETURNHOME)
# ----------------------------------------------------------------------
# Write operations
# The LCD data pins (D4-D7) connect to MCP pins 12-9 (PORTB4-1), in
# that order. Because this sequence is 'reversed,' a direct shift
# won't work. This table remaps 4-bit data values to MCP PORTB
# outputs, incorporating both the reverse and shift.
flip = ( 0b00000000, 0b00010000, 0b00001000, 0b00011000,
0b00000100, 0b00010100, 0b00001100, 0b00011100,
0b00000010, 0b00010010, 0b00001010, 0b00011010,
0b00000110, 0b00010110, 0b00001110, 0b00011110 )
# Low-level 4-bit interface for LCD output. This doesn't actually
# write data, just returns a byte array of the PORTB state over time.
# Can concatenate the output of multiple calls (up to 8) for more
# efficient batch write.
def out4(self, bitmask, value):
hi = bitmask | self.flip[value >> 4]
lo = bitmask | self.flip[value & 0x0F]
return [hi | 0b00100000, hi, lo | 0b00100000, lo]
# The speed of LCD accesses is inherently limited by I2C through the
# port expander. A 'well behaved program' is expected to poll the
# LCD to know that a prior instruction completed. But the timing of
# most instructions is a known uniform 37 mS. The enable strobe
# can't even be twiddled that fast through I2C, so it's a safe bet
# with these instructions to not waste time polling (which requires
# several I2C transfers for reconfiguring the port direction).
# The D7 pin is set as input when a potentially time-consuming
# instruction has been issued (e.g. screen clear), as well as on
# startup, and polling will then occur before more commands or data
# are issued.
pollables = ( LCD_CLEARDISPLAY, LCD_RETURNHOME )
# Write byte, list or string value to LCD
def write(self, value, char_mode=False):
""" Send command/data to LCD """
# If pin D7 is in input state, poll LCD busy flag until clear.
if self.ddrb & 0b00010000:
lo = (self.portb & 0b00000001) | 0b01000000
hi = lo | 0b00100000 # E=1 (strobe)
self.i2c.bus.write_byte_data(
self.i2c.address, self.MCP23017_GPIOB, lo)
while True:
# Strobe high (enable)
self.i2c.bus.write_byte(self.i2c.address, hi)
# First nybble contains busy state
bits = self.i2c.bus.read_byte(self.i2c.address)
# Strobe low, high, low. Second nybble (A3) is ignored.
self.i2c.bus.write_i2c_block_data(
self.i2c.address, self.MCP23017_GPIOB, [lo, hi, lo])
if (bits & 0b00000010) == 0: break # D7=0, not busy
self.portb = lo
# Polling complete, change D7 pin to output
self.ddrb &= 0b11101111
self.i2c.bus.write_byte_data(self.i2c.address,
self.MCP23017_IODIRB, self.ddrb)
bitmask = self.portb & 0b00000001 # Mask out PORTB LCD control bits
if char_mode: bitmask |= 0b10000000 # Set data bit if not a command
# If string or list, iterate through multiple write ops
if isinstance(value, str):
last = len(value) - 1 # Last character in string
data = [] # Start with blank list
for i, v in enumerate(value): # For each character...
# Append 4 bytes to list representing PORTB over time.
# First the high 4 data bits with strobe (enable) set
# and unset, then same with low 4 data bits (strobe 1/0).
data.extend(self.out4(bitmask, ord(v)))
# I2C block data write is limited to 32 bytes max.
# If limit reached, write data so far and clear.
# Also do this on last byte if not otherwise handled.
if (len(data) >= 32) or (i == last):
self.i2c.bus.write_i2c_block_data(
self.i2c.address, self.MCP23017_GPIOB, data)
self.portb = data[-1] # Save state of last byte out
data = [] # Clear list for next iteration
elif isinstance(value, list):
# Same as above, but for list instead of string
last = len(value) - 1
data = []
for i, v in enumerate(value):
data.extend(self.out4(bitmask, v))
if (len(data) >= 32) or (i == last):
self.i2c.bus.write_i2c_block_data(
self.i2c.address, self.MCP23017_GPIOB, data)
self.portb = data[-1]
data = []
else:
# Single byte
data = self.out4(bitmask, value)
self.i2c.bus.write_i2c_block_data(
self.i2c.address, self.MCP23017_GPIOB, data)
self.portb = data[-1]
# If a poll-worthy instruction was issued, reconfigure D7
# pin as input to indicate need for polling on next call.
if (not char_mode) and (value in self.pollables):
self.ddrb |= 0b00010000
self.i2c.bus.write_byte_data(self.i2c.address,
self.MCP23017_IODIRB, self.ddrb)
# ----------------------------------------------------------------------
# Utility methods
def begin(self, cols, lines):
self.currline = 0
self.numlines = lines
self.clear()
# Puts the MCP23017 back in Bank 0 + sequential write mode so
# that other code using the 'classic' library can still work.
# Any code using this newer version of the library should
# consider adding an atexit() handler that calls this.
def stop(self):
self.porta = 0b11000000 # Turn off LEDs on the way out
self.portb = 0b00000001
sleep(0.0015)
self.i2c.bus.write_byte_data(
self.i2c.address, self.MCP23017_IOCON_BANK1, 0)
self.i2c.bus.write_i2c_block_data(
self.i2c.address, 0,
[ 0b00111111, # IODIRA
self.ddrb , # IODIRB
0b00000000, # IPOLA
0b00000000, # IPOLB
0b00000000, # GPINTENA
0b00000000, # GPINTENB
0b00000000, # DEFVALA
0b00000000, # DEFVALB
0b00000000, # INTCONA
0b00000000, # INTCONB
0b00000000, # IOCON
0b00000000, # IOCON
0b00111111, # GPPUA
0b00000000, # GPPUB
0b00000000, # INTFA
0b00000000, # INTFB
0b00000000, # INTCAPA
0b00000000, # INTCAPB
self.porta, # GPIOA
self.portb, # GPIOB
self.porta, # OLATA
self.portb ]) # OLATB
def clear(self):
self.write(self.LCD_CLEARDISPLAY)
def home(self):
self.write(self.LCD_RETURNHOME)
row_offsets = ( 0x00, 0x40, 0x14, 0x54 )
def setCursor(self, col, row):
if row > self.numlines: row = self.numlines - 1
elif row < 0: row = 0
self.write(self.LCD_SETDDRAMADDR | (col + self.row_offsets[row]))
def display(self):
""" Turn the display on (quickly) """
self.displaycontrol |= self.LCD_DISPLAYON
self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol)
def noDisplay(self):
""" Turn the display off (quickly) """
self.displaycontrol &= ~self.LCD_DISPLAYON
self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol)
def cursor(self):
""" Underline cursor on """
self.displaycontrol |= self.LCD_CURSORON
self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol)
def noCursor(self):
""" Underline cursor off """
self.displaycontrol &= ~self.LCD_CURSORON
self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol)
def ToggleCursor(self):
""" Toggles the underline cursor On/Off """
self.displaycontrol ^= self.LCD_CURSORON
self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol)
def blink(self):
""" Turn on the blinking cursor """
self.displaycontrol |= self.LCD_BLINKON
self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol)
def noBlink(self):
""" Turn off the blinking cursor """
self.displaycontrol &= ~self.LCD_BLINKON
self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol)
def ToggleBlink(self):
""" Toggles the blinking cursor """
self.displaycontrol ^= self.LCD_BLINKON
self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol)
def scrollDisplayLeft(self):
""" These commands scroll the display without changing the RAM """
self.displayshift = self.LCD_DISPLAYMOVE | self.LCD_MOVELEFT
self.write(self.LCD_CURSORSHIFT | self.displayshift)
def scrollDisplayRight(self):
""" These commands scroll the display without changing the RAM """
self.displayshift = self.LCD_DISPLAYMOVE | self.LCD_MOVERIGHT
self.write(self.LCD_CURSORSHIFT | self.displayshift)
def leftToRight(self):
""" This is for text that flows left to right """
self.displaymode |= self.LCD_ENTRYLEFT
self.write(self.LCD_ENTRYMODESET | self.displaymode)
def rightToLeft(self):
""" This is for text that flows right to left """
self.displaymode &= ~self.LCD_ENTRYLEFT
self.write(self.LCD_ENTRYMODESET | self.displaymode)
def autoscroll(self):
""" This will 'right justify' text from the cursor """
self.displaymode |= self.LCD_ENTRYSHIFTINCREMENT
self.write(self.LCD_ENTRYMODESET | self.displaymode)
def noAutoscroll(self):
""" This will 'left justify' text from the cursor """
self.displaymode &= ~self.LCD_ENTRYSHIFTINCREMENT
self.write(self.LCD_ENTRYMODESET | self.displaymode)
def createChar(self, location, bitmap):
self.write(self.LCD_SETCGRAMADDR | ((location & 7) << 3))
self.write(bitmap, True)
self.write(self.LCD_SETDDRAMADDR)
def message(self, text):
""" Send string to LCD. Newline wraps to second line"""
lines = str(text).split('\n') # Split at newline(s)
for i, line in enumerate(lines): # For each substring...
if i > 0: # If newline(s),
self.write(0xC0) # set DDRAM address to 2nd line
self.write(line, True) # Issue substring
def backlight(self, color):
c = ~color
self.porta = (self.porta & 0b00111111) | ((c & 0b011) << 6)
self.portb = (self.portb & 0b11111110) | ((c & 0b100) >> 2)
# Has to be done as two writes because sequential operation is off.
self.i2c.bus.write_byte_data(
self.i2c.address, self.MCP23017_GPIOA, self.porta)
self.i2c.bus.write_byte_data(
self.i2c.address, self.MCP23017_GPIOB, self.portb)
# Read state of single button
def buttonPressed(self, b):
return (self.i2c.readU8(self.MCP23017_GPIOA) >> b) & 1
# Read and return bitmask of combined button state
def buttons(self):
return self.i2c.readU8(self.MCP23017_GPIOA) & 0b11111
# ----------------------------------------------------------------------
# Test code
if __name__ == '__main__':
lcd = Adafruit_CharLCDPlate()
lcd.begin(16, 2)
lcd.clear()
lcd.message("Adafruit RGB LCD\nPlate w/Keypad!")
sleep(1)
col = (('Red' , lcd.RED) , ('Yellow', lcd.YELLOW), ('Green' , lcd.GREEN),
('Teal', lcd.TEAL), ('Blue' , lcd.BLUE) , ('Violet', lcd.VIOLET),
('Off' , lcd.OFF) , ('On' , lcd.ON))
print "Cycle thru backlight colors"
for c in col:
print c[0]
lcd.clear()
lcd.message(c[0])
lcd.backlight(c[1])
sleep(0.5)
btn = ((lcd.SELECT, 'Select', lcd.ON),
(lcd.LEFT , 'Left' , lcd.RED),
(lcd.UP , 'Up' , lcd.BLUE),
(lcd.DOWN , 'Down' , lcd.GREEN),
(lcd.RIGHT , 'Right' , lcd.VIOLET))
print "Try buttons on plate"
lcd.clear()
lcd.message("Try buttons")
prev = -1
while True:
for b in btn:
if lcd.buttonPressed(b[0]):
if b is not prev:
print b[1]
lcd.clear()
lcd.message(b[1])
lcd.backlight(b[2])
prev = b
break
#!/usr/bin/python
import smbus
# ===========================================================================
# Adafruit_I2C Class
# ===========================================================================
class Adafruit_I2C :
@staticmethod
def getPiRevision():
"Gets the version number of the Raspberry Pi board"
# Courtesy quick2wire-python-api
# https://github.com/quick2wire/quick2wire-python-api
try:
with open('/proc/cpuinfo','r') as f:
for line in f:
if line.startswith('Revision'):
return 1 if line.rstrip()[-1] in ['1','2'] else 2
except:
return 0
@staticmethod
def getPiI2CBusNumber():
# Gets the I2C bus number /dev/i2c#
return 1 if Adafruit_I2C.getPiRevision() > 1 else 0
def __init__(self, address, busnum=-1, debug=False):
self.address = address
# By default, the correct I2C bus is auto-detected using /proc/cpuinfo
# Alternatively, you can hard-code the bus version below:
# self.bus = smbus.SMBus(0); # Force I2C0 (early 256MB Pi's)
# self.bus = smbus.SMBus(1); # Force I2C1 (512MB Pi's)
self.bus = smbus.SMBus(
busnum if busnum >= 0 else Adafruit_I2C.getPiI2CBusNumber())
self.debug = debug
def reverseByteOrder(self, data):
"Reverses the byte order of an int (16-bit) or long (32-bit) value"
# Courtesy Vishal Sapre
byteCount = len(hex(data)[2:].replace('L','')[::2])
val = 0
for i in range(byteCount):
val = (val << 8) | (data & 0xff)
data >>= 8
return val
def errMsg(self):
print "Error accessing 0x%02X: Check your I2C address" % self.address
return -1
def write8(self, reg, value):
"Writes an 8-bit value to the specified register/address"
try:
self.bus.write_byte_data(self.address, reg, value)
if self.debug:
print "I2C: Wrote 0x%02X to register 0x%02X" % (value, reg)
except IOError, err:
return self.errMsg()
def write16(self, reg, value):
"Writes a 16-bit value to the specified register/address pair"
try:
self.bus.write_word_data(self.address, reg, value)
if self.debug:
print ("I2C: Wrote 0x%02X to register pair 0x%02X,0x%02X" %
(value, reg, reg+1))
except IOError, err:
return self.errMsg()
def writeList(self, reg, list):
"Writes an array of bytes using I2C format"
try:
if self.debug:
print "I2C: Writing list to register 0x%02X:" % reg
print list
self.bus.write_i2c_block_data(self.address, reg, list)
except IOError, err:
return self.errMsg()
def readList(self, reg, length):
"Read a list of bytes from the I2C device"
try:
results = self.bus.read_i2c_block_data(self.address, reg, length)
if self.debug:
print ("I2C: Device 0x%02X returned the following from reg 0x%02X" %
(self.address, reg))
print results
return results
except IOError, err:
return self.errMsg()
def readU8(self, reg):
"Read an unsigned byte from the I2C device"
try:
result = self.bus.read_byte_data(self.address, reg)
if self.debug:
print ("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" %
(self.address, result & 0xFF, reg))
return result
except IOError, err:
return self.errMsg()
def readS8(self, reg):
"Reads a signed byte from the I2C device"
try:
result = self.bus.read_byte_data(self.address, reg)
if result > 127: result -= 256
if self.debug:
print ("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" %
(self.address, result & 0xFF, reg))
return result
except IOError, err:
return self.errMsg()
def readU16(self, reg):
"Reads an unsigned 16-bit value from the I2C device"
try:
result = self.bus.read_word_data(self.address,reg)
if (self.debug):
print "I2C: Device 0x%02X returned 0x%04X from reg 0x%02X" % (self.address, result & 0xFFFF, reg)
return result
except IOError, err:
return self.errMsg()
def readS16(self, reg):
"Reads a signed 16-bit value from the I2C device"
try:
result = self.bus.read_word_data(self.address,reg)
if (self.debug):
print "I2C: Device 0x%02X returned 0x%04X from reg 0x%02X" % (self.address, result & 0xFFFF, reg)
return result
except IOError, err:
return self.errMsg()
if __name__ == '__main__':
try:
bus = Adafruit_I2C(address=0)
print "Default I2C bus is accessible"
except:
print "Error accessing default I2C bus"
#!/usr/bin/python
# Copyright 2012 Daniel Berlin (with some changes by Adafruit Industries/Limor Fried)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal MCP230XX_GPIO(1, 0xin
# 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.
from Adafruit_I2C import Adafruit_I2C
import smbus
import time
MCP23017_IODIRA = 0x00
MCP23017_IODIRB = 0x01
MCP23017_GPIOA = 0x12
MCP23017_GPIOB = 0x13
MCP23017_GPPUA = 0x0C
MCP23017_GPPUB = 0x0D
MCP23017_OLATA = 0x14
MCP23017_OLATB = 0x15
MCP23008_GPIOA = 0x09
MCP23008_GPPUA = 0x06
MCP23008_OLATA = 0x0A
class Adafruit_MCP230XX(object):
OUTPUT = 0
INPUT = 1
def __init__(self, address, num_gpios):
assert num_gpios >= 0 and num_gpios <= 16, "Number of GPIOs must be between 0 and 16"
self.i2c = Adafruit_I2C(address=address)
self.address = address
self.num_gpios = num_gpios
# set defaults
if num_gpios <= 8:
self.i2c.write8(MCP23017_IODIRA, 0xFF) # all inputs on port A
self.direction = self.i2c.readU8(MCP23017_IODIRA)
self.i2c.write8(MCP23008_GPPUA, 0x00)
elif num_gpios > 8 and num_gpios <= 16:
self.i2c.write8(MCP23017_IODIRA, 0xFF) # all inputs on port A
self.i2c.write8(MCP23017_IODIRB, 0xFF) # all inputs on port B
self.direction = self.i2c.readU8(MCP23017_IODIRA)
self.direction |= self.i2c.readU8(MCP23017_IODIRB) << 8
self.i2c.write8(MCP23017_GPPUA, 0x00)
self.i2c.write8(MCP23017_GPPUB, 0x00)
def _changebit(self, bitmap, bit, value):
assert value == 1 or value == 0, "Value is %s must be 1 or 0" % value
if value == 0:
return bitmap & ~(1 << bit)
elif value == 1: