Subscribe to Bodom's Universe        RSS Feed
-----

AVR with the C Programming Language for the Beginner

Icon Leave Comment
So this year, I took a course entitled "Electrical Engineering Design II." Part of this course work was to create a robot which would venture through an obstacle course and complete a task. We were given kits to do so, and basically told to go at it. Included in this kit was an Atmel ATMega644PA on a school-made programmer and evaluation board.

The first thing that should be said is that 99% of what you need to know to program an AVR micro controller can be found in the manual. The rest is either documented online in the avr-libc files, or from your knowledge of C, which, guess what, I assume you already have. The second is that the code presented below is meant for but not necessarily specific to the ATMega644PA. By reading the text of this article and not just skipping down to the end, you will have an understanding of where to find the information used in this blog entry.

I'm going to do a few things in this article. I will:
  • Describe how to generate a makefile (Windows only, sorry)
  • Set up a main function
  • Set up your initialization function
  • Create a timer comparison interrupt
  • Send a digital value to an output pin


There are a few things you will need for this tutorial to go smoothly. If you are running Windows, make sure you have winAVR installed. For linux, you will want things like gcc-avr and make, and your avr programmer. AVR programmers are out of the scope of this article, sorry. But there are plenty of programmers available, and all will have their documentation.

You will also need some form of text editor. If you know how to use it, vim is highly recommended, as it has syntax highlighting, and has the useful command :make, to build your project on the fly. This brings us to the first part of this tutorial.

Generating the Makefile (WINDOWS ONLY)
Please note: as capitalized in excess above, this will only apply for windows. The code generated this way is the same as would be used for unix, but since mFile, is, as far as i know, a windows program, this really will only work on windows. (Maybe try getting MFile and use wine?)

So, assuming you have everything installed, you are going to want to start by creating a folder for your project. Mine happens to be in my home folder, in source\c\AVR. Any file name will do, just be sure you remember where you put everything. Hit start, and find MFile, in the winAVR start menu. Using the Makefile Menu, we want to tailor it to our situation. You can change the main file name if you want. What's important here is that you change the MCU type to match whatever you are using, mine being the ATMega644PA. Change Debug Format to GDB if you wish, we won't use this. Also, it's good to change the value of F_CPU in the makefile to whatever is specified in your documentation, or whatever the fuses in your MCU have been set to. By default, if I remember correctly, this is 8000000 Hz, or 8MHz. Mine is 14745600 Hz, so I would change it accordingly.

Now, save that into your project folder you just made. You've got a makefile that's ready to go for any project you throw at it, just be sure to change "TARGET=<your project name>" with every new project, so it will compile that one. (That is, unless you name your source file the same thing every time).

Set up the Main function
Well, really, setting up what I like to think of as a template program. Open up your text editor, and save the empty file as <your project name>.c in the folder your makefile is in. I shouldn't have to say replace <your project name> with the name of your project, but there. I said it. Now, we're going to want to include some libraries specific to AVR programming. The first one, io.h, allows you to utilize different parts of the micro controller, such as the ports and analog to digital converter. It will use the header file to figure out which ports and the like to include, given which MCU you chose. We're also going to want interrupts.h, which allows us to use the hardware interrupts on the device. There are tons of interrupts, all of which are detailed in avr-libc online documentation. We'll also be creating a normal main function, without any sort of arguments. (Some of you have seen argc and argv... don't include these).

You should have something that looks like this:
#include<avr/io.h>
#include<avr/interrupt.h>

int main(void) {
    return 0;
}



You can now build this with the make file (running make from a command line that's been cd'd into your project directory, or, if you are using vim, by executing :make), and it should compile. But... it does nothing. This is.. well... useless.

So, we'll add something.

Set up your initialization function
Okay. Initialization. I have a habit when programming for AVR to use a function called init(). This is user defined, and written above main. (Hey, no prototypes!).

What do we want to do in this function? Well...
  • Set up the timer
  • Enable the timer interrupt to go off every ___ ms
  • Zero the timer
  • Enable the timer interrupt
  • Enable a pin on a port for output
  • Enable global interrupts


Wow, that's a lot. How do we do all this? Well, if you look at the timer0 outline in the AVR documentation, you will find registers, descriptions, modes, etc. This makes it irrelevant for me to go over any of that here. But, I will tell you what I am doing, and, if it's relevant, why. The first thing I do is set up the timer. Each timer has two control registers that set up things like the clock prescalers and the waveform generation mode. We're going to use a simple square wave, with a prescaler of 1024. For simplicity, i'll just go ahead and assume an 8MHz clock. Dividing this by 1024 gives you a clock who ticks about 8000 times per second, or at 8kHz. Well, I want 1 kHz, I have 8. So, I'm going to use what's called timer output comparison. I'm going to set a register value to 8, and have the timer count 8 ticks, and then fire off the interrupt. This gives me an interrupt every 1 ms. Cool! The defined hex values will make more sense after you look at the documentation's tables, but CLK1024 sets the prescaler for 1024, and WGMCRC sets the waveform generation to use OCR0A as the top value, and tells it to clear the timer back to zero on every comparison. OCR0A is an unsigned 8 bit value (0x00 to 0xFF).

To zero the timer, we set TCNT0, or Timer Zero Count to 0 (duh...). To set the interrupt, we want to set the bit referred to by OCIE0A to 1. This is why we use _BV(). There is a definition somewhere that says #define OCIE0A 2. This is referring to the bit number, not the hex number. Therefore, _BV(OCIE0A) will return 4, or 0b100, not 0b10. Get it? Good. (or too bad, depending on your answer). A lot of the time, it's best to use |= instead of =, especially when only changing 1 or a few bits. Unless you know exactly what each value should be, go with |= instead of = (or - equal instead of equal)

We'll want to be able to toggle something when the interrupt fires. I'm using PD7 in this example, shown by setting the bit in the data direction register for port D (making it 1).

Lastly, I am enabling interrupts globally. This should always be done after you fully initialize your micro controller. You don't want to interrupt initialization! Weird things could happen! The code below shows everything in action. Add this into your program, and call init from main.

#define WGMCRC  0x2;
#define CLK1024 0x5;

void init(void) {
    // Step 1
    TCCR0A = WGMCRC;
    TCCR0B = CLK1024;
    // Step 2
    OCR0A = 8;
    // Step 3
    TCNT0 = 0;
    // Step 4
    TIMSK0 |= _BV(OCIE0A);
    // Step 5
    DDRD |= 0x80     // 0x80 referring to bit 7 (remember? 0x80 is 0b10000000)
    // Step 6
    sei();
}



Ok, so where do we go from here? Well, we don't want to just exit main, as this causes the program to stop, and our interrupts never occur. So add an infinite loop into the mix. Main should now look something like this:

int main(void) {
    init();
    for(;;) ;
    return 0;
}



Ok, onto creating an interrupt. This is literally the shortest step.

Making the interrupt
So, interrupts. You have a macro called ISR(), which takes one or more arguments. We'll only worry about using one, as this is what applies to most situations. The argument we will give it is the interrupt vector of the interrupt we desire. In our case it is as was stated above. TIMER0_COMPA_vect. So, we have this now above our init function.

ISR(TIMER0_COMPA_vect) {
}



And look at that, we have a function that is triggered every 1 ms. Peachy.

Send a digital value to an output pin
So, let's actually do something. We want to toggle a pin every time this fires off. This gives us a square wave with a period of T = 2ms. We'll use the simplest approach for doing so as well, an exclusive OR, or XOR. In C syntax, we will be using ^=, which is XOR equals. To refresh your memory, an XOR truth table looks like this:

|X||Y|Z|
|0|0||0|
|0|1||1|
|1|0||1|
|1|1||0|

So, when the values are the same, you get a zero, and when they are different, you get a 1. The code looks like this:
ISR(TIMER0_COMPA_vect) {
    PORTD ^= 0x80;
}



Is this safe? Will this effect other ports? Yes it is. No it will not. Given that each other bit is a 1 or 0, XOR will not effect the bit if written with a 0. If the previous bit is 1, the new bit will be 1 (1 XOR 0 is 1). If the previous bit is 0, 0 XOR 0 is 0. Everyone wins. The only thing ever toggled is bit 7. Now, you can hook bit 7 up to an oscilloscope or a motor, and you should see it running after you apply the full source code, compile it, and download it to your device. (Given of course, you have the proper circuitry.)

THE FULL SOURCE CODE -- THIS WILL NOT WORK ON EVERY ATMEL. YOU HAVE BEEN WARNED
/* Alexander Hart
 * July, 2010
 * This code is released to the public.
 * It may be used in any commercial or public application
 * In classrooms, wherever.  However, credit to the original
 * author is to be preserved.
 */
#include<avr/io.h>
#include<avr/interrupt.h>

#define WGMCRC  0x02
#define CLK1024 0x05

ISR(TIMER0_COMPA_vect) {
    PORTD ^= 0x80;
}

void init(void) {
    TCCR0A = WGMCRC;
    TCCR0B = CLK1024;
    OCR0A = 8;
    TCNT0 = 0;
    
    DDRD |= 0x80;
    sei();
}

int main(void) {
    init();
    for(;;) ;
    return 0;
}




And there you have it. Where to go from here? Try implementing another interrupt! Make a task controller with a set of leds. Explore function pointers.

Questions, Comments, Corrections, Quirks, etc. Please say so below.


Peace,

Bodom

0 Comments On This Entry

 

January 2022

S M T W T F S
      1
2345678
9101112131415
161718192021 22
23242526272829
3031     

Recent Entries

Recent Comments

Search My Blog

1 user(s) viewing

1 Guests
0 member(s)
0 anonymous member(s)