Skip to content

Commit

Permalink
Added a couple more example sketches
Browse files Browse the repository at this point in the history
  • Loading branch information
mattshepcar committed Jul 7, 2020
1 parent 04f2c9d commit cbf57e5
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 3 deletions.
168 changes: 168 additions & 0 deletions examples/SmoothPulseInterrupt/SmoothPulseInterrupt.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#include <SmoothLed.h>
#include <avr/wdt.h>
#include <avr/sleep.h>

// This is an interrupt driven version of the SmoothPulse example.
// It only works in SPI mode because it's not currently possible to
// override megaTinyCore's USART DRE interrupt.

// Enabling BUFFER_LED_DATA will cause the final data values for
// all the LEDs to be calculated before anything is sent to the LED
// strip. This is fast, requiring around 58 cycles per byte, but
// it requires additional memory. The non-buffered method calculates
// each LED data byte during the interrupt routine which adds a lot
// of extra overhead.

// This example fades WS2812B LEDs between 3 different colours.
// Interpolation, dithering and gamma correction are employed to
// ensure a smooth fade at low intensities.

#define NUM_LEDS 8
#define LED_CHANNELS 4 // use 4 for RGBW strands
#define UPDATE_INTERVAL_US 1000 // how long between updates
#define USE_TCA0_TIMING 1 // use timer interrupt for more accurate timing
#define BUFFER_LED_DATA 1 // enabling this uses an extra byte of memory per LED component but is faster
uint8_t r = 0x30, g = 0, b = 0; // initial colour

// Each LED component (R,G,B) requires 5 bytes of memory to store its current
// and target colours and its dithering state.
SmoothLed::Interpolator interpolators[NUM_LEDS * LED_CHANNELS];
SmoothLed leds(interpolators, NUM_LEDS * LED_CHANNELS);

void prepareLedData();
void setupPeriodicTimer(int microSeconds);
void waitForTimer();

void setup()
{
// initialise all LED values to 0
leds.clear();

leds.begin(
SmoothLedCcl::PA7_LUT1, // pin where LED data line is connected
SmoothLedCcl::PA3_SPI0_ASYNCCH0); // this pin will be an output but is only used for the clock signal

setupPeriodicTimer(UPDATE_INTERVAL_US);
}

void loop()
{
// reset hardware watchdog (might be enabled in fuses)
wdt_reset();

if (!leds.isFading())
{
// set target colours
for (uint8_t i = 0; i < NUM_LEDS; ++i)
{
leds.setFadeTarget(i * LED_CHANNELS + 0, g);
leds.setFadeTarget(i * LED_CHANNELS + 1, r);
leds.setFadeTarget(i * LED_CHANNELS + 2, b);
}
// fade over next 1000 updates (1 second at 1kHz)
leds.beginFade(1000);

// cycle to next colour
uint8_t t = r; r = g; g = b; b = t;
}

// make sure previous LED data has been sent (waits until interrupt has turned off)
while (SPI0.INTCTRL & SPI_DREIE_bm) { }
// get the LED data ready
prepareLedData();
// wait for 1kHz interval to start sending
waitForTimer();
// set up SPI for writing to LED strip
leds.beginTransactionSpi();
// enable 'data register empty' interrupt which will initiate the send
SPI0.INTCTRL = SPI_DREIE_bm;


// other work could be performed here while the LEDs update in the background
}

volatile uint8_t ledDataPos = 0;
#if BUFFER_LED_DATA
uint8_t ledData[NUM_LEDS * LED_CHANNELS];
void prepareLedData()
{
leds.update(ledData);
ledDataPos = 0;
}

ISR(SPI0_INT_vect)
{
// clear interrupt flag
SPI0.INTFLAGS = SPI_DREIF_bm;

// send data
uint8_t pos = ledDataPos;
SPI0.DATA = ledData[pos++];
ledDataPos = pos;

// turn off the interrupt when we're done
if (pos == sizeof(ledData))
SPI0.INTCTRL = 0;
}
#else
volatile uint8_t ledDeltaTime;
volatile uint8_t nextLedData;
void prepareNextLedByte()
{
nextLedData = interpolators[ledDataPos++].update(ledDeltaTime, leds.getGammaLut(), leds.getMaxValue(), leds.getDitherMask());
}
void prepareLedData()
{
ledDeltaTime = leds.updateTime();
ledDataPos = 0;
prepareNextLedByte();
}
ISR(SPI0_INT_vect)
{
// clear interrupt flag
SPI0.INTFLAGS = SPI_DREIF_bm;
// send next data
SPI0.DATA = nextLedData;
// turn off the interrupt when we're done
if (ledDataPos == NUM_LEDS * LED_CHANNELS)
SPI0.INTCTRL = 0;
else
prepareNextLedByte();
}
#endif

#if USE_TCA0_TIMING
void setupPeriodicTimer(int microSeconds)
{
// turn off split mode as per megaTinyCore guide
TCA0.SPLIT.CTRLA = 0;
TCA0.SPLIT.CTRLESET = TCA_SPLIT_CMD_RESET_gc | 0x03;
TCA0.SPLIT.CTRLD = 0;

// set up periodic timer
TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm;
TCA0.SINGLE.PER = (F_CPU * 1e-6 * microSeconds + 15) / 16 - 1;
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV16_gc | TCA_SINGLE_ENABLE_bm;
}
volatile bool wakeup = false;
ISR(TCA0_OVF_vect)
{
wakeup = true;
TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm;
}
void waitForTimer()
{
while (!wakeup)
sleep_mode();
wakeup = false;
}
#else
void setupPeriodicTimer(int){}
void waitForTimer()
{
// roughly 10us per 8 bits of data
int timeToWriteLeds = NUM_LEDS * LED_CHANNELS * 10;
// need a minimum of 50us to reset LEDs
delayMicroseconds(max(50, UPDATE_INTERVAL_US - timeToWriteLeds));
}
#endif
134 changes: 134 additions & 0 deletions examples/TwoStrips/TwoStrips.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#include <SmoothLed.h>
#include <avr/wdt.h>
#include <avr/sleep.h>

// This example updates two WS2812B LED strips at the same time,
// fading them between 3 different colours. Interpolation, dithering
// and gamma correction are employed to ensure a smooth fade at low
// intensities.

// A device with two TCB timers is required such as an ATtiny1614.
// Both strips are updated in parallel which is only faster when running
// at 16MHz or above. One strip uses SPI and the other uses USART.

// The main reason you might want to update two strips at once instead
// of just connecting them together is to improve the update frequency
// if the strips are long (80+ LEDs?) so that you get better dithering.

#define NUM_LEDS 50
#define LED_CHANNELS 3 // use 4 for RGBW strands
#define UPDATE_INTERVAL_US 2000 // how long between updates
#define USE_TCA0_TIMING 1 // use timer interrupt for more accurate timing
uint8_t r = 0x30, g = 0, b = 0; // initial colour

// Each LED requires 5 bytes of memory to store its current
// and target colours and its dithering state.
SmoothLed::Interpolator interpolators0[NUM_LEDS * LED_CHANNELS];
SmoothLed leds0(interpolators0, NUM_LEDS * LED_CHANNELS);
SmoothLed::Interpolator interpolators1[NUM_LEDS * LED_CHANNELS];
SmoothLed leds1(interpolators1, NUM_LEDS * LED_CHANNELS);

void setupPeriodicTimer(int microSeconds);
void waitForTimer();

void setup()
{
// initialise all LED values to 0
leds0.clear();
leds1.clear();

leds0.begin(
SmoothLedCcl::PA4_LUT0, // pin where first LED data line is connected
SmoothLedCcl::PA3_SPI0_ASYNCCH0, // this pin will be an output but is only used for the clock signal
TCB0);
leds1.begin(
SmoothLedCcl::PA7_LUT1, // pin where second LED data line is connected
SmoothLedCcl::PB1_USART0_ASYNCCH1, // this pin will be an output but is only used for the clock signal
TCB1); // need to specify secondary timer

setupPeriodicTimer(UPDATE_INTERVAL_US);
}

void loop()
{
// wait for 1kHz interval
waitForTimer();

// reset hardware watchdog (might be enabled in fuses)
wdt_reset();

if (!leds0.isFading())
{
// set target colours
for (uint8_t i = 0; i < NUM_LEDS; ++i)
{
leds0.setFadeTarget(i * LED_CHANNELS + 0, g);
leds0.setFadeTarget(i * LED_CHANNELS + 1, r);
leds0.setFadeTarget(i * LED_CHANNELS + 2, b);

leds1.setFadeTarget(i * LED_CHANNELS + 0, b);
leds1.setFadeTarget(i * LED_CHANNELS + 1, g);
leds1.setFadeTarget(i * LED_CHANNELS + 2, r);
}
// fade over next 500 updates (1 second at 500Hz)
leds0.beginFade(500);
leds1.beginFade(500);

// cycle to next colour
uint8_t t = r; r = g; g = b; b = t;
}

// update fade and write dithered & gamma corrected values to LED strips
uint8_t deltaTime = leds0.updateTime();
uint8_t n = NUM_LEDS * LED_CHANNELS;
const uint16_t* gammaLut = leds0.getGammaLut();
uint16_t maxValue = leds0.getMaxValue();
uint8_t ditherMask = leds0.getDitherMask();
SmoothLed::Interpolator* i0 = leds0.getInterpolators();
SmoothLed::Interpolator* i1 = leds1.getInterpolators();
leds0.beginTransactionSpi();
leds1.beginTransactionUsart();
do {
leds0.writeSpi(i0++->update(deltaTime, gammaLut, maxValue, ditherMask));
leds1.writeUsart(i1++->update(deltaTime, gammaLut, maxValue, ditherMask));
} while (--n);
leds0.endTransactionSpi();
leds1.endTransactionUsart();

delayMicroseconds(50);
}

#if USE_TCA0_TIMING
void setupPeriodicTimer(int microSeconds)
{
// turn off split mode as per megaTinyCore guide
TCA0.SPLIT.CTRLA = 0;
TCA0.SPLIT.CTRLESET = TCA_SPLIT_CMD_RESET_gc | 0x03;
TCA0.SPLIT.CTRLD = 0;

// set up periodic timer
TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm;
TCA0.SINGLE.PER = (F_CPU * 1e-6 * microSeconds + 15) / 16 - 1;
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV16_gc | TCA_SINGLE_ENABLE_bm;
}
volatile bool wakeup = false;
ISR(TCA0_OVF_vect)
{
wakeup = true;
TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm;
}
void waitForTimer()
{
while (!wakeup)
sleep_mode();
wakeup = false;
}
#else
void setupPeriodicTimer(int){}
void waitForTimer()
{
// roughly 10us per 8 bits of data and 50us to reset
int timeToWriteLeds = NUM_LEDS * LED_CHANNELS * 10 + 50;
delayMicroseconds(max(0, UPDATE_INTERVAL_US - timeToWriteLeds));
}
#endif
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=SmoothLed
version=1.0.0
version=1.0.1
author=Matt Shepcar
maintainer=Matt Shepcar <[email protected]>
sentence=Arduino library for FadeCandy style control of single-wire-based LED neopixels and WS2812B strips with megaTinyCore.
Expand Down
4 changes: 2 additions & 2 deletions src/SmoothLedCcl.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ inline void SmoothLedCcl::beginTransactionSpi()
inline void SmoothLedCcl::writeSpi(uint8_t value)
{
while ((SPI0.INTFLAGS & SPI_DREIF_bm) == 0) {}
SPI0.DATA = ~value;
SPI0.DATA = value;
}
inline void SmoothLedCcl::endTransactionSpi()
{
Expand All @@ -255,7 +255,7 @@ inline void SmoothLedCcl::beginTransactionUsart()
inline void SmoothLedCcl::writeUsart(uint8_t value)
{
while ((USART0.STATUS & USART_DREIF_bm) == 0) {}
USART0.TXDATAL = ~value;
USART0.TXDATAL = value;
}
inline void SmoothLedCcl::endTransactionUsart()
{
Expand Down

0 comments on commit cbf57e5

Please sign in to comment.