From e31677ad327f8676a95a4dc802f617ee3439fa8f Mon Sep 17 00:00:00 2001 From: xonestonex <51967236+xonestonex@users.noreply.github.com> Date: Sat, 15 Jan 2022 20:09:54 +0100 Subject: [PATCH] Add support for TLC5947 24-Channel PWM driver. (#551) * Add support for TLC5947 24-Channel PWM driver. * Refactor code to match coding guidelines and better documentation. --- keywords.txt | 8 ++ src/NeoPixelBrightnessBus.h | 6 + src/NeoPixelBus.h | 8 ++ src/internal/Tlc5947GenericMethod.h | 208 ++++++++++++++++++++++++++++ src/internal/TwoWireSpiImple.h | 22 +++ 5 files changed, 252 insertions(+) create mode 100644 src/internal/Tlc5947GenericMethod.h diff --git a/keywords.txt b/keywords.txt index 275839b..0b4cf05 100644 --- a/keywords.txt +++ b/keywords.txt @@ -568,6 +568,14 @@ P9813Spi2MhzMethod KEYWORD1 P9813Spi1MhzMethod KEYWORD1 P9813Spi500KhzMethod KEYWORD1 P9813SpiHzMethod KEYWORD1 +Tlc5947Method KEYWORD1 +Tlc5947Method16Bit KEYWORD1 +Tlc5947Spi30MhzMethod KEYWORD1 +Tlc5947Spi30MhzMethod16Bit KEYWORD1 +Tlc5947Spi15MhzMethod KEYWORD1 +Tlc5947Spi15MhzMethod16Bit KEYWORD1 +Tlc5947SpiMethod KEYWORD1 +Tlc5947SpiMethod16Bit KEYWORD1 NeoPixelAnimator KEYWORD1 AnimUpdateCallback KEYWORD1 AnimationParam KEYWORD1 diff --git a/src/NeoPixelBrightnessBus.h b/src/NeoPixelBrightnessBus.h index e110fdf..8dba1b1 100644 --- a/src/NeoPixelBrightnessBus.h +++ b/src/NeoPixelBrightnessBus.h @@ -83,6 +83,12 @@ public: { } + NeoPixelBrightnessBus(uint16_t countPixels, uint8_t pinClock, uint8_t pinData, uint8_t pinLatch, uint8_t pinOutputEnable = NOT_A_PIN) : + NeoPixelBus(countPixels, pinClock, pinData, pinLatch, pinOutputEnable), + _brightness(255) + { + } + NeoPixelBrightnessBus(uint16_t countPixels) : NeoPixelBus(countPixels), _brightness(255) diff --git a/src/NeoPixelBus.h b/src/NeoPixelBus.h index 489432e..3ec08b4 100644 --- a/src/NeoPixelBus.h +++ b/src/NeoPixelBus.h @@ -95,6 +95,7 @@ License along with NeoPixel. If not, see #include "internal/Lpd6803GenericMethod.h" #include "internal/Ws2801GenericMethod.h" #include "internal/P9813GenericMethod.h" +#include "internal/Tlc5947GenericMethod.h" #if defined(ARDUINO_ARCH_ESP8266) @@ -153,6 +154,13 @@ public: { } + NeoPixelBus(uint16_t countPixels, uint8_t pinClock, uint8_t pinData, uint8_t pinLatch, uint8_t pinOutputEnable = NOT_A_PIN) : + _countPixels(countPixels), + _state(0), + _method(pinClock, pinData, pinLatch, pinOutputEnable, countPixels, T_COLOR_FEATURE::PixelSize, T_COLOR_FEATURE::SettingsSize) + { + } + NeoPixelBus(uint16_t countPixels) : _countPixels(countPixels), _state(0), diff --git a/src/internal/Tlc5947GenericMethod.h b/src/internal/Tlc5947GenericMethod.h new file mode 100644 index 0000000..501d913 --- /dev/null +++ b/src/internal/Tlc5947GenericMethod.h @@ -0,0 +1,208 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Tlc5947 24 channel PWM controller using general Pins. + +Written by Michael C. Miller. +Written by Dennis Kasprzyk. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +// must also check for arm due to Teensy incorrectly having ARDUINO_ARCH_AVR set +#if defined(ARDUINO_ARCH_AVR) && !defined(__arm__) +#include "TwoWireBitBangImpleAvr.h" +#else +#include "TwoWireBitBangImple.h" +#endif + +#define TLC5947_MODULE_PWM_CHANNEL_COUNT 24 + +class Tlc5947Converter8Bit +{ +public: + static const size_t sizeChannel = 1; + static void ConvertFrame(uint8_t* sendBufferPtr, uint8_t* channelPtr) + { + // Write 2 channels into 3 bytes scaling 8-bit to 12-bit per channel + for (int indexChannel = 0; indexChannel < TLC5947_MODULE_PWM_CHANNEL_COUNT; indexChannel += 2) + { + *sendBufferPtr++ = *channelPtr; + *sendBufferPtr++ = (*channelPtr-- & 0xf0) | (*channelPtr >> 4); + *sendBufferPtr++ = ((*channelPtr << 4) & 0xf0) | (*channelPtr-- >> 4); + } + } +}; + +class Tlc5947Converter16Bit +{ +public: + static const size_t sizeChannel = 2; + static void ConvertFrame(uint8_t* sendBufferPtr, uint8_t* sourceBufferPtr) + { + uint16_t* channelPtr = (uint16_t*)sourceBufferPtr; + + // Write 2 channels into 3 bytes using upper 12-bit of each channel + for (int indexChannel = 0; indexChannel < TLC5947_MODULE_PWM_CHANNEL_COUNT; indexChannel += 2) + { + *sendBufferPtr++ = *channelPtr >> 8; + *sendBufferPtr++ = (*channelPtr-- & 0xf0) | (*channelPtr >> 12); + *sendBufferPtr++ = *channelPtr-- >> 4; + } + } +}; + + +template class Tlc5947MethodBase +{ +public: + typedef typename T_TWOWIRE::SettingsObject SettingsObject; + + // 24 channel * 12 bit + static const size_t sizeSendBuffer = 36; + + Tlc5947MethodBase(uint8_t pinClock, uint8_t pinData, uint8_t pinLatch, uint8_t pinOutputEnable, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + _countModule((pixelCount * elementSize + TLC5947_MODULE_PWM_CHANNEL_COUNT - 1) / TLC5947_MODULE_PWM_CHANNEL_COUNT), + _sizeData(_countModule * TLC5947_MODULE_PWM_CHANNEL_COUNT + settingsSize), + _wire(pinClock, pinData), + _pinLatch(pinLatch), + _pinOutputEnable(pinOutputEnable) + { + _data = static_cast(malloc(_sizeData)); + pinMode(pinLatch, OUTPUT); + pinMode(pinOutputEnable, OUTPUT); + digitalWrite(pinOutputEnable, HIGH); + } + + Tlc5947MethodBase(uint8_t pinClock, uint8_t pinData, uint8_t pinLatch, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + Tlc5947MethodBase(pinClock, pinData, pinLatch, -1, pixelCount, elementSize, settingsSize) + { + } + +#if !defined(__AVR_ATtiny85__) && !defined(ARDUINO_attiny) + Tlc5947MethodBase(uint8_t pinLatch, uint8_t pinOutputEnable, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + Tlc5947MethodBase(SCK, MOSI, pinLatch, pinOutputEnable, pixelCount, elementSize, settingsSize) + { + } + + Tlc5947MethodBase(uint8_t pinLatch, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + Tlc5947MethodBase(SCK, MOSI, pinLatch, -1, pixelCount, elementSize, settingsSize) + { + } +#endif + + ~Tlc5947MethodBase() + { + free(_data); + pinMode(_pinLatch, INPUT); + pinMode(_pinOutputEnable, INPUT); + } + + bool IsReadyToUpdate() const + { + return true; // dot stars don't have a required delay + } + +#if defined(ARDUINO_ARCH_ESP32) + void Initialize(int8_t sck, int8_t miso, int8_t mosi, int8_t ss) + { + _wire.begin(sck, miso, mosi, ss); + } +#endif + + void Initialize() + { + _wire.begin(); + memset(_data, 0, _sizeData); + } + + void Update(bool) + { + + digitalWrite(_pinOutputEnable, HIGH); + + digitalWrite(_pinLatch, LOW); + _wire.beginTransaction(); + + // We need to write the channels in reverse order. Get a Pointer to the last channel. + uint8_t* lastChannelPtr = _data + ((_countModule * TLC5947_MODULE_PWM_CHANNEL_COUNT - 1) * T_BITCONVERT::sizeChannel); + for (uint16_t countSend = 0; countSend < _countModule; countSend++) + { + // We pass a pointer to the last channel and ConvertFrame reads the channels backwards + T_BITCONVERT::ConvertFrame(_sendBuffer, lastChannelPtr); + _wire.transmitBytes(_sendBuffer, sizeSendBuffer); + lastChannelPtr -= TLC5947_MODULE_PWM_CHANNEL_COUNT * T_BITCONVERT::sizeChannel; + } + + _wire.endTransaction(); + digitalWrite(_pinLatch, HIGH); + digitalWrite(_pinLatch, LOW); + digitalWrite(_pinOutputEnable, LOW); + } + + uint8_t* getData() const + { + return _data; + }; + + size_t getDataSize() const + { + return _sizeData; + }; + + void applySettings(const SettingsObject& settings) + { + _wire.applySettings(settings); + } + +private: + const uint16_t _countModule; // Number of tlc5947 modules + const size_t _sizeData; // Size of '_data' buffer below + + T_TWOWIRE _wire; + uint8_t* _data; // Holds LED color values + uint8_t _sendBuffer[sizeSendBuffer]; // Holds channel values for one module + uint8_t _pinLatch; + uint8_t _pinOutputEnable; +}; + +typedef Tlc5947MethodBase Tlc5947Method; +typedef Tlc5947MethodBase Tlc5947Method16Bit; + +#if !defined(__AVR_ATtiny85__) && !defined(ARDUINO_attiny) +#include "TwoWireSpiImple.h" + +// for standalone +typedef Tlc5947MethodBase> Tlc5947Spi30MhzMethod; +typedef Tlc5947MethodBase> Tlc5947Spi30MhzMethod16Bit; + +// for cascaded devices +typedef Tlc5947MethodBase> Tlc5947Spi15MhzMethod; +typedef Tlc5947MethodBase> Tlc5947Spi15MhzMethod16Bit; + +typedef Tlc5947MethodBase> Tlc5947SpiMethod; +typedef Tlc5947MethodBase> Tlc5947SpiMethod16Bit; + + +#endif + + + diff --git a/src/internal/TwoWireSpiImple.h b/src/internal/TwoWireSpiImple.h index 906d86b..d581555 100644 --- a/src/internal/TwoWireSpiImple.h +++ b/src/internal/TwoWireSpiImple.h @@ -39,6 +39,17 @@ public: static const uint32_t Clock = 40000000L; }; +class SpiSpeed30Mhz +{ +public: + typedef NeoNoSettings SettingsObject; + SpiSpeed30Mhz() {}; + + static void applySettings(const SettingsObject& settings) {} + + static const uint32_t Clock = 30000000L; +}; + class SpiSpeed20Mhz { public: @@ -50,6 +61,17 @@ public: static const uint32_t Clock = 20000000L; }; +class SpiSpeed15Mhz +{ +public: + typedef NeoNoSettings SettingsObject; + SpiSpeed15Mhz() {}; + + static void applySettings(const SettingsObject& settings) {} + + static const uint32_t Clock = 15000000L; +}; + class SpiSpeed10Mhz { public: