Using ATmega8A oscillator calibration byte

ATmega8A (and ATmega8) microcontrollers have an internal RC oscillator that can provide a 1, 2, 4 or 8 MHz clock. This feature allows cost savings by reducing the number of components needed in a circuit.

This internal oscillator is calibrated at the factory for all of its four possible frequencies, and an adjustment value for each of them is stored in the microcontroller’s signature memory.

As an example of the importance of this calibration, we’ll run a small program that keeps a counter and sends it through the UART at 9600 bps. We’ll start using the internal oscillator at 1 MHz:

#include 
#include 

#define F_CPU 1000000UL
#include 
#define BAUD 9600 
#include 

#include 
#include 
void uart_init();
void uart_putc(char c);
void uart_puts(char* s);

int main(void) {
	char buffer[30];
	int i = 0;

	uart_init();

	_delay_ms(100);

	while(1) {
		_delay_ms(500);
		sprintf(buffer, "test: %d\n\r", i++);
		uart_puts(buffer);
	}

	return 0;
}

void uart_init() {
	UBRRH = UBRRH_VALUE;
	UBRRL = UBRRL_VALUE;

#if USE_2X
	UCSRA |= (1 << U2X);
#else
	UCSRA &= ~(1 << U2X);
#endif

	UCSRB = (1 << TXEN);
	UCSRC = (1 << URSEL) | (3 << UCSZ0);
}

void uart_putc(char c) {
	while ((UCSRA & (1 << UDRE)) == 0);

	UDR = c;
}

void uart_puts(char* s) {
	while(*s) {
		uart_putc(*s++);
	}
}

Make sure you set the internal oscillator to 1 MHz frequency, e. g. using an AVR Dragon programmer with avrdude:

$ avrdude -c dragon_isp -p m8 -U lfuse:w:0xe1:m -U hfuse:w:0xd9:m

Checking data in a terminal:

Now let's try to run at the highest possible frequency when using the internal oscillator, 8 MHz:
The program:

#include 
#include 

#define F_CPU 8000000UL
#include 
#define BAUD 9600 
#include 

#include 
#include 
void uart_init();
void uart_putc(char c);
void uart_puts(char* s);

int main(void) {
	char buffer[30];
	int i = 0;

	uart_init();

	_delay_ms(100);

	while(1) {
		_delay_ms(500);
		sprintf(buffer, "test: %d\n\r", i++);
		uart_puts(buffer);
	}

	return 0;
}

void uart_init() {
	UBRRH = UBRRH_VALUE;
	UBRRL = UBRRL_VALUE;

#if USE_2X
	UCSRA |= (1 << U2X);
#else
	UCSRA &= ~(1 << U2X);
#endif

	UCSRB = (1 << TXEN);
	UCSRC = (1 << URSEL) | (3 << UCSZ0);
}

void uart_putc(char c) {
	while ((UCSRA & (1 << UDRE)) == 0);

	UDR = c;
}

void uart_puts(char* s) {
	while(*s) {
		uart_putc(*s++);
	}
}

Fuses:

$ avrdude -c dragon_isp -p m8 -U lfuse:w:0xe4:m -U hfuse:w:0xd9:m 

The terminal shows:

What's happening? The problem is that the internal oscillator is not running at the nominal frequency of 8 MHz and we need to adjust it by setting the OSCCAL register to the corresonding calibration byte stored in the factory.
Ok, but why didn't we need to do it when running at 1 MHz?

Well, according to the datasheet:

During Reset, the 1MHz calibration value which is located in the signature
row High byte (address 0x00) is automatically loaded into the OSCCAL Register. If the internal RC is
used at other frequencies, the calibration values must be loaded manually. This can be done by first
reading the signature row by a programmer, and then store the calibration values in the Flash or
EEPROM. Then the value can be read by software and loaded into the OSCCAL Register.

The calibration value for 1 MHz is automatically loaded into the OSCCAL register, no matter what frequency we've set the internal RC oscillator to run. So, in order to use the proper value for 8 MHz frequency, we need to load it ourselves.

First, let's get the calibration data, e. g. using avrdude:

avrdude -c dragon_isp -p m8 -U cal:r:-:h

We now have the four calibration values:

  • 0xc0 for both 1 MHz and 2 MHz
  • 0xb8 for 4 MHz
  • 0xb7 for 8 MHz

Let's modify our test program and set the OSCCAL register to the calibration value for 8 MHz 0xb7:

#include 
#include 

#define F_CPU 8000000UL
#include 
#define BAUD 9600 
#include 

#include 
#include 
void uart_init();
void uart_putc(char c);
void uart_puts(char* s);

int main(void) {
	char buffer[30];
	int i = 0;

	OSCCAL = 0xb7; // apply calibration value for 8 MHz

	uart_init();

	_delay_ms(100);

	while(1) {
		_delay_ms(500);
		sprintf(buffer, "test: %d\n\r", i++);
		uart_puts(buffer);
	}

	return 0;
}

void uart_init() {
	UBRRH = UBRRH_VALUE;
	UBRRL = UBRRL_VALUE;

#if USE_2X
	UCSRA |= (1 << U2X);
#else
	UCSRA &= ~(1 << U2X);
#endif

	UCSRB = (1 << TXEN);
	UCSRC = (1 << URSEL) | (3 << UCSZ0);
}

void uart_putc(char c) {
	while ((UCSRA & (1 << UDRE)) == 0);

	UDR = c;
}

void uart_puts(char* s) {
	while(*s) {
		uart_putc(*s++);
	}
}

And now, in the terminal:

It works!

Of course, it wouldn't be practical to edit the code and recompile it for every single device we would program. A better approach is to modify the program so that the calibration byte is read from the EEPROM and applied to OSCCAL. So, we'd read the calibration byte with a programmer and then store it in the EEPROM, this would work for every microcontroller without recompiling the program.

Be the first to comment

Leave a Reply

Your email address will not be published.


*