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

Address modes in AVRs

  1. accessing SRAM locations,
  2. accessing portregister locations,
  3. accessing EEPROM locations,
  4. accessing flash memory locations
    1. Flash memory
    2. .CSEG directive
    3. LPM
    4. Advanced LPM
    5. Use examples

4.1 Flash memory addresses

Flash memory addressing All AVRs have a memory, called flash memory, that holds the executable program. As the executable instructions in AVRs have 16 bits, the memory is 16 bit wide (in words, not in bytes).

The size of the flash memory can be anything between 512 words and up to 393,216 words. The addresses are therefore between 0x01FF and 0x05FFFF. In most cases the constant FlashEnd from the def.inc file provides the last or highest address.
The address 0x00000 is special because it is the starting address: after power-up, a reset or a watchdog-reset the instruction word in address 0x000000 is the first that is executed.

4.2 The .CSEG directive

Assembling a source code writes all executable instructions and tables to the code segment CSEG by default. When switching the assembler either to the SRAM segment with the directive .DSEG or to the EEPROM segment with the directive .ESEG, the return back to the code segment can be dore with the directive .CSEG.

All code that has been assembled is written by the assembler to the .hex file. Its content can be written to the flash memory with the programmer soft- and hardware.

Instructions such as NOP write 16-bit words to the .hex file. Words with 16 bits can be written with the directive .DW 16-bit-value at any location within the flash.

Bytes can be assembled and written to the .hex file with the .DB 8-bit-value. When writing one single 8-bit-value, the upper significant byte MSB is always written to zero. When writing two 8-bit-constants within one .DB 8-bit-value-1, 8-bit-value_2 directive, the first value is written to the LSB, the second value to the MSB at the current location.

Assembling a source code Assembler listing The assembler source code to the left is assembled, the results of the assembling can be viewed in the assembler listing on the right side.

4.3 The LPM instruction

Accessing flash with LPM The instruction LPM or Load from Program Memory reads one byte from the flash. It takes the flash address from the register pair Z (ZH:ZL = R31:R30) and transfers the result to register R0.

But: each address in flash memory has two bytes, an LSB and an MSB. Which of the two bytes are to be read, and how to get the second byte at that same address?

The trick to do that is to shift the flash address left by one location and to add a zero or a one to the right of the address in bit 0 of Z. A zero to the right addresses the LSB, a one the MSB.

One disadvantage does the trick have: bit 15 of the flash address cannot be used. So better place your lengthy tables with thousands of values into the lower half of your 64k words wide flash.

Simulating the LPM instruction The following formulations in assembler are all the same and set Z to access the LSB and the MSB of the byte table below:

.equ FlashAddr = ByteTable ; Set the flash address
  ; Formulation 1
  ldi ZH,High(FlashAddr+FlashAddr+0) ; Access the LSB, MSB of Z
  ldi ZL,Low(FlashAddr+FlashAddr+0) ; dto., LSB of Z
  lpm
  ldi ZH,High(FlashAddr+FlashAddr+1) ; Access the MSB, MSB of Z
  ldi ZL,Low(FlashAddr+FlashAddr+1) ; dto., LSB of Z
  lpm
  ; 
  ; Formulation 2
  ldi ZH,High(2*FlashAddr+0) ; Access the LSB, MSB of Z
  ldi ZL,Low(2*FlashAddr+0) ; dto., LSB of Z
  lpm
  ldi ZH,High(2*FlashAddr+1) ; Access the MSB, MSB of Z
  ldi ZL,Low(2*FlashAddr+1) ; dto., LSB of Z
  lpm
  ;
  ; Formulation 3
  ldi ZH,High((FlashAddr<<1)|0) ; Access the LSB, MSB of Z
  ldi ZL,Low((FlashAddr<<1)|0) ; dto., LSB of Z
  lpm
  ldi ZH,High((FlashAddr<<1)|1) ; Access the MSB, MSB of Z
  ldi ZL,Low((FlashAddr<<1)|1) ; dto., LSB of Z
  lpm
Loop:
  rjmp Loop
;
ByteTable:
  .db 1
  .db 1,2
  .db 1,2,3
  .db "This is a text string"

Of course, you do not have to define an extra constant named FlashAddr but you can directly use the label ByteTable: as address in the LDI instructions. And all the +0 and |0 in the formulations are also superfluous because they have no effect.

So, whatever you prefer, it is all the same. The result is always in R0, as the simulated instruction shows. What the simulation also shows is that one access of the flash memory costs three cycles (the LDI are ony cycle each). Flash memory therefore is a little bit slower than SRAM and much slower than registers.

4.4 Advanced LPM instructions

ATMEL later added the opportunity to use any register as target, the formulation of those instruction are lpm register,Z, where register can be any of the 32 registers.

Also a little bit later the auto-increment was implemented. This increases the address in Z after the load has been performed. The effect is that the two instructions lpm and adiw ZL,1 are replaced by the instruction lpm register,Z+. Note that this additional step does not increase the access time.

The opposite, the auto-decrease to read tables from the end down to the beginning, was also implemented. Like in the case of SRAM auto-decrement, the decrementation is done prior to the load access. The formulation is lpm register,-Z and replaces sbiw ZL,1 and lpm. This additional decrementation does not change access time.

4.5 Use examples for LPM

The text has been copied to SRAM The first example uses LPM to copy a null-terminated text from flash memory to SRAM.

; Prepare data segment labels
.dseg
sText:
;
.cseg
  ; Point Z to flash in memory
  ldi ZH,High(2*Text)
  ldi ZL,Low(2*Text)
  ; Point X to SRAM target location
  ldi XH,High(sText)
  ldi XL,Low(sText)
CopyText:
  lpm R16,Z+ ; Load from program memory
  st X+,R16 ; Store in SRAM
  tst R16 ; Null termination?
  brne CopyText ; No, go on
; Do'nt run into table
Loop:
  rjmp Loop
; Prepare the text in flash memory
Text:
  .db "This text to be copied to SRAM.",0x00

The whole operation lasts 259 µs.

The second example is a little bit academic. Guess that your program needs to react to an event with 10 different subroutines, that are of an unequal length: some short ones, some long ones. That can be the case if the user presses one out of ten keys. You can now check whether the initial event was zero, one, two, etc. and up to nine and you can call the ten different subroutines.

Faster and more elegant is it to What you win here is that you are flexible in extending or reducing the number of subroutines, you are flexible to place them to any address you like, etc.

This is the source code.

; Prepare data segment labels
.dseg
sText:
;
.cseg
  ; Point Z to flash in memory
  ldi ZH,High(2*Text)
  ldi ZL,Low(2*Text)
  ldi XH,High(sText)
  ldi XL,Low(sText)
CopyText:
  lpm R16,Z+
  st X+,R16
  tst R16
  brne CopyText
; Icall part
.equ select = 0
  ldi R16,Low(RAMEND)
  out SPL,R16
  .ifdef SPH
    ldi R16,High(RAMEND)
    out SPH,R16
    .endif
  ldi R16,select ; Load selected routine number here
  lsl R16 ; Multiply by two
  ldi ZH,High(2*JmpTable) ; Point Z to table
  ldi ZL,Low(2*JmpTable)
  add ZL,R16 ; Add the doubled selection number
  ldi R16,0 ; Add carry, if any
  adc ZH,R16
  lpm R16,Z+ ; Read LSB
  lpm ZH,Z ; Read MSB
  mov ZL,R16 ; Copy LSB to ZL
  icall ; Call the routine in Z
Loop:
  rjmp Loop
;
; Routine 0
Routine0:
  nop
  ret
Routine1:
  nop
  nop
  ret
Routine2:
  nop
  nop
  nop
  ret
Routine3:
  nop
  nop
  nop
  nop
  ret
Routine4:
  nop
  nop
  nop
  nop
  nop
  ret
Routine5:
  nop
  nop
  nop
  nop
  nop
  nop
  ret
;
; Jump table
JmpTable:
  .dw Routine0,Routine1,Routine2
  .dw Routine3,Routine4,Routine5
  ; Add additional routines here

Starting ICALL simulation with select=0 The simulation has been started with select=0, the table address of this selection has been calculated in Z by adding the left-shifted select to the table's starting address. The address in Z points to the LSB of the first table entry.

The jump table 0x0033 is the first entry.

Reading the jump address from the table Now the jump address has been read from the table and prepared for an ICALL in Z.

Calling routine 0 The ICALL has called the Routine0.

With all different possible selects this jumps to the correct routine.

Conclusion: LPM and its more modern variations offer a wide variety of opportunities to handle texts and to access smaller or larger tables in the large flash memory. Effective programming very often involves such loads from program flash.

To the top of this page

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