Reading ATmega8A calibration byte and storing it in EEPROM

A while ago I discussed about the calibration byte value required to adjust the internal oscillator frequency, mainly to be able to use the USART module inside the ATmega8A microcontroller.

In the last part of the article, I mentioned it’d be more practical to read the calibration value, store it in the microcontroller EEPROM and retrieve it from within our program. Today I’ll describe a way of doing it using a small python script.

This is the code example where the calibration byte is included within the code itself.

#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++);
	}
}

Having the calibration value in the source code becomes impractical if you have to program more than a bunch of chips, or if you're programming microcontrollers for production and can't have the source code available to recompile it for every single chip. For these and other scenarios, storing the calibration byte into the EEPROM and reading it at run time is a lot more practical. Let's modify our source code to read the calibration byte from the EEPROM:

#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;

	// read calibration byte from EEPROM address 0
	OSCCAL = eeprom_read_byte(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++);
	}
}

Now, we need to read and store the calibration byte into address 0 of the ATmega8A EEPROM. We need a programmer to read the ATmega8A calibration bytes, as seen previously we can read them using avrdude with the following command:

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

We get this, the calibration value we need for our microcontroller working at 8 MHz is 0xb7:

And we can save it to EEPROM address 0 using this command in avrdude:

$ avrdude -c dragon_isp -p m8 -U eeprom:w:0xb7:h

Automating it

Once we know the avrdude commands to read the calibration byte and save it to the EEPROM, we can create a script to execute them when we're downloading our program to every microcontroller. For our script, we need a way to run an external program (avrdude), capture its output (so that we can get the calibration bytes) and capture the result of the execution (successful or not). The following python script does it all:

import shlex
import time
from subprocess import Popen, PIPE

# https://pythonadventures.wordpress.com/2014/01/08/capture-the-exit-code-the-stdout-and-the-stderr-of-an-external-command/
def get_exitcode_stdout_stderr(cmd):
    args = shlex.split(cmd)

    proc = Popen(args, stdout=PIPE, stderr=PIPE)
    out,err = proc.communicate()
    exitcode = proc.returncode

    return exitcode, out, err

if __name__ == __main__:
    # read calibration bytes
    cmd_read_cal = 'avrdude -c dragon_isp -p m8 -U cal:r:-:d'
    exitcode, out, err = get_exitcode_stdout_stderr(cmd_read_cal)

    if exitcode not 0:
        print(err)
        return exitcode

    # parse calibration data
    cal_1MHz, cal_2MHz, cal_4MHz, cal_8MHz = out.decode().strip().split(',')
    print('Calibration value for 8MHz:', cal_8MHz)

    # use calibration byte for 8 MHz
    cal_byte = cal_8MHz

    # several times I've got an error when running avrdude twice inmediately,
    # adding a small pause avoids it
    time.sleep(1)

    # save calibration data to eeprom address 0
    cmd_write_eeprom = 'avrdude -c dragon_isp -p m8 -U eeprom:w:' + cal_byte + ':m'
    print('Programming calibration byte with command: ', cmd_write_eeprom)
    exitcode, out, err = get_exitcode_stdout_stderr(cmd)
    
    return exitcode

You can see I've changed from h to m mode for the avrdude EEPROM reading/writing commands, with m mode the values are decimal instead of hexadecimal and so I don't need to convert them in the python script.

Final notes

ATmega8A is an old microcontroller, the "internal RC oscillator" with selectable frequency of 1, 2, 4 or 8 MHz is in reality a selection between four internal oscillators, that's why it has four different calibration values: one for each oscillator. Newer AVR microcontrollers have only one internal 8 MHz oscillator and derive the different subfrequencies from it, so they only need one calibration value that is loaded by the microcontroller on boot and you don't need to apply it yourself. Even if the ATmega8A had many more calibration values, if they were available at run time we wouldn't need the procedure described in this (and the previous post), one would directly read the corresponding calibration value and apply it at the beginning of the program without needing to do it "externally". Why did ATmega8A designers decided to not let the user program access these calibration values? Maybe they thought most applications would use an external oscillator and if the internal oscillator was needed 1 MHz would be more than enough (remember the 1 MHz calibration value is the one loaded on boot).

My recommendation would be to use an ATmega8A microcontroller only if you really need to, ATmega88A/PA family is a more recent equivalent (this family includes the ATmega328P used in the Arduino Uno board), or maybe consider jumping to a different architecture (32 bit ARM microcontrollers are now priced similarly to 8 bit ones).

Be the first to comment

Leave a Reply

Your email address will not be published.


*