88
99class Adafruit_CharLCDPlate (Adafruit_MCP230XX ):
1010
11+ # ----------------------------------------------------------------------
12+ # Constants
13+
1114 # Port expander input pin definitions
1215 SELECT = 0
1316 RIGHT = 1
@@ -57,6 +60,9 @@ class Adafruit_CharLCDPlate(Adafruit_MCP230XX):
5760 LCD_MOVELEFT = 0x00
5861
5962
63+ # ----------------------------------------------------------------------
64+ # Constructor
65+
6066 def __init__ (self , busnum = - 1 , addr = 0x20 , debug = False ):
6167
6268 self .mcp = Adafruit_MCP230XX (addr , 16 , busnum , debug )
@@ -77,24 +83,25 @@ def __init__(self, busnum=-1, addr=0x20, debug=False):
7783 # Init control lines, backlight on (white)
7884 self .mcp .outputAll (0 )
7985
80- self .displaycontrol = (self .LCD_DISPLAYON |
81- self .LCD_CURSOROFF |
82- self .LCD_BLINKOFF )
83- self .displaymode = (self .LCD_ENTRYLEFT |
84- self .LCD_ENTRYSHIFTDECREMENT )
85- self .displayshift = (self .LCD_CURSORMOVE |
86- self .LCD_MOVERIGHT )
87-
88- # self.write4(0x20) # Select 4-bit interface
89- self .write4 (0x33 ) # Init
90- self .write4 (0x32 ) # Init
91- self .write4 (0x28 ) # 2 line 5x8 matrix
92- self .write4 (self .LCD_CLEARDISPLAY )
93- self .write4 (self .LCD_CURSORSHIFT | self .displayshift )
94- self .write4 (self .LCD_ENTRYMODESET | self .displaymode )
95- self .write4 (self .LCD_DISPLAYCONTROL | self .displaycontrol )
96- self .write4 (self .LCD_RETURNHOME )
97-
86+ self .displayshift = (self .LCD_CURSORMOVE |
87+ self .LCD_MOVERIGHT )
88+ self .displaymode = (self .LCD_ENTRYLEFT |
89+ self .LCD_ENTRYSHIFTDECREMENT )
90+ self .displaycontrol = (self .LCD_DISPLAYON |
91+ self .LCD_CURSOROFF |
92+ self .LCD_BLINKOFF )
93+ self .write (0x33 ) # Init
94+ self .write (0x32 ) # Init
95+ self .write (0x28 ) # 2 line 5x8 matrix
96+ self .write (self .LCD_CLEARDISPLAY )
97+ self .write (self .LCD_CURSORSHIFT | self .displayshift )
98+ self .write (self .LCD_ENTRYMODESET | self .displaymode )
99+ self .write (self .LCD_DISPLAYCONTROL | self .displaycontrol )
100+ self .write (self .LCD_RETURNHOME )
101+
102+
103+ # ----------------------------------------------------------------------
104+ # Write operations
98105
99106 # The LCD data pins (D4-D7) connect to MCP pins 12-9 (PORTB4-1), in
100107 # that order. Because this sequence is 'reversed,' a direct shift
@@ -111,10 +118,12 @@ def out4(self, bitmask, value):
111118 # Write initial !E state, data is sampled on rising strobe edge
112119 self .mcp .i2c .bus .write_byte_data (
113120 self .mcp .i2c .address , self .mcp .MCP23017_OLATB , b )
114- # Strobe high
121+ # Strobe high (enable)
115122 self .mcp .i2c .bus .write_byte_data (
116123 self .mcp .i2c .address , self .mcp .MCP23017_OLATB , b | 0b00100000 )
117- # Strobe low
124+ # There's no need for delay calls when strobing, as the limited
125+ # I2C throughput already ensures the strobe is held long enough.
126+ # Strobe low (!enable)
118127 self .mcp .i2c .bus .write_byte_data (
119128 self .mcp .i2c .address , self .mcp .MCP23017_OLATB , b )
120129 b = bitmask | self .flip [value & 0x0F ] # Insert low 4 bits
@@ -124,11 +133,24 @@ def out4(self, bitmask, value):
124133 self .mcp .i2c .address , self .mcp .MCP23017_OLATB , b | 0b00100000 )
125134 self .mcp .i2c .bus .write_byte_data (
126135 self .mcp .i2c .address , self .mcp .MCP23017_OLATB , b )
127- return b
128-
129-
130- # Write 8-bit value to LCD over 4-bit interface
131- def write4 (self , value , char_mode = False ):
136+ return b # Last port state
137+
138+ # The speed of LCD accesses is inherently limited by I2C through the
139+ # port expander. A 'well behaved program' is expected to poll the
140+ # LCD to know that a prior instruction completed. But the timing of
141+ # most instructions is a known uniform 37 mS. The enable strobe
142+ # can't even be twiddled that fast through I2C, so it's a safe bet
143+ # with these instructions to not waste time polling (which requires
144+ # several I2C transfers for reconfiguring the port direction).
145+ # 'pollflag' is set when a potentially time-consuming instruction
146+ # has been issued (e.g. screen clear), as well as on startup, and
147+ # polling will then occur before more commands or data are issued.
148+
149+ pollables = ( LCD_CLEARDISPLAY , LCD_RETURNHOME )
150+ pollflag = True
151+
152+ # Write 8-bit value to LCD
153+ def write (self , value , char_mode = False ):
132154 """ Send command/data to LCD """
133155
134156 # The following code does not invoke the base class methods that
@@ -145,176 +167,180 @@ def write4(self, value, char_mode=False):
145167 # LCD pin E = MCP pin 13 (PORTB5) Strobe
146168 # LCD D4...D7 = MCP 12...9 (PORTB4...1) Data (see notes later)
147169
148- # Poll LCD busy state until clear. Data pins are inputs
149- # by default, so no need to reconfigure I/O direction yet.
150-
151- # Current PORTB pin state RS=0 RW=1
152- a = ((self .mcp .outputvalue >> 8 ) & 0b00000001 ) | 0b01000000
153- b = a | 0b00100000 # E=1
154- # Configure MCP lines for polling. Will restore later.
155- self .mcp .i2c .bus .write_byte_data (
156- self .mcp .i2c .address , self .mcp .MCP23017_OLATB , a )
157- while True :
158- # Strobe high (enable)
159- self .mcp .i2c .bus .write_byte_data (
160- self .mcp .i2c .address , self .mcp .MCP23017_OLATB , b )
161- # There's no need for delay calls when strobing, as the
162- # limited I2C throughput already ensures the strobe is
163- # held long enough.
164- # First nybble contains busy state
165- bits = self .mcp .i2c .bus .read_byte_data (
166- self .mcp .i2c .address , self .mcp .MCP23017_GPIOB )
167- # Strobe low (!enable)
170+ # If pollflag is set, poll LCD busy state until clear. Data
171+ # pins were previously set as inputs, no need to reconfigure
172+ # I/O yet.
173+ if self .pollflag :
174+ # Current PORTB pin state RS=0 RW=1
175+ a = ((self .mcp .outputvalue >> 8 ) & 0b00000001 ) | 0b01000000
176+ b = a | 0b00100000 # E=1
168177 self .mcp .i2c .bus .write_byte_data (
169178 self .mcp .i2c .address , self .mcp .MCP23017_OLATB , a )
170- if (bits & 0b00000010 ) == 0 : break # D7=0, not busy
171- # Ignore second nybble
179+ while True :
180+ # Strobe high (enable)
181+ self .mcp .i2c .bus .write_byte_data (
182+ self .mcp .i2c .address , self .mcp .MCP23017_OLATB , b )
183+ # First nybble contains busy state
184+ bits = self .mcp .i2c .bus .read_byte_data (
185+ self .mcp .i2c .address , self .mcp .MCP23017_GPIOB )
186+ # Strobe low (!enable)
187+ self .mcp .i2c .bus .write_byte_data (
188+ self .mcp .i2c .address , self .mcp .MCP23017_OLATB , a )
189+ if (bits & 0b00000010 ) == 0 : break # D7=0, not busy
190+ # Ignore second nybble
191+ self .mcp .i2c .bus .write_byte_data (
192+ self .mcp .i2c .address , self .mcp .MCP23017_OLATB , b )
193+ self .mcp .i2c .bus .write_byte_data (
194+ self .mcp .i2c .address , self .mcp .MCP23017_OLATB , a )
195+
196+ # Polling complete, change data pins to outputs
197+ save = self .mcp .direction >> 8 # PORTB
172198 self .mcp .i2c .bus .write_byte_data (
173- self .mcp .i2c .address , self .mcp .MCP23017_OLATB , b )
174- self .mcp .i2c .bus .write_byte_data (
175- self .mcp .i2c .address , self .mcp .MCP23017_OLATB , a )
176-
177- # Change data pins to outputs temporarily
178- save = self .mcp .direction >> 8 # PORTB
179- self .mcp .i2c .bus .write_byte_data (
180- self .mcp .i2c .address , self .mcp .MCP23017_IODIRB , save & 0b11100001 )
199+ self .mcp .i2c .address , self .mcp .MCP23017_IODIRB , save & 0b11100001 )
181200
182- a &= 0b00000001 # Mask out data bits & RW from current OLATB value
201+ # Mask out data bits & RW from current OLATB value
202+ a = ((self .mcp .outputvalue >> 8 ) & 0b00000001 )
183203 if char_mode : a |= 0b10000000 # RS = Command/data
184204
185- # If string or list, iterate through a write iteration
205+ # If string or list, iterate through multiple write ops
186206 if isinstance (value , str ):
187207 for v in value : b = self .out4 (a , ord (v ))
188208 elif isinstance (value , list ):
189209 for v in value : b = self .out4 (a , v )
190210 else :
191211 b = self .out4 (a , value )
192212
193- # Change data pins back to inputs
194- self .mcp .i2c .bus .write_byte_data (
195- self .mcp .i2c .address , self .mcp .MCP23017_IODIRB , save )
196- # And update mcp outputvalue state to reflect changes here
197- self .mcp .outputvalue = (self .mcp .outputvalue & 0x00FF ) | (b << 8 )
213+ # If a poll-worthy instruction was issued, reconfigure
214+ # data pins as inputs and set flag to poll on next call.
215+ if (not char_mode ) and (value in self .pollables ):
216+ self .mcp .i2c .bus .write_byte_data (
217+ self .mcp .i2c .address , self .mcp .MCP23017_IODIRB , save )
218+ # Update mcp outputvalue state to reflect changes here
219+ self .mcp .outputvalue = (self .mcp .outputvalue & 0x00FF ) | (b << 8 )
220+ self .pollflag = True
198221
199222
223+ # ----------------------------------------------------------------------
224+ # Utility methods
225+
200226 def begin (self , cols , lines ):
201227 self .currline = 0
202228 self .numlines = lines
203229 self .clear ()
204230
205231
206232 def clear (self ):
207- self .write4 (self .LCD_CLEARDISPLAY )
233+ self .write (self .LCD_CLEARDISPLAY )
208234
209235
210236 def home (self ):
211- self .write4 (self .LCD_RETURNHOME )
237+ self .write (self .LCD_RETURNHOME )
212238
213239
214240 row_offsets = ( 0x00 , 0x40 , 0x14 , 0x54 )
215241 def setCursor (self , col , row ):
216242 if row > self .numlines : row = self .numlines - 1
217243 elif row < 0 : row = 0
218- self .write4 (self .LCD_SETDDRAMADDR | (col + self .row_offsets [row ]))
244+ self .write (self .LCD_SETDDRAMADDR | (col + self .row_offsets [row ]))
219245
220246
221247 def display (self ):
222248 """ Turn the display on (quickly) """
223249 self .displaycontrol |= self .LCD_DISPLAYON
224- self .write4 (self .LCD_DISPLAYCONTROL | self .displaycontrol )
250+ self .write (self .LCD_DISPLAYCONTROL | self .displaycontrol )
225251
226252
227253 def noDisplay (self ):
228254 """ Turn the display off (quickly) """
229255 self .displaycontrol &= ~ self .LCD_DISPLAYON
230- self .write4 (self .LCD_DISPLAYCONTROL | self .displaycontrol )
256+ self .write (self .LCD_DISPLAYCONTROL | self .displaycontrol )
231257
232258
233259 def cursor (self ):
234260 """ Underline cursor on """
235261 self .displaycontrol |= self .LCD_CURSORON
236- self .write4 (self .LCD_DISPLAYCONTROL | self .displaycontrol )
262+ self .write (self .LCD_DISPLAYCONTROL | self .displaycontrol )
237263
238264
239265 def noCursor (self ):
240266 """ Underline cursor off """
241267 self .displaycontrol &= ~ self .LCD_CURSORON
242- self .write4 (self .LCD_DISPLAYCONTROL | self .displaycontrol )
268+ self .write (self .LCD_DISPLAYCONTROL | self .displaycontrol )
243269
244270
245271 def ToggleCursor (self ):
246272 """ Toggles the underline cursor On/Off """
247273 self .displaycontrol ^= self .LCD_CURSORON
248- self .write4 (self .LCD_DISPLAYCONTROL | self .displaycontrol )
274+ self .write (self .LCD_DISPLAYCONTROL | self .displaycontrol )
249275
250276
251277 def blink (self ):
252278 """ Turn on the blinking cursor """
253279 self .displaycontrol |= self .LCD_BLINKON
254- self .write4 (self .LCD_DISPLAYCONTROL | self .displaycontrol )
280+ self .write (self .LCD_DISPLAYCONTROL | self .displaycontrol )
255281
256282
257283 def noBlink (self ):
258284 """ Turn off the blinking cursor """
259285 self .displaycontrol &= ~ self .LCD_BLINKON
260- self .write4 (self .LCD_DISPLAYCONTROL | self .displaycontrol )
286+ self .write (self .LCD_DISPLAYCONTROL | self .displaycontrol )
261287
262288
263289 def ToggleBlink (self ):
264290 """ Toggles the blinking cursor """
265291 self .displaycontrol ^= self .LCD_BLINKON
266- self .write4 (self .LCD_DISPLAYCONTROL | self .displaycontrol )
292+ self .write (self .LCD_DISPLAYCONTROL | self .displaycontrol )
267293
268294
269295 def DisplayLeft (self ):
270296 """ These commands scroll the display without changing the RAM """
271297 self .displayshift = self .LCD_DISPLAYMODE | self .LCD_MOVELEFT
272- self .write4 (self .LCD_CURSORSHIFT | self .displayshift )
298+ self .write (self .LCD_CURSORSHIFT | self .displayshift )
273299
274300
275301 def scrollDisplayRight (self ):
276302 """ These commands scroll the display without changing the RAM """
277303 self .displayshift = self .LCD_DISPLAYMOVE | self .LCD_MOVERIGHT
278- self .write4 (self .LCD_CURSORSHIFT | self .displayshift )
304+ self .write (self .LCD_CURSORSHIFT | self .displayshift )
279305
280306
281307 def leftToRight (self ):
282308 """ This is for text that flows left to right """
283309 self .displaymode |= self .LCD_ENTRYLEFT
284- self .write4 (self .LCD_ENTRYMODESET | self .displaymode )
310+ self .write (self .LCD_ENTRYMODESET | self .displaymode )
285311
286312
287313 def rightToLeft (self ):
288314 """ This is for text that flows right to left """
289315 self .displaymode &= ~ self .LCD_ENTRYLEFT
290- self .write4 (self .LCD_ENTRYMODESET | self .displaymode )
316+ self .write (self .LCD_ENTRYMODESET | self .displaymode )
291317
292318
293319 def autoscroll (self ):
294320 """ This will 'right justify' text from the cursor """
295321 self .displaymode |= self .LCD_ENTRYSHIFTINCREMENT
296- self .write4 (self .LCD_ENTRYMODESET | self .displaymode )
322+ self .write (self .LCD_ENTRYMODESET | self .displaymode )
297323
298324
299325 def noAutoscroll (self ):
300326 """ This will 'left justify' text from the cursor """
301327 self .displaymode &= ~ self .LCD_ENTRYSHIFTINCREMENT
302- self .write4 (self .LCD_ENTRYMODESET | self .displaymode )
328+ self .write (self .LCD_ENTRYMODESET | self .displaymode )
303329
304330
305331 def createChar (self , location , bitmap ):
306- self .write4 (self .LCD_SETCGRAMADDR | ((location & 7 ) << 3 ))
307- self .write4 (bitmap , True )
308- self .write4 (self .LCD_SETDDRAMADDR )
332+ self .write (self .LCD_SETCGRAMADDR | ((location & 7 ) << 3 ))
333+ self .write (bitmap , True )
334+ self .write (self .LCD_SETDDRAMADDR )
309335
310336
311337 def message (self , text ):
312338 """ Send string to LCD. Newline wraps to second line"""
313- lines = text .split ('\n ' ) # Split at newline(s)
314- for line in lines : # Render each substring...
315- self .write4 (line , True )
316- if len (lines ) > 1 : # If newline(s),
317- self .write4 (0xC0 ) # set DDRAM address to second line
339+ lines = text .split ('\n ' ) # Split at newline(s)
340+ for line in lines : # Render each substring...
341+ self .write (line , True )
342+ if len (lines ) > 1 : # If newline(s),
343+ self .write (0xC0 ) # set DDRAM address to second line
318344
319345
320346 def backlight (self , color ):
@@ -330,6 +356,9 @@ def buttonPressed(self, b):
330356 return not self .mcp .input (b ) if 0 <= b <= self .LEFT else False
331357
332358
359+ # ----------------------------------------------------------------------
360+ # Test code
361+
333362if __name__ == '__main__' :
334363
335364 from time import sleep
0 commit comments