# This file converted from MicroPython to CircuitPython # by robjwells. Original copyright Waveshare. from time import sleep from adafruit_bus_device.i2c_device import I2CDevice from adafruit_register.i2c_struct import UnaryStruct from busio import I2C from microcontroller import Pin from rgb1602.colours import CSS_COLOURS class Constants: # Device I2C Addresses LCD_ADDRESS = 0x7C >> 1 RGB_ADDRESS = 0x3e >> 1 # fmt: off # CGRAM and DDRAM commands but also their memory addresses LCD_COMMAND_REG = 0x80 LCD_SETDDRAMADDR = 0x80 # 0b1_______ Display Data RAM LCD_SETCGRAMADDR = 0x40 # 0b_1______ Character Generator RAM LCD_DATA_REG = 0x40 # RGB registers REG_RED = 0x04 # 0b_1__ pwm2 REG_GREEN = 0x03 # 0b__11 pwm1 REG_BLUE = 0x02 # 0b__1_ pwm0 REG_MODE1 = 0x00 # 0b____ REG_MODE2 = 0x01 # 0b___1 REG_OUTPUT = 0x08 # 0b1___ # Commands -- bits of an 8-bit word LCD_CLEARDISPLAY = 0x01 # 0b_______1 LCD_RETURNHOME = 0x02 # 0b______1_ LCD_ENTRYMODESET = 0x04 # 0b_____1__ # Used with bits I/D SH: # I/D: 0x02 for entry left, 0x00 for entry right # SH: 0x01 for shift increment, 0x00 for decrement # See "flags for display entry mode" LCD_DISPLAYCONTROL = 0x08 # 0b____1___ # Used with bits DCB: # D: display on/off, # C: cursor on/off, # B: blink cursor on/off # These are listed below as "flags for display on/off control" LCD_CURSORSHIFT = 0x10 # 0b___1____ # Cursor or Display Shift # Uses bits S/C R/L - -: # S/C: 0x08 for screen or 0x00 for cursor # R/L: 0x04 for right or 0x00 for left LCD_FUNCTIONSET = 0x20 # 0b__1_____ # Sets number of display lines, and display font type. # The documentation doesn't mention 8/4 bit mode. # flags for display entry mode LCD_ENTRYRIGHT = 0x00 # 0b00 LCD_ENTRYLEFT = 0x02 # 0b10 LCD_ENTRYSHIFTINCREMENT = 0x01 # 0b01 LCD_ENTRYSHIFTDECREMENT = 0x00 # 0b00 # flags for display on/off control LCD_DISPLAYON = 0x04 # 0b1__ LCD_CURSORON = 0x02 # 0b_1_ LCD_BLINKON = 0x01 # 0b__1 LCD_DISPLAYOFF = 0x00 # 0b000 LCD_CURSOROFF = 0x00 # 0b000 LCD_BLINKOFF = 0x00 # 0b000 # flags for display/cursor shift LCD_DISPLAYMOVE = 0x08 # 0b1000 LCD_CURSORMOVE = 0x00 # 0b0000 LCD_MOVERIGHT = 0x04 # 0b0100 LCD_MOVELEFT = 0x00 # 0b0000 # flags for function set # I think the modes relate the to the number of bits # sent down the wire at a time, _not_ the colour # depth (as I thought originally). LCD_8BITMODE = 0x10 # 0b10000 LCD_4BITMODE = 0x00 # 0b00000 # Number of lines on the display LCD_2LINE = 0x08 # 0b01000 LCD_1LINE = 0x00 # 0b00000 LCD_5x8DOTS = 0x00 # 0b00000 # fmt: on class LCDControl: def __init__(self, i2c: I2CDevice): self.i2c_device = i2c # Required by UnaryStruct command_register = UnaryStruct(Constants.LCD_COMMAND_REG, " None: self.i2c_device = i2c REG_RED = UnaryStruct(Constants.REG_RED, " 0x20 (DMBLNK to 1, ie blinky mode) self._set_rgb_register("REG_MODE2", 0x20) self.set_white() def _command(self, cmd: int): assert 0 <= cmd <= 255, f"Command {cmd} out of range." self._lcd.command_register = cmd def _write_byte(self, data: int) -> None: assert 0 <= data <= 255, f"Command {data} out of range." self._lcd.data_register = data def _set_rgb_register(self, reg: str, data: int) -> None: assert reg in ( "REG_RED", "REG_BLUE", "REG_GREEN", "REG_MODE1", "REG_MODE2", "REG_OUTPUT", ), f"Register {reg} is not a known register." assert 0 <= data <= 255, f"Data {data} is out of range." setattr(self._rgb, reg, data) def _set_rgb_mode(self, mode: int, value: int) -> None: assert 0 <= value <= 0xFF, "Value not in range." if mode == 1: self._set_rgb_register("REG_MODE1", value) elif mode == 2: self._set_rgb_register("REG_MODE2", value) else: raise ValueError(f"Unknown mode: {repr(mode)}") def set_rgb(self, r: int, g: int, b: int): assert 0 <= r <= 255, f"Red value {r} out of range." assert 0 <= g <= 255, f"Green value {g} out of range." assert 0 <= b <= 255, f"Blue value {b} out of range." self._set_rgb_register("REG_RED", r) self._set_rgb_register("REG_GREEN", g) self._set_rgb_register("REG_BLUE", b) self.current_colour = (r, g, b) def position_cursor(self, *, col: int, row: int): assert ( 0 <= col < self.COLS ), f"Column {col} is out of bounds (max {self.COLS - 1})." assert ( 0 <= row < self.ROWS ), f"Row {row} is out of bounds (max {self.ROWS - 1})." if row == 0: col |= 0x80 else: col |= 0xC0 assert self._i2c.try_lock(), "Could not lock" self._i2c.writeto( Constants.LCD_ADDRESS, bytearray([Constants.LCD_SETDDRAMADDR, col]) ) self._i2c.unlock() def clear(self): self._command(Constants.LCD_CLEARDISPLAY) sleep(0.002) def write_bytes(self, arg: bytes): for byte in arg: self._write_byte(byte) def write_at_position(self, text: str | bytes, *, col: int, row: int) -> None: self.position_cursor(col=col, row=row) self.write_bytes(self._ensure_bytes(text)) @staticmethod def _ensure_bytes(s: str | bytes) -> bytes: if isinstance(s, bytes): return s # Not JIS X 0213 but close enough if you’re careful. return bytes(s, "jisx0213") def update( self, first_line: str | bytes, second_line: str | bytes | None = None ) -> None: first = self._ensure_bytes(first_line) self.clear() self.write_bytes(first[: self.COLS]) if second_line is not None: self.position_cursor(col=0, row=1) second = self._ensure_bytes(second_line) self.write_bytes(second[: self.COLS]) def set_white(self) -> None: self.set_css_colour("white") def set_css_colour(self, colour_name: str) -> None: self.set_rgb(*CSS_COLOURS[colour_name]) def set_css_color(self, color_name: str) -> None: self.set_css_colour(color_name) def set_backlight_power(self, on: bool) -> None: data = 0xFF if on else 0x00 self._set_rgb_register("REG_OUTPUT", data) @staticmethod def special_char(c: str) -> bytes: if c == "\\": raise ValueError("\\ (backslash) is not in the character set.") elif ord(c) < ord("}"): # Everything matches ASCII up to }, except for \ -> ¥. return c.encode("ascii") chars = { "→": b"\x7E", "←": b"\x7F", "•": b"\xA5", "☐": b"\xDB", "°": b"\xDF", "alpha": b"\xE0", "beta": b"\xE2", "epsilon": b"\xE3", "mu": b"\xE4", "sigma": b"\xE5", "rho": b"\xE6", "√": b"\xE8", "theta": b"\xF2", "omega": b"\xF4", "SIGMA": b"\xF6", "pi": b"\xF7", "÷": b"\xFD", "block": b"\xFF", } try: return chars[c] except KeyError: raise ValueError(f"Character {repr(c)} is not a registered special character.")