The First Tutorial,
was about getting started with Keil uVision using a bare-minimum
programing infrastructure.
This tutorial will demonstrate some other low-level aspects of
embedded programing while using Keil uVision as the IDE (integrated
development environment) and ARM processor as the target processor.
This tutorial will demonstrate how the Keil compiler uses memory to
store variables, constants or code. This can be important for
embedded software engineers, as memory is precious.
Consider the following C program:
typedef unsigned int uint32_t;
int main ()
{
int ii;
uint32_t avar[10] =
{0xaaaaaaaa,0xbbbbbbbb,0xcccccccc,0xdddddddd,0xeeeeeeee,0xffffffff,0xaabbaabb,0xccddccdd,0xeeffeeff,0xabcdefaa};
uint32_t *ap; //pointer pointing to first
element of vector avar.
uint32_t bvar[10] = {1,2,3,4,5,6,7,8,9,0xa};
uint32_t *bp; //pointer pointing to first
element of vector bvar.
ap = &avar[0]; //myp is pointing to the
address of myp
bp = &bvar[0];
const uint32_t maxc = 0xabcdefaa;
for(ii=0;ii<10;ii++)
{
asm("NOP");
*((uint32_t *) (0x20000000))
= maxc;
*bp = *ap;
ap++;
bp++;
asm("NOP");
}
while(1);
}
It has variables namely ii, avar, bvar.
It has pointer variables *ap, *bp.
It has const called 'maxc'.
Note that the variables have been initialized to constant values.
The program does the following 2 things:
1. It writes the location 0x2000_0000 with a value of the constant
'maxc'. User is advised not to use these kind of hard coded
addresses. Here it is only done for demonstration purposes only.
This address might be reseved for other other things and therefore
the execution of main program may get corrupted.
The program copies the variable ap into variable bp.
Compile it in Keil: To get an axf file, the user can use the same startup.s
file as introduced in First Tutorial,
and use the above C code. Then 'build' the target and use fromelf
utility to convert the 'axf' file into its text version:
The following is the extract from the text version of the resulting
axf file:
i.main
main
0x00000114:
b094
.. SUB
sp,sp,#0x50
0x00000116:
2228
(" MOVS
r2,#0x28
0x00000118:
490e
.I LDR
r1,[pc,#56] ; [0x154] = 0x15c
0x0000011a:
a80a
.. ADD
r0,sp,#0x28
0x0000011c: f7ffffb8
.... BL
__aeabi_memcpy4 ; 0x90
0x00000120:
2228
(" MOVS
r2,#0x28
0x00000122:
490c
.I LDR
r1,[pc,#48] ; [0x154] = 0x15c
0x00000124:
3128
(1 ADDS
r1,r1,#0x28
0x00000126:
4668
hF MOV
r0,sp
0x00000128: f7ffffb2
.... BL
__aeabi_memcpy4 ; 0x90
0x0000012c:
ae0a
.. ADD
r6,sp,#0x28
0x0000012e:
466d
mF MOV
r5,sp
0x00000130:
bf00
.. NOP
0x00000132:
2400
.$ MOVS
r4,#0
0x00000134:
e00a
..
B 0x14c ; main + 56
0x00000136:
bf00
.. NOP
0x00000138:
4807
.H LDR
r0,[pc,#28] ; [0x158] = 0xabcdefaa
0x0000013a: f04f5100
O..Q MOV
r1,#0x20000000
0x0000013e:
6008
.` STR
r0,[r1,#0]
0x00000140:
6830
0h LDR
r0,[r6,#0]
0x00000142:
6028
(` STR
r0,[r5,#0]
0x00000144:
1d36
6. ADDS
r6,r6,#4
0x00000146:
1d2d
-. ADDS
r5,r5,#4
0x00000148:
bf00
.. NOP
0x0000014a:
1c64
d. ADDS
r4,r4,#1
0x0000014c:
2c0a
., CMP
r4,#0xa
0x0000014e:
dbf2
.. BLT
0x136 ; main + 34
0x00000150:
bf00
.. NOP
0x00000152:
e7fe
..
B 0x152 ; main + 62
$d
0x00000154: 0000015c
\... DCD 348
0x00000158: abcdefaa
.... DCD 2882400170
$d.realdata
.constdata
0x0000015c: aaaaaaaa
.... DCD 2863311530
0x00000160: bbbbbbbb
.... DCD 3149642683
0x00000164: cccccccc
.... DCD 3435973836
0x00000168: dddddddd
.... DCD 3722304989
0x0000016c: eeeeeeee
.... DCD 4008636142
0x00000170: ffffffff
.... DCD 4294967295
0x00000174: aabbaabb
.... DCD 2864425659
0x00000178: ccddccdd
.... DCD 3437087965
0x0000017c: eeffeeff
.... DCD 4009750271
0x00000180: abcdefaa
.... DCD 2882400170
0x00000184: 00000001
.... DCD 1
0x00000188: 00000002
.... DCD 2
0x0000018c: 00000003
.... DCD 3
0x00000190: 00000004
.... DCD 4
0x00000194: 00000005
.... DCD 5
0x00000198: 00000006
.... DCD 6
0x0000019c: 00000007
.... DCD 7
0x000001a0: 00000008
.... DCD 8
0x000001a4: 00000009
.... DCD 9
0x000001a8: 0000000a
.... DCD 10
Region$$Table$$Base
0x000001ac: 000001bc
.... DCD 444
0x000001b0: 20000000
... DCD 536870912
0x000001b4: 00000400
.... DCD 1024
0x000001b8: 00000044
D... DCD 68
Region$$Table$$Limit
Things to Notice:
1. All the initialization constant values for avar and bvar are
stored in a section named '.constdata' which is placed next to
'main' code section.
2. Not so obvious is the storage of variables? These are all local
variables, and are stored on the stack, as it will be evident from a
debug of the above code.
BTW the default location of stack in the memory will be from
0x2000_0000 to 0x2000_0400, when the 'Stack_Size' is made 'EQU' to
0x00000400, as done in the startup.s
assembly code.
This will make the __initial_sp to point to 0x2000_0400, and the
lower limit of stack be 0x2000_0000.
To see the variables storage, start debugging your program as
instructed in First
Tutorial:
Put a break point just inside the for loop, and click on 'Run' icon
or press F5
Now mouse-over on the pointer variables 'ap' or 'bp'. Remember 'ab'
and 'bp' are both pointers to variables 'avar' and 'bvar'
respectively, so they should be storing the addresses of
'avar' and 'bvar' respectively.
Mouse over 'avar' reveals '0x2000_03DB'
Mouse over 'bvar' reveals '0x2000_03B0'
To confirm Click on 'Memory 1' tab near to bottom right of your Keil
window, and type in the address 0x20000300 to see the contents of
memory.
You will see the following:
3. The variable ii does not occupy any memory. Its in the register
r4 as shown below:
0x0000014a:
1c64
d. ADDS
r4,r4,#1
0x0000014c:
2c0a
., CMP
r4,#0xa
It can be observed from the above code that the variable ii is in
register r4, and its compared with an immediate constant 0xa
(decimal 10).
The constant values of '0xAAAAAAAA',
'0xBBBBBBBB' , '0xCCCCCCCC' are clearly visible at the memory
location pointed by the 'ap' pointer.
The user may keep on stepping through the program code, to observe
the memory contents pointed to by the pointer 'bp' changing, as it
is copied from 'avar'.
What happens when the user declares a global variable, i.e. a
variable which is not local to 'main'?
For example edit the C-code shown above and add a variable 'jj' just
before the 'main()' function, and use 'jj', instead of 'ii' in the
for loop
typedef unsigned int uint32_t;
int jj;
int main ()
{
int ii;
uint32_t avar[10] = ........
These variables are not stored in the stack region, but by default
they are stored at the start of R/W region, which is 0x2000_0000 by
default for Cortex M targets in Keil.
So the jj will be stored at the location 0x2000_0000.
This will also push the stack lower limit to 0x2000_0004, instead of
0x2000_0000.
Key Learnings:
- The local variables, i.e. the variables declared within the
'main()' function of the c-program are stored on the 'Stack'
- The constants/literals are stored in a section just following
the code.
- The program code and the constants are stored in memory
locations which are 'Read-Only' and 'Execute'
- The local variables are stored in memory locations which are
'Read-Write'
- Loop Variables may be stored in processor's registers.
- The global variables, or the variables declared outside the
main are stored at the start of RW memory region which is by
default 0x2000_0000 in Keil for Cortex- M3/M4 processors. It may
be the same for more of cortex-m processors, but the user is
advised to check it.
Following is a view where certain things from c-code are mapped to
assembly-code.
This is the end of this tutorial exercise:
However an interesting question arises:
The constants were stored in memory location just following the
'main' program code. i.e at locations 0x0000_015c to 0x0000_01a8,
But when the 'main' program runs, it can be observed that the
constant values were already copied to the memory locations pointed
by the pointers 'ap' and 'bp', that is to say that the memory
locations pointed to by the pointers 'ap' and 'bp' were already
'initialized'.
So how do the constant values from the Read-Only region of memory
gets copied into the 'Read-Write' region of memory?
<= PREV
NEXT =>