The AVR Microcontroller
Introduction Application and programmer boards WinAVR Basic I/O ADC, timers USART, LCD
Mehul Tikekar Chiraag Juvekar Electronics Club May 26, 2010
What is a microcontroller?
It is essentially a small computer Compare a typical microcontroller (uC) with a typical desktop
ATmega16 Typical desktop
Clock frequency CPU data size RAM ROM
I/O
16MHz 8 bits 1KB 16KB
32 pins
3GHz 32 bits 1GB 160GB
Keyboard, monitor 65W
Power consumption 20mW
Why use a micro-controller?
It is programmable. It is fun. A code (typically written in C) decides what it does Easier to write a code than design and make a custom circuit for complex jobs e.g. In a micromouse, a single uC controls the motors, finds distance from the walls, explores and solves the maze The same muC can be used in hundreds of applications http://instruct1.cit.cornell.edu/courses/ee476/Final Projects/ has 407 projects just on AVR microcontrollers as of 2009
AVR microcontroller
Lots of micro-controller families
o 8051, PIC, AVR, MSP430, ARM, etc.
http://www.instructables.com/id/How-to-choosea-MicroController/ AVR: Cheap, easy to use, fast and lots of features ATmega16:
o 16KB flash memory o 1KB SRAM o Up to 16MHz clock o Four 8-bit I/O ports o ADC, timers, serial interface, etc. o 40 pin DIP, VDD = 5V
4
ATmega16 pin diagram
Port B
Port A
Reset Power
Crystal Oscillator
Power
Port D
Port C
Alternate pin functions: Peripherals
Analog to Digital Converter (ADC) Programming interface (SPI)
Serial interface (USART)
Interrupts Timer 1
Programming and debugging (JTAG)
A typical code for the microcontroller
#include <avr/io.h> #include <util/delay.h> int main(void) { int i; DDRB = 255; for(i=0; i<100; i++) { PORTB = i; _delay_ms(250); } return 0; }
The next steps:
1. Compile 2. Program 3. Run
7
Lets get started
Software
WinAVR
Hardware
Programmer board Application board
WinAVR
A complete package for Windows
Programmers Notepad (user interface) avr-gcc (C/C++ compiler) Mfile (makefile generator) avrdude (programmer software)
Totally free! : http://winavr.sourceforge.net/
USBasp USB Programmer
www.fischl.de/usbasp for more information and usb driver
10
USBasp
11
Bare bones of the Application Board
12
On a bread-board
IC1
R2 LED1 C1 R1 Programming interface
13
Complete application board
14
The hardware setup
Programmers Notepad Application board
USBasp
15
Resources and Links
The ATmega16 datasheet a 358 page bible Tutorials and sample codes:
1. 2. 3. http://www.avrtutor.com http://winavr.scienceprog.com http://kartikmohta.com/tech/avr/tutorial
Atmel application notes: projects, tutorials, codeexamples, miscellaneous information
http://www.atmel.com/dyn/products/app_notes.asp?family_id=607
Forums: http://www.avrfreaks.net Advanced projects:
1. 2. http://instruct1.cit.cornell.edu/courses/ee476/FinalProjects http://www.obdev.at/products/vusb/projects.html
16
Configuring the microcontroller before running it the first time
Fuse bytes : high and low
Program them once before you start using the micro-controller Disable JTAG to free up PORTC for normal use Set the correct clock clock option
With the hardware set up, run in Command Prompt :
For 1MHz internal clock: For 16MHz external crystal: Refer to datasheet sections on System clock and clock options and Memory programming for other clock options and their settings. Setting the wrong fuse values may render the uC unusable
avrdude -c usbasp -P usb -p m16 -U hfuse:w:0xd9:m -U lfuse:w:0xe1:m
avrdude -c usbasp -P usb -p m16 -U hfuse:w:0xc9:m -U lfuse:w:0xef:m
17
Hello world
Blink an LED Choose a port and a pin In the code
1.
2. 3.
Port B
Set pin direction to output
Set pin output to high Wait for 250ms
4.
5. 6.
Set pin output to low
Wait for 250ms Go to 2
Relevant registers : DDR set pin data direction PORT set pin output PIN read pin input
18
blink.c
#include <avr/io.h> // contains definitions for DDRB, PORTB #include <util/delay.h> // contains the function _delay_ms()
int main(void) { DDRB = 0b11111111; // set all pins on PortB to output while(1) { PORTB = 0b00000001; // Set PortB0 output high, others low _delay_ms(250); // Do nothing for 250 ms PORTB = 0b00000000; // Set all of them low _delay_ms(250); // Do nothing for 250 ms } return 0; }
/* DDRB is a 8-bit register which sets direction for each pin in PortB PORTB decides output for output pins 0b prefix for binary numbers, 0x hex, no prefix - decimal Thus, 15 = 0xf = 0b1111
*/
19
Compiling and Programming
Save the code as blink.c in a separate folder (not strictly necessary, just a good practice) Create a makefile using Mfile and save in the same folder Open it in Programmers Notepad and change:
Line 44: MCU = atmega16 Line 65: F_CPU = 1000000 Line 73: TARGET = blink, Line 83: SRC = $(TARGET).c Alternatively, TARGET = anything_you_want and SRC = blink.c Line 278: AVRDUDE_PROGRAMMER = usbasp Line 281: AVRDUDE_PORT = usb
In Programmers Notepad, Tools > Make All to compile Connect USBasp to computer and ATmega16 Tools > Program to program ATmega16
20
A better code
#include <avr/io.h> #include <util/delay.h> int main(void) { DDRB = DDRB | 0b00000001; // Set PortB0 as output, leave other pin directions unchanged while(1) { PORTB = PORTB | 0b00000001; // Set output high without affecting others _delay_ms(250); PORTB = PORTB & 0b11111110; // Set output low without affecting others _delay_ms(250);
}
return 0; }
Try this out : Toggle the pin instead of set and clear
21
A more readable code
#include <avr/io.h> #include <util/delay.h> int main(void) { DDRB = DDRB | _BV(PB0);
/* _BV(x) = 2x and PB0 is defined to be 0 in avr/io.h
So, _BV(PB0) = 2PB0 = 20 = 1 = 0b00000001 while(1) { PORTB = PORTB | _BV(PB0); // Set output high without affecting others _delay_ms(250); PORTB = PORTB & (~(_BV(PB0))); // Set output low without affecting others */
_delay_ms(250);
} return 0; }
22
Input and output
/* If input on PortB0 is low, set output on PortB1 high. Else, set output on PortB1 low. #include <avr/io.h> #include <util/delay.h> int main(void) */
{
DDRB |= _BV(PB1); while(1) { // x |= y; is same as x = x|y;
if((PINB & _BV(PB0)) == 0) PORTB |= _BV(PB1);
else PORTB &= ~(_BV(PB1)); } return 0; }
Try these out: Blink LED on PortB1 if PortB0 is high, else turn LED off. What happens when an input pin if left floating?
23
Data types available
Data type Size in bits Range
char unsigned char int unsigned int
(unsigned) long (unsigned) long long float, double
8 8 16 16
32 64 32
-128 127 0 255 -32768 32767 0 65535
(0 232-1) -231 231-1 (0 264-1) -263 263-1 1.175*10-38 3.402*1038
1. Since AVR is an 8-bit uC, char and unsigned char are natural data types 2. int should be used only when the range of char is not sufficient 3. Replace floating point operations by int or char operations wherever possible. e.g. Instead of y = x*0.8, use y = (x*4)/5 4. Take care of overflow. Stay within the range of the data-type used 5. Beware of integer round-off
24
Analog to Digital converter
Converts an analog voltage VIN to a digital number ADC_Data 10-bit conversion result Conversion time = 13.5 * ADC clock period Up to 15k conversions per sec 8 inputs (Port A)
ADC_Data
1023 4 3 2 1 0 0 VREF
ADC _ Data
VIN 1024 VREF
(rounded off to nearest integer)
VIN
25
Initialize ADC and read input
Setting up the ADC Select reference voltage : REFS1:0 Select prescaler : ADPS2:0 Select output format (left adjust/right adjust) : ADLAR Enable the ADC : ADEN
Reading an analog input
Select input pin to read : MUX4:0 Start conversion : ADSC Wait for conversion to finish : ADSC Read the result registers : ADCH:ADCL
26
adcroutines.c
#include <avr/io.h> void adc_init(void) { DDRA = 0x00; // Set all PortA pins to input PORTA = 0x00; // Disable pull-ups ADMUX = _BV(REFS0) | _BV(ADLAR); ADCSRA = _BV(ADEN) |_BV(ADPS2)|_BV(ADPS1); /* Use AVcc as reference, Left adjust the result Enable the ADC, use prescaler = 64 for ADC clock */ }
unsigned char adc_read (unsigned char channel) // valid options for channel : 0 to 7. See datasheet { ADMUX = (ADMUX&0xe0) + channel; // Set channel bits in ADMUX without affecting other bits ADCSRA |= _BV(ADSC); // Start conversion while((ADCSRA & _BV(ADSC)) != 0) {}; // Do nothing until conversion is done return(ADCH); // Return upper 8 bits }
Try these out : Control the blinking speed using potentiometer Try the other ADC options : Free running, Auto-trigger, different channels, prescalers, etc.
27
Liquid Crystal Display (LCD)
Alpha-numeric display with backlight 16 columns x 2 rows (larger sizes available too) 8-bit data interface, 3 control signals For pin-starved applications : 4-bit data interface +5V supply, separate power for backlight Readymade libraries available Working with multiple files
1. main.c the main code
2.
3.
lcdroutines.c has functions for LCD interfacing
lcdroutines.h defines connections and function prototypes
In main.c, #includelcdroutines.h In Makefile,
TARGET = main SRC = $(TARGET).c lcdroutines.c
For more information, consult the datasheet of HD44870
28
29
On the bread-board
30
lcdroutines.h
// Connections between uC and LCD #define DATA_DDR DDRC #define DATA_PORT PORTC #define #define #define #define CONTROL_DDR CONTROL_PORT RS E DDRD PORTD PD6 PD7
/* Function prototypes for interfacing to a 16x2 LCD Actual functions in lcdroutines.c */ void lcd_init(void); // Initialize the LCD void lcd_clear(void); // Clear LCD and send cursor to first char void lcd_home(void); // Send cursor to first character void lcd_command(unsigned char command); // Send command to LCD void display_char(unsigned char data); void display_byte(unsigned char num); void display_int(unsigned int num); // Display ASCII character // Display number 0 - 255 // Display number 0 - 65535 // Move cursor
void move_to(unsigned char x, unsigned char y);
31
Codes:
1.
2. 3. 4. 5.
Hello World - lcd1.c
A better Hello World - lcd2.c Animated display - lcd3.c Digital Watch - lcd4.c Voltmeter - lcd5.c
lcd1.c
#include <avr/io.h> #include "lcdroutines.h" int main(void) { unsigned char a[] = {"Hello World!"}; lcd_init(); for(unsigned char i =0;i<sizeof(a)-1;i++) display_char(a[i]); while(1); return 0; }
32
Interrupts
Suppose you are making an obstacle avoiding robot. Drive motors continuously, and stop immediately when obstacle is detected.
while(1) { drive_motors();
if(obstacle_detected()) stop_motors();
}
No obstacle detection when driving motors. Might miss the obstacle and crash!
while(1) { drive_motors();
while(!obstacle_detected()) {};
stop_motors(); }
No motor driving when waiting for obstacle!
33
The solution - interrupts
Run motor-driving routine in the main loop. Interrupt it when obstacle is detected.
ISR(vector_name) {
stop_motors();
} int main(void)
{
initialize_interrupt(); while(1) {
drive_motors();
} }
34
Interrupts explained
Interrupt is a special function which gets called when a specific hardware condition is met. When condition is met, an interrupt flag is set in a specific register. If interrupt is enabled, the interrupt flag signals the muC to stop executing main program and jump to an interrupt service routine (ISR). After ISR is done, CPU resumes main program from where it left. Possible conditions that can cause interrupts Voltage change on pins INT0, INT1 and INT2. (External interrupt) ADC conversion complete, Timer, UART, etc. (Internal interrupt)
In ISR(vector name), vector name depends on which interrupt condition is being used. Get the vector name from avr-libc manual : WinAVR_installation_path/doc/avr-libc/avr-libc-usermanual/group__avr__interrupts.html
35
#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> volatile unsigned char i=0; /* Declare variables being used in ISR as global and voltatile */ ISR(INT0_vect) { PORTC |= _BV(PC6); PORTC &= ~(_BV(PC0)); i++; } int main(void) { DDRD = ~(_BV(PD2)); PORTD = _BV(PD2); // Set PortD2 (INT0) as input // Enable pullup on INT0 // Need to include this for interrupts
DDRC = _BV(PC7) | _BV(PC6); // LED outputs
MCUCR &= ~(_BV(ISC00) | _BV(ISC01)); // Low level on INT0 generates interrupt
GICR sei(); while (1) { _delay_ms(100); PORTC ^= _BV(PC7); PRTC &= ~(_BV(PC1)); } return 0; |= _BV(INT0); // Enable INT0 interrupt // Enable global interrupts. Else no interrupt works.
36
Blink LED with a timer
In blink.c, CPU does nothing useful for 250ms
Instead, let a timer run in the background
Interrupt the CPU every 250ms and toggle LED How a timer/counter works
Normally, counter register increments every timer clock pulse
(resets to zero when it reaches maximum value) Timer clock frequency = Main clock / prescaler When counter value equals compare value, a compare match interrupt flag is set
Timer clock
Compare value
Counter register
37
Timer/counters in ATmega16
ATmega16 has 3 timer/counters : 0, 1 and 2 0 and 2 are 8-bit counters, 1 is a 16-bit counter Each T/C has different modes of operation : normal, CTC and PWM Special waveform generation options : variable frequency pulses using CTC, variable duty-cycle pulses using PWM
38
Initializing a timer and interrupts
Select mode of operation : Normal Select prescaler Select the event which causes the interrupt Set the time delay to interrupt every 250ms Relevant registers for Timer 1:
TCNT1 : 16-bit count register TCCR1A : Mode of operation and other settings
TCCR1B : Mode of operation, prescaler and other settings
OCR1A : 16-bit compare A register OCR1B : 16-bit compare B register TIMSK : Interrupt mask register
39
timer1blink.c
#include <avr/io.h> #include <avr/interrupt.h>
void timer1_init(void) { TCCR1B = _BV(CS11); TIMSK = _BV(OCIE1A); } ISR(TIMER1_COMPA_vect) { PORTB ^= _BV(PB0); OCR1A += 31250; return; // Initialize timer
// Normal mode, prescaler = 8 // Enable T/C1 Compare Match A interrupt
// ISR for T/C1 Compare Match A interrupt // Toggle pin /* Increment Compare Match register by 250ms*1MHz/8 = 31250 */ // return to main code
}
int main(void) { DDRB = _BV(PB0); timer1_init(); sei(); while(1) {}; return 0; }
// // // //
Set pin to output Initialize timer Enable global interrupts Do anything you want here
40
CTC mode
Timer 1 counter register (TCNT1) increments every timer clock
When TCNT1 reaches OCR1A, compare match interrupt flag is set
TCNT1 is reset to zero Compare Match interrupt flag is set
OCR1A TCNT1
OCR1A 1 TTIMER1 OCR1A 1 TCLK prescaler
Try these out: Use CTC instead of Normal mode to blink LED Now control the blinking speed using potentiometer
41
Controlling the brightness of an LED using Pulse Width Modulation (PWM)
Apply a variable duty-cycle high frequency clock to the LED Duty-cycle decides the brightness of the LED Timer 1 in PWM mode Special output pins OC1A (PD5) and OC1B (PD4) Function of output pins depends on the compare output mode : COM1A and COM1B bits in TCCR1A No interrupt is needed
TOP OCR1A TCNT1 Overflow interrupt flag is set t OC1A pin Compare match interrupt flag is set
5V 0V
TOP 1 TCLK prescaler
TON
TON OCR1A 1 TCLK prescaler Duty cycle OCR1A 1 TOP 1
42
Fast PWM mode with non-inverted compare match output
Initialize the timer
Set timer to Fast PWM 8-bit mode Set compare output mode Set prescaler Set OC1A (PD5) pin to output
Initialize the ADC Use ADC result to change OCR1A
43
ledbrightness.c
#include <avr/io.h> void timer1_init(void) { TCCR1A = _BV(WGM10) | _BV(COM1A1); // Fast 8-bit non-inverting TCCR1B = _BV(WGM12) | _BV(CS11); OCR1AH = 0; } int main(void) { DDRD |= _BV(PD5); timer1_init(); // Necessary to set DDR value for PD5 // PWM with prescaler = 8
adc_init();
return 0; }
// Get ADC functions from the ADC tutorial
// Set duty cycle
while(1) OCR1AL = adc_read(0);
Try this out: Use CTC mode instead of PWM mode and change blinking speed instead of brightness without using interrupts
44
USART
Universal Synchronous and Asynchronous serial Receiver and Transmitter Serial communication : one bit at a time. Communication speed : baud rate (number of bits per sec) Master
RxD TxD
Slave
TxD RxD XCK
XCK (only for synchronous)
Master and slave can be microcontrollers, computers or any electronic device with a USART interface Computer serial port uses UART protocol. Connection between uC and computer can be used for data-logging, sending commands, etc. Programs like HyperTerminal, Tera Term, Brays terminal are available for using serial port in Windows
45
uC to computer
Serial port voltage levels are different: +13V : 0 and -13V : 1
Voltage converter IC :MAX232
ATmega16
RxD TxD 5V 0V
MAX232
R1OUT T1IN R1IN T1OUT -13V +13V
Computer serial port
TxD RxD
USB-serial converter can be used if no serial port is available
Some USB-serial converters do not require MAX232 to be used. Their output is already 5V compliant and can be connected to the uC pins directly.
Download Tera Term from http://ttssh2.sourceforge.jp/
46
Circuit diagram for serial port
serial port
MAX232
47
Using the UART in AVR
Relevant registers
Control and status registers UCSRA, UCSRB, UCSRC Baud rate registers UBRRH, UBRRL Data register UDR (2 registers by the same name)
Initialize UART
Enable transmitter and receiver Set baud rate typically 2400 bps Frame length 8bits Other settings 1 stop bit, no parity bits
Transmit data
Check if data register is empty (UDRE bit in UCSRA) Write data to data register UDR
Receive data
Check if receive is complete (RXC bit in UCSRA) Read UDR
48
UART functions
#include <avr/io.h> void uart_init( unsigned int ubrrval ) // ubrrval depends on uC clock frequency and required baudrate { UBRRH = (unsigned char) (ubrrval>>8); // set baud rate UBRRL = (unsigned char) ubrrval; // set baud rate UCSRB = _BV(RXEN) | _BV(TXEN); /* Enable UART receiver and transmitter, 8-bit data length, 1 stop bit, no parity bits */ } unsigned char uart_rx( void ) { while ( !(UCSRA & _BV(RXC)) ); return UDR; } void uart_tx( unsigned char data ) { while ( !(UCSRA & _BV(UDRE)) ); UDR = data; }
// wait until receive complete
// wait until UDR is empty
49
If things arent working as expected
Nothing works: Check the power uC doesnt get programmed: 1. Check connections from USBasp to uC. You might have connected the 6-pin programming connector in the reverse order.
2.
3. 4. 1. 2. 3.
Check if correct jumpers are set in USBasp.
Did you write fusebits incorrectly before? Check the Makefile. Is the LED connected in the reverse? Is the series resistor correct? Is it blinking too fast? Is the uC pin damaged/shorted? Is the DDR bit for the pin set?
LED doesnt blink:
Some pins on PortC arent working properly: Disable JTAG using fusebits.
50
Hyperterminal shows nothing/garbage:
1. Check the serial port and MAX232 using a loop-back test. 2. Do the settings on Hyperterminal and uC match? ADC doesnt give correct result: 1. Is the reference set correctly? 2. Is your output format correct (left adjust/right adjust) ? 3. Try reducing the ADC clock speed by increasing prescaler.
4. Check if there is too much noise on the input and reference. 5. Keep pull-ups disabled (PORTA = 0)
Mathematical operations give incorrect results:
1. Check for overflow, integer round-off.
2. Use the correct data type. Interrupts arent working: 1. Are global interrupts enabled using sei()? 2. Is the interrupt mask set correctly? 3. Is the ISR vector name correct?
51
for(int i= 0;i<50000;i++) is an infinite loop since the maximum value of an int is 32767. Read the Atmel application note on Efficient C Coding for AVR. Important precautions:
1.
Be extra careful when using USB power for powering the uC. Shorting the supply may damage the USB port. Use external 5V regulated power to be safe. Connect power, programmer and other connectors in the correct location and correct polarity. Use matching male-female connectors as far as possible as they do not allow reverse connection. Do not short output pins to any supply or other outputs. AVRs have some amount of ESD (electrostatic discharge) protection. But, still, do not touch them if you are charged, say by wearing a woolen sweater.
2.
3. 4.
52
Resources on the web
WinAVR : http://winavr.sourceforge.net USBasp: http://www.fischl.de/usbasp IITB Electronics Club: http://groups.google.com/group/elec-club Tutorials and sample codes:
1.
2. 3.
http://www.avrtutor.com
http://winavr.scienceprog.com http://kartikmohta.com/tech/avr/tutorial
Atmel application notes
http://www.atmel.com/dyn/products/app_notes.asp?family_id=607
Forums: http://www.avrfreaks.net Advanced projects:
1. 2. http://instruct1.cit.cornell.edu/courses/ee476/FinalProjects http://www.obdev.at/products/vusb/projects.html
53