Introduction: Smart Map of Idaho With LED Data + Art
I've always wanted a way to artistically and dynamically display geographic data by "painting" a map with light. I live in Idaho and love my state so I thought this would be a great place to start! In addition to being a piece of art with cool illumination effects, it provides useful information as well. For example, you can show a "heat map" per count of population density, precipitation levels, elevation maximums/minimums, number of acres of wilderness area, etc. After doing this map of Idaho, I'm motivated to do something similar on a global scale!
For this instructable you will need the following:
- (2) 2'x4' sheet of 1/4MDF
- (1) 10' piece 1"x8" Pine board
- (1) sheet of light diffusing acrylic
- 2 Strings of (50) ws2812B prewired indexible LED
- 5 volt power supply
- Stain, paint, glue
- Arduino Micro or equivalent
Tools Needed
- CNC Machine
- Soldering iron
- Clamps
- Sand Paper
Step 1: Glue Up the Wood
Whenever I glue up panels of wood, I always biscuit join them together. This prevents splitting due to shrinkage as the wood dries. This is especially important on this project as the surface area connecting the pieces together will be reduced due to the CNC pocketing operations. After getting a good bead of glue in on both sides as well as in both halves of the biscuit cavities, clamp and leave for 24 hours.
After pulling the clamps apart, use a palm sander (or if you are brave a belt sander) and sand the joints smooth. Invariably you will have glue squeeze out the joints and you want to sand the board down to be as flat and blemish free as possible.
now that we have the three panels we will be needing let's move on to the CNC work!
Step 2: CNC the Three Panels (Border Panel, Pine Core and LED Panel)
There are three panels that make up the project. You can see the models in the software I use. The map data was purchased from the excellent royalty free maptorian map packs. Amazing detail and value here! The CAD files are attached in the next step if you'd like either the DXF for the CAD or the Vector files.
The LED core panel is basically a machined 1/4" MDF sheet that holds the LEDs with a tight friction fit. You will notice on this panel a large "pocket" around the LED. This is to allow the light to diffuse as quickly as possible so to avoid hot spots on the acrylic.
The core is the pine panel we glued up in the previous step and represents the backdrop for the project. In order for the light to reach the acrylic panels we machined away each county.
Finally the top panel is machined with just the outlines of the counties and the state boarder. Each county has a small shelf that will receive the 1/8" light diffusing acrylic.
Speaking of acrylic, time to machine these next.
Step 3: Machine the Counties From an Acrylic Sheet
Machining the counties from acrylic took a little trial and error. Acrylic can melt if machined to slowly so a proper feedrate is necessary to get good results. Another tip is to use as large a tool as possible with good suction to clear away the chips. Small tools tend to not clear the chips as easily and build up heat that produces that undesirable melting.
I was able to get the resolution I needed with an 1/8" upcut two flute spiral bit at 18,500rpm and a feedrate of 200ipm. A good feeds and speeds calculator is useful here! I'd recommend the one at cnccookbook.com. A single flute bit would have worked even better but I didn't have one on hand. Keeping small tabs on these pieces in the CAM work is important to keep the finished pieces from breaking away and getting projected into the room!
The magic offset for making counties the right size, turned out to be .075 setback from the centerline on the cad drawing. This made allowance for 1/2 of the 1/8" border plus a little extra for the panel to drop in place. A small amount of sanding was required on certain pieces to get them to fall into place. Again, a bunch of friction fit pieces made this quick and easy work.
Getting all the counties to fit onto a single piece of acrylic was easy work with my vectric software that has a nesting feature to maximize sheet usage.
Just for fun I started test fitting some pieces. Starting to come along. Cool!
Want the files for machining the counties. Sure! See attachment.
Step 4: Paint and Stain
Before we assemble all our pieces, we should paint and stain first. I used a combination of stains for the wood panel, spray paint for the border layer and a reflective white for the LED layer. Quick work and we are on to the assembly. Having fun!
Step 5: Glue Up Panels
Now it's time to glue up the bottom panel to the bottom of the pine core and then the MDF state border panel to the to of the pine core. I just used a series of clamps to do this.
Step 6: Wire Up the LED's With Friction Fit and Connect Arduino
This nightmare job was super simple with the friction fit tolerances here. I used the back end of a pen to press them into place . The virtually snapped in and will not come out without significant force. No glue of any sort was used for this part of the project. This makes assembly, WAY easy! I've done many projects where I've had to wrangle wiring for hours and this literally took 10 minutes. This is by far the easiest way. I tried to wire up the state in zigzag order keeping the groupings such that each county was sequential along the string.
Connecting to the arduino was simple through the use of a small breadboard and connecting wires. The power supply was an ebay purchase. 5v and 8amps is overkill for this project but gives plenty of overhead. Wiring these things is dead simple. +5v to the VCC pin, Ground to the ground pin and then power the strand with the same 5v source. The only remaining pin is the data pin that powers the string! In my case, I used D7 for data. Now on to programming!
Step 7: Coding the Arduino
The LED's are powered by an arduino which makes coding cake. Some of the initial routines were borrowed (ie. stolen) from the excellent ws2813fx library on github. It was easy to modify these routines to do what I needed them to do. The full scope of the code would be difficult to explain in entirety but here's a few highlights!
Here's the available demonstration routines:
<p>#define FX_MODE_STATIC 0<br>#define FX_MODE_BLINK 1 #define FX_MODE_BREATH 2 #define FX_MODE_COLOR_WIPE 3 #define FX_MODE_COLOR_WIPE_INV 4 #define FX_MODE_COLOR_WIPE_REV 5 #define FX_MODE_COLOR_WIPE_REV_INV 6 #define FX_MODE_COLOR_WIPE_RANDOM 7 #define FX_MODE_RANDOM_COLOR 8 #define FX_MODE_SINGLE_DYNAMIC 9 #define FX_MODE_MULTI_DYNAMIC 10 #define FX_MODE_RAINBOW 11 #define FX_MODE_RAINBOW_CYCLE 12 #define FX_MODE_SCAN 13 #define FX_MODE_DUAL_SCAN 14 #define FX_MODE_FADE 15 #define FX_MODE_THEATER_CHASE 16 #define FX_MODE_THEATER_CHASE_RAINBOW 17 #define FX_MODE_RUNNING_LIGHTS 18 #define FX_MODE_TWINKLE 19 #define FX_MODE_TWINKLE_RANDOM 20 #define FX_MODE_TWINKLE_FADE 21 #define FX_MODE_TWINKLE_FADE_RANDOM 22 #define FX_MODE_SPARKLE 23 #define FX_MODE_FLASH_SPARKLE 24 #define FX_MODE_HYPER_SPARKLE 25 #define FX_MODE_STROBE 26 #define FX_MODE_STROBE_RAINBOW 27 #define FX_MODE_MULTI_STROBE 28 #define FX_MODE_BLINK_RAINBOW 29 #define FX_MODE_CHASE_WHITE 30 #define FX_MODE_CHASE_COLOR 31 #define FX_MODE_CHASE_RANDOM 32 #define FX_MODE_CHASE_RAINBOW 33 #define FX_MODE_CHASE_FLASH 34 #define FX_MODE_CHASE_FLASH_RANDOM 35 #define FX_MODE_CHASE_RAINBOW_WHITE 36 #define FX_MODE_CHASE_BLACKOUT 37 #define FX_MODE_CHASE_BLACKOUT_RAINBOW 38 #define FX_MODE_COLOR_SWEEP_RANDOM 39 #define FX_MODE_RUNNING_COLOR 40 #define FX_MODE_RUNNING_RED_BLUE 41 #define FX_MODE_RUNNING_RANDOM 42 #define FX_MODE_LARSON_SCANNER 43 #define FX_MODE_COMET 44 #define FX_MODE_FIREWORKS 45 #define FX_MODE_FIREWORKS_RANDOM 46 #define FX_MODE_MERRY_CHRISTMAS 47 #define FX_MODE_FIRE_FLICKER 48 #define FX_MODE_FIRE_FLICKER_SOFT 49 #define FX_MODE_FIRE_FLICKER_INTENSE 50 #define FX_MODE_CIRCUS_COMBUSTUS 51 #define FX_MODE_HALLOWEEN 52 #define FX_MODE_BICOLOR_CHASE 53 #define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_ICU 55</p>
And a look into one of the sample routines.
<p>uint16_t WS2812FX::mode_breath(void) {<br> // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // step uint16_t breath_delay_steps[] = { 7, 9, 13, 15, 16, 17, 18, 930, 19, 18, 15, 13, 9, 7, 4, 5, 10 }; // magic numbers for breathing LED uint8_t breath_brightness_steps[] = { 150, 125, 100, 75, 50, 25, 16, 15, 16, 25, 50, 75, 100, 125, 150, 220, 255 }; // even more magic numbers!</p><p> if(SEGMENT_RUNTIME.counter_mode_call == 0) { SEGMENT_RUNTIME.aux_param = breath_brightness_steps[0] + 1; // we use aux_param to store the brightness }</p><p> uint8_t breath_brightness = SEGMENT_RUNTIME.aux_param;</p><p> if(SEGMENT_RUNTIME.counter_mode_step < 8) { breath_brightness--; } else { breath_brightness++; }</p><p> // update index of current delay when target brightness is reached, start over after the last step if(breath_brightness == breath_brightness_steps[SEGMENT_RUNTIME.counter_mode_step]) { SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) % (sizeof(breath_brightness_steps)/sizeof(uint8_t)); }</p><p> int lum = map(breath_brightness, 0, 255, 0, _brightness); // keep luminosity below brightness set by user uint8_t w = (SEGMENT.colors[0] >> 24 & 0xFF) * lum / _brightness; // modify RGBW colors with brightness info uint8_t r = (SEGMENT.colors[0] >> 16 & 0xFF) * lum / _brightness; uint8_t g = (SEGMENT.colors[0] >> 8 & 0xFF) * lum / _brightness; uint8_t b = (SEGMENT.colors[0] & 0xFF) * lum / _brightness; for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { Adafruit_NeoPixel::setPixelColor(i, r, g, b, w); }</p><p> SEGMENT_RUNTIME.aux_param = breath_brightness; return breath_delay_steps[SEGMENT_RUNTIME.counter_mode_step]; }</p>
Full source can be downloaded from the ws2812fx github repository.
Step 8: Enjoy the Artistic Light Display
I was very happy with the result! It really is a joy to watch and I'm excited to continue to play around with various data display configurations! Feel free to ask any questions or hit me up for any information I missed.