With the recent purchase of an Arduino microcontroller, my interest in electronics was rekindled after many years hiatus. One of my goals back in the day was to create a colour organ where lghts would flash in beat with the music. With the Arduino, I can easily do that and much more, so I set about coming up with a project that made use of modern day LED’s. The result will be Christmas lights that:
- is a strip of colour LED’s that wraps around a tree where each LED is individually controllable
- the LED colours respond to music (the color organ)
- uses an IR remote control to change light sequences
I have already designed/tested the technology for each function and will need to add additional functionality and then integrate them and then package it all up.
LED Strip
The LED’s currently consist of a 1 meter strip of 32 – WS2801 RGB drivers connected to 5050 type LED’s. This strip uses 5V, and the SPI protocol for driving the LED’s. Essentially you:
- Define an array whose length is the # of LED’s in the strip
- Fill that array with 24 bit RGB values
- Clock the values in the array to the first WS2801
- Repeat to #2
This worked out quite well, and I have already programmed a number of different light sequences. This will need to be significantly expanded in order to support the Color Organ and the IR controller.
The published version used software based ‘bit banging’ to push the data to the LED’s. Future versions will use the FastSPI library, which now works with the current Arduino environment. Here’s a test circuit:
www.tuline.com/dru/content/led-strip-test
Color Organ
A graphics equalizer chip called the MSGEQ7 provides data on 7 frequency bands, whereby you send an analog signal (ie iPod) into the MSGEQ7, and sequentially clock out values on another pin for each of these frequency bands. These values can then be fed into the light show software. Here’s the test circuit:
www.tuline.com/dru/content/colour-organ
InfraRed Control
An infrared receiver called a KSM-603LM has a digital output that can be fed to an Arduino. There is a software library by Ken Shirriff that can decode the output of a remote controller. Since the library doesn’t decode ALL IR devices, I programmed a universal remote to emulate a controller that the library supports. Here’s a link to the IR test circuit:
www.tuline.com/dru/content/infrared-circuit
Full Test
Here’s the setup as of July, 2012:
Although I’m publishing my current code, I plan to re-write it to support the FastSPI library as well as provide increased IR responsiveness and add additional sequences, such as a persistence of vision routine. Oh, and the MSGEQ7 functionality hasn’t been added yet.
In the meantime, here’s the first publicly available version of the code:
/*
Codebase:
- Nathan Seidle of SparkFun provided sample WS2801 usage code
- Ken Shiriff provided IR library and usage code
- J Skoba provided MSGEQ7 sample usage code
Developed by: Andrew Tuline
Date: July 28, 2012
This software controls one or more WS2801 based LED's and uses an MSGEQ7 to respond to music as well
as supporting an IR remote control.
None of the signals uses PWM or analog and should be able to be ported over to a Raspberry Pi.
WS2801 notes
Each IC has its own internal clock so that it can do all the PWM for that specific LED for you. Each IC
requires 24 bits of data. This means you can have 256 levels of red, 256 of blue, and 256 levels of green for each RGB LED.
To control the chips/strip, you clock in data continually. Each IC automatically passes the data onto
the next IC. Once you pause for more than 500us, each IC 'posts' or begins to output the colour data
you just clocked in. So, clock in (24bits * 32LEDs = ) 768 bits, then pause for 500us. Then
repeat if you wish to display something new.
MSGEQ7 notes
Not yet programmed
KSM-608LM notes
Pretty simple configuration. I programmed the Innovate Jumbo Universal Remote as a Sony DVD 004.
Structures
In order to use the IR, which allows me to have a list of sequences, I need to support nested structures or an array of a structure or something.
Just need to learn how to use them. Term may be called nested structures.
*/
#include
#define NUM_LEDS 32 // Number of LEDs on this strip
int SDI = 11; // Data in (MOSI)
int CKI = 13; // Clock in (SCK)
int RECV_PIN = 7; // Infrared remote
long strip_colours[NUM_LEDS]; // Array of values for the strip colours
// Innovage Jumbo Universal Remote - Sony VCR Code 004
unsigned long keydown = 0; // Used to determine if key is pressed or not
unsigned long oldmillis = 0; // Used to slow down the repeat speed of the keys
int irchoice = 0; // Current set to be displayed, values 0 - 9
char* sonyname[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "pwr", "up", "down", "ch+", "ch-", "vol+", "vol-", "enter", "ok", "rew", "ff", "play", "stop", "pause", "info", "R", "G", "B"};
int sonynum[] = {0x908, 0x8, 0x808, 0x408, 0xc08, 0x208, 0xa08, 0x608, 0xe08, 0x108, 0xa88, 0x428, 0xc28, 0x88, 0x888, 0x490, 0xc90, 0xd08, 0x1a8, 0xd88, 0x388, 0x588, 0x188, 0x988, 0x5a8, 0xB28, 0XBa8, 0x290};
// Element 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
IRrecv irrecv(RECV_PIN);
decode_results results;
// Generic sequencing variables
int sequence_count = 0; // This counter determines which sequence we are on
int num_count = 0; // This counter determines how many times we've gone through each individual sequence
typedef struct {
int seqtype; // Which sequence we are running
int seqnum; // Number of iterations of that sequence
int seqdel; // Delay used for this sequence
long seqcol; // Colour to be used
long seqinc; // Colour increment value
int sequpdn; // Increase or decrease color
int seqdir; // Forward/reverse direction (0 = forward, 1 = reverse)
int seqtmp; // A temporary value
} SEQ;
// int num_sequences = sizeof(miseq)/sizeof(miseq[0][0])/4;
int num_sequences = 4;
// We have several IR sets with 4 sequences (so far) per set.
SEQ miseq[10][4] = {
{{3, 1, 1, 0x000000, 0x000000, 1, 1, 0}, // Set 0 - Adjustable solid colours starting with black
{0, 1, 1, 0x000000, 0x000000, 1, 1, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 1, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 1, 0}},
{{1, 8, 10, 0x000011, 0x000001, 1, 0, 0}, // Set 1 - Moving stripe with Prefilled pattern with adjustable delay
{1, 8, 10, 0x001100, 0x000000, 1, 0, 0},
{1, 8, 10, 0x110000, 0x000000, 1, 0, 0},
{1, 8, 10, 0x111111, 0x000000, 1, 0, 0}},
{{4, 1, 1, 0x000004, 0x000001, 1, 0, 0}, // Sets 2 - Variable brightness moving stripe with pre-filled pattern & adjustable delay
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0}},
{{3, 1, 500, 0x001100, 0x000000, 1, 0, 0}, // Set 3 - Flashing solid colours with adjustable delay
{3, 1, 500, 0x000011 , 0x000000, 1, 0, 0},
{3, 1, 500, 0x110000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0}},
{{10, 32, 100, 0x000000, 0x000000, 1, 0, 0}, // Set 4 - Random colours with adjustable delay
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0}},
{{1, 1, 1, 0x111111, 0x000000, 1, 0, 0}, // Set 5
{1, 31, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0}},
{{2, 32, 1, 0x010001, 0x000000, 1, 0, 0}, // Set 6
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0}},
{{2, 32, 1, 0x010001, 0x000000, 1, 0, 0}, // Set 7
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0}},
{{2, 32, 1, 0x010001, 0x000000, 1, 0, 0}, // Set 8
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0}},
{{2, 32, 1, 0x010001, 0x000000, 1, 0, 0}, // Set 9
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0},
{0, 1, 1, 0x000000, 0x000000, 1, 0, 0}}
};
void setup() {
pinMode(SDI, OUTPUT); // Initialize the 2801 data pin
pinMode(CKI, OUTPUT); // Initialize the 2801 clock pin
irrecv.enableIRIn(); // Start the IR receiver
randomSeed(analogRead(0)); // Random number seed
memset(strip_colours,0,sizeof(strip_colours)); // Clear the LED array
post_frame(); // Push the current colour frame to the strip
Serial.begin(9600);
} // end of setup()
void loop() {
sequencer();
if (miseq[irchoice][sequence_count].seqtype !=0) { // Only post a frame if seqtype is !=0
post_frame(); // Push the current colour frame to the strip
delay(miseq[irchoice][0].seqdel); // And delay a bit to slow it down (synchronize to the first in the list)
}
keyinp();
// end of Infrared decoding
} // end of loop()
void keyinp(void) {
keydown = 0;
if (irrecv.decode(&results) && (millis() - oldmillis>200)) { // Receive the next Infrared Code
keydown = results.value;
oldmillis = millis();
for (int x=0; x<=9; x++) { // Select a basic sequence, otherwise let a dedicated routine get it
if(keydown == sonynum[x])
irchoice = x;
}
irrecv.resume(); // Receive the next value
}
}
void sequencer(void) { // Select and perform a sequence
switch(miseq[irchoice][sequence_count].seqtype) {
case 0: // Do nothing
break;
case 1: // Stateless colour[] moving stripe
mover();
strip_colours[(NUM_LEDS-1)*miseq[irchoice][sequence_count].seqdir] = miseq[irchoice][sequence_count].seqcol;
changedelay();
break;
case 2: // Stateless increasing colour[] moving stripe
mover();
strip_colours[(NUM_LEDS-1)*miseq[irchoice][sequence_count].seqdir] = miseq[irchoice][sequence_count].seqcol*num_count;
changedelay();
break;
case 3: // Stateless changeable solid colour mood light
for (int i=0; i
for (int i=0; i
strip_colours[i] = 0;
delay(miseq[irchoice][sequence_count].seqdel);
post_frame();
}
break;
case 13:
break;
default:
// Serial.println("Default");
break;
} // end of switch
num_count = (num_count + 1) % miseq[irchoice][sequence_count].seqnum; // Increment to the next iteration of the sequence
if (num_count == 0) {
sequence_count = (sequence_count + 1) % (num_sequences); // Increment to the next sequence
// Serial.println(sequence_count);
}
} // end of sequencer
void changedelay(void) {
if (keydown == sonynum[13]) // Increase delay
miseq[irchoice][0].seqdel *= 2;
if (keydown == sonynum[14] )
miseq[irchoice][0].seqdel /= 2; // Reduce delay
miseq[irchoice][0].seqdel |= 1;
// Serial.println(miseq[irchoice][0].seqdel);
}
void changecolors(void) {
if (keydown == sonynum[25]) // Select red
miseq[irchoice][sequence_count].seqinc = 1;
if (keydown == sonynum[26]) // Select green
miseq[irchoice][sequence_count].seqinc = 0x0100;
if (keydown == sonynum[27]) // Select blue
miseq[irchoice][sequence_count].seqinc = 0x010000;
if (keydown == sonynum[16]) // vol- lowers the colour
miseq[irchoice][sequence_count].seqcol-= miseq[irchoice][sequence_count].seqinc;
if (keydown == sonynum[15])
miseq[irchoice][sequence_count].seqcol += miseq[irchoice][sequence_count].seqinc; // vol+ increases the colour
if (miseq[irchoice][sequence_count].seqcol <0 || (miseq[irchoice][sequence_count].seqcol & 0x00ff00) == 0x00ff00 || (miseq[irchoice][sequence_count].seqcol & 0x0000ff) == 0x0000ff) { // Must check that we didn't his 0xff for each colour
miseq[irchoice][sequence_count].seqcol += miseq[irchoice][sequence_count].seqinc;
}
}
void mover (void) {
if (miseq[irchoice][sequence_count].seqdir == 1) {
for ( int x = 0; x
strip_colours[x] = strip_colours[x-1];
}
} // end of mover
void post_frame (void) { //Takes the current strip colour array and pushes it out
for(int LED_number = 0 ; LED_number < NUM_LEDS ; LED_number++) { long this_led_colour = strip_colours[LED_number]; //24 bits of colour data for(byte colour_bit = 23 ; colour_bit != 255 ; colour_bit--) { //Feed colour bit 23 first (red data MSB) digitalWrite(CKI, LOW); //Only change data when clock is low long mask = 1L << colour_bit; //The 1'L' forces the 1 to start as a 32 bit number, otherwise it defaults to 16-bit. if(this_led_colour & mask) digitalWrite(SDI, HIGH); else digitalWrite(SDI, LOW); digitalWrite(CKI, HIGH); //Data is latched when clock goes high } } digitalWrite(CKI, LOW); //Pull clock low to put strip into reset/post mode // delayMicroseconds(500); //Wait for 500us to go into reset (don't really need this due to length of code) } // end of post_frame
Comments are closed.