Hi,
I've been working with an Arduino at 1Mhz (using the internal 8MHz clock and the divide by 8 option) ... all was working well until I tried to use the OneWire library ... digging around I found that the problem was the delayMicroseconds() function which only catered for 8MHz and 16MHz clocks. Also, given most of the use cases of this call use constants I felt there was a way to be much more accurate.
So I've now got it all working nicely with the following ...
A macro to determine if the call is using a constant, if it is then we can use some inline assembly to correctly delay (accurate to the clock cycle) given the compiler will optimise away all of the logic at compile time.
If it's not a constant then we call a standard function, for delay less than 15 clock cycles it will delay 15ish, for 15 - 20 it's about 20, then it should be pretty correct (to within 4 cycles.) I believe this is better than the existing routines when using 8 or 16Mhz clocks and it obviously caters for any other clock frequency by using F_CPU.
So in summary, this gives you clock cycle accurate delays when a constant is used, and not bad accuracy for non-constants. My OneWire device is now working perfectly on a 1MHz arduino. (It also works at 16Mhz, but that's the only other testing I've done so far.)
The code ... the first bit in wiring.h ...
#define delay_cycles(x) if(__builtin_constant_p(x)) { delay_cycles_CONST(x); } else { delay_cycles_VAR(x); }
#define delayMicroseconds(x) delay_cycles(x * clockCyclesPerMicrosecond())
#define delay_cycles_CONST(cycles) { \
if(cycles&1) asm volatile("nop"); \
if(cycles&2) asm volatile("nop \n\t nop"); \
if(cycles&4) asm volatile("push r26 \n\t pop r26"); \
if(cycles > 7) { \
asm volatile( \
"1: sbiw %0, 1" "\n\t" \
" brne 1b" "\n\t" \
" nop" "\n\t" \
" nop" "\n\t" \
" nop" "\n\t" \
: : "r" (((cycles&0xfff8)-4)>>2) \
); \
} \
}
void delay_cycles_VAR(unsigned int cycles) __attribute__ ((noinline));
Plus you need to comment out the existing delayMicroseconds prototype.
Then in wiring.c ...
void delay_cycles_VAR(unsigned int cycles) {
if(cycles < 15) return;
asm volatile(
"1: sbiw %0, 1" "\n\t" // 2 cycles
" brne 1b" // 2 cycles if branch taken
: : "r" ((cycles>>2))
);
}
And comment out the existing delayMicroseconds routine.
It does cause a few extra bytes of code for a constant based delay, this could probably be tidied up a little but I assumed it's better to be accurate.
Thoughts?
I removed my comments when I put it into a macro (was fighting gcc avoiding my always_inline directive), but I can happily supply the thought process behind it all if it's not obvious.
Lee.