Adafruit_CircuitPython_Trellis/adafruit_trellis.py
2025-05-15 17:45:39 +00:00

295 lines
9.2 KiB
Python

# SPDX-FileCopyrightText: 2018 ladyada for Adafruit Industries
# SPDX-FileCopyrightText: 2018 Michael Schroeder (sommersoft)
#
# SPDX-License-Identifier: MIT
# This is a library for the Adafruit Trellis w/HT16K33
#
# Designed specifically to work with the Adafruit Trellis
# ----> https://www.adafruit.com/products/1616
# ----> https://www.adafruit.com/products/1611
#
# These displays use I2C to communicate, 2 pins are required to
# interface
# Adafruit invests time and resources providing this open source code,
# please support Adafruit and open-source hardware by purchasing
# products from Adafruit!
#
# Also utilized functions from the CircuitPython HT16K33 library
# written by Radomir Dopieralski & Tony DiCola for Adafruit Industries
# https://github.com/adafruit/Adafruit_CircuitPython_HT16K33
"""
`adafruit_trellis` - Adafruit Trellis Monochrome 4x4 LED Backlit Keypad
=========================================================================
CircuitPython library to support Adafruit's Trellis Keypad.
* Author(s): Limor Fried, Radomir Dopieralski, Tony DiCola,
Scott Shawcroft, and Michael Schroeder
Implementation Notes
--------------------
**Hardware:**
* Adafruit `Trellis Monochrome 4x4 LED Backlit Keypad
<https://www.adafruit.com/product/1616>`_ (Product ID: 1616)
**Software and Dependencies:**
* Adafruit CircuitPython firmware (2.2.0+) for the ESP8622 and M0-based boards:
https://github.com/adafruit/circuitpython/releases
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Trellis.git"
from adafruit_bus_device import i2c_device
from micropython import const
try:
from typing import List, Optional, Tuple
from busio import I2C
from typing_extensions import Literal
except ImportError:
pass
# HT16K33 Command Contstants
_HT16K33_OSCILATOR_ON = const(0x21)
_HT16K33_BLINK_CMD = const(0x80)
_HT16K33_BLINK_DISPLAYON = const(0x01)
_HT16K33_CMD_BRIGHTNESS = const(0xE0)
_HT16K33_KEY_READ_CMD = const(0x40)
# LED Lookup Table
ledLUT = (
0x3A,
0x37,
0x35,
0x34,
0x28,
0x29,
0x23,
0x24,
0x16,
0x1B,
0x11,
0x10,
0x0E,
0x0D,
0x0C,
0x02,
)
# Button Loookup Table
buttonLUT = (
0x07,
0x04,
0x02,
0x22,
0x05,
0x06,
0x00,
0x01,
0x03,
0x10,
0x30,
0x21,
0x13,
0x12,
0x11,
0x31,
)
class TrellisLEDs:
def __init__(self, trellis_obj: "Trellis") -> None:
self._parent = trellis_obj
def __getitem__(self, x: int) -> bool:
if 0 < x >= self._parent._num_leds:
raise ValueError(("LED number must be between 0 -", self._parent._num_leds - 1))
led = ledLUT[x % 16] >> 4
mask = 1 << (ledLUT[x % 16] & 0x0F)
return bool(
(
(
self._parent._led_buffer[x // 16][(led * 2) + 1]
| self._parent._led_buffer[x // 16][(led * 2) + 2] << 8
)
& mask
)
> 0
)
def __setitem__(self, x: int, value: bool) -> None:
if 0 < x >= self._parent._num_leds:
raise ValueError(("LED number must be between 0 -", self._parent._num_leds - 1))
led = ledLUT[x % 16] >> 4
mask = 1 << (ledLUT[x % 16] & 0x0F)
if value:
self._parent._led_buffer[x // 16][(led * 2) + 1] |= mask & 0xFF
self._parent._led_buffer[x // 16][(led * 2) + 2] |= mask >> 8
elif not value:
self._parent._led_buffer[x // 16][(led * 2) + 1] &= ~mask
self._parent._led_buffer[x // 16][(led * 2) + 2] &= ~mask >> 8
else:
raise ValueError("LED value must be True or False")
if self._parent._auto_show:
self._parent.show()
def fill(self, on: bool) -> None:
fill = 0xFF if on else 0x00
for buff in range(len(self._parent._i2c_devices)):
for i in range(1, 17):
self._parent._led_buffer[buff][i] = fill
if self._parent._auto_show:
self._parent.show()
class Trellis:
"""
Driver base for a single Trellis Board
:param ~busio.I2C i2c: The `busio.I2C` object to use. This is the only required parameter
when using a single Trellis board.
:param list addresses: The I2C address(es) of the Trellis board(s) you're using. Defaults
to ``[0x70]`` which is the default address for Trellis boards. See
Trellis product guide for using different/multiple I2C addresses.
https://learn.adafruit.com/adafruit-trellis-diy-open-source-led-keypad
.. literalinclude:: ../examples/trellis_simpletest.py
:caption: Usage Example
:linenos:
"""
def __init__(self, i2c: I2C, addresses: Optional[List[int]] = None) -> None:
if addresses is None:
addresses = [0x70]
self._i2c_devices = []
self._led_buffer = []
self._buttons = []
for i2c_address in addresses:
self._i2c_devices.append(i2c_device.I2CDevice(i2c, i2c_address))
self._led_buffer.append(bytearray(17))
self._buttons.append([bytearray(6), bytearray(6)])
self._num_leds = len(self._i2c_devices) * 16
self._temp = bytearray(1)
self._blink_rate = None
self._brightness = None
self._auto_show = True
self.led = TrellisLEDs(self)
"""
The LED object used to interact with Trellis LEDs.
- ``trellis.led[x]`` returns the current LED status of LED ``x`` (True/False)
- ``trellis.led[x] = True`` turns the LED at ``x`` on
- ``trellis.led[x] = False`` turns the LED at ``x`` off
- ``trellis.led.fill(bool)`` turns every LED on (True) or off (False)
"""
self.led.fill(False)
self._write_cmd(_HT16K33_OSCILATOR_ON)
self.blink_rate = 0
self.brightness = 15
def _write_cmd(self, byte: int) -> None:
self._temp[0] = byte
for device in self._i2c_devices:
with device:
device.write(self._temp)
@property
def blink_rate(self) -> Literal[0, 1, 2, 3]:
"""
The current blink rate as an integer range 0-3.
"""
return self._blink_rate
@blink_rate.setter
def blink_rate(self, rate: Literal[0, 1, 2, 3]) -> None:
if not 0 <= rate <= 3:
raise ValueError("Blink rate must be an integer in the range: 0-3")
rate = rate & 0x03
self._blink_rate = rate
self._write_cmd(_HT16K33_BLINK_CMD | _HT16K33_BLINK_DISPLAYON | rate << 1)
@property
def brightness(self) -> int:
"""
The current brightness as an integer range 0-15.
"""
return self._brightness
@brightness.setter
def brightness(self, brightness: int) -> None:
if not 0 <= brightness <= 15:
raise ValueError("Brightness must be an integer in the range: 0-15")
brightness = brightness & 0x0F
self._brightness = brightness
self._write_cmd(_HT16K33_CMD_BRIGHTNESS | brightness)
def show(self) -> None:
"""Refresh the LED buffer and show the changes."""
pos = 0
for device in self._i2c_devices:
temp_led_buffer = bytearray(self._led_buffer[pos])
with device:
device.write(temp_led_buffer)
pos += 1
@property
def auto_show(self) -> bool:
"""
Current state of sending LED updates directly the Trellis board(s). ``True``
or ``False``.
"""
return self._auto_show
@auto_show.setter
def auto_show(self, value: bool) -> None:
if not isinstance(value, bool):
raise ValueError("Auto show value must be True or False")
self._auto_show = value
def read_buttons(self) -> Tuple[List[int], List[int]]:
"""
Read the button matrix register on the Trellis board(s). Returns two
lists: 1 for new button presses, 1 for button relases.
"""
for i in range(len(self._buttons)):
self._buttons[i][0] = bytearray(self._buttons[i][1])
self._write_cmd(_HT16K33_KEY_READ_CMD)
pos = 0
for device in self._i2c_devices:
with device:
device.readinto(self._buttons[pos][1])
pos += 1
pressed = []
released = []
for i in range(self._num_leds):
if self._just_pressed(i):
pressed.append(i)
elif self._just_released(i):
released.append(i)
return pressed, released
def _is_pressed(self, button: int) -> bool:
mask = 1 << (buttonLUT[button % 16] & 0x0F)
return self._buttons[button // 16][1][(buttonLUT[button % 16] >> 4)] & mask
def _was_pressed(self, button: int) -> bool:
mask = 1 << (buttonLUT[button % 16] & 0x0F)
return self._buttons[button // 16][0][(buttonLUT[button % 16] >> 4)] & mask
def _just_pressed(self, button: int) -> bool:
return self._is_pressed(button) & ~self._was_pressed(button)
def _just_released(self, button: int) -> bool:
return ~self._is_pressed(button) & self._was_pressed(button)