Are you an 8-bit or a 32-bit programmer?

At OMZLO, we have been mainly focussing our development efforts on newer 32-bit Arm-Cortex chips (STM32 and SAMD), which typically offer more RAM, more speed, more peripherals, at a similar or lower price-point than older 8-bit MCUs. But 8-bit MCUs are far from dead. Microchip has notably released a new series of chips, collectively branded as the "tinyAVR 0-series", which offer more modern peripherals than the older AVR chips at a very competitive price point. They seem like the perfect candidate for simple products that don't need all the features and fine-tuning capabilities of the newer 32-bit MCUs. 8-bit MCUs are also substantially simpler to program, which translates into faster development time.

Thanks to the success of the Arduino UNO, there are tons of tutorials online that explain how to program 8-bit Atmega328 microcontrollers and their cousins like the Attiny85 using direct register access, without the Arduino language or any vendor IDE such as Atmel Studio. Just google "atmega328 blinky". All you need is an AVR C-compiler, a text editor, avrdude, and an AVR programmer. Some resources even show how to also build the electronics needed to get a basic atmega328 running on a breadboard. However, it's hard to find the same information for these newer "tinyAVR 0" chips.

Of course, Microchip offers all the tools necessary to program these newer "TinyAVR" MCUs with their windows-only IDE. There are also "Arduino cores" for some of these newer "TinyAVR" MCUs that let you program them with the Arduino IDE. But again, if you like to write code for MCUs in "baremetal" style, with your favorite text editor, a makefile, and a c-compiler, there are few resources available online.

In this blog post, we will describe how to program a blinky firmware on an Attiny406, from the ground up, using the simplest tools. Most of the things described here can be easily transposed to other TinyAVR MCUs. Our approach is generally guided toward macOS or Linux users, but should also be applicable in an MS-Windows environment with a few minor changes.


We decided to play with the Attiny406, with a view of using it in the future to replace the Attiny45 we currently use on the PiWatcher, our Raspberry-Pi watchdog. The Attiny406 has 4K of flash space, 256 bytes of RAM, and can run at 20Mhz without an external clock source.

One of the most important differences between the new TinyAVR MCUs and the older classic AVR MCU like the Attiny85 is that the newer chips use a different programming protocol called UPDI, which requires only 3 pins, as opposed to the 6-pin ISP on the classic AVRs.

A little research shows that programming TinyAVRs with UPDI can be achieved with a simple USB-to-serial cable and a resistor, thanks to a python tool called pyupdi, which suggests the following connection diagram for firmware upload:

                        Vcc                     Vcc
                        +-+                     +-+
                         |                       |
 +---------------------+ |                       | +--------------------+
 | Serial port         +-+                       +-+  AVR device        |
 |                     |      +----------+         |                    |
 |                  TX +------+   4k7    +---------+ UPDI               |
 |                     |      +----------+    |    |                    |
 |                     |                      |    |                    |
 |                  RX +----------------------+    |                    |
 |                     |                           |                    |
 |                     +--+                     +--+                    |
 +---------------------+  |                     |  +--------------------+
                         +-+                   +-+
                         GND                   GND


We created a minimalistic breakout board for the Attiny406. The board can be powered by 5V through USB or a lower 3.3V through dedicated VCC/GND pins. An LED and a button were also fitted on the board. For testing purposes, we decided to embed the 4.7K resistor needed for the UPDI programming directly in the hardware (i.e. resistor R2). This gives us the following schematic:



The resulting breakout board is tiny and fits conveniently on a small breadboard. The design files are shared on

Programming the Attiny406 on the board with a USB-serial cable is done by connecting the headers on the board edge:



We installed pyupdi following the instructions provided on their webpage.

We connected our USB-Serial cable to the board with the 4 dedicated UPDI pins available on the board. Our USB-Serial converter shows up as the file /dev/tty.usbserial-FTF5HUAV on a MacOS system.

To test that the programmer recognizes the Attiny406, you can issue a command similar to the following, adapting the path for the USB-serial converter to your setup:

pyupdi -d tiny406 -c /dev/tty.usbserial-FTF5HUAV -i

This should result in the following output if all goes well:

Device info: {'family': 'tinyAVR', 'nvm': 'P:0', 'ocd': 'D:0', 'osc': '3', 'device_id': '1E9225', 'device_rev': '0.1'}

The C compiler

The typical avr-gcc available on macOS with homebrew did not seem to recognize the Attiny406 as a compiler target, so we went off to install the avr-gcc compiler provided by Microchip, which is available here. Downloading the compiler requires you to create an account on the Microchip website, which is a bit annoying.

AVR toolchain link

Once downloaded, we extracted the provided archive in a dedicated directory. The bin directory in the archive should be added to the PATH variable to make your life easier. Assuming the downloaded compiler is stored in the directory $HOME/Src/avr8-gnu-toolchain-darwin_x86_64, the PATH can be altered by adding the following line to your .bash_profile file:

export PATH=$PATH:$HOME/Src/avr8-gnu-toolchain-darwin_x86_64/bin/

Newer Attiny MCUs are not supported out of the box by the Microchip avc-gcc compiler. You need to download a dedicated Attiny Device Pack from their website, as shown below:

AVR toolchain link

The resulting downloaded Device Pack is named Atmel.ATtiny_DFP.1.6.326.atpack (or similar depending on versioning). Though the extension is .atpack, the file is actually a zip archive. We changed the extension to .zip and extracted the package in the directory $HOME/Src/Atmel.ATtiny_DFP.1.6.326 next to the compiler files.

C program

We created the following program that blinks the LED on pin PB5 of our Attiny board at a frequency of 1Hz.

#include <avr/io.h>
#include <util/delay.h>

int main() {
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0); // set to 20Mhz (assuming fuse 0x02 is set to 2)

    PORTB.DIRSET = (1<<5);
    for (;;) {
        PORTB.OUTSET = (1<<5);
        PORTB.OUTCLR = (1<<5);

The code looks very similar to what you would see on a classic AVR "blinky" program. One visible change is the use of structures to access various registers of the MCU: e.g instead of setting bits in PORTB, you access PORTB.DIRSET.

The other visible change is the clock setup code _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0). Out of the box, at reset, the Attiny406 runs at 3.33Mhz, which corresponds to a base frequency of 20Mhz with a 6x clock divider applied. To enable the full 20Mhz speed, the register CLKCTRL.MCLKCTRLB is cleared. Because this register needs to be protected against accidental changes, the Attiny406 requires a specific programming sequence to modify it. Fortunately, this is natively offered by the macro _PROTECTED_WRITE. More details are available in the Attiny406 datasheet.

In comparison with an STM32 or a SAMD21, the code is blissfully simple.


We assume the following directory structure where:

  • Src/Atmel.ATtiny_DFP.1.6.326/ is the location of the Microchip Device Pack
  • Src/attiny406-test/ is the directory where the code above is stored in a file called main.c

Compiling the code can be done by issuing the following command within attiny406-test/ directory,:

avr-gcc -mmcu=attiny406 -B ../Atmel.ATtiny_DFP.1.6.326/gcc/dev/attiny406/ -O3 -I ../Atmel.ATtiny_DFP.1.6.326/include/ -DF_CPU=20000000L -o attiny406-test.elf main.c

An -O optimization flag is required to make the _delay_ms() function calls work successfully, as well as defining the variable F_CPU to reflect the expected chip clock speed. The rest of the parameters provide the location of the Attiny406 device-specific files we previously extracted from the Device Pack.

Uploading the firmware to the MCU requires a conversion to the intel HEX format and a call to the pyupdi tool. To address all these steps, we created a simple Makefile.

ELF=$(notdir $(CURDIR)).elf  
HEX=$(notdir $(CURDIR)).hex

CFLAGS=-mmcu=attiny406 -B ../Atmel.ATtiny_DFP.1.6.326/gcc/dev/attiny406/ -O3
CFLAGS+=-I ../Atmel.ATtiny_DFP.1.6.326/include/ -DF_CPU=$(F_CPU)
LDFLAGS=-mmcu=attiny406 -B ../Atmel.ATtiny_DFP.1.6.326/gcc/dev/attiny406/

all:    $(HEX)  

$(ELF): $(OBJS)
                $(LD) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)

$(HEX): $(ELF)
                avr-objcopy -O ihex -R .eeprom $< $@

flash:  $(HEX)
                pyupdi -d tiny406 -c /dev/tty.usbserial-FTF5HUAV -f attiny406-test.hex

                pyupdi -d tiny406 -c /dev/tty.usbserial-FTF5HUAV -fr

                rm -rf $(OBJS) $(ELF) $(HEX)

To compile the code, we simply type make. Uploading is done with make flash. This Makefile can be further enhanced as needed.


With the right tools, baremetal programming on the new TinyAVR MCUs is as simple as on its older AVR cousins.

If you have programming tips for the AVRTiny, please share them with us on on Twitter or in the comments below.