Path: Home => AVR-overview => Assembler intro => Registers

Introduction to AVR assembler programming for beginners

What is a register?

Registers are special storages with 8 bits capacity and they look like this:
7654 3210

Note the numeration of these bits: the least significant bit starts with zero (20 = 1).
A register can either store numbers from 0 to 255 (positive number, no negative values), or numbers from -128 to +127 (whole number with a sign bit in bit 7), or a value representing an ASCII-coded character (e.g. 'A'), or just eight single bits that do not have something to do with each other (e.g. for eight single flags used to signal eight different yes/no decisions).
The special character of registers, compared to other storage sites, is that There are 32 registers in an AVR. They are originally named R0 to R31, but you can choose to name them to more meaningful names using an assembler directive. An example:

.DEF MyPreferredRegister = R16

Note that assembler directives like this are only meaningful for the assembler but do not produce any code that is executable in the AVR target chip. Instead of using the register name R16 we can now use our own name MyPreferredRegister, if we want to use R16 within a command. So we write a little bit more text each time we use this register, but we have an association what might be the content of this register.

Using the command line

   LDI MyPreferredRegister, 150

which means: load the number 150 immediately to the register R16, LoaD Immediate. This loads a fixed value or a constant to that register. Following the assembly or translation of this code the program storage written to the AVR chip looks like this:

000000 E906

The load command code as well as the target register (R16) as well as the value of the constant (150) is part of the hex value E906, even if you don't see this directly. Don't be afraid: you don't have to remember this coding because the assembler knows how to translate all this to yield E906.

Within one command two different registers can play a role. The easiest command of this type is the copy command MOV. It copies the content of one register to another register. Like this:

.DEF MyPreferredRegister = R16
.DEF AnotherRegister = R15
   LDI MyPreferredRegister, 150
   MOV AnotherRegister, MyPreferredRegister


The first two lines of this monster program are directives that define the new names of the registers R16 and R15 for the assembler. Again, these lines do not produce any code for the AVR. The command lines with LDI and MOV produce code:

000000 E906
000001 2F01


The commands write 150 into register R16 and copy its content to the target register R15. IMPORTANT NOTE:

The first register is always the target register where the result is written to!

(This is unfortunately different from what one expects or from how we speak. It is a simple convention that was once defined that way to confuse the beginners learning assembler. That is why assembler is that complicated.)


To the top of that page

Different registers

The beginner might want to write the above commands like this:

.DEF AnotherRegister = R15
   LDI AnotherRegister, 150


And: you lost. Only the registers from R16 to R31 load a constant immediately with the LDI command, R0 to R15 don't do that. This restriction is not very fine, but could not be avoided during construction of the command set for the AVRs.

There is one exception from that rule: setting a register to Zero. This command

    CLR MyPreferredRegister

is valid for all registers.

Besides the LDI command you will find this register class restriction with the following additional commands: In all these commands the register must be between R16 and R31! If you plan to use these commands you should select one of these registers for that operation. It is easier to program. This is an additional reason why you should use the directive to define a register's name, because you can easier change the registers location afterwards.

To the top of that page

Pointer-register

A very special extra role is defined for the register pairs R26:R27, R28:R29 and R30:R31. The role is so important that these pairs have extra names in assembler: X, Y and Z. These pairs are 16-bit pointer registers, able to point to adresses with max. 16-bit into SRAM locations (X, Y or Z) or into locations in program memory (Z).

The lower byte of the 16-bit-adress is located in the lower register, the higher byte in the upper register. Both parts have their own names, e.g. the higher byte of Z is named ZH (=R31), the lower Byte is ZL (=R30). These names are defined in the standard header file for the chips. Dividing these 16-bit-pointer-names into two different bytes is done like follows:

.EQU Adress = RAMEND ; RAMEND is the highest 16-bit adress in SRAM
   LDI YH,HIGH(Adress) ; Set the MSB
   LDI YL,LOW(Adress) ; Set the LSB

Accesses via pointers are programmed with specially designed commands. Read access is named LD (LoaD), write access named ST (STore), e.g. with the X-pointer:
PointerSequenceExample
XRead/Write from adress X, don't change the pointer LD R1,X
ST X,R1
X+Read/Write from/to adress X and increment the pointer afterwards by oneLD R1,X+
ST X+,R1
-XDecrement the pointer by one and read/write from/to the new adress afterwardsLD R1,-X
ST -X,R1
Similiarly you can use Y and Z for that purpose.

There is only one command for the read access to the program storage. It is defined for the pointer pair Z and it is named LPM (Load from Program Memory). The command copies the byte at adress Z in the program memory to the register R0. As the program memory is organised word-wise (one command on one adress consists of 16 bits or two bytes or one word) the least significant bit selects the lower or higher byte (0=lower byte, 1= higher byte). Because of this the original adress must be multiplied by 2 and access is limited to 15-bit or 32 kB program memory. Like this:

   LDI ZH,HIGH(2*Adress)
   LDI ZL,LOW(2*Adress)
   LPM


Following this command the adress must be incremented to point to the next byte in program memory. As this is used very often a special pointer incrementation command has been defined to do this:

   ADIW ZL,1
   LPM


ADIW means ADd Immediate Word and a maximum of 63 can be added this way. Note that the assembler expects the lower of the pointer register pair ZL as first parameter. This is somewhat confusing as addition is done as 16-bit- operation.
The complement command, subtracting a constant value of between 0 and 63 from a 16-bit pointer register is named SBIW, Subtract Immediate Word. (SuBtract Immediate Word). ADIW and SBIW are possible for the pointer register pairs X, Y and Z and for the register pair R25:R24, that does not have an extra name and does not allow access to SRAM or program memory locations. R25:R24 is ideal for handling 16-bit values.

As incrementation after reading is very often needed, newer AVR types have the instruction

   LPM R,Z+

This allows to transport the byte read to any location R, and auto-increments the pointer register.

How to insert that table of values in the program memory? This is done with the assembler directives .DB and .DW. With that you can insert bytewise or wordwise lists of values. Bytewise organised lists look like this:

.DB 123,45,67,89 ; a list of four bytes
.DB "This is a text. " ; a list of byte characters

You should always place an even number of bytes on each single line. Otherwise the assembler will add a zero byte at the end, which might be unwanted.
The similiar list of words looks like this:

.DW 12345,6789 ; a list of two words

Instead of constants you can also place labels (jump targets) on that list, like that:

Label1:
[ ... here are some commands ... ]
Label2:
[ ... here are some more commands ... ]
Table:
.DW Label1,Label2
; a wordwise list of labels

Note that reading the labels with LPM first yields the lower byte of the word.

A very special application for the pointer registers is the access to the registers themselves. The registers are located in the first 32 bytes of the chip's adress space (at adress 0x0000 to 0x001F). This access is only meaningful if you have to copy the register's content to SRAM or EEPROM or read these values from there back into the registers.

More common for the use of pointers is the access to tables with fixed values in the program memory space. Here is, as an example, a table with 10 different 16-bit values, where the fifth table value is read to R25:R24:

MyTable:
.DW 0x1234,0x2345,0x3456,0x4568,0x5678 ; The table values, wordwise
.DW 0x6789,0x789A,0x89AB,0x9ABC,0xABCD ; organised
Read5:    LDI ZH,HIGH(MyTable*2) ; Adress of table to pointer Z
   LDI ZL,LOW(MyTable*2) ; multiplied by 2 for bytewise access
   ADIW ZL,10 ; Point to fifth value in table
   LPM ; Read least significant byte from program memory
   MOV R24,R0 ; Copy LSB to 16-bit register
   ADIW ZL,1 ; Point to MSB in program memory
   LPM ; Read MSB of table value
   MOV R25,R0 ; Copy MSB to 16-bit register

This is only an example. You can calculate the table adress in Z from some input value, leading to the respective table values. Tables can be organised byte- or character-wise, too.

To the top of that page

Recommendation for the use of registers

  1. Define names for registers with the .DEF directive, never use them with their direct name Rx.
  2. If you need pointer access reserve R26 to R31 for that purpose.
  3. 16-bit-counter are best located R25:R24.
  4. If you need to read from the program memory, e.g. fixed tables, reserve Z (R31:R30) and R0 for that purpose.
  5. If you plan to have access to single bits within certain registers (e.g. for testing flags), use R16 to R23 for that purpose.


To the top of that page

©2002 by http://www.avr-asm-tutorial.net