Skip to content

Instantly share code, notes, and snippets.

@EarlGray
Last active December 30, 2021 15:31
Show Gist options
  • Save EarlGray/f6a0c7ddf470d56b12aebb131620de39 to your computer and use it in GitHub Desktop.
Save EarlGray/f6a0c7ddf470d56b12aebb131620de39 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
This is a driver for Gravity 16x2 LCD (single-color, not RGB) for Raspberry Pi.
It is tested on Raspberry Pi Zero W with
- i2c_arm,baudrate=100000
- i2c_arm,baudrate=400000
- dtoverlay=i2c-gpio,bus=3,i2c_gpio_delay_us=1
"""
from enum import IntFlag, IntEnum
import itertools
import time
import argparse
import smbus
class GravityBacklight:
REG_MODE1 = 0x00
REG_MODE2 = 0x01
REG_ONLY = 0x02
REG_OUTPUT = 0x08
def __init__(self, bus, light_addr=0x60):
self.bus = bus
self.addr = light_addr
def _cmd(self, reg, val):
self.bus.write_i2c_block_data(self.addr, reg, [val])
def init(self, brightness=0):
self._cmd(self.REG_MODE1, 0x00) # backlight init
self._cmd(self.REG_OUTPUT, 0xff) # PWM | GRPPWM
self._cmd(self.REG_MODE2, 0x20) # blinkiness something
self.set_brightness(brightness)
def set_brightness(self, brightness):
assert brightness in range(256)
self._cmd(self.REG_ONLY, brightness)
def off(self):
self.set_brightness(0)
class EntryMode(IntEnum):
DECR = 0x00
INCR = 0x02
class LCDLines(IntEnum):
ONE = 0x00
TWO = 0x08
class Font(IntEnum):
_5x8 = 0x00
_5x10 = 0x04
class ShiftOf(IntEnum):
CURSOR = 0x00
DISPLAY = 0x08
class ShiftTo(IntEnum):
LEFT = 0x00
RIGHT = 0x04
class AIP31086L:
"""
An AIP31068L driver
See: https://seeeddoc.github.io/Grove-LCD_RGB_Backlight/res/JHD1214Y_YG_1.0.pdf
"""
ROW_0_OFFSET = 0x00
ROW_1_OFFSET = 0x40
def __init__(self, bus, addr=0x3e):
self.addr = addr
self.bus = bus
def init(self, lines: LCDLines):
self._do_function_set(lines=lines, font=Font._5x8)
self._do_display_control(on=True, cursor=False)
self._do_clear_display()
self._do_entry_mode(shift=False)
def close(self):
self._do_clear_display()
self._do_display_control(on=False)
def _cmd(self, code, co=False):
# RS=0
cmd = (int(co) << 7)
self.bus.write_i2c_block_data(self.addr, cmd, [code])
def _set(self, value, co=False):
# RS=1
cmd = (int(co) << 7) | (1 << 6)
self.bus.write_i2c_block_data(self.addr, cmd, [value])
def _get(self, co=False):
# RS=1
return self.bus.read_byte(self.addr)
def _do_clear_display(self):
self._cmd(0x01)
time.sleep(0.0016)
def _do_cursor_home(self):
self._cmd(0x02)
time.sleep(0.0016)
def _do_entry_mode(self, shift: bool = True, mode: EntryMode = EntryMode.INCR):
self._cmd(0x04 | int(mode) | int(shift))
# time.sleep(39usec)
def _do_display_control(self, on=True, cursor=True, blink=False):
self._cmd(0x08 | (int(on) << 2) | (int(cursor) << 1) | int(blink))
# time.sleep(39usec)
def _do_set_shift(self, of=ShiftOf.DISPLAY, to=ShiftTo.RIGHT):
""" of='display'|'cursor', to='left'|'right' """
self._cmd(0x10 | int(of) | int(to))
# time.sleep(39usec)
def _do_function_set(self, lines=LCDLines.TWO, font=Font._5x8):
dl = 1
self._cmd(0x20 | (int(dl) << 4) | int(lines) | int(font))
# time.sleep(39usec)
def _do_cgram_addr(self, cgaddr):
self._cmd(0x40 | cgaddr)
def _do_ddram_addr(self, ddaddr):
self._cmd(0x80 | ddaddr)
class LCD:
def __init__(self, busno=1, addr=0x3e, light_addr=0x60):
bus = smbus.SMBus(bus=busno)
self.screen = AIP31086L(bus=bus, addr=addr)
self.screen.init(LCDLines.TWO)
self.light = GravityBacklight(bus=bus, light_addr=light_addr)
self.light.init(brightness=0x40)
def close(self):
self.screen.close()
self.light.off()
def clear(self):
self._do_clear_display()
self._do_cursor_home()
def cursor(self, on=True):
self.screen._do_display_control(on=True, cursor=on)
def cursor_forward(self):
self.screen._do_set_shift(ShiftOf.CURSOR, ShiftTo.RIGHT)
def cursor_back(self):
self.screen._do_set_shift(ShiftOf.CURSOR, ShiftTo.LEFT)
def cursor_home(self):
self.screen._do_cursor_home()
def scroll_forward(self):
self.screen._do_set_shift(ShiftOf.DISPLAY, ShiftTo.LEFT)
def scroll_back(self):
self.screen._do_set_shift(ShiftOf.DISPLAY, ShiftTo.RIGHT)
def __setitem__(self, key, value):
if isinstance(key, int):
if key == 0:
offset = AIP31086L.ROW_0_OFFSET
elif key == 1:
offset = AIP31086L.ROW_1_OFFSET
else:
raise ValueError('Only two lines supported')
for i, c in enumerate(itertools.islice(value, 40)):
if isinstance(c, str):
c = ord(c)
elif isinstance(c, int):
assert c in range(256)
self.screen._do_ddram_addr(offset+i)
self.screen._set(c)
else:
raise IndexError('Unknown key type %s' % type(key))
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
return False
args = argparse.ArgumentParser(description='Gravity LCD driver test program')
args.add_argument(
'-b', '--bus', metavar='BUS',
type=int, default=1,
help="i2c bus")
args.add_argument(
'-a', '--addr', metavar='LCDADDR',
type=int, default=0x3E,
help="i2c address of LCD")
args.add_argument(
'-l', '--light-addr', metavar='LIGHTADDR',
type=int, default=0x60,
help="i2c address of backlight")
args.add_argument(
'-t', '--timeout',
type=int, default=3,
help="display text for TIMEOUT seconds")
args.add_argument(
'-B', '--brightness',
type=int, default=0x40,
help="backlight brightness (0..255)")
args.add_argument(
'line1', help='Text in the first line')
args.add_argument(
'line2',
nargs='?', default="",
help='Text in the second line')
if __name__ == '__main__':
flags = args.parse_args()
with LCD(
busno=flags.bus,
addr=flags.addr,
light_addr=flags.light_addr
) as lcd:
lcd.light.set_brightness(flags.brightness)
lcd[0] = flags.line1
lcd[1] = flags.line2
time.sleep(flags.timeout)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment