Lighting Under Desk
So, why?
Funnily I had a practical need for lights under my desk. Because I drop electronics components and other tiny items on the floor every time I do my hobbies there :D And it's dark under the desk, so I have to go get a flashlight to search for the lost bit. So I had a strong wish to get a lamp that is attached to the desk's underside and could be switched on and off easily. I just can't keep the stuff on top of the table successfully.
But why stop with only white light. This is a good location for mood lights also.
My desk is almost an antique item, from 1950's, though somewhat in need for restoring. I wanted the lighting to blend in with the style. Hence the wooden casing.
Components and Materials
Skills prerequisites:
Basic woodworking.
Reading electronics schematics.
Soldering.
Basics of Arduino: uploading a sketch.
Electronics components:
Addressable RGB LED strip also known as NeoPixel or WS2812B (about 5$ on ebay for the stip I used)
An Arduino (I used Pro Mini clone that needs an FTDI cable for programming, 2$ ebay)
Toggle switch for power
Pushbutton for changing modes
470K potentiometer for choosing mood light color (or any value from 10K upwards)
Wires, preferrably aesthetic ones with 2 or 4 cores (I used pulled apart CAT5 cable ...)
Male and female pinheaders
5V power supply, in my case a USB charger
22 Kohm resistor (or 10K or somehting)
360 ohm resistor (anything between 300 and 500)
1000 uF (microFarad) electrolytic capacitor
(10 uF electrolytic capacitor and 100 nF ceramic capacitor also exist in my project but they are not necessary)
My system also includes 3 simple white LEDs that light up area that is not covered by the pixel strip. To implement this:
3pc white LEDs
NPN transistor (for example 1N2222)
1 Kohm resistor
3pc 68 ohm resistors (or calculate value for your LEDs)
Wooden parts:
A strip of wood somewhat longer and wider than the LED strip
A block of wood for the casing for controls and Arduino
Finishing of choice
A random piece of easily cuttable sheet of plastic (sized according to pushbutton + 2 screws)
Some screws
One really tiny screw
Hot melt glue
Superglue
Tools:
Drill and drill bits
Saw
Hand router (but you could make an enclosure with a drill press instead)
Measuring and marking tools (ruler, measuring tape, right angle, pencil)
Clamps
A pair of thick wood beams to temporarily clamp things in between
Soldering iron along with solder and flux
Wire cutters and strippers
Hot glue gun
Woodworking: Enclosure for the Arduino and Controls
I made a box that hides the Arduino and neatly contains the control buttons from a block of wood and some round dowel.
To hollow out the box I used hand router.
If you don't have a router but have access to drill press then you can use this technique http://makezine.com/projects/hollow-2x4-project-enclosure/
1. I marked the size and button positions on the block of wood.
2. Then I drilled holes for the controls. Lesson learned: don't drill wood with metal drill bits and without marking the position with a centre punch first. It ends up VERY misaligned. But I am a non-perfectionist. It will not be very easily observable in the final place of usage.
2. Clamped it back side up between other pieces of the same dimension wood to make a surface where the router could slide.
3. Hollowed out the back with a straight router bit. It's not a visible part of the end product so I just followed some lines by hand. It took several passes with incrementing the depth gradually.
4. The power switch needed a thinner wall than I had achieved with the router. So I drilled from the back side with a big spade drill bit. And just in case did the same for push button hole too.
5. Then I discovered that that hole for the push button that was supposed to allow a pre made dowel to loosely fit was too small. As it was already drilled I did not have an easy option for drilling it with a spade bit. So I routed it bigger with a thin straight router bit. Not an ideal way, better think ahead more next time.
6. Then I somehow rounded the front edges with a rounding bit with a bearing. I used a sacrificial piece of wood and improvised clamping system for the "corner" edges and a wedge system to keep the work piece steady while doing the front edges. This project was basically my first time using the router so some unplanned moves did happen in this step. Whatever.
7. For the covers of push button and potentiometer I cut pieces of 12mm dowel and drilled suitable holes into one end. Push button cover sits inside the hole and gets glued to the button. Potentiometer cover is above the surface of enclosure and will get a tiny screw to hold it in place. This way both controls can be pulled out from the inside if necessary.
Woodworking: Backing for the RGB LED Strip
I did not want to glue the self adhesive LED strip straight to the desk. Wouldn't that be lame? So I made a slat with a groove where the strip can be a little inset. One end will be towards the wall and have wires so I did not make much effort to beautify that part. The other end that is more visible got the natural roundness of a router groove and I like it.
1. Clamping a piece for routing that is so thin and long is problematic. Here I actually made first attempt that failed and that I had to throw away. My second, relatively satisfactory solution was to position the work piece between two longer and sturdier pieces of wood. I clamped them so far apart that the clamps would not interfere routing. Then I used the edge sliding aide that came with the router and slid it on one of the clamping beams. The result was still imperfect because my "beams" were a bit flexible when clamped that way. They bowed a bit because the clamps were offset from the piece in between them. Actually the groove in that slat ended up a little bit curved also. But that bending issue should be eliminated when one would use wider beams. I used what I had at the moment.
2. As I mentioned, I slid the router with it's parallel aide on the side of one beam used for the clamping.
3. Finally I made some screw holes for mounting.
Painting
Decent amount of sanding took place before painting.
I should have carefully sanded the inside of the LED strip groove also because the self adhesive strip did not stick well to the uneven surface I had left there.
The finshing is actually not paint but something called stain-varnish. It was a couple of years old maybe that's why the very visible brush stripes happened even after diluting it with solvent. But again, I'm a non-perfectionist.
Nails in a board as standoffs helped to get the casing and button covers painted on every side in one go.
I put on two coats of that stain-varnish.
Electronics
As there were not very many components I mounted them in "free form" - a mess on the top of the Arduino board. Long wires for the remotely positioned LEDs and 5V power plug came from CAT5 cable.
If you want to understand how the addressable LEDs work and how to work with them check out this page https://learn.adafruit.com/adafruit-neopixel-uberguide/overview .
On my photos there are also two capacitors attached in parallel to the push button but probably these are not of much use because in the end I had to include button debouncing in the code also.
Code
First you need NeoPixel library to make this code work. Here are instructions to installing it https://learn.adafruit.com/adafruit-neopixel-uberguide/arduino-library-installation
Below is my code.
Sort description of what it does:
- When the system resets (gets powered up) the addressable RGB light strip gets lit up with an effect. LEDs in the center of the strip light up first and then one by one towards outside others follow. At first they light up to half brightness. When the sequence ends the brightness gets turned to maximum and also the additional LEDs that are driven by a transistor fade in.
- When the push button gets pressed the mode changes. White light turns off with the effects in reverse order and then a colored light fades in. The color of this mode depends on the position of potentiometer. User can choose basically any color that can be combined from RGB at 1/3 brightness.
- When the push putton gets pressed again previous one color light fades out and a flowing rainbow is shown.
- Pushing the button again starts to loop through these 3 functions again.
Push button is attached to digital pin 2 that can react to interrupts. So and interrupt handler is used to get information about button presses without doing digitalRead.
/** Adressable RGB light strip that can be used as light source (white) * or as mood light (single choosable color or flowing rainbow). * * * * At first white light gets turned on with SciFi-like effect. * Additional simple white LEDs are turned on in this mode also. * On button press the white light goes off (with effect) and * a single color is shown. The color can be adjusted using a * potentiometer. * Next button press switches to drifting rainbow mode. * Following button presses go through the same sequence again. * * Code largely based on Adafruit's NeoPixel library sample code. * Modifications and additions by Anna Libahunt. * */ //Download library from https://github.com/adafruit/Adafruit_NeoPixel #include <Adafruit_NeoPixel.h> #ifdef __AVR__ #include <avr/power.h> #endif #define PIXELPIN 6 //NeoPixel strip control wire. // Parameter 1 = number of pixels in strip // Parameter 2 = Arduino pin number (most are valid) // Parameter 3 = pixel type flags, add together as needed: // NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) // NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) // NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) // NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) // NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) Adafruit_NeoPixel strip = Adafruit_NeoPixel(20, PIXELPIN, NEO_GRB + NEO_KHZ800); // IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across // pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input // and minimize distance between Arduino and first pixel. Avoid connecting // on a live circuit...if you must, connect GND first. // Sytem states as number sequence for easy switching: #define WHITE 0 #define COLOR 1 #define LIGHTSHOW 2 // Other pin connections const byte button = 2; //Pushbutton to switch modes. const byte settingPot = A0; //Potentiometer for choosing single mood light color. const byte extraLed = 5; // Transistor that switches on extra white LED // for additional light source. // Variables // Volatiles get changed by interrupt handler (mode change button triggers interrupt). volatile byte mode = WHITE; //Initial mode. volatile byte lastMode; volatile boolean stateChange = true; volatile unsigned long last_interrupt_time = 0; volatile unsigned long interrupt_time = millis(); int settingReading; //For saving potentiometer reading int R, G, B; //Helpers for calculating pixel colors byte wait; //Helper for intervals unsigned long c = strip.Color(0, 0, 0);//Special type for storing NeoPixel color unsigned int i, j;//helpers for looping through pixels and smooth dimming void setup() { pinMode(settingPot, INPUT); pinMode(extraLed, OUTPUT); pinMode(button, INPUT_PULLUP); //Library takes care of RGB strip pin strip.begin(); strip.show(); // Initialize all pixels to 'off' attachInterrupt(0, changeMode, LOW); } void loop() { stateChange = false; if (mode == WHITE) { startWhite(); //turns white on with effect and stays there until button is pressed } else if (mode == COLOR) { endWhite(); //turns white mode off with effect startColor(); //turns color light on and stays in that mode until button is pressed } else if (mode == LIGHTSHOW) { rainbowCycle(20); //turns flowing rainbow on and stays in that mode until button is pressed } } //Interrupt service routine for the mode change button void changeMode() { // Debouncing interrupt taken from https://github.com/adafruit/Adafruit_NeoPixel interrupt_time = millis(); if (interrupt_time - last_interrupt_time > 200) { lastMode = mode; mode++; if (mode >= 3) { mode = 0; } stateChange = true; } last_interrupt_time = interrupt_time; } void startWhite() { //Ensure everything is off for(i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(0, 0, 0)); } strip.show(); //Turn pixels on from center to out, on low brightness wait = 250; c = strip.Color(70, 70, 70); for(i=0; i<strip.numPixels()/2; i++) { strip.setPixelColor(strip.numPixels()/2+i, c); strip.setPixelColor(strip.numPixels()/2-1-i, c); strip.show(); delay(wait); if(stateChange) return; //detects that interrupt happened, returnes to loop } //Turn brightness gradually up wait = 10; for(j=70; j<256; j++) { c = strip.Color(j, j, j); for(i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, c); } strip.show(); analogWrite(extraLed, j); delay(wait); if(stateChange) return; //detects that interrupt happened, returnes to loop } while(!stateChange) { //pixels stay white as long button interrupt has not happened } } void endWhite() { //Turn brightness gradually down c; wait = 10; for(j=255; j>69; j--) { c = strip.Color(j, j, j); for(i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, c); } strip.show(); analogWrite(extraLed, j); delay(wait); if(stateChange) return; //detects that interrupt happened, returnes to loop } analogWrite(extraLed, 0); //Turn pixels on from ends to center wait = 250; c = strip.Color(0, 0, 0); for(i=0; i<strip.numPixels()/2; i++) { strip.setPixelColor(i, c); strip.setPixelColor(strip.numPixels()-1-i, c); strip.show(); delay(wait); if(stateChange) return; //detects that interrupt happened, returnes to loop } } void startColor() { //Ensure everything is off for(i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, strip.Color(0, 0, 0)); } strip.show(); analogWrite(extraLed, 0); getInput(); //fills global R, G, B values //Turn brightness gradually up in 1 sec period wait = 100; c; for(j=1; j<=10; j++) { c = strip.Color(R*0.1*j, G*0.1*j, B*0.1*j); for(i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, c); } strip.show(); delay(wait); if(stateChange) return; } //Check potentiometer after each little while and show that color wait = 200; while (!stateChange) { getInput(); c = strip.Color(R, G, B); for(i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, c); } strip.show(); delay(wait); } } void getInput() {//sets global R, G, B values from potentiometer reading //Read input settingReading = analogRead(settingPot); //Convert to RGB //0 = 100% red, 341 = 100% green, 682 = 100% blue, 1023 = 100% red if (settingReading - 682 >= 0) { int var1 = settingReading - 682; float var2 = float(var1)/(341.0/255.0); R = byte(var2); B = 255 - byte(var2); G = 0; } else if (settingReading - 341 >= 0) { int var1 = settingReading - 341; float var2 = float(var1)/(341.0/255.0); B = byte(var2); G = 255 - byte(var2); R = 0; } else { int var1 = settingReading; float var2 = float(var1)/(341.0/255.0); G = byte(var2); R = 255 - byte(var2); B = 0; } } // Rainbow code from NeoPixel "strandtest" example void rainbowCycle(uint8_t wait) { analogWrite(extraLed, 0);//ensure extra white led is off uint16_t i, j; for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel for(i=0; i< strip.numPixels(); i++) { strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255)); } strip.show(); delay(wait); if(stateChange) return; } } // Needed for rainbow, from NeoPixel example // Input a value 0 to 255 to get a color value. // The colours are a transition r - g - b - back to r. uint32_t Wheel(byte WheelPos) { WheelPos = 255 - WheelPos; if(WheelPos < 85) { return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); } if(WheelPos < 170) { WheelPos -= 85; return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); } WheelPos -= 170; return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); }
Final Assembly and Mounting
To mount the controls into the case I used hot melt glue. But that would not have been reliable for the push button. For it I cut out a strip from soft plastic and used some smaller screws to place the strip over the button. Having used bigger than normal button made this easy operation.
For the potentiometer shaft cover I chose the tiniest black screw I had in my "saved screws from disassembling stuff" bin. It ended up still protruding a little but not annoyingly.
I cut the end of the LED strip to follow the roundness of the routed groove.
I mounted the control box with screws that I put in from the inside of the drawer cupboard (if that makes sende, basically no screws are visible on the control box itself because they were put in from the other side of the wall it's sitting on).
The slat with the LED strip got two normal screws. One of them is under the pinheader. I should paint the visible one on the front black or brown maybe ...
Rest of it was lazy hot glue work :D I attached the wires to everywhere and the additional white LEDs to the bottom of the drawer box with glue. These are basically invisible places.
Not shown on the pictures or video is the power supply. I could have put an USB plug to the end of the cable. But as I was building this project in parallel to a binary clock that also uses 5V power, I made these two use just one USB port. So I have one USB plug with couple of pinheaders atteched to it. And both this device and my binary clock plug into those pin headers.
Result
So, now should have less stress because of dropping resistors and screws under the table :) Also I like colourful lights. I think It's going to get used.
I think I did not spoil the cool old desk with these few screw holes but added something suitable. I was to ever move it in the middle of the room then I should replace the hot melt glue on the wires with something more stylish. Maybe replace the wires themselves too with something with textile sheath.
About the electronics and code there still is something stubborn going on with the push button. Sometimes it does not switch from the one color to the rainbow on the first attempt. If I figure it out, I'll post in the comments. (Because I will enter a contest and will not be able to change the instuctable itselt later.)