Computing stuff tied to the physical world

Tracking time via the watchdog

In AVR, Software on Mar 27, 2012 at 00:01

The JeeLib library has a convenient loseSomeTime() function which puts the ATmega in low power mode for 16 to 60,000 ms of time. This is only 10% accurate, because it uses the hardware watchdog which is based on an internally RC-generated 128 KHz frequency.

But the worst bit is that when you use this in combination with interrupts to wake up the ATmega, then you can’t tell how much time has elapsed, because the clock is not running. All you know when waking up, is that no more than the watchdog timeout has passed. The best you can assume is that half of it has passed – but with loseSomeTime() accepting values up to 1 minute that’s horribly imprecise.

Can we do better? Yes we can…

Internally, loseSomeTime() works by cutting up the request time into smaller slices which the watchdog can handle. So for a 10000 ms request, for example, loseSomeTime() would wait 8192 + 1024 + 512 + 256 + 16 ms to reach the requested delay, approximately. Convenient, except for those long waits.

Here’s a test sketch which simply keeps waiting 8192 ms at a time:

Screen Shot 2012 03 21 at 13 14 16

The corresponding current consumption, measured via oscilloscope, shows this:

SCR58

First of all, note how 8192 ms ends up being 8255 ms, due to the watchdog timer inaccuracy.

But the main result is that to perform this sketch, the ATmega will draw 5 mA during about 50 µs. The rest of the time it’ll be a few µA, i.e. powered down. These wake-ups draw virtually no current, when averaged.

The downside is that under these conditions, interrupts can cause us to lose track of time, up to 8192 ms.

So let’s try something else. Let’s instead run the watchdog as briefly as possible:

Screen Shot 2012 03 21 at 13 04 43

Current consumption now changes to this:

SCR57

I don’t really understand why Because of a loop in the loseSomeTime() code which runs faster, the running time drops by half in this case (and hence total nanocoulomb charge halves too). But note that we’re now waking up about 60 times per second.

This means that interrupts can now only mess with our sense of time by at most 16 ms. Without interruptions (i.e. most of the time), the watchdog just completes and loseSomeTime() adds 16 ms to the millis() clock.

Let’s try and estimate the power consumption added by these very frequent wake-ups:

  • each wake-up pulse draw 5.5 mA for about 25 µs
  • the charge consumed by each pulse is 122 nC
  • there are (roughly) 60 wake-up pulses per second
  • so per second, these pulses consume 60 x 122 nC ≈ 7.3 µC in total
  • that comes down to an average current consumption of 7.3 µA

That’s not bad at all! By waking up 60 times per second (and going back to sleep as quickly as possible), we add only 7.3 µA current consumption to the total. The reason this works, is that wake-ups only take 25 µs, which – even at 60 times per second – hardly adds up to anything.

So this technique might be a very nice way to keep approximate track of time while mostly in sleep mode, with the ability to wake up whenever some significant event (i.e. interrupt) happens!

PS. In case you’re wondering about the shape of these signals, keep in mind that I’m measuring current draw before the regulator and 10 µF capacitor on the JeeNode.

  1. You scope seems to support measuring amps, but isn’t it possible with a “cheap” scope as well, which only measures voltage?

    I believe it had something to do with adding another resistor in series, and measuring the voltage over that resistor, followed by some basic math…? Although there were some gotchas with regards to the resistor value, reletive to the accuracy of the measurement…:-?

    I’ll probably give it a try myself, but still, perhaps it is a nice idea for a blog post some day? It’ll probably interest more beginners like me…

    • I measure voltage drop across a 10Ω resistor inserted in the PWR lead – should work with any scope, just make sure the power supply floats, because the scope will force one side of the resistor to earth ground. The only trick with my scope is that it lets me enter Amps and a 10 V/A scale factor, so that proper current values and units get displayed.

  2. Quote: I don’t really understand why the running time drops by half in this case….

    JC, on which frequency is the jeenode running in this example? There is quite a lot of shifting (of words and longs) done in the code while iteratively finding the correct watchdog prescaler, where 16ms is best case and 8192 is worst case regarding number of shifts. Can this be the explanation for the difference in runtime?

    • Runtime for the calculation of the watchdog bits would be independant of delay value if the calculation would be done like this:

      wdp = msecs>>5; if(wdp>9) wdp = 9;

      Hope this is correct and gives the desired result… (I didn’t dive into your code very deeply). (One correction to my last comment: longs are not shifted.)

    • I used a std JeeNode, running at 16 MHz and am not messing with clock dividers as far as I can tell.

      The calculation you give is not correct, I think: it calculates msecs/32, whereas the while loop iterates to calculate log2(msecs/32).

      Ah, but wait, that might explain it: the while loop loops a few times when msecs is 8192, whereas it immediately exits when msecs is 16. The difference in runtime would explain it the extra 25 µs or so. And in that case it’s harmless.

    • Yes, sure, now I see :-)

      But what one can learn from this little example is, that shifting can be quite ‘expensive’ on these little controllers without barrel shifters.

      Apart from this, in your test case the accuracy of the RC watchdog timer is amazingly good (within ~1% of the desired value).

  3. I can’t resist:

    for(wdp=0;msecs>=32;wdp++) msecs = msecs>>1;

    This shifts a word only once per iteration (not wdp times).

    • Heh :) – indeed, that looks like a good idea. But keep in mind that the optimization is (almost) self-defeating: with large ms values, the loop takes longer, but it also matters less since the sleep times increase as well. I’ve scheduled a follow-up weblog post about this – thx.

  4. Great think… in this case do you think that i can have a 1% accuracy for, for example 10 minutes ? if i make something like:

    void loop() {

    act_time = act_time + 16;

    Sleepy::loseSomeTime(16);

    // 10 minutes aprox (1000ms/16ms = 62,5)

    if (act_time >= (10 * 60 * 62,5) {

    send_rf; //send no. of pulses via RF

    act_time = 0;

    } }

    p>… so I can have 10 minutes time interval using also the loseSomeTime ?

    Sorry i’f I’m newbe and maybe I don’t understand so well the code. I will prefer the loseSomeTime to the external 32KHz crystal Timer2 example.

    Thanks a lot Denis

    • Yes, that’s the idea. But the times can be up to 10% off, worst case. You could calibrate your JeeNode to get better accuracy, assuming its ambient temperature stays relatively constant – then 1% sounds doable.

  5. If I use ‘losesometime’ with a relatively long (say 10 secs) time period and an external pin interrupt wakes me in the meantime, can I go back to sleep after (briefly) dealing with the interrupt, whilst STILL PRESERVING the 10 seconds time period – ie so that in the longer term I can more accurately calculate the frequency of the external interrupt pulses by averaging, whilst still maintaining a low power state? (The number of pulses will be logged by radio to an external system that has accurate time, so eventually number of pulses reported/ known accurate time will become more precise, at the slight expense of slow speed of reaction to change.

    • I’ve considered that, but never explored it. The current code in loseSomeTime() will disable the watchdog, and it splices 10s in 8+2s to reach that point in time. If you write your own version, you may be able to do what you describe. The watchdog should still be running after an interrupt, so you could indeed wait for it to happen to regain the proper sense of time (within 10%).

      Hmmm… that might even be useful with 16 ms cycles: handle the interrupt, and continue waiting for the watchdog to reach its 16 ms trigger. The exact time of the interrupt would still not be known, but the system time would advance more or less properly.

      If you try this out, please do tell – I’d be very interested in the results.

Comments are closed.