Electronic – Gosh darn PORT structs on AVR

attinyavr

To my shock, Atmel has restructured the way they do port addressing in the ATtiny-avr-one series (1616 for example.). I am writing a function that takes in a pin and a port and sets the corresponding pin control variable to have a pullup. The problem is, a PORT struct is defined as follows:

typedef struct PORT_struct {
register8_t DIR;  /* Data Direction */
register8_t DIRSET;  /* Data Direction Set */
register8_t DIRCLR;  /* Data Direction Clear */
register8_t DIRTGL;  /* Data Direction Toggle */
register8_t OUT;  /* Output Value */
register8_t OUTSET;  /* Output Value Set */
register8_t OUTCLR;  /* Output Value Clear */
register8_t OUTTGL;  /* Output Value Toggle */
register8_t IN;  /* Input Value */
register8_t INTFLAGS;  /* Interrupt Flags */
register8_t reserved_0x0A;
register8_t reserved_0x0B;
register8_t reserved_0x0C;
register8_t reserved_0x0D;
register8_t reserved_0x0E;
register8_t reserved_0x0F;
register8_t PIN0CTRL;  /* Pin 0 Control */
register8_t PIN1CTRL;  /* Pin 1 Control */
register8_t PIN2CTRL;  /* Pin 2 Control */
register8_t PIN3CTRL;  /* Pin 3 Control */
register8_t PIN4CTRL;  /* Pin 4 Control */
register8_t PIN5CTRL;  /* Pin 5 Control */
register8_t PIN6CTRL;  /* Pin 6 Control */
register8_t PIN7CTRL;  /* Pin 7 Control */
register8_t reserved_0x18;
register8_t reserved_0x19;
register8_t reserved_0x1A;
register8_t reserved_0x1B;
register8_t reserved_0x1C;
register8_t reserved_0x1D;
register8_t reserved_0x1E;
register8_t reserved_0x1F;} PORT_t;

The datasheet specifies the memory offset of the port registers:
Port Registers

And the PORTn macros, are declared as follows:

#define PORTA  (*(PORT_t *) 0x0400)
#define PORTB  (*(PORT_t *) 0x0420)
#define PORTC  (*(PORT_t *) 0x0440)

What does the pointer and the dereference together do? More importantly, how can I write a function (without using switch statements) that takes in a port macro and a pin number (like 1 or 4 for example) and sets the pincontrol register?

Best Answer

As an example, let's assume that you want to enable the pull-up for pin 0 of port A.

You need to set bit 3 (the fourth bit) at address 0x0410 to 1:

(*(unsigned char*) 0x0410) |= (1<<3);  

Why? PULLUPEN is bit 3 within the register PIN0CTRL, so (1<<3) shifts 1 (0b00000001) left three places (yielding 0b00001000). 0x0400 is an integer constant representing an address; the address of the first register in the set of registers related to PORTA (also known as the base address of PORTA). Within that set of registers, PIN0CTRL is located 16 bytes after the first register, an offset of 16 (or 0x10 in hex) relative to the base address. The address of the PIN0CTRL register for PORTA is thus 0x0400 + 0x10 = 0x0410.

Since 0x0410 is just an integer constant, you can't write to it as is. To get the compiler to treat it as an address to a byte, you need to cast it to a pointer with (unsigned char*) 0x0410, which you can then dereference and assign a value to with (*(unsigned char*) 0x0410) |= (1<<3);

What does the pointer and the dereference together do?

Atmel/Microchip has provided a bit of syntactic sugar to make this nicer, leveraging how a pointer to a struct object always points to the address of the first element of the struct. Interpreting the first register of PORTA as the first member of a suitably crafted struct, the second register as the second member and so on essentially maps the registers to conveniently named struct elements.

To use the struct yourself, you can cast the base address of PORTA to a PORT_t struct pointer,

(PORT_t *) 0x0400  

dereference the struct

(*(PORT_t *) 0x0400)  

and assign a value to the element PIN0CTRL (which is by design at the matching offset of 16 within the struct) with

(*(PORT_t *) 0x0400).PIN0CTRL |= (1<<3);  

The PORTn macros are designed to hide this complexity, so that you can write just

PORTA.PIN0CTRL |= (1<<3);  

To package this into a function

inline void enablePullup(PORT_t *port, const char pin)
{
    (&port->PIN0CTRL)[pin] |= (1<<3);
}
enablePullup(&PORTA, 0); //pass address of PORTA struct instance (0x0410) to function