Even if you could do what you are proposing, which you can't (see Edgar Bonet's excellent answer), imagine the performance hit running an interrupt every single microsecond would have on your code.
Every interrupt takes time away from your code.
Let's crunch some more numbers. If you wanted the millis/micros interrupt to take no more than 1% of the processing time, so it has no noticeable impact on your running code it would have to execute for only 10ns. That's 1% of 1000ns, or 0.000001 x 0.01.
Now assume it takes 50 clock cycles for a highly optimised version of your function to operate. That's a conservative estimate. That includes the time taken to prepare for, call and return from the interrupt.
For both those to hold true you would have to be able to execute 100 x 50 instructions in a microsecond. (To fill your 1uS you should be able to run your 1% interrupt 100 times, yes?) That's 5,000 x (1/0.000001) instructions per second, or 5,000,000,000 instructions per second.
Assuming a single clock cycle for each instruction, that means your chip would have to be running at 5GHz...!
Can you see the problem?
So we scale it back to something more realistic. Maybe a 10% performance hit instead of 1%. How would that be? Well, it's simple enough to say that it would require a chip running at 10% of the speed. 500 x (1/0.000001). So 500MHz.
Still a long long way away from the 16MHz that the Arduino's ATMega runs at.
So you can see the issue.
So we choose close to 1mS instead. That gives a much lower overhead and it's something that the 16MHz Arduino can cope with.
Why 1024 and not 1000? That's simple: 1000 is base 10. We, as humans, with two hands (usually) of (normally) 5 digits on each, love base 10. We thrive in base 10. However, computers with their 1s and 0s hate base 10. They much prefer working in base 2. It makes arithmetic really easy for them. And in interrupts we want the arithmetic to be easy.
Let's take a quick look at the maths involved and expand out the macros in the code.
Every tick the millis value is increased by MILLIS_INC
, and any extra is added to a "fractional" count. What's MILLIS_INC
? It's:
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
So let's expand that out - what's MICROSECONDS_PER_TIMER0_OVERFLOW
?
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
And what's clockCyclesToMicroseconds
?
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
What's clockCyclesPerMicrosecond()
?
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
And, of course, F_CPU
is (typically) 16,000,000.
So now we can put it all together.
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
becomes:
#define MILLIS_INC ((clockCyclesToMicroseconds(64 * 256)) / 1000)
which becomes:
#define MILLIS_INC (((64 * 256)/clocksPerMicrosecond()) / 1000)
Which in turn becomes:
#define MILLIS_INC (((64 * 256)/(F_CPU/1000000L)) / 1000)
Which finally turns into:
#define MILLIS_INC (((64 * 256)/(16000000/1000000L)) / 1000)
So reduce those down a little:
#define MILLIS_INC ((16384 / 16) / 1000)
And finally:
#define MILLIS_INC (1.024)
And since we're talking integers here that is automatically truncated (by the compiler, not at runtime, so it's essentially "free") to 1.
But what about the 0.024ms...? That's simple. The "Fractional" amount is added to another counter - but not as 0.024 - instead as a FRACT_INC
amount, which is calculated as ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
. That's 1024 % 100, or just 24, but then that's right shifted 3 times. That becomes 3. And when that count hits another limit it loops round and adds one to the millis count. That limit is FRACT_MAX
, which is 1000 >> 3
or 125.
All nice and simple and straight forward. Variables, like the fractional count, are kept to just 8 bits (which is what you want on an 8 bit CPU...) and the majority of calculations are done by the compiler, not at runtime by the CPU. Everything else is simple addition / subtraction and comparisons. Very light. And it runs often enough to be good enough for the majority of uses. And given that, because it's just a timer interrupt and is subject to collision with other interrupts, and can't run when interrupts are disabled, it's only a rough figure anyway. Even the act of reading millis()
can slow down the counting of milliseconds, since it disables interrupts while the value of the count is retreived (a so called "critical section").
And yes, it could be tweaked to run at precisely 1000uS intervals and do away with the fractional component - but why bother when it's never going to be that accurate anyway? Also the added complexity of calculating what 1000uS is for a timer configuration when you consider the vast array of speeds that Arduinos could potentially run at (16MHz for an Uno, 8MHz for the 3.3V boards, 1MHz for the default internal oscillator...) it's far simpler to say "This is what it ticks at, and this is the number of milliseconds per tick, with this bit left over" which is calculated at compile time, instead of having to work out prescalers and overflow counts, etc.