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.

Mnemonic Description
lds load direct from data space
ld load indirect
ldd load indirect with displacement
sts store direct to data space
st store indirect
std store indirect with displacement

Allocating Space in SRAM

Space can be allocated with the directive .byte, followed by the number of bytes to allocate, e.g.

	.byte	4		; allocate 4 bytes

In order to allocate space in SRAM, the directive .dseg must be used to specify data segment. For example, 2 bytes a piece can be allocated at the start of SRAM for the labels var1 and var2 as follows

var1:	.byte	2		; allocate 2 bytes to var1
var2:	.byte	2		; allocate 2 bytes to var2

Above, the directive .org is used to set the location of .dseg to SRAM_START, an address defined in the include file. The labels var1 and var2 are then used to mark the Data Memory address where space is being allocated.

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,

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 var in SRAM, initialize it with the value 0x55AA, and then load it into r1:r0.

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

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, var. 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

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. 2018