Skip to content

Commit a410634

Browse files
authored
Merge pull request #8 from ArthurDent62/chain_TLCs
Resolving issue #6 chaining TLC boards.
2 parents 327cdfe + bb6d999 commit a410634

File tree

3 files changed

+139
-16
lines changed

3 files changed

+139
-16
lines changed

README.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ This is easily achieved by downloading
2929
Usage Example
3030
=============
3131

32-
See examples/tlc5947_simpletest.py for a demo of the usage.
32+
| See examples/tlc5947_simpletest.py for a demo of the usage.
33+
| See examples/tlc5947_chain.py for a demo of chained driver usage.
3334
3435
Contributing
3536
============

adafruit_tlc5947.py

+30-15
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
CircuitPython module for the TLC5947 12-bit 24 channel LED PWM driver. See
2727
examples/simpletest.py for a demo of the usage.
2828
29-
* Author(s): Tony DiCola
29+
* Author(s): Tony DiCola, Walter Haschka
3030
3131
Implementation Notes
3232
--------------------
@@ -50,6 +50,8 @@
5050
# access is by design for the internal class.
5151
#pylint: disable=protected-access
5252

53+
_CHANNELS = 24
54+
_STOREBYTES = _CHANNELS + _CHANNELS//2
5355

5456
class TLC5947:
5557
"""TLC5947 12-bit 24 channel LED PWM driver. Create an instance of this by
@@ -66,6 +68,16 @@ class TLC5947:
6668
single one is updated. If you set to false to disable then
6769
you MUST call write after every channel update or when you
6870
deem necessary to update the chip state.
71+
72+
:param num_drivers: This is an integer that defaults to 1. It stands for the
73+
number of chained LED driver boards (DOUT of one board has
74+
to be connected to DIN of the next). For each board added,
75+
36 bytes of RAM memory will be taken. The channel numbers
76+
on the driver directly connected to the controller are 0 to
77+
23, and for each driver add 24 to the port number printed.
78+
The more drivers are chained, the more viable it is to set
79+
auto_write=false, and call write explicitly after updating
80+
all the channels.
6981
"""
7082

7183
class PWMOut:
@@ -115,14 +127,15 @@ def frequency(self, val):
115127
#pylint: enable=no-self-use,unused-argument
116128

117129

118-
def __init__(self, spi, latch, *, auto_write=True):
130+
def __init__(self, spi, latch, *, auto_write=True, num_drivers=1):
119131
self._spi = spi
120132
self._latch = latch
121133
self._latch.switch_to_output(value=False)
122-
# This device is just a big 36 byte long shift register. There's no
123-
# fancy protocol or other commands to send, just write out all 288
134+
# This device is just a big 36*n byte long shift register. There's no
135+
# fancy protocol or other commands to send, just write out all 288*n
124136
# bits every time the state is updated.
125-
self._shift_reg = bytearray(36)
137+
self._n = num_drivers
138+
self._shift_reg = bytearray(_STOREBYTES * self._n)
126139
# Save auto_write state (i.e. push out shift register values on
127140
# any channel value change).
128141
self.auto_write = auto_write
@@ -141,7 +154,7 @@ def write(self):
141154
# First ensure latch is low.
142155
self._latch.value = False
143156
# Write out the bits.
144-
self._spi.write(self._shift_reg, start=0, end=37)
157+
self._spi.write(self._shift_reg, start=0, end=_STOREBYTES*self._n +1)
145158
# Then toggle latch high and low to set the value.
146159
self._latch.value = True
147160
self._latch.value = False
@@ -152,10 +165,10 @@ def write(self):
152165
def _get_gs_value(self, channel):
153166
# pylint: disable=no-else-return
154167
# Disable should be removed when refactor can be tested
155-
assert 0 <= channel <= 23
168+
assert 0 <= channel < _CHANNELS * self._n
156169
# Invert channel position as the last channel needs to be written first.
157170
# I.e. is in the first position of the shift registr.
158-
channel = 23 - channel
171+
channel = _CHANNELS * self._n - 1 - channel
159172
# Calculate exact bit position within the shift register.
160173
bit_offset = channel * 12
161174
# Now calculate the byte that this position falls within and any offset
@@ -177,11 +190,11 @@ def _get_gs_value(self, channel):
177190
raise RuntimeError('Unsupported bit offset!')
178191

179192
def _set_gs_value(self, channel, val):
180-
assert 0 <= channel <= 23
193+
assert 0 <= channel < _CHANNELS * self._n
181194
assert 0 <= val <= 4095
182195
# Invert channel position as the last channel needs to be written first.
183196
# I.e. is in the first position of the shift registr.
184-
channel = 23 - channel
197+
channel = _CHANNELS * self._n - 1 - channel
185198
# Calculate exact bit position within the shift register.
186199
bit_offset = channel * 12
187200
# Now calculate the byte that this position falls within and any offset
@@ -226,20 +239,22 @@ def create_pwm_out(self, channel):
226239
# like when using the PWMOut mock class).
227240
def __len__(self):
228241
"""Retrieve the total number of PWM channels available."""
229-
return 24 # Always 24 channels on the chip.
242+
return _CHANNELS * self._n # number channels times number chips.
230243

231244
def __getitem__(self, key):
232-
"""Retrieve the 12-bit PWM value for the specified channel (0-23).
245+
"""Retrieve the 12-bit PWM value for the specified channel (0-max).
246+
max depends on the number of boards.
233247
"""
234-
assert 0 <= key <= 23
248+
assert 0 <= key < _CHANNELS * self._n
235249
return self._get_gs_value(key)
236250

237251
def __setitem__(self, key, val):
238-
"""Set the 12-bit PWM value (0-4095) for the specified channel (0-23).
252+
"""Set the 12-bit PWM value (0-4095) for the specified channel (0-max).
253+
max depends on the number of boards.
239254
If auto_write is enabled (the default) then the chip PWM state will
240255
immediately be updated too, otherwise you must call write to update
241256
the chip with the new PWM state.
242257
"""
243-
assert 0 <= key <= 23
258+
assert 0 <= key < _CHANNELS * self._n
244259
assert 0 <= val <= 4095
245260
self._set_gs_value(key, val)

examples/tlc5947_chain.py

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Simple demo of controlling a chain of several TLC5947 12-bit 24-channel PWM controllers.
2+
# Will update channel values to different PWM duty cycles.
3+
# Authors: Tony DiCola, Walter Haschka
4+
5+
import board
6+
import busio
7+
import digitalio
8+
9+
import adafruit_tlc5947
10+
11+
# Initialize SPI bus.
12+
spi = busio.SPI(clock=board.SCK, MOSI=board.MOSI)
13+
14+
# Initialize TLC5947
15+
DRIVER_COUNT = 2 # change this to the number of drivers you have chained
16+
LATCH = digitalio.DigitalInOut(board.D5)
17+
tlc5947 = adafruit_tlc5947.TLC5947(spi, LATCH, num_drivers=DRIVER_COUNT)
18+
19+
# You can optionally disable auto_write which allows you to control when
20+
# channel state is written to the chip. Normally auto_write is true and
21+
# will automatically write out changes as soon as they happen to a channel, but
22+
# if you need more control or atomic updates of multiple channels then disable
23+
# and manually call write as shown below.
24+
#tlc5947 = adafruit_tlc5947.TLC5947(spi, LATCH, num_drivers=DRIVER_COUNT, auto_write=False)
25+
26+
# There are two ways to set channel PWM values. The first is by getting
27+
# a PWMOut object that acts like the built-in PWMOut and can be used anywhere
28+
# it is used in your code. Change the duty_cycle property to a 16-bit value
29+
# (note this is NOT the 12-bit value supported by the chip natively) and the
30+
# PWM channel will be updated.
31+
32+
def first_last():
33+
"""Cycles the red pin of LED one up, then the other LED; now dims the LEDs
34+
both down. Repeats with green and blue pins. Then starts all over again.
35+
36+
Hook up one RGB LED to pins 0 (red), 1 (green), and 2 (blue), AND connect
37+
another RGB LED to pins 21, 22 and 23 of the last chained driver, respectively.
38+
"""
39+
redA = tlc5947.create_pwm_out(0)
40+
greenA = tlc5947.create_pwm_out(1)
41+
blueA = tlc5947.create_pwm_out(2)
42+
redZ = tlc5947.create_pwm_out(DRIVER_COUNT*24-3)
43+
greenZ = tlc5947.create_pwm_out(DRIVER_COUNT*24-2)
44+
blueZ = tlc5947.create_pwm_out(DRIVER_COUNT*24-1)
45+
46+
step = 10
47+
start_pwm = 0
48+
end_pwm = 32767 # 50% (32767, or half of the maximum 65535):
49+
50+
while True:
51+
for (pinA, pinZ) in ((redA, redZ), (greenA, greenZ), (blueA, blueZ)):
52+
# Brighten:
53+
print("LED A up")
54+
for pwm in range(start_pwm, end_pwm, step):
55+
pinA.duty_cycle = pwm
56+
# tlc5947.write() # see NOTE below
57+
58+
print("LED Z up")
59+
for pwm in range(start_pwm, end_pwm, step):
60+
pinZ.duty_cycle = pwm
61+
# tlc5947.write() # see NOTE below
62+
63+
# Dim:
64+
print("LED A and LED Z down")
65+
for pwm in range(end_pwm, start_pwm, 0 - step):
66+
pinA.duty_cycle = pwm
67+
pinZ.duty_cycle = pwm
68+
# tlc5947.write() # see NOTE below
69+
70+
# NOTE: if auto_write was disabled you need to call write on the parent to
71+
# make sure the value is written in each loop (this is not common, if disabling
72+
# auto_write you probably want to use the direct 12-bit raw access instead,
73+
# shown next).
74+
75+
#----------
76+
# The other way to read and write channels is directly with each channel 12-bit
77+
# value and an item accessor syntax. Index into the TLC5947 with the channel
78+
# number (0-max) and get or set its 12-bit value (0-4095).
79+
def test_all_channels(step):
80+
"""Loops over all available channels of all connected driver boards,
81+
brightening and dimming all LEDs one after the other. With RGB LEDs,
82+
all each component is cycled. Repeats forever.
83+
84+
:param step: the PWM increment in each cycle. Higher values makes cycling quicker.
85+
"""
86+
87+
start_pwm = 0
88+
end_pwm = 3072 # 75% of the maximum 4095
89+
90+
while True:
91+
for pin in range(DRIVER_COUNT*24):
92+
# Brighten:
93+
for pwm in range(start_pwm, end_pwm, step):
94+
tlc5947[pin] = pwm
95+
# Again be sure to call write if you disabled auto_write.
96+
#tlc5947.write()
97+
98+
# Dim:
99+
for pwm in range(end_pwm, start_pwm, 0 -step):
100+
tlc5947[pin] = pwm
101+
# Again be sure to call write if you disabled auto_write.
102+
#tlc5947.write()
103+
104+
#----------
105+
# Choose here which function to try:
106+
#first_last()
107+
test_all_channels(16)

0 commit comments

Comments
 (0)