Skip to content

Commit

Permalink
Merge pull request #4 from adafruit/current-control
Browse files Browse the repository at this point in the history
  • Loading branch information
jepler authored Oct 21, 2024
2 parents 3eea404 + f133320 commit 545e946
Showing 1 changed file with 108 additions and 40 deletions.
148 changes: 108 additions & 40 deletions adafruit_tm1814.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,51 +34,96 @@
# Datasheet high times for a "1" bit are 650 (min) 720 (typ) 1000 (max) ns
#
# Operating PIO at 14x the bit clock lets us achieve nominal 357ns and 714ns
_program = Program(
f"""
.side_set 1
.wrap_target
pull block side 1
out y, 32 side 1 ; get count of pixel bits
bitloop:
pull ifempty side 1 ; drive low
out x 1 side 1 [4]
jmp !x do_zero side 0 [3] ; drive low and branch depending on bit val
jmp y--, bitloop side 0 [3] ; drive low for a one (long pulse)
jmp end_sequence side 1 ; sequence is over
do_zero:
jmp y--, bitloop side 1 [3] ; drive high for a zero (short pulse)
end_sequence:
pull block side 1 ; get fresh delay value
out y, 32 side 1 ; get delay count
wait_reset:
jmp y--, wait_reset side 1 ; wait until delay elapses
.wrap
"""
)
_pio_source = ()

TM1814_MIN_CURRENT = 6.5
TM1814_MAX_CURRENT = 38
TM1814_CURRENT_SCALE = 2


def _convert_one_current(value):
if value < TM1814_MIN_CURRENT or value > TM1814_MAX_CURRENT:
raise ValueError("Current control out of range")
return round((value - TM1814_MIN_CURRENT) * TM1814_CURRENT_SCALE)


def _convert_brightness(x):
x = int(x * 63) + 13
x |= x << 8
return x | (x << 16)
def _current_control_word(arg):
if isinstance(arg, (int, float)):
arg = arg, arg, arg, arg
result = [_convert_one_current(value) for value in arg]
result += [value ^ 0xFF for value in result]
return result


class TM1814PixelBackground( # pylint: disable=too-few-public-methods
adafruit_pixelbuf.PixelBuf
):
def __init__(self, pin, n, *, brightness=1.0, pixel_order="WRGB"):
"""
A sequence of TM1814 addressable pixels
Except as noted, provides all the functionality of
`adafruit_pixelbuf.PixelBuf`, particularly
`adafruit_pixelbuf.PixelBuf.fill` and
`adafruit_pixelbuf.PixelBuf.__setitem__`.
As the strip always auto-written, there is no need to call the `show` method.
:param ~microcontroller.Pin pin: The pin to output neopixel data on.
:param int n: The number of neopixels in the chain
:param float brightness: Brightness of the pixels between 0.0 and 1.0 where 1.0 is full
brightness. This brightness value is software-multiplied with raw pixel values.
:param float|tuple[float,float,float] current_control: TM1814 current
control register. See documentation of the ``current_control`` property
below.
:param str pixel_order: Set the pixel color channel order. WRGB is set by
default. Only 4-bytes-per-pixel formats are supported.
:param bool inverted: True to invert the polarity of the output signal.
"""

def __init__( # noqa: PLR0913
self,
pin,
n: int,
*,
brightness: float = 1.0,
pixel_order: str = "WRGB",
current_control: float | tuple[float, float, float, float] = 38.0,
inverted: bool = False,
):
if len(pixel_order) != 4:
raise ValueError("Invalid pixel_order")

_program = Program(f"""
.side_set 1
.wrap_target
pull block side {not inverted:1d}
out y, 32 side {not inverted:1d} ; get count of pixel bits
bitloop:
pull ifempty side {not inverted:1d} ; drive low
out x 1 side {not inverted:1d} [4]
jmp !x do_zero side {inverted:1d} [3] ; drive low and branch depending on bit val
jmp y--, bitloop side {inverted:1d} [3] ; drive low for a one (long pulse)
jmp end_sequence side {not inverted:1d} ; sequence is over
do_zero:
jmp y--, bitloop side {not inverted:1d} [3] ; drive high for a zero (short pulse)
end_sequence:
pull block side {not inverted:1d} ; get fresh delay value
out y, 32 side {not inverted:1d} ; get delay count
wait_reset:
jmp y--, wait_reset side {not inverted:1d} ; wait until delay elapses
.wrap
""")

byte_count = 4 * n
bit_count = byte_count * 8 + 64 # count the 64 brightness bits

self._brightness = brightness
raw_brightness = _convert_brightness(brightness)
self._current_control = current_control

# backwards, so that dma byteswap corrects it!
header = struct.pack(">LLL", bit_count - 1, raw_brightness, raw_brightness ^ 0xFFFFFFFF)
header = struct.pack(">L8B", bit_count - 1, *_current_control_word(current_control))
trailer = struct.pack(">L", 38400) # Delay is about 3ms

self._sm = StateMachine(
Expand All @@ -94,14 +139,42 @@ def __init__(self, pin, n, *, brightness=1.0, pixel_order="WRGB"):
self._buf = None
super().__init__(
n,
brightness=1.0,
brightness=brightness,
byteorder=pixel_order,
auto_write=False,
header=header,
trailer=trailer,
)

self.show()
super().show()

def show(self) -> None:
"""Does nothing, because the strip is always auto-written"""

@property
def current_control(self) -> float | tuple[float, float, float, float]:
"""Access the current control register of the TM1814
The TM1814 has a per-channel current control register that is shared across
the entire strip.
The current regulation range is from 6.5mA to 38mA in 0.5mA increments.
Out of range values will throw ValueError.
The relationship between human perception & LED current value is highly
nonlinear: The lowest setting may appear only slightly less bright than the
brightest setting, not 6x brighter as you might expect.
If this property is set to a single number, then the same value is used for
each channel. Otherwise, it must be a tuple of 4 elements where each element
is applied to a different channel.
"""
return self._current_control

@current_control.setter
def current_control(self, value: float | tuple[float, float, float]) -> None:
struct.pack_into("8B", self._buf, 4, *_current_control_word(value))
self._current_control = value

def deinit(self) -> None:
"""Deinitialize the object"""
Expand All @@ -119,11 +192,6 @@ def auto_write(self) -> bool:
def auto_write(self, value: bool) -> None:
pass

@property
def brightness(self) -> float:
"""Returns the strip brightness (read-only)"""
return self._brightness

def _transmit(self, buf: bytes) -> None:
self._buf = buf
self._sm.background_write(loop=memoryview(buf).cast("L"), swap=True)

0 comments on commit 545e946

Please sign in to comment.