Friday, June 17, 2011

Generate a Clock Signal with AVR ATmega

AVR-generated clock signal
Several times lately my MCU needed to generate a clock signal to interface with a device, such as a camera, or a serial interface on an analog to digital converter (ADC).

Rather than bit-banging the clock signal, let the MCU's timer hardware do the work, freeing up cycles for real code.

Here's how to generate a simple, 50% duty cycle pulse train, aka clock signal, using an AVR MCU. For this experiment I used an ATmega328P but any of the AVR chips that support a 16-bit Timer1 should do.

I wanted a 500kHz clock signal. To generate it, the MCU must toggle an output pin, PB1 aka OC1A at 1MHz, or 1/16 the MCU's 16MHz clock, for a total period of 2us (500kHz).

Timer1 provides a mode called Clear Timer on Compare Match (CTC). The timer register, TCNT1 counts from 0 up to the value in the Output Compare Register, in this case OCR1A (to go along with the OC1A output). When TCNT1 == OCR1A, the MCU resets TCNT1 and starts counting again.

Ok, let's get started with the code.

Though we probably don't need to, why not initialize the counter:

TCNT1=0;

To run the timer at 1MHz, we need to divide the MCU clock by 16. Using a prescaler value of 1, we simply set the output compare register to 15, since the timer counts from 0-15 or 16 ticks.


So we come up with:

OCR1A = 15;

Note that we could also have used a prescaler value of 8 and an OCR1A of 1.

Now it's time to set some mode bits. The AVR has three Timer/Counter Control Registers for timer 1: TCCR1A, TCCR1B and, you guessed it, TCCR1C.  In TCCR1A, two bits control the behavior of OC1A when the timer matches OCR1A. Those two bits are COM1A1 and COM1A0. When set to 01, OC1A is toggled when there's a compare match.

TCCR1A |= (1<<COM1A0);

To tell Timer1 to operate in CTC mode, we set bits WGM13, WGM12, WGM11, WGM10 across two control registers. Actually for CTC mode, WGM12=1 and the rest are 0 (initial value on powerup)

TCCR1B |= (1<<WGM12);

To configure Timer1 prescaling, set bits CS12, CS11 and CS10 in TCCR1B. For no prescaling, use 001, respectively. That is, set CS10=1

TCCR1B |= (1<<CS10);

The only thing left to do is to enable the OC1A (PB1) pin for output

DDRB |= _BV(1);

Putting it all together, here's the code that generates the clock signal in the picture above.

TCNT1=0;
OCR1A = 15;
TCCR1A |= (1<<COM1A0);
TCCR1B |= (1<<CS10) | (1<<WGM12);
DDRB |= _BV(1);


Finally, if you want to sync your code to the rising and falling clock edge, check the TIFR1 register for the OCF1A flag:

if ((TIFR1 & _BV(1)) == _BV(1)) {
// do something here
}


Or, you can sync your code to a high or low clock value by reading PB1 from the PINB register.  The MCU can also be set up to call an interrupt service routine whenever there's a match.

9 comments:

  1. Hi this is really one of the things I need. thanks for the post. However, I'm having some problems with the results, the output pin is PB1, right? but when I connect it to the oscilloscope, I see frequency of only 100 kHz... thanks,,, I really need some help here.... I need at least 500 kHz for my project... thank you

    ReplyDelete
  2. @Anonymous: I just measured mine and it's running ~470kHz on a 16MHz ATmega328P. That's off by about 5% but that's much better than a 500% error :) We'll figure this out. :)

    No offense intended but are you counting divisions correctly? Most scopes show tiny hash marks that equal 0.2 division. The big grid marks count as 1 division.

    I set my Hitachi V-1050F's time/div to 1us and the ~500kHz signal shows up with a 2us period = 500kHz. (If you accidentally counted each hash as a division you'd get a 10us period = 100kHz which would explain your off-by-500% result)

    I have some other ideas if this isn't it.

    ReplyDelete
    Replies
    1. Your timer starts at 0, not 1. When you set OCR1A = 16, you're counting from 0-16, not 1-16.

      Delete
    2. Four years late, but remember that the counter starts at zero, not 1. When you set OCR1A = 16, you're counting from 0-16 not 1-15.

      Delete
    3. Argh, that's embarrassing on my part. :( I fixed the article and included the proper equation for figuring out frequency

      Delete
  3. yes, I'm quite sure that I measured it properly using my oscilloscope. anyway, I'm using Arduino duemilanove microcontroller(also ATMega 328P). and the pin mapping is PB1 -> digital pin9.

    so i directly connect the oscilloscope to the pin 9 of arduino?... where could the problem be?... I really need some help,, thanks.....

    ReplyDelete
  4. Connect oscope probe to Digital Pin 9, and connect oscope ground to ground pin.

    Freq is off by a factor of 5. So maybe clock scaling got messed up or a timer scaling issue is to blame.

    Can you send me your source and I will test it on my board just to eliminate that as a possibility. Use the Contact Me link at the top/right of the page.

    ReplyDelete
  5. hi! i'm a beginner in using microcontroller..i have questions and hope you can gives some pointers or tips..
    can atmega generate 2 clock signals?? it's because i need 2 clock signals that will be connected to a CCD sensors (charge-couped device)
    to activate the CCD,these 2 clocks are needed..for the ROG clocks (read-out gate) and CLK..is it possible??

    ReplyDelete
  6. hi!

    First of all I want to thank you for the time and effort you spend to provide this solution.

    I spend nearly two hours to find out, why the solution you provided did not work with my setup (Arudino UNO with 1.0 dev. environment).

    I had to actually reset the TCCR1A and TCCR1B registers, because they were initalized with 1 resp. 3??? (No modification done to either board or dev. environment), also I had to move the OCR1A init after the TCCR1A / TCCR1B init, otherwise it still had 0 and the timer didn't start ... (Complete code see below ...)

    #define CLK 9

    void setup() {
    // Set Clock to Output
    pinMode(CLK, OUTPUT);

    TCNT1=0;
    // Toggle OC1A on Compare Match
    TCCR1A = 0x00;
    bitSet(TCCR1A, COM1A0);
    // Clear Timer on Compare Match
    TCCR1B = 0x00;
    bitSet(TCCR1B, WGM12);
    // Set frequency (1MHz)
    OCR1A = 8;
    // No prescaling
    bitSet(TCCR1B, CS10);
    }

    ReplyDelete