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