Playing with the OSCCAL register of an ATmega168

I was having problems with the “led ring” boards. Their internal oscillators were not ticking with the same speed and required constant re-syncing. I tried to improve the situation by recalibrating by adjusting the OSCCAL registers. Recalibration can be done quite easily with an oscilloscope. Either assuming that the _delay_ms() routine is reasonably correct for say 5ms (easy to measure on a scope), or using that “out 0x05 r9″ (e.g. “PORTB = 0x00″) executes in just one clock cycle. Register r9 contains e.g. 0x00 to turn on a LED by pulling its cathode LOW. At 8MHz a single cycle should take 125ns.

The following graph is taken from the old! ATmega168 datasheet. It is not valid for the newer P or PA series. For the newer series there is also a difference between chips with different memory sizes. As usual, reading the datasheet is necessary.


Here’s the Arduino code used to find good values for OSCCAL using an oscilloscope. The current value of OSCCAL is sent to a terminal. Note that you’ll probably want to use your own pins, as I used my custom boards for this test. So better use pinMode() and digitalWrite() as usual for the LED. But on the other hand, if you already have an oscilloscope, you’ll probably know what you’re doing anyway ;-)

#include <util/delay.h>
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include "cxa_guard_acquire.h"

setup (void)
  Serial.begin (9600);

  DDRD |= ((1 << PD5));		// PD5 output
  PORTD |= ((1 << PD5));	// RED anodes HIGH. All 8 anodes go to this pin
  DDRB = 0xFF;			// all outputs
  PORTB = 0xFF;			// all 8 cathodes HIGH --> OFF

loop (void)
  unsigned int ctr1;		// using an int, so I won't have to check if ctr1 wraps from 255 to 0 in the for loop
  unsigned int ctr2;
  unsigned char osccal_tmp;
  static unsigned char osccal_factory = OSCCAL;	// see "cxa_guard_acquire.h" for a bit more info
  OSCCAL = 0;
  _delay_ms (2000);		// wait some time to let it stabilize        

  for (ctr1 = 0; ctr1 <= 255; ctr1++)
      _delay_ms (20);		// wait some time to let it stabilize      
      osccal_tmp = OSCCAL;	// save current value
      OSCCAL = osccal_factory;	// change to factory to make serial work all the time
      _delay_ms (20);		// wait some time to let it stabilize
      Serial.print ("osccal: ");
      Serial.print (osccal_tmp, BIN);
      Serial.print (" - ");
      Serial.println (osccal_tmp, DEC);
      _delay_ms (20);		// wait some time to let it stabilize
      OSCCAL = osccal_tmp;	// restore
      _delay_ms (20);		// wait some time to let it stabilize

      for (ctr2 = 0; ctr2 <= 10; ctr2++)
	{			// blink blink for the oscilloscope
	  PORTB = 0;		// all on
	  _delay_ms (5);	// check the LOW period on the scope. should be about 5ms 
	  PORTB = 0xFF;		// all off
	  _delay_ms (5);
      _delay_ms (20);		// wait some time to let it stabilize
      _delay_ms (20);		// wait some time to let it stabilize

For code download click on ‘Projects --> GIT repo’ in the menu above.

I used the "measure the 5ms delay" method, as it was easier than measuring 125ns on my old 10MHz scope. I got values for OSCCAL of 104 and 122 for the two boards. That's a relative deviation of 17.3% between the boards, using 104 as reference.

Final thoughts:

I still need to re-sync the two boards. Not much gained, but learned something new. It might have been a good idea to use the CLKO (clock output) pin of one board as the clock source for the other. But unfortunately that is on PB0 again. PORTB is used for driving the cathodes of the LEDs already. And I still want/need/crave a new digital scope. Maybe I should get a cheap 50MHz Rigol just so the 'itching' stops. I can still buy a good 4CH 100MHz one later.

This entry was posted in Arduino., Electronics. and tagged , , , , , . Bookmark the permalink.

One Response to Playing with the OSCCAL register of an ATmega168

  1. ericwertz says:

    Send me an email if you’re serious about the 50MHz scope. I know someone who’s got one to sell unopened.

Comments are closed.