Adjusting an AVR OSCCAL Value

knob

Adjusting an AVR OSCCAL Value

I’ve been playing with a uLog and pushing it’s limits and I wanted to see if the ATtiny24’s OSCCAL could be tuned better, thus improving the accuracy of the 8MHz internal RC. I used a DS3231 (ChronoDot) as a 32.768kHz TCXO connected to the uLog T1 pin.

You’re probably asking why. My uLogTimer project has been published here, however I thought somebody might otherwise find this useful.

uLogCal.h

//
//uLog pin outs
//
//PA0 = Not used this implementation*
//PA1 = Not used this implementation*
//PA2 = Not used this implementation*
//PA3 = SCK clock (DataFlash)
//PA4 = SCL/T1*
//PA5 = RX/MISO*
//PA6 = TX/MOSI*
//PA7 = SO (DataFlash)
//PB0 = Status LED
//PB1 = SI (DataFlash)
//PB2 = CS (DataFlash)
//PB3 = RESET*
//(*) pins on edge connector

#define TRUE                  	1
#define FALSE                 	0

//internal oscillator freq
#define FOSC  8000000UL
//frequency in MHz
#define CLOCK_CYCYLES_PER_MS	(FOSC/1000000UL)

uLogCal.c

/*
  uLog OSCCAL Calibrator
  Copyright 2012, all rights reserved.
  James M. Eli <j_eli@hotmail.com>
  1/17/2012
 
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
  ---
  
  borrows heavily from uLog source
  with inspiration from avrfreak Spamiam
  adapted for ATtiny24 & uLog
   
  PA4/SCL/T1: 32.768kHz precision clock input (I used a ChronoDot DS3231 TCXO) 
  Assumes uLog running on internal 8MHz oscillator
*/
#include <avr/io.h> 
#include <avr/interrupt.h> 
#include <string.h>
#include "uLogCal.h"

//global variable 
volatile uint8_t hi_ticks; //overflow counter 

//
//basic delay functions
//

//microseconds (limited to 256)
void delay_us(uint8_t us) {
  TIFR1 |= 0x01;		//Clear any interrupt flags on Timer1
  TCNT1 = 65536 - us; 
  while (!(TIFR1 & 0x01))
    ;	
}

//milliseconds (limited to 256)
void delay_ms(uint8_t ms) {
  for (; ms>0 ; ms--) {
    TIFR1 |= 0x01;		//Clear any interrupt flags on Timer1
    TCNT1 = 64538; 		//65536 - 998(us) == ~1ms, tweaked in AVRStudio
    while (!(TIFR1 & 0x01))
      ;
  }
}

//
//serial read/write functions
//

char SerialGetChar(void) {
  char k, m, l;
	
  l = 0;
  k = 0x20;
  while (k & 0x20) {
    k = PINA;
    k &= 0x20;
  }
  delay_us(35);				//bit width @ 38400
  for (k=0; k<8; k++) {
    l >>= 1;	
    m = PINA;
    if (m & 0x20) 
      l |= 0b10000000;
    else 
      l &= 0b01111111;
    delay_us(23);
  }
  return l;
}

void SerialPutChar(char c) {
  uint8_t i;
	
  PORTA &= 0b10111111;
  delay_us(23); 			//bit width at 38400
  for (i=0; i<8; i++) {		//8 data bits
    if (c & 0b00000001) 
      PORTA |= 0b01000000;
    else 
      PORTA &= 0b10111111;
    c >>= 1;
    delay_us(24);
  }
  PORTA |= 0b01000000;		//set it high again
  delay_us(23);
}

//string buffer used by btoa()
static char btoa_buffer[4];

char * btoa(uint8_t val) {
  char *p;
     
  p = btoa_buffer + sizeof(btoa_buffer);
  *--p = '\0';
  do {
    *--p = '0' + val%10;
    val /= 10;
  } while (val);
  return p;
}

void PrintNumber(uint8_t number) {
  char *s;
  uint8_t i;

  //convert uint8_t to ascii
  s = btoa(number);
  //output byte by byte
  for (i=0; i<strlen(s); i++)
    SerialPutChar(s[i]);
}

//out line feed & carriage return
void LineFeedCarriageReturn(void) {
  SerialPutChar(10);	//NL
  SerialPutChar(13);	//CR
}

void FlashLED(void) {
  //blink LED 
  PORTB &= 0xFE;		//status LED on
  delay_ms(20);
  PORTB |= 0x01;		//status LED off
}

//OSCCAL calibration direction
#define DOWN 1
#define UP   2

//calibrate OSCCAL here
void Calibrate(void)  { 
  uint8_t direction = 0;	//no direction yet
  uint8_t complete = FALSE; 
  uint16_t ticks;			//elapsed time in CPU clock cycles 

  while (complete == FALSE) { 
    hi_ticks = 0; 
    
	// Timer0 @ 8MHz on internal RC
	// Timer1 @ 32.768KHz on external signal
	// Run T1 for 210 counts then count T0 overflows + TCNT0
	// T1@210 ticks == T0@51269 ticks
	// math: (T1)32768/210=156.038, (T2)8000000/156.038=51269 

    //clear all interrupt flags 
    TIFR0 = 0xff;
    TIFR1 = 0xff;
    //zero both timers
    TCNT0 = 0; 
    TCNT1 = 0; 
    TIMSK0 = 0; 
    TIMSK1 = 0; 
    TCCR0A = 0;

    //CTC mode 2 with external clock on rising edge
    TCCR1A = 0;
    TCCR1B = (1<<CS10) | (1<<CS11) | (1<<CS12) | (1<<WGM12);
    OCR1A = 209; //(209 + 1) external clock transitions

    //wait for timer1 interrupt flag to set
    while (!(TIFR1 & (1<<OCF1A)));
    //start counting on timer0, normal mode 0
    TCCR0B = (1<<CS00);

    //enable timer0 overflow interrupt 
    TIMSK0 |=(1<<TOIE0); 
    //clear timer1 flag 
    TIFR1 |= (1<<OCF1A); 
    //enable interrupts 
    sei();
  
    //wait for timer1 interrupt to trip
    while (!(TIFR1 & (1<<OCF1A)));
    //turn off timer0 immediately (a few ticks latency, 4 clocks maybe?) 
    TCCR0B = 0; //timer0 off
    TCCR1B = 0; //timer1 off
  
    cli(); 
  
    //add up internal clock ticks (looking for 51269)
    ticks = ((uint16_t)hi_ticks<<8) + (uint16_t)TCNT0; 

    if (ticks > 51369) {        
      OSCCAL--; 	             //it's too slow
      if (direction == UP)       //if it changed sides then we are done 
        complete = TRUE; 
      else
        direction = DOWN; 

    } else if (ticks < 51169) {
      OSCCAL++;                      //it's too fast
      if (direction == DOWN)     //if it changed sides then we are done 
        complete = TRUE; 
      else
	    direction = UP;

	} else 
	  complete = TRUE;           // +/-100 clock cycle "acceptable" window
  
  } //while      
  sei();
} 

void Menu(void) {
  char c;
  
  sei();
  while (1) {
    SerialPutChar('?');		//anybody out there?
    LineFeedCarriageReturn();
    c = SerialGetChar();	//get user input
    SerialPutChar(c);		//echo
    LineFeedCarriageReturn();

    //output OSCCAL
    if (c == 'o') {
      PrintNumber(OSCCAL);
      LineFeedCarriageReturn();
    }

    //calibrate
    if (c == 'c') {
      uint8_t osccal;

      osccal = OSCCAL;
      FlashLED();
      Calibrate();
      //reset timer1 for use by delay functions
      OCR1AL = 0;
      TCCR1B |= (1<<CS11);	//clk/8
      PrintNumber(OSCCAL);
      LineFeedCarriageReturn();
      //
      //note: next line returns OSCCAL to original value 
      //
      OSCCAL = osccal;
      //
      //
      //
    }

  }  //while
}

int main(void) {
  //uint16_t i;
  char a;

  cli();

  //init ports
  DDRA = 0b01001000;  	//PA6=TX/MOSI, PA3=SCK
  PORTB = 0x07;			//0x07 = 0b00000111
  DDRB = 0b00000111;  	//PB0=Status LED, PB1=SI(local output), PB2=CS

  //setup timer1 for use by delay functions
  TCCR1B |= (1<<CS11);	//clk/8

  //hello
  FlashLED();

  //check for UART connection
  PORTA &= 0xDF;		//RX pin low to discharge a floating pin
  PORTA |= 0x40;		//TX pin hi to start communication
  DDRA |= 0x20;
  DDRA &= 0xDF;
  //pause
  delay_us(5);
  a = PINA;
  //if there's a high-ish voltage on the MISO line, we're likely connected to a serial link
  if (a & 0x20) 
    Menu();
	
  //we never get here...
  return 0;
}

interrupt.s

/*
  uLog OSCCAL Calibrator interrupt handler
*/
#include <avr/io.h>
#include "uLogCal.h"

/*
ISR(TIM0_OVF_vect) {
  hi_ticks++;
}
*/

TIM0_OVF_vect:
  push   r24          
  push   r0          
  in     r0, _SFR_IO_ADDR(SREG)    
  lds    r24, hi_ticks
  subi   r24, 0xff  
  sts    hi_ticks, r24   
  out    _SFR_IO_ADDR(SREG), r0     
  pop    r0          
  pop    r24 
  reti                  
Advertisements

About Jim Eli

µC experimenter
This entry was posted in Uncategorized and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s