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:
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:
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:
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