Arduino 4MHz External Oscillator

Hey guys,

I am currently working on a project for which I’d like my arduino to run on 4MHz instead of 16MHz. I already browsed around a bit on the internet but couldn’t find a definitive answer, so therefore my post.

In order for me to be able to run the arduino on 4MHz I had to adjust the boards.txt file and reset the fuses:

##############################################################

uno4M.name=Arduino/Genuino Uno at 4 MHz

uno4M.vid.0=0x2341
uno4M.pid.0=0x0043
uno4M.vid.1=0x2341
uno4M.pid.1=0x0001
uno4M.vid.2=0x2A03
uno4M.pid.2=0x0043
uno4M.vid.3=0x2341
uno4M.pid.3=0x0243

uno4M.upload.tool=avrdude
uno4M.upload.protocol=arduino
uno4M.upload.maximum_size=32256
uno4M.upload.maximum_data_size=2048
uno4M.upload.speed=28800

uno4M.bootloader.tool=avrdude
uno4M.bootloader.low_fuses=0xFD
uno4M.bootloader.high_fuses=0xDE
uno4M.bootloader.extended_fuses=0x05
uno4M.bootloader.unlock_bits=0x3F
uno4M.bootloader.lock_bits=0x0F
uno4M.bootloader.file=optiboot/optiboot_atmega328.hex

uno4M.build.mcu=atmega328p
uno4M.build.f_cpu=4000000L
uno4M.build.board=AVR_UNO
uno4M.build.core=arduino
uno4M.build.variant=standard

With these adaptions I was able to bootload my arduino and program it through the IDE. However, I am still confused regarding 2 things.

I understood that the 16MHz arduino has an internal division by 8 on the clock. When I still left this in when setting my fuses for a 4M clock, every delay(1000) became 8 seconds instead of one. This is not the case with the 16MHz arduino. I only changed the uno.build.f_cpu=16000000L to uno4M.build.f_cpu=4000000L and changed my fuse to work with a 4Meg clock instead of 16. So I am wondering which change inferred this effect.

Another thing I am wondering about is whether or not my millis library is still functioning properly. I was now able to get a ~1 second delay when typing delay(1000), however I wasn’t able to properly test the millis function yet as I do not have any calibrated equipment. The reason why I am asking this is because as far as I understood the millis library was created especially for 8 and 16MHz clocks as there is a factor 8 included somewhere in the function:

// the fractional number of milliseconds per timer0 overflow. we shift right
// by three to fit these numbers into a byte. (for the clock speeds we care
// about - 8 and 16 MHz - this doesn't lose precision.)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)

static unsigned char timer0_fract = 0;

Does this mean that if I change the >>3 to >>2 it should just work fine with 4 MHz?

A really hard problem, requires a look into the many low level details of the libraries.

As documented, the >>3 is used to make the values fit into a single byte, which also is the T0 register size. With a decreasing clock frequency the clockCyclesPerMicrosecond decrease, while clockCyclesToMicroseconds and MICROSECONDS_PER_TIMER0_OVERFLOW increase. 4MHz/64/256 yields 4096µs per timer overflow, i.e. 4ms MILLIS_INC and 96µs for the micros.

Now the trick is to count the 96µs increments in a way that doesn't exceed the byte limit (255). A 1ms adjustment is required when 96µs counts sum up to >= 1000 (1ms), i.e. after 10-11 overflows (96µs*11=1.056ms). The remainders of 56µs and 96µs will not lose precision when shifted right by 3 bits, because 56>>3 is exactly 7 and 96>>3 is exactly 12, even 1000>>3 == 125. So this cannot be a problem, provided my calculations are right - please check.

Back to your questions. IMO there is not normally an internal by 8 division of the clock, this would reduce normal CPU operation to 2MHz from a 16MHz source. As I understand it, the Mini Pro e.g. divides the fix 16MHz quartz clock to 8MHz in 3.3V mode, because 16MHz CPU operation is not supported at 3.3V. With a 4MHz quartz I see no need to further slow down the CPU.

But I'm not sure of the bootloader and reset, where a /8 may be meaningful for proper operation down to the absolute Vcc limits. But then the startup code could revert a fuse-forced divider back to /1. I'm not familiar with the fuses, please check your settings. I'd suspect that you select the /8 clock divider, resulting in a 500kHz CPU clock and consequently all timing slowed down by that factor.

See init() for the implemented settings of the prescalers. Keep in mind that T0 can also be used for PWM, so that the prescaler rate should be chosen for common PWM frequencies (480 or 960Hz), even when clocked by 4MHz.

DrDiettrich:
As I understand it, the Mini Pro e.g. divides the fix 16MHz quartz clock to 8MHz in 3.3V mode, because 16MHz CPU operation is not supported at 3.3V.

I'm sure the 8 MHz/3.3V Mini Pro comes with an 8 MHz crystal. No need for a clock prescale.

johnwasser:
I'm sure the 8 MHz/3.3V Mini Pro comes with an 8 MHz crystal. No need for a clock prescale.

Not mine. They work on 3.3V 8Mhz or 5V 16MHz. When programmed for one voltage and run at a different voltage, the Serial baudrate is off by a factor of 2.

Now the trick is to count the 96µs increments in a way that doesn't exceed the byte limit (255). A 1ms adjustment is required when 96µs counts sum up to >= 1000 (1ms), i.e. after 10-11 overflows (96µs*11=1.056ms). The remainders of 56µs and 96µs will not lose precision when shifted right by 3 bits, because 56>>3 is exactly 7 and 96>>3 is exactly 12, even 1000>>3 == 125. So this cannot be a problem, provided my calculations are right - please check.

If I understand your argument correctly, it means that the millis() function will not be offset significantly am I right? I took a look deeper into the wiring.c file and came to the following:

The Atmel ATmega168/328 based Arduino has 3 timers, of which the millis function uses the microcontroller’s Timer #0. When looking on the default preset for an Atmega328, the Timer #0 prescale factor is 64.

 // set timer 0 prescale factor to 64
#elif defined(TCCR0B) && defined(CS01) && defined(CS00)
 // this combination is for the standard 168/328/1280/2560
 sbi(TCCR0B, CS01);
 sbi(TCCR0B, CS00);

So if I understand it correctly, the timer is ticking 64*(1/4,000,000) times, which is every 1.6e-5 s. Given that Timer #0 has an 8-bit register, the timer will roll-over back to 0 when it tries to increment to 256. Which will trigger an timer interrupt each (1/4,000,000)64256*1000 = 4.096 milliseconds.

When digging through different Arduino hardware files, I came across the following defines which seemed of interest:

#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds (a) ( ((a) * 1000L) / (F_CPU / 1000L) )
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
// the whole number of milliseconds per timer0 overflow
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
// the fractional number of milliseconds per timer0 overflow. we shift right
// by three to fit these numbers into a byte. (for the clock speeds we care
// about - 8 and 16 MHz - this doesn't lose precision.)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)

If I understood correctly the F_CPU is provided during sketch compilation, is this correct? If this is the case with an F_CPU of 4,000,000 I get the following:

clockCyclesPerMicrosecond = 4
MICROSECONDS_PER_TIMER0_OVERFLOW = 4096
MILLIS_INC = 4.096 ~ 4 (I suppose it rounds down to an integer?)
FRACT_INC = 96 >> 3 = 12
FRACT_MAX = 125

When I now look at the millis function, which is provided as:

#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{
	// copy these to local variables so they can be stored in registers
	// (volatile variables must be read from memory on every access)
	unsigned long m = timer0_millis;
	unsigned char f = timer0_fract;

	m += MILLIS_INC;
	f += FRACT_INC;
	if (f >= FRACT_MAX) {
		f -= FRACT_MAX;
		m += 1;
	}

	timer0_fract = f;
	timer0_millis = m;
	timer0_overflow_count++;
}

unsigned long millis()
{
	unsigned long m;
	uint8_t oldSREG = SREG;

	// disable interrupts while we read timer0_millis or we might get an
	// inconsistent value (e.g. in the middle of a write to timer0_millis)
	cli();
	m = timer0_millis;
	SREG = oldSREG;

	return m;
}

I assume plain and simple this just gives me a long of the ISR function, which simplified should give me something like:

ISR(TIMER0_OVF_vect)
{
        timer0_millis += 4;
	timer0_fract += 12;
	if (timer0_fract >= 125) {
		timer0_fract -= 125;
		timer0_millis += 1;
	}
	timer0_overflow_count++;
}

So if I understand it correctly this would mean that Timer #0 increments timer0_millis by 4 every 4.096 milliseconds and adds another 1 every time it exceeds 125 (to catch up with counting?). So does this mean without any additional alteration of the code the millis() function itself will account for the missing 0.096 ms from every overflow during 125/12 = 10.4167 ms intervals? If this is correct it would mean that timer0_millis accumulates a 0.096 error every time it executes, until the error approaches 1 ms where it will correct with a jump by 5 instead of 4 to correct itself?

If this is the case it would be great, given that there will be no explicit need for me to adjust any code regarding the millis function. The only downside with regards to a 16MHz clock is that I accumulate errors over a 4ms interval instead of a 1ms interval.

I also found out that the delayMicroseconds() function does not have a compatibility with 4MHz. However, I suppose since I have no interest in using delays() or microsecond delays accurately it should not impose huge issues, am I right?

See init() for the implemented settings of the prescalers. Keep in mind that T0 can also be used for PWM, so that the prescaler rate should be chosen for common PWM frequencies (480 or 960Hz), even when clocked by 4MHz.

Given that the current pre-defined pre-scaler also works with millis(), having a MHz clock with the same pre-scaler to perform PWM should also be fine am I right? If I understand it correctly I am having a slightly more inaccurate timing since I accumulate errors every 4ms instead of 1ms, but it shouldn't impose any significant issues regarding PWM. Or am I forgetting something?

Thanks for your help! It already made me understand the underlying concepts a lot more.

Fine to see that we come to the same figures and conclusions :slight_smile:

PWM controlled devices often have a restricted frequency range, that’s why the timer frequencies are important for that purpose. The 4.096ms (250Hz) should be applicable with many devices. If you want finer millis() resolution and set the prescaler to /1 for 0.512ms increments, the PWM frequency would jump up to 2kHz, what may be too high for standard PWM. If this causes trouble, the other timers can be programmed according to special device requirements.