Friday, April 8, 2011

5 - AVR GCC Compiling

Sorry for the confusion. When these tutorials were written and photographed, we used the ATmega8. We now carry the newer ATmega168. You will find all ATmega168 information in the following pages, but the pictures will show an ATmega8.
I know very little about the ins and outs of the AVR-GCC compiler. I've learned a few basics that helped me along the way, but when you run up against a jam, google and AVRfreaks.net are your friend.
First, we did the blinky. Open this code in PN2 and make sure you can compile it. Click on Tools->Make All. The window in the bottom screen should say 'Process Exit Code: 0' meaning the compilation was successful. If not, there should be a line number listing of the problem line of code. Be sure to check above and below the indicated line for problems.
In the second example C file called basic-out-atmega168.c (basic-out.c for the ATmega8), I've inserted a handful of functions and lines of code. First of the black magic:
#define FOSC 16000000
#define BAUD 9600
#define MYUBRR FOSC/16/BAUD-1
What is all this noise at the top of the file? This is a series of defines that calculates the MYUBRR variable with the correct number. Since serial communication depends on the fact that we will be transmitting and receiving at 9600 bits per second, it's crucial to tell the ATmega168 what bit rate to set. Because the ATmega168 is dictated by the oscillator that it is using, we must correctly calculate what value we need to load into the ATmega168 hardware so that the ATmega168 sends the serial pulses at the correct rate with a given oscillator type. In our case, we are using a 16MHz oscillator so we can setup the define statement as shown. MYUBRR is calculated at compile time and is loaded successfully into the hardware UART during run time.

A reader's untested submission:
The UBRR value calculation in Lecture 5 could be more accurate with the following macro:
#define MYUBRR (((((FOSC * 10) / (16L * BAUD)) + 5) / 10) - 1)
There is also pretty useful web form for UBRR calculation:
http://www.wormfood.net/avrbaudcalc.php

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
This line creates a buffer for the printf statement to post to. I'd rather not explain it, simply because I don't understand it. When I am working on a new coding project I never start from a blank page, I *always* start from a known working program and slowly bring in bits of other projects to get the code I need, writing bits along the way. Please start from this printf example and build away. The purpose here is to get your printing string to the terminal window.
Checkout the ioinit() function. You'll notice some new commands.
UCSR0B = (1<<RXEN0)|(1<<TXEN0);
This is the really funky, but very practical, method of setting bits on the AVR series. RXEN0 is defined in some file as '5'. 1<<RXEN0 translates to 'shift a 1 to the left by 5 spaces'.  This is handy because you don't need to remember specifically where the RX enable bit resides, you only need to know to set (or not set) the RXEN0 bit by using this bit command. Same goes for TX enable. Using the example code above, these two bits (RXEN0 and TXEN0) get set and loaded into UCSR0B register, enabling the TX and RX hardware on the ATmega168.
Finally, we see the very comfortable line of code:
printf("Test it! x = %d", x);
What did you say? This is not a comfortable line of C code for you? Ok - printf is somewhat of a universal function to pass serial strings and variables to the outside world. The line of code above will pass the string "Test it! x =" to the serial port and it should display on the terminal window. After that, the %d gets converted to an actual decimal number so that whatever digital number is currently stored in the variable x gets printed to the terminal screen. So what? This simple printf statement allows you to print variable contents and see what's going on within your C program.
Time to load the basic-out-atmega168.c file onto your breadboard. Open up PN2, compile basic_out-atmega168.c. Power up your board, click on Tools->[WinAVR] Program from within Programmer's Notepad. The code should now be loaded onto your ATmega168. If WinAVR throws a verification error, try again. Open up the terminal window at 9600bps if you don't already have it open.
Text output from the ATmega168 and MAX232 circuit
All right! We've got output from the ATmega168! Now let's talk about some more of the code:
sbi(PORTC, STATUS_LED);
Another funky one if you're not used to the AVR series. To toggle a GPIO pin (general purpose input/output pin), you need to read the state of the port, mask the bit change into the state-word, and then write the 8-bits back onto the port effectively modifying just the one bit. Instead of doing all that by every time you want to toggle a port pin, there's this handy macro:
#define sbi(var, mask) ((var) |= (uint8_t)(1 << mask))
SBI sets a bit. CBI clears a bit. You have to specify which port you're working with and which pin you want to alter. Throw another define at the top of your code:
#define STATUS_LED 0
Now you can control your STATUS_LED on PORT C using these two simple commands:
sbi(PORTC, STATUS_LED);
To turn on the LED and
cbi(PORTC, STATUS_LED);
To turn it off.
You should have an LED tied to pin 23 on the ATmega168. When in doubt, toggle your status LED to figure out where the code is hanging or use a printf statement.
There are also some tweaks to the delay_ms() routine. Because we increased the oscillator from 1MHz to 16MHz, I increased the loop iterations to tie up the processor for longer. I didn't do any real calculations so don't depend on my delay_ms routine. delay_ms(1000) looks to be roughly a 1 second delay.
Open basic-in-atmega168.c (basic-in.c for the ATmega8) and load up your breadboard:
Key presses and various responses
Here we see that whatever character we hit, the ATmega168 responds with 'I heard : ' and the character. Also, if you hit return, X, or g, you will see various special output.
key_press = uart_getchar();

printf("I heard : %c\n", key_press);

if(key_press == 'g') printf(" GO!\n");
if(key_press == 'X') printf(" EXIT\n");
if(key_press == 13) printf(" RETURN\n");
uart_getchar sits waiting for a character to appear in the UART. Once received, the ATmega168 outputs the character (%c) and goes to a new line (\n). It then checks to see if the key press was one of three special cases. If so, it prints an extra string accordingly. I hope you are starting to see the power of the input/act-upon/output that a microcontroller is capable of. With a little bit of work, you could program your own text-based adventure game. Go to town.
Remember back when you were struggling to get your power supply wired up? Nice job! Time to heat up your irons.

No comments:

Post a Comment