| Author |
Message |
|
|
Posted: Mar 04, 2006 - 06:18 AM |
|


Joined: Jan 23, 2004
Posts: 6964
Location: Melbourne, Victoria, Australia
|
|
Freaks,
Another extract from my ButtLoad project. This code, modified from Collin O'Flynn's code, will calibrate the Butterfly's internal 8MHz RC oscilator down to 7372800Hz for very good 115200 baud serial communications. It assumes that a 32768Hz watch crystal is attached to timer 2.
Can be ported easily to other AVR microcontrollers.
Osccal.c
Code:
#include "OSCCal.h"
/* Code taken from Colin Oflynn from AVRFreaks and modified. His code originally used an externally
divided 32768Hz clock on an external interrupt pin, but I changed that to use the timer 2 async
mode with an overflow interrupt (clock source is the external 32768Hz crystal on the Butterfly.
Code will calibrate to 7372800Hz for correct serial transmission at 115200 baud. */
volatile static uint16_t ActualCount;
void OSCCAL_Calibrate(void)
{
uint8_t SREG_Backup;
uint8_t LoopCount = (0x7F / 2); // Maximum range is 128, and starts from the middle, so 64 is the max number of iterations required
// Make sure all clock division is turned off (8Mhz RC clock)
CLKPR = (1 << CLKPCE);
CLKPR = 0;
// Inital OSCCAL of half its maximum for speed
OSCCAL = (0x7F / 2);
// Save the SREG
SREG_Backup = SREG;
// Disable all timer 1 interrupts
TIMSK1 = 0;
// Set timer 2 to asyncronous mode (32.768KHz crystal)
ASSR = (1 << AS2);
// Timer 2 overflow interrupt enable
TIMSK2 = (1 << TOIE2);
// Enable interrupts
sei();
// Start both counters with no prescaling
TCCR1B = (1 << CS10);
TCCR2A = (1 << CS20);
// Wait until timer 2's external 32.768KHz crystal is stable
while (ASSR & ((1 << TCN2UB) | (1 << TCR2UB) | (1 << OCR2UB)));
// Clear the timer values
TCNT1 = 0;
TCNT2 = 0;
while (LoopCount--)
{
// Let it take a few readings (60ms, approx 7 readings)
_delay_ms(60);
if (ActualCount > (TARGETCOUNT + 5)) // Clock is running too fast
OSCCAL--;
else if (ActualCount < (TARGETCOUNT - 5)) // Clock is running too slow
OSCCAL++;
else // Clock is just right
break;
}
// Disable all timer interrupts
TIMSK1 = 0;
TIMSK2 = 0;
// Stop the timers
TCCR1B = 0x00;
TCCR2A = 0x00;
// Turn off timer 2 asynchronous mode
ASSR &= ~(1 << AS2);
// Restore SREG
SREG = SREG_Backup;
return;
}
ISR(TIMER2_OVF_vect) // Occurs 32768/256 timers per second, or 128Hz
{
// Stop timer 1 so it can be read
TCCR1B = 0x00;
// Record timer 1's value
ActualCount = TCNT1;
// Reset counters and restart timer 1
TCNT1 = 0;
TCNT2 = 0;
TCCR1B = (1 << CS10);
}
Osccal.h
Code:
#ifndef OSCCAL_H
#define OSCCAL_H
// DEFINES:
#define TARGETCOUNT (uint16_t)((7372800 / 128) - 5) // Compile time constant, ((Target Freq / Reference Freq) - 5)
// INCLUDES:
#include <avr/io.h>
#include <avr/interrupt.h>
// PROTOTYPES:
void OSCCAL_Calibrate(void);
#endif
- Dean  |
_________________
Last edited by abcminiuser on Oct 04, 2006 - 11:52 AM; edited 2 times in total
|
| |
|
|
|
|
|
Posted: Apr 05, 2006 - 01:18 PM |
|


Joined: Jan 23, 2004
Posts: 6964
Location: Melbourne, Victoria, Australia
|
|
I had a brainwave before.
The problem with my routine above, is that if the internal oscillator cannot be calibrated to within the small tollerance, the routine would loop for the full 64 iterations. During that time the OSCCAL value bounces between the two closest values. Armed with this knowlege, I've added a two-byte array to store the previous two OSCCAL values. On each loop the current value is compared with the one two iterations previous and if it matches, the best value has been found and the loop exits.
To use this code instead of my previous code, replace the contents of OSCCAL.c with this new code, and keep the existing OSCCAL.h.
OSCCAL.c
Code:
#include "OSCCal.h"
/* Code taken from Colin Oflynn from AVRFreaks and modified. His code originally used an externally
divided 32768Hz clock on an external interrupt pin, but I changed that to use the timer 2 async
mode with an overflow interrupt (clock source is the external 32768Hz crystal on the Butterfly.
Code will calibrate to 7372800Hz for correct serial transmission at 115200 baud. */
volatile uint16_t ActualCount;
void OSCCAL_Calibrate(void)
{
uint8_t SREG_Backup;
uint8_t LoopCount = (0x7F / 2); // Maximum is 128 and starts from the middle so 64 is the max number of iterations required
uint8_t PrevOSCCALValues[2] = {};
// Make sure all clock division is turned off (8Mhz RC clock)
CLKPR = (1 << CLKPCE);
CLKPR = 0;
// Inital OSCCAL of half its maximum for speed
OSCCAL = (0x7F / 2);
// Save the SREG
SREG_Backup = SREG;
// Disable all timer 1 interrupts
TIMSK1 = 0;
// Set timer 2 to asyncronous mode (32.768KHz crystal)
ASSR = (1 << AS2);
// Timer 2 overflow interrupt enable
TIMSK2 = (1 << TOIE2);
// Enable interrupts
sei();
// Start both counters with no prescaling
TCCR1B = (1 << CS10);
TCCR2A = (1 << CS20);
// Wait until timer 2's external 32.768KHz crystal is stable
while (ASSR & ((1 << TCN2UB) | (1 << TCR2UB) | (1 << OCR2UB)));
// Clear the timer values
TCNT1 = 0;
TCNT2 = 0;
while (LoopCount--)
{
// Let it take a few readings (60ms, approx 2 readings)
_delay_ms(60);
PrevOSCCALValues[1] = PrevOSCCALValues[0];
PrevOSCCALValues[0] = OSCCAL;
if (ActualCount > (TARGETCOUNT + 5)) // Clock is running too fast
OSCCAL--;
else if (ActualCount < (TARGETCOUNT - 5)) // Clock is running too slow
OSCCAL++;
else // Clock is just right
break;
// If the OSCCAL cannot calibrate to within the tollerace, the routine will hover around the closest
// two values for the full 64 iterations. To speed the routine up, the previous two OSCCAL values are
// saved into an array. If the current value is the same as two iterations ago, the routine is hovering
// and the loop exits as the OSCCAL is now as close to the target as possible.
if (OSCCAL == PrevOSCCALValues[1])
break;
}
// Disable all timer interrupts
TIMSK1 = 0;
TIMSK2 = 0;
// Stop the timers
TCCR1B = 0x00;
TCCR2A = 0x00;
// Turn off timer 2 asynchronous mode
ASSR &= ~(1 << AS2);
// Restore SREG
SREG = SREG_Backup;
return;
}
ISR(TIMER2_OVF_vect) // Occurs 32768/256 timers per second, or 128Hz
{
// Stop timer 1 so it can be read
TCCR1B = 0x00;
// Record timer 1's value
ActualCount = TCNT1;
// Reset counters and restart timer 1
TCNT1 = 0;
TCNT2 = 0;
TCCR1B = (1 << CS10);
}
- Dean  |
_________________
|
| |
|
|
|
|
|
Posted: Apr 05, 2006 - 01:37 PM |
|


Joined: Feb 24, 2006
Posts: 794
Location: http://avr.x.am
|
|
I haven't dug into the details of this routine yet, but here's a suggestions off-the-top-of-my-head that will save time and space.
Instead of an array for memory... how about averaging the last two readings and using that instead, then you can scrap the array and everything that goes with it.
Another idea might be to use a special type of average similar to a moving average.
I think it's called a weighted average. Anyhow it will take into account all reading going back to infinity (theoretically).
You maintain a sort of sum-average. You add in your latest reading and divide it by two, then on next iteration the same, and so-on to infinity. The average takes into account many-many readings and when you hover, it will stablize. You can also adjust the "weight" perhaps you want 3/4 of last and only 1/4 of new reading, etc. When that figure get to within your very tight tollerances, then you know that's about the best your gonna' get so exit.
That way you sort of squeeze an infinite array into a single byte, and the oldest readings are least influential on your decision making and the newest are the most important.
Also, you in some kinda' hurry with this routine? If I'm reading the code correctly, you only allow maximum of 64 iterations for the calibration(?) What if it hasn't "settled" by then? |
|
|
| |
|
|
|
|
|
Posted: Apr 05, 2006 - 10:47 PM |
|


Joined: Feb 24, 2006
Posts: 794
Location: http://avr.x.am
|
|
This is from another thread, but directly relates to this conversation:
alejmrm wrote:
Hey guys,
If this helps, I have the C version of OSCCAL_retrocal(), but right now I am in the office so allow me 8 hours to return home, and I will post the OSCAL_minical (function that is small from retroDan tutorial and still readable for human beings) and the OSCCAL_retrocal(function that is even small but not readable for normal mortals)... and the functions takes the same space as the asm... I am using gcc... so I think is really to go in any project that use the win-avr gcc compiler...
|
|
|
| |
|
|
|
|
|
Posted: Apr 06, 2006 - 04:47 AM |
|


Joined: Jan 23, 2004
Posts: 6964
Location: Melbourne, Victoria, Australia
|
|
Yes, a hurry. My version uses timer two which is overflowing at a rate of 128Hz. I've altered the code so that the delay inside the loop is 14ms, or about two readings (erronously put as 60ms=2 readings in the code). Since the code waits for timer 2 to stabalise, and takes two readings for each loop to ensure that the OSCCAL has taken effect this should work fine. OSCCAL starts from 64 in my code, so the 64 iterations should allow it to reach both extreame ends of the scale without falling off.
My new code means that the next version of ButtLoad should calibrate in under a second worse-case-scenario.
- Dean  |
_________________
|
| |
|
|
|
|
|
Posted: Apr 06, 2006 - 05:24 AM |
|


Joined: Feb 24, 2006
Posts: 794
Location: http://avr.x.am
|
|
If you're concerned about doing it FAST, how about, instead of incrementing/decrementing by only one, you calculate the error by subtracting the actual-count from target and adjusting OSSCAL accordingly.
Should be able to "Nail It" SuperFast.
Have you worked-out how much one step in OSCCAL will theoretically change Oscillator? |
|
|
| |
|
|
|
|
|
Posted: Apr 06, 2006 - 05:30 AM |
|


Joined: Jan 23, 2004
Posts: 6964
Location: Melbourne, Victoria, Australia
|
|
Nope, havn't done that. Every AVR's OSCCAL will change the frequency slightly differently, which is most likely why the Atmel engineers chose the same method as I'm using above (decrement/increment by one). Calibration doesn't need to be absolutly instant, but the quicker the better. If the calibration time with the new code turns out to be short enough, i'm going to place it into the UART enable code in ButtLoad so that each time the AVRISP mode (and other UART-reliant modes) is entered the OSCCAL is recalibrated.
- Dean  |
_________________
|
| |
|
|
|
|
|
Posted: Apr 06, 2006 - 07:45 AM |
|

Joined: Nov 17, 2004
Posts: 9102
Location: Vancouver, BC
|
|
|
Quote:
Calibration doesn't need to be absolutly instant, but the quicker the better.
Dean, have you looked at my successive approximation code in the "Shrinking OSCCAL" thread? It calibrates in 7 iterations and doesn't have the bouncing problems. As I stated in one of the posts, it can end up 1 off, but that can easily be corrected with another six lines of code (in assembly). If you need it done in C, I'm working on the translation now. |
_________________ Regards,
Steve A.
The Board helps those that help themselves.
wylfwt?
|
| |
|
|
|
|
|
Posted: Apr 06, 2006 - 07:51 AM |
|


Joined: Jan 23, 2004
Posts: 6964
Location: Melbourne, Victoria, Australia
|
|
I just went back and had a look at your routine, Steve. Neato - calibration in under 0.01 secs! Sign me up for the C translation when it's done. I'd be very interested in using your code (plus any modifications I make to it) in ButtLoad, will that be a problem?
- Dean  |
_________________
|
| |
|
|
|
|
|
Posted: Apr 06, 2006 - 07:59 AM |
|

Joined: Nov 17, 2004
Posts: 9102
Location: Vancouver, BC
|
|
|
Quote:
will that be a problem?
Not a problem at all. I'd be honored! I should have the C code for you tomorrow. I'd do it now, but it's bedtime. |
_________________ Regards,
Steve A.
The Board helps those that help themselves.
wylfwt?
|
| |
|
|
|
|
|
Posted: Apr 06, 2006 - 07:26 PM |
|


Joined: May 15, 2004
Posts: 37
|
|
Later devices, such as the tiny2313 have a warning in the data sheet that says:
Quote:
Avoid changing the calibration value in large steps when calibrating the calibrated internal RC Oscillator to ensure stable operation of the MCU. A variation in frequency of more than 2% from one cycle to the next can lead to unpredictable behavior. Changes in OSCCAL-register should not exceed 0x20 for each calibration.
I wonder if this could also apply to the M169?
Randy Ott |
|
|
| |
|
|
|
|
|
Posted: Apr 07, 2006 - 08:05 AM |
|


Joined: Jan 23, 2004
Posts: 6964
Location: Melbourne, Victoria, Australia
|
|
Hmmm, very good Randy. I knew there must have been a reason for the Atmel engineer's decision to use single stepping on the OSCCAL calibration! I believe I have read that also, but i'll have to go and check to see if it's in the M169 datasheet.
- Dean  |
_________________
|
| |
|
|
|
|
|
Posted: Apr 07, 2006 - 08:50 AM |
|

Joined: Nov 17, 2004
Posts: 9102
Location: Vancouver, BC
|
|
Here's my successive approximation routine:
Code:
/*****************************************************************************
*
* Function name : OSCCAL_sa
*
* Returns : None
*
* Parameters : None
*
* Purpose : Calibrate the internal OSCCAL byte, using successive
* approximation and the external 32,768 kHz crystal as reference
*
*****************************************************************************/
void OSCCAL_sa()
{
unsigned char mask = 0x40;
unsigned char tempH;
unsigned char tempL;
unsigned char tempC;
CLKPR = (1<<CLKPCE); // set Clock Prescaler Change Enable
// set prescaler = 8, Inter RC 8Mhz / 8 = 1Mhz
CLKPR = (1<<CLKPS1) | (1<<CLKPS0);
TIMSK2 = 0; //disable OCIE2A and TOIE2
ASSR = (1<<AS2); //select asynchronous operation of timer2 (32,768kHz)
OCR2A = 218; // set timer2 compare value. This creates a target count of 0x1A00
TIMSK0 = 0; // delete any interrupt sources
TCCR1B = (1<<CS10); // start timer1 with no prescaling
TCCR2A = (1<<CS20); // start timer2 with no prescaling
TIFR2 = 0xFF; // delete TIFR2 flags
Delay(1000); // wait for external crystal to stabilise
cli(); // disable global interrupt
tempC = 0;
while(mask) //As long as we have a bit to mask in...
{
tempC |= mask; // OR the mask in
OSCCAL = tempC;
TIFR2 = 0xFF; // delete TIFR1 flags
TCNT2 = 0; // clear timer2 counter
while (ASSR & (1<<TCN2UB)); //Wait for timer 2 to update
TCNT1H = 0; // clear timer1 counter
TCNT1L = 0;
while ( !(TIFR2 & (1<<OCF2A)) ); // wait for timer2 compareflag
// read out the timer1 counter value
tempL = TCNT1L;
tempH = TCNT1H;
if (tempH >= 0x1A)
{
tempC -= mask; // too high, so remove the mask bit
}
mask >>= 1; // Shift the mask
}
//The second to last reading may have actually been closer
if (tempH < 0x1A)
{
if (tempL < 0xD8)
{
tempC++; //The last reading was below 0x1AD8, so one step up is closer
}
}
else
{
if (tempL < 0x28)
{
// The last reading was over 0x1A00, so the last bit was removed,
// but since the count was below 0x1A28, the previous reading was closer,
// so put the bit back
tempC++;
}
}
OSCCAL = tempC;
sei(); // __enable_interrupt(); // enable global interrupt
}
The sa in the name could stand for successive approximation, but it's really my initials
The section after the loop is recovering the last bit. If smaller size is required and less accuracy is needed, this could be left out. The line:
Code:
while (ASSR & (1<<TCN2UB)); //Wait for timer 2 to update
could also be left out as well, but without it the routine gives an answer consistently one step lower than with it. It is not included in the original Atmel routine, but I believe that they compensated for this by using a target count higher than what it theoretically should be.
The note on large changes in the OSCCAL is interesting. My routine just squeaks in. The change from the first pass to the second pass is 0x20. All others are smaller. In my testing I am getting virtually the same value no matter what routine I use, so I think that it is safe. |
_________________ Regards,
Steve A.
The Board helps those that help themselves.
wylfwt?
|
| |
|
|
|
|
|
Posted: Dec 07, 2006 - 04:44 AM |
|


Joined: Jan 23, 2004
Posts: 6964
Location: Melbourne, Victoria, Australia
|
|
I modified my routine a while ago, removing the need for the ISRs. This is extremely fast and gives great results. Unlike my original routine, each calibration cycle lasts for one reading check, rather than 7:
OSCCAL.c
Code:
#include "OSCCal.h"
void OSCCAL_Calibrate(void)
{
uint8_t LoopCount = (0x7F / 2); // Maximum range is 128, and starts from the middle, so 64 is the max number of iterations required
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
// Make sure all clock division is turned off (8MHz RC clock)
CLKPR = (1 << CLKPCE);
CLKPR = 0x00;
// Inital OSCCAL of half its maximum
OSCCAL = (0x7F / 2);
// Disable timer interrupts
TIMSK1 = 0;
TIMSK2 = 0;
// Set timer 2 to asyncronous mode (32.768KHz crystal)
ASSR = (1 << AS2);
// Ensure timer 1 control register A is cleared
TCCR1A = 0;
// Start both counters with no prescaling
TCCR1B = (1 << CS10);
TCCR2A = (1 << CS20);
// Wait until timer 2's external 32.768KHz crystal is stable
while (ASSR & ((1 << TCN2UB) | (1 << TCR2UB) | (1 << OCR2UB)));
// Clear the timer values
TCNT1 = 0;
TCNT2 = 0;
while (LoopCount--)
{
// Wait until timer 2 overflows
while (!(TIFR2 & (1 << TOV2)));
// Stop timer 1 so it can be read
TCCR1B = 0x00;
// Check timer value against ideal constant
if (TCNT1 > OSCCAL_TARGETCOUNT) // Clock is running too fast
OSCCAL--;
else if (TCNT1 < OSCCAL_TARGETCOUNT) // Clock is running too slow
OSCCAL++;
// Clear timer 2 overflow flag
TIFR2 |= (1 << TOV2);
// Restart timer 1
TCCR1B = (1 << CS10);
// Reset counters
TCNT1 = 0;
TCNT2 = 0;
}
// Stop the timers
TCCR1B = 0x00;
TCCR2A = 0x00;
// Turn off timer 2 asynchronous mode
ASSR &= ~(1 << AS2);
}
END_ATOMIC_BLOCK
return;
}
OSCCAL.h
Code:
#ifndef OSCCAL_H
#define OSCCAL_H
// INCLUDES:
#include <avr/io.h>
#include <avr/interrupt.h>
// CODE DEFINES:
#define ATOMIC_BLOCK(exitmode) { exitmode cli();
#define END_ATOMIC_BLOCK }
#define ATOMIC_RESTORESTATE inline void __irestore(uint8_t *s) { SREG = *s; } \
uint8_t __isave __attribute__((__cleanup__(__irestore))) = SREG;
#define ATOMIC_ASSUMEON inline void __irestore(uint8_t *s) { sei(); *s = 0; } \
uint8_t __isave __attribute__((__cleanup__(__irestore))) = 0;
// CONFIG DEFINES:
#define OSCCAL_TARGETCOUNT (uint16_t)(F_CPU / (32768 / 256)) // (Target Freq / Reference Freq)
// PROTOTYPES:
void OSCCAL_Calibrate(void);
#endif
- Dean  |
_________________
|
| |
|
|
|
|
|
Posted: Nov 09, 2007 - 11:55 PM |
|

Joined: Jun 20, 2006
Posts: 13
|
|
|
Code:
// Clear the timer values
TCNT1 = 0;
TCNT2 = 0;
while (LoopCount--)
{ // Wait until timer 2 overflows
while (!(TIFR2 & (1 << TOV2)));
bette
Code:
// Clear the timer values
while (LoopCount--)
{ // Wait until timer 2 overflows
[b] TCNT1 = 0;
TCNT2 = 0;
[/b] while (!(TIFR2 & (1 << TOV2)));
and #define OSCCAL_TARGETCOUNT (uint16_t)(F_CPU / (32768 / 256)) +2 // (Target Freq / Reference Freq) |
|
|
| |
|
|
|
|
|
Posted: Nov 09, 2007 - 11:57 PM |
|

Joined: Jun 20, 2006
Posts: 13
|
|
if (TCNT1 > OSCCAL_TARGETCOUNT) // Clock is running too fast
OSCCAL--;
else if (TCNT1 < OSCCAL_TARGETCOUNT) // Clock is running too slow
OSCCAL++;
else LoopCount = 0; |
|
|
| |
|
|
|
|
|
Posted: Nov 11, 2007 - 07:38 AM |
|


Joined: Jan 23, 2004
Posts: 6964
Location: Melbourne, Victoria, Australia
|
|
Thank's Dalo, very clever. However, I think there's a slight problem with that - shifting the OSCCAL_TARGETCOUNT by two makes no difference apart from adding a slight error to the final calibrated value. If what you want to do is to add a little window which is deemed a "correct" calibrated value, you need to add the offset to one, and subtract from the other:
Code:
if (TCNT1 > (OSCCAL_TARGETCOUNT + OSCCAL_TOLLERANCE)) // Clock is running too fast
OSCCAL--;
else if (TCNT1 < (OSCCAL_TARGETCOUNT - OSCCAL_TOLLERANCE)) // Clock is running too slow
OSCCAL++;
else // Clock within tollerance
LoopCount = 0;
- Dean  |
_________________
|
| |
|
|
|
|
|
Posted: Dec 14, 2007 - 01:35 PM |
|

Joined: Oct 19, 2007
Posts: 11
|
|
All, I've been struggling with RTC on my 90USB1287 as I've mentioned in another thread... I ordered my watch crystal and I'm currently playing with a RTC on Timer 2 running async... I think all of the code you mentioned will be very useful, but I'm confused over exactly how to connect the watch crystal. The 90USB1287 "talks" about connecting a watch crystal between TOSC1 & 2, but it references a page in the docs that shows the crystal connected between XTAL1&2 with a couple small 6pf caps...
So far I haven't been able to get much to work.
If I could get a clear answer on exactly where to hook the watch crystal and how that would help immensely... especially given this thread on the calibration. |
|
|
| |
|
|
|
|
|
Posted: Dec 14, 2007 - 07:33 PM |
|

Joined: Oct 19, 2007
Posts: 11
|
|
figured it out...
This may be specific to me... I decided that the 32.768 watch crystal needed to be connected to the 90usb1287 via the TOSC1/2 directly with no caps. This was based on Collin O'Flynn's "Why you need a clock source" pdf... although the files link for the document is broken on AVF Freaks.
The tricky part for me was using the 90USB1287 on a STK525 mounted to the STK500.
As it turns out the PE4/5 pins (TOSC1/2) aren't broken out on the STK500/STK525...
I was hooking my crystal to the wrong pins altogether...
Now lucky for me the board had solder pads for these pins accessible and I ended up soldering my crystal directly to the board...
Once I did that and tweaked my software I was off and running.
Hope this helps someone else... took me long enough to figure it out.
Now that I have this Asyc watch crystal based RTC, I can calibrate my internal oscillator ah la AVR055, and I should have a stable RTC!
I'm running a test over the weekend to see how accurate this setup is. I'll report next week. |
|
|
| |
|
|
|
|
|