last updated: 2021-05-18
Song of this chapter: Ben Platt > Sing To Me Instead > Share your address
When loading and storing data, there are several addressing methods available in Assembly language. The AVR microcontroller support 13 different address modes for accessing the Flash
and SRAM
.
We distinguish the following addressing modes:
X
, Y
or Z
) and can be changed! The 16 bit address register is often called index register or pointer.The operands (register and constant values) are part of the instruction. These instructions have often an i
for immediate in their opcode and are used with the working registers r16
-r31
.
Examples:
ldi r18,0xA3 ; load immediate 0xA3 (163) to r18
andi r24,254 ; logical and immediate with mask 0b11111110
subi r25,0b00001111 ; subtract immediate r25 = r25 - 15
inc r23 ; r23 = r23 + 1
com r16 ; one's complement of r16
mov r5,r20 ; copy content of r20 to r5
sub r5,r20 ; r5 = r5 - r20
cp r18,r22 ; compare r18 with r22 (same as sub)
We have six different instructions to access the 64 SPR
:
in r19,0x09 ; read PIND to r19 (0x09 = address of PIND)
out PORTD,r20
sbi PORTD,4 ; set bit 4 in SPR PORTD
cbi 0x05,PB7 ; clear bit 7 in PORTD (0x05 address PORTB, PB7 = 7)
sbic PINA,2 ; skip if bit in register cleared
sbis 0x03,PB3 ; skip if bit in register set
Only two instructions (lds
(load direct from data space) and sts
(store direct to data space)) allow direct addressing of the SRAM:
sts 0x0100,r18 ; store content of r18 direct to SRAM
lds r16,0x0AAA ; load value of SRAM address 0x0AAA to r16
The address can be changed, because it is located in a register. It can be easily e.g. incremented in a loop to access all the bytes in a table.
The AVR controller have 6 working register that can be used as 16 bit register to hold the 16 bit addresses. They are called in X
(r26:r27
), Y
(r28:r29
) and Z
(r31:r30
) in AVR Assembly language.
Here an example:
ldi XL,0x02 ;initialise X with the address 0x0102
ldi XH,0x01
With the help of indirect addressing we can address all bytes of the SRAM (GPR
, SPR
, data and stack)!
Two instructions (ld
(load) and st
(store)) allow direct addressing of the SRAM:
st X,r17 ; store content of r17 to data space (address in X)
ld r16,Y ; load content from data space to r16 (address in Y)
There are 12 convenient instructions to automatically increment or decrement the address pointer when storing or loading. Here two examples:
st Y+,r16 ; store content of r16 to SRAM (address in Y) and then increment
; the address pointer Y (Y=Y+1)
ld r16,-X ; first decrement the address pointer X (X=X-1) and the load
; the content of the data memory (address in Y) to r16
Other convenient instructions allow to add a constant displacement (0-63) to the address pointer, and so simplifying the access of tables with data sets.
Here two examples:
std Y+25,r16 ; store the content of r16 to the data memory
; the address is the sum of the address pointer and a
; constant (here 25)
ldd r17,Y+12 ; load the content of the SRAM-address (Y+12) to r17
push
and pop
.As seen in the previous chapter, the address pointer is called stack pointer and resides in the SRAM SPR
data range at the addresses 0x3D
for SPL
, and 0x3E
for SPH
. The stack pointer must be initialised, but is for the rest administrated by the hardware.
We distinguish:
rjmp
and rcall
)jmp
and call
)ijmp
and icall
)lpm
)Let's take a closer look at the last point.
As the Flash is a read only memory, we have only instructions to load data from Flash to a working register lpm
(load program memory). The indirect address has to be stored in Z
. The addressing will be byte-wise!, so the Flash address has to be multiplied by 2.
Initializing the Z-pointer:
ldi ZL,LOW(TAB*2) ; initialize the address pointer with the
ldi ZH,HIGH(TAB*2) ; Flash address (TAB) * 2
Get data from Flash:
lpm r20,Z ; load the content from Flash (address in Z) to r20
lpm r20,Z+ ; load from Flash (address in Z) to r20 and increment address
In other high-level programming languages then C
(e.g. java) it is normal to create objects at runtime, meaning during the running of the program. The garbage collector will free the memory automatically when it is no more needed by the program. C
has no such garbage collector, so dynamic memory allocation must be done manually. Because this is prone to error, and because of our very limited memory, dynamic memory allocation is not recommended when programming in Arduino.
We have already used arrays in our exercises. Here four examples of creating a static allocated array;
byte my_1_array[] = {1,2,3};
char my_2_array[] = "Hello"; // C-string 0 terminated
char * my_3_array = "Text"; // C-string 0 terminated
byte my_4_array[5];
------------- different_arrays -----------------------
*** byte my_1_array[] = {1,2,3}; ***
sizeof(my_1_array): 3
pointer -> Serial.println(int(my_1_array),HEX): 106
my 1. array: 1 2 3
*** char my_2_array[] = "Hello"; // C-string ***
sizeof(my_2_array): 6
string size -> strlen(my_2_array): 5
pointer -> Serial.println(int(my_2_array),HEX): 100
my 2. array: Hello0
*** char * my_3_array = "Text"; // C-string ***
pointer size! -> sizeof(my_3_array): 2
pointer -> Serial.println(int(my_3_array),HEX): 31C
string size -> strlen(my_3_array): 4
my 3. array: Text0
*** byte my_4_array[5]; ***
sizeof(my_4_array): 5
pointer -> Serial.println(int(my_4_array),HEX): 4B0
my 4. array: 2 5 0 0 7
sizeof()
function returns only 2 bytes for the third array (search the net)?In the third array (my_3_array
) we initialize only a pointer (address register) to the array. Because we assign a text to the pointer and use the char
type, the compiler knows, a null terminated C-string is needed and reserves the corresponding memory before runtime. We could also initialize the pointer without assignment:
byte * my_5_array; // pointer, but memory not allocated!
Now the compiler does not know the size of the memory to allocate at compile time and we have to do it later in the program (runtime) before using the array with the malloc()
function:
my_5_array = (byte *)malloc(5);
my_5_array[2] = 22;
my_5_array[4] = 99;
my_4_array
) and 5 (my_5_array
)?: *** byte * my_5_array; // memory not allocated! ***
pointer size! -> sizeof(my_5_array): 2
pointer -> Serial.println(int(my_5_array),HEX): 563
my 5. array: 5 0 22 5 99
Attach the following code to your program. It shows all of our SRAM. Find all five 5 arrays in the output and document the corresponding lines. If needed use the ASCII table of the first chapter.
byte *testpointer;
Serial.println("\n----------------- SRAM ----------------------------------");
int i = 0;
while (testpointer<0x900) {
if (i%16==0) {
Serial.println();
if (testpointer < 0x10) {
Serial.print('0');
}
if (testpointer < 0x100) {
Serial.print('0');
}
Serial.print(int(testpointer),HEX);
Serial.print(": ");
}
if (*testpointer < 0x10) Serial.print('0');
Serial.print(int(*testpointer),HEX);
Serial.print(' ');
testpointer++;
i++;
}
Find the SREG
register and the stack pointer SP
. Document the corresponding lines.