Loading Data from Program Memory
Program Memory, a.k.a. flash, can be loaded with constants like arrays and strings when programming your chip. Accessing Program Memory at runtime can be done with just two instructions, shown in the table below.
|lpm||load program memory|
|spm||store program memory|
Storing data to Program Memory at runtime warrants a separate tutorial and is really only used for bootloader programs. Here we will look at the much simpler and more commonly used concept of loading data from Program Memory.
Defining Constants in Program Memory
Like we saw in the SRAM tutorial, space can be allocated in Program Memory for storing values. However, unlike SRAM, values can be written directly to flash by your programmer which you can access immediately. There are a few directives used for allocating space, shown in the table below.
|.db||define constant byte(s)|
|.dw||define constant word(s)|
|.dd||define constant double word(s)|
|.dq||define constant quad word(s)|
Defining a sequence of bytes can be done as
myBytes: .db 0x00,0x01,0x02,0x03,0x04,0x05 myNums: .db 0,1,2,3,4,5 myChars: .db 'H','e','l','l','o','!' myString: .db "Hello!"
The same can be done with words (16-bits) using
myWords: .dw 0x0000,0x0001,0x0002,0x0003
Double words (32-bits) using
And quad words (64-bits) using
Space allocated in Program Memory must be an even number of bytes. If it is not, the assembler will automatically append a zero byte as padding. The assembler will generate a warning to notify you but will still compile. For example, this will generate a warning
myBytes: .db 0,1,2 ; padding byte will be added
Loading Data from Program Memory
Loading data from Program Memory can only be done indirect using the Z pointer. This is shown below
ldi ZL,LOW(2*var) ; load 2*var ldi ZH,HIGH(2*var) ; into Z pointer lpm r4,Z ; load pmem var into r4 var: .db 3
Note that the address 2* is loaded into the Z pointer rather than . The reason for this is that Program Memory is word addressed. Thus each address in Program Memory holds two bytes. However, the Z pointer is byte addressed. As shown below, for every word address, there are two byte addresses.
The word address 0x0000 contains the byte addresses 0x00 and 0x01, word address 0x0001 contains byte addresses 0x02 and 0x03 and so on. Thus, a word address can be converted to its byte addresses by multiplying it by two for the lower byte, and multiplying it by two and adding one for the higher byte.
For example, the higher and lower bytes of a word stored at the labelwould be accessed as
ldi ZL,LOW(2*myWord) ; load 2*myWord ldi ZH,HIGH(2*myWord) ; into Z pointer lpm r4,Z ; load lower byte of var into r4 (0x34) ldi ZL,LOW(2*myWord)+1 ; increment Z low lpm r5,Z ; load upper byte of var into r5 (0x12) myWord: .db 0x1234
The Z pointer can be incremented after each lpm by placing a + after it, e.g.
lpm r16,Z+ ; load from program memory and increment Z
Using this, the above example of loading a word could be simplified as
ldi ZL,LOW(2*myWord) ; load 2*myWord ldi ZH,HIGH(2*myWord) ; into Z pointer lpm r4,Z+ ; load lower byte of var into r4 (0x34) lpm r5,Z ; load upper byte of var into r5 (0x12) myWord: .db 0x1234
A Quick Example
Suppose you have an array defined in Program Memory and you want to transfer it to SRAM. The following program shows an example of how this could be done.
.include "m328pdef.inc" .equ numB = 20 ; number of bytes in array .def tmp = r16 ; define temp register .def loopCt = r17 ; define loop count register .dseg .org SRAM_START sArr: .BYTE numB ; allocate bytes in SRAM for array .cseg .org 0x00 ldi XL,LOW(sArr) ; initialize X pointer ldi XH,HIGH(sArr) ; to SRAM array address ldi ZL,LOW(2*pArr) ; initialize Z pointer ldi ZH,HIGH(2*pArr) ; to pmem array address ldi loopCt,numB ; initialize loop count to number of bytes arrLp: lpm tmp,Z+ ; load value from pmem array st X+,tmp ; store value to SRAM array dec loopCt ; decrement loop count brne arrLp ; repeat loop for all bytes in array loop: rjmp loop ; infinite loop pArr: .db 0,1,2,3,4,5,6,7,8,9,\ 10,11,12,13,14,15,16,\ 17,18,19 ; program memory array
The X pointer is used to access our array location in SRAM, so it is loaded with a Data Memory address
ldi XL,LOW(sArr) ; initialize X pointer ldi XH,HIGH(sArr) ; to SRAM array address
The Z pointer is used to access our array location in Program Memory, so it is loaded with 2 times its Program Memory address
ldi ZL,LOW(2*pArr) ; initialize Z pointer ldi ZH,HIGH(2*pArr) ; to pmem array address
We then setup a simple loop to iterate through the number of bytes in our array (which we know beforehand). We load individualy bytes from Program Memory into the tmp register and then store them to SRAM. You can see how pointer incrementing significantly simplifies the code
ldi loopCt,numB ; initialize loop count to number of bytes arrLp: lpm tmp,Z+ ; load value from pmem array st X+,tmp ; store value to SRAM array dec loopCt ; decrement loop count brne arrLp ; repeat loop for all bytes in array
Note the \ in the Program Memory array definition. This is used as a line continuation to keep our code from getting too wide.
pArr: .db 0,1,2,3,4,5,6,7,8,9,\ 10,11,12,13,14,15,16,\ 17,18,19 ; program memory array