/ adafruit_tlc5947.py
adafruit_tlc5947.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2017 Tony DiCola for Adafruit Industries
  4  #
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy
  6  # of this software and associated documentation files (the "Software"), to deal
  7  # in the Software without restriction, including without limitation the rights
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9  # copies of the Software, and to permit persons to whom the Software is
 10  # furnished to do so, subject to the following conditions:
 11  #
 12  # The above copyright notice and this permission notice shall be included in
 13  # all copies or substantial portions of the Software.
 14  #
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21  # THE SOFTWARE.
 22  """
 23  `adafruit_tlc5947`
 24  ====================================================
 25  
 26  CircuitPython module for the TLC5947 12-bit 24 channel LED PWM driver.  See
 27  examples/simpletest.py for a demo of the usage.
 28  
 29  * Author(s): Tony DiCola, Walter Haschka
 30  
 31  Implementation Notes
 32  --------------------
 33  
 34  **Hardware:**
 35  
 36  * Adafruit `24-Channel 12-bit PWM LED Driver - SPI Interface - TLC5947
 37    <https://www.adafruit.com/product/1429>`_ (Product ID: 1429)
 38  
 39  **Software and Dependencies:**
 40  
 41  * Adafruit CircuitPython firmware for the ESP8622 and M0-based boards:
 42    https://github.com/adafruit/circuitpython/releases
 43  """
 44  __version__ = "0.0.0-auto.0"
 45  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_TLC5947.git"
 46  
 47  
 48  # Globally disable protected access.  Ppylint can't figure out the
 49  # context for using internal decorate classes below.  In these cases protectected
 50  # access is by design for the internal class.
 51  # pylint: disable=protected-access
 52  
 53  _CHANNELS = 24
 54  _STOREBYTES = _CHANNELS + _CHANNELS // 2
 55  
 56  
 57  class TLC5947:
 58      """TLC5947 12-bit 24 channel LED PWM driver.  Create an instance of this by
 59      passing in at least the following parameters:
 60  
 61      :param spi: The SPI bus connected to the chip (only the SCK and MOSI lines are
 62                  used, there is no MISO/input).
 63      :param latch: A DigitalInOut instance connected to the chip's latch line.
 64  
 65      Optionally you can specify:
 66  
 67      :param auto_write: This is a boolean that defaults to True and will automatically
 68                         write out all the channel values to the chip as soon as a
 69                         single one is updated.  If you set to False to disable then
 70                         you MUST call write after every channel update or when you
 71                         deem necessary to update the chip state.
 72  
 73      :param num_drivers: This is an integer that defaults to 1. It stands for the
 74                          number of chained LED driver boards (DOUT of one board has
 75                          to be connected to DIN of the next). For each board added,
 76                          36 bytes of RAM memory will be taken. The channel numbers
 77                          on the driver directly connected to the controller are 0 to
 78                          23, and for each driver add 24 to the port number printed.
 79                          The more drivers are chained, the more viable it is to set
 80                          auto_write=False, and call write explicitly after updating
 81                          all the channels.
 82      """
 83  
 84      class PWMOut:
 85          """Internal PWMOut class that mimics the behavior of CircuitPython's
 86          PWMOut class but is associated with a channel on the TLC5947.  You can
 87          get and set the instance's duty_cycle property as a 16-bit PWM value
 88          (note there will be quantization errors as the TLC5947 is a 12-bit PWM
 89          chip, instead use the TLC5947 class item accessor notation for direct
 90          12-bit raw PWM channel access).  Note you cannot change the frequency
 91          as it is fixed by the TLC5947 to ~2.4-5.6 mhz.
 92          """
 93  
 94          def __init__(self, tlc5947, channel):
 95              self._tlc5947 = tlc5947
 96              self._channel = channel
 97  
 98          @property
 99          def duty_cycle(self):
100              """Get and set the 16-bit PWM duty cycle value for this channel.
101              """
102              raw_value = self._tlc5947._get_gs_value(self._channel)
103              # Convert to 16-bit value from 12-bits and return it.
104              return (raw_value << 4) & 0xFFFF
105  
106          @duty_cycle.setter
107          def duty_cycle(self, val):
108              if val < 0 or val > 65535:
109                  raise ValueError(
110                      "PWM intensity {0} outside supported range [0;65535]".format(val)
111                  )
112              # Convert to 12-bit value (quantization error will occur!).
113              val = (val >> 4) & 0xFFF
114              self._tlc5947._set_gs_value(self._channel, val)
115  
116          @property
117          def frequency(self):
118              """Frequency of the PWM channel, note you cannot change this and
119              cannot read its exact value (it varies from 2.4-5.6 mhz, see the
120              TLC5947 datasheet).
121              """
122              return 0
123  
124          # pylint bug misidentifies the following as a regular function instead
125          # of the associated setter: https://github.com/PyCQA/pylint/issues/870
126          # Must disable a few checks to make pylint happy (ugh).
127          # pylint: disable=no-self-use,unused-argument
128          @frequency.setter
129          def frequency(self, val):
130              raise RuntimeError("Cannot set TLC5947 PWM frequency!")
131  
132          # pylint: enable=no-self-use,unused-argument
133  
134      def __init__(self, spi, latch, *, auto_write=True, num_drivers=1):
135          if num_drivers < 1:
136              raise ValueError(
137                  "Need at least one driver; {0} is not supported.".format(num_drivers)
138              )
139          self._spi = spi
140          self._latch = latch
141          self._latch.switch_to_output(value=False)
142          # This device is just a big 36*n byte long shift register.  There's no
143          # fancy protocol or other commands to send, just write out all 288*n
144          # bits every time the state is updated.
145          self._n = num_drivers
146          self._shift_reg = bytearray(_STOREBYTES * self._n)
147          # Save auto_write state (i.e. push out shift register values on
148          # any channel value change).
149          self.auto_write = auto_write
150  
151      def write(self):
152          """Write out the current channel PWM values to the chip.  This is only
153          necessary to call if you disabled auto_write in the initializer,
154          otherwise write is automatically called on any channel update.
155          """
156          # Write out the current state to the shift register.
157          try:
158              # Lock the SPI bus and configure it for the shift register.
159              while not self._spi.try_lock():
160                  pass
161              self._spi.configure(baudrate=1000000, polarity=0, phase=0, bits=8)
162              # First ensure latch is low.
163              self._latch.value = False
164              # Write out the bits.
165              self._spi.write(self._shift_reg, start=0, end=_STOREBYTES * self._n + 1)
166              # Then toggle latch high and low to set the value.
167              self._latch.value = True
168              self._latch.value = False
169          finally:
170              # Ensure the SPI bus is unlocked.
171              self._spi.unlock()
172  
173      def _get_gs_value(self, channel):
174          # pylint: disable=no-else-return
175          # Disable should be removed when refactor can be tested
176          if channel < 0 or channel >= _CHANNELS * self._n:
177              raise ValueError(
178                  "Channel {0} not available with {1} board(s).".format(channel, self._n)
179              )
180          # Invert channel position as the last channel needs to be written first.
181          # I.e. is in the first position of the shift registr.
182          channel = _CHANNELS * self._n - 1 - channel
183          # Calculate exact bit position within the shift register.
184          bit_offset = channel * 12
185          # Now calculate the byte that this position falls within and any offset
186          # from the left inside that byte.
187          byte_start = bit_offset // 8
188          start_offset = bit_offset % 8
189          # Grab the high and low bytes.
190          high_byte = self._shift_reg[byte_start]
191          low_byte = self._shift_reg[byte_start + 1]
192          if start_offset == 4:
193              # Value starts in the lower 4 bits of the high bit so you can
194              # just concat high with low byte and return the 12-bit value.
195              return ((high_byte << 8) | low_byte) & 0xFFF
196          elif start_offset == 0:
197              # Value starts in the entire high byte and spills into upper
198              # 4 bits of low byte.  Shift low byte and concat values.
199              return ((high_byte << 4) | (low_byte >> 4)) & 0xFFF
200          else:
201              raise RuntimeError("Unsupported bit offset!")
202  
203      def _set_gs_value(self, channel, val):
204          if channel < 0 or channel >= _CHANNELS * self._n:
205              raise ValueError(
206                  "Channel {0} not available with {1} board(s).".format(channel, self._n)
207              )
208          if val < 0 or val > 4095:
209              raise ValueError(
210                  "PWM intensity {0} outside supported range [0;4095]".format(val)
211              )
212  
213          # Invert channel position as the last channel needs to be written first.
214          # I.e. is in the first position of the shift registr.
215          channel = _CHANNELS * self._n - 1 - channel
216          # Calculate exact bit position within the shift register.
217          bit_offset = channel * 12
218          # Now calculate the byte that this position falls within and any offset
219          # from the left inside that byte.
220          byte_start = bit_offset // 8
221          start_offset = bit_offset % 8
222          # Grab the high and low bytes.
223          high_byte = self._shift_reg[byte_start]
224          low_byte = self._shift_reg[byte_start + 1]
225          if start_offset == 4:
226              # Value starts in the lower 4 bits of the high bit.
227              high_byte &= 0b11110000
228              high_byte |= val >> 8
229              low_byte = val & 0xFF
230          elif start_offset == 0:
231              # Value starts in the entire high byte and spills into upper
232              # 4 bits of low byte.
233              high_byte = (val >> 4) & 0xFF
234              low_byte &= 0b00001111
235              low_byte |= (val << 4) & 0xFF
236          else:
237              raise RuntimeError("Unsupported bit offset!")
238          self._shift_reg[byte_start] = high_byte
239          self._shift_reg[byte_start + 1] = low_byte
240          # Write the updated shift register values if required.
241          if self.auto_write:
242              self.write()
243  
244      def create_pwm_out(self, channel):
245          """Create an instance of a PWMOut-like class that mimics the built-in
246          CircuitPython PWMOut class but is associated with the TLC5947 channel
247          that is specified.  This PWMOut class has a duty_cycle property which
248          you can read and write with a 16-bit value to control the channel.
249          Note there will be quantization error as the chip only supports 12-bit
250          PWM, if this is problematic use the item accessor approach to update
251          the raw 12-bit channel values.
252          """
253          return self.PWMOut(self, channel)
254  
255      # Define index and length properties to set and get each channel's raw
256      # 12-bit value (useful for changing channels without quantization error
257      # like when using the PWMOut mock class).
258      def __len__(self):
259          """Retrieve the total number of PWM channels available."""
260          return _CHANNELS * self._n  # number channels times number chips.
261  
262      def __getitem__(self, key):
263          """Retrieve the 12-bit PWM value for the specified channel (0-max).
264          max depends on the number of boards.
265          """
266          if key < 0:  # allow reverse adressing with negative index
267              key = key + _CHANNELS * self._n
268          return self._get_gs_value(key)  # does parameter checking
269  
270      def __setitem__(self, key, val):
271          """Set the 12-bit PWM value (0-4095) for the specified channel (0-max).
272          max depends on the number of boards.
273          If auto_write is enabled (the default) then the chip PWM state will
274          immediately be updated too, otherwise you must call write to update
275          the chip with the new PWM state.
276          """
277          if key < 0:  # allow reverse adressing with negative index
278              key = key + _CHANNELS * self._n
279          self._set_gs_value(key, val)  # does parameter checking