Skip to content
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

ESP32 support #24

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
Fork of https://github.com/PaulStoffregen/Tlc5940 with ESP32 support

Tested on ESP32, but should work on ESP32-S2, S3, etc. with no or minimal mods
Requires the following board support package in Arduino IDE (Tools -> Board -> Boards Manager…): esp32 by Espressif Systems

DOT Correction can be set for each channel individually.
Animation doesn't work as PROGMEM snippets for ESP32 were not finished.

# Tlc5940 Library

16 channel PWM LED driver based on the Texas Instruments TLC5940 chip.
Expand Down
261 changes: 241 additions & 20 deletions Tlc5940.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
#include "pinouts/pin_functions.h"


int sin_pin;
int sout_pin;
int sclk_pin;
int xlat_pin;
int blank_pin;
int gsclk_pin;
int vprg_pin;
int xerr_pin;

/** This will be true (!= 0) if update was just called and the data has not
been latched in yet. */
volatile uint8_t tlc_needXLAT;
Expand All @@ -49,6 +58,9 @@ volatile void (*tlc_onUpdateFinished)(void);
the array is the same as the format of the TLC's serial interface. */
uint8_t tlc_GSData[NUM_TLCS * 24];

/** Packed DOT Correction data, 12 bytes (16 * 6 bits) per TLC. */
uint8_t tlc_DCData[NUM_TLCS * 12];

/** Don't add an extra SCLK pulse after switching from dot-correction mode. */
static uint8_t firstGSInput;

Expand Down Expand Up @@ -85,6 +97,37 @@ void ftm1_isr(void)
Tlc5940_interrupt();
}
#endif

#elif defined (ARDUINO_ARCH_ESP32)
portMUX_TYPE TLC5940_timerMux = portMUX_INITIALIZER_UNLOCKED;
// volatile int TLC5940_xlat_enabled = 0;
// volatile int TLC5940_interrupt_enabled = 0;

void IRAM_ATTR TLC5940_onTimer()
{
portENTER_CRITICAL_ISR(&TLC5940_timerMux);
int xlat_enabled = Tlc.TLC5940_xlat_enabled;
int interrupt_enabled = Tlc.TLC5940_interrupt_enabled;
portEXIT_CRITICAL_ISR(&TLC5940_timerMux);

// BLANK, XLAT
set_pin(blank_pin);
if (xlat_enabled)
{
set_pin(xlat_pin);
clear_pin(xlat_pin);
}
clear_pin(blank_pin);

if (interrupt_enabled)
Tlc5940_interrupt();
}
#endif

#if defined (ARDUINO_ARCH_ESP32)
// SPI to send gray-scale data
const uint32_t TLC5940_SPI_CLK = 8000000; // 8MHz
SPIClass* TLC5940_vspi = NULL;
#endif

/** \defgroup ReqVPRG_ENABLED Functions that Require VPRG_ENABLED
Expand All @@ -107,20 +150,52 @@ void ftm1_isr(void)
zeros, or whatever initialValue is set to and the Timers will start.
\param initialValue = 0, optional parameter specifing the inital startup
value */
void Tlc5940::init(uint16_t initialValue)
void Tlc5940::init( uint16_t initialValue, int sin, int sout, int sclk, int xlat, int blank, int gsclk, int vprg, int xerr )
{
/* Pin Setup */
#if defined (ARDUINO_ARCH_ESP32)
sin_pin = sin;
sout_pin = sout;
sclk_pin = sclk;
xlat_pin = xlat;
blank_pin = blank;
gsclk_pin = gsclk;
vprg_pin = vprg;
xerr_pin = xerr;

output_pin(xlat_pin);
output_pin(blank_pin);
clear_pin(xlat_pin);
set_pin(blank_pin); // start with BLANK
#if VPRG_ENABLED
if (vprg_pin >= 0)
{
output_pin(vprg_pin);
clear_pin(vprg_pin); // LOW: GS-reg, HIGH: DC-reg
}
#endif
#if XERR_ENABLED
if (xerr_pin >= 0)
{
pullup_pin(xerr_pin);
}
#endif
set_pin(blank_pin); // leave blank high (until the timers start)
#elif
output_pin(XLAT_DDR, XLAT_PIN);
output_pin(BLANK_DDR, BLANK_PIN);
output_pin(GSCLK_DDR, GSCLK_PIN);
#if VPRG_ENABLED

#if VPRG_ENABLED
output_pin(VPRG_DDR, VPRG_PIN);
clear_pin(VPRG_PORT, VPRG_PIN); // grayscale mode (VPRG low)
#endif
#if XERR_ENABLED
#endif
#if XERR_ENABLED
pullup_pin(XERR_DDR, XERR_PORT, XERR_PIN); // XERR as input, enable pull-up resistor
#endif

set_pin(BLANK_PORT, BLANK_PIN); // leave blank high (until the timers start)
#endif
set_pin(BLANK_PORT, BLANK_PIN); // leave blank high (until the timers start)

tlc_shift8_init();

Expand All @@ -129,7 +204,11 @@ void Tlc5940::init(uint16_t initialValue)
disable_XLAT_pulses();
clear_XLAT_interrupt();
tlc_needXLAT = 0;
pulse_pin(XLAT_PORT, XLAT_PIN);
#if defined (ARDUINO_ARCH_ESP32)
pulse_pin(xlat_pin);
#elif
pulse_pin(XLAT_PORT, XLAT_PIN);
#endif


/* Timer Setup */
Expand Down Expand Up @@ -218,6 +297,28 @@ void Tlc5940::init(uint16_t initialValue)
NVIC_ENABLE_IRQ(IRQ_FTM1);
CORE_PIN4_CONFIG = PORT_PCR_MUX(3)|PORT_PCR_DSE|PORT_PCR_SRE;
#endif

#elif defined(ARDUINO_ARCH_ESP32)
const uint8_t TIMER_NUM = 0; // hardware timer number
const uint16_t TIMER_DIV = 80; // timer divider to make 1MHz from 80MHz
const uint64_t TIMER_ALM = 2050; // interrupt every 2.050ms
hw_timer_t* TLC5940_timer = NULL;

// start timer
TLC5940_timer = timerBegin(TIMER_NUM, TIMER_DIV, true); // increment mode
timerAttachInterrupt(TLC5940_timer, &TLC5940_onTimer, true); // edge mode
timerAlarmWrite(TLC5940_timer, TIMER_ALM, true); // auto-reload mode
timerAlarmEnable(TLC5940_timer);

// LEDC for GS(Gray-scale) clock
const uint8_t TLC5940_LEDC_CHN = 0; // LEDC channel
const double TLC5940_LEDC_FRQ = 2e6; // LEDC frequency 2MHz
const uint8_t TLC5940_LEDC_RSL = 5; // LEDC resolution 5bit = 32
const uint32_t TLC5940_LEDC_DTY = 8; // LEDC duty 25% .. 8 / 32

ledcSetup(TLC5940_LEDC_CHN, TLC5940_LEDC_FRQ, TLC5940_LEDC_RSL);
ledcAttachPin(gsclk_pin, TLC5940_LEDC_CHN);
ledcWrite(TLC5940_LEDC_CHN, TLC5940_LEDC_DTY);
#endif
update();
}
Expand Down Expand Up @@ -249,7 +350,14 @@ uint8_t Tlc5940::update(void)
// adds an extra SCLK pulse unless we've just set dot-correction data
firstGSInput = 0;
} else {
#if defined (ARDUINO_ARCH_ESP32)
TLC5940_vspi->end(); // Release SCLK pin
output_pin(sclk_pin);
pulse_pin(sclk_pin);
TLC5940_vspi->begin(sclk_pin, sout_pin, sin_pin, -1);
#elif
pulse_pin(SCLK_PORT, SCLK_PIN);
#endif
}
uint8_t *p = tlc_GSData;
while (p < tlc_GSData + NUM_TLCS * 24) {
Expand Down Expand Up @@ -319,12 +427,85 @@ void Tlc5940::setAll(uint16_t value)

#if VPRG_ENABLED

static inline uint8_t byte_mask(uint8_t bit_ofs, uint8_t bit_num)
{
uint8_t result = 0;
uint8_t num = bit_num;
for (uint8_t i = 0; i < 8; i++)
{
if (i >= bit_ofs && num)
{
result |= (uint8_t)(1 << i);
num--;
}
}
return result;
}

/** \addtogroup ReqVPRG_ENABLED
From the \ref CoreFunctions "Core Functions":
- \link Tlc5940::setAllDC Tlc.setAllDC(uint8_t value(0-63)) \endlink - sets
all the dot correction data to value */
/* @{ */

/** Shifts in the data from the DOT Correction data array, #tlc_DCData.
*/
void Tlc5940::updateDC(void)
{
tlc_dcModeStart();

uint8_t *p = tlc_DCData;
while (p < tlc_DCData + NUM_TLCS * 12) {
tlc_shift8(*p++);
tlc_shift8(*p++);
tlc_shift8(*p++);
}

// dot correction data latch
#if defined (ARDUINO_ARCH_ESP32)
pulse_pin(xlat_pin);
#elif
pulse_pin(XLAT_PORT, XLAT_PIN);
#endif

tlc_dcModeStop();
}

/** Sets channel to value in the DOT Correction data array, #tlc_DCData.
\param channel (0 to #NUM_TLCS * 16 - 1). OUT0 of the first TLC is
channel 0, OUT0 of the next TLC is channel 16, etc.
\param value (0-63). The DOT Correction value, 63 is maximum.
\see get */
void Tlc5940::setDC(TLC_CHANNEL_TYPE channel, uint8_t value)
{
TLC_CHANNEL_TYPE index8 = (NUM_TLCS * 16 - 1) - channel;
uint8_t *index6p = tlc_DCData + ((((uint16_t)index8) * 3) >> 2);
int8_t offset = ((((uint16_t)index8) * 3) % 4) * 2;

value &= 0x3F;

*index6p = (*index6p & ~byte_mask(offset, min(8 - offset, 6))) | (value << offset);
*(index6p+1) = (*(index6p+1) & ~byte_mask(0, max(offset - 2, 0))) | (value >> min(8 - offset, 6));
}

/** Gets the current DOT Correction value for a channel
\param channel (0 to #NUM_TLCS * 16 - 1). OUT0 of the first TLC is
channel 0, OUT0 of the next TLC is channel 16, etc.
\returns current DOT Correction value (0 - 63) for channel
\see set */
uint8_t Tlc5940::getDC(TLC_CHANNEL_TYPE channel)
{
TLC_CHANNEL_TYPE index8 = (NUM_TLCS * 16 - 1) - channel;
uint8_t *index6p = tlc_DCData + ((((uint16_t)index8) * 3) >> 2);
int8_t offset = ((((uint16_t)index8) * 3) % 4) * 2;
uint8_t result = 0;

result |= (*index6p & byte_mask(offset, min(8 - offset, 6))) >> offset;
result |= (*(index6p+1) & byte_mask(0, max(offset - 2, 0))) << min(8 - offset, 6);

return result;
}

/** Sets the dot correction for all channels to value. The dot correction
value correspondes to maximum output current by
\f$\displaystyle I_{OUT_n} = I_{max} \times \frac{DCn}{63} \f$
Expand All @@ -335,20 +516,15 @@ void Tlc5940::setAll(uint16_t value)
\param value (0-63) */
void Tlc5940::setAllDC(uint8_t value)
{
tlc_dcModeStart();

uint8_t firstByte = value << 2 | value >> 4;
uint8_t secondByte = value << 4 | value >> 2;
uint8_t thirdByte = value << 6 | value;

for (TLC_CHANNEL_TYPE i = 0; i < NUM_TLCS * 12; i += 3) {
tlc_shift8(firstByte);
tlc_shift8(secondByte);
tlc_shift8(thirdByte);
uint8_t *p = tlc_DCData;
while (p < tlc_DCData + NUM_TLCS * 12) {
*p++ = firstByte;
*p++ = secondByte;
*p++ = thirdByte;
}
pulse_pin(XLAT_PORT, XLAT_PIN);

tlc_dcModeStop();
}

/* @} */
Expand All @@ -361,13 +537,28 @@ void Tlc5940::setAllDC(uint8_t value)
\returns 1 if a TLC is reporting an error, 0 otherwise. */
uint8_t Tlc5940::readXERR(void)
{
return ((XERR_PINS & _BV(XERR_PIN)) == 0);
#if defined (ARDUINO_ARCH_ESP32)
return (digitalRead(xerr_pin) == LOW);
#elif
return ((XERR_PINS & _BV(XERR_PIN)) == 0);
#endif
}

#endif

/* @} */

#if defined (ARDUINO_ARCH_ESP32)
void Tlc5940::XLAT_mode(int val)
{
TLC5940_xlat_enabled = val;
}
void Tlc5940::INT_mode(int val)
{
TLC5940_interrupt_enabled = val;
}
#endif

#if DATA_TRANSFER_MODE == TLC_BITBANG

/** Sets all the bit-bang pins to output */
Expand All @@ -393,6 +584,27 @@ void tlc_shift8(uint8_t byte)

#elif DATA_TRANSFER_MODE == TLC_SPI

#if defined (ARDUINO_ARCH_ESP32)

/** Initializes the SPI module */

void tlc_shift8_init(void)
{
TLC5940_vspi = new SPIClass(VSPI);
TLC5940_vspi->begin(sclk_pin, sout_pin, sin_pin, -1);
}

/** Shifts out a byte, MSB first */
void tlc_shift8(uint8_t byte)
{
TLC5940_vspi->beginTransaction(SPISettings(TLC5940_SPI_CLK, MSBFIRST, SPI_MODE0));
TLC5940_vspi->transfer(byte);
TLC5940_vspi->endTransaction();
}

#elif


/** Initializes the SPI module to double speed (f_osc / 2) */
void tlc_shift8_init(void)
{
Expand All @@ -414,7 +626,7 @@ void tlc_shift8(uint8_t byte)
while (!(SPSR & _BV(SPIF)))
; // wait for transmission complete
}

#endif
#endif

#if VPRG_ENABLED
Expand All @@ -425,13 +637,22 @@ void tlc_dcModeStart(void)
disable_XLAT_pulses(); // ensure that no latches happen
clear_XLAT_interrupt(); // (in case this was called right after update)
tlc_needXLAT = 0;
set_pin(VPRG_PORT, VPRG_PIN); // dot correction mode
#if defined (ARDUINO_ARCH_ESP32)
set_pin(vprg_pin); // dot correction mode
#elif
set_pin(VPRG_PORT, VPRG_PIN); // dot correction mode
#endif

}

/** Switches back to grayscale mode. */
void tlc_dcModeStop(void)
{
clear_pin(VPRG_PORT, VPRG_PIN); // back to grayscale mode
#if defined (ARDUINO_ARCH_ESP32)
clear_pin(vprg_pin); // back to grayscale mode
#elif
clear_pin(VPRG_PORT, VPRG_PIN); // back to grayscale mode
#endif
firstGSInput = 1;
}

Expand Down
Loading