-
Notifications
You must be signed in to change notification settings - Fork 8
Add support for multiple chained boards #7
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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 | ||||||||||
-------------------- | ||||||||||
|
@@ -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 | ||||||||||
|
@@ -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: | ||||||||||
|
@@ -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 | ||||||||||
|
@@ -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 | ||||||||||
|
@@ -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 | ||||||||||
|
@@ -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 | ||||||||||
|
@@ -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 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To allow for Python's negative indices (adressing elements from the rear end of an array), the suggested change is needed. Otherwise the negative index adressing near the end of tlc5947_chain.py would fail.
Suggested change
|
||||||||||
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 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here - enable Python's negative array index feature:
Suggested change
|
||||||||||
assert 0 <= val <= 4095 | ||||||||||
self._set_gs_value(key, val) |
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,78 @@ | ||||||||||||||||
# 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 | ||||||||||||||||
|
||||||||||||||||
# Define pins connected to the TLC5947 | ||||||||||||||||
SCK = board.SCK | ||||||||||||||||
MOSI = board.MOSI | ||||||||||||||||
LATCH = digitalio.DigitalInOut(board.D5) | ||||||||||||||||
|
||||||||||||||||
# Initialize SPI bus. | ||||||||||||||||
spi = busio.SPI(clock=SCK, MOSI=MOSI) | ||||||||||||||||
|
||||||||||||||||
# Initialize TLC5947 | ||||||||||||||||
DRIVER_COUNT = 2 # change this to the number of drivers you have chained | ||||||||||||||||
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. | ||||||||||||||||
|
||||||||||||||||
# With one RGB LED hooked up to pins 0, 1, and 2, AND | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplify comment.
Suggested change
|
||||||||||||||||
# with another RGB LED hooked up to pins 21, 22 and 23 of the last chained driver, | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove.
Suggested change
|
||||||||||||||||
# cycle the red, green, and blue pins of LED one up and down, and the other vice versa: | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move to below, and change to reflect code changes.
Suggested change
|
||||||||||||||||
|
||||||||||||||||
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) | ||||||||||||||||
|
||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move code comment here, and reflect code changes:
Suggested change
|
||||||||||||||||
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, LED Z down") | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With current code the second LED will stay lit after the cycle. Proposed change will brighten the LEDs one after the other, then dim them again, repeating for each channel:
Suggested change
|
||||||||||||||||
for pwm in range(start_pwm, end_pwm, step): | ||||||||||||||||
pinA.duty_cycle = pwm | ||||||||||||||||
pinZ.duty_cycle = end_pwm - pwm | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove.
Suggested change
|
||||||||||||||||
# tlc5947.write() # see NOTE below | ||||||||||||||||
|
||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. now brighten the LED on the last pins of the last driver board:
Suggested change
|
||||||||||||||||
# Dim: | ||||||||||||||||
print("LED A down, LED Z up") | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dimming both LEDs now:
Suggested change
|
||||||||||||||||
for pwm in range(end_pwm, start_pwm, 0 - step): | ||||||||||||||||
pinA.duty_cycle = pwm | ||||||||||||||||
pinZ.duty_cycle = end_pwm - pwm | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just do the same with both LEDs:
Suggested change
|
||||||||||||||||
# 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). | ||||||||||||||||
# For example set channel 1 to 50% duty cycle. | ||||||||||||||||
#tlc5947[1] = 2048 | ||||||||||||||||
#tlc5947[-2] = 3072 # example: set channel 22 on last driver to 75% duty cycle. | ||||||||||||||||
# Again be sure to call write if you disabled auto_write. | ||||||||||||||||
#tlc5947.write() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...otherwise we wind up with a float here, at least on CPython.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for finding this one! I hoped to avoid this pitfall by rephrasing
_STOREBYTES = _CHANNELS * 1.5
but forgot about Python 3 division rules. So here is my fix:which I find less bulky, but also a bit less clear.