Step 7: Loading Data Memory

We presented a simple scheme to read a hex file, produced by the AVR tool-chain. However, we did not mention how to load data memory.

In the AVR, there are separate areas for instructions and data, and our simple hex file we examined only included instruction data. We need to set up an experiment to see what the data memory loading looks like.

Flash vs SRAM

The first thing we need to realize is that instruction memory is very different form data memory in the AVR, Instructions are stored in flash memory, which is primarily a read-only area. However, we want out data area to live in normal read/write RAM. The problem with the SRAM in the AVR is that it is volatile, meaning everything in it disappears when we power down the chip.

We want our program to be able to run immediately when we power up, but id the loader put the initialized data directly into the SRAM, it would not survive a restart.

The solution is simple. We load the initialized data into the program space, then copy it to SRAM when we start up the code. We have not been doing this in our simple experiments, and got away wit it due to the fact that the code did not really depend on initial values in the SRAM area.

Compiler to the Rescue

When you have problems figuring out what needs to happen in a chip, a great tool for exploring code is the compiler. We can build a simple experiment, then dump the code and see what happened in assembly language.

Here is an example that will help:

main.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
char data[] = {
    1,2,3,4,5
};

char test[] = "test string";

int main(void) {
    char x = test[0];
    data[0] = x;
}


This code does not do much. It sets up some initialized data then simple copies one byte between two different locations, hopefully both in SRAM.

Here is the listing file obtained by building this code:

dataloader.lst
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

dataloader.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__do_copy_data>:
   0:	11 e0       	ldi	r17, 0x01	; 1
   2:	a0 e0       	ldi	r26, 0x00	; 0
   4:	b1 e0       	ldi	r27, 0x01	; 1
   6:	e4 e2       	ldi	r30, 0x24	; 36
   8:	f0 e0       	ldi	r31, 0x00	; 0
   a:	02 c0       	rjmp	.+4      	; 0x10 <__zero_reg__+0xf>
   c:	05 90       	lpm	r0, Z+
   e:	0d 92       	st	X+, r0
  10:	a2 31       	cpi	r26, 0x12	; 18
  12:	b1 07       	cpc	r27, r17
  14:	d9 f7       	brne	.-10     	; 0xc <__zero_reg__+0xb>

00000016 <main>:
  16:	80 91 00 01 	lds	r24, 0x0100	; 0x800100 <test>
  1a:	80 93 0c 01 	sts	0x010C, r24	; 0x80010c <data>
  1e:	90 e0       	ldi	r25, 0x00	; 0
  20:	80 e0       	ldi	r24, 0x00	; 0
  22:	08 95       	ret

The compiler tells the story. That first block of code with the reference __do_copy_data is where the magic happens.

Let’s examine this code and see what is going on:

;
    ldi     r17, 0x01
    ldi     r26, 0x00   ;X = 0x0100
    ldi     r27, 0x01   ;
    ldi     r30, 0x24   ;Z = 0x0024
    ldi     r31, 0x00   ;
    rjmp    2f      ; while loop
1:  lpm     r0, Z+      ; R0 <- [Z], Z++
    st      X+, r0      ; [X] <- R0, X++
2:  cpi     r26, 0x12   ; test id X is 0x0112 (18 bytes to move)
    cpc     r27, r17    ; (16 bit compare)
    brne    1b      ; loop back if not done

This is a simple copy loop, using some new instructions. Most of this code involves manipulating the 16-bit registers at the top of the register bank. The lpm and st instructions are interesting in that they both refer to those 16-bit registers, and “auto-increment” them after moving data from one area to another. The 16-bit comparison check involves two steps: one to compare the low two bytes, and one to compare the high two bytes. If there was a “borrow” from the low pair, that is included in the high check.

All this code really does does is copy the data from the flash memory to SRAM using pointers stored in the X and Z registers. This happens on startup, nd every time the processor is reset, since the copy code begins at address zero, then drops through into the normal main code.

Hex FIle Example

Here is the hex file produced by the code:

aBink.hex
1
2
3
4
5
6
:1000000011E0A0E0B1E0E4E2F0E002C005900D9262
:10001000A231B107D9F78091000180930C0190E0E3
:0400200080E00895DF
:100024007465737420737472696E6700010203044B
:020034000500C5
:00000001FF

The last three lines show the data being loaded. The first data line shows that the data is loaded at 0x0024 in the instruction memory. It appears that the compiler placed the string in memory first, even though it was declared second. Interesting. If you over-index arrays, this will help you understand what data is being clobbered.

Finding the Data

From this experiment, it appears that the HEX file is not going to help us figure out where the data ended up in our machine. That is unfortunate, but perhaps we can cheat a bit.

The hex file lines clearly have a “break” at the beginning of the data block. The clue was the short line before the data started. Perhaps we can use that fact to locate the beginning of the data area, and extract the data as a separate chunk. Of course, if your program accidentally ends on an even 16-byte boundary, is idea will fail! Oh well.

If we copy the compiler’s code into our assembly code, we can extract the data address from those first few instructions. That is not really clean, but it would let us automate the loading directly from assembly files (properly formed, of course).

The only other trick we can use is to examine the listing file and locate the last byte in the code block, then pass that address as a parameter to the loader file. Ugly, but it will work until we get a better scheme.

For this simulator project, I am going with the second ide, since it properly models what the processor does t load data memory, which is exactly nothing. It is the programmer’s responsibility to load that memory.

Note

Learn something new every day! In this case, all of my previous example assembly language, and many examples from the web, ignored initialized data completely. I skipped over the copy code generated by the compiler. The only program presented in this class that needs data memory at all is the final multi-tasking project, and that one is running