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.
Mnemonic | Description |
---|---|
ldi | load immediate |
mov | copy register |
movw | copy register pair |
One of the most common instructions you will use is ldi - load immediate. ldi 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 mov, movw 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.
Mnemonic | Description |
---|---|
and | logical AND |
andi | logical AND with immediate |
or | logical OR |
ori | logical OR with immediate |
eor | exclusive OR |
com | one's complement |
neg | two'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 ldi, andi 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 AND, OR 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.
Mnemonic | Description |
---|---|
add | add without carry |
adc | add with carry |
adiw | add immediate to word |
sub | subtract without carry |
subi | subtract immediate |
sbc | subtract with carry |
sbci | subtract immediate with carry |
sbiw | subtract immediate from word |
inc | increment |
dec | decrement |
mul | multiply unsigned(1) |
muls | multiply signed(1) |
mulsu | multiply signed with unsigned(1) |
fmul | fractional multiply unsigned(1) |
fmuls | fractional multiply signed(1) |
fmulsu | fractional multiply signed with unsigned(1) |
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 carry. sub 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 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|
20 | 2-1 | 2-2 | 2-3 | 2-4 | 2-5 | 2-6 | 2-7 |
1 | 0.5 | 0.25 | 0.125 | 0.0625 | 0.03125 | 0.015625 | 0.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.
Mnemonic | Description |
---|---|
lsl | logical shift left |
lsr | logical shift Right |
rol | rotate left through carry |
ror | rotate right through carry |
asr | arithmetic 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.
Mnemonic | Description |
---|---|
sbr | set bit(s) in register |
cbr | clear bit(s) in register |
ser | set register |
clr | clear register |
swap | swap 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