Professional Documents
Culture Documents
https://www.mainframe.cx/~ckuethe/avr-c-tutorial/
section
is
an
introduction
to
microcontrollers.
Out
of
the
box,
your
06/12/2011 07:31 PM
https://www.mainframe.cx/~ckuethe/avr-c-tutorial/
microcontroller won't do anything - you need to load a program before it's useful. Here I present the the hardware equivalent of "Hello World" - a blinking light. To begin, let's look at the pin map. We see that Arduino pin 13 is PB5 on the ATmega168 - part of port B. To use this pin, port B must first be set to be an output pin. There are various ways to do this[2] - writing to Port B Data Direction Register[3] at address 0x24 or the lazy/better way, using the DDRB macro. Let's keep things simple and set the whole port to output with DDRB = 0xff;. Finally, we can start writing to PORTB (address 0x25). A simple approach would would be to do something like this:
while(1){ PORTB = 0xff; _delay_ms(500); PORTB = 0x00; _delay_ms(500); }
which makes use of the delay routines defined in <util/delay.h>. If you try run this, you'll find your LED blinking very rapidly. Far more rapidly than you'd like. A brief inspection of delay.h is instructive: with respect to _delay_ms, "The maximal possible delay is 262.14 ms / F_CPU in MHz." This can easily be addressed by computing the maximum time _delay_ms will sleep, and given that, the number of times to call _delay_ms to achieve the desired delay interval. You may wish to put this computation into a separate function which you can call whenever you need to delay. lesson1.c is one possible solution.
2 of 9
06/12/2011 07:31 PM
https://www.mainframe.cx/~ckuethe/avr-c-tutorial/
Try a program to read a switch connected between digital pin 2 and ground, and use this to control an LED. One possible solution is lesson3.c
3 of 9
06/12/2011 07:31 PM
https://www.mainframe.cx/~ckuethe/avr-c-tutorial/
the datasheet the clock generator is updated when the low byte is written. Thus the baud rate register should be written to high-byte first. Next is framing, most things these days default to "8N1", and that's what we will do here. Section 19.10.4 (UCSR0C) describes the framing controls. To use asynchronous mode, set the top 2 bits to 00. To inhibit parity generation and checking, set the next 2 bits to 00. To use 1 stop bit, set the next bit to 0. To use 8 data bits, set the next 2 bits to 11 (decimal 3). In async mode, the bottom bit should be 0. Altogether, UCSR0C = 0x06; or UCSR0c = (3 << UCSZ00); will program the register correctly. Once the serial port is initialized, you can write to it by writing to UDR0. You should check that the port is actually ready to transmit; the usual way to do this seems to be a busy wait that tests if the USART Data Register is Empty - (UCSR0A & (1 << UDRE0)). Try write a program that prints a fixed string to the serial port. One way to do this is lesson4.c
4 of 9
06/12/2011 07:31 PM
https://www.mainframe.cx/~ckuethe/avr-c-tutorial/
short pulses we can simulate their average effect. The math is simple: 100ms@100%+900ms@0% has the same average power over 1 second as 1000ms@10%. In other words, a low-pass filter. With that in mind, let's set up the the AVR to do PWM output. Try writing a program to slowly ramp the brightness of the LED. Or maybe have it fade in and out. lesson7.c is one way to do this. Alas, this solution sucks - it's basically a big nasty busy wait that prevents you from getting any real work done while generating the PWM waveform. As we learn about hardware PWM, application Note 130 will be your friend. Though it's written for the AT90S8535 and doesn't use gcc, the general principles are still useful. First off, you need a timer to generate pulses. This is created from a counter, a periodic increment and a compare register. For this example, TCNT0 is the counter, the system clock is the periodic increment (CS = 1), and OCR0A as the compare register. Section 13.3.3 of the ATmega168 datasheet says that PORTD.6 is the output pin for OC0A. First, the control register TCCR0A should be set for Fast PWM mode. Then the compare mode (inverting/noninverting) is also set in the control register. At this point, it would be good to initialize the timer and comparator values because the timer isn't ticking until the clock source is set (control register TCCR0B). It is now safe to set the output bits on the output port. One way to do hardware PWM is lesson7b.c.
Macro
loop_until_bit_is_set(PORTD, _BV(PD2)); PORTD = _BV(PD2); #define BAUD 9600 #include <util/setbaud.h> UBRR0H = UBRRH_VALUE; UBRR0L = UBRRL_VALUE; UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); UCSR0B = _BV(RXEN0) | _BV(TXEN0);
I'm rather fond of stdio and formatted IO. I've been spoiled by big machines with multiple multi-GHz cpus, a few GB of memory and a few TB of storage. Programming an embedded device makes me realize how wonderful printf("sensor %d value %f\n", n, values[n]) is. Avr-libc has some glue to simulate stdio. Here's an example of how to use this: lesson8.c. The manual warns that printf and scanf are quite resource-intensive. This is not even a little bit of an exaggeration. Have a look at the annotated assembly
5 of 9
06/12/2011 07:31 PM
https://www.mainframe.cx/~ckuethe/avr-c-tutorial/
files and be afraid. Be very afraid. In my example implementation, I also use the program space macros to avoid keeping constant strings in RAM when they could simply be used straight from flash.
Each of these items takes a just single line of code. First, the clock source must be
6 of 9
06/12/2011 07:31 PM
https://www.mainframe.cx/~ckuethe/avr-c-tutorial/
configured. The timer system can accept external inputs or use the system clock optionally divided down to a slower rate. The Clock Select (CS00-CS02) bits in the timer/counter control register B (TCCR0B) are used. Assuming a 16MHz clock, a prescale of 1024 will cause 15625 increment operations per second. Since the counter can hold 256 unique values, it will overflow at 61Hz. If a higher interrupt rate is needed, the prescaler can be set lower, or a smaller Output Compare (OCR0A) can be used. The timer/counter interrupt mask register defaults to 0 - no interrupts will be generated by the timer/counter system. To enable timer overflow interrupts, set the TOIE0 bit of TIMSK0. Timers can be set and reset by storing into them, in this case TCNT0 = 0. Finally, the sei() function enables interrupt processing. One way to do this is lesson10.c. My version can blink either the onboard LED on PORTB.5 or a set of LEDs on PORTB.1 - PORTB.3.
https://www.mainframe.cx/~ckuethe/avr-c-tutorial/
From time to time, it may be useful to have an interrupt or reset delivered independently of the main program code. This may be to wake the AVR from sleep mode or to reset when the program gets stuck in a loop; that's where the watchdog timer comes in. An onboard 128kHz oscillator is used to drive the watchdog with intervals from 16ms to 8s. If the watchdog expires without being touched, the system can catch an interrupt, be reset or both. If you "kick the dog" before the time expires, no action is taken. While it can be slightly complicated to set up the watchdog by hand, avr-libc has some convenience functions. To enable the watchdog, call watchdog_enable() with the desired duration. Refreshing the watchdog is as simple as a call to watchdog_reset(). If the WDTON fuse is set the watchdog activates automatically, rather than requiring manual activation. A short example of using the watchdog timer to wake from a deep sleep mode is found in lesson13.c.
ATtiny85-specific
Most of this document should be applicable to any AVR board. I wrote it using a Freeduino SB which has an ATmega168. I'm also running some ATtiny85 parts. This section covers some of the differences when running on this hardware. While porting my firefly simulator to the tiny85, I found the smaller peripheral mix got to be very annoying. In particular, TIMER2 which can wake the AVR from sleep does not exist, nor does the SLEEP_MODE_PWR_SAVE. Power down mode does exist, but it may disable too many other parts of the processor. In this case, I was able to use the watchdog timer to wake from SLEEP_MODE_PWR_DOWN, but the big lesson was to carefully read the datasheets before committing to a particular part.
8 of 9
06/12/2011 07:31 PM
https://www.mainframe.cx/~ckuethe/avr-c-tutorial/
AT90USB162-specific
Most of this document should be applicable to any AVR board. I wrote it using a Freeduino SB which has an ATmega168. I'm also running a Teensy which is based on the AT90USB162. This section covers some of the differences when running on this hardware.
References
[1] pin_map.html [2] http://www.atmel.com/dyn/resources/prod_documents/avr_3_04.pdf [3] http://www.atmel.com/dyn/resources/prod_documents/doc2545.pdf http://avrbasiccode.wikispaces.com/Atmega168 http://javiervalcarce.es/wiki/Program_Arduino_with_AVR-GCC http://www.ladyada.net/learn/Arduino/ http://ccrma.stanford.edu/courses/250a/docs/avrlib/html/ http://www.nongnu.org/avr-libc/user-manual/ http://www.engbedded.com/cgi-bin/fc.cgi http://piconomic.berlios.de/ http://www.sanguino.cc/ http://www.pjrc.com/teensy/ ... and the rest of atmel's ginormous tech library
NB: older versions of GCC may not support newer mcu types like -mmcu=atmega168 or -mmcu=atmega644p. For some of these examples you may be able to use -mmcu=avr5, but things like the serial port won't work correctly or at all without the correct mcu flag. Arduino-0011 uses GCC 4.0.4, that should be considered the minimum required version. Copyright 2008,2009 Chris Kuethe <chris.kuethe@gmail.com>
$CSK: index.html,v 1.27 2010/01/09 21:54:37 ckuethe Exp $
9 of 9
06/12/2011 07:31 PM