Birds Like Wires

Feed the Birds

Revisiting Arduino Bootloaders

A couple of years ago, I put together an Arduino / ATmega-based project that really needed to skimp on power. There wasn’t a huge amount of documentation around at the time, but after some digging I managed to get the ATmega running at 8MHz using it’s own internal oscillator. Taking it one step further, I then dropped that rate down to 1MHz by recompiling the bootloader. Tada! That project ran constantly for many months at a time on a couple of D-cell batteries.

When it came to my latest endeavour I dug out the old bootloaders. No beans. Times have moved on a little in the Arduino world and the Arduino 1.0.5 IDE wasn’t playing with my bootloaders any more. I thought I’d narrowed it to a simple tweak of the boards.txt file, but apparently not. I’d never been completely happy that I’d not gone the distance and used Optiboot anyway, so before I knew it the source code was downloading.

Building Optiboot

The Optiboot bootloader is actually included in the Arduino IDE these days, but compiled just at 16MHz for the veteran ATmega168 / 328 chips and not actually available for use without some tinkering. So, I decided to replicate my earlier setup and create 1, 8 and 16MHz versions.

All of the following was performed on a Mac running OS X v10.9.2. If this is your first shot, I’d recommend compiling within the IDE, as everything should be ready to go. The procedure should be pretty much the same on other systems, though you will need to install your tools from different sources if compiling outside of the environment. To compile successfully outside of the IDE requires avr-gcc 4.6.2 or earlier (back to 4.3.2, I believe), which can be installed on OS X using the 2013-02-12 version of CrossPack.

Install CrossPack and grab the source code, or use Terminal.app to navigate to the optiboot directory within the IDE.

cd /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/bootloaders/optiboot

You’ll then need to add the following to create new targets in the Makefile:

# ATmega168 @ 1MHz Internal (9600 baud)
#
atmega168_1mhz: TARGET = atmega168
atmega168_1mhz: MCU_TARGET = atmega168
atmega168_1mhz: CFLAGS += '-DLED_START_FLASHES=2' '-DBAUD_RATE=9600'
atmega168_1mhz: AVR_FREQ = 1000000L 
atmega168_1mhz: $(PROGRAM)_atmega168_1mhz.hex
atmega168_1mhz: $(PROGRAM)_atmega168_1mhz.lst

atmega168_1mhz_isp: atmega168
atmega168_1mhz_isp: TARGET = atmega168
atmega168_1mhz_isp: MCU_TARGET = atmega168

atmega168_1mhz_isp: LFUSE = 62
atmega168_1mhz_isp: HFUSE = DD
atmega168_1mhz_isp: EFUSE = 04
atmega168_1mhz_isp: isp

# ATmega168 @ 8MHz Internal (38400 baud)
#
atmega168_8mhz: TARGET = atmega168
atmega168_8mhz: MCU_TARGET = atmega168
atmega168_8mhz: CFLAGS += '-DLED_START_FLASHES=2' '-DBAUD_RATE=38400'
atmega168_8mhz: AVR_FREQ = 8000000L 
atmega168_8mhz: $(PROGRAM)_atmega168_8mhz.hex
atmega168_8mhz: $(PROGRAM)_atmega168_8mhz.lst

atmega168_8mhz_isp: atmega168
atmega168_8mhz_isp: TARGET = atmega168
atmega168_8mhz_isp: MCU_TARGET = atmega168

atmega168_8mhz_isp: LFUSE = E2
atmega168_8mhz_isp: HFUSE = DD
atmega168_8mhz_isp: EFUSE = 04
atmega168_8mhz_isp: isp

# ATmega168 @ 16MHz External (115200 baud)
#
atmega168_16mhz: TARGET = atmega168
atmega168_16mhz: MCU_TARGET = atmega168
atmega168_16mhz: CFLAGS += '-DLED_START_FLASHES=2' '-DBAUD_RATE=115200'
atmega168_16mhz: AVR_FREQ = 16000000L 
atmega168_16mhz: $(PROGRAM)_atmega168_16mhz.hex
atmega168_16mhz: $(PROGRAM)_atmega168_16mhz.lst

atmega168_16mhz_isp: atmega168
atmega168_16mhz_isp: TARGET = atmega168
atmega168_16mhz_isp: MCU_TARGET = atmega168

atmega168_16mhz_isp: LFUSE = FF
atmega168_16mhz_isp: HFUSE = DD
atmega168_16mhz_isp: EFUSE = 04
atmega168_16mhz_isp: isp

# ATmega328 @ 1MHz Internal (9600 baud)
#
atmega328_1mhz: TARGET = atmega328
atmega328_1mhz: MCU_TARGET = atmega328p
atmega328_1mhz: CFLAGS += '-DLED_START_FLASHES=2' '-DBAUD_RATE=9600'
atmega328_1mhz: AVR_FREQ = 1000000L
atmega328_1mhz: LDSECTIONS = -Wl,--section-start=.text=0x7e00 -Wl,--section-start=.version=0x7ffe
atmega328_1mhz: $(PROGRAM)_atmega328_1mhz.hex
atmega328_1mhz: $(PROGRAM)_atmega328_1mhz.lst

atmega328_1mhz_isp: atmega328
atmega328_1mhz_isp: TARGET = atmega328
atmega328_1mhz_isp: MCU_TARGET = atmega328p

atmega328_1mhz_isp: LFUSE = 62
atmega328_1mhz_isp: HFUSE = DE
atmega328_1mhz_isp: EFUSE = 05
atmega328_1mhz_isp: isp

# ATmega328 @ 8MHz Internal (38400 baud)
#
atmega328_8mhz: TARGET = atmega328
atmega328_8mhz: MCU_TARGET = atmega328p
atmega328_8mhz: CFLAGS += '-DLED_START_FLASHES=2' '-DBAUD_RATE=38400'
atmega328_8mhz: AVR_FREQ = 8000000L
atmega328_8mhz: LDSECTIONS = -Wl,--section-start=.text=0x7e00 -Wl,--section-start=.version=0x7ffe
atmega328_8mhz: $(PROGRAM)_atmega328_8mhz.hex
atmega328_8mhz: $(PROGRAM)_atmega328_8mhz.lst

atmega328_8mhz_isp: atmega328
atmega328_8mhz_isp: TARGET = atmega328
atmega328_8mhz_isp: MCU_TARGET = atmega328p

atmega328_8mhz_isp: LFUSE = E2
atmega328_8mhz_isp: HFUSE = DE
atmega328_8mhz_isp: EFUSE = 05
atmega328_8mhz_isp: isp

# ATmega328 @ 16MHz External (115200 baud)
#
atmega328_16mhz: TARGET = atmega328
atmega328_16mhz: MCU_TARGET = atmega328p
atmega328_16mhz: CFLAGS += '-DLED_START_FLASHES=2' '-DBAUD_RATE=115200'
atmega328_16mhz: AVR_FREQ = 16000000L
atmega328_16mhz: LDSECTIONS = -Wl,--section-start=.text=0x7e00 -Wl,--section-start=.version=0x7ffe
atmega328_16mhz: $(PROGRAM)_atmega328_16mhz.hex
atmega328_16mhz: $(PROGRAM)_atmega328_16mhz.lst

atmega328_16mhz_isp: atmega328
atmega328_16mhz_isp: TARGET = atmega328
atmega328_16mhz_isp: MCU_TARGET = atmega328p

atmega328_16mhz_isp: LFUSE = FF
atmega328_16mhz_isp: HFUSE = DE
atmega328_16mhz_isp: EFUSE = 05
atmega328_16mhz_isp: isp

Looking inside the Makefile it should be obvious where this goes, but if in doubt, paste it before the line:

# 20MHz clocked platforms

Once you’ve done that you can cross your fingers and compile them:

chmod +x ./omake
./omake atmega168_1mhz atmega168_8mhz atmega168_16mhz atmega328_1mhz atmega328_8mhz atmega328_16mhz

Or from outside the IDE will probably be:

make OS=macosx atmega168_1mhz atmega168_8mhz atmega168_16mhz atmega328_1mhz atmega328_8mhz atmega328_16mhz

With any luck, in that optiboot directory you’ll now have some lovely new bootloaders.

So, what did we just do?

Those new targets you added to the Makefile created some rules for each variation of the bootloader that we wanted. They tell the compiler the speed at which the chip will be running (1Mhz, 8MHz or 16MHz) and set a suitable baud rate for communication. The faster the chip is running, the more reliably it can communicate at high speeds. Optiboot can chat at 115200 baud when the chip is at 16MHz, but the less accurate internal oscillator requires us to slow things down. I found a baud rate of 38400 for 8MHz and 9600 for 1MHz to be the most reliable settings.

There’s one more very important thing that is configured here; the fuse settings for the chip. If you’ve read my earlier articles you’ll be familiar with these, but if not, they’re ‘sticky’ settings that tell the chip how it should perform. You can check out the available options using this Fuse Calculator. It is these fuse settings that ultimately tell the microcontroller where it’s clock source is coming from and how big it’s boot flash area should be, amongst other things. Another bonus of using Optiboot is that it needs half the space of the normal Arduino loader, so you’ve got just that tiny bit more space for your code!

If you were so inclined, you could use the Fuse Calculator to pick some other options, but I’ll leave that up to you.

Adding to the Arduino IDE

Once the compiling is done, you need to tell the IDE where these new bootloaders live and how to use them. This is done by editing the boards.txt file. This is a couple of levels up from the optiboot directory in the IDE, or pretty obvious in the downloaded source.

##############################################################

atmega168_1mhz.name=[Optiboot] ATmega168 @ 1MHz Internal (9600 baud)

atmega168_1mhz.upload.protocol=arduino
atmega168_1mhz.upload.maximum_size=15872
atmega168_1mhz.upload.speed=9600

atmega168_1mhz.bootloader.low_fuses=0x62
atmega168_1mhz.bootloader.high_fuses=0xDD
atmega168_1mhz.bootloader.extended_fuses=0x04
atmega168_1mhz.bootloader.path=optiboot
atmega168_1mhz.bootloader.file=optiboot_atmega168_1mhz.hex
atmega168_1mhz.bootloader.unlock_bits=0x3F
atmega168_1mhz.bootloader.lock_bits=0x0F

atmega168_1mhz.build.mcu=atmega168
atmega168_1mhz.build.f_cpu=1000000L
atmega168_1mhz.build.core=arduino:arduino
atmega168_1mhz.build.variant=arduino:standard

##############################################################

atmega168_8mhz.name=[Optiboot] ATmega168 @ 8MHz Internal (38400 baud)

atmega168_8mhz.upload.protocol=arduino
atmega168_8mhz.upload.maximum_size=15872
atmega168_8mhz.upload.speed=38400

atmega168_8mhz.bootloader.low_fuses=0xE2
atmega168_8mhz.bootloader.high_fuses=0xDD
atmega168_8mhz.bootloader.extended_fuses=0x04
atmega168_8mhz.bootloader.path=optiboot
atmega168_8mhz.bootloader.file=optiboot_atmega168_8mhz.hex
atmega168_8mhz.bootloader.unlock_bits=0x3F
atmega168_8mhz.bootloader.lock_bits=0x0F

atmega168_8mhz.build.mcu=atmega168
atmega168_8mhz.build.f_cpu=8000000L
atmega168_8mhz.build.core=arduino:arduino
atmega168_8mhz.build.variant=arduino:standard

##############################################################

atmega168_16mhz.name=[Optiboot] ATmega168 @ 16MHz External (115200 baud)

atmega168_16mhz.upload.protocol=arduino
atmega168_16mhz.upload.maximum_size=15872
atmega168_16mhz.upload.speed=115200

atmega168_16mhz.bootloader.low_fuses=0xFF
atmega168_16mhz.bootloader.high_fuses=0xDD
atmega168_16mhz.bootloader.extended_fuses=0x04
atmega168_16mhz.bootloader.path=optiboot
atmega168_16mhz.bootloader.file=optiboot_atmega168_16mhz.hex
atmega168_16mhz.bootloader.unlock_bits=0x3F
atmega168_16mhz.bootloader.lock_bits=0x0F

atmega168_16mhz.build.mcu=atmega168
atmega168_16mhz.build.f_cpu=16000000L
atmega168_16mhz.build.core=arduino:arduino
atmega168_16mhz.build.variant=arduino:standard

##############################################################

atmega328_1mhz.name=[Optiboot] ATmega328 @ 1MHz Internal (9600 baud)

atmega328_1mhz.upload.protocol=arduino
atmega328_1mhz.upload.maximum_size=30720
atmega328_1mhz.upload.speed=9600

atmega328_1mhz.bootloader.low_fuses=0x62
atmega328_1mhz.bootloader.high_fuses=0xDE
atmega328_1mhz.bootloader.extended_fuses=0x05
atmega328_1mhz.bootloader.path=optiboot
atmega328_1mhz.bootloader.file=optiboot_atmega328_1mhz.hex
atmega328_1mhz.bootloader.unlock_bits=0x3F
atmega328_1mhz.bootloader.lock_bits=0x0F

atmega328_1mhz.build.mcu=atmega328p
atmega328_1mhz.build.f_cpu=1000000L
atmega328_1mhz.build.core=arduino:arduino
atmega328_1mhz.build.variant=arduino:standard

##############################################################

atmega328_8mhz.name=[Optiboot] ATmega328 @ 8MHz Internal (38400 baud)

atmega328_8mhz.upload.protocol=arduino
atmega328_8mhz.upload.maximum_size=30720
atmega328_8mhz.upload.speed=38400

atmega328_8mhz.bootloader.low_fuses=0xE2
atmega328_8mhz.bootloader.high_fuses=0xDE
atmega328_8mhz.bootloader.extended_fuses=0x05
atmega328_8mhz.bootloader.path=optiboot
atmega328_8mhz.bootloader.file=optiboot_atmega328_8mhz.hex
atmega328_8mhz.bootloader.unlock_bits=0x3F
atmega328_8mhz.bootloader.lock_bits=0x0F

atmega328_8mhz.build.mcu=atmega328p
atmega328_8mhz.build.f_cpu=8000000L
atmega328_8mhz.build.core=arduino:arduino
atmega328_8mhz.build.variant=arduino:standard

##############################################################

atmega328_16mhz.name=[Optiboot] ATmega328 @ 16MHz External (115200 baud)

atmega328_16mhz.upload.protocol=arduino
atmega328_16mhz.upload.maximum_size=30720
atmega328_16mhz.upload.speed=115200

atmega328_16mhz.bootloader.low_fuses=0xFF
atmega328_16mhz.bootloader.high_fuses=0xDE
atmega328_16mhz.bootloader.extended_fuses=0x05
atmega328_16mhz.bootloader.path=optiboot
atmega328_16mhz.bootloader.file=optiboot_atmega328_16mhz.hex
atmega328_16mhz.bootloader.unlock_bits=0x3F
atmega328_16mhz.bootloader.lock_bits=0x0F

atmega328_16mhz.build.mcu=atmega328p
atmega328_16mhz.build.f_cpu=16000000L
atmega328_16mhz.build.core=arduino:arduino
atmega328_16mhz.build.variant=arduino:standard

This provides the Arduino IDE and a program called avrdude (which actually does the programming) the correct information about the bootloaders we compiled, including where on the filesystem to find them, which microcontroller to expect, what those fuses should be set to.

Copy and paste that little lot into the file and restart the Arduino app if you had it loaded. You should now see some new options under the Tools > Board menu!

TL;DR

optiboot_blw.zip [9.62 kB] (386)

Too lazy for all that? I don’t blame you. Up above is a zip file with the bootloaders all packaged up and tied with a bow. Just dump it in the hardware directory of your Arduino Sketchbook location (check the preferences of the Arduino app for this). What, no hardware directory in there? Well, you’ll at least have to make that yourself. ;)

Burning the Bootloader

You’ve compiled it or installed it and now you’d like to use the thing. You’re going to need something to write that bootloader to a chip and you’ve got two options. Assuming you have a breadboard and hookup wire (you have, else why on earth are you reading this!) the cheapest way is to use your Arduino board. Write the ArduinoISP sketch from File > Examples > ArduinoISP to your board and hook it up to the target chip on a breadboard as shown on the Arduino site.

An Arduino flashed with the ArduinoISP sketch, connected to an ATmega on a breadboard. An Arduino flashed with the ArduinoISP sketch, connected to an ATmega on a breadboard with an external oscillator.

Alternatively, if you have such a thing as a USBtinyISP, you can hook that up to the ICSP pins on your Arduino board, as shown in the picture at the top of this post. The handy thing about using a USBtinyISP and an Arduino is that you don’t need to faff with hookup wire and can just swap your USB cables between the programmer and the Arduino board when testing. If you flash an internal oscillator bootloader the chip just ignores the external one, so it’s a quick way to check that everything is as it should be.

Remember, if you’re hooking up to a microcontroller on a breadboard and that chip is set to use an external crystal, you must provide one, else it won’t run and won’t be able to accept the new fuse settings and bootloader.

Once you’re wired up correctly, choose your programmer from Tools > Programmer. Then just hit Tools > Burn Bootloader.

Afterburn

An Arduino board with no MCU, connected to an MCU on a breadboard. An Arduino board with no MCU, connected to an MCU on a breadboard with an external crystal.

If all goes to plan, Arduino.app should tell you that the bootloader has burned successfully. At this point you should be able to alter the hookup wiring so that you can start writing sketches to it, or swap the USB cable over to the Arduino board if you had a USBtinyISP to hand.

One way to tell whether the chip is running our Optiboot bootloader is to watch for the initialisation flash on the LED labelled ‘L’ on the Arduino board. It should give two rapid flashes when reset. The default for the normal loader is to flash once and for the standard Optiboot loader it is three.

Troubleshooting

So long as you’ve managed to write the bootloader, everything should work as normal from now on. However, if you do have issues it can be worthwhile burning a different version of the bootloader for your microcontroller; particularly swapping between the 16MHz external and 8MHz internal versions.

I had one ATmega168 that worked perfectly with an external clock, accepted the 8MHz bootloader just fine, but then I could never upload a sketch to it, no matter how slow the baud rate. A second ’168 worked perfectly every time, so I can only assume that the squiffy one had a dodgy internal crystal.

And if you’re using a breadboard, always check and double-check your wiring!

← Recent Articles