This page was last updated 1 August 2009.

Computer-controlled LED lamp

Wouldn't it be cool to have a desk lamp that could change color, for example flashing red when you receive a mail? Or that pulls weather reports from the Internet, and translates the forecast temperature to a color? It could add some white flashes if thunderstorms are forecast. Or when your phone rings, it could flash green if it's a friend or red if it's your boss, based on caller ID. The possibilities are endless.

This page shows you to build a lamp that can change color, and can be controlled by a computer. Simple Python and C programs for your PC or Mac are provided that let you program color patterns into the lamp. Implementing the weather forecast and other ideas are then up to you. Web scraping with wget is easy if you understand a scripting language. The whole project should cost under 100 euro, half of which is for the lamp (glass ball, LED module, microcontroller, and odds and ends), and the other half for the flash programmer if you don't have one.

There are enormously bright LEDs now, bright enough to be used as lamps. They also come with three emitters, red, green, and blue, on a single module, spaced closely enough that the colors mix. One such module is the Z-Power by Seoul Semiconductor. This page shows how to build a lamp using this module that can be programmed to show any color or cycle of colors, as instructed by a PC or Mac over a serial RS232 line. (Or USB, using a cheap USB-to-serial adapter - unfortunately USB cables cannot be longer than about two meters before they become either unreliable or very expensive.)

Building the lamp takes three steps:

  1. Build the circuit. It's extremely simple. Install it into some suitable lamp with a diffusor; mine is a glass ball with a diameter of 20cm.

  2. Program the microcontroller. This requires a programming device that works with Mac, Linux, or Windows computers. Cost is about 50 euro.

  3. Connect the lamp to a computer, and run a program that controls the color of the lamp. Python and Linux C examples are included, but other platforms should be very easy to write.

The picture to the right shows the Z-Power LED assembly without heat sink. The mounting plate is about the size of a five-cent coin. I got mine at LEDs and more, but there are cheaper sources on the Internet. You should pay around 10 euro.

1. The hardware

The circuit consists of the Z-Power LED module, which must be mounted on a heat sink because it can run hot. Each LED is driven by a 8550 transistor connected to an Atmel ATMEGA 8-16PU microcontroller. A MAX-232 chip converts the serial signals of the microcontroller to RS-232 voltages. The circuit runs on 5V; I use a cheap power supply used for external USB hard disk enclosures that can be bought for a few euro. Only use the ground and 5V wires! The 12V wire can destroy the circuit. In the power supply I used, the cable wires did not follow the usual black/red/yellow color scheme for ground/5V/12V, so always check very carefully with a voltmeter!

Those Atmel microcontrollers are amazing. In one small 28-pin DIP package it contains an 8-bit microcontroller running at 1 MHz (up to 8 MHz with a an external crystal), 20 KB Flash memory, RAM, programmer ROM, parallel and serial ports, timers - it's a small computer on a chip that requires no external chips at all. Its parallel output pins can drive normal LEDs directly, but high-power LEDs require a driver transistor. This is a view of the breadboard under the lamp, and the LED assembly on its heat sink inside the lamp. Click here for a larger picture of the breadboard. Note the five-pin connector at the bottom with the rightmost pin marked black (GND); this is the programming interface that the AVR USB programmer plugs into. More on that below.

breadboard with microcontroller LED assembly on the heat sink

Here is the circuit diagram. I used a breadboard with small copper rings around the holes, and thin magnet wire (0.07mm) to wire up the pins. Magnet wire is normally used for winding electromagnets; I like it because it's insulated with a thin yellowish coating that burns off when soldered to a pin. For the connection to the LED assembly I used thicker wires with a conventional green plastic sheath.

circuit diagram of the LED lamp

You should be sufficiently familiar with soldering and TTL chips to attempt this. Chips in DIP packaging have an indentation at one end; pin 1 is to the left of that mark. Pin count then runs counter-clockwise. I recommend machined sockets, especially for the programmer connector. Sockets with metal tabs tend to become unreliable after a while. For Americans: the rectangular box side of the capacitor symbol is positive, like the curved side of the US symbol. Do not get the polarity wrong; and yes, the top left capacitor really does have its negative side connected to VCC. The MAX-232 chip uses DC-DC converters to generate +/-12V internally.

The speaker shown in the circuit diagram is not currently supported by the firmware. You should omit it. If you do add one, use a piezo-electric buzzer; a small plastic box that contains the piezo disc and a small buzzer chip. It will start beeping when DC power is applied. Do not use a normal speaker here.

2. The firmware

The microcontroller must be programmed with firmware that controls the LEDs and watches the serial interface. This is done in three steps:

  1. Write the source code in C. (Or download it from the link below.)

  2. Compile it to an executable program. I am using the AVR-GCC compiler, a cross-compilation C compiler suite available for Linux and other operating systems. It compiles the C code to a "hex" file. If you do not want to compile your own code, you'll find my hex file in the download package.

  3. Download the hex file to the lamp microcontroller. You will need a programming device. I use the AVR programmer USB, all SMD, stk500 V2 compatible, sold for 50 euro by Tuxgraphics. It comes with sample code and USB drivers and programming tools for Linux, MacOS, and Windows. Make sure you get the "all SMD" version and not the big printed circuit board. The picture on the left shows the AVR programmer, it's really tiny. Tuxgraphics also sells the microcontroller cheaply; it should be between 3 and 4 euro.

The AVR programmer connects to the lamp microcontroller with a five-pin connector that is plugged into the lamp microcontroller board's programming connector. Make sure that you follow the pinout in the circuit diagram above: the AVR programmer's connector is color-coded so that gray = GND, brown = SCK, white = MISO, yellow = MOSI, and green = reset. Please check with the programmer's documentation in case they change the connector.

The AVR programmer can remain plugged in all the time during development. You can compile, download the hex file, and watch the firmware run on the lamp microcontroller without swapping cables. This makes for a fast development cycle. The download package contains a Makefile for Linux or the Mac (didn't try it on Windows) that will compile and download if you type make and make load.

The source code and control files are part of the download package at the end of this section. If you just want to take a look, here is the source code.

The code consists of several functions:

You can download the firmware here, in gzipped tar format (can be unpacked with tar xzvf ledlamp-firmware.tgz on Linux and MacOS, or most unzippers on Windows with CR/LF expansion disabled):

http://www.bitrot.de/ledlamp-firmware.tgz

The tar file includes the C source code, Makefile, and a hex file that can be directly downloaded to the lamp microcontroller. With the hex file, you only need the Tuxgraphics AVR programmer, but you don't need an ACR C compiler - provided you do not plan to modify the firmware. If you do, use make to recompile the source code, and make load to download the modified firmware into the lamp.

3. Desktop computer control

After turning on the lamp, it slowly cycles through six colors. But you can control it with your PC or Mac by connecting the serial interface to your PC or Mac. You need to connect the lamp's RxD input to an RS-232 sub-D connector: of the nine pins, you only need to connect pin 2 to the lamp's RxD, and pin 7 to the lamp's GND. You can also connect pin 3 to the lamp's TxD, but the back channel currently just echoes back what the lamp has received on RxD. This is useful for debugging; if you get an echo the lamp firmware is working. This 9-pin connector then goes into your PC's serial connector, or if it doesn't have one (like Macs), into a USB/serial adapter available for some 10 euro.

You can then connect to the lamp with a terminal program such as minicom. Set it to 9600 baud, one stop bit, no parity, no software flow control, no hardware flow control (CTS/DTS). On Linux, the first serial port has the name /dev/ttyS0; if you are using the USB/serial adapter it's /dev/ttyUSB0. Depending on your motherboard wiring, it might be /dev/ttyS1 instead of S0. The Windows names are COM0 or COM1. I don't know under what names the USB/serial adapters show up but I believe it's a COMn for some n in the range between 0 and 15. (Do not change the Atmega firmware to more than 9600 baud, that is not reliable.)

You can now send commands to the lamp. If you have connected the lamp's TxD line, the characters will be echoed to you; it you didn't, you may be typing blindly with no characters appearing on the screen. All commands are lines of text ending in a newline (CR, LF, or both). Blanks and tabs are ignored. All numbers must have an exact length (one, two, four, or six digits); all numbers are hexadecimal. In the descriptions below, the digit 0 is shown in places where a number (0..f) is expected.

Each command begins with the following characters:

No characters may follow the + and - commands. The = command must be followed by zero or more of the following subcontrols that closely follow the action fields described above:

These following table shows the default actions created when the lamp boots, and that cause it to begin cycling through the rainbow. Note how the patterns are staggered: the first uses bits 0 and 1, the next uses bits 2 and 3, and so on. They are all started at the same time with the same speeds, so they stay synchronized. Each action shows its foreground color for two ticks (two consecutive pattern bits are on), so the soft blending to the next tick will show the foreground color for a full tick, and not just reach it very briefly and immediately begin to fade to the next.

action command comment
0 =0
f400000
b000000
p0003
l0a
r00
s32
S
A
program action 0
foreground color is full red
background color is black
bit pattern is --------** (red for first two ticks)
length of bit pattern is decimal 10
repeat forever
speed is decimal 50 * 20ms units, one pattern bit lasts one second
blend softly between foreground and background color
activate action now
1 =1
f004000
b000000
p000c
l0a
r00
s32
S
A
program action 1
foreground color is full green
see above
bit pattern is ------**-- (green for next two ticks)
see above
see above
see above
see above
see above
2 =2
f400030
b000000
p0030
l0a
r00
s32
S
A
program action 2
foreground color is magenta
see above
bit pattern is ----**---- (magenta for next two ticks)
see above
see above
see above
see above
see above
3 =3
f401400
b000000
p00c0
l0a
r00
s32
S
A
program action 3
foreground color is yellow
see above
bit pattern is --**------ (yellow for next two ticks)
see above
see above
see above
see above
see above
4 =4
f000040
b000000
p0300
l0a
r00
s32
S
A
program action 4
foreground color is full blue
see above
bit pattern is **-------- (blue for last two ticks)
see above
see above
see above
see above
see above

The commands are shown on multiple lines for clarity; if sent over the serial interface they must be all on one line, and they must be terminated with a newline. Here are some additional preprogrammed actions that are not enabled after booting (the final A is missing):

action command comment
5 =5
f404040
b000500
p02aa
l0b
o0b
r01
s02
H
program action 5: five fast white flashes
foreground color is full white
background color is dark green
bit pattern is *-*-*-*-*- (alternate between white and dark green)
length of bit pattern is decimal 11
override lower actions during entire white/dark green flashing
run pattern only once
speed is 2 * 20ms units, the entire action lasts 11*2*20 = 440ms
hard switching between white and dark green, no blending
6 =6
f400000
b000000
p002a
l20
o07
r2e
s0a
H
program action 6: red triple pulses for five minutes
foreground color is full red
background color is black
bit pattern is ----------*-*-*- (three red pulses, otherwise black)
length of bit pattern is decimal 32 (pattern controls only first 16)
override lower actions during the red pulses only
run pattern decimal 46 times
speed is 10 * 20ms units, the entire action lasts 46*32*10*20ms = 5 minutes
hard switching between red and black, no blending
7 =7
f000040
b400000
p5555
l10
o10
r00
s04
H
program action 7: pulse red/blue forever
foreground color is full blue
background color is full red
bit pattern is *-*-*-*-*-*-*-*- (alternate red and blue)
length of bit pattern is decimal 16
override lower actions permanently
run pattern indefinitely
speed is 4 * 20ms units, 1000/(2*4*20) = 6.25 pulses per second
soft blending between red and blue
8 =8
f000000
b000040
p0055
l08
o02
rb9
s02
H
program action 8: blue flashing for one minute
foreground color is black
background color is full blue
bit pattern is *-*-*-*- (alternate black and blue)
length of bit pattern is 8
override lower actions for the first of every four flashes
run pattern decimal 185 times
speed is 2 * 20ms units, action runs for 185*8*2*20ms = 1 minute
hard switching between blue and black, no blending
9 =9
f000000
b400000
p0055
l08
o08
rb9
s02
H
program action 9: red flashing for one minute
foreground color is black
background color is full red
bit pattern is *-*-*-*- (alternate black and red)
length of bit pattern is 8
override lower actions for the entire duration
run pattern decimal 185 times
speed is 2 * 20ms units, action runs for 185*8*2*20ms = 1 minute
hard switching between red and black, no blending

To activate one of the actions 5 through 9, send a command such as +5 followed by a newline. Similarly, to stop a running action, send a command such as -5. The actions a through f are not preprogrammed but can be programmed with a command beginning with =a or similar. All actions can be reprogrammed at any time.

Here is a simple Python script that starts action 5. It assumes that your lamp is attached to an USB port using an USB/serial adapter, and that the script runs on Linux. If this is not the case, please replace /dev/ttyUSB0 with the appropriate device name on your computer. Make sure that you have read and write permissions for the device node; you may have to chmod 666 it as root.

  import termios
  
  lamp = open("/dev/ttyUSB0", "w")
  [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] = termios.tcgetattr(lamp)
  termios.tcsetattr(lamp, termios.TCSANOW,
  	[iflag, oflag, cflag, lflag, termios.B9600, termios.B9600, cc])
  
  def tell_lamp(command):
      lamp.write(command + "\n")
      lamp.flush()
  
  tell_lamp("+5")

Here is a program written in C that allows sending commands to the lamp from the command line, one command per argument word. Again, replace the device name with one that is appropriate for your system.

  #include <stdio.h>
  #include <termios.h>
  #include <unistd.h>
  #include <fcntl.h>

  int fd;

  void init()
  {
      fd = open("/dev/ttyUSB0", O_RDWR);
      if (fd < 0) {
          perror("/dev/ttyUSB0");
          fprintf(stderr, "cannot connect to LED lamp\n");
          return;
      }
      struct termios tc;                  // 9600 baud, 8n1, no flow control
      tcgetattr(fd, &tc);
      tc.c_iflag = IGNPAR;
      tc.c_oflag = 0;
      tc.c_cflag = CS8 | CREAD | CLOCAL;
      tc.c_lflag = 0;
      cfsetispeed(&tc, B9600);
      cfsetospeed(&tc, B9600);
      tcsetattr(fd, TCSANOW, &tc);
  }

  void send(
      const char *command)                // send this command
  {
      write(fd, command, strlen(command));
      write(fd, "\n", 1);
  }

  void main(
      int        argc,                    // number of command-line options
      const char **argv)                  // each argument word is one command
  {
      init();
      for (int i=1; i < argc; i++)
          send(argv[i]);
  }

Enjoy! And please tell me about your experiences and modifications.

Michael McTernan has also built a similar lamp, and he has added an Ethernet interface too. Tuxgraphics now offers the necessary components for under 10 Euro.

Tell me if you found this information interesting or useful, or if you have comments.