Arduino memory architecture

Hi
I am a CS student, learning computer architecture and assembly language. I’m using the arduino microcomputer to practice and learn more about how data is stored in memory.
The arduino microcomputer (at least the arduino UNO) has 3 memory spaces: Program Memory (flash), data memory(register/RAM) and configuration memory(EEPROM). Anyway, I bumped into a strange behavior while saving and trying to retrieve data stored in Program memory. Here is a simplified version of the code I was practicing with:

#include <pgmspace.h>

PROGMEM char array1[] = {"Array 1"};

void setup() {
	Serial.begin(9600);

       // proper way to retrieve data from program memory. This works just fine!, printing "Array 1"
	char c;
	for(char* ptr = array1 ; (c = pgm_read_byte(ptr)) != NULL ; ptr++)
		Serial.print(c);

	Serial.println();
 
        // the code bellow should not work since a pointer should only allow me to access data memory
        // and array1 in stored in program memory. But it works to some extent!! (I explain bellow!)
	int k = 0;
	for(char* ptr = array1; k < 3; ptr++,k++)
		Serial.print((char)*ptr);
} 

void loop() {
}

I don’t know how, but the second for-loop will work for up to the first 3 bytes in my array1 array. The second for-loop will actually print “Arr”, but as soon as I try to print the fourth byte in my array1 (that would be the character ‘a’ in this case) the code doesn’t print “Arr” or “Arra” instead it prints all garbage data, which I kind of expect.

array1 is supposed to be located in program memory only, and the proper way to retrieve data from program memory is to use functions from pgmspace.h, like I did in the first for-loop.

My question is why/how is the code on the second for-loop able to retrieve the first 3 bytes (“Arr”) while using a “data-pointer”.

I hope I was not too confusing with my question but I figure I may be able to get an answer if I asked here.

Thank You!!!

I am guessing here, but you may be picking up stuff from the stack.

Try transposing the loops (second moved to before first) and see if it still works the same way.

Hi,

Thanks for replying so quickly.

I tried transposing the loops(second moved to before first) and I got the same behavior.

This time it printed:
"Arr"
"Array 1"

I look forward to someone else being able to explain this then. We will both learn something :slight_smile:

Did you study this:
http://arduino.cc/en/Reference/PROGMEM

I really don’t care what is printed in the second for-loop, and you should also not care about it. It’s wrong, and that’s all. There is no learning experience, or perhaps there is: you should learn not even to think about something like that :slight_smile:

If you want to know what is going on, you could let the compiler output the assembly code. I don’t know how to do that in the Arduino IDE. And perhaps even that would not give a final answer.

Perhaps we should give this kind of questions a name. Questions like: "I did something wrong, and now the result is wrong, what should I do? My answer is ofcourse: “Do it right”.

I did study : PROGMEM - Arduino Reference , as you probably can tell by looking at the first loop, which is a proper to retrieve data from program memory. It would be nice if I could have the compiler output the assembly code but I can't do that with the arduino IDE. This behavior is strange. The code in the second for loop should not be able to access program memory at all. I know how to do it right (Read the code again). If you don't know the answer then you don't have to answer or give you opinion about how you feel about it. Some other people may be interested in understanding this behavior.

Thank you!

But I do have to give my opinion as a programmer : Stay away from wrong, because wrong is not good.

Well, the pointer has to point somewhere; AVR doesn’t do any “invalid memory location” trapping.
When I compile the snippet as show, it looks like array1 ends up at about address 0x64 (in flash.) That’s in the middle of SFR space on a 328; I don’t know why you’d get data that looks like your string (is it still there if you change the string?) If your actual sketch is different, the location of array1 might be different as well. Use “avr-nm” to see the symbol values…

If you’re still interested in this here’s some reasoning. Not sure I’m 100% correct everywhere but hopefully it’ll make some sense at least

It appears that if the “for” loop has 3 or less iterations the compiler optimizes this to 3 sequential in-line calls otherwise it produces actual loop code.

Here’s the relevant parts of the in-line code

  fc:	88 e9       	ldi	r24, 0x98	; 152
  fe:	91 e0       	ldi	r25, 0x01	; 1
 100:	61 e4       	ldi	r22, 0x41	; 65
 102:	0e 94 c7 02 	call	0x58e	; 0x58e <_ZN5Print5printEc>
 106:	88 e9       	ldi	r24, 0x98	; 152
 108:	91 e0       	ldi	r25, 0x01	; 1
 10a:	62 e7       	ldi	r22, 0x72	; 114
 10c:	0e 94 c7 02 	call	0x58e	; 0x58e <_ZN5Print5printEc>
 110:	88 e9       	ldi	r24, 0x98	; 152
 112:	91 e0       	ldi	r25, 0x01	; 1
 114:	62 e7       	ldi	r22, 0x72	; 114
 116:	0e 94 c7 02 	call	0x58e	; 0x58e <_ZN5Print5printEc>

See the loading of r22 before each call to Serial.print. It’s simply coded the “Arr” (0x41, 0x72, 0x72)as immediate values i.e. it’s not reading the Arr from ANY memory - it’s right there as immediate data in the code.

Now when you increase the loop to > 3, it no longer optimizes to in-line and instead produces the loop code you might expect.

  fc:	c8 e6       	ldi	r28, 0x68	; 104
  fe:	d0 e0       	ldi	r29, 0x00	; 0
 100:	88 e9       	ldi	r24, 0x98	; 152
 102:	91 e0       	ldi	r25, 0x01	; 1
 104:	69 91       	ld	r22, Y+
 106:	0e 94 c3 02 	call	0x586	; 0x586 <_ZN5Print5printEc>

The crux here is that it’s using ldi which means it’s loading from data memory and so you get the rubbish you were expecting.

Finally the relevant code for the first loop.

  dc:	c8 e6       	ldi	r28, 0x68	; 104
  de:	d0 e0       	ldi	r29, 0x00	; 0
  e0:	05 c0       	rjmp	.+10     	; 0xec <setup+0x24>
  e2:	88 e9       	ldi	r24, 0x98	; 152
  e4:	91 e0       	ldi	r25, 0x01	; 1
  e6:	0e 94 c3 02 	call	0x586	; 0x586 <_ZN5Print5printEc>
  ea:	21 96       	adiw	r28, 0x01	; 1
  ec:	fe 01       	movw	r30, r28
  ee:	64 91       	lpm	r22, Z+
  f0:	66 23       	and	r22, r22
  f2:	b9 f7       	brne	.-18     	; 0xe2 <setup+0x1a>

See the lpm instruction? this is the read of program memory from the address in r22 offset by Z.

wow, yaafm + 1

Nice analysis! I had noticed the inlining of the loop, but I didn't notice that it was loading "constant" values rather than dereferencing the pointer. I wonder if this would count as a compiler bug? (not "current", though. My understanding is that the handling of multiple data spaces has changed quite substantially in the latest versions of gcc.)

Good Stuff!! Very nice analysis!!! Thank You!! Thumbs Up