Programmer / Target Board
While I have looked around for a cheaper version of a programmer (possibly using an Arduino with a special sketch), I finally decided that my time was worth the cost, and bought a USBTiny programmer from Lady Ada. I wired up a quick and dirty target board with a couple of sockets and jumper pins, and all is working perfectly. (My target board supports both 8 and 28 pin AVRs, specifically the ATtiny13 and the ATmega168, although in theory any AVR with the same programming pinout as these chips should work.) Quite an adaptable system for about $25.
Uploading a HEX file directly to an Arduino
Behind the scenes, the Arduino is just using avrdude to upload sketches; the bootloader emulates a (subset) of the stk500 programmer. While using the Arduino IDE is nice for many scenarios, sometimes you just want to be able to upload a file directly.
Well as it turns out, this is quite easy. First, you will need to use the avrdude which is bundled with Arduino; I don't know if they have modified it, or if that particular version just plays nicer with the bootloader, but whatever the case it seems to just work. Next, you will use the following command to upload your program:
avrdude -V -F -c stk500v1 -p atmega168 -P /dev/tty.usbserial-A8007UF6 -b 19200 -U flash:w:main.hex
You will want to change the serial device as required, and replace the 'main.hex' with whatever your .hex file is called.
(Thanks to Warren for sending me this command)
AVRDude Cheat Sheet
To program AVR chips using this setup, I use
Programmer / Chip Verification
This first test will verify that you have your programmer and chip hooked up correctly. If it works, you should see a 'Reading' progress bar, a device signature, and some other text.
avrdude -c usbtiny -p m168
Note that -p is the 'part' argument; change this to match your destination chip. See man page or config file avrdude.conf to find correct part name for your chip.
To write to the flash memory, you should use a command somewhat like the following (adjust for your system, of course):
avrdude -V -F -p atmega644 -P /dev/tty.usbserial-FTE0U36U -C /opt/local/etc/avrdude.conf -c usbtiny -b 19200 -U flash:w:Program.hex
Reading fuses from a chip can be used to verify what frequency it is running at, whether the internal oscilator is enabled, etc. To read the fuses, use the following command format:
avrdude -V -F -p atmega644 -P /dev/tty.usbserial-FTE0U36U -C /opt/local/etc/avrdude.conf -c usbtiny -b 19200 -U hfuse:r:-:h -U lfuse:r:-:h
Most of the options will change based on your setup, but the important part is the -U arguments. The format for the argument is <fuse>:r:-:h. This will read the byte value from the specified fuse, and write it in hex format (h) on stdout (-). In place of stdout, you can also specify a file.
The first step to burning AVR fuses is to figure out what they should be. You can use the very handy Fuse Calculator for this. Be sure to double (triple!) check your fuse values, as incorrect ones can brick your chips.
You should include the same arguments as you would when you are programming a chip, the only difference is the -U options. An example fuse burning command is below; this is for the ATMega644 using AVRDude from MacPorts, on a USBTiny programmer. The actual fuse programming string can be obtained from the above calulator website.
avrdude -V -F -p atmega644 -P /dev/tty.usbserial-FTE0U36U -C /opt/local/etc/avrdude.conf -c usbtiny -b 19200 -U lfuse:w:0xef:m -U hfuse:w:0x99:m