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

AVR Oscilloscope, Part 1

Icon Leave Comment
So, I feel like it's been a while since I've actually done any sort of coding in a blog entry. Therefore, I present to you the following piece on getting a small scale oscilloscope running on an ATMega chip. The code presented here is being compiled for an ATMega644PA, but you should be able to tailor it to your specific situation quite easily. First, some background.

Why on earth would anyone find this useful?
When it comes down to it, all we're really doing is taking data read in over an ADC, and throwing it through a USART, right? Well yeah, but that's a bit of an oversimplification. Look at it this way. There are tons of USB interfaceable oscilloscopes out there that work plain well, but these can be expensive ($200+) What I am presenting here is a setup that will cost you between $50 and $80, depending on what programmer you buy (chips are generally $8 or so, an AVR Dragon is ~$60). Of course, if you already have the equipment, it's completely free! Any chip with at least one UART or USART (same damn thing as far as I'm concerned...) and an Analog to Digital converter.

Now, my chip at least has a maximum speed of 20 MHz. The ADC can't run near that really, and top on transferring data up the USART, and, well, there are some speed issues. Low frequency signals are no problem for this setup. Perfect, in fact. By interfacing our device with MATLAB, we can plot and view the data after performing some nifty FFT's on it, but this comes much later. For now, we'll concentrate on getting the USART up and running, and make sure you all understand the concept of interrupts, and how they can be better than polling.

But.... but I want to see really fast signals!
This is entirely possible. With the right amount of effort, you can measure signals in the GHz with such a device. The idea here is that you're sampling rate is not a multiple of the wave being measured. For example, if I have a 1 GHz signal I want to plot, I'd choose to measure at something like 33 kHz. Since 1GHz/33kHz isn't a rational number, we can assume that a good picture of the waveform can be generated. This is called "under-sampling." This is not intended to be covered in this tutorial, but may be reflected upon in the end, I'll give insight on how to go about creating such a thing.

Ok, so about this USART
Well, let's get started. If you take a look through your Atmel's datasheet, you will find examples of initialization code and transmit code, etc, that can be used for the USART. My code is a slightly modified version of what's there. So, go ahead and open your favorite text editor, or AVR Studio, or Eclipse (with the AVR plugin ;)) and lets get started! I'll leave the directory structure up to you. (Some people like to have header files in one directory, source in another, etc...)

Let's go ahead and start right with the USART, since it's basically laid out for us. We'll create a header file, titled something easy to remember like 'usart.h', and add this to it:

#ifndef _USART_H_
#define _USART_H_

#endif



If you've ever written header files before, you should recognize this, it makes sure that the declarations only happen once!
Go ahead and add the prototypes for the initialization and transmission functions between the #define and #endif statements. These will look just like this:

void USART_Init(unsigned int baud);
void USART_Transmit(unsigned char data);



Great! Now we need the source file, where the functions go. Create a new file called 'usart.c', or <whatever you named your header file>.c, and lets set up our USART.

We'll need a couple of header files for things such as the io facilities, and the declarations we just created. So, we'll need the following, first thing:
#include<avr/io.h>
#include"usart.h"



We'll then add a function for initializing our usart. It'll go something like this:
void USART_Init(unsigned int baud) {
    UCSR0A = _BV(U2X0);
    UBRR0H = (unsigned char)(baud >> 8);
    UBRR0L = (unsigned char)baud;
    UCSR0C = _BV(TXEN0) | _BV(RXEN0) | _BV(RXCIE0);
}


This allows for the atmel to send and receive data at a baud rate determined by Baud, which is a code determined by your desired baud rate and the speed of your IO clock, which is about 14.7 MHz in my case, but may vary in yours, atmels basically all come with the IO clock set to 8 MHz by default. We don't need to set our character size or any of that, this comes preset to 8 bit data, 1 stop bit, no parity, no handshake, and works like a dream. U2X0 can be looked up in the documentation.

Right on, now for transmitting. Lets add a function to transmit data. This function polls UCSR0A to check if UDRE0 is set. When UDR0, the USART Data register, is empty, this bit is set, meaning we can transfer new data. This is done with a simple while loop. Once we break from the while loop, we load UDR0 with the data we want to transmit, and the microprocessor sends it off. Neat eh?

So, what does this function look like? Simple enough:
void USART_Transmit(unsigned char data) {
	while (!(UCSR0A & (1<<UDRE0)));
	UDR0 = data;
}



Now for receiving data. Now, this isn't so much important for creating an oscilloscope, and will probably be scrapped further down the line, but knowing that this feature is available and knowing how to use it can allow for powerful control over your microprocessor application via a terminal or via MATLAB. So, lets write an interrupt service routine that will trigger whenever data is received, and echo that data back to the sender. Trivial, but it allows us to demonstrate that we have a functional USART.

This can be added in usart.c, but I prefer to keep all of my interrupts in my main source file. So, go ahead and create a new source file called main.c, and lets add some stuff to it.

We'll want the standard io, interrupts, and our usart header file.
What I've done is enabled port d pin 7 as an output, and turned it high after initialization. This lets me know things have gone smoothly. We can do more with this, such as have the LED flash whenever we enter the Interrupt service routine, but I chose to keep it simple for now. As for hooking up the LED, make sure you plug it in the correct way, and add a resistor on the anode going to ground (small resistance, like 220 ohms) to drain some of the current. Heres my main.c in all it's glory.
#include<avr/io.h>
#include<avr/interrupt.h>
#include"usart.h"

ISR(USART0_RX_vect) {
    USART_Transmit(UDR0);
}

void init(void) {
    DDRD = 0x80;    // Enable PD7 for output
    USART_Init();   // Initialize USART
    sei();          // Enable global interrupts
}

int main(void) {
    init();
    PORTD = 0x80;    // Turn on PD7
    for(;;) ;        // Infinite Loop
    return 0;        // Should never get here
}



Now compile, download to your chip, and see if it works. Just try to log in via Putty, Hyperterminal, or MATLAB. You should be able to type a character, and have it appear on the screen.

Next time, we'll look at getting the ADC running, and go over how to approach sending 10 bits worth of data to our computer.

~Bodom

0 Comments On This Entry

 

January 2022

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

Recent Comments

Search My Blog

10 user(s) viewing

10 Guests
0 member(s)
0 anonymous member(s)