1
1
#!/usr/bin/python
2
2
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.
4
8
# lrvic - https://github.com/lrvick/raspi-hd44780/blob/master/hd44780.py
5
9
# LiquidCrystal - https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/LiquidCrystal.cpp
6
10
7
- from Adafruit_MCP230xx import Adafruit_MCP230XX
11
+ from Adafruit_I2C import Adafruit_I2C
12
+
8
13
9
- class Adafruit_CharLCDPlate (Adafruit_MCP230XX ):
14
+ class Adafruit_CharLCDPlate (Adafruit_I2C ):
10
15
11
16
# ----------------------------------------------------------------------
12
17
# Constants
13
18
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
+
14
27
# Port expander input pin definitions
15
28
SELECT = 0
16
29
RIGHT = 1
@@ -65,23 +78,55 @@ class Adafruit_CharLCDPlate(Adafruit_MCP230XX):
65
78
66
79
def __init__ (self , busnum = - 1 , addr = 0x20 , debug = False ):
67
80
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 )
85
130
86
131
self .displayshift = (self .LCD_CURSORMOVE |
87
132
self .LCD_MOVERIGHT )
@@ -90,6 +135,7 @@ def __init__(self, busnum=-1, addr=0x20, debug=False):
90
135
self .displaycontrol = (self .LCD_DISPLAYON |
91
136
self .LCD_CURSOROFF |
92
137
self .LCD_BLINKOFF )
138
+
93
139
self .write (0x33 ) # Init
94
140
self .write (0x32 ) # Init
95
141
self .write (0x28 ) # 2 line 5x8 matrix
@@ -112,27 +158,15 @@ def __init__(self, busnum=-1, addr=0x20, debug=False):
112
158
0b00000010 , 0b00010010 , 0b00001010 , 0b00011010 ,
113
159
0b00000110 , 0b00010110 , 0b00001110 , 0b00011110 )
114
160
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.
116
165
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
+
136
170
137
171
# The speed of LCD accesses is inherently limited by I2C through the
138
172
# port expander. A 'well behaved program' is expected to poll the
@@ -141,82 +175,83 @@ def out4(self, bitmask, value):
141
175
# can't even be twiddled that fast through I2C, so it's a safe bet
142
176
# with these instructions to not waste time polling (which requires
143
177
# 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
145
179
# instruction has been issued (e.g. screen clear), as well as on
146
180
# startup, and polling will then occur before more commands or data
147
181
# are issued.
148
182
149
183
pollables = ( LCD_CLEARDISPLAY , LCD_RETURNHOME )
150
184
151
- # Write 8-bit value to LCD
185
+ # Write byte, list or string value to LCD
152
186
def write (self , value , char_mode = False ):
153
187
""" Send command/data to LCD """
154
188
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 )
176
195
while True :
177
196
# 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 )
180
198
# 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 ])
186
203
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
202
213
203
214
# If string or list, iterate through multiple write ops
204
215
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
206
231
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 = []
208
242
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.
216
251
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 )
220
255
221
256
222
257
# ----------------------------------------------------------------------
@@ -343,16 +378,18 @@ def message(self, text):
343
378
344
379
345
380
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 )
352
389
353
390
354
391
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
356
393
357
394
358
395
# ----------------------------------------------------------------------
0 commit comments