kico-r4/lib/rgb1602/display.py

308 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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, "<B")
data_register = UnaryStruct(Constants.LCD_DATA_REG, "<B")
class RGBControl:
def __init__(self, i2c: I2CDevice) -> None:
self.i2c_device = i2c
REG_RED = UnaryStruct(Constants.REG_RED, "<B")
REG_GREEN = UnaryStruct(Constants.REG_GREEN, "<B")
REG_BLUE = UnaryStruct(Constants.REG_BLUE, "<B")
REG_MODE1 = UnaryStruct(Constants.REG_MODE1, "<B")
REG_MODE2 = UnaryStruct(Constants.REG_MODE2, "<B")
REG_OUTPUT = UnaryStruct(Constants.REG_OUTPUT, "<B")
class Screen:
# Set dimensions as class variables.
# Hint is in the module name (LCD1602)!
COLS = 16
ROWS = 2
current_colour: tuple[int, int, int]
_i2c: I2C
_lcd: LCDControl
_rgb: RGBControl
def __init__(self, i2c_bus: I2C):
self._i2c = i2c_bus
self._lcd = LCDControl(I2CDevice(self._i2c, Constants.LCD_ADDRESS))
self._rgb = RGBControl(I2CDevice(self._i2c, Constants.RGB_ADDRESS))
self._reset_display()
def _reset_display(self):
"""Initialise the display to its standard settings.
It is unclear to me (rjw) why some of these are necessary, as
there seems to be no difference in removing some of the below.
*However* I have not removed what remains because it wasn't
*obviously unnecessary*, unlike the now-removed lines.
"""
# Send function set command sequence
show_function = (
Constants.LCD_4BITMODE | Constants.LCD_2LINE | Constants.LCD_5x8DOTS
)
self._command(Constants.LCD_FUNCTIONSET | show_function)
sleep(0.05)
# turn the display on with no cursor or blinking default
show_control = (
Constants.LCD_DISPLAYON | Constants.LCD_CURSOROFF | Constants.LCD_BLINKOFF
)
self._command(Constants.LCD_DISPLAYCONTROL | show_control)
self.clear()
# Initialize to default text direction (for romance languages)
self._showmode = Constants.LCD_ENTRYLEFT | Constants.LCD_ENTRYSHIFTDECREMENT
self._command(Constants.LCD_ENTRYMODESET | self._showmode)
# backlight init
self._set_rgb_register("REG_MODE1", 0)
# set LEDs controllable by both PWM and GRPPWM registers
self._set_rgb_register("REG_OUTPUT", 0xFF)
# set MODE2 values
# 0010 0000 -> 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 youre 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.")