Building an Arduino powered LED lamp
A while ago my dad acquired a large ceramic egg full of holes and a heavy metal stand from a garage sale. His idea was that these two items could be combined to form a lamp. Clearly this called for an Arduino project.

A ceramic egg

The metal stand

Egg and stand combined
Development
Initially, I used an Arduino Nano without attached headers, soldering the wires directly to the Nano and components. This caused some issues as it made it more difficult to add new features later, mostly due to doing fiddly soldering without a clamp.
Given that the internals will be out of sight most of the time, I redid it with an Arduino Nano with headers attached and jumper cables for connecting components. This looks messier but makes it far easier to modify. Some parts have a female header plug on one end and the other end soldered onto the component, mostly because I wanted to reuse the components from the soldered attempt.
The LED modules by themselves did not result in a great effect as the light intensity varied too much depending on how the viewer was positioned relative to the lamp. To spread the light more evenly a diffuser was called for. I remember reading somewhere that an ping pong ball makes an excellent diffuser. One visit to a $2 shop, and a packet of ping pong balls was mine. Drilling a hole to insert the LED through worked well for mounting, but the colours were not evenly distributed when looking down on the LED. See the image titled “Colour banding”. There are many articles describing how to improve the diffusion of LEDs, such as this article.
Initial LED module, and with diffuser.

Colour banding
I ended up changing the LED modules I was using to a different LED design. The second LED module is brighter and has an even colour, as you can see in the image titled “Second module with diffuser”. The LED appears very blue in the picture, but it is showing white, the same as the LED in the “Colour banding” image.

Second LED module with ping pong ball diffuser

Second module with diffuser
Once the basic design was working, I looked through my parts box to see new features could be added. A motion sensor was considered, intensifying the light of the lamp the closer you got. There were some practical difficulties with sensor range so this idea was dropped.
The next obvious candidate was an IR receiver. Because clearly you need to be able to control your lighting fixtures from the other side of the room. It is a handy interface for controlling the various lighting effects.
The final wiring diagram can be seen below.

Wiring diagram for the lamp - created with fritzing
I did think about creating a scaffold for the LED modules within the egg but it proved unnecessary.
Parts
The parts list for the final version of this project is pretty simple. The only parts I had to order were the RGB LEDs. Note that the links to parts are to show the part only, I haven’t used any of the linked stores.
- (1x) arduino nano
- (1x) IR receiver
- (2x) RGB LED
- (1x) remote similar to IR remote control
- (1x) on/off switch
- (1x) power coupling
- (1x) 6xAA battery pack
- (2x) ping pong ball
- jumper cables

The parts look rather a mess in the photo, but it doesn’t matter once they are installed in the lamp.
Shrink wrapping the IR receiver leads in white helps hide it as it needs to be mounted on the outside of the lamp.

The stand is just the right size to contain the battery pack and the arduino.
Code
The initial code I wrote for the LED effects was a mess. I tidied it up and extracted it into a library called RGBEffects. See Introducing RGBEffects: An RGB LED effect library for Arduino for a detailed dissection of the library and its capabilities. I make no claims to the quality of the library, it probably uses far more memory than it needs to but it does work. The effects available include a solid colour, looping through a set of colours, transitioning between two points in a colour cube1, and fading in and out between colours2.
IRLib provides the IR command decoding functionality. It was simple to use and has been reliable. To obtain the IR codes for my remote, I used an example that came with IRLib.
Most of the logic in the lamp code is for dealing with IR commands. The remote can be used to increase or decrease the time between colour updates, pause the colour changes, as well as selecting a specific effect.
// IRLib from https://github.com/cyborg5/IRLib | |
#include <IRLib.h> | |
// RGBEffects from https://github.com/ajesler/Arduino-RGBEffects | |
#include <RGBEffects.h> | |
// if isPaused is true, update will stop being called until the lamp is unpaused. | |
int isPaused = 0; | |
// the time between effect updates. | |
int effect_delay = 2100; | |
#define EFFECT_DELAY_STEP 400 | |
#define EFFECT_DELAY_MIN 100 | |
#define PAUSE_DELAY 300 | |
/* START LED EFFECTS CONFIGURATION */ | |
#define RED_PIN 5 | |
#define GREEN_PIN 6 | |
#define BLUE_PIN 9 | |
RGBEffects rgbEffects( RED_PIN, GREEN_PIN, BLUE_PIN ); | |
/* END LED EFFECTS CONFIGURATION */ | |
/* START IR CONFIGURATION*/ | |
#define IR_RECV_PIN 4 | |
IRrecv irReceiver(IR_RECV_PIN); | |
IRdecode irDecoder; | |
// Hex codes of remote buttons | |
#define BTN_PLAY_PAUSE 0xFF22DD | |
#define BTN_PLUS 0xFF906F | |
#define BTN_MINUS 0xFFA857 | |
#define BTN_REWIND 0xFF02FD | |
#define BTN_FAST_FORWARD 0xFFC23D | |
#define BTN_0 0xFF6897 | |
#define BTN_1 0xFF30CF | |
#define BTN_2 0xFF18E7 | |
#define BTN_3 0xFF7A85 | |
#define BTN_4 0xFF10EF | |
#define BTN_5 0xFF38C7 | |
#define BTN_6 0xFF5AA5 | |
#define BTN_7 0xFF42BD | |
#define BTN_8 0xFF4AB5 | |
#define BTN_9 0xFF52AD | |
#define BTN_MODE 0xFF629D | |
/* END IR CONFIGURATION*/ | |
void increaseEffectDelay(){ | |
effect_delay += EFFECT_DELAY_STEP; | |
} | |
void decreaseEffectDelay(){ | |
if(effect_delay - EFFECT_DELAY_STEP >= EFFECT_DELAY_MIN){ | |
effect_delay -= EFFECT_DELAY_STEP; | |
} | |
} | |
void processIRCommand(int command){ | |
switch(command){ | |
case BTN_PLAY_PAUSE: | |
isPaused = !isPaused; | |
break; | |
case BTN_0: | |
rgbEffects.setEffect(EFFECT_RAINBOW); | |
break; | |
case BTN_1: | |
rgbEffects.setEffect(EFFECT_FADE); | |
break; | |
case BTN_2: | |
rgbEffects.setEffect(EFFECT_CUBE); | |
break; | |
case BTN_3: | |
rgbEffects.setEffect(EFFECT_SOLID_RED); | |
break; | |
case BTN_4: | |
rgbEffects.setEffect(EFFECT_SOLID_GREEN); | |
break; | |
case BTN_5: | |
rgbEffects.setEffect(EFFECT_SOLID_BLUE); | |
break; | |
case BTN_6: | |
rgbEffects.setEffect(EFFECT_SOLID_YELLOW); | |
break; | |
case BTN_7: | |
rgbEffects.setEffect(EFFECT_SOLID_PURPLE); | |
break; | |
case BTN_8: | |
rgbEffects.setEffect(EFFECT_SOLID_VIOLET); | |
break; | |
case BTN_9: | |
rgbEffects.setEffect(EFFECT_SOLID_WHITE); | |
break; | |
case BTN_PLUS: | |
increaseEffectDelay(); | |
break; | |
case BTN_MINUS: | |
decreaseEffectDelay(); | |
break; | |
case BTN_MODE: | |
rgbEffects.nextEffect(); | |
break; | |
} | |
} | |
void setup(){ | |
irReceiver.No_Output(); //Turn off any unused IR LED output circuit | |
irReceiver.enableIRIn(); // Start the receiver | |
randomSeed(analogRead(0)); | |
rgbEffects.setEffect(EFFECT_RAINBOW); | |
} | |
void loop(){ | |
// handle any received IR commands | |
if (irReceiver.GetResults(&irDecoder)) { | |
irDecoder.decode(); | |
if(irDecoder.decode_type==NEC){ | |
processIRCommand(irDecoder.value); | |
} | |
// get ready to receive the next command | |
irReceiver.resume(); | |
} | |
if(!isPaused){ | |
rgbEffects.update(); | |
delay(effect_delay); | |
} else { | |
delay(PAUSE_DELAY); | |
return; | |
} | |
} |
In Action

In the dark

In brighter surroundings