Blowing a Fuse

By | 2012-09-19

I like to use avrdude for programming my AVR’s. It’s got support for pretty much every possible programming method supported by the AVR CPUs and has a good command line interface. A command line interface is very important when you use Makefiles to act as your IDE.

Here, for example, is a little bit of a Makefile from an AVR-based project:

TARGET     := projectname
MCU        := atmega88
PROGDEVICE := usb
DUDEMODE   := dragon_jtag

default: upload

# I'm leaving out the compiler part that actually makes you a .elf file
# to program

# avrdude needs a .hex file to burn, objcopy can extract parts of a
# .elf file and convert them to intel-hex format
%.hex: %.elf
    avr-objcopy --strip-all --strip-debug \
        --only-section=text --only-section=data \
        --output-target=ihex $< $@

upload-%: %.hex
    avrdude -p $(PROGDEVICE) -c $(DUDEMODE) \
        -U flash:w:$<

upload: upload-$(TARGET)

That’s all pretty standard Makefile fare; make a hex file, then burn it. A nice automated way of getting your program into a device. However, there is another component to consider when programming a microcontroller; its so-called “fuses”. These have differing functions on different devices, but they often control clock source, memory protection, allowed programming methods. The AVR is no exception. avrdude can program these fuses for you. For example:

avrdude -p $(PROGDEVICE) -c $(DUDEMODE) \
    -U lfuse:w:0xc2:m \
    -U hfuse:w:0x91:m \
    -U efuse:w:0xff:m

This programs the low fuse, the high fuse and the extended fuse.

What’s wrong then? The Makefile is the wrong place for this information. What goes in the chip is described by the source files, not by the Makefile. Why should the fuse bytes be any different from the program bytes? This is particularly relevant when we consider that often the program will rely on a particular fuse configuration. Further, when we make a binary release, we don’t want the receiver to need our development enviornment to be able to program a device; or even the Makefile.

Ideally we want to be able to store the fuse values in source code, translate those values to special sections in the elf file (where they are permantly documented as part of the release) and burn them from the Makefile. Since avr-libc v1.6, we can do so.

Step 1 then: get them from the source file to the elf file.

#include <avr/io.h>
#include <avr/fuse.h>

// Fuses are inverse logic, so should be combined with AND (&) not OR
// (|) as you would normally
FUSES = {
#   if FUSE_MEMORY_SIZE == 1
    //  Low Fuse Byte (e.g. ATmega88)
    //   FUSE_CKSEL0
    //   FUSE_CKSEL1
    //   FUSE_CKSEL2
    //   FUSE_CKSEL3
    //   FUSE_SUT0
    //   FUSE_SUT1
    //   FUSE_BODEN
    //   FUSE_BODLEVEL
    .low = LFUSE_DEFAULT,
#   elif FUSE_MEMORY_SIZE == 2
    //  High Fuse Byte (e.g. ATmega88)
    //   FUSE_BOOTRST
    //   FUSE_BOOTSZ0
    //   FUSE_BOOTSZ1
    //   FUSE_EESAVE
    //   FUSE_CKOPT
    //   FUSE_SPIEN
    //   FUSE_WDTON
    //   FUSE_RSTDISBL
    .high = HFUSE_DEFAULT,
#   elif FUSE_MEMORY_SIZE == 3
    .extended = EFUSE_DEFAULT,
#   else
#       error "No support for more than three fuse bytes"
#   endif
}

The avr/fuse.h creates the FUSES symbol along with the other constants to make this work. You would set .low, .high and .extended to whatever suited your project – I’ve simply left them at the defaults here. I’d suggest you put this code in a separate module, then link it with the rest of your application. Here’s what you might get in your elf (I used an empty main()):

$ avr-nm --numeric-sort --demangle fuses.elf > fuses.sym

fuses.elf:     file format elf32-avr

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000050  00000000  00000000  00000074  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .fuse         00000003  00820000  00820000  000000c4  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .stab         00000aec  00000000  00000000  000000c8  2**2
                  CONTENTS, READONLY, DEBUGGING
  3 .stabstr      000007a7  00000000  00000000  00000bb4  2**0
                  CONTENTS, READONLY, DEBUGGING
  4 .comment      00000011  00000000  00000000  0000135b  2**0
                  CONTENTS, READONLY

   text    data     bss     dec     hex filename
   0x50     0x3     0x0      83      53 fuses.elf
   0x50     0x3     0x0      83      53 (TOTALS)

Note in particular the new section .fuse, which is (not surprisingly), three bytes long; note also that its load address (LMA) is set well outside the memory space of any AVR (purposefully – you will never program them to normal memory). Let’s have a look at what’s in that section.

$ avr-objdump -s --section=.fuse fuses.elf 

fuses.elf:     file format elf32-avr

Contents of section .fuse:
 820000 0000f9                               ...             

There we go: 0x00, 0x00, 0xf9. Three fuse bytes store in the elf file and accessible from the command line.

With a bit of Makefile magic, we can pull them out and into a variable ready for programming.

upload-fuses: FUSES = $(shell avr-objdump -s --section=.fuse $(TARGET).elf 
    | tail -1 | awk '{print substr($$2,1,2),substr($$2,3,2),substr($$2,5,2)}')
upload-fuses: $(TARGET).elf
    avrdude -p $(PROGDEVICE) -c $(DUDEMODE) \
        $(if $(word 1,$(FUSES)),-U lfuse:w:0x$(word 1,$(FUSES)):m) \
        $(if $(word 2,$(FUSES)),-U hfuse:w:0x$(word 2,$(FUSES)):m) \
        $(if $(word 3,$(FUSES)),-U efuse:w:0x$(word 3,$(FUSES)):m)

That’s all a bit hairy, but is mindlessly copy-and-pasteable between projects; it makes use of a little-known facility of Makefiles called target-specific variables. This lets you set a variable based on the target being built. Unusually, I’m not using it here for its intended purpose, the variable FUSES is not target-specific at all. However, target specific variables, by their nature, have to be evaluated at the time the target runs. That means we can defer the running of the ‘shell’ command line used to generate FUSES until after we are sure the elf file is available. If we used a normal global makefile variable, it would likely fail because the elf file doesn’t exist until the makefile generates it.

The recipe to actually program the fuses is a little bit convoluted too, but it’s written so that it will flexibly handle any number of fuses from one to three – if no third byte exists in the elf, then no “-U efuse:...” will be included on the command line.

Leave a Reply