AVR Lecture 3
AVR Lecture 3
ADD : The mnemonic is ADD The operands are the destination register (Rd) and the source register (Rr).
The description indicates that this is an add without using the carry bit
The operation indicates that the values in the two registers are added and the result is stored in the
destination register (Rd)
The Z, C, N, V, S, and H bits are affected by this instruction
The instruction takes one clock cycle to execute
ADC :
The mnemonic is ADC
The operands are the destination register (Rd) and the source register (Rr)
The description indicates that this is an add withthe carry bit
The operation indicates that the values in the two registers and the value of the C bit are added and the
result is stored in the destination register (Rd)
The Z, C, N, V, S, and H bits are affected by this instruction
The instruction takes one clock cycle to execute
ADIW :
MOV :
LD :
The data at the memory location pointed to by X is transferred to Rd The value in X is incremented by 1
and then stored back in X The data at the memory location pointed to by X is transferred to Rd The value
in X is decremented by 1 and then stored back in X The order of these statements tells us which is done
first. For the LD Rd,X+, first the data is transferred to Rd then X is incremented. In the LD Rd,-X, first X is
decremented, then data is transferred to Rd
Review of THE REGISTERS:
before we can write assembly programs for the AVR, a good understanding of the registers is important.
There are 32 internal registers in the AVRs typically referred to as R0-R31. The most often used is R16-
R31 because they are easier to use. They can be loaded directly with a constant. For example, if you want
to load 100 into register R16 with 100 you would use:
LDI means LoaD Immediate, R16 is the register and 100 is our constant. Anything after the semi-colon
“;” is a comment which is ignored by the Assembler.
If you want to move the value of 100 into one of the Registers R0-R15 you CANNOT do this:
To move 100 to one of the registers R0-R15 you would do something like:
This moves the 100 into Register R16 first, then we move it from R16 then into R0. Note that the
operands are read right-to-left. The MOV is from R16 to R1 and not the reverse, even though it may
appear to read that way.
So we can see that Registers R16-R31 are easier to use because they are half the work to load. Out of
these, Registers R26-R31 are used as two-byte pointers by more advanced commands, so we should stick
to the ten Registers R16-R25 as our main workspace to start.
REGISTER DEFINITIONS:
We can use the .DEF command to give our registers meaningful names.
.DEF A = R16
.DEF B = R18
.DEF N = R20
.DEF STUDENT_SCORE = R25
Now we can load the R16 Register with 100 using the command:
LDI A,100
CONSTANTS:
Constants are values that do not change value while the program is running. They are defined at the time
your program is assembled into machine language (binary code) and do not change when your program is
executed.
Constants can be given a name with the .SET (or .EQU) command. In our last example we loaded the R16
register with the value of 100. Instead of using the constant 100 we could give it a name like
PERFECT_SCORE with the statements:
Then later in the program we can load R16 with 100 using the command:
LDI R16,PERFECT_SCORE
Constants can be represented in a number of ways. They can be defined as hexadecimal, octal, binary, etc.
All of the following define PERFECT_SCORE as 100:
As we have seen before, a constant can be loaded directly into the Registers from R16 to 31. All of the
following will load R16 with 100:
LDI R16,100
LDI R16,PERFECT_SCORE
LDI R16,(2000+500)/25
LDI R16,$64
LDI R16,0b0110_0100
LDI R16,'d'
LDI A,PERFECT_SCORE ;if you have defined A = R16
The AVRs include a large family of chips. To help us produce code for the various processors, ATMEL
provides a file for each one that contains a series of standard .DEF and .EQU definitions tailored to that
specific chip. For example here is a small clip from the M69DEF.INC file for the ATmega169 processor
that is used in the AVR Butterflys that defines the R26-R31 Registers as two-byte pointers called X, Y
and Z:
The .INCLUDE directive tells the assembler to read in a file as part of our program. For example at the
top of a program for the Butterfly you will typically see:
.INCLUDE "TN13DEF.INC"
You could even create your own libraries of commonly used routine or constants and include them
yourself.
.INCLUDE “MYFILE.ASM”
;--------------------------------------------------;
; BEEP.ASM for AVR BUTTERFLY ;
; MAKE THAT FAMOUS SOUND! ;
; AUTHOR: Name of the Author [email protected] ;
; CREATED: 01-Jul-16 UPDATED: 01-Jul-16 ;
; NOTE: SPEAKER APPEARS TO BE ON PB5 IN SCHEMATICS ;
;--------------------------------------------------;
;-----------------------------------------;
; FIRST WE'LL DEFINE SOME REGISTER TO USE ;
;-----------------------------------------;
.DEF A = R16 ;GENERAL PURPOSE ACCUMULATOR
.DEF I = R21 ;INDEXES FOR LOOP CONTROL
.DEF J = R22
.ORG $0000
;-----------------------------------------;
; FIRST WE SETUP A STACK AREA THEN SET ;
; DIRECTION BIT ON PORT-B FOR OUTPUT/SPKR ;
;-----------------------------------------;
START:
LDI A,LOW(RAMEND) ;SETUP STACK POINTER
OUT SPL,A ;SO CALLS TO SUBROUTINES
LDI A,HIGH(RAMEND) ;WORK CORRECTLY
OUT SPH,A ;
LDI A,0b1111_1111 ;SET ALL PORTB FOR OUTPUT
OUT DDRB,A ;WRITE 1s TO DIRECT REGS
;--------------;
; MAIN ROUTINE ;
;--------------;
BEEP: CLR I
BLUPE:
SER A ;TURN SPKR ON
OUT PORTB,A
RCALL PAUSE ;WAIT
CLR A ;TURN IT OFF
OUT PORTB,A
RCALL PAUSE ;WAIT AGAIN
DEC I
BRNE BLUPE
LOOP: RJMP LOOP ;STAY HERE WHEN DONE
;----------------;
;PAUSE ROUTINE ;
;----------------;
PAUSE:
CLR J
PLUPE:
NOP
DEC J
BRNE PLUPE
RET
If our programming was successful your Butterfly should emit a small beep then stop.
The first part of the program : It starts with some comments starting with the “;” which the Assembler will ignore.
Then we use the .INCLUDE directive to tell the Assembler to read in the file M169DEF.INC which contains
definitions for the ATmega169 chip.
;--------------------------------------------------;
; BEEP.ASM for AVR BUTTERFLY ;
; MAKE THAT FAMOUS SOUND! ;
; AUTHOR: Name of the Author [email protected] ;
; CREATED: 01-Jul-16 UPDATED: 01-Jul-16 ;
; NOTE: SPEAKER APPEARS TO BE ON PB5 IN SCHEMATICS ;
;------------------------------------------------------------------------------------------;
If you are using another chip, like the ATtiny13 you would use AT13DEF.INC. If you have done a standard install in
Windows XP these files can be found in C:\Program Files\Atmel\AVR Tools\AvrAssembler2\Appnotes if you need
to look up the name of the file for the chip you are using.
Next if you are using 3 Volts to power your chip you will need to connect a small speaker to Port B, Pin
5. Check the pin-outs at the top of the data-sheet for your chip which you can download free from
Atmel.com. You can use the small PC speaker from an old computer and connect from Port B, Pin 5 to
either +3 Volts or to ground. (+3 Volts should be slightly louder). If you are using 5 Volts to power your
chip put a small resister in the range of 100 to 220 ohms in series with the speaker to limit the current.
If Port B, Pin 5 is not available, for example on the ATtiny chips it is used as the reset line. Then connect
to another pin on Port B like Pin zero and we will change the program later.
Back to our program. Next we give our own names (A,I,J) to the registers we will be using. Note they are
all in the range of R16 to R25:
;-----------------------------------------;
; FIRST WE'LL DEFINE SOME REGISTER TO USE ;
;-----------------------------------------;
.DEF A = R16 ;GENERAL PURPOSE ACCUMULATOR
.DEF I = R21 ;INDEXES FOR LOOP CONTROL
.DEF J = R22
THE ORIGIN DIRECTIVE:
Before we can actually create our first lines of code, we have to tell the Assembler, where to put it in
program memory. This is done with the Origin Directive “.ORG” and typically AVR programs start at the
bottom of memory at location zero because that is where the AVR chip looks when it is fired-up or reset.
.ORG $0000
The $0000 is just another constant like the ones we discussed earlier so we could have used .ORG 0 or
even something crazy like .ORG 100 -100 however traditionally it is done with the hexadecimal $
notation.
LABELS:
In order to do jumps, loops and subroutines, we need a way to tell the Assembler where to go. Labels are
used for this purpose. They are also used to help us understand what the code is doing. For example, since
we are at the beginning of our program, let us label it “START:”.
Labels typically start at the far left of the screen and the rest of the program is indented. Labels are made
of numbers and letters, but must start with a letter and when we define them they end with the colon”:” To
use a label we do not include the “:”.
we would code:
RJMP START
Before we can call any subroutines, we need to set-up a memory structure called the stack in memory.
Typically our program goes at the bottom-of-memory and the stack goes at the top. Usually programmers
set-up the stack at the start of their programs.
Inside the include files (M169DEF.INC in our case) a constant (RAMEND) is defined to the top of
memory for us. The problem is that if we have more than 256 bytes of RAM, it is going to be more than
we can fit into a single byte. The expressions LOW() and HIGH() will break-down a sixteen-bit number
into two bytes for us. LOW(RAMEND) will give us the lower-byte and HIGH(RAMEND) will give us
the high-byte. Note that these two bytes are constants, and if we want to write them to the Stack Pointer
we must first load them into a register in the R16-R32 range just like any other constant.
The Stack Pointer is a special two-byte memory location that always points to the top item on the stack.
The two memory locations are defined as SPL (low byte) and SPH (high byte). On the AVRs they are
treated like a port so the MOV command will not work and we have to use the OUT command. OUT
typically send the contents of a register to a port location.
START:
LDI A,LOW(RAMEND) ;SETUP STACK POINTER
OUT SPL,A ;SO CALLS TO SUBROUTINES
LDI A,HIGH(RAMEND) ;WORK CORRECTLY
OUT SPH,A ;
Each input/output port of the AVRs has an associated Data Direction Register which is defined in our
include file (M169DEF.INC). For Port B it is DDRB, for Port C it would be DDRC, etc. If we set a bit in
this register to one, then that pin becomes an output pin, on-the-other-hand if we write a zero it becomes
and input pin.
Since we need Port B, Pin 5 to be an output pin and we are not using any other pins on Port B, we can
simply make them all output pins by writing all ones.
Once again we move a constant, this time the value 0b1111_1111 into the R16 Register. Since the Data
Direction Registers are Port Registers we cannot use the MOV command, we must use the OUT
command again:
Now that all our initializations are done, we get into the main part of our program. Since our program
makes a beep on the speaker I have labelled it BEEP, but we could have labelled it MAIN or any name
you wish to use.
We don't want our AVR to beep continuously and be a major annoyance, so we are going to set-up a
counter to limit how long it will beep. We are going to use register R21 which we have previously given
the name “I” and we are going to start it with the value of zero. Normally we might do this by loading it
directly with the command LDI I,0 but we are going to use a new command CLR that will set all the bits
in a register to zero.
The advantage of the CLR command is that it can be used on ALL the registers from R0 to R31. The
opposite of the CLR command is SER (SEt Register) which will set all the bits in a register to one. In fact,
previously we used LDI A,0b1111_1111 to set all the bits in Register A to one, but we could have used
SER A instead.
;--------------;
; MAIN ROUTINE ;
;--------------;
BEEP: CLR I
The way we are going to create a sound coming from the speaker is to activate it then deactivate it by
sending out a series of ones and zeros like 10101010101... etc. This will cause the speaker to move in and
out and thus create a tone. To do this efficiently we will use a loop, and a loop will require a label, so we
call this section of code BLUPE (Beep Loop): BLUPE:
To activate the speaker we need to send out a one to PortB, Pin 5. It is over-kill but since we are not using
any of the other pins on Port B we can use the SER A command to set ALL the bits to one then send them
out to Port B.
Since the processor runs very fast, in the Butterfly it is 2MHz and the default on many other chips is
1Mhz, unless we slow things down the tone emitted from our speaker will be too high to hear. So we use
a subroutine that we call PAUSE that we jump to with the RCALL command that will simply waste some
time for us.
The RCALL command saves our spot on the stack then jumps to the subroutine and continues to execute
the commands located there. The opposite of the RCALL command is the RET (RETurn) command, that
fetches our previous location from the stack so the program can return to where it came from. So every
subroutine must end with a RET command. We indent the RCALL PAUSE command to remind us that
the program is jumping to another location.
Previously we activated the speaker by writing all ones to Port B, now we want to do the opposite so we
use the CLR command to set all the bits in Register A to zero, then send them out to Port B.
Previously we set-up Register I as a counter to limit the length of time the speaker will beep. To do this
we are going to subtract one from our counter and stop after we have activated & deactivated the speaker
256 times.
To subtract one from Register I we could first LDI A,1 then SUB I,A which will subtract the value in A
from the value in I. Similarly we could also ADD I,A if we wanted to add the contents of A to I.
A better way to subtract one from Register I is the SUBtract Immediate command SUBI I,1 that allows us
to subtract a constant from a Register. Unfortunately there is not an add immediate command.
An even better way to subtract one from I is to use the DECrement command DEC I. Adding and
subtracting one from registers is so common that there are separate commands that do just that.
DECrement (DEC I) will subtract one from I and INCrement (INC I) will add one to Register I.
DEC I
JUMPING & BRANCHING:
Typically when we want to jump to a location and not necessarily return we use the RJMP command, but
if we only want to jump based on certain criteria, it is commonly called a BRANCH, like the limb of a
tree.
We are going to decrement the Register I each time through the main loop and stop when it reaches zero.
To do this we are going to use the BRanch if Not Equal to zero instruction (BRNE). So that if the I
register has not hit zero yet, then we branch back to the start of our main loop (BLUPE). When the
Register I hits zero the program will NOT branch, but will continue to the next instruction and exit the
loop.
BRNE BLUPE
If we want to unconditionally jump to another part of the program, we typically use the Relative JuMP
command (RJMP). The RJMP instruction will quickly take us to another part of the program, but it is
limited in the distance we can go. If we run into an error because the label we want to jump to is too far,
then we can use the slower JMP command without any limits on distance.
Some of you may ask, how is it that we start with zero, then subtract one each time through the loop and
still stop at zero? The answer is that when we subtract one from zero the eight-bit register will “roll over”
to 255, then we continue to subtract one from 255 until we reach zero again.
Once we have produced our beep sound, we don't need the processor to do anything more, so we send it
in an infinite loop by having it jump to itself:
To slow down our program so we can hear the tone emitted, we use a subroutine that does nothing but go
in a loop 255 times between activating the speaker and deactivating it. We create this loop by using
another Register we have defined as J and decrementing it until it reaches zero, just as we did in our main
loop, with the BRanch if Not Equal to zero (BRNE) instruction.
To slow-down our program even more we can insert a command that does nothing but wait for one clock
cycle called a No Operation (NOP). If fact we can change the frequency of our tone by adding even more
NOPs.
;----------------;
;PAUSE ROUTINE ;
;----------------;
PAUSE:
CLR J
PLUPE:
NOP
DEC J
BRNE PLUPE
RET
As mentioned earlier, all subroutines must end with a RETurn (RET) instruction.
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 c omplete
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 "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 you look at the truth table above, you 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:
Previously in the main loop of our program, to make the speaker generate a tone we wrote a one to the
speaker port, waited a short time, then wrote a zero, and waited a short time, then repeated the cycle 256
times.
Another way to accomplish a similar result would be to read-in the value at the speaker, and if it is a one,
invert it to a zero, and if it is a zero, invert it to a one. This is where the Exclusive OR (EOR) instruction
can be used because we now know that anything EORed with ones will give us the inverse or opposite.
In our new version of the main part of our program, we can use the Register J to hold the value of all ones
and use it to invert what is on Port B:
;--------------;
; MAIN ROUTINE ;
;--------------;
BEEP: CLR I
BLUPE:
LDI J,0b1111_1111 ;LOAD BITMASK
IN A,PORTB ;READ IN PORT B
EOR A,J ;INVERT/TOGGLE
OUT PORTB,A ;WRITE OUT TO PORT B
RCALL PAUSE ;WAIT
DEC I
BRNE BLUPE
LOOP: RJMP LOOP ;STAY HERE WHEN DONE
We could even further refine our program by only inverting the pin that the speaker is on. Perhaps we
might want to use the other pins on Port B for something other than creating a tone.
On the Butterfly the speaker is connected to Pin 5 of Port B, so we could load the Register J with the
value 0b0001_0000 instead.
The Set Bit in I/O Port (SBI) and Clear Bit in I/O Port (CBI) instructions can be used to set or clear bits in
an I/O Port that will send a one or zero out on the corresponding pin. For example we could use them in
the main loop of our BEEP program to activate the speaker:
;--------------;
; MAIN ROUTINE ;
;--------------;
BEEP: CLR I
BLUPE:
SBI PORTB,5 ;ACTIVATE THE SPEAKER
RCALL PAUSE ;WAIT
CBI PORTB,5 ;SHUT OFF SPEAKER
RCALL PAUSE ;WAIT AGAIN
DEC I
BRNE BLUPE
LOOP: RJMP LOOP ;STAY HERE WHEN DONE
The Data-Sheet tells us that we can Toggle an output pin by writing a one to the input pin (PINx). Each
output port has an associated input register, for PORTB it would be PINB. We can simplify toggling the
speaker:
;--------------;
; MAIN ROUTINE ;
;--------------;
BEEP: CLR I
BLUPE:
SBI PINB,5 ;TOGGLE SPEAKER
RCALL PAUSE ;WAIT
DEC I
BRNE BLUPE
LOOP: RJMP LOOP ;STAY HERE WHEN DONE
THE STACK:
The Stack is a memory structure that is like a stack of plates. You can only ever place new plates on the
top of the stack. For example if there were three items on the stack and you added a fourth:
[TOP] [TOP]
[3] [4] <-- NEW
[2] ==> [3]
[1] [2]
[STACK] [1]
[STACK]
Now if we remove [4] from the top of the stack, then [3] become the top of the stack again. Then if we
remove [3] then [2] is on the top:
The PUSH instruction copies a register to the top of the stack, and POP removes a value from the top of
the stack and copies it into a register.
One common use is to preserve the value of a Register. For example if we wanted our BEEP program to
use only Register A in both the main the loop of our program and in the PAUSE subroutine. We could
push A on the stack before we call the PAUSE routine then pop it off the stack after we come back:
;--------------;
; MAIN ROUTINE ;
;--------------;
BEEP: CLR A ;USE A AS COUNTER
BLUPE: SBI PINB,5 ;TOGGLE SPEAKER
PUSH A ;SAVE CONTENTS OF A
RCALL PAUSE ;WAIT
POP A ;RESTORE A
DEC A
BRNE BLUPE
LOOP: RJMP LOOP ;STAY HERE WHEN DONE
PAUSE: CLR A
PLUPE: NOP
DEC A
BRNE PLUPE
RET
An even better way to do this would be to place the PUSH & POP instructions inside the PAUSE
subroutine. The first thing we do is save A on the stack before its value gets changed, then we restore it
just before we return. This makes the PAUSE routine reusable and portable because it saves & restores
the value of the register it uses: