Skip to content

Resolving issue #6 chaining TLC boards. #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ This is easily achieved by downloading
Usage Example
=============

See examples/tlc5947_simpletest.py for a demo of the usage.
| See examples/tlc5947_simpletest.py for a demo of the usage.
| See examples/tlc5947_chain.py for a demo of chained driver usage.

Contributing
============
Expand Down
45 changes: 30 additions & 15 deletions adafruit_tlc5947.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
CircuitPython module for the TLC5947 12-bit 24 channel LED PWM driver. See
examples/simpletest.py for a demo of the usage.

* Author(s): Tony DiCola
* Author(s): Tony DiCola, Walter Haschka

Implementation Notes
--------------------
Expand All @@ -50,6 +50,8 @@
# access is by design for the internal class.
#pylint: disable=protected-access

_CHANNELS = 24
_STOREBYTES = _CHANNELS + _CHANNELS//2

class TLC5947:
"""TLC5947 12-bit 24 channel LED PWM driver. Create an instance of this by
Expand All @@ -66,6 +68,16 @@ class TLC5947:
single one is updated. If you set to false to disable then
you MUST call write after every channel update or when you
deem necessary to update the chip state.

:param num_drivers: This is an integer that defaults to 1. It stands for the
number of chained LED driver boards (DOUT of one board has
to be connected to DIN of the next). For each board added,
36 bytes of RAM memory will be taken. The channel numbers
on the driver directly connected to the controller are 0 to
23, and for each driver add 24 to the port number printed.
The more drivers are chained, the more viable it is to set
auto_write=false, and call write explicitly after updating
all the channels.
"""

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


def __init__(self, spi, latch, *, auto_write=True):
def __init__(self, spi, latch, *, auto_write=True, num_drivers=1):
self._spi = spi
self._latch = latch
self._latch.switch_to_output(value=False)
# This device is just a big 36 byte long shift register. There's no
# fancy protocol or other commands to send, just write out all 288
# This device is just a big 36*n byte long shift register. There's no
# fancy protocol or other commands to send, just write out all 288*n
# bits every time the state is updated.
self._shift_reg = bytearray(36)
self._n = num_drivers
self._shift_reg = bytearray(_STOREBYTES * self._n)
# Save auto_write state (i.e. push out shift register values on
# any channel value change).
self.auto_write = auto_write
Expand All @@ -141,7 +154,7 @@ def write(self):
# First ensure latch is low.
self._latch.value = False
# Write out the bits.
self._spi.write(self._shift_reg, start=0, end=37)
self._spi.write(self._shift_reg, start=0, end=_STOREBYTES*self._n +1)
# Then toggle latch high and low to set the value.
self._latch.value = True
self._latch.value = False
Expand All @@ -152,10 +165,10 @@ def write(self):
def _get_gs_value(self, channel):
# pylint: disable=no-else-return
# Disable should be removed when refactor can be tested
assert 0 <= channel <= 23
assert 0 <= channel < _CHANNELS * self._n
# Invert channel position as the last channel needs to be written first.
# I.e. is in the first position of the shift registr.
channel = 23 - channel
channel = _CHANNELS * self._n - 1 - channel
# Calculate exact bit position within the shift register.
bit_offset = channel * 12
# Now calculate the byte that this position falls within and any offset
Expand All @@ -177,11 +190,11 @@ def _get_gs_value(self, channel):
raise RuntimeError('Unsupported bit offset!')

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

def __getitem__(self, key):
"""Retrieve the 12-bit PWM value for the specified channel (0-23).
"""Retrieve the 12-bit PWM value for the specified channel (0-max).
max depends on the number of boards.
"""
assert 0 <= key <= 23
assert 0 <= key < _CHANNELS * self._n
return self._get_gs_value(key)

def __setitem__(self, key, val):
"""Set the 12-bit PWM value (0-4095) for the specified channel (0-23).
"""Set the 12-bit PWM value (0-4095) for the specified channel (0-max).
max depends on the number of boards.
If auto_write is enabled (the default) then the chip PWM state will
immediately be updated too, otherwise you must call write to update
the chip with the new PWM state.
"""
assert 0 <= key <= 23
assert 0 <= key < _CHANNELS * self._n
assert 0 <= val <= 4095
self._set_gs_value(key, val)
107 changes: 107 additions & 0 deletions examples/tlc5947_chain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Simple demo of controlling a chain of several TLC5947 12-bit 24-channel PWM controllers.
# Will update channel values to different PWM duty cycles.
# Authors: Tony DiCola, Walter Haschka

import board
import busio
import digitalio

import adafruit_tlc5947

# Initialize SPI bus.
spi = busio.SPI(clock=board.SCK, MOSI=board.MOSI)

# Initialize TLC5947
DRIVER_COUNT = 2 # change this to the number of drivers you have chained
LATCH = digitalio.DigitalInOut(board.D5)
tlc5947 = adafruit_tlc5947.TLC5947(spi, LATCH, num_drivers=DRIVER_COUNT)

# You can optionally disable auto_write which allows you to control when
# channel state is written to the chip. Normally auto_write is true and
# will automatically write out changes as soon as they happen to a channel, but
# if you need more control or atomic updates of multiple channels then disable
# and manually call write as shown below.
#tlc5947 = adafruit_tlc5947.TLC5947(spi, LATCH, num_drivers=DRIVER_COUNT, auto_write=False)

# There are two ways to set channel PWM values. The first is by getting
# a PWMOut object that acts like the built-in PWMOut and can be used anywhere
# it is used in your code. Change the duty_cycle property to a 16-bit value
# (note this is NOT the 12-bit value supported by the chip natively) and the
# PWM channel will be updated.

def first_last():
"""Cycles the red pin of LED one up, then the other LED; now dims the LEDs
both down. Repeats with green and blue pins. Then starts all over again.

Hook up one RGB LED to pins 0 (red), 1 (green), and 2 (blue), AND connect
another RGB LED to pins 21, 22 and 23 of the last chained driver, respectively.
"""
redA = tlc5947.create_pwm_out(0)
greenA = tlc5947.create_pwm_out(1)
blueA = tlc5947.create_pwm_out(2)
redZ = tlc5947.create_pwm_out(DRIVER_COUNT*24-3)
greenZ = tlc5947.create_pwm_out(DRIVER_COUNT*24-2)
blueZ = tlc5947.create_pwm_out(DRIVER_COUNT*24-1)

step = 10
start_pwm = 0
end_pwm = 32767 # 50% (32767, or half of the maximum 65535):

while True:
for (pinA, pinZ) in ((redA, redZ), (greenA, greenZ), (blueA, blueZ)):
# Brighten:
print("LED A up")
for pwm in range(start_pwm, end_pwm, step):
pinA.duty_cycle = pwm
# tlc5947.write() # see NOTE below

print("LED Z up")
for pwm in range(start_pwm, end_pwm, step):
pinZ.duty_cycle = pwm
# tlc5947.write() # see NOTE below

# Dim:
print("LED A and LED Z down")
for pwm in range(end_pwm, start_pwm, 0 - step):
pinA.duty_cycle = pwm
pinZ.duty_cycle = pwm
# tlc5947.write() # see NOTE below

# NOTE: if auto_write was disabled you need to call write on the parent to
# make sure the value is written in each loop (this is not common, if disabling
# auto_write you probably want to use the direct 12-bit raw access instead,
# shown next).

#----------
# The other way to read and write channels is directly with each channel 12-bit
# value and an item accessor syntax. Index into the TLC5947 with the channel
# number (0-max) and get or set its 12-bit value (0-4095).
def test_all_channels(step):
"""Loops over all available channels of all connected driver boards,
brightening and dimming all LEDs one after the other. With RGB LEDs,
all each component is cycled. Repeats forever.

:param step: the PWM increment in each cycle. Higher values makes cycling quicker.
"""

start_pwm = 0
end_pwm = 3072 # 75% of the maximum 4095

while True:
for pin in range(DRIVER_COUNT*24):
# Brighten:
for pwm in range(start_pwm, end_pwm, step):
tlc5947[pin] = pwm
# Again be sure to call write if you disabled auto_write.
#tlc5947.write()

# Dim:
for pwm in range(end_pwm, start_pwm, 0 -step):
tlc5947[pin] = pwm
# Again be sure to call write if you disabled auto_write.
#tlc5947.write()

#----------
# Choose here which function to try:
#first_last()
test_all_channels(16)