I started playing with OSCCAL and got carried away. OSCCAL lets you tune the internal 8MHz oscillator roughly between 5MHz and 15MHz. While it isn't a high-precision clock, it turns out that you can use the watchdog oscillator to tune OSCCAL and it seems to work pretty well.
This library tunes OSCCAL to a target frequency and reconfigures Serial.begin() to keep the same real baudrate at the new speed. It is also very simple to use.
#include <avr/wdt.h>
#include "Overclock.h"
const uint32_t SERIAL_BAUD = 115200 ; // Recommend 38400, 19200, or 9600 for better stability. 115200 works for me, but may be less stable in some setups.
Overclock my_Overclock_object=Overclock((uint32_t)14000000, (uint32_t)SERIAL_BAUD);
ISR(WDT_vect) {
my_Overclock_object.WatchdogFired();
// Add your other watchdog code here.
}
< somewhere later on>
my_Overclock_object.SetClockSpeedOC();
or
my_Overclock_object.SetClockSpeed((uint32_t)12000000);
Does it have a way to compensate? Not built in, but not hard to deal with.
AFAIK (heard but havent looked), the delay and delayMicroseconds functions are determined at compile-time. Would be nice to have a way to provide the current clockspeed.
I suppose it could be added (without modifying the delay libs) by either:
Add functions for delay and delayMicroseconds. These functions first change clock back to stock, then do the delay, then change to whatever it was before. This has the downside of breaking buffered serial communications or causing interrupts to not run at full speed.
Calculate an equivalent delay based on the ratio of stock clock vrs new clock, then do a delay with the new value.
Because calibration against the watchdog timer is not an exact process anyway, you could let the compiler figure out an equivalent delay based on the set point (so long as the requested/expected speed is within the OSCCAL calibration range). This incurs no performance penalty but will be slightly less accurate. Will it matter based on the other inaccuracies in the system? Probably not. Using the example,
const uint32_t SERIAL_BAUD = 115200 ;
const uint32_t OVERCLOCK_SPEED = 15000000; //Note: should be within the OSCCAL calibration range.
Overclock my_Overclock_object=Overclock(OVERCLOCK_SPEED, SERIAL_BAUD);
.
.
.
delay(1000*(OVERCLOCK_SPEED/F_CPU)); This should delay for 1 second if you're at the overclocked speed.
Maybe I should add an IsStock() function so you can do something like this with very little overhead?
Library updated, improved accuracy through:
a) Calibrating for watchdog clock error against factory calibration of internal 8MHz oscillator.
b) Looking at adjacent values for minimal absolute error after doing the binary search for a close within-bounds value.
Testing shows increased reliability of serial communications after these changes