Ever since Philips released their Ambilight enabled televisions, hackers around the world have gone "ooooh, cool" and then immediately "how can I build that?"

If you're not familiar with the concept, it's basically a light source that surrounds a display screen. It's not a new idea...many very old televisions had a white "halo" around the screen both to make the screen seem bigger, and to reduce eyestrain. Flat panel televisions are usually mounted on or near a wall, so the idea of putting a glow on the surrounding wall was born. Philips improved on the idea by building a processor into their TVs that analyzes the signal and adjusts a ring of LEDs to match colors. Supposedly this increases immersion and reduces eyestrain...but we all know it just looks really cool.

DIY Ambilight-style setups have been around for years. One of the more popular video viewing programs, VLC, even ships with a plugin to control external LED hardware. Many purpose-built devices and DIY designs exist too. But you can use general-purpose controllers, like an Arduino, with general-purpose LED modules. Here are a few easy to build projects (some of which are pretty recent):

There are many designs that just rely on a microcontroller or chips like the TLC5940 to generate several channels of PWM. However, the more channels you can have, the better your system will look. So a chainable LED system like ShiftBrites or a digital pixel strand will look the best and allow expansion in the future. One thing to remember is that these systems all require the video source to be a computer, since high definition video signals are hard to analyze. Therefore only Philips' system will work with live TV. The exception would be a system that uses a webcam to analyze the picture, but that's another level of difficulty and I've only seen it done a couple times.

With all these Ambilight clone projects popping up in recent weeks, we decided to try building our own system. One problem...the only accessible TV with an ideal wall situation was a friend's 58" plasma screen. Plasma screens are bright! Therefore we would need LEDs that could compete. Naturally, the new OctoBar and Satellite S-001 came to mind...they are really bright and the phone cables would make it easy to wire up.

So, one Saturday afternoon, we gathered all the parts and tools needed and started building. The actual build and wiring process is shown in the video at the top of this post, if you haven't watched it already (I always watch the video first!). We started off with six modules on the top, three on each side, two on the bottom, and two in the back middle. However, there was a dark area in the top corners so we added two more modules on angle to fill in. The total is over 60 watts of LED power! This is the equivalent of 900 single LEDs, the ultrabright ones that are already blinding to look at directly! It's brighter than 270 ShiftBrite modules!

The LEDs are mounted on a stable frame of 3/4" square dowel that attaches to the TV's carry handles. Angle brackets mounted to the frame hold four more dowels that hold the Satellite S-001 modules. These dowels can pivot in the angle brackets when the screws and lock washers are loosened..this allowed us to find the perfect angles to project a smooth color field on the wall.

Once everything was assembled and glowing a test pattern, we configured the necessary software. The video source in this case was an Atom mini PC running Ubuntu. I downloaded and compiled the boblight software package. You'll need to remember to install the X11 development dependencies first, or boblight won't compile the boblight-X11 program.

Boblight is pretty simple to use. Define the type of device, number of channels, available colors, and screen zones to sample. This all goes in the /etc/boblight.conf file. In the config file below, I created [light] entries in the same order the light appeared on the OctoBar chain. Each zone corresponds to an individual Satellite S-001 module, matching the actual position of the module as closely as possible.

[global] timeout 20 interface 127.0.0.1 port 19333 interpolation on proportional 100.0 saturation 1.5 value 1.2 valuerange 0.0 1.0 use yes method average threshold 10 [device] name ambilight type atmo output "/dev/ttyACM0" rate 115200 channels 54 interval 20000 prefix FF [color] name red rgb FF0000 gamma 1.0 adjust 0.82 blacklevel 0.0 [color] name green rgb 00FF00 gamma 1.0 adjust 1.12 blacklevel 0.0 [color] name blue rgb 0000FF gamma 1.0 adjust 0.9 blacklevel 0.0 [light] name BR color red ambilight 1 color green ambilight 2 color blue ambilight 3 hscan 50 90 vscan 85 100 [light] name R3 color red ambilight 4 color green ambilight 5 color blue ambilight 6 hscan 90 100 vscan 70 100 [light] name R2 color red ambilight 7 color green ambilight 8 color blue ambilight 9 hscan 90 100 vscan 40 70 [light] name R1 color red ambilight 10 color green ambilight 11 color blue ambilight 12 hscan 90 100 vscan 10 40 [light] name T6 color red ambilight 13 color green ambilight 14 color blue ambilight 15 hscan 78 92 vscan 0 15 [light] name T5 color red ambilight 16 color green ambilight 17 color blue ambilight 18 hscan 64 78 vscan 0 15 [light] name T4 color red ambilight 19 color green ambilight 20 color blue ambilight 21 hscan 50 64 vscan 0 15 [light] name T3 color red ambilight 22 color green ambilight 23 color blue ambilight 24 hscan 36 50 vscan 0 15 [light] name T2 color red ambilight 25 color green ambilight 26 color blue ambilight 27 hscan 22 36 vscan 0 15 [light] name T1 color red ambilight 28 color green ambilight 29 color blue ambilight 30 hscan 8 22 vscan 0 15 [light] name L1 color red ambilight 31 color green ambilight 32 color blue ambilight 33 hscan 0 10 vscan 10 40 [light] name L2 color red ambilight 34 color green ambilight 35 color blue ambilight 36 hscan 0 10 vscan 40 70 [light] name L3 color red ambilight 37 color green ambilight 38 color blue ambilight 39 hscan 0 10 vscan 70 100 [light] name BL color red ambilight 40 color green ambilight 41 color blue ambilight 42 hscan 10 50 vscan 85 100 [light] name C1 color red ambilight 43 color green ambilight 44 color blue ambilight 45 hscan 50 80 vscan 25 75 [light] name C2 color red ambilight 46 color green ambilight 47 color blue ambilight 48 hscan 20 50 vscan 25 75 [light] name TR color red ambilight 49 color green ambilight 50 color blue ambilight 51 hscan 92 100 vscan 0 15 [light] name TL color red ambilight 52 color green ambilight 53 color blue ambilight 54 hscan 0 8 vscan 0 15

The next step was to get the Arduino to understand the AtmoLight protocol and control the LEDs. This was pretty easy, since there's an existing product called ArduinoAtmo that already controls ShiftBrite, MegaBrites, ShiftBars, and OctoBars. I just used it pretty much as-is, modifying for the new number of channels, but believe the code could use some more straightforward handling of the color data and serial input.

#define clockpin 13 // CI #define enablepin 10 // EI #define latchpin 9 // LI #define datapin 11 // DI //number of modules in the chain #define NumLEDs 24 int LEDChannels[NumLEDs][3] = {0}; int SB_CommandMode; int SB_RedCommand; int SB_GreenCommand; int SB_BlueCommand; void setup() { Serial.begin(115200); pinMode(datapin, OUTPUT); pinMode(latchpin, OUTPUT); pinMode(enablepin, OUTPUT); pinMode(clockpin, OUTPUT); SPCR = (1<<SPE)|(1<<MSTR)|(0<<SPR1)|(0<<SPR0); digitalWrite(latchpin, LOW); digitalWrite(enablepin, LOW); } //sample code from macetech void SB_SendPacket() { if (SB_CommandMode == B01) { SB_RedCommand = 127; SB_GreenCommand = 127; SB_BlueCommand = 127; } SPDR = SB_CommandMode << 6 | SB_BlueCommand>>4; while(!(SPSR & (1<<SPIF))); SPDR = SB_BlueCommand<<4 | SB_RedCommand>>6; while(!(SPSR & (1<<SPIF))); SPDR = SB_RedCommand << 2 | SB_GreenCommand>>8; while(!(SPSR & (1<<SPIF))); SPDR = SB_GreenCommand; while(!(SPSR & (1<<SPIF))); } //sample code from macetech void WriteLEDArray() { SB_CommandMode = B00; // Write to PWM control registers for (int h = 0;h<NumLEDs;h++) { SB_RedCommand = LEDChannels[h][0]; SB_GreenCommand = LEDChannels[h][1]; SB_BlueCommand = LEDChannels[h][2]; SB_SendPacket(); } delayMicroseconds(15); digitalWrite(latchpin,HIGH); // latch data into registers delayMicroseconds(15); digitalWrite(latchpin,LOW); SB_CommandMode = B01; // Write to current control registers for (int z = 0; z < NumLEDs; z++) SB_SendPacket(); delayMicroseconds(15); digitalWrite(latchpin,HIGH); // latch data into registers delayMicroseconds(15); digitalWrite(latchpin,LOW); } int incomingatmo[255]; int buffer=0; int channels; int i=0; int BluePin=11; int GreenPin=9; int RedPin=10; int laststatus=0; int lastbyte=0; int average=0; int j=0; int channel=0; int x=0; //order is reversed, first byte is last module in the chain //0=sum //1=left //2=right //3=top //4=bottom byte channelorder[24]={15,15,15,14,14,14,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}; int gammatable[]={0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,3,/ 3,3,4,4,4,5,5,6,6,6,7,7,8,8,9,10,10,11,12,12,13,14,14,15,16,17,18,19,/ 19,20,21,22,23,24,26,27,28,29,30,31,33,34,35,36,38,39,41,42,44,45,47,48,/ 50,52,53,55,57,58,60,62,64,66,68,70,72,74,76,78,80,82,85,87,89,92,94,96,99,/ 101,104,106,109,112,114,117,120,123,125,128,131,134,137,140,143,146,149,153,/ 156,159,162,166,169,172,176,179,183,187,190,194,198,201,205,209,213,217,221,/ 225,229,233,237,241,246,250,254,258,263,267,272,276,281,286,290,295,300,305,/ 310,314,319,324,329,335,340,345,350,355,361,366,372,377,383,388,394,400,405,/ 411,417,423,429,435,441,447,453,459,465,472,478,484,491,497,504,510,517,524,/ 530,537,544,551,558,565,572,579,586,593,601,608,615,623,630,638,645,653,661,/ 668,676,684,692,700,708,716,724,732,740,749,757,765,774,782,791,800,808,817,/ 826,835,844,853,862,871,880,889,898,908,917,926,936,945,955,965,974,984,994,/ 1004,1014,1023}; const int numReadings = 1; int bluereadings[numReadings]; int blueindex = 0; int bluetotal = 0; int redreadings[numReadings]; int redindex = 0; int redtotal = 0; int greenreadings[numReadings]; int greenindex = 0; int greentotal = 0; void outputpwm(int curchannel) { int blue=gammatable[incomingatmo[(channelorder[curchannel]*3)+3]]; int red=gammatable[incomingatmo[(channelorder[curchannel]*3)+1]]; int green=gammatable[incomingatmo[(channelorder[curchannel]*3)+2]]; // int blue=incomingatmo[(channelorder[curchannel]*3)+3]*4; // int red=incomingatmo[(channelorder[curchannel]*3)+1]*4; // int green=incomingatmo[(channelorder[curchannel]*3)+2]*4; bluetotal= bluetotal - bluereadings[blueindex]; redtotal= redtotal - redreadings[redindex]; greentotal= greentotal - greenreadings[greenindex]; bluereadings[blueindex] = blue; redreadings[redindex] = red; greenreadings[greenindex] = green; bluetotal= bluetotal + bluereadings[blueindex]; redtotal= redtotal + redreadings[redindex]; greentotal= greentotal + greenreadings[greenindex]; // advance to the next position in the array: blueindex = blueindex + 1; redindex = redindex + 1; greenindex = greenindex + 1; if (blueindex >= numReadings) blueindex = 0; if (redindex >= numReadings) redindex = 0; if (greenindex >= numReadings) greenindex = 0; // calculate the average: average = redtotal / numReadings; LEDChannels[curchannel][0]=average; average = greentotal / numReadings; LEDChannels[curchannel][1]=average; average = bluetotal / numReadings; LEDChannels[curchannel][2]=average; } void loop() { if(Serial.available()>0) { buffer = Serial.read(); if(buffer==0xff) { while(Serial.available()==0) { } buffer=Serial.read(); if(buffer==0x00) { while(Serial.available()==0) { } buffer=Serial.read(); if(buffer==0x00) { for(i=0;i<55;i++) { while(Serial.available()==0) { } incomingatmo[i]=Serial.read(); } for(channel=0;channel<NumLEDs;channel++) { outputpwm(channel); } WriteLEDArray(); } } } } else { WriteLEDArray(); delay(5); } }

Overall results were great, as you can see in the video. I needed to spend about an hour tweaking the colors in boblight to get everything matching the TV, and it could probably be tuned further. During a scene with lots of action and bright colors, it lights up the entire room! For LED maniacs like us it can't possibly be bright enough, but most people would probably need to turn it down.

That's the project, hope you like it! Feel free to ask any questions or make suggestions in the comments.