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 0×05 r9″ (e.g. “PORTB = 0×00″) executes in just one clock cycle. Register r9 contains e.g. 0×00 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 ;-)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | #include <util/delay.h> #include <stdint.h> #include <avr/io.h> #include <avr/interrupt.h> #include "cxa_guard_acquire.h" void 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 } void 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 OSCCAL++; _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.
Related posts:







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