From 9d6d547a63451981cdcae5cae485cbff4567ed0c Mon Sep 17 00:00:00 2001 From: ArthurDent62 <45048413+ArthurDent62@users.noreply.github.com> Date: Sun, 25 Nov 2018 20:45:35 +0100 Subject: [PATCH 1/5] Resolving issue #6 chaining TLC boards Extended README with new example file. Extended library with support for multiple chained boards. --- README.rst | 1 + adafruit_tlc5947.py | 45 ++++++++++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index e093c73..2b1f397 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,7 @@ Usage Example ============= See examples/tlc5947_simpletest.py for a demo of the usage. +See examples/tlc5947_chain.py for a demo of chained driver usage. Contributing ============ diff --git a/adafruit_tlc5947.py b/adafruit_tlc5947.py index d090495..22ca4e4 100644 --- a/adafruit_tlc5947.py +++ b/adafruit_tlc5947.py @@ -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 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) From 6fdb5a28e3f26e3e2c9a2e590f1ad1526fa311a5 Mon Sep 17 00:00:00 2001 From: ArthurDent62 <45048413+ArthurDent62@users.noreply.github.com> Date: Sun, 25 Nov 2018 20:52:59 +0100 Subject: [PATCH 2/5] Changed line style within example section. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 2b1f397..c8e111c 100644 --- a/README.rst +++ b/README.rst @@ -29,8 +29,8 @@ This is easily achieved by downloading Usage Example ============= -See examples/tlc5947_simpletest.py for a demo of the usage. -See examples/tlc5947_chain.py for a demo of chained driver 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 ============ From 05c620013713d0135761fa9c79b318a981aaa722 Mon Sep 17 00:00:00 2001 From: ArthurDent62 <45048413+ArthurDent62@users.noreply.github.com> Date: Sun, 25 Nov 2018 20:53:45 +0100 Subject: [PATCH 3/5] Usage examples for chaining drivers. --- examples/tlc5947_chain.py | 110 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 examples/tlc5947_chain.py diff --git a/examples/tlc5947_chain.py b/examples/tlc5947_chain.py new file mode 100644 index 0000000..ebe8f99 --- /dev/null +++ b/examples/tlc5947_chain.py @@ -0,0 +1,110 @@ +# 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. + +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) From 9516917c3d4705ac56c5bed01889a949e16ba523 Mon Sep 17 00:00:00 2001 From: ArthurDent62 <45048413+ArthurDent62@users.noreply.github.com> Date: Sun, 25 Nov 2018 21:42:42 +0100 Subject: [PATCH 4/5] pylinted --- examples/tlc5947_chain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tlc5947_chain.py b/examples/tlc5947_chain.py index ebe8f99..e1c8ded 100644 --- a/examples/tlc5947_chain.py +++ b/examples/tlc5947_chain.py @@ -71,7 +71,7 @@ def first_last(): # 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 + # 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). From bb6d999572f2e32d9434d79b54ea1577e063ef68 Mon Sep 17 00:00:00 2001 From: ArthurDent62 <45048413+ArthurDent62@users.noreply.github.com> Date: Wed, 28 Nov 2018 23:58:15 +0100 Subject: [PATCH 5/5] harmonizing with other example scripts SPI variables are now inlined. --- examples/tlc5947_chain.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/tlc5947_chain.py b/examples/tlc5947_chain.py index e1c8ded..c4fb273 100644 --- a/examples/tlc5947_chain.py +++ b/examples/tlc5947_chain.py @@ -8,17 +8,14 @@ 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) +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