| 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 } } |