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..833dc5c 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) diff --git a/examples/tlc5947_chain.py b/examples/tlc5947_chain.py new file mode 100644 index 0000000..89d1705 --- /dev/null +++ b/examples/tlc5947_chain.py @@ -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 +# with another RGB LED hooked up to pins 21, 22 and 23 of the last chained driver, +# cycle the red, green, and blue pins of LED one up and down, and the other vice versa: + +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, LED Z down") + for pwm in range(start_pwm, end_pwm, step): + pinA.duty_cycle = pwm + pinZ.duty_cycle = end_pwm - pwm +# tlc5947.write() # see NOTE below + + # Dim: + print("LED A down, LED Z up") + for pwm in range(end_pwm, start_pwm, 0 - step): + pinA.duty_cycle = pwm + pinZ.duty_cycle = end_pwm - 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). +# 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()