I'm in the process of constructing/setting up my office in the house, and for lighting, I have decided that I want to use xmas light-style lighting. Many years ago, I used to light my room with multicolored incandescent lights. I loved the warm indirect glow, and smooth light without a single light source. This time, I'm going to take it a step further.
While there's nothing about this project yet that is really innovative over what others have done, it is the first step to getting the office lighting done. The real fun will come into play once I'm able to hang this up, and start programming effects, and tying those effects in to physical or time-based events.
A couple years back I picked up a strand of addressable LED lights, similar to this one, available at adafruit.com. I got a strand of 50 lights, blew out one of them while being stupid, and used a few of them in Jasper's Toy Box (posts to come about that eventually), so I'm left with 42 lights. A nice number.
In any event, the plan is to hang them up around the upper perimeter of the room, and it will give a nice comfortable glow to illuminate the room. I can also extend it by doing lighting effects with the color. For example, in the evening I can have all of them dim blue, and randomly twinkle one to white, to simulate a star in the sky. I could also tie them in to an automation system to glow a particular corner of the room red or yellow when i have email from a specific person. I could also adjust their color based on the content of my monitor, or the light outisde, etc.
The basic design for the control circuitry is that there will be an Arduino-based AVR micro (actually one of the D-15 servo controllers I've appropriated), which is perfect, since the strands only need two lines to control them. The host computer will send down codes to address the LEDs (set all to color X, set led Y to color X, etc) and this will pass on the content to the strand, and twiddle the data lines and all of that fun stuff. I had considered putting more "smarts" into the micro, but the amount of space in there would severely limit the kind of content I could "display", so I decided to put all of the grunt work back on the host computer.
To power it, I needed to get a 5 volt power supply. I snagged a power brick from an old external drive case, as well as a standard PC power connector, from a failed power supply, and spliced the two of them together.
Copious, yet appropriate amounts of heat shrink tubing and splicing some wires yielded a nice power supply.
Next, I built an interface board to tie it all together. The ports on the board are (left to right) - 6 pin FTDI interface for serial IO, 2 pin jumper (power the D15 from the power supply rather than FTDI source), 3 pin power, 4 pin light strand connector. You can also see in this picture, the process of crimping the terminals for the molex connector on the LED strand's wires.
I kept the layout and pinout of the FTDI the same as I used for my serial node experiment. This will help me plug that connector in correctly. I still need to add visual cues (colored sharpie markings) to help me align the pins correctly. The power connector has GND on pins 1 and 3, and +5V on pin 2. Keeping it symmetrical will help me always plug it in correctly, reducing the chance that I will blow it all up. The 4 pin connector is the same pinout as the wiring of the LEDs. GND, Data, Clock, +5.
The jumper on the board (dis)connects the power header from the D15 and FTDI portion. If I make standalone firmware for it, I can power everything from the power supply, if need be. The tiny green LED on the board just lights when the D15 has power. A nice indicator in case everything else is not functioning.
The protocol I used for this is very simple. There's a command character sent through serial, then the data for that command. If the firmware is expecting a command character but gets something it doesn't understand, it just keeps checking the serial input for a command it knows. The protocol is as follows:
p<index of LED><red value><green value><blue value>
f<red value><green value><blue value>
Force all of the lights to the specified color. This is handy for clearing everything to black, or flashing/fading effects.
Here's the Arduino firmware used to handle all of this: (Note: it requires that the strand's library be installed.)
/*
* Firmware for D15-based LED strand controller. (Arduino.ino)
* Scott Lawrence yorgle@gmail.com January 2013
*/
#include "SPI.h"
#include "WS2801.h"
/*****************************************************************************
based on the example sketch for driving WS2801 pixels
*****************************************************************************/
int dataPin = 12;
int clockPin = 11;
int indicatorPin = 8; // LED indicator on the D15 node
// Set the first variable to the NUMBER of pixels. 25 = 25 pixels in a row
WS2801 strip = WS2801(42, dataPin, clockPin);
void setup()
{
// set up the indicator for output
pinMode( indicatorPin, OUTPUT );
digitalWrite( indicatorPin, LOW );
// set up the strip
strip.begin();
// Update LED contents, to start they are all 'off'
strip.show();
// ready light
allColored( Color( 0, 0, 0 ));
setLED( 0, Color( 0, 255, 0 ));
// start Serial IO
Serial.begin( 9600 );
}
// Create a 24 bit color value from R,G,B
uint32_t Color(byte r, byte g, byte b)
{
uint32_t c;
c = r;
c <<= 8;
c |= g;
c <<= 8;
c |= b;
return c;
}
void setLED( int idx, uint32_t c ){
strip.setPixelColor( idx, c );
strip.show();
}
int getSerial()
{
while( !Serial.available() );
return Serial.read();
}
void allColored( uint32_t c )
{
int i;
for (i=0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, c);
}
strip.show();
}
void loop()
{
int cmd; int ii, rr,gg,bb;
// blink the indicator LED
digitalWrite( indicatorPin, millis() & 0x80 );
if( Serial.available() ) {
cmd = Serial.read();
// current commands:
// p<index><red><green><blue>
// set a single pixel a color
// f<red><green><blue>
// flood fill with this color
if( cmd == 'p' ) {
ii = getSerial();
rr = getSerial();
gg = getSerial();
bb = getSerial();
setLED( ii, Color(rr, gg, bb) );
}
if( cmd == 'f' ) {
rr = getSerial();
gg = getSerial();
bb = getSerial();
allColored( Color(rr, gg, bb) );
}
}
}
/*
* Firmware for D15-based LED strand controller. (Arduino.ino)
* Scott Lawrence yorgle@gmail.com January 2013
*/
#include "SPI.h"
#include "WS2801.h"
/*****************************************************************************
based on the example sketch for driving WS2801 pixels
*****************************************************************************/
int dataPin = 12;
int clockPin = 11;
int indicatorPin = 8; // LED indicator on the D15 node
// Set the first variable to the NUMBER of pixels. 25 = 25 pixels in a row
WS2801 strip = WS2801(42, dataPin, clockPin);
void setup()
{
// set up the indicator for output
pinMode( indicatorPin, OUTPUT );
digitalWrite( indicatorPin, LOW );
// set up the strip
strip.begin();
// Update LED contents, to start they are all 'off'
strip.show();
// ready light
allColored( Color( 0, 0, 0 ));
setLED( 0, Color( 0, 255, 0 ));
// start Serial IO
Serial.begin( 9600 );
}
// Create a 24 bit color value from R,G,B
uint32_t Color(byte r, byte g, byte b)
{
uint32_t c;
c = r;
c <<= 8;
c |= g;
c <<= 8;
c |= b;
return c;
}
void setLED( int idx, uint32_t c ){
strip.setPixelColor( idx, c );
strip.show();
}
int getSerial()
{
while( !Serial.available() );
return Serial.read();
}
void allColored( uint32_t c )
{
int i;
for (i=0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, c);
}
strip.show();
}
void loop()
{
int cmd; int ii, rr,gg,bb;
// blink the indicator LED
digitalWrite( indicatorPin, millis() & 0x80 );
if( Serial.available() ) {
cmd = Serial.read();
// current commands:
// p<index><red><green><blue>
// set a single pixel a color
// f<red><green><blue>
// flood fill with this color
if( cmd == 'p' ) {
ii = getSerial();
rr = getSerial();
gg = getSerial();
bb = getSerial();
setLED( ii, Color(rr, gg, bb) );
}
if( cmd == 'f' ) {
rr = getSerial();
gg = getSerial();
bb = getSerial();
allColored( Color(rr, gg, bb) );
}
}
}
For now, that's it. I made a simple interface on the desktop side in Processing, adapted from my previous controllable pixel software, to let me click and change the color of an LED. I also added some key commands to do simple effects with the lights. (all red/green/blue. flash, etc)
/*
* Pixel Strand Driver (Processing.pde)
* Scott Lawrence yorgle@gmail.com January 2013
*/
import processing.serial.*;
import processing.serial.*;
Serial myPort; // comms to the micro
PImage colorsPic; // color wheel image
color lastColor = color( 255 ); // last clicked color
PFont fnt; // font for display
void setup()
PFont fnt; // font for display
void setup()
{
// setup window
size(450, 370 );
// setup serial
// setup serial
println( Serial.list()[0] );
println( Serial.list()[1] );
println( Serial.list()[2] );
println( Serial.list()[3] );
println( Serial.list()[4] );
myPort = new Serial( this, Serial.list()[4], 9600 );
// setup the pic
colorsPic = loadImage("color_sphere.png");
image(colorsPic, 0, 0);
loadPixels();
// setup the font
fnt = loadFont( "Glass_TTY_VT220-20.vlw" );
textFont( fnt );
noSmooth();
}
// for main color display
color frameColor;
int selector = 0;
// for effects
// for effects
float r,g,b;
int flash;
void draw()
{
// fill with the last color
background( lastColor );
// draw the image over it
image( colorsPic, 0, 0 );
// draw the "selector" number (0..9)
fill( lastColor );
String s = "" + selector;
text( s, 5, 20 );
// if we're doing the "flash" animation, handle that now
if( flash != 0 ) {
if( flash == 1 ) {
flash = 2;
fillPixels( 1.0, 1.0, 1.0 );
} else {
flash = 1;
fillPixels( 0.0, 0.0, 0.0 );
}
}
}
void keyPressed()
{
// key 0-9 picks from the first 10 LEDs to change
if( key >= '0' && key <='9' ) {
selector = key - '0';
}
// key "a" means that the color picked will affect ALL LEDs
if( key == 'a' ) { selector = -1; }
// key "F" will toggle flashing all of the LEDs
if( key == 'f' ) {
flash = (flash == 0)?1:0;
}
// keys r,g,b will toggle the mask to full intensity
if( key == 'r' ) { r=(r==1.0)?0.0:1.0; sendRGBMask(); }
if( key == 'g' ) { g=(g==1.0)?0.0:1.0; sendRGBMask(); }
if( key == 'b' ) { b=(b==1.0)?0.0:1.0; sendRGBMask(); }
}
// for the "full intensity" flags, send that down
// for the "full intensity" flags, send that down
void sendRGBMask()
{
fillPixels( r, g, b );
}
// send down a "fill all pixels" command with the specified color
// send down a "fill all pixels" command with the specified color
void fillPixels( float r, float g, float b )
{
int rr = int( r * 254.0 );
int gg = int( g * 254.0 );
int bb = int( b * 254.0 );
myPort.write( "f" );
myPort.write( char(rr) );
myPort.write( char(gg) );
myPort.write( char(bb) );
}
// send down a "set this pixel" command with the specified color
// send down a "set this pixel" command with the specified color
void setPixel( int index, float r, float g, float b )
{
int rr = int( r * 254.0 );
int gg = int( g * 254.0 );
int bb = int( b * 254.0 );
myPort.write( "p" );
myPort.write( char(index) );
myPort.write( char(rr) );
myPort.write( char(gg) );
myPort.write( char(bb) );
}
// on mouse press, set the color under the cursor, and send it down
void mousePressed()
// on mouse press, set the color under the cursor, and send it down
void mousePressed()
{
lastColor = get( mouseX, mouseY );
if( selector >= 0 ) {
setPixel( selector, red(lastColor)/255.0,
green(lastColor)/255.0,
blue(lastColor)/255.0 );
} else {
fillPixels( red(lastColor)/255.0,
green(lastColor)/255.0,
blue(lastColor)/255.0 );
}
}
// handle dragging the mouse as well
// handle dragging the mouse as well
void mouseDragged(){
mousePressed();
}
NOTE: All of the source/projects for this are available on github.
Eventually, I will write better desktop software which will use the LEDs for indication of events, as well as f.lux style color effects throughout the day, audio/visual synchronization to media being played, and other effects as well as time goes on
NOTE: All of the source/projects for this are available on github.
No comments:
Post a Comment