Are pointers treated differently in AVR compared to say x86?

Microcontroller: ATtiny13

IDE: Atmel Studio

I'm trying to write a Hello World application by writing high to port PB4.

This works fine:

int main(void)
{
DDRB = 0x10;
PORTB = 0x10;

while(1)
{
}
}


Looking at the definition of DDRB and PORTB, they point to 0x17 and 0x18, as expected.

This, however, doesn't work:

int main(void)
{
char *dir = (void *)0x17;
char *port = (void *)0x18;

*dir = (unsigned int)0x10;
*port = (unsigned int)0x10;

while(1)
{
}
}


Is my code wrong, or do I need to do something else to use pointers?

-
Why would you use pointers for something that simple? –  Matt Young Jul 10 at 14:30
Got to start somewhere - first time programming an AVR mcu. –  tgun926 Jul 10 at 14:31
Comparing the disassembly of both would be a good excercise. –  Matt Young Jul 10 at 14:32
The unsigned int casts don't make sense; The data direction and port registers are only 8bits wide. It's quite unlikely to be your issue, but try casting uint8_t. –  sherrellbc Jul 10 at 15:00
@sherrellbc I've tried different types of casting and no casting, but it didn't make a difference. 0x10 is 8 bits wide, isn't it? –  tgun926 Jul 10 at 15:02

Pointers are pointers. That is what they are. They aren't treated differently at all (how could you treat them differently?)

The main differences between X86 and AVR are:

• AVR is 8 bit, X86 is 32-bit (or 64 bit for x86_64), so pointers are a different size.
• The AVR is Modified Harvard architecture, so there is more than one address space, so you have to ensure you are referencing the right address space.

Also, your code makes no sense:

char *dir = (void *)0x17;


Assign a void * to a char *?

error: invalid conversion from ‘void*’ to ‘char*’


I'm not set up for compiling the ATTiny13 at the moment, so these numbers are all for the ATMega328p:

Accessing DDRB and PORTB results in this assembly:

12c:    80 e1           ldi r24, 0x10   ; 16
12e:    84 b9           out 0x04, r24   ; 4
130:    85 b9           out 0x05, r24   ; 5


Accessing a pointer to a memory location results in this assembly:

12c:    80 e1           ldi r24, 0x10   ; 16
12e:    80 93 17 00     sts 0x0017, r24
132:    80 93 18 00     sts 0x0018, r24


As you can see, DDRB and PORTB aren't normal variables. DDRB is defined as:

#define DDRB _SFR_IO8(0x04)


and PORTB as:

#define PORTB _SFR_IO8(0x05)


_SFR_IO8() is a macro:

#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)


and _MMIO_BYTE is:

#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))


__SFR_OFFSET can be either 0 or 0x20 depending on chip (normally 0x20).

So, that must mean that the addresses of DDRB and PORTB must be more than 0x20.

Looking at the 328P chip, DDRB is 0x04 and PORTB is 0x05. So, accessing as 0x24 and 0x25, with the right data types thus:

volatile uint8_t *dir = (volatile uint8_t *)0x24;
volatile uint8_t *port = (volatile uint8_t *)0x25;

*dir = 0x10;
*port = 0x10;


results in this assembly:

12c:    80 e1           ldi r24, 0x10   ; 16
12e:    84 b9           out 0x04, r24   ; 4
130:    85 b9           out 0x05, r24   ; 5


Look familiar? The compiler has recognized the 0x20 offset, realized they're SFRs, and compiled in the right out instructions without the 0x20 offset.

So, accessing your port addresses + 0x20 may work for the ATTiny13.

Just looking at the ATTiny25, the DDRB = 0x10 results in:

out 0x17, r24


and accessing a pointer at address 0x37 results in:

out 0x17, r24


So that looks like it is probably it (add 0x20 to your pointer address).

-
What do you mean there are more than one address space? I've looked at the docs for the mcu, and it shows one memory map from 0x0 to 0x9F which is used to access the registers, I/O and SRAM. What are the other address spaces? –  tgun926 Jul 10 at 14:54
Harvard architecture defines a minimum of 2 address spaces - program (flash) and data (sram). There are special functions for accessing the program address space. –  Majenko Jul 10 at 15:08
They can get optimized out because the compiler doesn't know that they're being read. If a variable is being set yet never then being read it can discard the write since it isn't used anywhere. By having it volatile it tells the compiler it's being read elsewhere (in the hardware in this case), so to leave the write in place. –  Majenko Jul 10 at 15:23
SFR = Special Function Register. It recognizes them by the address. No idea how, but it does. You should check out the ioxxxx.h file for the chip you're using. It has all the definitions for these registers in there. –  Majenko Jul 10 at 15:30
Even on the 8x86 used in the PC (and the 8080 before it) there were separate instructions for accessing I/O space and memory. Some I/O devices such as display cards use part of the memory address space in addition to some I/O address space, but on the 8x86 there's way to define a pointer to e.g. the LPT1 control register. It has an address (0x378) but writing 0x34 to it in a typical C dialect would require outportb(0x378, 0x34);. –  supercat Jul 10 at 15:45