Everyone agrees that the world can always use a new Larson Scanner build.
A friend celebrated his 13th brithday by building his own super-awesome gaming rig. It came out looking great, but a bit generic. I made the ALSO to add some bling to his rig, but also to give him a tangible way to get into programming on the Ardunio.
Everyone agrees that the world can always use a new Larson Scanner build.
A built-in command line with a mini macro language that lets you program the output sequence
Comes with a dozen or so (really nice!) effects out of the box including...
kit car left
kit car right
sweep right
sweep left
shoot right
shoot left
strobe
right
strobe left
detonate red
detonate blue
police
ambulance
sparkle
warp
breathe
all blue on instantly
all blue off instantly
all blue on fade
all blue off fade
red on
red off
pause 0.01 seconds
wait 0.10 seconds
wait 1.00 seconds
Powers up into a very pleasing demo mode
Flexable code allows for non-evenly laid out LEDs while still maintaining visual continutiy while scanning
Adds capability for a single "out-of-band" red LED for extra special multicolor effects
Uses real linear intensity calculations and gamma correction rather than just a simple integer or sinewave sweep so it looks surprisingly real
Seriously, it really looks like there is a guy back there swinging a flashlight
First was to make the custom besel on the CNC milling machine. |
||
After much trial and error with hot melt glue and casting resin, I ultimately found that normal hardware store two part epoxy was the best thing to fill the back with. This holds the inside letter peices together and also diffuses the light coming from the LEDs. | ||
These are just some Radio Shack colored LEDs. I used hotmelt glue to attache them to a peice of plastic I found on the floor. Thsis holds them back a bit to s the light beam projects onto the full letter. | ||
View inside the plastic tube. | ||
An old peice of ethernet cable works perfectly for wiring the 7 LEDs plus common ground. | ||
Made a handy Ardunio connector out of hotmelt and a popsicle stick I also found on the floor. Always pays to have lots of stuff on the floor when you start a project. | ||
First off, connect up to 7 LEDs to the 7 PWM ports on your Ardunio. In my build, I have a bar of 6 blues and a single red in the middle. Connect the Aurduino up to a computer, run the Ardunio IDE, and then copy and paste to code from below.
If you've used a different number of LEDs than me, you can chaneg the #defines at the top of the code appropriately. You also might want to change the virtualblues[] array to match the location of your LEDs. Imagine that your blue LEDs are on a number line that starts at 0 on the left and ends at 100 on the right. You can see that in my build the blue LEDs are equally distributed except that there is one missing at location 50 becuase this is where the red LED is.
Compile, download, and run the code and your should see the default demo sequence (this sequence is defined lower down in the code as demostring). Yeay!
Now pull up a termial program and connect to Ardunio press any key. You should a command prompt. From here you can type '?' to see a list of available commands.
To add a new effect, code it up and then add it to commandRecs array. You can use the primatives setBlueLed, redOn, and redOff or use the higher level graphics routines like virtualDot, scan, and sineWave. Let me know if you come up with something nice to share with everyone!
#define BLUE_COUNT 6 #define MAX_BLUE_BRIGHTNESS 63 const unsigned int bluePins[] = {3,5,6,9,10,11}; //array of LED pins used with PWM const unsigned int redPin = 12; // Middle red pin
// the location of the blue leds on a virtual number line from 0 - 100 (101 slots) // where 0 is to the left of the first blue and 80 is to the right // (missing 50 becuase that is here the red LED is) const int virtualblues[] = { 20 , 30 , 40 , 60 , 70 , 80 }; // the right end of the virtual blue coordinate line (left end is always 0) #define VIRTUAL_BLUES_END 101
#define GAMMA_MAX 63 int gamma[64] = { 0,0,0,0,0,0,1,1,2,2,3,3,4,5,6,8,9,10,12,13,15,17,19,22,24,26,29,32,35,38, 41,45,48,52,56,60,65,69,74,79,84,89,94,100,106,112,118,124,131,138,145,152, 159,167,175,183,191,200,208,217,227,236,246,255 }; // gamma levels for linear brightness 0-255 #define MAX_SINE 63 int sine[64] = { 0,0,0,2,3,5,8,11,14,18,22,26,30,35,40,45,49,54,59,64,69,73,77,81,85,88,91,94,96,97,99,99,100, 99,99,97,96,94,91,88,85,81,77,73,69,64,59,54,50,45,40,35,30,26,22,18,14,11,8,5,3,2,0,0 }; // full sine wave table for y=0-100 // turn red led on void redon() { digitalWrite( redPin , HIGH ); } // turn red led off void redoff() { digitalWrite( redPin , LOW ); } // set the level of blue LED number l to brightness b // 0<=b<64 and brightness is gamma corrected void setblueled( int l , int b ) { analogWrite( bluePins[ l ] , gamma[ b ] ); } //set all blue LEDs to level n void allblueset(int n) { int l ; for( l =0 ; l < BLUE_COUNT ; l++ ) { setblueled( l , n ); } } void allblueon() { allblueset(MAX_BLUE_BRIGHTNESS); } void allblueoff() { allblueset(0); } //turn all LEDs on void allon() { allblueon(); redon(); } //turn all LEDs off void alloff() { allblueoff(); redoff(); } // send a little help teaser to the serial port void sendhelp() { Serial.println("Universal Programable Bezal Display Unit, (c)2010 Josh Levine [http://josh.com]"); Serial.println("* to stop, # for demo mode, ? for command list, or ! followed by a new command string"); } void setup() { int x; pinMode( redPin , OUTPUT ); alloff(); Serial.begin(9600); sendhelp(); } void detonatered() { int y = 400; while( y > 0 ) { redon(); delay( y ); redoff(); delay( y ); y -= ( y / 10) + 1 ; } } void detonateblue() { int y = 400; while( y > 0 ) { allblueon(); delay( y ); allblueoff(); delay( y ); y -= ( y / 10) + 1 ; } } void bluefadeoff() { int y= MAX_BLUE_BRIGHTNESS; while (y>0) { y--; allblueset( y ); delay(20); } } void bluefadeon() { int y=0; while ( y < MAX_BLUE_BRIGHTNESS ) { allblueset( y ); delay(20); y++; } } // figure out how much of a dot would contibute to the bightness of a given LED // l = led#, x=virtual location (0-100), w=dot width // returns 0-63 for how bright the LED would be int virtualcontribution( int l , int x , int w ) { int y = virtualblues[l]; int d = abs(x-y); // d=distance from dot int b; //brightness if ( d > w ) { b = 0; } else { b = (( w - d ) * MAX_BLUE_BRIGHTNESS) / w ; } return(b); } int bluebuffer[BLUE_COUNT]; // A buffer to store brightnesses while working // set the blue LEDs as if there was a virtual dots at location x where 0<=x<100 // w is width of the dots void virtualdots( int x1 , int x2 , int w ) { int l; // Clear all brightnesses to zero for( l = 0 ; l < BLUE_COUNT ; l++ ) { bluebuffer[l]=0; } // figure out the contribution of first dot for( l = 0 ; l < BLUE_COUNT ; l++ ) { bluebuffer[l]+= virtualcontribution( l , x1 , w ) ; // div by two becuase we are adding 2 together } // add in the contribution of second dot for( l = 0 ; l < BLUE_COUNT ; l++ ) { bluebuffer[l] += virtualcontribution( l , x2 , w ) ; } //set the LEDs for( l = 0 ; l < BLUE_COUNT ; l++ ) { setblueled( l , min( bluebuffer[l] , MAX_BLUE_BRIGHTNESS ) ); // Clip brightnesses that are too high } } void virtualdot( int x , int w ) { int l; for( l = 0 ; l < BLUE_COUNT ; l++ ) { setblueled( l , virtualcontribution( l , x , w) ); } } // blue virtual dot smoothly bouncing back and forth void scan( int start , int end , int width , int slowness ) { int step; int x = start; if ( start < end ) { do { virtualdot( x , width ); delay(slowness); x++; } while (x<=end); } else { do { virtualdot( x , width ); delay(slowness); x--; } while (x>end); } } // two dots - one dot starts on left going right, other the oposite. They cross in the middle void doublescan(int width , int slowness ) { int step; int x = 0; while (x < VIRTUAL_BLUES_END ) { virtualdots( x , VIRTUAL_BLUES_END - x , width ); x++; delay(slowness); } while (x >0 ) { x--; virtualdots( x , VIRTUAL_BLUES_END - x , width ); delay(slowness); } } void sinewave(int width) { int x; for(x=0; x< MAX_SINE ; x++ ) { virtualdot( sine[x] , width ); if ( x == 16 || x ==48 ) { redon(); } else { redoff(); } delay(10); } } void kitkarright() { scan( 0 , 100 , 30 , 10 ); } void kitkarleft() { scan( 100 , 0 , 30 , 10 ); } void sweepright() { scan( 0 , 100 , 30 , 10 ) ; } void sweepleft() { scan( 100 , 0 , 30 , 10 ) ; } void shotright() { scan( 0 , 100 , 20 , 2 ) ; } void shotleft() { scan( 100 , 0 , 20 , 2 ) ; } void stroberight() { int i; for( i = 20 ; i > 0 ; i--) { scan( 100 , 0 , 30 , i ); } } void criscross() { doublescan( 20 , 10 ); } void strobeleft() { int i; for( i = 20 ; i > 0 ; i--) { scan( 0 , 100 , 30 , i ); } } void rotator() { sinewave(20); } void sparkle() { int x; for(x=0;x<100;x++) { int l = random(0,6); int b = random(0,MAX_BLUE_BRIGHTNESS); setblueled( l , b ); delay(10); setblueled( l , 0 ); } } void warp() { int x; for(x=0 ; x<50 ; x++) { virtualdots( 50 + x , 50 - x , 30 ); delay( 5 ); //delay( ( 50-x) ); } allblueoff(); } void breathe() { int x; x = 0; while ( x < MAX_BLUE_BRIGHTNESS ) { x++; allblueset( x ); delay(40- (x/10)); //delay( MAX_BLUE_BRIGHTNESS - x + 1 ); } while ( x > 0 ) { x--; allblueset( x ); delay(40-(x/10)); //delay( MAX_BLUE_BRIGHTNESS - x + 1 ); } } void pause() { delay(10); } void wait() { delay(100); } void sleep() { delay(1000); } typedef struct { char command; void (*function)(); char *description; } commandrec; const commandrec commandrecs[] = { { 'B' , allblueon , "all blue on instantly" } , { 'b' , allblueoff , "all blue off instantly"} , { 'F' , bluefadeon, "all blue on fade" } , { 'f' , bluefadeoff, "all blue off fade" }, { 'R' , redon , "red on" }, { 'r' , redoff , "red off"} , { 'J' , kitkarleft , "kit car left" } , { 'K' , kitkarright, "kit car right" }, { 'S' , sweepright , "sweep right" }, { 'T' , sweepleft , "sweep left"}, { 'U' , shotright , "shoot right" }, { 'V' , shotleft , "shoot left"}, { 'G' , stroberight , "strobe right" }, { 'H' , strobeleft , "strobe left"}, { 'D' , detonatered , "detonate red" } , { 'C' , detonateblue, "detonate blue" }, { 'N' , rotator , "police" }, { 'X' , criscross , "ambulance" }, { 'L' , sparkle , "sparkle" }, { 'W' , warp , "warp" }, { 'E' , breathe , "breathe" }, { 'P' , pause , "pause 0.01 seconds" }, { 'I' , wait , "wait 0.10 seconds" } , { 'Z' , sleep , "wait 1.00 seconds" } , { 0x00 , NULL , NULL } }; char *DEMO_STRING = "BfIIIIIIBfIIIIIBfIIIDrBfLLLLLNNNNNNNbrSTSTSTSTXXXXXKKKfJJJfCRbrZZVZZUZZUIIIIIUIIUIIIVIVIIIVZIIIIIVIUIIUVIUVIUVVUUVVVURIIBrfZZZWWWWWWJKJKJKJKJKEPEPEPEPEPEPEPGbZZZ"; void docommand( char command ) { int i=0; while (commandrecs[i].command != 0x00) { if (commandrecs[i].command == command ) { Serial.print( command ); Serial.print('-'); Serial.println( commandrecs[i].description ); (*commandrecs[i].function)(); // don't return here to avoid jitter } i++; } } char *commandstring = DEMO_STRING; void setdemomode() { Serial.println("Entering demo mode"); commandstring = DEMO_STRING; } void showprompt() { int i=0; Serial.println(""); Serial.println("Current command string is..."); Serial.println(commandstring); Serial.println("Available commands:"); while ( commandrecs[i].command != 0x00 ) { Serial.print( commandrecs[i].command ); Serial.print( " - " ); Serial.print( commandrecs[i].description ); Serial.println(""); i++; } } #define COMMANDSTRINGBUFFER_MAX 100 char commandstringbuffer[COMMANDSTRINGBUFFER_MAX+1]; void readcommandstring() { char k; int commandstringlen =0; Serial.println("Enter new command string followed by enter..."); while (1) { while (Serial.available()==0); // wait for key k = Serial.read(); if (k==13 || k==10) { // enter Serial.println(""); if (commandstringlen==0) { Serial.println("Nothing entered, command string unchanged."); } else { commandstringbuffer[commandstringlen] = 0x00; commandstring = commandstringbuffer; Serial.println("New command string set."); } return; } if (commandstringlen < COMMANDSTRINGBUFFER_MAX) { Serial.print( k ); commandstringbuffer[ commandstringlen++ ] = k; } else { Serial.println("too long"); } } } char *nextcommand = ""; void loop() { if ( (*nextcommand) == 0x00 ) { nextcommand = commandstring; } docommand( *nextcommand ); nextcommand++; if (Serial.available()>0) { char action = Serial.read(); switch (action) { case '?': showprompt(); break; case '!': readcommandstring(); nextcommand = commandstring; break; case '#': setdemomode(); break; case '*': commandstring=""; Serial.println("Stopped. Enter new command string or demo mode to restart."); break; case 13: break; case 10: break; // ignore extra unexpected line breaks default: sendhelp(); }; } }
11/27/2010 - Published.
1/8/11 - Fixed link in video.