If you want the code to be equivalent to DDRA |= (1 << PA1);
- i.e. the simplest instruction that made at compile time without reading/writing arrays and pointers to IO registers. You may do it like this.
1) Let's assume we have definded somewhere (e.g. thru <avr/io.h>
)
#define PA1 1
#define PA2 2
...
#define DDRA _SFR_IO8(0X01)
#define PB1 1
#define PB2 2
...
#define DDRB _SFR_IO8(0X01)
2) You want to have some kind of declaration like this:
#define BIG_RED_LED PA1
#define SMALL_GREEN_LED PB2
for then just to use them like
setAsOutput(BIG_RED_LED);
setAsOutput(SMALL_GREEN_LED);
setLow(BIG_RED_LED);
setHigh(SMALL_GREEN_LED);
etc., where each line is a simple write to a BIT in corresponding IO register.
To achieve that you can define tons of
#define DDR_PA0 DDRA
#define PORT_PA0 PORTA
#define PIN_PA0 PINA
#define DDR_PA1 DDRA
#define PORT_PA1 PORTA
#define PIN_PA1 PINA
...
#define DDR_PB0 DDRB
#define PORT_PB0 PORTB
#define PIN_PB0 PINB
...
and then
#define setAsOutput(px) { DDR_ ## px |= (1 << px); }
#define setHigh(px) { PORT_ ## px |= (1 << px); }
#define setLow(px) { PORT_ ## px &= ~(1 << px); }
etc.
then, each time in your code happened something like setAsOutput(PA1)
it will be compiled exactly the same as DDRA |= (1 << PA1);
But
if you want to store them in the array and access by array index, as it is in your example, then you have no other way except as defining two arrays, or array of structs, where both elements will contain the bit number, or bit mask, and the pointer to the IO/register.
Since, although name PA1
PA2
etc. have A
letter in it, at the runtime it will be compiled into it's value. I.e. 'PA1' will be 1, but also PB1
will be 1 too. So it is no way for the compiler to know which register is accessed, considering only the index inside that array.
But here I can give you several little life-hacks:
1) since registers PINx, DDRx, PORTx are almsot always going in succession in that order (refer to the register set summary in the datasheet), you do not need to store them all, it is enough to store only reference to PINx register, and calculate location of DDRx and PORTx just adding 1 or 2 to the address, since AVR have instructions to inderect memory access with displacement, the code will be effective enough.
2) those registers are located in the lower memory addresses, so instead of storing 2/4-byte pointers you can cast them to byte
and cast them back to pointer when accessing. It will not only save space but also speed up. Also it is always a good practice to store that kind of tables in the flash memory, instead of wasting RAM.
3) AVR architecture has only one position bit shifting instructions, so (1 << x) where x is not known at the compile time - is compiled as a loop, which may be the part requiring the most of time in such kind of code. So, instead of storing uint8_t FOO[] = {PA1, PA2};
you may want to store uint8_t FOO[] = {(1 << PA1), (1 << PA2)};
- i.e. precalculated mask values.