Working With Registers R0-R31

 

Working With Registers R0-R31

AVR microcontrollers contain 32 8-bit general purpose working registers that are directly connected to the Arithmetic Logic Unit (ALU). Almost all data manipulation in your program must be done with the general purpose working registers so it is important to understand the instructions they offer and how to use them.

Below is an overview of these instructions. It is a lot to take in if this is your first time seeing them so don't worry if you feel overwhelmed. The important thing is to get a feel for the types of instructions available and their general syntax, not memorize all of them.

Data Transfer

The AVR architecture has many instructions for data transfer, but for now, we will only look at the ones specific to transfering data between the general purpose working registers. They are shown in the table below.

MnemonicDescription
ldiload immediate
movcopy register
movwcopy register pair

One of the most common instructions you will use is ldi - load immediateldi allows you to load a constant 8-bit value directly to a specified register. For example, the constant 85 can be loaded to register r16 with the following code:

	ldi	r16,85 		; load the value 85 into register 16

The constant can be specified in a variety of ways. All of the below are valid and equivalent:

	ldi	r16,85		; load 85 into register 16 (decimal)
	ldi	r16,0x55	; load 85 into register 16 (hex)
	ldi 	r16,$55		; load 85 into register 16 (also hex)
	ldi	r16,0125	; load 85 into register 16 (octal)
	ldi	r16,0b01010101	; load 85 into register 16 (binary)

Note the order of the instruction and the operands. The instruction comes first, followed by the destination, and then the input. This can be confusing at first, but it is helpful to read it as load r16 with the value 85.

ldi is part of a special set of immediate instructions. Immediate instructions operate on a register and require a constant be supplied as an operand. Immediate instructions are very useful as they allow you to operate with a number you supply in the code itself. However, not all of the 32 registers can be used with immediate instructions - only registers 16 through 31.

Remember: Immediate instructions only work with registers 16 through 31. Trying to use them with registers 0 through 15 will result in an error.


The contents of one register can be copied to another register using the mov instruction. Since ldi only works on registers 16 through 31, the mov instruction is a useful way to load a constant into one of the lower 16 registers. For example, the below code shows a constant being loaded in r16 and mov being used to copy it to r0.

	ldi	r16,0x55	; load 0x55 into r16
	mov	r0,r16		; copy contents of r16 into r0

As with ldi, the order of the operands with the mov instruction may be confusing with the destination provided first, followed by the source. Again, it is helpful to read it as copy into r0 the contents of r16

Note: Even though the mov instruction looks like move, it actually copies the contents of one register into another. After the instruction, the source register is left unchanged.


Although the AVR is an 8-bit architecture, there are a select few instructions that allow you to operate on 16-bit values (words). One of these is the instruction movw, which allows you to copy the contents of one register pair into another register pair. For example, the below code shows how to load the constant 0x1234 into r16 and r17, and copy it to r0 and r1.

	ldi	r16,0x34	; load lower byte of 0x1234 into r16
	ldi	r17,0x12	; load upper byte of 0x1234 into r17

	movw	r1:r0,r17:r16	; copy the contents of r17:r16 into r1:r0

Instead of typying the pair of registers you want to copy, you can just specify the lower of the two and the upper register will be implied, e.g.

	movw	r0,r16		; copy the contents of r17:r16 into r1:r0

Note that this instruction only works on register pairs in which the lower register (for both source and destination) is an even value. For example, the following will generate an error since the lower register of the destination is odd

	movw	r2:r1,r19:r18	; r2:r1 is an invalid operand for movw

As with movmovw performs a copy rather than a move. The source registers are not changed after this instruction.

Logical Instructions

The CPU supports logical operations between two registers or a register and a constant. The basic logical instructions are shown in the table below.

MnemonicDescription
andlogical AND
andilogical AND with immediate
orlogical OR
orilogical OR with immediate
eorexclusive OR
comone's complement
negtwo's complement

ANDs ORs and Exclusives

There are two logical immediate instructions - andi and ori - which compute the logical AND or OR between a register and a constant. They are written the same way as ldi, with the instruction mnemonic first, followed by a register, and then a constant. Some examples are shown below:

	ldi	r16,0x55	; load 0x55 into r16
	andi	r16,0x0F	; mask the upper 4 bits of r16 (result = 0x05)

	ldi	r17,0x00	; load 0x00 into r17
	ori	r17,0x0F	; set the lower 4 bits of r17 (result = 0x0F)

As with ldiandi and ori only work on registers 16 through 31.

The other logical instructions are not immediate - they operate on two registers rather than a register and a constant. The CPU can compute the logical ANDOR or Exclusive OR between the two registers and overwrite the first register with the result. For example, the following shows the values 0x55 and 0x0F being loaded into r16 and r17 respectively. Then, the logical AND is computed between the two and the output is stored in r16.

        ldi     r16,0x55	; load 0x55 into r16
        ldi     r17,0x0F	; load 0x0F into r17
        and     r16,r17		; mask the upper 4 bits of r16 (result = 0x05)

If the order of the registers were switched, the output would be stored in r17, i.e.

	and	r17,r16		; mask the upper 4 bits of r17 (result = 0x05)

The or and eor instructions work the same way:

        ldi     r16,0x55	; load 0x55 into r16
        ldi     r17,0x0F	; load 0x0F into r17
        or      r16,r17		; set the lower 4 bits of r16 (result = 0x5F)

        ldi     r16,0x55	; load 0x55 into r16
        ldi     r17,0xFF	; load 0xFF into r17
        eor     r16,r17		; toggle the lowest 4 bits of r16 (result = 0xAA)

The same register can also be used for both operands

	eor	r16,r16		; clear r16 (result = 0x00)

Complements and Negatives

The instruction com allows you to toggle all bits of a register. For example

	ldi	r16,0x55	; load 0x55 into r16
	com	r16		; one's complement of r16 (result = 0xAA)

The instruction neg takes the two's complement of a register (i.e. it negates it).

	ldi	r16,5		; load 5 into r16
	neg	r16		; negate r16 (result = -5 = 0xFB)

Arithmetic Instructions

The AVR supports a variety of arithmetic instructions for the general purpose registers, shown in the table below.

MnemonicDescription
addadd without carry
adcadd with carry
adiwadd immediate to word
subsubtract without carry
subisubtract immediate
sbcsubtract with carry
sbcisubtract immediate with carry
sbiwsubtract immediate from word
incincrement
decdecrement
mulmultiply unsigned(1)
mulsmultiply signed(1)
mulsumultiply signed with unsigned(1)
fmulfractional multiply unsigned(1)
fmulsfractional multiply signed(1)
fmulsufractional multiply signed with unsigned(1)
1: Not implemented in ATtiny

Addition and Subtraction

Registers can be added using the add instruction. add takes two registers as inputs, computes their sum, and stores the value in the first input. The code below shows how to compute the sum of 5 and 6 and store the result in r16:

	ldi	r16,5		; load 5 into register 16
	ldi	r17,6		; load 6 into register 17
	add	r16,r17		; add r16 and r17 and store the result in r16

Similarly, the adc instruction - add with carry, takes two registers as inputs and computes their sum, but will add a carry bit if the previous operation resulted in an overflow. This is useful when computing the sums of 16 bit numbers. As we saw before, the addition of 0x1234 and 0xABCD can be computed as

	ldi	r16,0x34	; place lower byte of 0x1234 in r16
	ldi	r17,0x12	; place upper byte of 0x1234 in r17
	ldi	r18,0xCD	; place lower byte of 0xABCD in r18
	ldi	r19,0xAB	; place upper byte of 0xABCD in r19

	add	r16,r18		; compute sum of lower bytes (result = 0x01)
	adc	r17,r19		; compute sum of upper bytes with carry (result = 0xBE)

The above results in the sum 0xBE01 being stored in r16 and r17.

Just like with adding, there is a subtraction instruction pair, sub - subtract and sbc - subtract with carrysub and sbc are used the same way as add and adc. For example

	ldi	r16,0x01	; place lower byte of 0xBE01 in r16
	ldi	r17,0xBE	; place upper byte of 0xBE01 in r17
	ldi	r18,0x34	; place lower byte of 0x1234 in r18
	ldi	r19,0x12	; place upper byte of 0x1234 in r19

	sub	r16,r18		; subtract lower bytes (result = 0xCD)
	sbc	r17,r19		; subtract upper bytes with carry (result = 0xAB)

In addition, there is a subtract immediate instruction - sbi, which allows you to subtract an 8-bit constant from a register.

	ldi	r16,0x05		; load 0x05 into register 16
	sbi	r16,0x05		; subtract 5 from r16 (result = 0x00)

Unfortunately, there is no corresponding add immediate instruction.

The inc and dec pair are simple ways to increase or decrease the value in a register by one. They require a single register as an operand, and work on any of the 32 registers.

	clr	r16		; clear r16 (result = 0)
	inc	r16		; increment r16 (result = 1)
	dec	r16		; decrement r16 (result = 0)

Some arithmetic instructions allow you to operate on 16-bit values, i.e. words. The instruction adiw - add immediate to word, allows you to add a constant value of the range 0 - 63 to the register pair r24:r25 or the X (r26:r27), Y (r28:r29) or Z (r30:r31) pointers. If only one register is specified the compiler will automatically fill in the next (i.e. r24 → r25:r24). The following are ways that adiw can be used

	ldi	r24,0x00	; load 0x1000 into
	ldi	r25,0x10	; registers r24:r25

	adiw	r24,0x0A	; add 0x0A to r24:r25 (result = 0x100A)

	ldi	XL,0x80		; load 0x8080 into
	ldi	XH,0x80		; X pointer

	adiw	X,1		; increment X pointer (result = 0x80801)

sbiw - subtract immediate from word is used the same way

	ldi	YL,0xAA		; load 0x55AA into
	ldi	YL,0x55		; Y pointer

	sbiw	Y,0x10		; subtract 0x10 from Y (result = 0x559A)

adiw and sbiw can only be used on the registers from r24 through r31. Also, they will only work on register pairs for which the lower is an even number, i.e. r25:r24 are valid operands, but r26:r25 are not.



Multiplication

If you are using an ATmega or XMega microntroller, you have the ability to do hardware multiplication (ATtiny does not implement these instructions).

The instruction mul allows you to compute the unsigned product of any of the 32 general purpose working registers. Because the product of two 8-bit numbers may be up to 16-bits, the result needs two registers. The output of mul will always be stored in r1 and r0, regardless of the operands.

	ldi	r16,0x18	; load r16 with 0x18 (22)
	ldi	r17,0x37	; load r17 with 0x37 (55)
	mul	r16,r17		; multiply r16 and r17 (r1:r0 = 0x0528 = 1210)

The instruction muls computes the product of two signed 8-bit values. Again, the result will be stored in r1 and r0. However, muls only works on registers r16 through r31. The signed component must be in Two's Complement format.

	ldi	r16,0xF2	; load r16 with 0xF2 (-14)
	ldi	r17,0x37	; load r17 with 0xDF (-33)
	mul	r16,r17		; multiply r16 and r17 (r1:r0 = 0x01CE = 462)

If one of your operands is signed and the other is unsigned, then there is an intruction just for you: mulsu. This instruction will treat the first operand as signed and the second as unsigned. The drawback is that mulsu is even more limiting with registers - it will only work on r16 through r23.

	ldi	r16,0xF2	; load r16 with 0xF2 (-14)
	ldi	r17,0x37	; load r17 with 0x83 (131)
	mul	r16,r17		; multiply r16 and r17 (r1:r0 = 0xF8D6 = -1834)

To convert a value to a Two' Complement format, simply add 256 if it is less than zero. For example, to represent -14:

-14 + 256 = 242 (0xF2)


Fractional Multiplication

In addition to multiplying integers, the AVR supports limited multiplication of fractional numbers. This is a topic that is deserving of an entire tutorial on its own, and to be frank a bit more complicated than necessary for a beginner. Feel free to skip this portion for now, but a very brief overview will be given below.

Fractional numbers between [0, 2) can be represented by an 8-bit number in a 1.7 format, where the most significant bit represents an integer component of 0 or 1, and the rest of the bits represent a fractional component as shown in the table below.

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
202-12-22-32-42-52-62-7
10.50.250.1250.06250.031250.0156250.0078125

For example, the binary number 01001100 would equal 0*1 + 1*0.5 + 0*0.25 + 0*0.125 + 1*0.0625 + 1*0.03125 + 0*0.015625 + 0*0.0078125 = 0.59375 using this format.

The instruction fmul computes the sum of two 8-bit numbers in the 1.7 format and outputs the result in r1 and r0 in a 1.15 format. In 1.15, the first bit of the higher byte represents an integer component and the rest of the bits represent fractional components. fmul will only work with r16 through r23.

	ldi	r16,0xC0	; load r16 with 0xC0 (1.5)
	ldi	r17,0xA0	; load r17 with 0xA0 (1.25)
	fmul	r16,r17		; multiply r16 and r17 (r1:r0 = 0xF000 = 1.875)

As with integer multiplication, there is a signed version fmuls that can multiply fractional numbers between [-1,1)

	ldi	r16,0xA0	; load r16 with 0xA0 (-0.75)
	ldi	r17,0xF0	; load r17 with 0xF0 (-0.125)
	fmuls	r16,r17		; multiply r16 and r17 (r1:r0 = 0x0C00 = 0.4375)

And a signed unsigned version fmulsu

	ldi	r16,0xC0	; load r16 with 0xC0 (-0.5)
	ldi	r17,0x60	; load r17 with 0x60 (0.75)
	fmulsu	r16,r17		; multiply r16 and r17 (r1:r0 = 0xD000 = -0.375)

To convert a fractional value to a Two' Complement format, simply add 2 if it is less than zero. For example, to represent -0.75:

-0.75 + 2 = 1.25 (0xA0)



Bit Shifts

The AVR architecture has a number of instructions for shifting the bits in a general purpose working register, shown in the table below.

MnemonicDescription
lsllogical shift left
lsrlogical shift Right
rolrotate left through carry
rorrotate right through carry
asrarithmetic shift right

The lsl and lsr instructions perform logical bit shifts. A zero bit will automatically be shifted into the register in the empty space left by the shift and the bit that is shifted out will be stored as a carry bit in the Status Register (much more on this later). For example

	ldi	r16,0b10101010	; load 1010101010 into r16
	lsl	r16		; shift r16 left (result = 01010100)
	lsr	r16		; shift r16 right (result = 00101010)

The rol and ror instructions work very similar to lsl and lsr, except instead of automatically shifting a zero into the empty space, a one or zero is shifted if a carry occured in the previous operation.

	ldi	r16,0b10101010	; load 1010101010 into r16
	ldi	r17,0b10101010	; load 1010101010 into r17

	lsl	r16		; shift r16 left (result = 01010100)
	rol	r17		; shift r17 left and bring carry (result = 01010101)

In the above example, the most significant bit of r16 is one, so when it is shifted left that one is stored in the status register. When r17 is rotated left, the one from the previous operation is shifted into its least significant bit.

Similarly for ror

	ldi	r16,0b10101010	; load 1010101010 into r16
	ldi	r17,0b10101010	; load 1010101010 into r17

	lsr	r17		; shift r17 right (result = 01010101)
	ror	r16		; shift r16 right and bring carry (result = 01010101)

A zero is shifted out of r17, so when r16 is rotated right, a zero is shifted into its most significant bit.

Bit Manipulation

Lastly, there are a few instructions to manipulate the bits of a register.

MnemonicDescription
sbrset bit(s) in register
cbrclear bit(s) in register
serset register
clrclear register
swapswap nibbles

The instructions sbr and cbr allow you to set or clear bits in a register. sbr and cbr require a constant mask as an operand that determines which bits are set or cleared.

	ldi	r16,0x00	; load 0x00 into r16
	sbr	r16,0x0F	; set lower 4 bits of r16 (result = 0x0F)
	cbr	r16,0x0F	; clear lower 4 bits of r16 (result = 0x00)

You may notice that sbr seems identical to the ori instruction. In fact, it is. Your assembler will generate the exact same machine code regardless of which instruction you use - sbr is really just an alias for the ori instruction.

cbr is almost identical to andi, except that the constant mask needs to be negated to produce the same result. When the cbr instruction is used, the assembler will automatically negate the constant provided and produce machine code for an andi. The following instruction pairs will produce the same output from the assembler.

	ori	r16,0x0F	; this is the same
	sbr	r16,0x0F	; as this

	andi	r16,0xF0	; this is the same
	cbr	r16,0x0F	; as this

Assemblers provid the cbr instruction as a matter of convenience so a negative bit mask does not have to be supplied, and sbr is provided as an alias for ori to maintain consistency.

Even though they don't have the word immediate in the name, sbr and cbr only work on registers 16 through 31.


ser - set register and clr - clear register, allow you to set or clear all bits in a register. They are called with a single register between 16 and 31.

	ser	r16		; set all bits in r16 (result = 0xFF)
	clr	r16		; clear all bits in r16 (result = 0x00)

As before, you may notice that ser is the same as using ldi with 0xFF and clr is the same as performing an eor with the same register as the source and destination (actually, there are several different ways to achieve either of these results). The assembler will replace

	ser	r16
	clr	r16

With the machine code for

	ldi	r16,0xFF
	eor	r16,r16

ser and clr are provided by the assembler as they are quicker to type than the alternatives.

ser and clr are not actual instructions implemented in the architecture, but are provided by the assembler as aliases for other instructions.


Finally, the swap instruction allows you to swap the lower 4 bits with the upper 4 bits of a register (also referred to as nibbles).

	ldi	r16,0x0F	; load 0x0F into r16
	swap	r16		; swap nibbles of r16 (result = 0xF0)

The swap instruction is most useful when working with Binary Coded Decimal (BCDs), where the binary value of two numbers 0-9 are stored in the upper and lower nibbles of an 8-bit number.

Conclusions

Congratulations if you made it all the way through. This was a lot to take in in one sitting. You may have to reread this a few times for it to fully sink in, but you should at least be familiar now with the types of operations that can be performed with the general purpose working registers.

It's probably time to take a look at another practical example, so next we will go over the veritable "Hello World" of embedded systems: Blinking an LED.

Post a Comment

Post a Comment (0)

Previous Post Next Post