I Make Projects . com
On the right project, some bad rust looks better than some good paint.

Main | Projects | About | Contact and Services

This is an older site that's no longer maintained (as of ~2010). For my professional site and contact information, visit AE Innovations. You can also visit my new site at It Came From The Workshop.

A Quickstart Tutorial for ATMEL AVR Microcontrollers

If you're at all like me, you learn best by example and by doing. If that also sounds like you and you're interested in the popular AVR microcontrollers, this tutorial should be right up your alley.

The goal is to get you up and running as quickly as possible, so you can get to exploring and modifying on your own without having to fiddle with hardware, software, parts, or settings.

If you can program in basic C and are familar with most concepts around microcontrollers (perhaps you've used a Basic Stamp, for example) and basic electronics, you'll be up and running in no time.

The software and hardware presented here are suitable for beginners, but also entirely appropriate for more advanced projects.

What You Will Get Out Of This

Most tutorials skim over a lot of information. Not this one. This tutorial assumes you have some basic knowledge, but tries not to make any other assumptions. The goal is to lower the barrier to entry for getting started with the AVR by starting at square one (i.e. you don't even own a programmer) and getting you using the tools as quickly as possible.

You will write some sample programs in ATMEL's AVR Studio IDE (Integrated Development Environment), and run/debug them within AVR Studio using the software's simulator. These C programs will be compiled with AVR-GCC, and programmed into an AVR microcontroller with the help of a USB programmer from ATMEL (the AVRISP mkII) using the ISP (In-System Programming) interface. The programmed chip can then be used standalone in your circuit of choice.

You can then get on with learning on your own by exploring and modifying, instead of wasting time figuring out fundamentals or usage of the tools!

Overview

This tutorial will demonstrate the following development process:

  1. Write C programs in AVR Studio.

  2. Compile them into a .hex file using the AVR-GCC compiler (which integrates into AVR Studio).

  3. Simulate the target AVR chip and debug the code within AVR Studio.

  4. Program the actual chip using the AVRISP mkII USB device, which is attached to our target chip with a special 6-pin cable.

  5. Once programmed, the chip runs the program in your circuit.



Hardware and Software Setup

1. Purchase an ATMEL AVRISP mkII programmer, and an ATTINY45 (8pin) microcontroller. They are both available from Digi-Key, among other places. (There are other programmer options out there, but I'll use the AVRISP mkII)


The AVRISP mkII Programmer

2. Go to ATMEL.com and download AVRStudio and any service packs. At this writing (February 2008) there was AVRStudio 4, and two service packs. This is the main IDE we will be using.

3. Go to AVR-GCC's homepage at sourceforge.net. Download WinAVR. This contains the C compiler for the AVR.

4. Install AVRStudio. Install Service packs (if any) starting at 1, then 2, etc. The install is pretty straightforward.

5. Install WinAVR. The install should also be straightforward. I simply used all the defaults.

6. Install the software for the AVRISP mkII. (Either it came with the programmer, or download the latest version from atmel.com. Follow all directions and like most USB devices, only plug it in once directed to do so by the install program.)

7. Get a breadboard and stick the ATTINY45 into it. Make the following connections, which are the bare bones for the part to function:

ATTINY45 Pin Number

Pin Function

Connects to:

1

/RESET

+5V through 4.7k resistor

2

PB3


3

PB4


4

GND

Power supply GND

5

MOSI


6

MISO


7

SCK


8

+VCC

+5V

These are the minimum connections required for the ATTINY45 to function. In our application, the MOSI (Master Out Slave In) and MISO (Master In Slave Out) and SCK (Serial Clock) pins will be used for the ISP programming header. That leaves two general purpose I/O pins (PB3 and PB4) for our use. The chip will be using its built-in internal oscillator as its clock, so no external crystal needs to be attached.

Note: Many pins are multi-function, and there are different ways to configure what pin does what. That's something you can explore on your own after this tutorial is done, but for now just be aware that it can be done. For this tutorial, we'll be left with two I/O pins on the ATTINY45.

8. Attach LEDs - one LED and one current-limiting resistor (300 ohms, but actually anything between that and 1k should do the trick) from to each I/O pin (PB3 and PB4) to GND. These LEDs will be the outputs for our simple program - we're going to make them blink on and off.

9. Make the ISP header and connections. The ISP header is a 6-pin (3x2) interface to the AVR programmer hardware.

A header is tough to breadboard, but you can make your own with some long pin headers as shown. The top left pin will be pin #1 (just like the location of pin #1 when looking down at a chip).




Make the necessary connections so that the 6-pin header connects to the ATTINY45 pins as follows:

ISP Header Pin #

Signal Name

Connect to ATTINY45 Pin:

Pin 1

MISO

MISO (Pin 6)

Pin 2

VCC

+VCC (Pin 8)

Pin 3

SCK

SCK (Pin 7)

Pin 4

MOSI

MOSI (Pin 5)

Pin 5

RESET

/RESET (Pin 1)

Pin 6

GND

GND (Pin 4)

You will notice that the pins are all named pretty much the same and the signals are connected 1-to-1 from ISP header to AVR microcontroller. This is true of all AVR microcontrollers - not just the ATTINY45 we are using. The only difference is that the signals may be on physically different pins on the AVR device depending on which you are using. Here, we're just going to stick with the ATTINY45.

For some additional information about AVR programmers and target boards, you can visit this page at Evil Mad Scientist Labs.

You should therefore now have the following wired up on your breadboard:




Now you're ready to get started with the software development!



Software Development

1. Start AVR Studio on your workstation. Select "New Project". Type is "AVR-GCC". Project name: "MyFirstProject". Check off the "create folder" box. Modify the location if desired.


Click "Next".

Debug platform should be "AVR Simulator". Device is "ATTINY45".


Click Finish. You will now be in the IDE.




2. Write the following code into the window in the middle of the screen (the window for MyFirstProject.c):

#include <avr/io.h>

int main(void)
{
  // Set Port B pins as all outputs
  DDRB = 0xff;

  // Set all Port B pins as HIGH
  PORTB = 0xff;

  return 1;
}

This is a quick and dirty way to turn both LEDs on.

This code tells all of Port B to become outputs by writing 0xFF (binary 1111 1111) to DDRB which is the data direction register for port B. Each bit is mapped to a port B pin - a '1' written means that pin should be an output. A '0' means the pin is an input. So we're making all of Port B outputs.

The next statement is similar in that we're sending 0xFF to PORTB - again, each bit is mapped to a Port B pin. So writing 0xFF sets all of Port B as high (logical '1').

Now as you may recall, the only two I/O pins we are using are PB3 and PB4. All other pins on the part are being used for something else.

So we really only needed to make Port B bits 3 and 4 outputs, then logical 1's. But we're using a shotgun approach for simplicity so we just used 0xFF in both cases.

3. Compile the code with "Build -> Build" from the menu, or the F7 shortcut for "Build". The bottom window will show the progress and results. You should see "Build succeeded with 0 warnings."

If there is an error, check your code for typos. The error message should give you the offending line number.

A successful compile will result in a .hex file being generated. This is the binary code in a format ready to be burned into your AVR chip by the programmer. (Think of the .hex file as a program that the target AVR chip can run once we put it on there, sort of like writing to a memory card.)

You should be able to locate MyFirstProject.hex in your project dir. For me, it was in "AVR\src\MyFirstProject\default\".

4. Now let's debug the code in the simulator to get a feel for how it works.

Use "Build -> Build and Run" from the menu, or use the CTRL-F7 shortcut.

Note the following:

  • We have a yellow arrow at the current execution.

  • We have some debugging keys at the top (we want STOP and STEP INTO now).

  • We have "AVR SIMULATOR" at the bottom which is no longer greyed out.

Now click on the right pane on PORTB so we can look at it in the "I/O View". The bottom right window will populate with DDRB, PINB, and PORTB. These represent some states of the simulator's virtual ATTINY45 hardware.

5. Step through the program line by line with "STEP INTO (F11)" button.

Notice DDRB (direction of pins for PORTB: input or output) changes on the bottom right after "DDRB = 0xff" is executed.

6. Step again and notice that PORTB becomes set to 0xff (all logical 1 output) when "PORTB = 0xff" is executed.

We are now at the end of the program. Click "STOP DEBUGGING" (the blue square button on the menu bar ) or CTRL-SHIFT-F5 to stop the debugger and chip simulator and return to the coding view.

Those are the basics of debugging with the simulator and the IDE.



Programming the AVR Chip with our Compiled Code

The next step in the development process is that of getting the compiled .hex file onto the actual AVR chip so it can run in the real world, not just in the simulator. This is done with the AVRISP mkII through AVR Studio.

In the real world, the two available Port B pins have LEDs on them, so once they are set to HIGH (logical 1s) the LEDs will light up.

Make sure the programmer hardware is connected and powered:

1. Ensure your AVRISP mkII is plugged in via USB to your workstation. You should have a green light on the AVRISP nearest the USB connector.

2. Connect the ISP cable from the programmer to our 6-pin header that we breadboarded up as per earlier directions. The light by the ISP cable will be RED (indicating no power on target circuit).

3. Turn on power to your breadboard, the AVRISP mkII's light beside the ISP cable should turn GREEN. (This is because the chip must have power before it can be programmed. The AVRISP mkII itself is powered from USB but it doesn't power the your circuit.)

Note: If this doesn't work for you, consult the documentation for the AVRISP to troubleshoot the connection. (The colors of the LEDs on the programmer should tell you what is wrong.)

Configure AVR Studio to use the programmer hardware:

AVR Studio integrates with the AVRISP mkII programmer, so there is no need to run a separate programming program to burn our .hex file into the chip. So now we select the AVRISP mkII as the programmer and inform it of where our .hex file is in the context of this project.

1. Click the [CON] button on the menu bar (it looks like a little chip with 'CON' in it) to connect to the programmer. A selection dialog will appear. Select "AVRISP mkII" as the Platform, and "USB" as the choice for the Port (it should be the only option for the mkII.) This allows us to set up the programmer. Once we do this once, we can skip the setup step by clicking the [AVR] button next to [CON] instead.




2. Click "CONNECT..." You will see a window with things like LockBits, Fuses, etc. You will someday want to mess with these settings, but for now just ignore them and go to the PROGRAM tab. All we're going to do here is tell it where to find our .hex file.

Fill out the "Input HEX File" by clicking the browse button and selecting the MyFirstProject.hex file - it should be in the project folder.




3. Change the ISP Frequency. By default, the ATTINY45 is set use to internal oscillator for its clock. This is what we want, but since the default ISP speed generates an error as a result, go to "MAIN" tab and change the speed to 6.48kHz, then click WRITE and exit. (The error message you'll get if the ISP frequency is wrong is pretty clear.)







4. Program the part!

Now you can go back to the PROGRAM tab and click PROGRAM.

The light nearest the ISP cable on the AVRISP mkII should go orange while programming is in progress.

Look at the output window near the bottom - the programming should be successful.

5. The program auto-runs once the programming is done. Look over at the ATTINY45 and you will see the two LEDs are lit!

If all you needed your microcontroller to do was make those two pins go HIGH after a RESET, you could take that ATTINY45 off the breadboard and put it into your project right now.

But let's change the program a little to demonstrate a delay. Instead of the LEDs turning on immediately after a RESET (i.e. after the part is programmed) we will have it wait a short time first - giving us time to look over and see the LEDs turn on.



Example of a Delay

1. Change the program to read:

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
  // Set Port B pins as all outputs
  DDRB = 0xff;

  // Use this function from delay.h to delay for 4 seconds
  _delay_ms(4000);        

  // Set all of Port B pins as HIGH
  PORTB = 0xff;

  return 1;
}



2. Save and BUILD (F7 or BUILD -> BUILD). Whenever you change the program, remember to re-build it before programming it into your AVR.

(NOTE: Feel free to come back to this part later if your brain is already feeling full so far, but since it is never a bad time to demonstrate the correct way to do things here is a quick note about something you will probably see at this point. You may see this: "Warning: F_CPU not defined for < util/delay.h >" If so, this is because the compiler needs to know what the clock speed of the microcontroller will be, so it can make the appropriate delay. Without it, the code we just wrote will delay but not necessarily for the intended 4 seconds. Fix this by right-clicking on the project name in the left pane of AVR Studio and select "Edit Configuration Options". Enter "1000000" (one million) for the frequency in Hz. This will match the 1 MHz default clock speed of the ATTINY45, and will make the warning go away - ensuring your delay does in fact delay for the intended amount of time.)

3. Click the [AVR] button that looks like a little chip on the menu bar - it's beside the [CON] button we used earlier. (Since we already connected to the AVRISP mkII programmer earlier, we don't need to do it again and you can just use the AVR button instead of CON again.)

4. Program the code into the ATTINY45 with the PROGRAM button and look over to your AVR. Once the programming is done, the LEDs will light after a few seconds' delay. You have implemented a delay!



Setting Specific PORTB Bits, and Adding a Loop

So far we've been using the sledgehammer approach to playing with Port B by writing 0xFF's.

Let's modify the code so that we are writing to only the bits required for Port B pins 3 and 4. If we ever want our port to have some inputs, some outputs, and set some pins high and others low, we'll need to do this.

While we're at it, we're going to create a "run forever" program loop as well.

Enter the following program:

#include <avr/io.h>     
#include <util/delay.h>

// This program will turn the LEDs on for 100ms, 
// then off for 200ms, endlessly.

int main(void)
{
  // Set Port B pins for 3 and 4 as outputs
  // PORTB bit 3 = physical pin #2 on the ATTINY45
  // PORTB bit 4 = physical pin #3 on the ATTINY45

  DDRB = 0x18;  // In binary this is 0001 1000 (note that is bit 3 and 4)
  // AVR-GCC also would accept 0b00011000, by the way.

  // Set up a forever loop using your favorite C-style 'for' loop
  for ( ; 1==1 ; )  // loop while 1 equals 1
  {
    // Set Port B pins for 3 and 4 as HIGH (i.e. turn the LEDs on)
    PORTB = 0x18;   // If we wanted only PB4 on, it'd be PORTB=0x10

    // Use a function (defined in delay.h) to pause 100 milliseconds
    _delay_ms(100);

    // Set PORTB to be all LOWs (i.e. turn the LEDs off)
    PORTB = 0x00;

    // Delay for a 200ms
    _delay_ms(200);
  }

return 1;
}

1. Re-build the code (F7 or BUILD -> BUILD). Ensure no errors at bottom of page.Re-build the code (F7 or BUILD -> BUILD). Ensure no errors at bottom of page.

2. Program the newly-compiled .HEX file into the part with [AVR] and "Program".

3. Observe that the LEDs now blink endlessly.



Using Functions

Here is an example of two simple functions: one that waits one second, and another that uses binary math operators to set or clear a specific bit in PORTB while leaving all the other bits unchanged.

Enter the following program, build it, and run it in your ATTINY45.

#include <avr/io.h>     
#include <util/delay.h>

// Function prototypes
int     wait_one_second(void);
int     set_PORTB_bit(int position, int value);

// This program will merely blink the LEDs on PB3 and PB4, but the code
// illustrates using the new functions, including the one that uses
// a bunch of binary operators to set or clear specific bits in PORTB 
// while leaving the other bits unchanged.
int main(void)
{
  // Set Port B pins for 3 and 4 as outputs
  // PORTB bit 3 = physical pin #2 on the ATTINY45
  // PORTB bit 4 = physical pin #3 on the ATTINY45
  DDRB = 0x18;  // In binary this is 0001 1000 (note that is bit 3 and 4)

  // Set up a forever loop using 'C'-style for loop
  // i.e. loop while '1' equals '1'
  for ( ; 1==1 ; )
  {
    set_PORTB_bit(3, 1);    // Set bit 3 high
    set_PORTB_bit(4, 1);    // Set bit 4 high
    wait_one_second();
    set_PORTB_bit(3, 0);    // Set bit 3 low, leaving bit 4 unchanged
    wait_one_second();
  }

  return 1;
}

// Functions
int wait_one_second(void)
{
        _delay_ms(1000);
        return 1;
}

int set_PORTB_bit(int position, int value)
{
        // Sets or clears the bit in position 'position' 
        // either high or low (1 or 0) to match 'value'.
        // Leaves all other bits in PORTB unchanged.
        
        if (value == 0)
        {
                PORTB &= ~(1 << position);      // Set bit # 'position' low
        }
        else
        {
                PORTB |= (1 << position);       // Set bit # 'position' high
        }
        return 1;
}



Reading an Input (e.g. a button)

We have been writing to "PORTB" as outputs to make Port B pins high or low.

You might think that to read Port B pin states you just read in "PORTB" but that is not the case.

To read Port B pin states, you have to use "PINB".

Write to PORTB for output, read PINB for input.

This example will demonstrate reading the state of the pin with a button (PB3).

1. Replace the LED on PB3 (ATTINY45 pin #2) with a button to GND and a pullup resistor to +5V. The circuit diagram below has been updated to reflect this change:




2. Enter the following program into the IDE. We are using much of the same code from the previous example:

#include <avr/io.h>     
#include <util/delay.h>

// Function prototype
int     set_PORTB_bit(int position, int value);

int main(void)
{
  int temp;

  // Set Port B 4 as output (binary 1), 3 as input (binary 0)
  // PORTB bit 3 = physical pin #2 on the ATTINY45
  // PORTB bit 4 = physical pin #3 on the ATTINY45
  DDRB = 0b00010000;

  // Set up a forever loop
  for ( ; 1==1 ; )
  {
    // Bitwise AND the state of the pins of
    // PORT B with 0000 1000 (PB3)
    // In other words, set 'temp' to be 
    // the value of PINB's bit #3.
    // 'temp' will therefore only ever be 0x08 or 0x00.
    temp = (PINB & 0x08);

    // If the button is pushed (i.e. that bit is 0) 
    // then turn the LED on with a function.
    if ( temp == 0 )
    {
        set_PORTB_bit(4,1);  // LED on (LED is on PB4)
    }
    else
    {
        set_PORTB_bit(4,0);  // LED off (LED is on PB4)
    }
  }
  return 1;
}

// Function code
int set_PORTB_bit(int position, int value)
{
  // Sets or clears the bit in position 'position' 
  // either high or low (1 or 0) to match 'value'.
  // Leaves all other bits in PORTB unchanged.
                
  if (value == 0)
  {
    PORTB &= ~(1 << position);      // Set bit position low
  }
  else
  {
    PORTB |= (1 << position);       // Set high, leave others alone
  }

  return 1;
}



3. Build and program this into the ATTINY45. You will see that the LED on PB4 lights while the button on PB3 is pressed.

Congratulations! That is how to use inputs, outputs, variables, and functions!

This same code and circuit can illustrate one more thing to us, though. Take a look at the final section below.



A Dubugging Gotcha

Here is an annoying problem you may run into. I ran into it shortly after writing my first couple test programs and it was a hair-puller. At the time I wasn't sure whether I was mis-using the tools or not.

Using the code from above (i.e. for reading a button), build and run the code in the debugger.

Add a WATCH to the variable 'temp' by right-clicking on the variable and selecting "Add watch - temp".

Now step through the code and process the line which assigns a value to 'temp'.

temp = (PINB & 0x08);

If you look at the watch window, you see that 'temp' is indicated as being undefined in some way. Which is a little weird, considering that we're specifically assigning it a value right here!


You are not misusing the tools or misunderstanding what you are seeing. There is a reason for this, and once you understand it you will be able to work around it.

The problem is that the debugger is debugging the actual compiled code, which is running on a virtual ATTINY45; and that compiled code is not the same as our 'C' code.

The compiled binary code running in the simulator is the result of the AVR-GCC compiler taking our C program, turning it into machine code and in the process optimizing it. That optimization process is part of any compiler and is why we are seeing what we are.

If the variable 'temp' resided in RAM, we would be able to see it in this window. But that isn't necessarily where it will be.

When we compiled the program, the compiler decided how to handle 'temp' and where that variable physically resided in the ATTINY45's hardware. It may decide to put the variable into one of the hardware registers. Or, if your program only did "temp = 10", the compiler would probably optimize it completely away (replacing it with a literal "10") since the value never changes.

If you want to see this 'temp' variable in your watch window, you can re-compile your code with optimization OFF.

First, I want to make it clear that there are many considerations to turning off compiler optimization, but for now what you need to keep in mind is:

  1. If you want to debug meaningfully at a basic level, it can be helpful to do so with optimization off.

  2. Turning optimization off does not just mean "make debugging work"!



To turn off compiler optimization, right-click on the project main file and select "Edit Configuration Options".

Then change the "Optimization" from the default of "-Os" to -O0".




Then re-build your code. You will notice a warning: "Compiler optimizations disabled; functions from <util/delay.h> won't work as designed".

So, you can see already why turning off optimization is more complex than "make variables watchable".

However, as a result of recompiling without optimization, we can now debug and step through the code as before, but the variable 'temp' can be watched just as you'd expect!

You will notice something else too if you look closely at the compiler's output, or program the code into the ATTINY45. Notice how much longer the chip took to program?

The size of the compiled code for our simple program is now 3.3k in size!

Now turn compiler optimization back to the default of -Os and re-build the code. The code is now 170 bytes. Quite a difference!

So, while turning off optimization can be useful to you in some cases (particularly if you're wanting to watch this variable in the debugger), remember that turning off optimization changes some fundamental things. It can be useful, but once you are looking at un-optimized code you're no longer seeing it the way it will exist when programmed into the microcontroller.

So if nothing else, at least remember to turn optimization back on!

Additional discussion is outside the scope of this document, but to understand more about this issue, you can read the discussion on AVRFreaks.net for "When is -O0 appropriate?"

Conclusion

This wraps up the quickstart tutorial - you should now be able to use the tools and write basic code, and have a context in which to learn and explore more. Now you can get on with playing, instead of wresting with the tools and simple basics!

Main | Projects | About | Contact and Services


Original Content - Copyright 2010 (Except where specified)