Pfad: Home => AVR-EN => Beginner's Intro => SRAM address modes    (Diese Seite in Deutsch: Flag DE) Logo

Address modes in AVRs

  1. accessing SRAM locations,
    1. Accessing SRAM/registers/port-registers
    2. Fixed addresses access
    3. Pointer access
    4. Increasing pointers
    5. Decreasing pointers
    6. Displacement of pointers
  2. accessing portregister locations,
  3. accessing EEPROM loactions, and
  4. accessing flash memory locations.

1.1 Accessing SRAM, registers and port registers

Address spaces in AVRs The first thing to learn when accessing memory types in AVRs is that there are two types of addressing:
  1. Physical addresses, and
  2. Pointer addresses.
Both are in most cases not identical. In case of SRAM, the two address types have the following values (see the diagram): Note that when accessing SRAM you'll never use the physical address of this memory space, only and exclusively the pointer address. This is a major difference to accessing port registers, where you can use both address types.

Consequently, if you switch the assembler to the data segment using the .dseg directive, its address pointer starts at SRAM_START (in many cases 0x0060, in other cases beyond that address). How can you find out that address? Now, either you search the def.inc file for that address or, more convenient, you use gavrasm as assembler or use the avr_sim simulator. Place the following lines into your source code:

.nolist
.include "m324PBdef.inc" ; Define device ATmega324PB
.list
.dseg
TheFirstSramLocation:
.cseg

Symbol table after .dseg After assembling with gavrasm, with the -s option active, or with avr_sim you'll see in the symbol table within the lower section of the listing that the label placed behind the .dseg directive: the pointer address of the first SRAM location is in that case 0x0100. If you would have selected an ATtiny13 (with .include "tn13def.inc" in the second line), you would get a different value. If you'll need the constant RAMEND: just use .equ my_ramend = RAMEND to get the value of RAMEND into the symbol list. To remove the directive .NOLIST from the source code is not recommended as it flows your attention with hundreds of information lines that you are not really interested in.

If you are working with avr_sim, you can also use the feature "View" and "Symbols" and filter the list with the term "RAMEND".

1.2 Accessing SRAM locations with fixed addresses

SRAM can have a physical size of up to 32,768 bytes. As the pointer address is always higher, the addresses to be handled are therefore 16 bits wide (and not just 15 bits).

Such locations can be addressed directly using the instructions STS 16-bit-address,register or LDS register,16-bit-address. Register can be any register between R0 and R31. The 16-bit-address can be any 16-bit wide fixed number.

The following code writes 0xAA (or binary 0b10101010) to the first physical SRAM location. This is also written to to the 15th byte in SRAM. To demonstrate the usefulness of the pointer addressing, we also write this byte to registers R0 and R15:

.dseg
FirstSramLocation: ; Place a label to this address
;
.cseg
  ldi R16,0xAA
  sts FirstSramLocation,R16 ; Write to first SRAM location
  sts FirstSramLocation+15,R16 ; Write to SRAM 15 bytes later
  sts 0,R16 ; Write to register R0
  sts 15,R16 ; Write to register R15

Wrting to SRAM with STS Wrting to registers with STS To the left, the two bytes written to SRAM can be seen. To the right the two bytes written to those registers can be seen.

If we would have to read from these locations we would use the following:

  lds R16,FirstSramLocation ; Read from first SRAM location
  lds R17,FirstSramLocation+15 ; Read from SRAM 15 bytes later
  lds R18,0 ; Read from register R0
  lds R19,15 ; Read from register R15

If you want your controller to waste some time: lds R16,16 or sts 16,R16 are wonderful operations.

Assembler listing of the STS instruction Note that all four locations use fixed addresses. Those addresses are added to the STS instruction word 0x9300, as can be seen from the assembler listing, so that a double-word instruction is resulting.

1.3 Accessing SRAM location with pointers

Addressing SRAM locations with X, Y and Z To access areas of locations we'll need to address dynamicly, in registers. AVRs can handle 16 bit wide addresses in three double registers or so-called register pairs): To point double register X to the first SRAM location we use the two following instructions:

.dseg
FirstSramLocation: ; Place a label to this address
.cseg
  ldi XH,High(FirstSramLocation) ; Set the MSB of the address
  ldi XL,Low(FirstSramLocation) ; Set the LSB of the address

The address is now in X. To write 0xAA to this address we add the following:

  ldi R16,0xAA ; Write AA to register R16
  st X,R16 ; and to the first SRAM location

Now, that is not very advanced. It is still only one byte to write. But we'll see how we can use the pointer for more.

1.4 Accessing SRAM location with increasing pointers

Now we can easily fill the first 16 bytes of SRAM with the 0xAA by using a loop that increases that address:


.dseg
FirstSramLocation: ; Place a label to this address
.cseg
  ldi XH,High(FirstSramLocation) ; Set the MSB of the address
  ldi XL,Low(FirstSramLocation) ; Set the LSB of the address
  ldi R16,0xAA ; Write AA to register R16
FillLoop:
  st X+,R16 ; and to the SRAM location and increase the address
  cpi XL,Low(FirstSramLocation+16) ; Check if end of fill area
  brne FillLoop

The state ar start-up This shows the initiation stage:

The first step has been executed The first step has been executed, ST has written the content of register R16 to the first SRAM location. The plus behind X auto-increases the address right after writing the register to the location in X. It replaces two instructions:
  1. ST X,R16, plus
  2. ADIW XL,1,
but consumes only two clock cycles instead of four. That is called Auto-Increment.

The last step is to check whether X already points to outside of our row. The first location outside our row is LastLocationPlus1:. Note that this only works for area lengthes of up to 256 bytes, because we check only the LSB of the address byte. An alternative way, to be able to fill any desired length of SRAM with that constant, would be:

.dseg
FirstSramLocation: ; Place a label to this address
  .byte 16 ; Define length of area
LastSramLocationPlus1:
;
.cseg
  ldi XH,High(FirstSramLocation) ; Set the MSB of the address
  ldi XL,Low(FirstSramLocation) ; Set the LSB of the address
  ldi R16,0xAA ; Write AA to register R16
FillLoop:
  st X+,R16 ; and to the SRAM location and increase the address
  cpi XH,High(LastSramLocationPlus1) ; Check MSB
  brne FillLoop
  cpi XL,Low(LastSramLocationPlus1) ; Check if end of fill area
  brne FillLoop

Now: whatever length is defined in the SRAM segment, we'll write our constant to that complete area.

1.5 Accessing SRAM location with decreasing pointers

Increasing pointers and repeated read/write allow very fast and effective programs. But what if we need decreasing?

As a somewhat weird example: we want to copy an SRAM area to a different area in a reversed row, so that the text in one area appears reversed in another area.

Text pattern in SRAM First we have to create a text pattern in a first area. Like this:

.dseg
  Textarea:
  .byte 16
  TextareaEnd:
.cseg
  ldi XH,High(Textarea)
  ldi XL,Low(Textarea)
  ldi R16,'a'
FillLoop:
  st X+,R16
  inc R16
  cpi XL,Low(TextareaEnd)
  brne FillLoop



Filling the text area That produces the pattern here. In the initiation step the pointer X is set to the beginning of the text area, R16 is set to the ASCII character 'a'. In a loop then this character is written to SRAM, Y is auto-incremented and R16 is also incremented, which produces a 'b', and so on.

Now we'd like to reverse that. Of course we need a second pointer for this, e.g. Y. The first character, that the pointer X points to at the beginning, the "a", goes to the last position of the second area. The next character goes one position left to that, so we have to decrease the second pointer. If you think, that possibly an Y- would be sufficient to avoid a pointer decrease with SBIW YL,1, you are on a good assumption, but the assembler complains: ST Y-,R16 is not a valid instruction:

Assembler error message ST Y- This is the error message of the assembler when trying to use ST X-,R16: the minus is valid on the left of Y, not to the right of it. This has a serious consequence: the minus is executed first, before storing. And: the pointer Y starts one position to the right, after the last used target byte.

Reversed copy of the text This here is the complete source code for reversed copying.

.dseg
  Textarea:
  .byte 16
  TextareaEnd:
  .org 0x0080 ; Leave some space in between
  Textreverse:
  .byte 16
  TextreverseEnd:
.cseg
  ldi XH,High(Textarea)
  ldi XL,Low(Textarea)
  ldi R16,'a'
FillLoop:
  st X+,R16
  inc R16
  cpi XL,Low(TextareaEnd)
  brne FillLoop
  ldi XH,High(Textarea)
  ldi XL,Low(Textarea)
  ldi YH,High(TextreverseEnd)
  ldi YL,Low(TextreverseEnd)
CopyLoop:
  ld R16,X+
  st -Y,R16
  cpi XL,Low(TextareaEnd)
  brne CopyLoop

Filling the text area with characters The first part works like filling with a constant, but here we increase the characters in R16 from 'a' to 'p'.

Setting the pointers X and Y The X-pointer is then set to point to the beginning of that area. Then we add pointer Y, which points at the end of the reversed text area, plus 1.

Reading from the text area In a loop, first the next character from the text area is read. Of course, with an auto-increment.

Writing to the reversed text area The character that was read to R16 is then copied to the reverse text area using the pointer Y. But this is done only after decreasing the pointer address with -Y.

These two steps, read and write, are repeated in the copy loop. The loop ends if That was a quick reverse copy. All address manipulation of the two pointers use the Auto-Increment- and Auto-Decrement-features of the AVRs, no fuzzy and time-consuming ADIW or SBIW necessary. All with the built-in instruction set of the AVRs. But there are even more addressing modes in AVRs.

1.6 Accessing SRAM locations with displacement addressing

AVRs have an additional addressing mode that temporarily adds a displacement to a pointer. This is also called indirect mode. Only Y and Z are capable for that, not the X register pair.

The two instructions doing that are STD Y/Z+d,register and LDD register,Y/Z+d. d is a constant between 1 and 63. It is only added temporarily, Y or Z are not changed at all. So this instruction replaces the following sequence (here for Y):

  adiw YL,d ; Add displacement d to Y
  st Y,R16 ; store R16 on displaced location
  sbiw YL,d ; Subtract displacement d from Y  

The difference between the STD and this sequence is that STD does not affect any SREG flags. And it requires only two clock cycles instead of six.

STD and LDD are useful in cases where you'll have to access byte rows in respect to a fixed address: access to displaced bytes is eased. An example for this.

Record structure Your ADC has up to eight channels, each channel has its own sum where all measuring results are summed up, its own multiplier, its multiplication result, its own compare values, its own jump address, etc.

This record of max. 64 bytes each has to be treated as a whole, e.g. the multiplication routine for all eight channels exists only once and is called with Y or Z pointing to the current channel.

To access the data bytes in this structure, e.g. summing up a 10-bit measuring result in R1:R0, the sum value can be accessed as follows:

  ld R16,Y ; Read the LSB of the sum
  add R16,R0 ; Add the LSB
  st Y,R16 ; Store the LSB
  ldd R16,Y+1 ; Read the MSB of the sum with displacement
  adc R16,R1 ; Add the MSB
  std Y+1,R16 ; Store the MSB with displacement

The only thing you need is to set Y to the channel's record address. Because all LD/ST and LDD/STD do not affect SREG the ADC of the MSB can use the carry flag. With ADIW or SDIW in between, that wouldn't be possible.

The advantage of the use of displacement access via Y or Z is that any of the necessary routines accesses one of the eight channel's record. Only the Y pointer's address decides which one of the channels is manipulated.

String filled into SRAM But the displacement access can be useful in many other cases. Here a simple example. Let us assume we have filled an area with characters, such as here.

.dseg
TextLocation: ; Place a label to this address
  .byte 27
TextLocationEnd:
  .byte 1
TextLocationEndPlusOne:
;
.cseg
  ldi YH,High(TextLocation) ; Set the MSB of the address
  ldi YL,Low(TextLocation) ; Set the LSB of the address
  ldi R16,'A' ; Write character A to register R16
FillLoop:
  st Y+,R16 ; and to the SRAM location, auto-increase the address
  inc R16
  cpi YL,Low(TextLocationEnd) ; Check if end of fill area
  brne FillLoop

, Now let us assume further that we need space for an additional character at the start of that string, but we want to keep the original. That means we extend this text by one SRAM location and add the additional character at the beginning.

Filling the area with A to Z This is the filling process, like already seen in the examples above.

The last fill operation In that case it is clear that we'll have to start from the end of the string, the 'Z': only if we shift the 'Z' one position to the right, we'll have space to shift the next character, the 'Y', also one position to the right.

We see that the last fill operation already ends with the Y pointer pointing to right behind the 'Z'. We already know how to read the 'Z': just with LD R16,-Z. That decreases the address in Y by one and then reads the 'Z'.

But we'll have to write the 'Z' now to the next higher address. We can do that by increasing Y with ADIW YL,1 first, then write the character and then going back with SBIW YL,1. As the character is by one location to the left, we can go back by two. That would lead to the following down-up-and-down-orgy:

  sbiw YL,1 ; 2 clock cycles
Loop:
  ld R16,Y ; +2 = 4 clock cycles
  adiw YL,1 ; +2 = 6 clock cycles
  st Y,R16 ; +2 = 8 clock cycles
  sbiw YL,2 ; +2 = 10 clock cycles
  ; check end of loop here
  brne Loop

Reading the character with LD R16,-Y A much more elegant solution uses the "Decrease before reading" feature of AVR addressing: LD R16,-Y first decreases the pointer and reads the byte from the already decreased address. This replaces the SBIW plus the LD and packs it into one instruction. By that it decreases execution from four clock cycles down to two.

Writing the character with STD Y+1,R16 The two steps which then increases the pointer again and writes the character there, can be replaced by another intelligent AVR address mode: STD Y+1,R16. This adds temporarily a one to Y and writes the character there. As the one is only temporarily added, Y remains the same after the instruction has executed. No further address manipulation is necessary.

27 characters shifted, one added With these two addressing tricks we come to the following optimal code for that string shifting:

ShiftLoop:
  ld R16,-Y ; 2 clock cycles
  std Y+1,R16 ; +2 = 4 clock cycles
  cpi YL,Low(TextLocation) ; +1 = 5 clock cycles
  brne ShiftLoop ; +1 or +2 = 6/7 clock cycles
  ldi R16,'@'
  st Y,R16

Now that is really amazing: no pointer corrections in between: no ADIWs or SBIWs, anything in fast, two cycle long instructions. 153 µs for shifting 28 characters at 1.2 MHz clock in an ATtiny13.

And: more than double as fast than with address manipulation. So if your PIC does not know auto-decrement and displacement access, it needs double as long as an AVR. And: that makes a clock cycle increase in an ATtiny13 with the factor of two, without increasing its power consumption at all.


Conclusion: if your program goes far beyond a blink routine in respect to complexity, think about such advanced instruction capabilities. It makes your code more elegant, executes faster, is much more effective and, if associated with enough comments, it reads simpler.

To the top of this page

©2021 by http://avr-asm-tutorial.net