Introduction: Lighting Under Desk

About: Maker of all trades ... or at least many :) Interested in electronics including programming, woodworking, how to grow edible plants in cold climate of my homeland, building clever dwelling houses from mostly t…

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.

Step 1: Components and Materials

Skills prerequisites:

Basic woodworking.

Reading electronics schematics.


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



Drill and drill bits


Hand router (but you could make an enclosure with a drill press instead)

Measuring and marking tools (ruler, measuring tape, right angle, pencil)


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

Step 2: 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

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.

Step 3: 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.

Step 4: 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.

Step 5: 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 .

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.

Step 6: Code

First you need NeoPixel library to make this code work. Here are instructions to installing it

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

#include <Adafruit_NeoPixel.h>

#ifdef __AVR__
  #include <avr/power.h>

#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();; // 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

  interrupt_time = millis();
  if (interrupt_time - last_interrupt_time > 200) {
    lastMode = 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));
  //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);;
    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);
    analogWrite(extraLed, j);
    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
  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);
    analogWrite(extraLed, j);
    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);;
    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));
  analogWrite(extraLed, 0);
  getInput(); //fills global R, G, B values
  //Turn brightness gradually up in 1 sec period
  wait = 100;
  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);
    if(stateChange) return;
  //Check potentiometer after each little while and show that color
  wait = 200;
  while (!stateChange) {
    c = strip.Color(R, G, B);
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, c);

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));
    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);

Step 7: 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.

Step 8: 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.)

Lights Contest 2017

Participated in the
Lights Contest 2017