Friday 25 May 2012

Program an ATTiny45 with a Teensy 2.0

I have a Teensy 2.0 and some ATTiny45 chips from Atmel, which I want to program with a minimum of fuss and parts. For me this means using the Teensy as an "In System Programmer" or ISP, and there are plenty of other articles on how to do this. But I thought I'd file what I've got here for reference anyway.

It's very easy - no hardware required except a Teensy, the ATTiny, a breadboard and 4 wires, and for software I started with the Arduino 1.0 environment with the Teensyduino additions - just what you'd normally need to upload sketches to the Teensy. Here's what I did.

  1. Download and install the ATTiny cores for the Arduino IDE (for now I'm using the IDE, I'll update this when I switch to make and avrdude). There are a quite a few different cores developed by different people and they're not all equal, which is not obvious when you start this process. I picked the ones from the above repository.
  2. Open the Arduino IDE and plug in the Teensy 2.0. Select the "Arduino ISP" from the File/Examples menu and change the #define for LED_HB to 11. This will blink the orange light on the Teensy when the programmer is running. Then upload the sketch to the Teensy.
  3. On a breadboard, wire these pins together:
    • Teensy 2.0 pin B0 <-> ATTiny45 pin 1
    • Teensy 2.0 pin B1 <-> ATTiny45 pin 7
    • Teensy 2.0 pin B2 <-> ATTiny45 pin 5
    • Teensy 2.0 pin B3 <-> ATTiny45 pin 6
    • ... and connect 5V to ATTiny45 pin 8 and GND to Attiny45 pin 4
    Here are pinouts for the ATTiny45 and the Teensy.
  4. In the Arduino IDE, under the Tools menu change the "Programmer" to "Arduino as ISP" and the "Board" to "ATTiny45 @ 1Mhz". Then load your sketch for the ATTiny and upload as before. For testing I used the "Blinky" example and changed the pin to 4. It should upload with a couple of "Please define PAGEL and BS2 signals in the configuration file for part ATtiny45" warnings, which you can ignore
  5. If you'd prefer to program directly from avrdude, the command should look something like:
    avrdude -c avrisp -P /dev/tty.usbmodem12341 \
       -p attiny45 -U flash:w:main.hex:i
    
    where:
    • /dev/tty.usbmodem12341 is the USB port the Teensy is plugged into
    • attiny45 is the type of AVR chip you're programming
    • main.hex is the hex file you have compiled and built with avr-gcc and avr-objcopy

That's it, really, and it's all ground that's been covered before.

If you're going to do this a bit then it's worth getting it off the breadboard, so I rustled up a quick circuit in Eagle (which you can download). It has sockets for programming 8, 14 or 20 pin ATTiny chips, plus a couple of LEDs to indicate error (red) and communicating with the ATTiny (green), and you can see a photo at the top of this blog. You'll need the following parts:

The download contains the circuit as a PNG for etching (make sure you etch so the text is the right way around) plus the source for a slightly modified version of ArduinoISP which I've called "TeensyISP.ino" - this has the correct #defines for the pins on the Teensy. Then just plug the Teensy into the PCB connectors in the middle of the board, put your ATTiny chip in the appropriate socket and upload as described above. Easy.

Incidentally, with a cost of about $16 for the Teensy plus maybe $2 for the LEDs and sockets, this is also probably the cheapest way to build an ISP programmer.

Notes

  • The first Blinky sketch I uploaded I was expecting to see the light blink on for a second, then off for a second. What I actually saw was on for a second, off for a fraction of a second. I haven't quite figured this one out yet but if I reverse the order in Blinky to "led off" then "led on" it works as expected. Still pondering this.
  • The LEDs on the circuit above don't normally come on during programming, at least not for me so far
  • I've double checked but haven't actually tested the 14 or 20 pin sockets in this circuit

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