Skip to content

Commit fa95931

Browse files
committed
Add support for I2C/SPI backpack with MCP23008 and 74LS595. New Character_LCD_I2C and Character_LCD_SPI classes.
1 parent e1a939a commit fa95931

File tree

7 files changed

+494
-1
lines changed

7 files changed

+494
-1
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.mpy
2+

adafruit_character_lcd/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
from adafruit_character_lcd.character_lcd import Character_LCD
1+
from adafruit_character_lcd.character_lcd import Character_LCD, Character_LCD_I2C, Character_LCD_SPI
22
from adafruit_character_lcd.character_lcd_RGB import Character_LCD_RGB

adafruit_character_lcd/character_lcd.py

+146
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
"""
3838
import time
3939
import math
40+
4041
import digitalio
4142
from board import *
4243

44+
4345
# Commands
4446
LCD_CLEARDISPLAY = const(0x01)
4547
LCD_RETURNHOME = const(0x02)
@@ -81,6 +83,34 @@
8183
# Offset for up to 4 rows.
8284
LCD_ROW_OFFSETS = (0x00, 0x40, 0x14, 0x54)
8385

86+
# MCP23008 I2C backpack pin mapping from LCD logical pin to MCP23008 pin.
87+
_MCP23008_LCD_RS = const(1)
88+
_MCP23008_LCD_EN = const(2)
89+
_MCP23008_LCD_D4 = const(3)
90+
_MCP23008_LCD_D5 = const(4)
91+
_MCP23008_LCD_D6 = const(5)
92+
_MCP23008_LCD_D7 = const(6)
93+
_MCP23008_LCD_BACKLIGHT = const(7)
94+
95+
# 74LS595 SPI backpack pin mapping from LCD logical pin to 74LS595 pin.
96+
_74LS595_LCD_RS = const(1)
97+
_74LS595_LCD_EN = const(2)
98+
_74LS595_LCD_D4 = const(6)
99+
_74LS595_LCD_D5 = const(5)
100+
_74LS595_LCD_D6 = const(4)
101+
_74LS595_LCD_D7 = const(3)
102+
_74LS595_LCD_BACKLIGHT = const(7)
103+
104+
105+
def _set_bit(byte_value, position, val):
106+
# Given the specified byte_value set the bit at position to the provided
107+
# boolean value val and return the modified byte.
108+
if val:
109+
return byte_value | (1 << position)
110+
else:
111+
return byte_value & ~(1 << position)
112+
113+
84114
class Character_LCD(object):
85115
""" Interfaces with a character LCD
86116
:param ~digitalio.DigitalInOut rs: The reset data line
@@ -280,3 +310,119 @@ def create_char(self, location, pattern):
280310
self._write8(LCD_SETCGRAMADDR | (location << 3))
281311
for i in range(8):
282312
self._write8(pattern[i], char_mode=True)
313+
314+
315+
class Character_LCD_I2C(Character_LCD):
316+
"""Character LCD connected to I2C/SPI backpack using its I2C connection.
317+
This is a subclass of Character_LCD and implements all of the same
318+
functions and functionality.
319+
"""
320+
321+
def __init__(self, i2c, cols, lines):
322+
"""Initialize character LCD connectedto backpack using I2C connection
323+
on the specified I2C bus and of the specified number of columns and
324+
lines on the display.
325+
"""
326+
# Import the MCP23008 module here when the class is used
327+
# to keep memory usage low. If you attempt to import globally at the
328+
# top of this file you WILL run out of memory on the M0, even with
329+
# MPY files. The amount of code and classes implicitly imported
330+
# by all the SPI and I2C code is too high. Thus import on demand.
331+
import adafruit_character_lcd.mcp23008 as mcp23008
332+
self._mcp = mcp23008.MCP23008(i2c, address)
333+
# Setup pins for I2C backpack, see diagram:
334+
# https://learn.adafruit.com/assets/35681
335+
rs = self._mcp.DigitalInOut(_MCP23008_LCD_RS, self._mcp)
336+
en = self._mcp.DigitalInOut(_MCP23008_LCD_EN, self._mcp)
337+
d4 = self._mcp.DigitalInOut(_MCP23008_LCD_D4, self._mcp)
338+
d5 = self._mcp.DigitalInOut(_MCP23008_LCD_D5, self._mcp)
339+
d6 = self._mcp.DigitalInOut(_MCP23008_LCD_D6, self._mcp)
340+
d7 = self._mcp.DigitalInOut(_MCP23008_LCD_D7, self._mcp)
341+
backlight = self._mcp.DigitalInOut(_MCP23008_LCD_BACKLIGHT, self._mcp)
342+
# Call superclass initializer with MCP23008 pins.
343+
super().__init__(rs, en, d4, d5, d6, d7, cols, lines,
344+
backlight=backlight)
345+
346+
def _write8(self, value, char_mode=False):
347+
# Optimize a command write by changing all GPIO pins at once instead
348+
# of letting the super class try to set each one invidually (far too
349+
# slow with overhead of I2C communication).
350+
gpio = self._mcp.gpio
351+
# Make sure enable is low.
352+
gpio = _set_bit(gpio, _MCP23008_LCD_EN, False)
353+
# Set character/data bit. (charmode = False).
354+
gpio = _set_bit(gpio, _MCP23008_LCD_RS, char_mode)
355+
# Set upper 4 bits.
356+
gpio = _set_bit(gpio, _MCP23008_LCD_D4, ((value >> 4) & 1) > 0)
357+
gpio = _set_bit(gpio, _MCP23008_LCD_D5, ((value >> 5) & 1) > 0)
358+
gpio = _set_bit(gpio, _MCP23008_LCD_D6, ((value >> 6) & 1) > 0)
359+
gpio = _set_bit(gpio, _MCP23008_LCD_D7, ((value >> 7) & 1) > 0)
360+
self._mcp.gpio = gpio
361+
# Send command.
362+
self._pulse_enable()
363+
# Now repeat for lower 4 bits.
364+
gpio = self._mcp.gpio
365+
gpio = _set_bit(gpio, _MCP23008_LCD_EN, False)
366+
gpio = _set_bit(gpio, _MCP23008_LCD_RS, char_mode)
367+
gpio = _set_bit(gpio, _MCP23008_LCD_D4, (value & 1) > 0)
368+
gpio = _set_bit(gpio, _MCP23008_LCD_D5, ((value >> 1) & 1) > 0)
369+
gpio = _set_bit(gpio, _MCP23008_LCD_D6, ((value >> 2) & 1) > 0)
370+
gpio = _set_bit(gpio, _MCP23008_LCD_D7, ((value >> 3) & 1) > 0)
371+
self._mcp.gpio = gpio
372+
self._pulse_enable()
373+
374+
375+
class Character_LCD_SPI(Character_LCD):
376+
"""Character LCD connected to I2C/SPI backpack using its SPI connection.
377+
This is a subclass of Character_LCD and implements all of the same
378+
functions and functionality.
379+
"""
380+
381+
def __init__(self, spi, latch, cols, lines):
382+
"""Initialize character LCD connectedto backpack using SPI connection
383+
on the specified SPI bus and latch line with the specified number of
384+
columns and lines on the display.
385+
"""
386+
# See comment above on I2C class for why this is imported here:
387+
import adafruit_character_lcd.shift_reg_74ls595 as shift_reg_74ls595
388+
self._sr = shift_reg_74ls595.ShiftReg74LS595(spi, latch)
389+
# Setup pins for SPI backpack, see diagram:
390+
# https://learn.adafruit.com/assets/35681
391+
rs = self._sr.DigitalInOut(_74LS595_LCD_RS, self._sr)
392+
en = self._sr.DigitalInOut(_74LS595_LCD_EN, self._sr)
393+
d4 = self._sr.DigitalInOut(_74LS595_LCD_D4, self._sr)
394+
d5 = self._sr.DigitalInOut(_74LS595_LCD_D5, self._sr)
395+
d6 = self._sr.DigitalInOut(_74LS595_LCD_D6, self._sr)
396+
d7 = self._sr.DigitalInOut(_74LS595_LCD_D7, self._sr)
397+
backlight = self._sr.DigitalInOut(_74LS595_LCD_BACKLIGHT, self._sr)
398+
# Call superclass initializer with shift register pins.
399+
super().__init__(rs, en, d4, d5, d6, d7, cols, lines,
400+
backlight=backlight)
401+
402+
def _write8(self, value, char_mode=False):
403+
# Optimize a command write by changing all GPIO pins at once instead
404+
# of letting the super class try to set each one invidually (far too
405+
# slow with overhead of SPI communication).
406+
gpio = self._sr.gpio
407+
# Make sure enable is low.
408+
gpio = _set_bit(gpio, _74LS595_LCD_EN, False)
409+
# Set character/data bit. (charmode = False).
410+
gpio = _set_bit(gpio, _74LS595_LCD_RS, char_mode)
411+
# Set upper 4 bits.
412+
gpio = _set_bit(gpio, _74LS595_LCD_D4, ((value >> 4) & 1) > 0)
413+
gpio = _set_bit(gpio, _74LS595_LCD_D5, ((value >> 5) & 1) > 0)
414+
gpio = _set_bit(gpio, _74LS595_LCD_D6, ((value >> 6) & 1) > 0)
415+
gpio = _set_bit(gpio, _74LS595_LCD_D7, ((value >> 7) & 1) > 0)
416+
self._sr.gpio = gpio
417+
# Send command.
418+
self._pulse_enable()
419+
# Now repeat for lower 4 bits.
420+
gpio = self._sr.gpio
421+
gpio = _set_bit(gpio, _74LS595_LCD_EN, False)
422+
gpio = _set_bit(gpio, _74LS595_LCD_RS, char_mode)
423+
gpio = _set_bit(gpio, _74LS595_LCD_D4, (value & 1) > 0)
424+
gpio = _set_bit(gpio, _74LS595_LCD_D5, ((value >> 1) & 1) > 0)
425+
gpio = _set_bit(gpio, _74LS595_LCD_D6, ((value >> 2) & 1) > 0)
426+
gpio = _set_bit(gpio, _74LS595_LCD_D7, ((value >> 3) & 1) > 0)
427+
self._sr.gpio = gpio
428+
self._pulse_enable()

adafruit_character_lcd/mcp23008.py

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# MCP23008 I2C GPIO Extender Driver
2+
# Bare-bones driver for the MCP23008 driver, as used by the character LCD
3+
# backpack. This exposes the MCP2308 and its pins as standard CircuitPython
4+
# digitalio pins. Currently this is integrated in the character LCD class for
5+
# simplicity and reduction in dependent imports, but it could be broken out
6+
# into a standalone library later.
7+
# Author: Tony DiCola
8+
import digitalio
9+
10+
import adafruit_bus_device.i2c_device as i2c_device
11+
12+
13+
# Registers and other constants:
14+
_MCP23008_ADDRESS = const(0x20)
15+
_MCP23008_IODIR = const(0x00)
16+
_MCP23008_IPOL = const(0x01)
17+
_MCP23008_GPINTEN = const(0x02)
18+
_MCP23008_DEFVAL = const(0x03)
19+
_MCP23008_INTCON = const(0x04)
20+
_MCP23008_IOCON = const(0x05)
21+
_MCP23008_GPPU = const(0x06)
22+
_MCP23008_INTF = const(0x07)
23+
_MCP23008_INTCAP = const(0x08)
24+
_MCP23008_GPIO = const(0x09)
25+
_MCP23008_OLAT = const(0x0A)
26+
27+
28+
class MCP23008:
29+
30+
# Class-level buffer for reading and writing registers with the device.
31+
# This reduces memory allocations but makes the code non-reentrant/thread-
32+
# safe!
33+
_BUFFER = bytearray(2)
34+
35+
class DigitalInOut:
36+
"""Digital input/output of the MCP23008. The interface is exactly the
37+
same as the digitalio.DigitalInOut class (however the MCP23008 does not
38+
support pull-down resistors and an exception will be thrown
39+
attempting to set one).
40+
"""
41+
42+
def __init__(self, pin_number, mcp23008):
43+
"""Specify the pin number of the MCP23008 (0...7) and MCP23008
44+
instance.
45+
"""
46+
self._pin = pin_number
47+
self._mcp = mcp23008
48+
49+
def switch_to_output(value=False, **kwargs):
50+
self.direction = digitalio.Direction.OUTPUT
51+
self.value = value
52+
53+
def switch_to_input(self, pull=None, **kwargs):
54+
self.direction = digitalio.Direction.INPUT
55+
self.pull = pull
56+
57+
@property
58+
def value(self):
59+
gpio = self._mcp.gpio
60+
return bool(gpio & (1 << self._pin))
61+
62+
@value.setter
63+
def value(self, val):
64+
gpio = self._mcp.gpio
65+
if val:
66+
gpio |= (1 << self._pin)
67+
else:
68+
gpio &= ~(1 << self._pin)
69+
self._mcp.gpio = gpio
70+
71+
@property
72+
def direction(self):
73+
iodir = self._mcp._read_u8(_MCP23008_IODIR)
74+
if iodir & (1 << self._pin) > 0:
75+
return digitalio.Direction.INPUT
76+
else:
77+
return digitalio.Direction.OUTPUT
78+
79+
@direction.setter
80+
def direction(self, val):
81+
iodir = self._mcp._read_u8(_MCP23008_IODIR)
82+
if val == digitalio.Direction.INPUT:
83+
iodir |= (1 << self._pin)
84+
elif val == digitalio.Direction.OUTPUT:
85+
iodir &= ~(1 << self._pin)
86+
else:
87+
raise ValueError('Expected INPUT or OUTPUT direction!')
88+
self._mcp._write_u8(_MCP23008_IODIR, iodir)
89+
90+
@property
91+
def pull(self):
92+
gppu = self._mcp._read_u8(_MCP23008_GPPU)
93+
if gppu & (1 << self._pin) > 0:
94+
return digitalio.Pull.UP
95+
else:
96+
return None
97+
98+
@pull.setter
99+
def pull(self, val):
100+
gppu = self._mcp._read_u8(_MCP23008_GPPU)
101+
if val is None:
102+
gppu &= ~(1 << self._pin) # Disable pull-up
103+
elif val == digitalio.Pull.UP:
104+
gppu |= (1 << self._pin)
105+
elif val == digitalio.Pull.DOWN:
106+
raise ValueError('Pull-down resistors are not supported!')
107+
else:
108+
raise ValueError('Expected UP, DOWN, or None for pull state!')
109+
self._mcp._write_u8(_MCP23008_GPPU, gppu)
110+
111+
def __init__(self, i2c, address=_MCP23008_ADDRESS):
112+
"""Initialize MCP23008 instance on specified I2C bus and optionally
113+
at the specified I2C address.
114+
"""
115+
self._device = i2c_device.I2CDevice(i2c, address)
116+
# Reset device state to all pins as inputs (safest option).
117+
with self._device as device:
118+
# Write to MCP23008_IODIR register 0xFF followed by 9 zeros
119+
# for defaults of other registers.
120+
device.write('\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00')
121+
122+
def _read_u8(self, register):
123+
# Read an unsigned 8 bit value from the specified 8-bit register.
124+
with self._device as i2c:
125+
self._BUFFER[0] = register & 0xFF
126+
i2c.write(self._BUFFER, end=1, stop=False)
127+
i2c.readinto(self._BUFFER, end=1)
128+
return self._BUFFER[0]
129+
130+
def _write_u8(self, register, val):
131+
# Write an 8 bit value to the specified 8-bit register.
132+
with self._device as i2c:
133+
self._BUFFER[0] = register & 0xFF
134+
self._BUFFER[1] = val & 0xFF
135+
i2c.write(self._BUFFER)
136+
137+
@property
138+
def gpio(self):
139+
"""Get and set the raw GPIO output register. Each bit represents the
140+
output value of the associated pin (0 = low, 1 = high), assuming that
141+
pin has been configured as an output previously.
142+
"""
143+
return self._read_u8(_MCP23008_GPIO)
144+
145+
@gpio.setter
146+
def gpio(self, val):
147+
self._write_u8(_MCP23008_GPIO, val)

0 commit comments

Comments
 (0)