Skip to content
This repository was archived by the owner on Sep 30, 2019. It is now read-only.

Commit 8d720ef

Browse files
CharLCDPlate subclass I2C directly, not MCP230XX; quicker
1 parent 894c16c commit 8d720ef

File tree

2 files changed

+168
-158
lines changed

2 files changed

+168
-158
lines changed

Adafruit_CharLCDPlate/Adafruit_CharLCDPlate.py

+141-104
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
#!/usr/bin/python
22

3-
# Based on code from lrvick and LiquidCrystal
3+
# Python library for Adafruit RGB-backlit LCD plate for Raspberry Pi.
4+
# Written by Adafruit Industries. MIT license.
5+
6+
# This is essentially a complete rewrite, but the calling syntax
7+
# and constants are based on code from lrvick and LiquidCrystal.
48
# lrvic - https://github.com/lrvick/raspi-hd44780/blob/master/hd44780.py
59
# LiquidCrystal - https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/LiquidCrystal.cpp
610

7-
from Adafruit_MCP230xx import Adafruit_MCP230XX
11+
from Adafruit_I2C import Adafruit_I2C
12+
813

9-
class Adafruit_CharLCDPlate(Adafruit_MCP230XX):
14+
class Adafruit_CharLCDPlate(Adafruit_I2C):
1015

1116
# ----------------------------------------------------------------------
1217
# Constants
1318

19+
# Port expander registers
20+
MCP23017_IOCON_BANK0 = 0x0A # IOCON when Bank 0 active
21+
MCP23017_IOCON_BANK1 = 0x05 # IOCON when Bank 1 active
22+
# These are register addresses when in Bank 1 only:
23+
MCP23017_GPIOA = 0x09
24+
MCP23017_IODIRB = 0x10
25+
MCP23017_GPIOB = 0x19
26+
1427
# Port expander input pin definitions
1528
SELECT = 0
1629
RIGHT = 1
@@ -65,23 +78,55 @@ class Adafruit_CharLCDPlate(Adafruit_MCP230XX):
6578

6679
def __init__(self, busnum=-1, addr=0x20, debug=False):
6780

68-
self.mcp = Adafruit_MCP230XX(addr, 16, busnum, debug)
69-
70-
for i in range(0,16):
71-
if i < 6:
72-
# Configure button lines as inputs w/pullups
73-
self.mcp.config(i, Adafruit_MCP230XX.INPUT)
74-
self.mcp.pullup(i, True)
75-
elif 9 <= i <= 12:
76-
# Configure LCD data lines as inputs w/o pullups
77-
self.mcp.config(i, Adafruit_MCP230XX.INPUT)
78-
self.mcp.pullup(i, False)
79-
else:
80-
# All other lines are outputs
81-
self.mcp.config(i, Adafruit_MCP230XX.OUTPUT)
82-
83-
# Init control lines, backlight on (white)
84-
self.mcp.outputAll(0)
81+
self.i2c = Adafruit_I2C(addr, busnum, debug)
82+
83+
# I2C is relatively slow. MCP output port states are cached
84+
# so we don't need to constantly poll-and-change bit states.
85+
self.porta, self.portb, self.ddrb = 0, 0, 0b00010000
86+
87+
# Set MCP23017 IOCON register to Bank 0 with sequential operation.
88+
# If chip is already set for Bank 0, this will just write to OLATB,
89+
# which won't seriously bother anything on the plate right now
90+
# (blue backlight LED will come on, but that's done in the next
91+
# step anyway).
92+
self.i2c.bus.write_byte_data(
93+
self.i2c.address, self.MCP23017_IOCON_BANK1, 0)
94+
95+
# Brute force reload ALL registers to known state. This also
96+
# sets up all the input pins, pull-ups, etc. for the Pi Plate.
97+
self.i2c.bus.write_i2c_block_data(
98+
self.i2c.address, 0,
99+
[ 0b00111111, # IODIRA R+G LEDs=outputs, buttons=inputs
100+
self.ddrb , # IODIRB LCD D7=input, Blue LED=output
101+
0b00111111, # IPOLA Invert polarity on button inputs
102+
0b00000000, # IPOLB
103+
0b00000000, # GPINTENA Disable interrupt-on-change
104+
0b00000000, # GPINTENB
105+
0b00000000, # DEFVALA
106+
0b00000000, # DEFVALB
107+
0b00000000, # INTCONA
108+
0b00000000, # INTCONB
109+
0b00000000, # IOCON
110+
0b00000000, # IOCON
111+
0b00111111, # GPPUA Enable pull-ups on buttons
112+
0b00000000, # GPPUB
113+
0b00000000, # INTFA
114+
0b00000000, # INTFB
115+
0b00000000, # INTCAPA
116+
0b00000000, # INTCAPB
117+
self.porta, # GPIOA
118+
self.portb, # GPIOB
119+
self.porta, # OLATA 0 on all outputs; side effect of
120+
self.portb ]) # OLATB turning on R+G+B backlight LEDs.
121+
122+
# Switch to Bank 1 and disable sequential operation.
123+
# From this point forward, the register addresses do NOT match
124+
# the list immediately above. Instead, use the constants defined
125+
# at the start of the class. Also, the address register will no
126+
# longer increment automatically after this -- multi-byte
127+
# operations must be broken down into single-byte calls.
128+
self.i2c.bus.write_byte_data(
129+
self.i2c.address, self.MCP23017_IOCON_BANK0, 0b10100000)
85130

86131
self.displayshift = (self.LCD_CURSORMOVE |
87132
self.LCD_MOVERIGHT)
@@ -90,6 +135,7 @@ def __init__(self, busnum=-1, addr=0x20, debug=False):
90135
self.displaycontrol = (self.LCD_DISPLAYON |
91136
self.LCD_CURSOROFF |
92137
self.LCD_BLINKOFF)
138+
93139
self.write(0x33) # Init
94140
self.write(0x32) # Init
95141
self.write(0x28) # 2 line 5x8 matrix
@@ -112,27 +158,15 @@ def __init__(self, busnum=-1, addr=0x20, debug=False):
112158
0b00000010, 0b00010010, 0b00001010, 0b00011010,
113159
0b00000110, 0b00010110, 0b00001110, 0b00011110 )
114160

115-
# Low-level 4 bit output interface
161+
# Low-level 4-bit interface for LCD output. This doesn't actually
162+
# write data, just returns a byte array of the PORTB state over time.
163+
# Can concatenate the output of multiple calls (up to 8) for more
164+
# efficient batch write.
116165
def out4(self, bitmask, value):
117-
b = bitmask | self.flip[value >> 4] # Insert high 4 bits of data
118-
# Write initial !E state, data is sampled on rising strobe edge
119-
# Commented out, seems to be OK setting & strobing at same time
120-
# self.mcp.i2c.bus.write_byte_data(
121-
# self.mcp.i2c.address, self.mcp.MCP23017_OLATB, b)
122-
# Strobe high (enable)
123-
self.mcp.i2c.bus.write_byte_data(
124-
self.mcp.i2c.address, self.mcp.MCP23017_OLATB, b | 0b00100000)
125-
# There's no need for delay calls when strobing, as the limited
126-
# I2C throughput already ensures the strobe is held long enough.
127-
b = bitmask | self.flip[value & 0x0F] # Insert low 4 bits
128-
# This also does strobe low (!enable) for prior nybble
129-
self.mcp.i2c.bus.write_byte_data(
130-
self.mcp.i2c.address, self.mcp.MCP23017_OLATB, b)
131-
self.mcp.i2c.bus.write_byte_data(
132-
self.mcp.i2c.address, self.mcp.MCP23017_OLATB, b | 0b00100000)
133-
self.mcp.i2c.bus.write_byte_data(
134-
self.mcp.i2c.address, self.mcp.MCP23017_OLATB, b)
135-
return b # Last port state
166+
hi = bitmask | self.flip[value >> 4]
167+
lo = bitmask | self.flip[value & 0x0F]
168+
return [hi | 0b00100000, hi, lo | 0b00100000, lo]
169+
136170

137171
# The speed of LCD accesses is inherently limited by I2C through the
138172
# port expander. A 'well behaved program' is expected to poll the
@@ -141,82 +175,83 @@ def out4(self, bitmask, value):
141175
# can't even be twiddled that fast through I2C, so it's a safe bet
142176
# with these instructions to not waste time polling (which requires
143177
# several I2C transfers for reconfiguring the port direction).
144-
# I/O pins are set as inputs when a potentially time-consuming
178+
# The D7 pin is set as input when a potentially time-consuming
145179
# instruction has been issued (e.g. screen clear), as well as on
146180
# startup, and polling will then occur before more commands or data
147181
# are issued.
148182

149183
pollables = ( LCD_CLEARDISPLAY, LCD_RETURNHOME )
150184

151-
# Write 8-bit value to LCD
185+
# Write byte, list or string value to LCD
152186
def write(self, value, char_mode=False):
153187
""" Send command/data to LCD """
154188

155-
# The following code does not invoke the base class methods that
156-
# handle I/O exceptions. Instead, the underlying smbus calls are
157-
# invoked directly for expediency, the expectation being that any
158-
# I2C access or address type errors have already been identified
159-
# during initialization.
160-
161-
# The LCD control lines are all on MCP PORTB, so I2C byte ops
162-
# on that single port (rather than word ops on both PORTA and
163-
# PORTB together) are used here to save some bandwidth.
164-
# LCD pin RS = MCP pin 15 (PORTB7) Command/data
165-
# LCD pin RW = MCP pin 14 (PORTB6) Read/write
166-
# LCD pin E = MCP pin 13 (PORTB5) Strobe
167-
# LCD D4...D7 = MCP 12...9 (PORTB4...1) Data (see notes later)
168-
169-
# If I/O pins are in input state, poll LCD busy flag until clear.
170-
if self.mcp.direction & 0b0001111000000000 > 0:
171-
# Current PORTB pin state RS=0 RW=1
172-
a = ((self.mcp.outputvalue >> 8) & 0b00000001) | 0b01000000
173-
b = a | 0b00100000 # E=1
174-
self.mcp.i2c.bus.write_byte_data(
175-
self.mcp.i2c.address, self.mcp.MCP23017_OLATB, a)
189+
# If pin D7 is in input state, poll LCD busy flag until clear.
190+
if self.ddrb & 0b00010000:
191+
lo = (self.portb & 0b00000001) | 0b01000000
192+
hi = lo | 0b00100000 # E=1 (strobe)
193+
self.i2c.bus.write_byte_data(
194+
self.i2c.address, self.MCP23017_GPIOB, lo)
176195
while True:
177196
# Strobe high (enable)
178-
self.mcp.i2c.bus.write_byte_data(
179-
self.mcp.i2c.address, self.mcp.MCP23017_OLATB, b)
197+
self.i2c.bus.write_byte(self.i2c.address, hi)
180198
# First nybble contains busy state
181-
bits = self.mcp.i2c.bus.read_byte_data(
182-
self.mcp.i2c.address, self.mcp.MCP23017_GPIOB)
183-
# Strobe low (!enable)
184-
self.mcp.i2c.bus.write_byte_data(
185-
self.mcp.i2c.address, self.mcp.MCP23017_OLATB, a)
199+
bits = self.i2c.bus.read_byte(self.i2c.address)
200+
# Strobe low, high, low. Second nybble (A3) is ignored.
201+
self.i2c.bus.write_i2c_block_data(
202+
self.i2c.address, self.MCP23017_GPIOB, [lo, hi, lo])
186203
if (bits & 0b00000010) == 0: break # D7=0, not busy
187-
# Ignore second nybble
188-
self.mcp.i2c.bus.write_byte_data(
189-
self.mcp.i2c.address, self.mcp.MCP23017_OLATB, b)
190-
self.mcp.i2c.bus.write_byte_data(
191-
self.mcp.i2c.address, self.mcp.MCP23017_OLATB, a)
192-
193-
# Polling complete, change data pins to outputs
194-
self.mcp.direction &= 0b1110000111111111
195-
self.mcp.i2c.bus.write_byte_data(self.mcp.i2c.address,
196-
self.mcp.MCP23017_IODIRB, self.mcp.direction >> 8)
197-
198-
# Mask out data bits & RW from current OLATB value
199-
a = ((self.mcp.outputvalue >> 8) & 0b00000001)
200-
if char_mode: a |= 0b10000000 # RS = Command/data
201-
b = a
204+
self.portb = lo
205+
206+
# Polling complete, change D7 pin to output
207+
self.ddrb &= 0b11101111
208+
self.i2c.bus.write_byte_data(self.i2c.address,
209+
self.MCP23017_IODIRB, self.ddrb)
210+
211+
bitmask = self.portb & 0b00000001 # Mask out PORTB LCD control bits
212+
if char_mode: bitmask |= 0b10000000 # Set data bit if not a command
202213

203214
# If string or list, iterate through multiple write ops
204215
if isinstance(value, str):
205-
for v in value: b = self.out4(a, ord(v))
216+
last = len(value) - 1 # Last character in string
217+
data = [] # Start with blank list
218+
for i, v in enumerate(value): # For each character...
219+
# Append 4 bytes to list representing PORTB over time.
220+
# First the high 4 data bits with strobe (enable) set
221+
# and unset, then same with low 4 data bits (strobe 1/0).
222+
data.extend(self.out4(bitmask, ord(v)))
223+
# I2C block data write is limited to 32 bytes max.
224+
# If limit reached, write data so far and clear.
225+
# Also do this on last byte if not otherwise handled.
226+
if (len(data) >= 32) or (i == last):
227+
self.i2c.bus.write_i2c_block_data(
228+
self.i2c.address, self.MCP23017_GPIOB, data)
229+
self.portb = data[-1] # Save state of last byte out
230+
data = [] # Clear list for next iteration
206231
elif isinstance(value, list):
207-
for v in value: b = self.out4(a, v)
232+
# Same as above, but for list instead of string
233+
last = len(value) - 1
234+
data = []
235+
for i, v in enumerate(value):
236+
data.extend(self.out4(bitmask, v))
237+
if (len(data) >= 32) or (i == last):
238+
self.i2c.bus.write_i2c_block_data(
239+
self.i2c.address, self.MCP23017_GPIOB, data)
240+
self.portb = data[-1]
241+
data = []
208242
else:
209-
b = self.out4(a, value)
210-
211-
# Update mcp outputvalue state to reflect changes here
212-
self.mcp.outputvalue = (self.mcp.outputvalue & 0x00FF) | (b << 8)
213-
214-
# If a poll-worthy instruction was issued, reconfigure
215-
# data pins as inputs to indicate need for poll on next call.
243+
# Single byte
244+
data = self.out4(bitmask, value)
245+
self.i2c.bus.write_i2c_block_data(
246+
self.i2c.address, self.MCP23017_GPIOB, data)
247+
self.portb = data[-1]
248+
249+
# If a poll-worthy instruction was issued, reconfigure D7
250+
# pin as input to indicate need for polling on next call.
216251
if (not char_mode) and (value in self.pollables):
217-
self.mcp.direction |= 0b0001111000000000
218-
self.mcp.i2c.bus.write_byte_data(self.mcp.i2c.address,
219-
self.mcp.MCP23017_IODIRB, self.mcp.direction >> 8)
252+
self.ddrb |= 0b00010000
253+
self.i2c.bus.write_byte_data(self.i2c.address,
254+
self.MCP23017_IODIRB, self.ddrb)
220255

221256

222257
# ----------------------------------------------------------------------
@@ -343,16 +378,18 @@ def message(self, text):
343378

344379

345380
def backlight(self, color):
346-
n = ((self.mcp.outputvalue & 0b1111111000111111) |
347-
(((~color) & 0b111) << 6))
348-
# Direct smbus call so everything toggles together
349-
self.mcp.i2c.bus.write_word_data(
350-
self.mcp.i2c.address, self.mcp.MCP23017_OLATA, n)
351-
self.mcp.outputvalue = n
381+
c = ~color
382+
self.porta = (self.porta & 0b00111111) | ((c & 0b011) << 6)
383+
self.portb = (self.portb & 0b11111110) | ((c & 0b100) >> 2)
384+
# Has to be done as two writes because sequential operation is off.
385+
self.i2c.bus.write_byte_data(
386+
self.i2c.address, self.MCP23017_GPIOA, self.porta)
387+
self.i2c.bus.write_byte_data(
388+
self.i2c.address, self.MCP23017_GPIOB, self.portb)
352389

353390

354391
def buttonPressed(self, b):
355-
return not self.mcp.input(b) if 0 <= b <= self.LEFT else False
392+
return (self.i2c.readU8(self.MCP23017_GPIOA) >> b) & 1
356393

357394

358395
# ----------------------------------------------------------------------

0 commit comments

Comments
 (0)