Electronic – arduino – How to access GPIO pins on Atmega with memory mapping

arduinoatmega328patmelavrc

I'm programming the Atmega328P in embedded C. I'm going to use 16 pins for turning on LEDs, and I have to use PORTB, PORTD and PORTC for this. I would like to just iterate a pointer so I can turn them on/off instead having to deal with what port each pin relates to. So an example would be how I need three if statements to iterate through the 16 pins like shown below:

// Enable outputs
DDRB = 0b00111111; // 6 outputs
DDRC = 0b00110000; // 2 outputs
DDRD = 0xFF;       // 8 outputs

// LED pins
uint8_t ledpin[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
uint8_t idx = 0;

// Set LEDs
if (idx < 8)
    for(int i = 0; i < 8; i++)
        PORTD |= (1 << ledpin[i]);

if( idx > 8 && idx < 14)
    for(int i = 8; i < 14 ; i++)
        PORTB |= (1 << ledpin[i]);

if (idx >= 14)
    for(int i = 14; i < 16; i++)
        PORTC |= (1 << ledpin[i]);

I would rather use a memory map where I could just say "pin0 address + offset" like this:

// Base address of ports
uint8_t base 0x00
uint8_t offset = 0x04;

// LED pins
uint8_t ledpin[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

// Set LEDs
for(int i = 0; i < 16; i++){
    (base + offset) |= (1 << ledpin[i]); // Pseudo code
     offset += 0x04;
}

The code above is obviously not optimized, nor totally functioning code but I think it gets my point accross as to what I want to achieve. Ultimately I will use this in a code to control a LED cube which will work quite differently, but it's essential that I don't need to decode what port each pin belong to all the time. Is there a good way to solve this with virtually no overhead?

EDIT

After investigating and trying out a lot of stuff with good help from the answers I have found that it's not possible to address individual bits in the AVR ports. Each PORTB/C/D have their own address, but it's not possible to manipulate the bits with memory mapping, like incrementing a pointer address. Atmega328P datasheet page 280 shows the memory map of the IO ports (image below).

enter image description here

Best Answer

Is there a good way to solve this with virtually no overhead?

There is no practical way to get 'virtually no overhead' in C on AVR. Code that looks like it should have low overhead often doesn't, and you have to look at the disassembly to see the real code.

AVR has a RISC architecture that needs many instructions to do 'simple' operations. IN, OUT, and SBI/CBI (bit set/clear) instructions take literal arguments only, which must be 'hard-coded' at compile time. But there is no barrel shifter, so bit masks computed at run time can take many instructions to produce. Pointers are made from 8 bit registers that are loaded one byte at a time, so pointer operations may use more cycles than port I/O with conditional code.

Here's the best I have managed to produce so far (It's basically the same as your code except the pin masks are precomputed, and it's wrapped in a function instead of inlined)...

// LED pin masks
#define LP0  1<<0
#define LP1  1<<1
#define LP2  1<<2
#define LP3  1<<3
#define LP4  1<<4
#define LP5  1<<5
#define LP6  1<<6
#define LP7  1<<7
#define LP8  1<<0
#define LP9  1<<1
#define LP10 1<<2
#define LP11 1<<3
#define LP12 1<<4
#define LP13 1<<5
#define LP14 1<<0
#define LP15 1<<1

// pin mask array
uint8_t ledpin_mask[] = {LP0,LP1,LP2,LP3,LP4,LP5,LP6,LP7,LP8,LP9,LP10,LP11,LP12,LP13,LP14,LP15};

// turn on LED
void setled(uint8_t pin)
{
  if (pin < 8)
  {
       PORTD |= (ledpin_mask[pin]);
  }
  else if ( pin < 14)
  {
       PORTB |= (ledpin_mask[pin]);
  }
  else
  {
       PORTC |= (ledpin_mask[pin]);
  }
}

...which generates the following machine code:-

void setled(uint8_t pin)
{
  96:   28 2f           mov r18, r24
  98:   30 e0           ldi r19, 0x00   ; 0
if (pin < 8)
  9a:   88 30           cpi r24, 0x08   ; 8
  9c:   40 f4           brcc    .+16        ; 0xae <setled+0x18>
{
     PORTD |= (ledpin[pin]);
  9e:   9b b1           in  r25, 0x0b   ; 11
  a0:   f9 01           movw    r30, r18
  a2:   e0 50           subi    r30, 0x00   ; 0
  a4:   ff 4f           sbci    r31, 0xFF   ; 255
  a6:   80 81           ld  r24, Z
  a8:   89 2b           or  r24, r25
  aa:   8b b9           out 0x0b, r24   ; 11
  ac:   08 95           ret
}
else if ( pin < 14)
  ae:   8e 30           cpi r24, 0x0E   ; 14
  b0:   40 f4           brcc    .+16        ; 0xc2 <setled+0x2c>
{
     PORTB |= (ledpin[pin]);
  b2:   95 b1           in  r25, 0x05   ; 5
  b4:   f9 01           movw    r30, r18
  b6:   e0 50           subi    r30, 0x00   ; 0
  b8:   ff 4f           sbci    r31, 0xFF   ; 255
  ba:   80 81           ld  r24, Z
  bc:   89 2b           or  r24, r25
  be:   85 b9           out 0x05, r24   ; 5
  c0:   08 95           ret
}
else
{
     PORTC |= (ledpin[pin]);
  c2:   98 b1           in  r25, 0x08   ; 8
  c4:   f9 01           movw    r30, r18
  c6:   e0 50           subi    r30, 0x00   ; 0
  c8:   ff 4f           sbci    r31, 0xFF   ; 255
  ca:   80 81           ld  r24, Z
  cc:   89 2b           or  r24, r25
  ce:   88 b9           out 0x08, r24   ; 8
  d0:   08 95           ret

That's 12 or 14 machine code instructions executed depending on the port, which is probably about as close as you can get to 'virtually no overhead' without coding in optimized assembler.

Related Topic