Working With Extended I/O And SRAM
As we saw in the previous tutorial, standard I/O Registers have a special set of instructions to access them. However, when accessing the Extended I/O Registers and SRAM, a more general set of instructions must be used, shown in the table below.
|lds||load direct from data space|
|ldd||load indirect with displacement|
|sts||store direct to data space|
|std||store indirect with displacement|
Allocating Space in SRAM
Space can be allocated with the directive, followed by the number of bytes to allocate, e.g.
.byte 4 ; allocate 4 bytes
In order to allocate space in SRAM, the directivemust be used to specify data segment. For example, 2 bytes a piece can be allocated at the start of SRAM for the labels and as follows
.dseg .org SRAM_START var1: .byte 2 ; allocate 2 bytes to var1 var2: .byte 2 ; allocate 2 bytes to var2
Above, the directive SRAM_START, an address defined in the include file. The labels and are then used to mark the Data Memory address where space is being allocated.is used to set the location of to
Note that we cannot write values to Data Memory when programming the chip, we can only allocate space for them and initialize them at runtime. Values can be written to SRAM without allocating space, but the above gives us meaningful labels for the data we are storing and lets us keep track of how much SRAM we are using.
Loading and Storing Direct
The first method of accessing data in Extended I/O and SRAM is referred to as direct. Direct access means the address you want to load to or store from is provided with the instruction itself.
The instructions used for direct access are lds - load direct from data space and sts - store direct to data space. lds and sts are called with a Data Memory address and a general purpose register, e.g.
lds r0,TWDR ; load TWDR into r0 sts TWDR,r16 ; write r16 to TWDR
The Register TWDR is in Extended I/O so we cannot use in and out to access it like before. Instead, we must use lds and sts. Nearly all of the registers controlling peripherals (e.g. UART, I2C) are in Extended I/O so they can only be accessed with these instructions.
lds and sts support any of the 32 general purpose working registers as operands and can access up to 64KB of Data Memory space.
Note: Using lds and sts with r0 through r15 costs one more clock cycle and takes up more space in program memory than using r16 through r31. Use r16 through r31 as operands whenever possible.
lds and sts can be used to write to and store data that we have allocated in SRAM. For example,
.dseg .org SRAM_START var: .byte 2 ... ldi r16,0xAA ; load r16 with 0xAA ldi r17,0x55 ; load r17 with 0x55 sts var,r16 ; store 0x55AA in sts var+1,r17 ; var lds r0,var ; load var into lds r1,var+1 ; r1:r0
Above we allocate 2 bytes to 0x55AA, and then load it into r1:r0.in SRAM, initialize it with the value
Loading and Storing Indirect
The other method for accessing data in Extended I/O and SRAM is referred to as indirect. Indirect access is done through the use of pointers. Recall that there are three special registers among the general purpose working registers referred to as the X, Y, and Z pointers. We can access Data Memory by loading an address in one of these pointers. When calling the indirect access instructions, the contents of the Data Memory location pointed to by the X, Y, or Z pointers will be fetched.
For example, the instructions ld and st can be used to access Data with the X pointer as
.dseg .org SRAM_START var: .byte 1 ... ldi r16,0xFF ; load 0xFF into r16 ldi XL,LOW(var) ; initialize X pointer ldi XH,HIGH(var) ; to var address st X,r16 ; store r16 to var ld r0,X ; load r0 with var
Above, the lower and higher bytes of the X pointer are loaded with the address of the Data Space location we are interested in, . Then ld and st are called with the pointer X as an operand.
The same can be done with the Y pointer
ldi YL,LOW(var) ; initialize Y pointer ldi YH,HIGH(var) ; to var address st Y,r16 ; store r16 to var ld r0,Y ; load r0 with var
And the Z pointer
ldi ZL,LOW(var) ; initialize Z pointer ldi ZH,HIGH(var) ; to var address st Z,r16 ; store r16 to var ld r0,Z ; load r0 with var
Post-Increment and Pre-Decrement
If sequential bytes need to be accessed in Data Memory, the X, Y, and Z pointers can be automatically incremented after calling the instruction by placing a + next to the pointer in the operand, i.e.
st X+,r16 ; store r16 to X and increment pointer st Y+,r17 ; store r17 to Y and increment pointer st Z+,r18 ; store r18 to Z and increment pointer
Pointers can also be decremented before calling the instruction by placing a - before to the pointer in the operand, i.e.
st -X,r16 ; store r16 to X and decrement pointer st -Y,r17 ; store r17 to Y and decrement pointer st -Z,r18 ; store r18 to Z and decrement pointer
This feature is extremely useful as it keeps you from having to reload your pointers each time you need to access data. For example, 4 bytes can be stored to data space and then read back as
.dseg .org SRAM_START var: .byte 4 ... ldi r16,0x01 ; load 0x01 into r16 ldi r17,0x23 ; load 0x23 into r17 ldi r18,0x45 ; load 0x45 into r18 ldi r19,0x67 ; load 0x67 into r19 ldi XL,LOW(var) ; initialize X pointer ldi XH,HIGH(var) ; to var address st X+,r16 ; store r16 to var+0 and increment pointer st X+,r17 ; store r17 to var+1 and increment pointer st X+,r18 ; store r18 to var+2 and increment pointer st X+,r19 ; store r19 to var+3 and increment pointer ld r3,-X ; decrement pointer and load var+3 to r3 ld r2,-X ; decrement pointer and load var+2 to r2 ld r1,-X ; decrement pointer and load var+1 to r1 ld r0,-X ; decrement pointer and load var+0 to r0
The Y and Z pointers support the additional feature that they may be displaced by up to 63 bytes ahead of the address they are initialized to. This is done with the instructions ldd - load indirect with displacement and sts - store indirect with displacement.
ldd r0,Y+0 ; load r0 with Y pointer ldd r1,Y+1 ; load r1 with Y pointer + 1 std Z+0,r0 ; load r0 to Z pointer std Z+1,r1 ; load r1 to Z pointer + 1
Displacement is useful for accessing sequential Data Memory locations without changing the value loaded in the pointer in case you need it again later.