Programming your device with the code you want to execute, whether with a bootloader or directly with a programmer, is one layer of programming your device. Another layer is the so-called fuses inside an ATmega microcontroller.
Fuses are programmable nonvolatile registers that are used to configure things like what the clock source and rate for the microcontroller is, whether brown-out detection is enabled, whether you're using a bootloader and if so how much flash will be reserved for a bootloader. Programming fuses can be scary because if you don't do it right, you can brick the micro. However, you can gain finer-grained control over your project by setting the micro's fuses yourself.
In this article, I will investigate using fuses to configure a 3.3V/8MHz Pro Mini to set a brown-out detection level that is different from what the standard 3.3V/8MHz Pro Mini uses because this happens to be the use case that has driven me to learn about this. However, what I cover should be adaptable to other Arduinos that use other ATmega processors. It may not be very relevant to other micro families.
There are a couple ways we can go about setting the fuses on an ATmega328P Arduino. We can use a programmer along with the command-line avrdude
utility to explicitly set fuse values, or we can create an additional Arduino board specification with the desired fuse settings, then burn a bootloader using the Arduino IDE, which will also set the fuses for us.
Both create different kinds of difficulties. To keep things as “Arduino” as possible, in what follows I am going to pursue the second approach. You can think of this process in terms of four questions, which I try to answer below. After that I walk through a case study.
Specs for Arduino boards, including fuse bit settings, are found in various board.txt
files, themselves found in various hardware
folders. This can get a little confusing as there are number of these folders and files that you'll find on your computer. I'll try to go through the differences below.
A board.txt
file is included in the Arduino package you installed. You'll find it at
/path-where-you-installed-arduino/hardware/arduino/avr
This file has board descriptions written in a syntax the IDE understands for default boards Arduino supported at the time the package was released. This is not where you will add descriptions for your custom board or existing board with modified fuse settings, but it's good place to look to begin to grok the syntax.
When you open this boards.txt
file in a text editor, look for the line:
uno.name=Arduino/Genuino Uno
Following that are a number of lines starting with uno.
These are all parameters that describe the specifics of the Arduino/Genuino Uno board. Knowing this, you can probably make at least partial sense of some of the parameters. The lines that begin uno.bootloader.
are where the fuse settings are given. You can see hexadecimal entries for the low bits, high bits, and extended bits along with the lock and unlock bits.
If you do some more searching, you'll find an entry for
pro.name=Arduino Pro or Pro Mini
The syntax here is a little more elaborate because the Pro and Pro Mini come in different flavors: they have been made with both ATmega328P and ATmega168 micros, and in 5V/16MHz and 3.3V/8MHz versions. Bootloader parameters that apply to all Pro Minis are prefixed with pro.bootloader.
Those that apply to a specific version are prefixed with pro.menu.cpu.<version-identifier>.bootloader.
You will find another boards.txt
file if you look in
/your-Arduino-profile-folder/packages/arduino/hardware/avr/<a-version-number>
This is not your Arduino sketch folder. Rather it's where global application configuration information is stored. You can see the path to your profile folder towards the end of the Preferences dialog box. The Arduino profile folder on Linux systems is /home/<username>/.arduino15
.
The boards.txt
here is one that the Aurdino IDE actually uses. It might be a copy of the boards.txt
that shipped with the installation bundle, or it might have been subsequently updated by the IDE. If you have added additional boards using the IDE, you are likely to find additional folders for their architectures under /your-Arduino-profile-folder/packages
or its subfolders.
It's my understanding that you can add your custom board descriptions in these areas, but the prevailing advice is not to. This area is designed to be managed by the IDE. If you make additions here, the IDE may overwrite them or you might corrupt the ability for the IDE to successfully add/remove/update boards. You can safely add new boards here if you go through a somewhat cumbersome process involving a few additional files. But there seems to be an easier way.
The following method works on the version of Arduino I used at the time or writing this: 1.8.8. I don't know whether it can be relied on to hang around for a while or has been deprecated.
So, until I'm advised this is a horrible idea, the way I recommended you add your own custom board descriptions is to create a folder called hardware
in the folder where your sketches are located and add the needed files there. I walk through the details in the case study that follows.
There is a lot of arcane knowledge that applies to writing custom Arduino board.txt
entries, and I haven't found a source that documents it thoroughly. So my best advice is to study the boards.txt
found at
/path-where-you-installed-arduino/hardware/arduino/avr/
to get as familiar with the syntax. What interests us here are the entries for low_fuses
, high_fuses
, and extended_fuses
(and to a certain extent unlock_bits
and lock_bits
. The values for these entries will change depending on what features and behavior you want to enable or disable.
Another good resource to look through is the files found in the breadboard-1-6-x.zip archive found under “Minimal Circuit (Eliminating the External Clock)” at From Arduino to a Microcontroller on a Breadboard
This article covers making a variant of an existing board where only the fuses have been changed. The relevant parameters in the boards.txt
specs will have low_fuses
, high_fuses
, and extended_fuses
in their names. The values associated with those parameters are hexadecimal.
Exactly which fuses do what and how is an article in and of itself. I haven't written one of those, so you might want to check out this article by Martyn Currey for a friendly description. You might also find online fuse calculators from Eleccelerator and Engbedded to be helpful. But be forewarned, there's no guarantee these calculators are bug-free. The ultimate reference is the ATmega328P datasheet.
When you're figuring out what your fuse values should be, don't be afraid to reference back to the original settings in whatever board.txt
file entry you're basing things off.
There is one additional caveat here: You can only change fuses that are compatible with what the bootloader you plan to use will let you do. Again there isn't a lot of documentation here, so if you've done everything right but things still don't work, this might be the reason. The solution to this is to compile a custom bootloader, which is well beyond the scope of this piece.
This too is a pretty big topic, but the TL;DR is: hook up a programmer to your board, select the right board and programmer, then do a Tools > Burn Bootloader. This Sparkfun tutorial is a good reference if you need more hand-holding.
With this covered, let's get busy.
Reading an Atmega328P's fuse settings is a lot easier than writing them. So we start with this. We will also need this to confirm that everything had gone to plan when we burn the new settings.
With a USBtinyISP (or theoretically any supported programmer) connected to your Atmega328P target, use the following command to get a reading of how the fuses are configured.1) This assumes you are using a Linux-like CLI and do not have avrdude
installed as a global command.
$ /path/to/avrdude -C /path/to/avrdude.conf -c usbtiny -p m328p -U lfuse:r:-:i -v
The /path/to/avrdude
is typically:
/path-to-where-you-installed-arduino/hardware/tools/avr/bin/avrdude
The /path/to/avrdude.conf
is typically:
/path-to-where-you-installed-arduino/hardware/tools/avr/etc/avrdude.conf
Newer versions of these might be found under
/your-Arduino-profile-folder/packages/arduino/tools/avrdude
The output of the above for an Arduino Uno looks like:
# preamble, preamble, preamble ... avrdude: safemode: lfuse reads as FF avrdude: safemode: hfuse reads as DE avrdude: safemode: efuse reads as FD avrdude: safemode: Fuses OK (E:FD, H:DE, L:FF) avrdude done. Thank you.
For a stock 3.3V/8MHz Pro Mini it's:
# preamble, preamble, preamble ... avrdude: safemode: lfuse reads as FF avrdude: safemode: hfuse reads as DA avrdude: safemode: efuse reads as FD avrdude: safemode: Fuses OK (E:FD, H:DA, L:FF) avrdude done. Thank you.
For the nitty-gritty breakdown on the command see this page.
In the section, I walk through the process of creating a custom version of a 3.3V/8MHz Pro Mini where the nominal brown-out detection (BOD) threshold is changed from the stock 2.7V to 1.8V.
If you reduce the BOD threshold or turn off BOD entirely, don't run programs uploaded by the bootloader at low voltages. See this for an explanation why.
The only fuse settings I want to change are those that affect the BOD. These are defined in the lowest three bits of the extended fuse bits. The upper five bits are not used/reserved.
extended fuse bit | function |
---|---|
7 | not used/reserved |
6 | not used/reserved |
5 | not used/reserved |
4 | not used/reserved |
3 | not used/reserved |
2 | BODLEVEL2 |
1 | BODLEVEL1 |
0 | BODLEVEL0 |
BODLEVEL2, 1, 0 | VBOT |
---|---|
111 | BOD disabled |
110 | 1.8V2) |
101 | 2.7V |
100 | 4.3V |
0xx | not used/reserved |
Now is a good time to mention that the ATmega328P fuses are active low, meaning that a 0 sets (“programs”) a bit, and a 1 unsets (“unprograms”) it. This can sometimes lead to hilarious misunderstandings, so be careful.
So, to set the brown-out threshold to 1.8V I want the last three bits of the extended fuse byte to be b110
and the entire byte to be b11111110
or 0xFE
. Online fuse calculators agree, so it's probably right.
Inside my Arduino sketches folder (/home/<username>/Arduino
in Debian), I created the following folder structure:
<arduino-sketches-folder>/ +-- hardware/ +-- mfkcustom/ +-- avr/ +-- bootloaders/ +-- atmega/ +-- variants/
mfkcustom
is where I will put all my own custom board definitions. The remaining folder structure is needed by Arduino to make things work.
Inside the avr
folder, create a text file called boards.txt
for your board definition(s). I based the board definition here on the specs for the standard 3.3V Pro Mini and the format of boards.txt
found in the breadboard-1-6-x.zip archive found under “Minimal Circuit (Eliminating the External Clock)” at From Arduino to a Microcontroller on a Breadboard:
############################################################## # Arduino Pro Mini 3.3V/8Mhz, 1.8V BOD (ATmega328P only) mfkpro18bod.name=Pro Mini 3.3V/8Mhz, 1.8V BOD, ATmega328P mfkpro18bod.upload.protocol=arduino mfkpro18bod.upload.maximum_size=30720 mfkpro18bod.upload.maximum_data_size=2048 mfkpro18bod.upload.speed=57600 mfkpro18bod.bootloader.low_fuses=0xFF mfkpro18bod.bootloader.high_fuses=0xDA mfkpro18bod.bootloader.extended_fuses=0xFE mfkpro18bod.bootloader.file=atmega/ATmegaBOOT_168_atmega328_pro_8MHz.hex mfkpro18bod.bootloader.unlock_bits=0x3F mfkpro18bod.bootloader.lock_bits=0x0F mfkpro18bod.build.mcu=atmega328p mfkpro18bod.build.f_cpu=8000000L mfkpro18bod.build.core=arduino:arduino mfkpro18bod.build.variant=arduino:eightanaloginputs mfkpro18bod.build.board=AVR_PRO mfkpro18bod.bootloader.tool=arduino:avrdude mfkpro18bod.upload.tool=arduino:avrdude ##############################################################
I didn't create an option to use an ATmega168 instead of an ATmega328P because I wanted to keep the syntax as simple as possible.
Some things to note here:
mfkpro18bod
here) needs to be unique throughout the installation.arduino:
to indicate (I think) that global Arduino files and tools that define these should be used rather than a local ones.mfkpro18bod.bootloader.file
refers to a hex file that's is in a local folder called atmega
. That's the folder at <arduino-sketches-folder>/hardware/mfkcustom/avr/bootloaders/atmega/
created above. We'll copy in that hex file next.
I will be using the same bootloader that is used for the standard 3.3V/8MHz Pro Mini. Standard bootloader files for ATmega-based Arduinos are found in:
/path-to-where-you-installed-arduino/packages/arduino/hardware/avr/<version-number>/bootloaders/atmega/
Copy ATmegaBOOT_168_atmega328_pro_8MHz.hex
there to:
<arduino-sketches-folder>/hardware/mfkcustom/avr/bootloaders/atmega/
and make sure the name matches with what you specified in boards.txt
.
Whether you plan to upload your sketches with a USB to serial converter (i.e., using the bootloader) or directly using a programmer, you still need to burn the bootloader to set the fuses. So let's burn and test it.
# preamble, preamble, preamble ... avrdude: safemode: lfuse reads as FF avrdude: safemode: hfuse reads as DA avrdude: safemode: efuse reads as FE avrdude: safemode: Fuses OK (E:FD, H:DA, L:FF) avrdude done. Thank you.
Verify that the extended fuse setting has changed to 0xFE
.
If you reduce the BOD threshold or turn off BOD entirely, don't run programs uploaded by the bootloader at low voltages. To run a sketch at low voltage you should upload your sketch directly using a programmer. See this for an explanation why.
After burning the bootloader to set the fuses, hook up your programmer. Be sure you have set both the board and programmer to the correct values under Tools. Then upload your simple test sketch with Sketch > Upload Using Programmer. Your sketch should upload and start running (possibly only after you have disconnected the programmer).
Follow the instructions above to read the fuse settings. You should see:
# preamble, preamble, preamble ... avrdude: safemode: lfuse reads as FF avrdude: safemode: hfuse reads as DA avrdude: safemode: efuse reads as FE avrdude: safemode: Fuses OK (E:FD, H:DA, L:FF) avrdude done. Thank you.
Confirm that the extended fuse is still 0xFE
.
You can now connect the Pro Mini to a variable supply to see whether it works as expected below 2.7V. It may not work all the way down to 1.8V though, but because you're not using a bootloader, you won't run the risk of corrupting the program memory.