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

Tuesday, 31 January 2012

Bluetooth, Teensy and Linux

Notes on integrating a BTM400-6B Bluetooth module from Hong Kong Electronics on ebay, with a Teensy 2.0. The bluetooth module is currently USD$7. Here's what I did:

  1. Soldered the Bluetooth module to the breakout board supplied by the vendor. This breaks out GND, 5V, Tx, Rx, PIO8/State (wired to a status led on the board) and PIO11/Key - when this is high the board is in "command mode", when it's low it's in data mode. Unfortunately RESET isn't broken out, so I've soldered an additional wire onto that pin.
  2. Wired as follows:
    • Teensy P7 - BTM400 Tx
    • Teensy P8 - BTM400 Rx
    • Teensy P13 - BTM400 PIO11/Key
    • Teensy P14 - BTM400 Reset
    • plus 5V and GND to both - BT module is 3.3V but the breakout board includes a voltage regulator
  3. Wrote a quick echo app for the Teensy
#define LED 11
#define BTKEY 13
#define BTRESET 14

HardwareSerial bt = HardwareSerial();
boolean command = true;
int plus = 0;

void setup() {
  Serial.begin(9600);
  pinMode(LED, OUTPUT);
  pinMode(BTKEY, OUTPUT);
  pinMode(BTRESET, OUTPUT);
  digitalWrite(LED, command ? HIGH : LOW);
  digitalWrite(BTKEY, command ? HIGH : LOW);
  digitalWrite(BTRESET, HIGH);
  bt.begin(38400);
}

void loop() {
  int c;
  
  if (Serial.available() > 0) {
    c = Serial.read();
    if (c == 27 && command) {
      Serial.println("Resetting!");
      digitalWrite(BTRESET, LOW);
      delay(10);
      digitalWrite(BTRESET, HIGH);
    } else if (c == '+') {
      if (command) {
        Serial.write(c);
      }
      if (++plus == 3) {
        command = !command;
        digitalWrite(BTKEY, command ? HIGH : LOW);
        digitalWrite(LED, command ? HIGH : LOW);
        Serial.println(command ? "Entering command mode" : "Leaving command mode");
        plus = 0;
      }
    } else { 
      while (plus > 0) {
        bt.write('+');
        plus--;
      }
      bt.write(c);
      if (command) {
        Serial.write(c);
      }
      // BT module will continue sending response until it gets 0x10
      if (c == '\r') {
        bt.write('\n');
        if (command) {
          Serial.write('\n');
        }
      }
    }
  }

  if (bt.available() > 0) {
    c = bt.read();
    Serial.write(c);
  }
}

This will start the module in command mode, and relay input from the USB to the BT module (echoing the input). An input of "+++" will switch the module to data mode, and another "+++" from the USB end will switch it back to command - old school modem stuff (technically I should check for a ½ second delay before and after +++). If I press ESC while in command mode, the BT module will do a hard reset by dropping RESET for 10ms. Finally the Teensy 2.0 LED on P11 is lit when in command mode.

USB Comms

Communicating with this was done using "screen", which is on OS X and Linux. Nothing else seemed to work properly. Broadly, with device wired in as described fire up
screen /dev/cu.usbmodem12341 9600
on OS X to connect (your USB dev might be elsewhere, and this is after installing the Teensy dev environment and drivers, as described here). This will connect you to the USB end of the link. Type "AT" then enter and you should get back "OK". Here's a dialog of one session
AT
OK
AT+VERSION?
+VERSION:2.0-20100601
OK
AT+INIT
OK
AT+CLASS?
+CLASS:0
OK
AT+ROLE?
+ROLE:0
OK
AT+IAC?
+IAC:9e8b33
OK
AT+INQM?
+INQM:1,9,48
OK
AT+PSWD?
+PSWD:1234
OK
AT+UART?
+UART:9600,0,0
OK
AT+CMODE?
+CMOD:0
OK
AT+BIND?
+BIND:0:0:0
OK
AT+POLAR?
+POLAR:0,1
OK
AT+MPIO?
+MPIO:900
OK
AT+SNIFF?
+SNIFF:0,0,0,0
OK
AT+SENM?
+SENM:0,0
OK
AT+ADCN?
+ADCN:2
OK
AT+MRAD?
+MRAD:15:83:15a310
OK
AT+STATE?
+STATE:INITIALIZED
OK
AT+INQ
OK
AT+INQM=1,9,48
OK
AT+INQ
OK
AT+CLASS=0
OK
AT+INQ
OK
AT+INQC
OK
OK
AT+STATE
+STATE:PAIRED
OK
AT+ADCN?
+ADCN:2
OK
Of note is that AT+INQ doesn't find any devices. However switch it into Master role with AT+ROLE=1 then AT+INQ finds my Macbook:
AT+ROLE=1
OK
AT+INQ
+INQ:58B0:35:XXXXXX:3A010C,FFBA
+INQ:58B0:35:XXXXXX,3A010C,FFBD
+INQ:58B0:35:XXXXXX,3A010C,FFBB
+INQ:58B0:35:XXXXXX,3A010C,FFBA
+INQ:58B0:35:XXXXXX,3A010C,FFBB
+INQ:58B0:35:XXXXXX,3A010C,FFB9
+INQ:58B0:35:XXXXXX,3A010C,FFBC
OK
That's one device - last field is signal strength, suitably strong given the two devices are next to eachother. Now to get it talking over the radio.

Bluetooth comms to OS X

Type "+++" to drop PIO11 and put the device into slave pairing mode. Then on OS X, open Bluetooth, add device - the device should appear (called HC-05 for me) then pair it with the code 1234 (the default). This created the character special file /dev/cu.HC-05-DevB and you can connect up to this with screen in another terminal with

screen /dev/cu.HC-05-DevB 9600
Now, with luck, typing in one screen will appear in the other and vice versa.

Bluetoooth comms to Linux

On Ubuntu 11.04 (Natty) with a BT dongle plugged in, after a bit of fiddling the steps appear to be as follows (assuming 00:11:12:09:XX:XX is the BT module address, as discovered by hcitool scan)

hcitool scan
bluez-simple-agent hci0 00:11:12:09:XX:XX
rfcomm bind rfcomm0 00:11:12:09:XX:XX
screen /dev/rfcomm0

hcitool scan should list your BT dongle - if not, check the state (from AT+STATE) is "PAIRING" and that the red light is flashing fast. You should be able to get there with AT+RESET, AT+INIT and AT+ROLE=0.

Step two is bluez-simple-agent which does the initial pairing. This should only need to be done once - it will ask you for the pin code. If the pairing has been deleted on the device, you can force a repairing by passing "remove" in as the third argument.

Step three is rfcomm bind, which connects to the Bluetooth module and creates the file /dev/rfcomm0. From there you should be able to run screen to connect. Once you're done, run rfcomm release 0 to drop the connection.

Issues & Notes

  • If you're sending commands to the device, they're executed immediately on 0x13 (\r), however you must send an 0x10 (\n) immediately after. If you don't the device will repeatedly send you the response until you do.
  • AT+RESET must be followed by an AT+INIT in order to do anything useful (ie become pairable). This is despite AT+STATE returning INITIALIZED
  • BT module sometimes came up in a weird slave mode where the characters relayed to the USB serial aren't what's sent - for instance, sending "eeee" gives an "x" then various bytes > 0x7F. Flicking back and forth to command mode, unplugging & replugging seems to work. This was before I'd wired in the hard reset pin so perhaps it's fixed - I haven't been able to reproduce it since.
  • Device seems to hold several pairings - how many?
  • If you're in command mode when the pairing is established, you get an unprompted "OK". If you're in commend mode on disconnect you get an unprompted "+DISC:SUCCESS" then "OK". You can enter command mode while paired, but leaving it again will drop the connection.
  • According to this page PIO9 is high iff device is paired. I haven't played with the other pins yet, no doubt there's stuff to experiment with.
  • There are lots of pins on the BT module board which don't apply to this firmware, so don't get your hopes up with the Audio I/O pins.
  • The BT module is powered at 3.3V, but I've hooked it up to the Teensy running on 5V - so the BT module' RX pin is receiving 5V and it's sending 3.3V back to the Teensy. This seems to be OK, they're still talking and nothing has started smoking.

Conclusion

A two-way radio data connection that works perfectly with Linux and OS X? Seven bucks well spent