where : ibrtses embedded

AVR RC Oscillator calibration to an XTAL

A recent project required me to work with an RC driven Mega169P. The setup
just had the RC plus a 32kHz Quarz. The calibrateable RC oscillator has a
tuning graph as

calibratable tuning graph
Taken fom the Mega169P manual 8018I-AVR-11/06, page 363

and I wanted to have the clock at the either timing convenient 8MHz or at
the baudrate convenient 7.3728MHz. First came the manual approach

manual frequency tuning

The manual tuning is not that hard. Have a 16bit timer running to produce say 10ms if it
was running at the desired frequency. Then either poll the timer overflow flag (or have
an interrupt) and toggle a pin. On the scope measure the pin and edit the value until the
period matches.

Since the scope doesn't have the correct resolution, the following code uses two timers
against each other. One as reference, the other for the Osc. It initializes the two timers,
timer2 for the 32kHz XTAL and timer0 for the supposed 10ms tick. The 32kHz clock produces
pulses at 1Hz, which are compared to the 10ms tick. Any other combination is also thinkable.

// timer0 is the 10ms tick from the 7.3728MHz or 8MHz Clock
// This is the 10ms system tick
SetupTimer0:
	ldi	temp,0x05	; clk div 1024 = 128us @8MHz 
	sts	TCCR0,temp      ;             or 138.8us @7.3728MHz
	ldi	temp,184	; 256-78 for 10ms @8MHz 
	sts	TCNT0,temp      ; or 256-72 for 10ms@7.3728MHz
	ret

// Setup timer2 for 1Hz and enable timer0
//
CalibrateManual:
	ldi	temp,0x44	; for 7.3728MHz ** enter this 
	sts	OSCCAL,temp     ;           value manually **
	ldi	temp,0		; Timer2 off
	sts	TCCR2A,temp
	ldi	temp,0x07	; div 1024
	sts	TCCR2A,temp
	ldi	temp,223	; 255-32 for 1 Hz
	sts	TCNT2,temp
	ldi	temp,0x08	;
	sts	ASSR,temp
	ldi	temp,0x01	; enable Timer0
	sts	TIMSK,temp
	ret
Why are two procedures above ? The Tick ist used anyway, The CalibrateManual
is replaced by intializing the OSCCAL in the normal Init. Now the interrupts.
They both basically just reload the counters, and toggle a pin. The pins are
from the application unused pins.
interrupt timer0
	push  ...
	ldi	temp,184	; 256-72 for 10ms
	sts	TCNT0,temp
	sbi	PORTB,6		; make a pulse
	cbi	PORTB,6
	pop ...
	iret

interrupt timer2
	push ...
	ldi	temp,223	; 255-32 for 1 Hz
	sts	TCNT2,temp	
	sbi	PORTE,6		; make a pulse
	cbi	PORTE,6
	pop ...
	iret
I has been shown by calibarting different units of the same hardware, that the
values for OSCCAL are almost independent on the voltage, a bit dependent on the
temperature, but very dependent on the specific controller itself. While for
one example the value was 0x44, with another, it was 0x5C. This means there is
no simple factory preset solution. This led to the automatic tuning described
now. Yes, a thinkable intelligent programmer could do a calibration itself.
Such a solution would then be dependent on the supply- and temperature condition
during the programming.

automatic frequency tuning

This version of the automatic tuning is done at powerup once. It starts with the
OSCCAL at the lowest and increases until the period is short enough. Here, to have
a sufficiently quick calibration the XTAL runs in an overflow mode, meaning it counts
from 0 to 255 and sets the overflow flag every 7.8125ms. While the timer2 counts
round, the software loop counts number of loops it can do incrementing a 16 bit
counter. At approximate 9000 counts per period, the resolution is sufficiently fine.
Before it is forgotten, the interrupts are disabled here.

If the temperature and supply conditions change considerably such that the UART
is not operating reliably anymore, another dynamic, meaning periodic calibration
will have to be considered.
CalibrateRC:
	ldi	temp,0
	sts	OSCCAL,temp
; init Timer2 as Async Timer
	ldi	temp,0		;Timer2 off
	sts	TCCR2A,temp
	ldi	temp,0x08	;Timer2 Async
	sts	ASSR,temp
;	ldi	temp,0x02	;timer2 div 8, on
;	sts	TCCR2A,temp	; overflow every 62.5ms
	ldi	temp,0x01	;timer2 div 1, on
	sts	TCCR2A,temp	; overflow every 7.8125ms
OptLoop1:
	ldi	ZL,0
	ldi	ZH,0
waitcrc1:
	in	temp,TIFR2
	andi	temp,0x01
	breq	waitcrc1
;	sbi	PORTB,6		;debug ******
;	cbi	PORTB,6		;debug ******
	ldi	temp,0x01
	out	TIFR2,temp
waitcrc2:			; counting loop takes 6 cycles
	adiw	ZH:ZL,1		; thus countin target is 9600
; wait for here
	in	temp,TIFR2
	andi	temp,0x01
	breq	waitcrc2
;	sbi	PORTB,6		;debug ******
;	cbi	PORTB,6		;debug ******
	ldi	temp,0x01
	out	TIFR2,temp
;	ldi	temp,0x62	;send 0x62 through the UART
;	sts	UDR,temp
; compare the Z with 9600
	cpi16	ZL,ZH,9600
	brcc	OptEnd	
; still too low

	lds	temp,OSCCAL
	inc	temp
	sts	OSCCAL,temp
	rjmp	OptLoop1
OptEnd:
	ret

The optionally inserted 0x62 for the UART serves as visual 
check for the scope.

There are a few more thinkable solutions, all of which are rather simple and take
just a few ASM lines. They provide a working, calibrated RC oscillator with little
effort.
Yes, before it is forgotten. Atmel has an Appnote, AVR053, where the STK500 or so
does that. There is another Appnote AVR055, where they also use a 32kHz Quartz for
the RC calibration. They have the focus rather on quickness and operate with binary
seaches for a quick calibration and use in the order of 300 bytes for that, about 5
times more than the above solution.

The above outlined calibration freely uses timer resources, and this is less of a
problem in a bootloader. To the contrary, this is hardly doable in an application.
A dynamic calibration has to be made to fit into the application. A serially serving
device (always able to receive) can never have the baudarte too far off. Changing
the interrupt behaviour require carefull considerations. We either haven't gone this
far, or didn't consider publishing it yet.

Comments are welcome.


Questions ?
Suggestions?
Feedback ?






sponsored links




home
the embedded pages
the AVR pages


last updated 21.oct.07, or perhaps later

Copyright (99,2007) Ing.Büro R.Tschaggelar