Skip to content

Commit 545e946

Browse files
authored
Merge pull request #4 from adafruit/current-control
2 parents 3eea404 + f133320 commit 545e946

File tree

1 file changed

+108
-40
lines changed

1 file changed

+108
-40
lines changed

adafruit_tm1814.py

Lines changed: 108 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -34,51 +34,96 @@
3434
# Datasheet high times for a "1" bit are 650 (min) 720 (typ) 1000 (max) ns
3535
#
3636
# Operating PIO at 14x the bit clock lets us achieve nominal 357ns and 714ns
37-
_program = Program(
38-
f"""
39-
.side_set 1
40-
.wrap_target
41-
pull block side 1
42-
out y, 32 side 1 ; get count of pixel bits
43-
44-
bitloop:
45-
pull ifempty side 1 ; drive low
46-
out x 1 side 1 [4]
47-
jmp !x do_zero side 0 [3] ; drive low and branch depending on bit val
48-
jmp y--, bitloop side 0 [3] ; drive low for a one (long pulse)
49-
jmp end_sequence side 1 ; sequence is over
50-
51-
do_zero:
52-
jmp y--, bitloop side 1 [3] ; drive high for a zero (short pulse)
53-
54-
end_sequence:
55-
pull block side 1 ; get fresh delay value
56-
out y, 32 side 1 ; get delay count
57-
wait_reset:
58-
jmp y--, wait_reset side 1 ; wait until delay elapses
59-
.wrap
60-
"""
61-
)
37+
_pio_source = ()
38+
39+
TM1814_MIN_CURRENT = 6.5
40+
TM1814_MAX_CURRENT = 38
41+
TM1814_CURRENT_SCALE = 2
42+
43+
44+
def _convert_one_current(value):
45+
if value < TM1814_MIN_CURRENT or value > TM1814_MAX_CURRENT:
46+
raise ValueError("Current control out of range")
47+
return round((value - TM1814_MIN_CURRENT) * TM1814_CURRENT_SCALE)
6248

6349

64-
def _convert_brightness(x):
65-
x = int(x * 63) + 13
66-
x |= x << 8
67-
return x | (x << 16)
50+
def _current_control_word(arg):
51+
if isinstance(arg, (int, float)):
52+
arg = arg, arg, arg, arg
53+
result = [_convert_one_current(value) for value in arg]
54+
result += [value ^ 0xFF for value in result]
55+
return result
6856

6957

7058
class TM1814PixelBackground( # pylint: disable=too-few-public-methods
7159
adafruit_pixelbuf.PixelBuf
7260
):
73-
def __init__(self, pin, n, *, brightness=1.0, pixel_order="WRGB"):
61+
"""
62+
A sequence of TM1814 addressable pixels
63+
64+
Except as noted, provides all the functionality of
65+
`adafruit_pixelbuf.PixelBuf`, particularly
66+
`adafruit_pixelbuf.PixelBuf.fill` and
67+
`adafruit_pixelbuf.PixelBuf.__setitem__`.
68+
69+
As the strip always auto-written, there is no need to call the `show` method.
70+
71+
:param ~microcontroller.Pin pin: The pin to output neopixel data on.
72+
:param int n: The number of neopixels in the chain
73+
:param float brightness: Brightness of the pixels between 0.0 and 1.0 where 1.0 is full
74+
brightness. This brightness value is software-multiplied with raw pixel values.
75+
:param float|tuple[float,float,float] current_control: TM1814 current
76+
control register. See documentation of the ``current_control`` property
77+
below.
78+
:param str pixel_order: Set the pixel color channel order. WRGB is set by
79+
default. Only 4-bytes-per-pixel formats are supported.
80+
:param bool inverted: True to invert the polarity of the output signal.
81+
"""
82+
83+
def __init__( # noqa: PLR0913
84+
self,
85+
pin,
86+
n: int,
87+
*,
88+
brightness: float = 1.0,
89+
pixel_order: str = "WRGB",
90+
current_control: float | tuple[float, float, float, float] = 38.0,
91+
inverted: bool = False,
92+
):
93+
if len(pixel_order) != 4:
94+
raise ValueError("Invalid pixel_order")
95+
96+
_program = Program(f"""
97+
.side_set 1
98+
.wrap_target
99+
pull block side {not inverted:1d}
100+
out y, 32 side {not inverted:1d} ; get count of pixel bits
101+
102+
bitloop:
103+
pull ifempty side {not inverted:1d} ; drive low
104+
out x 1 side {not inverted:1d} [4]
105+
jmp !x do_zero side {inverted:1d} [3] ; drive low and branch depending on bit val
106+
jmp y--, bitloop side {inverted:1d} [3] ; drive low for a one (long pulse)
107+
jmp end_sequence side {not inverted:1d} ; sequence is over
108+
109+
do_zero:
110+
jmp y--, bitloop side {not inverted:1d} [3] ; drive high for a zero (short pulse)
111+
112+
end_sequence:
113+
pull block side {not inverted:1d} ; get fresh delay value
114+
out y, 32 side {not inverted:1d} ; get delay count
115+
wait_reset:
116+
jmp y--, wait_reset side {not inverted:1d} ; wait until delay elapses
117+
.wrap
118+
""")
119+
74120
byte_count = 4 * n
75121
bit_count = byte_count * 8 + 64 # count the 64 brightness bits
76122

77-
self._brightness = brightness
78-
raw_brightness = _convert_brightness(brightness)
123+
self._current_control = current_control
79124

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

84129
self._sm = StateMachine(
@@ -94,14 +139,42 @@ def __init__(self, pin, n, *, brightness=1.0, pixel_order="WRGB"):
94139
self._buf = None
95140
super().__init__(
96141
n,
97-
brightness=1.0,
142+
brightness=brightness,
98143
byteorder=pixel_order,
99144
auto_write=False,
100145
header=header,
101146
trailer=trailer,
102147
)
103148

104-
self.show()
149+
super().show()
150+
151+
def show(self) -> None:
152+
"""Does nothing, because the strip is always auto-written"""
153+
154+
@property
155+
def current_control(self) -> float | tuple[float, float, float, float]:
156+
"""Access the current control register of the TM1814
157+
158+
The TM1814 has a per-channel current control register that is shared across
159+
the entire strip.
160+
161+
The current regulation range is from 6.5mA to 38mA in 0.5mA increments.
162+
Out of range values will throw ValueError.
163+
164+
The relationship between human perception & LED current value is highly
165+
nonlinear: The lowest setting may appear only slightly less bright than the
166+
brightest setting, not 6x brighter as you might expect.
167+
168+
If this property is set to a single number, then the same value is used for
169+
each channel. Otherwise, it must be a tuple of 4 elements where each element
170+
is applied to a different channel.
171+
"""
172+
return self._current_control
173+
174+
@current_control.setter
175+
def current_control(self, value: float | tuple[float, float, float]) -> None:
176+
struct.pack_into("8B", self._buf, 4, *_current_control_word(value))
177+
self._current_control = value
105178

106179
def deinit(self) -> None:
107180
"""Deinitialize the object"""
@@ -119,11 +192,6 @@ def auto_write(self) -> bool:
119192
def auto_write(self, value: bool) -> None:
120193
pass
121194

122-
@property
123-
def brightness(self) -> float:
124-
"""Returns the strip brightness (read-only)"""
125-
return self._brightness
126-
127195
def _transmit(self, buf: bytes) -> None:
128196
self._buf = buf
129197
self._sm.background_write(loop=memoryview(buf).cast("L"), swap=True)

0 commit comments

Comments
 (0)