Arduino source code: Vacuum tube lights using RGBW SK6812 LEDs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Neopixel mood lighting for vacuum tubes | |
// Ed Nisley - KE4ANU - June 2016 | |
// September 2016 - Add Morse library and blinkiness | |
// October 2016 - Set random colors at cycle end | |
// March 2017 - RGBW SK6812 LEDs | |
#include <Adafruit_NeoPixel.h> | |
#include <morse.h> | |
#include <Entropy.h> | |
//---------- | |
// Pin assignments | |
const byte PIN_NEO = A5; // DO - data out to first Neopixel | |
const byte PIN_HEARTBEAT = 13; // DO - Arduino LED | |
#define PIN_MORSE 12 | |
//---------- | |
// Constants | |
// number of pixels | |
#define PIXELS 2 | |
// index of the Morse output pixel and how fast it sends | |
boolean Send_Morse = false; | |
#define PIXEL_MORSE (PIXELS - 1) | |
#define MORSE_WPM 10 | |
// lag between adjacent pixel, degrees of slowest period | |
#define PIXELPHASE 45 | |
// update LEDs only this many ms apart (minus loop() overhead) | |
#define UPDATEINTERVAL 50ul | |
#define UPDATEMS (UPDATEINTERVAL - 1ul) | |
// number of steps per cycle, before applying prime factors | |
#define RESOLUTION 500 | |
//---------- | |
// Globals | |
// instantiate the Neopixel buffer array | |
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXELS, PIN_NEO, NEO_GRBW + NEO_KHZ800); | |
uint32_t FullWhite = strip.Color(255,255,255,255); | |
uint32_t FullOff = strip.Color(0,0,0,0); | |
uint32_t MorseColor; | |
struct pixcolor_t { | |
unsigned int Prime; | |
unsigned int NumSteps; | |
unsigned int Step; | |
float StepSize; | |
float Phase; | |
byte MaxPWM; | |
}; | |
unsigned int PlatterSteps; | |
byte PrimeList[] = {3,5,7,13,19,29}; | |
// colors in each LED | |
enum pixcolors {RED, GREEN, BLUE, WHITE, PIXELSIZE}; | |
struct pixcolor_t Pixels[PIXELSIZE]; // all the data for each pixel color intensity | |
uint32_t UniColor; | |
unsigned long MillisNow; | |
unsigned long MillisThen; | |
// Morse code | |
char * MorseText = " cq cq cq de ke4znu"; | |
LEDMorseSender Morse(PIN_MORSE, (float)MORSE_WPM); | |
uint8_t PrevMorse, ThisMorse; | |
//-- Figure PWM based on current state | |
byte StepColor(byte Color, float Phi) { | |
byte Value; | |
Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize + Phi)); | |
// Value = (Value) ? Value : Pixels[Color].MaxPWM; // flash at dimmest points for debug | |
return Value; | |
} | |
//-- Select three unique primes for the color generator function | |
// Then compute all the step parameters based on those values | |
void SetColorGenerators(void) { | |
Pixels[RED].Prime = PrimeList[random(sizeof(PrimeList))]; | |
do { | |
Pixels[GREEN].Prime = PrimeList[random(sizeof(PrimeList))]; | |
} while (Pixels[RED].Prime == Pixels[GREEN].Prime); | |
do { | |
Pixels[BLUE].Prime = PrimeList[random(sizeof(PrimeList))]; | |
} while (Pixels[BLUE].Prime == Pixels[RED].Prime || | |
Pixels[BLUE].Prime == Pixels[GREEN].Prime); | |
do { | |
Pixels[WHITE].Prime = PrimeList[random(sizeof(PrimeList))]; | |
} while (Pixels[WHITE].Prime == Pixels[RED].Prime || | |
Pixels[WHITE].Prime == Pixels[GREEN].Prime || | |
Pixels[WHITE].Prime == Pixels[BLUE].Prime); | |
printf("Primes: %d %d %d %d\r\n",Pixels[RED].Prime,Pixels[GREEN].Prime,Pixels[BLUE].Prime,Pixels[WHITE].Prime); | |
Pixels[RED].MaxPWM = 255; | |
Pixels[GREEN].MaxPWM = 255; | |
Pixels[BLUE].MaxPWM = 255; | |
Pixels[WHITE].MaxPWM = 32; | |
unsigned int PhaseSteps = (unsigned int) ((PIXELPHASE / 360.0) * | |
RESOLUTION * (unsigned int) max(max(max(Pixels[RED].Prime,Pixels[GREEN].Prime),Pixels[BLUE].Prime),Pixels[WHITE].Prime)); | |
printf("Pixel phase offset: %d deg = %d steps\r\n",(int)PIXELPHASE,PhaseSteps); | |
for (byte c=0; c < PIXELSIZE; c++) { | |
Pixels[c].NumSteps = RESOLUTION * Pixels[c].Prime; // steps per cycle | |
Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps; // radians per step | |
Pixels[c].Step = random(Pixels[c].NumSteps); // current step | |
Pixels[c].Phase = PhaseSteps * Pixels[c].StepSize;; // phase in radians for this color | |
printf(" c: %d Steps: %d Init: %d Phase: %d deg",c,Pixels[c].NumSteps,Pixels[c].Step,(int)(Pixels[c].Phase * 360.0 / TWO_PI)); | |
printf(" PWM: %d\r\n",Pixels[c].MaxPWM); | |
} | |
} | |
//-- Helper routine for printf() | |
int s_putc(char c, FILE *t) { | |
Serial.write(c); | |
} | |
//------------------ | |
// Set the mood | |
void setup() { | |
pinMode(PIN_HEARTBEAT,OUTPUT); | |
digitalWrite(PIN_HEARTBEAT,LOW); // show we arrived | |
Serial.begin(57600); | |
fdevopen(&s_putc,0); // set up serial output for printf() | |
printf("Vacuum Tube Mood Light - RGBW\r\nEd Nisley - KE4ZNU - March 2017\r\n"); | |
Entropy.initialize(); // start up entropy collector | |
// set up pixels | |
strip.begin(); | |
strip.show(); | |
// lamp test: a brilliant white flash | |
printf("Lamp test: flash white\r\n"); | |
for (byte i=0; i<5 ; i++) { | |
for (int j=0; j < strip.numPixels(); j++) { // fill LEDs with white | |
strip.setPixelColor(j,FullWhite); | |
} | |
strip.show(); | |
delay(500); | |
for (int j=0; j < strip.numPixels(); j++) { // fill LEDs with black | |
strip.setPixelColor(j,FullOff); | |
} | |
strip.show(); | |
delay(500); | |
} | |
// get an actual random number | |
uint32_t rn = Entropy.random(); | |
printf("Random seed: %08lx\r\n",rn); | |
randomSeed(rn); | |
// set up the color generators | |
SetColorGenerators(); | |
// set up Morse generator | |
Morse.setup(); | |
Morse.setMessage(String(MorseText)); | |
MorseColor = strip.Color(255,random(32,64),random(16),0); | |
PrevMorse = ThisMorse = digitalRead(PIN_MORSE); | |
printf("Morse enabled: %d at %d wpm color: %08lx\n [%s]\r\n",Send_Morse,MORSE_WPM,MorseColor,MorseText); | |
MillisNow = MillisThen = millis(); | |
} | |
//------------------ | |
// Run the mood | |
void loop() { | |
if (!Morse.continueSending()) { | |
printf("Restarting Morse message\r\n"); | |
Morse.startSending(); | |
} | |
ThisMorse = digitalRead(PIN_MORSE); | |
MillisNow = millis(); | |
if (((MillisNow - MillisThen) >= UPDATEMS) || // time for color change? | |
(PrevMorse != ThisMorse)) { // Morse output bit changed? | |
digitalWrite(PIN_HEARTBEAT,HIGH); | |
if (Send_Morse && ThisMorse) { // if Morse output high, overlay flash | |
strip.setPixelColor(PIXEL_MORSE,MorseColor); | |
} | |
PrevMorse = ThisMorse; | |
strip.show(); // send out precomputed colors | |
boolean CycleRun = false; // check to see if all cycles have ended | |
for (byte c=0; c < PIXELSIZE; c++) { // compute next increment for each color | |
if (++Pixels[c].Step >= Pixels[c].NumSteps) { | |
Pixels[c].Step = 0; | |
printf("Cycle %d steps %d at %8ld delta %ld ms\r\n",c,Pixels[c].NumSteps,MillisNow,(MillisNow - MillisThen)); | |
} | |
else { | |
CycleRun = true; // this color is still cycling | |
} | |
} | |
// If all cycles have completed, reset the color generators | |
if (!CycleRun) { | |
printf("All cycles ended: setting new color generator values\r\n"); | |
SetColorGenerators(); | |
} | |
for (int i=0; i < strip.numPixels(); i++) { // for each pixel | |
byte Value[PIXELSIZE]; | |
for (byte c=0; c < PIXELSIZE; c++) { // ... for each color | |
Value[c] = (Pixels[c].MaxPWM / 2.0) * (1.0 + sin(Pixels[c].Step * Pixels[c].StepSize - i*Pixels[c].Phase)); | |
} | |
UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE],Value[WHITE]); | |
strip.setPixelColor(i,UniColor); | |
} | |
MillisThen = MillisNow; | |
digitalWrite(PIN_HEARTBEAT,LOW); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
More details on my blog at http://wp.me/poZKh-6BP