AVR Lecture 4
AVR Lecture 4
AVR Lecture 4
The "AND" operation can be demonstrated with the following circuit of two switches and
a light in series:
It is clear to see that the LED will only illuminate when both switches are closed to
produce a complete circuit. Switch one AND switch two both have to be closed before
the LED will work. This result can be displayed in a truth table where a zero means off
and a one means on:
The "AND" operation can be used to clear a bit to zero. From the truth table above, you
can see that anything that is ANDed with a zero is zero. Lets say you wanted to clear the
high bit of a register, the following code will so just that:
"AND" operations can also be used with a "bit-mask" to strip off bits we are interested in.
For example if we are only interested in the highest four bits of a byte. We can use the
binary number 0b1111_0000 to strip away the high nybble of that register and ignore the
remainder:
The "OR" operation can be demonstrated with the following circuit with two switches in
parallel connected to a light:
Switch_1
-------------/ ---------+
| LED
+--------D
Switch_2 |
------------/ ----------+
It is clear to see that the LED will light when one "OR" the other switch is closed, and
even if both are closed. This can be represented by a truth table:
The "OR" operation can be used to set a bit to one. From the truth table above, you can
see that anything that is ORed with a one is one. Lets say we need to set the high bit of a
register, the following code will do that:
The "EOR" operation is the same as the "OR" operation except that it is off when both
switches are closed. This means the LED is on if one "OR" the other is on, but not if both
are. This can be demonstrated with the following truth table:
If we look at the truth table above, we will see that a one EORed with zero give a one,
and a one EORed with a one gives us zero. EORing something with a one gives us the
opposite or inverse. This gives us the property of flipping a bit. If you need to "blink" the
high bit of a register on and off, the following code will do that without disturbing the
other bits of the "A" register:
LDI B,0b1000_0000
LDI A,0b0101_0101 ;A = 0101_0101
EOR A,B ;A = 1101_0101
EOR A,B ;A = 0101_0101
EOR A,B ;A = 1101_0101
The NOT or inverse operation means you want the opposite, ones become zero and zeros
become one. The truth table for this is:
A NOT_A
0 1
1 0
If we think back to the EOR command, we realize that when we EOR something with a
one, we flip that bit. So to get the inverse or NOT of an entire value, we can EOR it with
all ones:
The left-shift operation moves bits in a register, one place to the left. Why would we want
to do this? Lets look at the example of left-shifting the value of three:
Since computers run on a Binary System, each time we shift a value one bit to the left, we
are multiplying that value by two. If we want to multiply something by eight, we would
left-shift it three times. What the Logical Shift Left command (LSL) does is shift the
highest bit out into the carry flag, shift the contents of the register to the left one bit and
shifts a zero into the lowest bit. The result is a multiplication by two:
CARRY A-REGISTER
[?] [0101_0101] BEFORE LEFT SHIFT VALUE = 85
[0] << [1010_1010] <<0 AFTER LEFT SHIFT VALUE = 85x2 = 170
What if the highest bit was a one? If we are working with more than eight bits we can use
the rotate-left command ROL which rotates the value stored in the carry bit into the
lowest bit and then loads the carry flag with the value that was shifted out the high end:
In the example above the zero that was in the carry flag is shifted into the low end of the
register, the remaining bits are shifted one to the left and the high bit is shifted out and
into the carry flag.
LSL always shifts a zero into the lowest bit, while ROL shifts the contents of the carry
flag into the lowest bit. Using both we can multiply numbers larger than one byte.
To multiply a sixteen bit number by two, we first LSL the lower byte, then ROL the high
byte, this has the net effect of "rolling" the high bit of the lower byte into the first bit of
the 2nd byte. This technique can be expanded to multiply even larger numbers.
The right-shift operation moves bits in a register, one place to the right. Why would we
want to do this? Lets look at the example of right-shifting the value of twelve:
Since computers run on a Binary System, each time we shift a value one bit to the right,
we are dividing that value by two. If we wanted to divide something by eight, we would
right-shift it three times.
What the Logical Shift Right command (LSL) does is shift a zero into the highest bit,
shift the contents to the right and shifts the lowest bit into the carry flag. The result is a
division by two:
A-REGISTER CARRY
[1010_1010] [?] BEFORE RIGHT SHIFT VALUE = 170
0>>[0101_0101]>>[0] AFTER RIGHT SHIFT VALUE = 170/2 = 85
If we are working with more than eight bits and we wish to preserve the value of the
lowest bit, you can use the rotate-right command ROR which rotates the value stored in
the carry bit into the highest bit and then loads the carry flag with the value that was
shifted out the low end.
In the example above the one that was in the carry flag is shifted into the low end of the
register, the remaining bits are shifted one to the right and the low bit is shifted out and
into the carry flag.
LSR always shifts a zero into the highest bit, while ROR shifts the contents of the carry
flag into the highest bit. Using both we can divide numbers larger than one byte.
To divide a sixteen bit number by two, you first LSR the highest byte, then ROR the
lower byte, this has the net effect of "rolling" the low bit of the high byte into the high bit
of the lower byte. This technique can be expanded to divide even larger numbers.
The swap command exchanges the two nybbles of a byte. It exchanges the high four bits
with the lower four bits:
For example if we want to isolate the high byte of register A: ANDI A,0b1111_0000
;MASK OFF HIGH NYBBLE LSR A ;RIGHT SHIFT = /16 LSR A LSR A LSR A
ANDI A,0b1111_0000
SWAP A
The SBI & CBI commands can be used to set or clear bits in an output port respectively.
For example to set PORTB0 for output we need to set the zero-bit of the Data Direction
register for Port B to one:
SBI DDRB,0
To set PORTB0 for input we need to clear bit zero:
CBI DDRB,0
The number following the register DDRB is the bit position to set or clear, it is not the
value to write to the port. For example to set bit seven the command would be:
and not:
The SBIS and SBIC commands are used to branch based on if a bit is set or cleared. SBIS
will skip the next command if a selected bit in a port is set, and SBIC will skip the next
command if the bit is clear. For example if we are waiting for an ADC to complete, we
poll the ADIF Flag of the ADCSRA register to. We could read in the register and check if
the flag has been set with:
Or we can accomplish the same with the SBIS command that checks the status of the 4th
bit without the need to read the port into a register:
The SBRS and SBRC commands function the same as the SBIS/SBIC commands except
that they work on registers instead of ports.
A single register in the 8-Bit AVRs are 8 bits wide so they can hold values from zero to
255 (zero to $FF in Hex). To work with larger numbers we combine the use of several
registers. First we will examine commands that work with single registers and later we
will combine these commands to handle larger numbers.
1. ADDING AND SUBTRACTING ONE FROM A REGISTER:
Adding and subtracting one to a register is done so often, such as modifying counters in
loops, that there are specific commands that do just that. The increment (INC) and
decrement (DEC) will add or subtract a one from a register.
Another advantage of using the increment (INC) and decrement (DEC) commands over
standard addition and subtraction is that they require less operands and work with all
registers R0 to R31. Some commands only work on registers R16 to R31.
.DEF N = R0
SER N ;SEt Register N to all ones = 255 ($FF)
LOOP: DEC N ;DECrement N by one
BRNE LOOP ;Keep looping until N equals zero
DONE: ;N (R0)=ZERO so we are done our loop
Instructions that work with constants are referred to as “immediate” mode instructions.
To load a constant value into a register we use the LoaD Immediate (LDI) command.
.DEF A = R16
LDI A,10 ;Set A (R16) to value of 10
Only registers in the range R16 to R31 can be loaded immediate. We cannot load a
constant into the registers R0 to R15 directly. It would have to be loaded into a valid
register first then copied. To load the value of 10 into register zero (R0):
.DEF N = R0
.DEF A = R16
LDI A,10 ;Set A (R16) to value of 10
MOV N,A ;Copy contents of A(R16) to N(R0)
3. ADDING AND SUBTRACTING A CONSTANT FROM A SINGLE
REGISTER:
Constants are values that do not change during the execution of a program. Since they
don't change in value, there is no need to use an additional registers to store them.
To subtract a constant from a register we will use the SUBtract Immediate instruction
(SUBI). Please note that the SUBI command does not work with Registers Zero to
Fifteen (R0-R15), so our working register must be in the range of (R16 to R31). In the
example below we use Register 16 (R16).
Adding a constant to a register is a little tricky using AVR ASM because it does not have
an add immediate instruction. The way we can do it is to negate the constant we wish to
add and subtract that negative constant from the register. This takes advantage of the fact
that subtracting a negative number results in a double negative that is the same as
addition: X-(-2)=X+2.
To add two registers together we simply load the registers with the values we want and
use the ADD instruction which works with all registers R0 to R31.
Since a single register can only hold 8 bits with a maximum value of 255. To be complete
we should check if our total is more than 255. When we ADD two registers together that
total more than 255 the “Carry Flag” will be set automatically.
For us to subtract two registers we use the subtract (SUB) command. For example to
convert a numerical character to its numerical value we subtract 48.
If we need to check if the result of our subtraction is negative, the carry flag is
automatically set when our result goes below zero. We can check that flag with the
Branch if Carry Set (BRCS) command.
If we wish to work with numbers greater than 255 we can use two registers together to
give us a range from zero to 65,535 ($FFFF). To automatically break a constant like 420
into two bytes we can use the HIGH and low functions.
We can expand this method for numbers up to four bytes long with the BYTE2(),
BYTE3() and BYTE4() functions. This gives us a range from zero to 4,294,967,295
approximately 4.3 Billion.
To subtract a two byte constant from a two byte value stored in the register pair AL/AH
we first subtract the low byte of the constant from the low byte register (AL). If the result
of the subtraction is negative the carry flag gets set. This carry flag is used by the
SuBtract with Carry Immediate command (SBCI) below to indicate that the result of
subtracting the two lower bytes resulted in a negative number so a one needs to be
“borrowed” from the result of subtracting the two high bytes.
Since the AVRs do not have a Add Immediate instruction we have to subtract the
negative of the constant we wish to add. Once again we use the low() and high()
functions to break our constant into two bytes.
To add two 16 bit registers we first add the two lower bytes together with the ADD
command, then we add the two high bytes together and include the carry bit which
contains any overflow from the addition of the lower bits. This is accomplished with the
Add with Carry command (ADC).
For subtraction we use a similar procedure to addition, we first subtract the lower bits
with the subtract command (SUB) then we subtract the two high bytes including the carry
bit with the subtract with carry command (SBC). The carry flag will indicate that the
addition of the lower bytes resulted in a negative number and that a one needs to be
“borrowed” from the high bytes. If the carry flag is one, by subtracting its value from the
high bytes we accomplish the needed borrowing of one.
If your AVR chip supports the multiplication command (MUL) then multiplying two
eight-bit numbers is quite simple. MUL will work on all 32 registers R0 to R31 and leave
the low-byte of the result in R0 and the high-byte in R1. The registers for multiplicand
and multiplier remain unchanged. The routine takes about three cycles.
If our AVR does not support the hardware MUL command we will have to compute
multiplications manually. If we need to multiply by a power of two such as 2,4,8 etc. The
result can be achived by shifting bits to the left. Each shift to the left is a multiplication
by two.
The Logical Shift Left (LSL) command is used on the lower byte because it will shift the
contents one bit to the left, a zero is shifted into the lowest bit and the highest bit is
shifted into the carry flag.
10101010
Carry [1] <-- 01010100 <--0 (LSL)
We use the Rotate Left though Carry (ROL) command on the high byte because it will
also shift contents one bit to the left, but it will shift the contents of the Carry Flag into
the lowest bit.
00000000
Carry [0] <-- 00000001 <--[1] Carry (ROL)
Every time we shift the multiplicand to the left we are multiplying it by two. So to
multiply by eight we simply shift the multiplicand to the left three times. The routine
takes about ten cycles.
00101010 = 42 multiplicand
x00001010 = 10 multiplier
--------
00000000
00101010
00000000
00101010
00000000
00000000
00000000
00000000
----------------
0000000110100100 = 420 result
================
The routine below mimics the hardware multiply (MUL) by leaving the multiplicand and
multiplier untouched, and the result appears in the register pair R1 and R0. It shifts the
bits of the multiplier into the carry bit and uses the contents of the carry to add the
multiplicand if it is a one or skip it if the carry is a zero. The routine takes about sixty
cycles.
Multiplying two 16-bit numbers can result in a four-byte result. We use the hardware
multiply (MUL) command to create all four cross products and add them to the 32-bit
result. The MUL command leaves it results each time in R1:R0 which we then add into
our result. The routine takes about twenty cycles.
MUL16x16:
CLR ZERO ;Set R2 to zero
MUL AH,BH ;Multiply high bytes AHxBH
MOVW ANS4:ANS3,R1:R0 ;Move two-byte result into answer
MUL16x16:
CLR ANS3 ;Set high bytes of result to zero
CLR ANS4 ;
LDI C,16 ;Bit Counter
LOOP: LSR BH ;Shift Multiplier to the right
ROR BL ;Shift lowest bit into Carry Flag
BRCC SKIP ;If carry is zero skip addition
ADD ANS3,AL ;Add Multiplicand into high bytes
ADC ANS4,AH ;of the Result
SKIP: ROR ANS4 ;Rotate high bytes of result into
ROR ANS3 ;the lower bytes
ROR ANS2 ;
ROR ANS1 ;
DEC C ;Check if all 16 bits handled
BRNE LOOP ;If not then loop back
The following routine will multiply two 32-bit numbers with a 64-Bit (8 Byte) result. The
routine takes about 500 clock cycles.
The Logical Shift Right (LSR) command is used on the higher byte because it will shift
the contents one bit to the right, a zero is shifted into the highest bit and the lowest bit is
shifted into the carry flag.
01010101
0--> 00101010 -->[1] Carry
We use the Rotate Right though Carry (ROR) command on the low byte because it will
also shift contents one bit to the right, but it will shift the contents of the Carry Flag into
the highest bit.
00000000
Carry [1] --> 10000000 -->[0] Carry (ROL)
Every time we shift the multiplicand to the right we are dividing it by two. So to divide
by eight we simply shift the multiplicand to the right three times. The routine takes about
ten cycles.
Just as multiplication can be achieved with shifting and addition. Dividing can be
accomplished by shifting and subtraction. The routine below tries to repeatedly subtract
the divisor. If the result is negative it reverses the process and shifts the dividend to the
left to try again. The routine takes about 90 cycles.
The previous routine can be expanded to handle divide two-byte numbers in the range of
zero to 65,535. The routine takes about 230 cycles.
The previous routine can be futher expanded to handle 32-bit number divided by a 16-bits
number, This means numbers in the range of zero to 4,294,967,295 (4.3 billion) divided
by numbers in the range zero to 65,535. The routine takes about 700 cycles.
DIV3216:
CLR ZERO
MOVW ANS2:ANS1,A2:A1 ;Copy dividend into answer
MOVW ANS4:ANS3,A4:A3 ;
LDI C,33 ;Load bit counter
SUB REM1,REM1 ;Clear Remainder and Carry
CLR REM2 ;
CLR REM3 ;
CLR REM4 ;
LOOP: ROL ANS1 ;Shift the answer to the left
ROL ANS2 ;
ROL ANS3 ;
ROL ANS4 ;
DEC C ;Decrement Counter
BREQ DONE ;Exit if 32 bits done
ROL REM1 ;Shift remainder to the left
ROL REM2 ;
ROL REM3 ;
ROL REM4 ;
SUB REM1,BL ;Try to subtract divisor from remainder
SBC REM2,BH ;
SBC REM3,ZERO ;
SBC REM4,ZERO ;
BRCC SKIP ;If the result was negative then
ADD REM1,BL ;reverse the subtraction to try again
ADC REM2,BH ;
ADC REM3,ZERO ;
ADC REM4,ZERO ;
CLC ;Clear Carry Flag so zero shifted into A
RJMP LOOP ;Loop Back
SKIP: SEC ;Set Carry Flag to be shifted into A
RJMP LOOP
DONE:
To compute the square of an eight-byte number we can take advantage of the fact that the
sum of the odd numbers create square numbers:
1 = 1^2 = 1
1+3 = 2^2 = 4
1+3+5 = 3^2 = 9
1+3+5+7 = 4^2 = 16
1+3+5+7+9 = 5^2 = 25
If we study the above table we notice that the square-root of the number equals the
number of odd numbers we have summed. We simply create a routine that keeps track of
the total of odd numbers that have been subtracted. The routine takes fifteen to one
hunded cycles.
SQRT:
LOOP:
SUB A,B ;Subtract B from Square
BRCS DONE ;If bigger than sqaure we are done
INC ANS ;Increment the answer
SUBI B,-2 ;Increment B by two
RJMP LOOP
We could expand the routine above to handle the square-root of a sixteen-bit number but
the number of clock cycles can be as high as 3750. The routine below processes two bits
at a time starting with the highest bits and also provides the remainder. It uses about 160
clock cycles.
LDI AL,LOW($FFFF)
LDI AH,HIGH($FFFF)
SQRT16:
PUSH AL ;Save Square for later restore
PUSH AH ;
CLR REML ;Initialize Remainder to zero
CLR REMH ;
CLR ANSL ;Initialize Root to zero
CLR ANSH ;
LDI C,8 ;Set Loop Counter to eight
LOOP:
LSL ANSL ;Multiply Root by two
ROL ANSH ;
LSL AL ;Shift two high-bits of Square
ROL AH ;into Remainder
ROL REML ;
ROL REMH ;
LSL AL ;Shift second high bit of Sqaure
ROL AH ;into Remainder
ROL REML ;
ROL REMH ;
CP ANSL,REML ;Compare Root to Remainder
CPC ANSH,REMH ;
BRCC SKIP ;If Remainder less or equal than Root
INC ANSL ;Increment Root
SUB REML,ANSL ;Subtract Root from Remainder
SBC REMH,ANSH ;
INC ANSL ;Increment Root
SKIP:
DEC C ;Decrement Loop Counter
BRNE LOOP ;Check if all bits processed
LSR ANSH ;Divide Root by two
ROR ANSL ;
POP AH ;Restore Original Square
POP AL
RJMP LOOP
We can expand the previous routine to handle 32-bit numbers. It takes between 500 to
580 clock cycles to complete.
Since there are only seven possible cube roots of a single-byte number (0 to 6) this
routine simply cycles through them until the correct root is found. The routine takes from
20 to 90 clock cycles.
The following routine finds the cube root by using two successive approximations, one
high and one low and waits until they merge. The routine uses 150 to 250 clock cycles.
;----------------------------;
; Calculates Cube of B ;
; Results in CUB3:CUB2:CUB1 ;
;----------------------------;
CUBE:
MUL B,B ;Calc B*B
MOVW TMP2:TMP1,R1:R0 ;
MUL TMP1,B ;Calc B*B*B
MOVW CUB2:CUB1,R1:R0 ;
MUL TMP2,B ;
ADD CUB2,R0 ;
CLR CUB3 ;
ADC CUB3,R1 ;
RET
2c. LOGARITHMS
CONTENTS:
The advantage of using integer math is the speed as versus the much slower speed and
large size of floating-point math, however answers can be subject to larger rounding
errors with integer math.
The integer value of logarithm base two of a number is useful in determining the number
of bits required to store the number. The routine works by shifting the number left until a
one falls out. The routine yields an error of about 1% and uses 10 to 40 clock cycles.
We can expand the previous routine to handle 16-bit numbers. The routine uses 20 to 100
clock cycles and should have an error of about 0.1%.
The value of e is 2.718281828. To estimate this with integer math we use the ratio 87/32
which is 2.71875 yielding an error of 0.017%. We first multiply our number by 87 then
shift the result five times to the right for a division of 32. The routine is about 16 bytes
long and takes about 28 clock cycles.
To estimate the value of e = 2.718281828 we use the ratio 5567/2048 = 2.71826 which
yields an error of 0.001%. The routine is about 40 bytes long and takes about 24 clock
cycles.
The number 1/e = 0.367879441 can be estimated with the ratio 94/256 which is 0.36719
with an error of 0.19%. The routine is about 6 bytes long and takes about 2 clock cycles.
The constant 1/e = 0.367879441 can be estimated by the ratio 24109/65536 = 0.36787
with an error of 0.001%. The routine is about 36 bytes long and takes about 20 clock
cycles.
MUL16x16:
CLR ZERO ;Set Zero
MUL AH,BH ;Multiply Original Value by 24109
MOVW WRK4:WRK3,R1:R0 ;
MUL AL,BL ;
MOVW WRK2:WRK1,R1:R0 ;
MUL AH,BL ;
ADD WRK2,R0 ;
ADC WRK3,R1 ;
ADC WRK4,ZERO ;
MUL BH,AL ;
ADD WRK2,R0 ;
ADC WRK3,R1 ;
ADC WRK4,ZERO ;
MOV ANS2,WRK4 ;Store Answer
MOV ANS1,WRK3 ;By ignoring the lower two bytes we
;get a division by 65536
Previously we created routines to calculate the log base two of a number. From the
equations below we see that by multiplying the log2 of a number by 0.6931472 we can
calculate the natural logarithm.
ln(x) = log2(x)/log2(e)
= log2(x) * 1/1.442695
= log2(x) * 0.6931472
= C * Log2(x) ; where C = 0.6931472
The routine is about 40 bytes long and uses about 50 to 80 clock cycles the error should
be less than 1%.
Previously we created routines to calculate the log base two of a number. From the
equations below we see that by multiplying the log2 of a number by 0.6931472 we can
calculate the natural logarithm.
ln(x) = log2(x)/log2(e)
= log2(x) * 1/1.442695
= log2(x) * 0.6931472
= C * Log2(x) ; where C = 0.6931472
The routine is about 70 bytes long and takes about 40 to 120 clock cycles.
.DEF ANSF = R2 ;Fractional Part of Answer
.DEF ANS = R3 ;Integer Part of Answer
.DEF TMP1 = R4 ;Temporary Workspace
.DEF TMP2 = R5 ;
.DEF TMP3 = R6 ;
.DEF TMP4 = R7 ;
.DEF ZERO = R10 ;Hold Zero
.DEF AL = R16 ;Original Value
.DEF AH = R17 ;
.DEF N = R20 ;Counter/Index
Previously we created routines to calculate the log base two of a number. From the
equations below we see that by multiplying the log2 of a number by 0.301030004 we can
calculate the logarithm base 10.
log10(x) = log2(x)/log2(10)
= log2(x) * 1/3.321928
= log2(x) * 0.301030004
= C * Log2(x) ; where C = 0.0.301030004
We will use the following ratio to estimate the constant above.
The routine is about 40 bytes long and takes about 20 to 50 clock cycles.
Previously we created routines to calculate the log base two of a number. From the
equations below we see that by multiplying the log2 of a number by 0.301030004 we can
calculate the logarithm base 10.
log10(x) = log2(x)/log2(10)
= log2(x) * 1/3.321928
= log2(x) * 0.301030004
= C * Log2(x) ; where C = 0.0.301030004
The routine is about 70 bytes long and takes about 40 to 125 clock cycles.