How do AVR I/O macro definitions work to allow access to registers?

Viewed 492

I was looking at the header of ATmega2560 registers mapping (iom2560.h) where there are all the definitions of the registers. For example:

#define PINA    _SFR_IO8(0X00)
//Macro definition:
#define _SFR_IO8(io_addr) ((io_addr) + 0X20)

So PINA is a hexadecimal value 8-bit corresponding to the address of the 8-bit microcontroller register. When I'm writing code I can change the value inside the register by just by typing the following code:

PINA |= (1 << 3); // Setting the third bit.

Here is the question: Why can I write the register value ("pointed by his address _SFR_IO8(0X00)") by just assign the value to PINA? Isn't it the address to the register that pointed to? How does the compiler works?

Thank you very much in advance

1 Answers

Short answer - hidden away in the included headers from Atmel are a collection of macros that create pointers to the register locations. Here's a brief overview of the process:

Your Makefile defines the device to be used, and then passes the definition to the compiler.

DEVICE = atmega2560
...
-D__$(DEVICE)__

You then include io.h, which automatically includes the neccessary headers based on your device:

// In main source file
#include <io.h>    

// In io.h
#include <avr/sfr_defs.h>
// ...
#elif defined (__AVR_ATmega2560__)
    #  include <avr/iom2560.h>

// In sfr_defs.h
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
#define __SFR_OFFSET 0x20
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)

// In iom2560.h
#include <avr/iomxx0_1.h>
// Other device specific definitions

// Om iomxx0_1.h
#define PINA    _SFR_IO8(0X00)
// Other device family shared definitions

So if you unroll all of that, what you get is a volatile pointer to the register address. When ever you use PINA in your code, the precompiler replaces it with all of the expanded macros:

PINA
_SFR_IO8(0X00)
_MMIO_BYTE((0X00) + __SFR_OFFSET)
(*(volatile uint8_t *)((0X00) + 0x20))

Which specifies that PINA is a pointer to a volatile 8-bit memory address of 0x20. The internal chip architecture then maps that address to the appropriate peripheral register whenever it is accessed.

Related