[CODE] [C] 115200 baud serial comms with the Butterfly

Go To Last Post
21 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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

#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

#ifndef OSCCAL_H
#define OSCCAL_H

// DEFINES:
#define TARGETCOUNT (uint16_t)((7372800 / 128) - 5) // Compile time constant, ((Target Freq / Reference Freq) - 5)

// INCLUDES:
#include 
#include 

// PROTOTYPES:
void OSCCAL_Calibrate(void);

#endif

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

Last Edited: Wed. Oct 4, 2006 - 10:52 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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

#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 :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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 :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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 :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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 :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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 :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's my successive approximation routine:

/*****************************************************************************
*
*   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 :wink:

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:

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.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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

#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

#ifndef OSCCAL_H
#define OSCCAL_H

	// INCLUDES:
	#include 
	#include 

	// 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 :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
// Clear the timer values 
      TCNT1  = 0; 
      TCNT2  = 0; 
      while (LoopCount--) 
      {  // Wait until timer 2 overflows 
         while (!(TIFR2 & (1 << TOV2)));

bette

// Clear the timer values 
      while (LoopCount--) 
      {  // Wait until timer 2 overflows 
      TCNT1  = 0; 
      TCNT2  = 0; 
         while (!(TIFR2 & (1 << TOV2)));

and #define OSCCAL_TARGETCOUNT (uint16_t)(F_CPU / (32768 / 256)) +2 // (Target Freq / Reference Freq)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

if (TCNT1 > OSCCAL_TARGETCOUNT) // Clock is running too fast
OSCCAL--;
else if (TCNT1 < OSCCAL_TARGETCOUNT) // Clock is running too slow
OSCCAL++;
else LoopCount = 0;

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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:

			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 :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

first off, thank you dean as well as others who post and answers questions on this board. i can't say thanks enough because this place is such a great resource.
-----
noob question about the first routine (1st cal routine from thread) -

when the isr triggers, actual count should have a value close to 65536.
this is compared with 57600-5.

If osccal decrements or increments by 1 how can you get within that range in 64 iterations?

----

similar question about your revised method (almost at the end of the thread)

TCNT1 should be close to 65536 and the target is 8mhz/(32k/256) = 62500.

again, how can you get within the range in 64 iterations?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Funny... I needed some code to do this calibration, and searching found this code, which is apparently based on code I don't remember writing at all :p

Just a note for anyone using this:

Check the bits refer to the correct register. I'm using a Mega1281, and this line:

TCCR2A = (1 << CS20);

Is NOT correct for that processor, needs to be TCCR2B.

-Colin