Full Version : Fast Clock from 32Khz
avr >>HOME & TIME & TEMPERATURE PROJECTS >>Fast Clock from 32Khz


AVR_Admin- 04-16-2006
Deriving a fast clock from a 32kHz resonator

This code performs continuous adjustment of the internal RC oscillator from an attached 32kHz watch resonator.

The resulting RC oscillator calibration is good enough to drive a UART, and it's good over the entire temp range and voltage range.

Code is implemented in ATMega169L but should work in any device that can drive the 32kHz oscillator.

Link to Project: http://www.avrfreaks.net/index.php?module=...em_type=project

CODE

/*

  This example shows how to lock the on-chip RC oscillator to an external
  32768Hz crystal, giving an ATMega a 'poor-man's PLL' functionality.
  This is desirable because

    + we can use and tweak the on-chip oscillator for UART communications
    + lower EMI
    + we only need to stock 32kHz resonators or cannibalize watches
    + temperature/voltage effects get compensated out
    + I can have lowpower standby AND communications on my Mega169.

  This demo was implemented on an ATMega169L, the LCD interrupt was used
  to generate a slow (113Hz) interrupt derived from the 32kHz crystal,
  and timer2 was used for counting the RC oscillator.
  It waits for 32768 cycles (one second) for the oscillator to stabilize,
  then measures the RC oscillator 5 times per second, and bumps OSCCAL one
  up or down as required. Experience shows that the resulting clock is
  within 1% of the desired value.

  Porting instructions:
  ---------------------
  If implemented on something like ATMega16, use timer2 in asynchronous mode
  to get a 32kHz-derived interrupt, and timer0 for the RC-counter.

  When implemented, the measurement period (in this example 20 ticks of the
  LCD interrupt) should produce no more than 16000 counts on the RC-derived
  timer. This ensures that it doesn't overflow if OSCCAL is set high by
  accident, giving, say, a 24MHz clock.

  Fuses should be set for internal 8MHz oscillator.


  32kHz-derived-interrupt pseudocode:
  -----------------------------------
  if just powered on, wait appprox. 1 second for xtal to stabilize.
  on first interrupt, get the 16 bit timer timestamp.
  on the 21st interrupt, 20 interrupt-times later, get it again.
  calculate the difference.
  if the difference is outside the tolerable values, adjust OSCCAL +/-1.
  repeat indefinitely.

  Example (C) 2004 Kasper Pedersen

  2006.04.08: fixed incorrect ovf2 check that caused glitches
*/

//FLL parameters for 32kHz watch xtal mode.
//unit = 728,17777 Hz with 32768/16/3/6 reference and 20 loops/test.
// 8MHz/unit=10986.3,  maxdeviation=100/10986=0.91%.
#define OSCILLATOR_CENTER 10986
#define OSCILLATOR_PULLIN 100

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/signal.h>
#include <avr/interrupt.h>

/////////////////////////////////////////////////////////////////////////////
//
//  SystemTimers
//

unsigned char t2c_h,lcd_adjustdiv;   //t2c_h incrementes at 244Hz in my demo..
unsigned char calen;                 //allow adjustment
unsigned int lcd_lasttime,lcd_delta; //timing values


SIGNAL(SIG_LCD) //we're using the LCD interrupt as 32kHz-derived timer
{
 unsigned char t2l,t2h;
 unsigned int lcd_ticktime;
 ++lcd_adjustdiv;
 if ((lcd_adjustdiv==0)||(lcd_adjustdiv==20)) {

   //get the 16-bit-timer-value. I've used an 8 bit timer and soft-extended
   //it to 16 bits.
   t2l=TCNT2;           //get counter and check for overflow interrupt zone.
   t2h=t2c_h;
   if ((t2l&0x80)==0)   //if TCNT2 was in the range where carry has happened
     if (TIFR2&(1<<TOV2)) t2h++;        //and we have an outstanding carry then fix it.
   lcd_ticktime=(t2h*256)+t2l;  //looks stupid, optimizes away. check your compiler for best way to do this.

   //if we're at tick 20, adjust.
   if (lcd_adjustdiv) {
     lcd_delta=lcd_ticktime-lcd_lasttime;
     if (lcd_delta<(OSCILLATOR_CENTER-OSCILLATOR_PULLIN)) { if (calen) OSCCAL=OSCCAL+1; }
     else
     if (lcd_delta>(OSCILLATOR_CENTER+OSCILLATOR_PULLIN)) { if (calen) OSCCAL=OSCCAL-1; }
     lcd_adjustdiv=0xFF; //skip one timeslot. if adjustment has happened, the active slot is a mix of new and old.
   }
   else
     lcd_lasttime=lcd_ticktime; //remember the timestamp for next time.
 }
}

SIGNAL(SIG_OVERFLOW2)       /*CPU_CLOCK frquency counter function*/
{
 t2c_h++;   //make this a softextended-16-bit timer.
}

/////////////////////////////////////////////////////////////////////////////
//
//  demo main
//

void setdigit(unsigned char dig, unsigned char v)
{
 //specific to your display. sets a digit, 16 means blank.
}

int main(void)
{
 unsigned long t;
 
 ////////////////////////////// I/O SETUP ////////////////////////////
 ACSR =0x80;  //disable analog comparator to save power.

 ////////////////////////////// LCD init ////////////////////////////
 LCDCRA = 0x98;  // Enable LCD, enable LCD interrupt.
 LCDCRB = 0xA6;  // external clock, 1/3
 LCDFRR = 0x02;  // /16 prescaler, 3 divider, 6 tridutymode-divider
 LCDCCR = 0x0F;  // Contrast = 15
 //this results in a LCD framerate of 113.77Hz.

 ////////////////////////////// SYSTICK init ////////////////////////////
 //8MHz to 62kHz divider, used for FLL and general-purpose timer functions.
 TCCR2A=5; //div 128. -> 62500/sek -> 244Hz overflow rate.
 TIMSK2=1; //overflow irq
 lcd_adjustdiv=256-113;   //one second startup delay for FLL

 ////////////////////////////// USART init ////////////////////////////
 UCSR0A=0x22; //UDRE 2X
 UCSR0C=0x06; //UCZS1 UCSZ0
 UBRR0H=0;
 UBRR0L=13-1;   // 8M /8 /76800 = 13.0
 UCSR0B=0x1C; //RXEN TXEN UCSZ2
 //results in 76800 baud, 9-bit frame (for multimaster)

 ////////////////////////////// Main loop ////////////////////////////

 calen=1; //allow the interrupt to fiddle the clock.
          //If we want to see the clock, but not autoadjust it, set 0.

 sei();
 for(;;)
 {
   cli();
   t=lcd_delta;
   sei();
   t*=7282; t/=100000;

   //setdigit() is a function for writing digits on an attached LCD display,
   //and is not included in this example. Roll your own.
   setdigit(7,t%10);
   t/=10;
   setdigit(6,t%10);
   t/=10;
   setdigit(5+16,t%10);
   t/=10;
   t%=10;
   if (t) setdigit(4,t);
   else setdigit(4,16); //blank digit

   //transmit a char
   while ((UCSR0A&0x20)==0);
   UCSR0B&=~1; //clear D8
   UDR0=0x55;  //results in a 01010101001
 }

}



Forumer™ is Voted #1 Free Forum Hosting provider
Build your own community today with the largest message board hosting company.