Creiamo una libreria in C

si ma no. Nel senso, sai la posizione del dato nella struttura(offset del dato), quindi "indirizzo dato"-"offset" = "indirizzo struttura". Però poi se cambi la struttura crei casini, senza contare l'illeggibilità del codice.
IMHO è sbagliato il fatto che alla funzione passi il dato: passagli tutta la struttura!

Si al di la del fatto che è sbagliato, se voglio risalire alla struttura dal dato come faccio? l'unica che mi viene e quella di operare l'aritmetica dei puntatori, ma questa cosa funziona solo se la struttura contiene variabili stivate in ram in sequenza, che è la regola poi, solo che a me questa regola stava stretta perchè volevo (per risparmiare) fare in modo che ogni campo punta ad un indirizzo di memoria nel quale è conservato il valore del registro. Insomma una magia che la struct non mi permette di fare.

non capisco perche' non la fai piu' semplice e non fai hardcoding: hai 4 porte gpio, usa le define per definire i dettagli che servono a 4 gruppi di operazioni, e per ogni operazione definisci una function, quindi per ogni gpio hai una set, una get, una init, ognuna hai parametri hardcoded definiti at the compile time dalla rispettiva define. Semplice semplice, altamente performante, specialmente se fai richiesta di inline.

Si però tu usi termini tecnici che a me non risultano di comprensione lampante. Cioè hardcoding, cosè?
Comunque il discorso della struct ho capito che non porta benefici su micro con poche risorse RAM, ed è poco efficiente nei confronti della RAM proprio perchè i registri sono già mappati in RAM e con una struct devo contenere campi puntatori a questi indirizzi per cui migliora forse la leggibilià del codice che tra l'altro risulta coeerente a tutti i micro ma occupo memoria e perdo pure rapidità di esecuzione (diciamo che siamo la).

Al momento ho giocato con le macro abusandone.

#include <avr/io.h>
#include "mio.h"

BEGIN {
1    gpio_mode(GPIO_B0, mode_output);
2    gpio_on(GPIO_B0);
3    volatile uint8_t *out_b0 = get_out(GPIO_B0);
4    *out_b0 _bit_unset(PB0);
5    T16_1_SET_PRESCALER(T16_1_PRESCALER_256);
6    T16_1_ENABLE_CLOCK;
7    T16_1_DISABLE_CLOCK;

END }

In mio.h ci sono macro e inline. Singolare il codice a linea 3 e 4. dove get_out è una macro e ritorna il puntatore al registro PORTB chiamato out. Ancora più singolare la macro _bit_unset perchè la sintassi e fuori dal C/C++, cioè prima scrivo la variabile su cui voglio operare e poi l'operazione che voglio compiere su questa.

1 imposta il bit 0 di porta B in modo output
2 accende il bit 0 di porta Bù
3 ricavo il valore di port B
4 spengo il bit 0 di port B e lo faccio in altro modo
5 imposto il prescale per il timer1 a 256 ma non lo avvio qui
6 abilito il timer1
7 disabilito

Queste sono le define per impostare un bit

#define _bit_set(bit) |= _BV(bit)
#define _bit_unset(bit) &= ~_BV(bit)

Sono alle prese con i timer e visto le mie ridotte risorse mentali faccio fatica a memorizzare tutti i registri coinvolti, cacchio il timer è un gatto a nove code.

Ciao