New method for writting registers on the 0- and 1-series

A project log for ATtiny 1-series with Arduino support

Creating a break-out board for the ATtiny1616 where sketches can be uploaded from Arduino with the Arduino UNO or a modified AVR JTAG ICE

Sander van de BorSander van de Bor 06/08/2019 at 05:210 Comments

Arduino uses a lot of functions and macros to make it easier for an end-user, with some minor programming and no micro-controller experience, to develop a sketch. Without using these functions, but still using macros, a program for the ATMEGA328P will look like this:

int main(void) {
  DDRB |= (1 << 2);
  while (1) {
    PORTB |= (1 << 2);
    PORTB &= ~(1 << 2);
  return 0;

DDRB and PORTB are both macros and are referring to a register on the ATMEGA328P.

This example above sets PB2 as an output and toggles it on and off. Read by someone experienced in C or C++, but with no experience with the AVR, this code will probably not make any sense. What is DDRB, and why do we write a value to PORTB?

DDRB and PORTB are macro’s and converted by the preprocessor to a register address. DDRB is address 0x04 and PORTB is 0x05. See page#100 of the datasheet for more information:

But why use macro names that are so hard to understand. Often I hear that a good written code does not need any comments since the variables should be clear to understand.

Here comes the beauty of these new 0- and 1-series micro-controllers where almost every register and every variable going into those registers has a label or name that makes more sense. You can find all of these labels in the C:\Users\username\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino5\avr\include\avr folder and just compare IOM4809.IO (229KB for ATMEGA4809) vs iom328p.h (20KB for ATMEGA328P). Here is a simple example of the same code above for the ATtiny1616:

int main(void)
  While (1) {
      PORTB.OUTSET = PIN2_bm;
      PORTB.OUTCLR = PIN2_bm;    

It is a little bit easier to read. DIRSET set the direction of PIN2 of PORTB followed by setting and clearing the output with OUTSET and OUTCLR using PIN#2 bitmask.  

See this blog for some more information:  

Everything we do on the micro-controller is done by setting bits in registers. As you can see above you can shift bits in and out or use macros. Macros are not slowing down your code or increasing your compiled code, because the values for the macros replaced in your code by the preprocessor before it will be compiled. These macros are getting very helpful when you work with the registers for peripherals.

The addresses of registers for peripherals can be found in the datasheet for the ATtiny1616 on page#44 (

You will see the base address in the first column and a description in the second. This is just a base address, a lot more registers for these peripherals after this base address could be reserved to store data, for example PORTMUX has a base of 0x0200. Going to page#138 shows the registers following this PORTMUX baseline of 0x0200 and the needed offset. For example, CTRLB has an offset of 0x01, so it has the address of 0x0200 + 0x01 = 0x0201.

In this register you can set the alternative pin locations for SPI0 and USART0. Just shifting in a bit to turn on the first bit will activate USART0 on the alternative pin. Instead if using bit shifts, there are actually macros created for each possible combination, which will result in the following code:

PORTMUX.CTRLB|= PORTMUX_USART0_DEFAULT_gc; // does this -> (0x0200 + 0x01 |= (0x00<<0) 

I recommend looking at the log of Simon’s project  since it has some other great examples of how to use the macros on this new 0- and 1-series AVR’s as well: