Edit: Here is a variation on this idea that uses millis()
instead
of interrupts. Of course, Nick Gammon is right: you do not need
interrupts for a clock that updates its display a few times per second.
It was just a bit easier to write an interrupt-based code, as the time
between successive calls of the function is a known constant.
Given that you are going to use a 32-bit processor, I wrote this using 32-bit arithmetic. The ratios should be a little bit more accurate.
uint32_t earth_seconds, mars_seconds, titan_seconds;
void update_planet_times() {
// Compute number of elapsed milliseconds since last update.
static uint32_t last_update;
uint32_t now = millis();
uint32_t elapsed_millis = now - last_update;
last_update = now;
// Update Earth time. 1 Earth second = 1000 / 1 ms.
static uint32_t earth_ticks;
earth_ticks += elapsed_millis * 1;
uint32_t elapsed_earth_seconds = elapsed_earth_ticks / 1000;
earth_ticks -= elapsed_earth_seconds * 1000;
earth_seconds += elapsed_earth_seconds;
// Update Mars time. 1 Mars second = 763426 / 743 ms.
static uint32_t mars_ticks;
mars_ticks += elapsed_millis * 743;
uint32_t elapsed_mars_seconds = elapsed_mars_ticks / 763426;
mars_ticks -= elapsed_mars_seconds * 763426;
mars_seconds += elapsed_mars_seconds;
// Update Titan time. 1 Titan second = 3193819 / 3200 ms.
static uint32_t titan_ticks;
titan_ticks += elapsed_millis * 3200;
uint32_t elapsed_titan_seconds = elapsed_titan_ticks / 3193819;
titan_ticks -= elapsed_titan_seconds * 3193819;
titan_seconds += elapsed_titan_seconds;
}
Note that using this technique for the Earth may seem superfluous: one
may count seconds by simply computing millis()/1000
. This, however,
would fail when millis()
rolls over in 49.7 days. The code above
is immune to this rollover, and should count Earth seconds reliably for
the next 136.1 years.
Edit: Here is a variation on this idea that uses millis()
instead
of interrupts. Of course, Nick Gammon is right: you do not need
interrupts for a clock that updates its display a few times per second.
It was just a bit easier to write an interrupt-based code, as the time
between successive calls of the function is a known constant.
Given that you are going to use a 32-bit processor, I wrote this using 32-bit arithmetic. The ratios should be a little bit more accurate.
uint32_t earth_seconds, mars_seconds, titan_seconds;
void update_planet_times() {
// Compute number of elapsed milliseconds since last update.
static uint32_t last_update;
uint32_t now = millis();
uint32_t elapsed_millis = now - last_update;
last_update = now;
// Update Earth time. 1 Earth second = 1000 / 1 ms.
static uint32_t earth_ticks;
earth_ticks += elapsed_millis * 1;
uint32_t elapsed_earth_seconds = elapsed_earth_ticks / 1000;
earth_ticks -= elapsed_earth_seconds * 1000;
earth_seconds += elapsed_earth_seconds;
// Update Mars time. 1 Mars second = 763426 / 743 ms.
static uint32_t mars_ticks;
mars_ticks += elapsed_millis * 743;
uint32_t elapsed_mars_seconds = elapsed_mars_ticks / 763426;
mars_ticks -= elapsed_mars_seconds * 763426;
mars_seconds += elapsed_mars_seconds;
// Update Titan time. 1 Titan second = 3193819 / 3200 ms.
static uint32_t titan_ticks;
titan_ticks += elapsed_millis * 3200;
uint32_t elapsed_titan_seconds = elapsed_titan_ticks / 3193819;
titan_ticks -= elapsed_titan_seconds * 3193819;
titan_seconds += elapsed_titan_seconds;
}
Note that using this technique for the Earth may seem superfluous: one
may count seconds by simply computing millis()/1000
. This, however,
would fail when millis()
rolls over in 49.7 days. The code above
is immune to this rollover, and should count Earth seconds reliably for
the next 136.1 years.
Your question is a perfect example of an XY problem: you are asking something that has little to do with your actual problem, but is rather about your misguided idea of a solution. Counting Mars seconds or Titan seconds is an easy problem. Dividing the frequency of a 1 MHz signal by successive powers of 1.25 is a hard problem. There is little value in turning an easy problem into a hard one.
I suggest a solution inspired by Bresenham's algorithm for drawing slanted lines:
- configure a timer to deliver a periodic interrupt to your program, with a period shorter than your shortest relevant second
- compute the ratio of your favorite planet's second to the interrupt period
- approximate this ratio by a rational number p/q
In the interrupt service routine:
- increment a counter by q
- when the counter reaches p, decrement it by p and note that one second has elapsed.
The lazy way of getting a periodic interrupt is to simply enable the TIMER0_COMPA interrupt. Timer 0 is already configured by the Arduino core to count full cycle every 1,024 μs. You may want to set OCR0A to something near 128 in order to prevent the overflow interrupt (used by Arduino for timing) and your interrupt from getting too close.
For the Earth, the ratio is
1 s / 1,024 μs = 1,000,000 / 1,024 = 976.5625 = 15625 / 16
For Mars and Titan, they can be approximated by 22,075/22 and 33,139/34 respectively, to within a fraction of a ppm.
Here is the interrupt service routine:
// Count of elapsed seconds on each planet.
volatile uint32_t earth_secs, mars_secs, titan_secs;
// Executed every 1,024 us on a timer interrupt.
ISR(TIMER0_COMPA_vect) {
static uint16_t earth_ticks, mars_ticks, titan_ticks;
earth_ticks += 16;
if (earth_ticks >= 15625) {
earth_ticks -= 15625;
earth_secs++;
}
mars_ticks += 22;
if (mars_ticks >= 22075) {
mars_ticks -= 22075;
mars_secs++;
}
titan_ticks += 34;
if (titan_ticks >= 33139) {
titan_ticks -= 33139;
titan_secs++;
}
}
Note that you can build better approximations of Mars and Titan seconds by using rational numbers with larger p and q, but then you would have to do 32-bit arithmetics in the ISR. You could also configure another timer to deliver an interrupt with a longer period: you would then be able to achieve better accuracy with 16-bit arithmetics, at the cost of increased jitter.