Skip to content

Instantly share code, notes, and snippets.

@kylemarsh
Last active January 19, 2016 01:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kylemarsh/2b6257281d6c0dfc31d8 to your computer and use it in GitHub Desktop.
Save kylemarsh/2b6257281d6c0dfc31d8 to your computer and use it in GitHub Desktop.
ShiftBrite Playground -- Different sketches that I've come up with while working on a shiftbrite project
#include "shiftbrite.h" // See https://github.com/kylemarsh/particle_shiftbrite
#define latchpin D0 // replace with pin you use for the latch
#define numleds 4 // Number of LEDs in your chain
typedef struct {
uint16_t red;
uint16_t green;
uint16_t blue;
} rgb;
rgb pixels[numleds];
rgb hsv2rgb(double hue, double sat, double value);
ShiftBrite sb(numleds, latchpin);
void setup() {
sb.begin();
sb.show();
}
void loop() {
colorwheel_wave();
//colorwheel();
}
void colorwheel_wave(void) {
for (int hue = 0; hue < 360; ++hue) {
for (int i = 0; i < numleds; ++i) {
rgb color = hsv2rgb((double)((hue + 15*i) % 360), 1.0, 1.0);
sb.setPixelRGB(i, color.red, color.green, color.blue);
}
sb.show();
delay(10);
}
}
void colorwheel(void) {
for (int hue = 0; hue < 360; ++hue) {
rgb color = hsv2rgb((double)hue, 1.0, 1.0);
sb.allOn(color.red, color.green, color.blue);
delay(10);
}
}
rgb hsv2rgb(double hue, double sat, double value) {
double sextant, chroma, q, t, ff;
double red, blue, green;
long i;
rgb out;
if(sat <= 0.0) {
red = value;
green = value;
blue = value;
return out;
}
sextant = hue;
if(sextant >= 360.0) sextant = 0.0;
sextant /= 60.0;
i = (long)sextant;
ff = sextant - i;
chroma = value * (1.0 - sat);
q = value * (1.0 - (sat * ff));
t = value * (1.0 - (sat * (1.0 - ff)));
switch(i) {
case 0:
red = value;
green = t;
blue = chroma;
break;
case 1:
red = q;
green = value;
blue = chroma;
break;
case 2:
red = chroma;
green = value;
blue = t;
break;
case 3:
red = chroma;
green = q;
blue = value;
break;
case 4:
red = t;
green = chroma;
blue = value;
break;
case 5:
default:
red = value;
green = chroma;
blue = q;
break;
}
out.red = (uint32_t)(red * 1023);
out.green = (uint32_t)(green * 1023);
out.blue = (uint32_t)(blue * 1023);
return out;
}
#ifndef my_structs_h
#define my_structs_h
typedef struct {
int16_t red;
int16_t green;
int16_t blue;
} rgb;
#endif // my_structs_h
#define RegLatchPin D0 // replace with pin you use for the latch
#define numleds 4 // Number of LEDs in your chain
#define numcolors 9 // Number of colors to cycle through (adjust if you change the ColorWheel array)
#define train 1 // If 1 colors will cycle down through the chain. If 0 the whole chain will be the same color.
typedef union
{
uint32_t value;
struct // Current control and clock mode registers
{
unsigned greenDotCorrect:7;
unsigned clockMode:2;
unsigned :1;
unsigned redDotCorrect:7;
unsigned :3;
unsigned blueDotCorrect:7;
};
struct // PWM registers and address bit
{
unsigned green:10;
unsigned red:10;
unsigned blue:10;
unsigned command:1;
};
} ShiftBritePacket;
void TransmitSerial(ShiftBritePacket packet);
void TransmitSPI(ShiftBritePacket packet);
ShiftBritePacket colorPacket(unsigned int red, unsigned int green, unsigned int blue);
int pos = 0;
ShiftBritePacket ColorWheel[numcolors];
void setup() {
pinMode(RegLatchPin, OUTPUT);
digitalWrite(RegLatchPin, LOW);
Serial.begin(9600); // We can talk Serial over USB and SPI over pins A2-A5 at the same time.
// ShiftBrites are driven by the Allegro A6281
// Datasheet for A6281 can be found here: https://www.pololu.com/file/download/allegroA6281.pdf?file_id=0J225
// Particle Core's SPI library uses the following pins
// A2: SS (You can change this when invoking SPI.begin() but we're not using it in this sketch anyway)
// A3: SCK - Clock
// A4: MISO - Master in, slave out (receiving data from a downstream device) (we don't use this)
// A5: MOSI - Master out, slave in (sending data to a downstream device)
// The Allegro A6281 has a 32bit shift register and uses the following pinout:
// DI - Data In - Data received from upstream device (the bit to shift in on the clock's next rising edge)
// LI - Latch In - Pulse the latch high after last bit has been shifted in to "save" the shift registers
// EI - Enable In - Hold low to enable the LEDs. Hold high to disable the LEDs.
// CI - Clock In - Clock signal from upstream.
// Each input has a corresponding output that gets propagated through the chip to the next chip;
// DO is the oldest bit that was shifted into the shift registries, rather than the current value of DI
// These inputs map to the SPI outputs like so:
// SCK --> CI
// MISO --> DI
// We will be manually controlling LI (latch) and EI (simply connect to GND)
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setClockSpeed(8, MHZ); // Datasheet indicates max clock of 5 MHz, I think. Mine seem to work up to 8 MHz
SPI.setDataMode(SPI_MODE1); // Data Mode is an implementation-level detail of SPI defining the particular timing of signals. A6281 uses mode 1
/* Cycle through Primary & Secondary colors plus a dim white */
ColorWheel[0] = colorPacket( 0, 0, 0); // Off
ColorWheel[1] = colorPacket(1023, 0, 0); // Red
ColorWheel[2] = colorPacket( 0, 1023, 0); // Green
ColorWheel[3] = colorPacket( 0, 0, 1023); // Bue
ColorWheel[4] = colorPacket( 0, 1023, 1023); // Cyan
ColorWheel[5] = colorPacket(1023, 1023, 0); // Yellow
ColorWheel[6] = colorPacket(1023, 0, 1023); // Magenta
ColorWheel[7] = colorPacket(1023, 1023, 1023); // White
ColorWheel[8] = colorPacket(127, 127, 127); // Dim White
}
void loop() {
for (int i = 0; i < numleds; ++i) {
int x = train ? i : 0;
TransmitSerial(ColorWheel[(pos + x) % numcolors]);
TransmitSPI(ColorWheel[(pos + x) % numcolors]);
}
latch();
pos = (pos + 1) % numcolors;
delay(1000);
}
void TransmitSerial(ShiftBritePacket packet) {
uint32_t data = packet.value;
Serial.printf("pos = %d\n", pos);
Serial.printf("Data Received: %x\n", data);
Serial.println("Bytes:");
Serial.printf(" 3: %x\n", (byte)(data >> 24 & 0xFF));
Serial.printf(" 2: %x\n", (byte)(data >> 16 & 0xFF));
Serial.printf(" 1: %x\n", (byte)(data >> 8 & 0xFF));
Serial.printf(" 0: %x\n", (byte)(data >> 0 & 0xFF));
}
void TransmitSPI(ShiftBritePacket packet) {
uint32_t data = packet.value;
SPI.transfer((byte)(data >> 24 & 0xFF));
SPI.transfer((byte)(data >> 16 & 0xFF));
SPI.transfer((byte)(data >> 8 & 0xFF));
SPI.transfer((byte)(data >> 0 & 0xFF));
}
void latch() {
digitalWrite(RegLatchPin, HIGH);
digitalWrite(RegLatchPin, LOW);
}
ShiftBritePacket colorPacket(unsigned int red, unsigned int green, unsigned int blue)
{
//Make a packet and initialize all of the bits to zero.
ShiftBritePacket shiftbrite_packet = {value:0};
shiftbrite_packet.red = red;
shiftbrite_packet.green = green;
shiftbrite_packet.blue = blue;
return shiftbrite_packet;
}
#include "ticker.h"
Ticker::Ticker(void (*func)(Ticker *t, uint16_t), int num_targets, int speed_adjust, int offset) :
tick_func(func), num_targets(num_targets), speed_adjust(speed_adjust),
offset(offset), targets(NULL)
{
if ((targets = (rgb *)malloc(num_targets * 6))) {
memset(targets, 0, num_targets * 6);
}
}
Ticker::~Ticker()
{
if (targets) free(targets);
}
void Ticker::Tick(uint16_t tick)
{
this->tick_func(this, tick);
}
#include "application.h"
#include "my_structs.h"
#ifndef Ticker_h
#define Ticker_h
class Ticker
{
public:
Ticker(void (*func)(Ticker *t, uint16_t), int num_targets, int speed_adjust, int offset);
~Ticker();
int
num_targets,
speed_adjust,
offset;
rgb current;
rgb *targets;
void Tick(uint16_t tick);
private:
void (*tick_func)(Ticker *t, uint16_t tick);
};
#endif // Ticker_h
/*
* WIP preparing for Galaxy Topper firmware.
* Uses ShiftBrite library to drive a string of NUMLEDS ShiftBrite pixels.
* Uses Ticker class to decouple behavior of each pixel from overall driving
* code.
*
* To control a pixel, create a new Ticker object with the desired
* configuration and put it into the Tickers array at the pixel's address.
*
* Each time Ticker.Tick() is called the Ticker calls the function passed in
* as tick_func: void tick_func(Ticker *t, uint16_t tick) which will operate
* on the Ticker object that it is passed. The updated RGB values are then
* stored in Ticker.current
*
* When writing a tick function pay careful attention to the number of
* "target" rgb values the ticker has -- the number of targets the tick
* function requires should be passed into the Ticker's constructor, which
* will allocate space for the required target rgb values.
*
* TO FLASH THIS TO A CORE:
* particle flash corename ticking.ino shiftbrite/shiftbrite.* ticker.* my_structs.h
*/
#include "shiftbrite.h"
#include "ticker.h"
#include "math.h" // only used to get pow(...) for our skew(...) function
/* Constants */
#define LATCHPIN D0 // replace with pin you use for the latch
#define NUMLEDS 6 // Number of LEDs in your chain
#define FPS 100 // number of times per second to update shiftbrites
#define TPS 100 // ticks per second
/* Predeclarations */
// Necessary to avoid confusing particle's preprocessor
// see https://docs.particle.io/reference/firmware/core/#preprocessor
void colorwheel_tick(Ticker *t, uint16_t tick);
void cycling_tick(Ticker *t, uint16_t tick);
void fading_cycling_tick(Ticker *t, uint16_t tick);
void targeted_tick(Ticker *t, uint16_t tick);
rgb hsv2rgb(double hue, double sat, double value);
/* Define Objects */
ShiftBrite sb(NUMLEDS, LATCHPIN);
Ticker Tickers[NUMLEDS] = {
// Careful with the second argument -- it allocates memory for the
// Ticker's targets, so make sure you give it at least as many targets
// as the tick_function uses.
/*
Ticker(&targeted_tick, 3, 0, 20),
Ticker(&targeted_tick, 3, 0, 20),
Ticker(&targeted_tick, 3, 0, 20),
Ticker(&targeted_tick, 3, 0, 20),
Ticker(&targeted_tick, 3, 0, 20),
Ticker(&targeted_tick, 3, 0, 20),
*/
Ticker(&colorwheel_tick, 0, 0, 0),
Ticker(&colorwheel_tick, 0, 0, 15),
Ticker(&colorwheel_tick, 0, 0, 30),
Ticker(&colorwheel_tick, 0, 0, 45),
Ticker(&colorwheel_tick, 0, 0, 60),
Ticker(&colorwheel_tick, 0, 0, 75),
/*
Ticker(&cycling_tick, 8, 0, 0),
Ticker(&cycling_tick, 8, 0, 0),
Ticker(&cycling_tick, 8, 0, 0),
Ticker(&cycling_tick, 8, 0, 0),
Ticker(&cycling_tick, 8, 0, 0),
Ticker(&cycling_tick, 8, 0, 0),
Ticker(&fading_cycling_tick, 8, 0, 0),
Ticker(&fading_cycling_tick, 8, 0, 0),
Ticker(&fading_cycling_tick, 8, 0, 0),
Ticker(&fading_cycling_tick, 8, 0, 0),
Ticker(&fading_cycling_tick, 8, 0, 0),
Ticker(&fading_cycling_tick, 8, 0, 0),
*/
};
/* Timing */
uint16_t frame_delay = 1000 / FPS;
uint16_t tick_delay = 1000 / TPS;
uint16_t tick = 0;
uint32_t last_tick, last_frame = 0;
void setup()
{
// Initialize shiftbrite
sb.begin();
sb.show();
// Initialize tickers
for (int i = 0; i < NUMLEDS; ++i) {
// Targeted ticker targets
//Tickers[i].targets[1] = {red: 0, green: 0, blue: 511};
//Tickers[i].targets[2] = {red: 1023, green: 1023, blue: 1023};
// Cycling ticker targets
Tickers[i].targets[0] = {red: 0, green: 0, blue: 0};
Tickers[i].targets[1] = {red: 1023, green: 0, blue: 0};
Tickers[i].targets[2] = {red: 0, green: 1023, blue: 0};
Tickers[i].targets[3] = {red: 0, green: 0, blue: 1023};
Tickers[i].targets[4] = {red: 1023, green: 1023, blue: 0};
Tickers[i].targets[5] = {red: 0, green: 1023, blue: 1023};
Tickers[i].targets[6] = {red: 1023, green: 0, blue: 1023};
Tickers[i].targets[7] = {red: 1023, green: 1023, blue: 1023};
}
}
void loop()
{
uint32_t now = millis();
// Is it time to do another tick?
if (now - last_tick > tick_delay) {
last_tick = millis();
for(int i = 0; i < NUMLEDS; ++i) {
Tickers[i].Tick(tick);
rgb color = Tickers[i].current;
sb.setPixelRGB(i, color.red, color.green, color.blue);
}
++tick;
}
// Is it time to refresh our shiftbrites?
if (millis() - last_frame > frame_delay) {
last_frame = millis();
sb.show();
}
}
/*********************
* TICKER FUNCTIONS *
*********************/
/*
* Cycles around the color wheel by using tick to compute the hue (value and
* saturation are held constant at 1).
*
* Ticker's speed_adjust is used to slow the cycle by powers of 2
*
* Ticker's offset is used to set the color *ahead* of the tick's base hue.
* This can be used to sync multiple colorwheel_tick Tickers into a ribbon.
*
* This tick function does not use any target colors.
*/
void colorwheel_tick(Ticker *t, uint16_t tick)
{
int16_t hue = (tick >> t->speed_adjust) % 360;
t->current = hsv2rgb((double)((hue + t->offset) % 360), 1.0, 1.0);
}
/*
* Cycles through a fixed list of target colors.
*/
void cycling_tick(Ticker *t, uint16_t tick)
{
uint16_t current_index = (tick / TPS) % t->num_targets; // Update target once per second.
t->current = t->targets[current_index];
}
/*
* Cycles through a fixed list of target colors, fading between them.
*/
void fading_cycling_tick(Ticker *t, uint16_t tick)
{
uint16_t previous_index = (tick / TPS) % t->num_targets; // Update target once per second.
rgb previous_color = t->targets[previous_index];
rgb next_color = t->targets[(previous_index + 1) % t->num_targets];
int16_t red_delta = next_color.red - previous_color.red;
int16_t current_red = previous_color.red + ((red_delta * (tick % TPS)) / TPS);
int16_t green_delta = next_color.green - previous_color.green;
int16_t current_green = previous_color.green + ((green_delta * (tick % TPS)) / TPS);
int16_t blue_delta = next_color.blue - previous_color.blue;
int16_t current_blue = previous_color.blue + ((blue_delta * (tick % TPS)) / TPS);
t->current = {red: current_red, green: current_green, blue: current_blue};
}
/*
* Fades to a target color. Once target color is reached, the target and fade
* speed are randomized. Target randomization is controllable within min and
* max values on each color channel.
*
* Ticker's offset is used as the step size to control how quickly the color
* moves towards the target.
*
* Ticker's speed_adjust is used to skip cycles by powers of 2:
* 0 executes every cycle
* 1 executes every other cycle
* 2 executes every fourth cycle...
*
* This tick function uses three "target" colors:
* 0: The color to fade towards
* 1: The minimum value for each color channel when randomizing
* 2: The maximum value for each color channel when randomizing
*/
void targeted_tick(Ticker *t, uint16_t tick)
{
// speed_adjust keeps us from ticking on certain cycles
if (tick % (1 << t->speed_adjust)) {return;}
rgb* current = &t->current;
rgb* target = &t->targets[0];
rgb minima = t->targets[1];
rgb maxima = t->targets[2];
int16_t step_size = t->offset;
if ( current->red == target->red
&& current->green == target->green
&& current->blue == target->blue)
{
// We've reached the target! Pick a new one.
target->red = random(minima.red, maxima.red);
target->green = random(minima.green, maxima.green);
target->blue = random(minima.blue, maxima.blue);
// And just for fun let's also randomize the fade speed!
t->offset = skew(random(1, 100), 5, 50, 100);
t->speed_adjust = random(0, 2);
}
// Still working towards the target. Take a step in the right direction
// TODO: Should we adjust the step sizes so that each channel reaches
// its target at the same time?
current->red += stepToward(target->red, current->red, step_size);
current->green += stepToward(target->green, current->green, step_size);
current->blue += stepToward(target->blue, current->blue, step_size);
}
/*** HELPER FUNCTIONS ***/
/*
* Given a target value, current value, and step size, calculate the value to
* add to the current value to move it towards its target by the step size
* without going past the target.
*/
int16_t stepToward(int16_t target, int16_t current, int step_size)
{
int16_t delta = target - current;
delta = abs(delta);
int direction = 0;
if (target > current) {direction = 1;}
if (target < current) {direction = -1;}
return (delta > step_size ? step_size : delta) * direction;
}
/*
* This is the gamma correction function that I included a lookup table for in
* shiftbrite.h. It's used here to map a uniform distribution to something skewed
* heavily towards the low end.
*
* Basically I want my targeting Tickers to fade slowly most of the time but do a
* really quick flicker every once in a while.
*/
int16_t skew(int16_t x, int16_t exponent, int16_t max_out, int16_t range)
{
return (int16_t)(pow((float)x / (float)range, exponent) * max_out + 1);
}
/*
* This function converts an HSV value into an RGB value.
* Code taken from StackOverflow user DavidH:
* http://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both
*/
rgb hsv2rgb(double hue, double sat, double value)
{
double sextant, chroma, q, t, ff;
double red, blue, green;
long i;
rgb out;
if(sat <= 0.0) {
red = value;
green = value;
blue = value;
return out;
}
sextant = hue;
if(sextant >= 360.0) sextant = 0.0;
sextant /= 60.0;
i = (long)sextant;
ff = sextant - i;
chroma = value * (1.0 - sat);
q = value * (1.0 - (sat * ff));
t = value * (1.0 - (sat * (1.0 - ff)));
switch(i) {
case 0:
red = value;
green = t;
blue = chroma;
break;
case 1:
red = q;
green = value;
blue = chroma;
break;
case 2:
red = chroma;
green = value;
blue = t;
break;
case 3:
red = chroma;
green = q;
blue = value;
break;
case 4:
red = t;
green = chroma;
blue = value;
break;
case 5:
default:
red = value;
green = chroma;
blue = q;
break;
}
out.red = (int16_t)(red * 1023);
out.green = (int16_t)(green * 1023);
out.blue = (int16_t)(blue * 1023);
return out;
}
#include "shiftbrite.h" // See https://github.com/kylemarsh/particle_shiftbrite
#define latchpin D0 // replace with pin you use for the latch
#define numleds 4 // Number of LEDs in your chain
typedef struct {
uint16_t red;
uint16_t green;
uint16_t blue;
} rgb;
rgb pixels[numleds];
void wanderPixel(int address, rgb minima, rgb maxima);
ShiftBrite sb(numleds, latchpin);
void setup() {
Serial.begin(9600);
Serial.println("Starting up");
sb.begin();
sb.show();
for (int i = 0; i < numleds; ++i) {
pixels[i].red = random(0, 1023);
pixels[i].green = random(0, 1023);
pixels[i].blue = random(766, 0123);
Serial.printf(" Pixel %d:\n R: %d\n G: %d\n B: %d\n\n", i, pixels[i].red, pixels[i].green, pixels[i].blue);
}
}
void loop() {
wander();
}
void wander() {
const rgb min = {
red: 0,
green: 0,
blue: 766
};
const rgb max = {
red: 1023,
green: 1023,
blue: 1023
};
for (int i = 0; i < numleds; ++i) {
Serial.printf("Wandering pixel %d\n", i);
wanderPixel(i, min, max);
sb.setPixelRGB(i, pixels[i].red, pixels[i].green, pixels[i].blue);
}
sb.show();
delay(10);
}
void wanderPixel(int address, rgb minima, rgb maxima) { // FIXME: this trends to 0. Why?
Serial.printf(" Starting values:\n R: %d\n G: %d\n B: %d\n\n", pixels[address].red, pixels[address].green, pixels[address].blue);
rgb percents, deltas, newval;
percents.red = random(0, 20);
percents.green = random(0, 20);
percents.blue = random(0, 20);
Serial.printf(" Percentages:\n R: %d\n G: %d\n B: %d\n\n", percents.red, percents.green, percents.blue);
deltas.red = pixels[address].red * percents.red / 100;
deltas.green = pixels[address].green * percents.green / 100;
deltas.blue = pixels[address].blue * percents.blue / 100;
int red_sign = random(0, 2) ? 1 : -1;
int green_sign = random(0, 2) ? 1 : -1;
int blue_sign = random(0, 2) ? 1 : -1;
Serial.printf(" Deltas:\n R: %d\n G: %d\n B: %d\n\n", (long)deltas.red * red_sign, (long)deltas.green * green_sign, (long)deltas.blue * blue_sign);
//Serial.printf(" Deltas:\n R: %d\n G: %d\n B: %d\n\n", deltas.red, deltas.green, deltas.blue);
newval.red = constrain(pixels[address].red + (long)deltas.red * red_sign, minima.red, maxima.red);
newval.green = constrain(pixels[address].green + (long)deltas.green * green_sign, minima.green, maxima.green);
newval.blue = constrain(pixels[address].blue + (long)deltas.blue * blue_sign, minima.blue, maxima.blue);
pixels[address].red = newval.red;
pixels[address].green = newval.green;
pixels[address].blue = newval.blue;
Serial.printf(" New values:\n R: %d\n G: %d\n B: %d\n\n", pixels[address].red, pixels[address].green, pixels[address].blue);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment