AVR
From Ggl's wiki
What is an AVR?
The AVR [1] is a family of Atmel's 8-bit microcontrollers that implements a modified Harvard RISC architecture[2].
An AVR stores program instruction in a on-chip flash memory. This way the MCU[3] might be reprogram several times (10 000 write/erase cycles for an Attiny85).
Why yet another architecture?
AVR is optimised for small lower-power applications (an Attiny85 consumes 300uA in active mode and 0.1uA in power-down mode at 1.8V both[4]). It provides a simple RISC instruction set of 120 instructions. Most of them execute in a single clock cycle.
It can be programmed with a low-cost programmer connected to a computer's usb, serial or parallel port (if you still have one). It comes in different packages and some are easy to use on a breadboard or a prototyping board. However when the time to make a actual product comes you can easily switch to another package and solder it to a PCB.
Which AVR?
It really depends on you actually need. For prototyping, an atmega168/328 provides lots of features but comes in a bigger package and consumes more power. If you only need to read or write values on somes pins, an ATtiny should be fine as it provides:
- an analog comparator
- an ADC
- timers
However if you need to communicate through serial UART or SPI, a realtime counter or bootloader support, you will need something more featureful like an atmega328.
What you really get by using a microcontroller is the hardware fits your need and then is simple, low-power and efficient. That's why choosing the good mcu is not trivial :).
PINS
ATtiny85
Port B is a 6-bit bi-directional I/O port with internal pull-up resistors (selected for each bit). The Port B output buffers have symmetrical drive characteristics with both high sink and source capability. As inputs, Port B pins that are externally pulled low will source current if the pull-up resistors are activated. The Port B pins are tri-stated when a reset condition becomes active, even if the clock is not running.
Each port pin consists of three register bits: DDxn, PORTxn, and PINxn. As shown in “Register Description” on page 66, the DDxn bits are accessed at the DDRx I/O address, the PORTxn bits at the PORTx I/O address, and the PINxn bits at the PINx I/O address[5].
- DDRx select if the port x is input (0) or output (1).
- PORTx set the port in low (0) or high (1) state.
- PINx allow to read the value of port independantly of its direction.
Setting ports
To set a port as output, just set it's bit in DDRB (Data Direction Register). Let set the port PB4 as output:
DDRB |= 1<<PB4;
Ok, now set PB4 to high (e.g. set 5th bit - remember we start at PB0 - to logical 1):
PORTB |= 1<<PB4; /* set PB4 high */ _delay_ms(100); /* wait 100 ms */ PORTB &= ~(0x00|PB4); /* set PB4 low */ _delay_ms(100); /* wait again to blink */
Assembly is also straightforward:
in r16, PORTB andi r16, (1<<PB4) in r17, DDRB andi r17, (1<<DDB4) out PORTB, r16 out DDRB, r17 nop
note: reading PORTB and DDRB might be useless because their states were probably saved before.
As I'm not sure the code above is correct, this one does not try to keep the state of other ports:
ldi r16, (1<<PB4) ldi r17, (1<<DDB4) out PORTB, r17 out DDRB, r16 nop
note: nop is recommended for synchronization[5]
If the pin is enabled in some modes (external interrupt for example), it can override the SLEEP.
Below is a simple reference for setting bits:
ATmega328
Memory
An AVR provides three memories area:
- Program: Flash (In-System Re-programmable Flash Program Memory), ndurance of at least 10,000 write/erase cycles. The size and the number of memory locations depends on the MCU model. For example, ATtiny25/45/85 contains 2/4/8K byte for flash program memory, organised as 1024/2048/4096 x 16. Program Counter (PC) is respectively 10/11/12 bits wide. Range from 0x0000 to 0x03FF/0x07FF/0x0FFF.
- Data: SRAM
- EEPROM: 128/256/512 bytes of data EEPROM memory. separate data space, in which single bytes can be read and written. Endurance of at least 100,000 write/erase cycles.
Registers
AVR 8-bit MCUs have 32 8-bit registers from R0 to R31. Registers from R0 to R15 do not support some commands like LDI. Only the registers from R16 to R31 load a constant immediately with the LDI command.
| ANDI Rx,K | Bit-And of register Rx with a constant value K |
|---|---|
| CBR Rx,M | Clear all bits in register Rx that are set to one within the constant mask value M |
| CPI Rx,K | Compare the content of the register Rx with a constant value K |
| SBCI Rx,K | Subtract the constant K and the current value of the carry flag from the content of register Rx and store the result in register Rx |
| SBR Rx,M | Set all bits in register Rx to one, that are one in the constant mask M |
| SER Rx | Set all bits in register Rx to one (equal to LDI Rx,255) |
| SUBI Rx,K | Subtract the constant K from the content of register Rx and store the result in register Rx |
The pairs R26:R27, R28:R29 and R30:R31 are also respectively 16-bit pointer registers X, Y and Z. The first register stores the lower bits and the second the higher ones.
Instruction Set
Atmel AVR Instruction Set (wikipedia)
Asm
Assembly is the closest language to the machine after machine language itself (binary values). It requires to really understand how the hardware works and you will really know what the program is doing. I believe knowing assembly is a good way to learn how the hardware works. It also helps to debug programs because you often disassemble code and analyse asm in debug sessions.
Gerhard Schmidt AVR Asm Tutorial . The author publishes a free avr assembler gavrasm. Unfortunately the license is not actually free as in freedom :(. You may write assembly for gcc avr-as instead if you feel concerned by the software license. Another great resource is Atmel AVR Assembler User Guide.
The C language partly aims to write portable programs. Portable means it should abstract the underlying hardware and allow the program to use common data and control structures. However writing programs for microcontrollers sometimes requires to exactly know what is happening on the hardware. You would like to know how many cycle a procedure takes for example. It is really hard to do in C because the compiler will generate assembly code. When performance is at the algorithm level, the C language helps to focus on high-level optimizations. However when you deal with interrupts, ports input/output C library mostly brings opacity and no more efficiency. Furthermore with a RISC insruction set it is easier to know which instruction to use. All these reasons make me think AVR programming should be learned from bottom-up. Then if a programmer wants to write high-level algorithms, she knows if she should write them in assembly or in C, depending on the time she has and how she can optimize the algorithms.
GNU Toolchain
C language provides a higher-level interface to AVR microcontroller. It allows to abstract some specific parts and write more portable code.
Build
Atmel provides a complete toolchain with AVR Studio. I prefer to use the free - as in freedom - GNU toolchain (that AVR Studio also use):
- avr-libc
- gcc-avr
- binutils-avr
- avrdude
- gbd
And my common programming tools (vim, shell, etc...).
Libraries
You can find some libraries avrlib, mmclib.
You can also re-use Arduino libraries (Wiring API like digitalWrite(), analogRead(), init(), etc ...). Checkout the code:
$ svn checkout http://arduino.googlecode.com/svn/trunk/hardware arduino-hardware
and build it as a library to directly compile the code you need.
Basic Software Workflow[6]
- Write the source code with your favorite editor
- Compile the code with gcc-avr:
$ avr-gcc -g -Os -mmcu=atmega8 -c hello.c
- Link into the binary:
$ avr-gcc -g -mmcu=atmega8 -o hello.elf hello.o
- Check the size of the sections (because size matters in MCU world):
$ avr-objdump -h -S hello.elf > hello.objdump $ avr-gcc -g -mmcu=atmega8 -Wl,-Map,demo.map -o hello.elf hello.o
- Convert binary to Intel Hex format with avr-objcopy:
$ avr-objcopy -j .text -j .data -O ihex hello.elf hello.hex
- Copy the binary to the MCU's flash with avrdude
$ avrdude -c stk500 -p t85 /dev/ttyACM0 -U flash:w:hello.hex
You may also test your code with simulavr.
Makefile
I detailed the command line to show the explicit workflow. However, we will write a Makfile to automate and re-use to it in other projects.
I took /usr/share/doc/avr-libc/examples/demo/Makefile and removed unused variables and sections. I did not include the eeprom part because this example does not use EEPROM. Specific variables for a project are PRG, OBJ, and DEPS at the beginning of the Makefile. Then uncomment the MCU_TARGET corresponding to the MCU you are programming. AVRDUDE, PROTO, SERIAL, and MCUPART are also specific. Take care of indenting lines with a tabulation and not spaces (with vim you may use the modeline #vim:ft=Makefile,ts=8,sw=8,noet).
The Makefile below is for ATtiny85 with commented lines for ATtemga328p.
PRG = blink
OBJ = blink.o
DEPS = blink.c
MCU_TARGET = attiny85
#MCU_TARGET = atmega328p
OPTIMIZE = -Os
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
CFLAGS = -g -Wall $(OPTIMIZE) -mmcu=$(MCU_TARGET) $(DEFS)
LDFLAGS = -Wl,-Map,$(PRG).map
AVRDUDE = /usr/bin/avrdude
PROTO = stk500
SERIAL = /dev/ttyACM0
MCUPART = t85
#MCUPART = m328p
all: $(PRG).elf lst text
$(PRG).elf: $(OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)
# dependency:
$(OBJ): $(DEPS)
install:
$(AVRDUDE) -c $(PROTO) -P $(SERIAL) -p $(MCUPART) -U flash:w:$(PRG).hex
clean:
rm -rf *.o $(PRG).elf $(PRG).hex
rm -rf *.lst *.map
lst: $(PRG).lst
%.lst: %.elf
$(OBJDUMP) -h -S $< > $@
# Program .text
text: hex
hex: $(PRG).hex
%.hex: %.elf
$(OBJCOPY) -j .text -j .data -O ihex $< $@
How to program an AVR?
Several programmers exists. I bought the Olimex AVR-ISP500 from Sparkfun and to development board Olimex AVR-P28 and AVR-P8]. It works with avrdude. However the last low-cost Atmel's programmer, AVR Dragon provides all programming protocol and also debugging. You might bought some lower cost programmer or even do-it-yourself, but you should check before that it can program the target mcu.
I use avrdude on linux (debian sid with 2.6.32.3 kernel). My usb programmer requires the cdc-acm module (in usb device drivers). cdc-acm binds /dev/ttyACMx devices (x=0 for first device).
$ avrdude -c stk500 -p t85 /dev/ttyACM0 -U flash:w:hello.hex
You may read other examples of avrdude usage.
How to choose a Power Source?
You need to provide power to your MCU. Then you need to know the voltage and current it requires. We find the following table in the ATmega48PA/88PA/168PA/328P datasheet, part 28. Electrical Characteristics:
- Maximum Operating Voltage: 6.0V
- DC Current per I/O Pin: 40.0 mA
- DC Current VCC and GND Pins: 200.0 mA
You may have a transformer that provide 6V and a current below 200 mA. If you don't have one, you can take another and adapt the voltage and current to the ones the MCU requires. We start with a circuit on a breadboard powered by a 9V battery. We have 9V in input and want an output of a bit above 5V. For that purpose we use a voltage regulator (like a LM317). We need to resistors and two capacitors.
Where to buy?
- Sparkfun and Adafruit, more expansive than the others, but the web site is clearer and you can buy some neat components[7].
The three shops below are cheaper than Sparkfun but it somewhat hard to browse their catalog if you don't have an accurate reference. However once you know what you want it is really cheaper (most of them also provide country specific sub-sites like fr.digikey.com or fr.mouser.com).
Checklist before starting a project
The following examples are basic and we will do them with an Atmel Attiny85. Common steps are involved before doing the project in practice:
- Get the datasheets of all components
- Read the electrical requirements (to know the resistors and capacitors to add)
- Define the I/O ports to use
- Define the power source (and then how to provide the required voltage and intensity to the components)
Examples
Blink LED (outpu/delay)
Concepts:
- digital output
- basic delay (with _delay_ms())
Blinking a LED is the electronics equivalent of a "Hello world!" program in a computer language.
Part List
To make a LED blinking in this example, you need:
- a LED
- a resistor (we will see how to calculate is resistance value)
- a MCU
- a power source
- datasheet of all components
Hardware Design
First, choose the port you connect to the LED input. Then answer the following questions:
- What is the output low voltage of the port?
- What is the forward voltage drop of the LED?
- What is the voltage across the resistor?
- What intensity does the mcu sink according to its operational voltage (5V or 3V)?
- What is the resistance of the resistor?
- How much power will the resistor have to dissipate?
Example[8]:
- What is the output low voltage of the port?
The output low voltage of an ATtiny15 I/O pin is 0.6 V when the processor is operating on a 5 V supply and 0.5 V when operating on a 3 V supply. Let's assume that we are using a power supply (VCC) of 5 V, then output low voltage is 0.6V.
- What is the forward voltage drop of the LED?
The LED has a forward voltage drop of 1.6V.
- What is the voltage across the resistor?
Sending the output low places the LED's cathode at 0.6 V. This means the voltage difference between VCC (5 V) and the cathode is 4.4 V. If the LED has a voltage drop of 1.6 V, this means the voltage drop across the resistor is 2.8V (5 V - 1.6 V - 0.6 V = 2.8 V).
- What intensity does the MCU sink according to its operational voltage (5V or 3V)?
The digital I/O pins of an AVR can sink up to 20 mA[9]if the processor is running on a 5 V supply.
- What is the resistance of the resistor?
The resistor limits the current flow to the supported amount (20mA as we've seen previously). If the resistor has a voltage difference across it of 2.8 V (as we calculated) and a current flow of 20 mA, then from Ohm's Law:
R = V / I = 2.8 / 20 = 140 Ω
The closest available resistor value to this is 150Ω, so that's what we'll use (this will give us an actual current of 18.6 mA, which is fine). - How much power will the resistor have to dissipate?
If we try to pump too much current through the resistor, we'll burn it out. Power is calculated by:
P = V * I = 2.8 * 20 = 0.056 Watts = 56 mW
The resistor value we need for R is 150Ω and 0.0625W (0.0625W is the lowest power rating commonly available in resistors).
These were small and very detailed steps. In practice, it does not take too much time to get all these value.
Firmware
Ok, now we have done the hardware design part. Let's write the source code. Even if it appears over-simple we call it the firmware :). The code below is for an ATmega328p. The LED on the development board is connected to the port PC5 and the button to PD2. Be careful on the schematic below (from AVR-P28 datasheet page 3), PINs locations do not correspond to there physical place on the MCU (see its datasheet for that).
#define F_CPU 1000000UL /* ATmega328 at 1MHz by default*/
#include <avr/io.h>
#include <util/delay.h>
int
main(void)
{
DDRC = 0x00; /* Reset port direction */
PORTC = 0x00; /* Reet PORTB, put all ports low */
DDRC |= 1<<PC5; /* Configure port PB0 as output */
for (;;) { /* Always blink */
PORTC ^= 1<<PC5; /* Turn LED off */
_delay_ms(500); /* Wait 500 ms */
PORTC |= 1<<PC5; /* Turn LED on */
_delay_ms(500); /* Wait 500 ms */
}
}
Note: PORTC ^= 1<<PC5 (^ is exclusive or, xor) is equivalent to PORTC &= ~(1<<PC5) (with no parentheses and an operator less).
We need to define the macro F_CPU because _delay_ms() is a convenience function. It is supposed to be defined to a constant defining the CPU clock frequency (in Hertz)[10].
By default on an ATmega328p The device is shipped with internal RC oscillator at 8.0MHz and with the fuse CKDIV8 programmed, resulting in 1.0MHz system clock. The startup time is set to maximum and time-out period enabled (CKSEL = "0010", SUT = "10", CKDIV8 = "0"). The default setting ensures that all users can make their desired clock source setting using any available programming interface.
Read the documentation to understand how to use _delay_ms():
"Note: As an alternative method, it is possible to pass the F_CPU macro down to the compiler from the Makefile. Obviously, in that case, no #define statement should be used. The functions in this header file are wrappers around the basic busy-wait functions from <util/delay_basic.h>. They are meant as convenience functions where actual time values can be specified rather than a number of cycles to wait for. The idea behind is that compile-time constant expressions will be eliminated by compiler optimization so floating-point expressions can be used to calculate the number of delay cycles needed based on the CPU frequency passed by the macro F_CPU. Note: In order for these functions to work as intended, compiler optimizations must be enabled, and the delay time must be an expression that is a known constant at compile-time. If these requirements are not met, the resulting delay will be much longer (and basically unpredictable), and applications that otherwise do not use floating-point calculations will experience severe code bloat by the floating-point library routines linked into the application. The functions available allow the specification of microsecond, and millisecond delays directly, using the application-supplied macro F_CPU as the CPU clock frequency (in Hertz)."
If you wonder why the CPU Clock Frequency is important, think that _delay_ms() does not rely on a timer but on a busy loop. A busy loop use the number of clock cycles to approximate time. It heavily depends on the underlying oscillator precision and stability (which may depends on temperature). Busy looping works in this context because only a single task is running on the CPU. This technique was also used in old time of personal computer for example in DOS (remember the time of the real-time mode ;) ). In a multitasking environment, execution switches between multiple tasks. This action is called scheduling.
If we have needed an accurate delay we should have use a timer instead.
The CPU Clock Frequency - as well as the clock source - is configure by a fuse. Why configuring a clock frequency below the maximum supported by the MCU? Remember that each cycle consumes energy. Electronics is useful when you need small and mobile devices. You don't want a small device with big batteries or to be plugged to a wall. Then you should calculate the mininum frequency the device needs.
Blink a LED with timer (output/timer/interrupt/sleep)
Concepts
- timer
- interrupts
- sleep mode
Light a LED with button (input)
Concepts
- input (in busy-loop)
#define F_CPU 1000000UL /* ATmega328 at 1MHz by default*/
#include <avr/io.h>
#include <util/delay.h>
#define LED_TOGGLE (PORTC ^= 1<<PC5)
int
main(void)
{
DDRC = 0x00;
PORTC = 0x00;
DDRC |= 1<<PC5;
for (;;) {
if( ~PIND & (1<<PD2))
LED_TOGGLE; _delay_ms(500);
}
}
Light a LED with button (input/interrupt/sleep)
In the previous example, the LED was blinking. Imagine we want to light the LED on when we push the button, and light on again when we push the button a second time. We need to trigger some code when the button is pushed. To not waste power while waiting to button to be switched, we install a interrupt handler for the port (INT0 for PD2) that will switch the light when the button is push. The interrupt is trigger by the changed on the port as the path is open when the button is pushed, and implies the port becomes low.
An interrupt is an event that interrupt the current execution and ask the MCU to execute a interruption routine. An interruption routine is a procedure that saves the CPU state, does something, restore the CPU state, then give the control back to the CPU. When it ends the CPU continues to execute the code that was interrupted.
#define F_CPU 1000000UL /* ATmega328 at 1MHz by default*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#define LED_TOGGLE (PORTC ^= 1<<PC5)
ISR(INT0_vect)
{
LED_TOGGLE;
_delay_ms(500);
LED_TOGGLE;
}
int
main(void)
{
MCUCR |= 1<<ISC01;
sei();
DDRC = 0x00;
PORTC = 0x00;
DDRC |= 1<<PC5;
for (;;) {
_delay_ms(500); /* sleep not yet implemented */
}
}
Blink a LED with PWM
Concepts:
- PWM (analog output)
Some ports can be configured in PWM mode, with basically means the voltage can vary. We may use PWM to smoothly blink the LED and not use a resistor anymore.
Display numbers on a 7-segment LED
Display things on a 7-segment LED is not really harder than lighting a simple LED on. However it is a good exercice to read datasheets :).
Display numbers on a 7-segment LED with a shift register
Using a shift register helps to decrease the number of ports and wires used in the circuit.
Get values from temperature sensor
- Analog input
- ADC
It is a good introduction to the ADC (Analog-Digital Converter).
Newbie's Guide to the AVR ADC (on avrfreaks forum).
Display the temperature from a sensor on two 7-segment LEDs
Merge all the previous examples and make a simple but more "real-world" project :).
Play with an alcohol gas sensor
Might be fun when you invite friends at home ;)
More Resources
- AVR's community: AVR Freaks
- asm tutorials: avr-asm-tutorial
- Book Designing Embedded Hardware, by John Catsoulis (O'Reilly), even if it does not only target AVR it's full of useful information.
Notes
- ↑ which seems to stand for A(lf-Egil Bogen), V(egard Wollan), R(ISC Microcontroller) [1]
- ↑ basically it means that it stores the program instructions and the data in separate memory spaces. However "modified" means it allows program instructions to be accessed as data.
- ↑ MicroController Unit
- ↑ P = V x I => P = 1.8 x 300 x 10^-6 = 0.54 mW in active mode
- ↑ 5.0 5.1 ATtiny 25/45/85 datasheet 10.2.1)
- ↑ heavily inspired by avr-blic example
- ↑ Like the usb-to-uart converter or wireless chips like XBee or nordic
- ↑ from Designing Embedded Hardware 15.2.1
- ↑ The AVR can sink 20mA per pin when operating on a 5 V supply. However, the amount of current it can sink decreases with supply voltage. When running on a 2.7 V supply, the AVR can sink only 10 mA. As always, it's important to read the datasheets carefully.
- ↑ _delay_ms()








