Sunday 6 May 2012

Bambilight - a cheap bluetooth Ambilight clone

This is my TV. I hate cables, so when I was planning this I routed all the cables I'd ever need behind the wall. HDMI, TV Aerial, speaker, ethernet, and a power socket. I forgot to route USB.

If I want to use an Atmolight this is a problem. Lights behind the TV that change color require a cable to the computer. They also require a power supply and, apparently, quite an extensive and expensive list of parts. My other problem is that the IR remote receiver plugged into my computer is just above the floor, and the receiver keeps getting covered by the rug.

My solution is an Atmolight clone controlled by Bluetooth. It draws power for the chip from USB (so can be powered by the USB sockets on the TV) with a separate supply to power the LEDs. When done it will relay IR commands from its built-in IR receiver to the computer via Bluetooth. Atmolight was a DIY version of the orginal Philips Ambilight, so as this is a Bluetooth version I have therefore christened it "Bambilight" and take childish pleasure in trampling over not one but two trademarks.

Parts

  • 1 x Teensy 2.0. I love this board, it's simple to use, well documented and cheap for what it does.
  • 1 x BTM-400 Bluetooth module, with breakout board. Optional - the board and code can also communicate over USB
  • 2 x ULN2803 octal Darlington array in DIL package
  • 5m roll of SMD RGB LEDs.
  • 1 x Infrared photo-detector.
  • Some PCB Sockets and pins.
  • A 12V power supply and matching PCB mountable socket. You probably want 2.1mm, which seems to be the standard for 12V power
  • Copper PCB for etching, the final board is about 70mm x 40mm
Total cost of the above is probably about USD$55. The roll of SMD mounted LEDs bumps up the cost and was the second solution I tried - the first involved wiring my own strips and using the 5V from USB to power them. Disaster. Even with a dual-power USB cable you can only get 1A which limits you to about 12 RGB leds (at 20ma per color). These cast great pools of light onto the walls at irregular intervals, and were hard to stick to the TV. The SMD strip can be chopped into sections, has a sticky backing so fits really easily, but runs off 12v and so requires a separate power supply - at 0.6A/m you need a power supply that will put out about 0.6A x 4m = 2.4A (30W) to around the edge of a 46" TV. I hardwired a second cable into my recessed wall plug behind the TV for power.

Design

I designed the board in Eagle, which took me a long time to get to grips with - I'm still not 100% satisfied with it, but you can download the board, Eagle schematic and source code from this link. My first etch had holes that were too small, which was fixed with a re-edit in Photoshop. This gave me the chance to add some text, and a measurement bar - my second print effort failed after etching and drilling the board because the ****ing printer had been set to scale the image by not enough to be obvious, but just enough to be useless.

I ended up printing to plain paper, verifying the size then photocopying onto transparency acetate. The image is at 600dpi and the text is flipped horizontally - put it this way up on your lightbox with the photosensitive copper board on top and it will etch the right way round. Something I learned after my first print effort.

Build

The Bluetooth chip is an SMD board and is optionally supplied with a breakout board. This had pins wired horizontally, which is really annoying, so I unsoldered and replaced with pins that point down from the board. I then drilled the board - I can't imagine doing this without a 0.7mm bit, a Dremel and a Dremel drill press as lining these up by hand would be impossible. The bit will self-align to the stripped hole in the middle of each solder pad, so it's not as daunting as it first appears. The Jellybaby is for sizing, I didn't have any coins.

Once drilled I ran three wires on the top of the board as indicated by the red lines on the photo - here's the top view. Then started soldering the components, cheap bits first. Watch the orientation of the darlington arrays. Another lesson I learned shortly after switching the board design to using a 12V strip of leds is that it's much easier to solder a socket than to try then fail to desolder your expensive IC.

Here's the board with the Teensy and Bluetooth boards in place. The yellow linking wire on top saved me some hairy routing below - it connects Teensy pin B0 with the RESET pin on the Bluetooth module. I ate the Jellybaby.

LEDs and install

The LED strips are really easy to work with, each group of 3 is wired in series to run off 12V, so you can cut the strips after every 3 LEDs as needed. Solder wire terminated with a 4-pin header to each section, making sure to order the pins R, G, B and 12v. Then plug these into the headers on the board, with 12V towards the edge of the board. (I have double headers, a legacy of my first design. You don't need them). Then plug the board USB into a USB socket on your TV - any will do, it's only for 5V supply - and the 12V supply into the 2.1mm DC socket you can see poking out of the top-left of the board. Assuming you've programmed the Teensy you can then tuck this out of sight behind the TV.

Software

Here's the code I uploaded to the Teensy (note this can be done before or after the chip is in place - progamming a Teensy is that easy). There are 12 LED channels plus IR and bluetooth control so I'm using just about every I/O pin on the board, and controlling the LED brightness by bit-bashing the PWM. This isn't pretty but it works.
#include "IRremote.h"

#define BLUETOOTH

#define NUMCHANNELS 4
#define NUMLEDS (NUMCHANNELS*3) 
#define MODE_LEDON 0
#define MODE_LEDOFF 1
#define MODE_SLEEP 2
#define BUFLEN 32

#define IRTX 10                 // C7 wired but not used
#define IRRX 11                 // D6
#define BTKEY 9                 // C6
#define BTRESET 0               // B0
#define OUTPUTLOW0 15           // Set to input, wired to low

byte pins[NUMLEDS] = {
    19, 20, 21,                 // 1st clockwise from USB   F4 F1 F0
    18, 17, 16,                 // 2nd clockwise from USB   F5 F6 F7
    4, 5, 6,                    // 3rd clockwise from USB   B7 D0 D1
    3, 2, 1,                    // 4th clockwise from USB   B3 B2 B1
};
byte rgb[NUMLEDS];
byte gamma[256];

float gammapower = 2.0;         // LED brightness based on gamma = 2.0
byte count = 0, i = 0, buflength = 0;
byte ledmode = MODE_LEDON;
boolean debug = false;
byte buf[BUFLEN];
decode_results results;

IRrecv irrecv(IRRX);
void dopwm();
void setrgb(byte *, byte);
void readcommand(int);
#ifdef BLUETOOTH
void btcmd(HardwareSerial bt, char*msg);
HardwareSerial bt = HardwareSerial();
#endif

//------------------------------------------------------------------------------------

void setup() {
    for (int i=0;i<256;i++) {
        gamma[i] = (byte)(pow(i/255.0, gammapower) * 255 + 0.5);
    }

    Serial.begin(38400);
    for (int i=0;i<NUMLEDS;i++) {
       if (pins[i] < 255) {
           pinMode(pins[i], OUTPUT);
           digitalWrite(pins[i], HIGH);
       }
    }
    pinMode(OUTPUTLOW0, INPUT);
    pinMode(IRRX, INPUT);
    pinMode(BTKEY, OUTPUT);
    pinMode(BTRESET, OUTPUT);

    irrecv.enableIRIn();
    irrecv.blink13(true);

#ifdef BLUETOOTH
    // Reset BT device
    digitalWrite(BTRESET, LOW);
    delay(500);
    digitalWrite(BTRESET, HIGH);
    delay(500);
    digitalWrite(BTKEY, HIGH);
    delay(500);

    // Initialize, set name
    bt.begin(38400);
    bt.flush();
    btcmd(bt, "AT+INIT");
    btcmd(bt, "AT+NAME=Bambilight");
    btcmd(bt, "AT+PSWD=0000");
    // Enter BT slave mode
    digitalWrite(BTKEY, LOW);
#endif

    // Initialize all LEDs to off
    for (int i=0;i<NUMLEDS;i++) {
        buf[i] = 0;
    }
    setrgb(buf, NUMLEDS);
}

void loop() {
    while (Serial.available()) {
        readcommand(Serial.read());
    }
#ifdef BLUETOOTH
    while (bt.available()) {
        readcommand(bt.read());
    }
#endif

    if (irrecv.decode(&results)) {
        // Not currently used, need to patch boblightd first
        irrecv.resume();
    }
    dopwm();
}

// Read command from "buf", which could be set from BT or USB serial
void readcommand(int v) {
    if (i != 0 || v == 255) {
        buf[i] = v;
        if (buflength !=0 && (i == buflength || i == BUFLEN-1)) {
            if (buf[3] == 15 || buf[3] == 12 || buf[3] == 9 || buf[3] == 3) {  // SETRGB
                setrgb(buf + 4, buf[3]);
            }
            i = 0;
        } else {
            if (i == 3 && v < 16) {     // SETRGB
                buflength = v + 3;
            }
            i++;
        }
    }
    if (debug) {
        Serial.print("read ");
        Serial.print(v);
        Serial.print(" @");
        Serial.println((int)i);
    }
}

// Set the RGB value for the LEDS - b is array of "numleds" values
// from 0 (off) to 255 (full).
void setrgb(byte* b, byte numleds) {
    int i;
    for (i=0;i<numleds;i++) {
        rgb[i] = gamma[b[i]];
    }
    for (i=numleds;i<NUMLEDS;i++) {
        rgb[i] = 0;
    }
}

// PWM the led bits. for each x=rgb[i], pin is on from 0..x and off from x+1..255
// count is unsigned byte so wraps at 255. Called in busy loop so nothing complex.
void dopwm() {
    for (int i=0;i<NUMLEDS;i++) {
        if (pins[i] < 255) {
            byte val = rgb[i];
            if (ledmode == MODE_LEDON && count == 0 && val != 0) {
                digitalWrite(pins[i], HIGH);
            } else if (ledmode != MODE_LEDON || (count == val + 1)) {
                digitalWrite(pins[i], LOW);
            }
        }
    }
    count++;
}

#ifdef BLUETOOTH
void btcmd(HardwareSerial bt, char *msg) {
    if (debug) {
        Serial.print("Sending '");
        Serial.print(msg);
        Serial.println("'");
    }
    bt.print(msg);
    bt.write(13);
    bt.write(10);
    int v, j = 0;
    while ((v=bt.read()) != 10) {
        if (v >= 0) {
            buf[j++] = (byte)v;
        }
    }
    buf[j] = 0;
    delay(100);         // I found I needed this to work reliably.
}
#endif
Or click here to download with a Makefile (which works on OS X and should work on Linux too). "make upload" to upload via the normal Teensy Uploader.

The LEDs are controlled using the Atmolight protocol, which is spoken by Boblight: install this on your Linux box, connect to the Bluetooth chip using bluez-simple-agent and rfcomm bind as described here, then setup the following in /etc/boblight.conf
[global]
interface 127.0.0.1
port  19333

[device]
name  bambilight
output  /dev/rfcomm0
channels 12
type  atmo
interval 40000
rate  9600
delayafteropen  1000000

[color]
name  red
rgb  FF0000

[color]
name  green
rgb  00FF00

[color]
name  blue
rgb  0000FF

[light]
name  top
color  red  bambilight 1
color  green  bambilight 2
color  blue  bambilight 3
hscan  0 100
vscan  0 50

[light]
name  right
color  red     bambilight 4
color  green   bambilight 5
color  blue    bambilight 6
hscan  50 100
vscan  0 100

[light]
name  bottom
color  red  bambilight 7
color  green  bambilight 8
color  blue  bambilight 9
hscan  0 100
vscan  50 100

[light]
name  left
color  red     bambilight 10
color  green   bambilight 11
color  blue    bambilight 12
hscan  0 50
vscan  0 100

XBMC

Nothing to it. Install the XBMC Boblight addon, run boblightd, XBMC will automatically find it and, er, Bob's your uncle.

Notes

  • Unlike here we're configuring the bluetooth chip directly. Just trying to run the commands in sequence failed, the chip seems to need time to respond. I've catered for this by adding delays - 500ms during reset, 500ms after, then 100ms after each command. This works, it may be more conservative than necessary.
  • The Bambilight can be controlled via Bluetooth or via USB - if no bluetooth is required, drop that chip from the circuit and don't #define BLUETOOTH in the source code.
  • There is an occasional brief flicker from the LEDs, say one every 5-10 minutes for a few ms of a second. No doubt this is down to my cheap bit-banging approach, but it's barely noticable and (for me) not a problem.
  • The IR receiver is wired in and should work in theory (I'm having trouble with the RC-6 protocol used by my remote, the others seem to work) but boblightd needs to be patched to relay the response from the device to the Linux IR subsystem. Patching boblightd looks relatively simple, but I don't know how Linux talks to the IR system, particularly as it changed recently. So this part of the project on hold.
  • For my own record, this build required: 3 attempts to build UV LED-based PCB etcher, 4 main PCB boards (1 backwards, 1 misscaled, 1 running LEDs off 5V and final one), 2 Teensys (blew first one up desoldering from board 3), 4 Darlingtons (blew two up on board 3), 12 LEDs & resistors (discarded for SMD roll after board 3).

4 comments:

  1. **hi, i'm French so sorry for ma bad english ;s

    I'm looking for ambilight for poor people XD, and i was interresting by your work. If I understand all in your test you have 5M of led ? oO near than 1 metter by side of the TV ? For this coast ?

    Can you send me an email i have some questions to give for you if you can spend some time for me :)**cheers from France ! my mail:

    mistralbreton@yahoo.fr

    ReplyDelete
  2. Next time you decide to run cables behind a wall, consider installing an adequate-sized plastic conduit instead, since that will allow you to add and remove cables as needs and technology change.

    ReplyDelete
  3. i know this post is years old, but do you think a bluefruit would work in this usecase?

    https://www.adafruit.com/products/2829

    ReplyDelete