joeycastillo / Sensor-Watch

A board replacement for the classic Casio F-91W wristwatch
Other
1.17k stars 233 forks source link

WIP: High-precision timer #382

Open zzm634 opened 6 months ago

zzm634 commented 6 months ago

Adds a high-precision (1,024hz) reference timestamp that faces may use to measure elapsed time or schedule future events independent of calendar time (aka, the RTC).

WORK IN PROGRESS. Still to do:

Currently, in most faces that need it, the RTC is used to measure elapsed time and schedule background tasks. Although the RTC offers higher tick-rates than 1hz, the reference timestamp it provides is only precise down to the second. Stopwatches and other timing faces need a timestamp with higher precision. Also, the value stored in the RTC can be modified by the user when they set the time or change time zones, meaning that comparing RTC timestamps is not an entirely reliable way to measure elapsed time.

The High-Precision Timer (HPT) uses TC2 (and TC3) to keep track of a 32-bit counter, ticking upwards at 1,024 hz. It is enabled on-demand by faces or features that need access to the timestamp it provides. When no faces or features require this timestamp, the timer peripheral is disabled to save power. watch_hpt.h provides low-level access to the timer peripheral, while Movement provides an implementation to handle counter overflows and an API to share access to timer features across multiple faces.

It's sorta like the difference between performance.now() and Date.now() in javascript.

The RTC provides an "alarm" system that can trigger an interrupt at a specific calendar time, but this is also still tied to its one-second resolution. This means it is not suitable for scheduling background that are to occur in the very near future. For example, buzzer tones are often under a full second long. Controlling the buzzer is currently handled using a "fast ticks" mode that wakes up the CPU using the 128hz period provided by the RTC. If we want to play a tone for a quarter of a second, we enable the buzzer, turn on "fast ticks" mode, count 32 wakeups later and then disable the buzzer. The HPT provides access to a background wakeup event with an arbitrary delay. Using the HPT we can instead enable the buzzer, schedule a HPT wakeup for 256 ticks later, go to sleep, wake up, and disable the buzzer, all without ever needing to wake the CPU up between turning the buzzer on and off.

This PR adds:

If this PR is accepted, I will begin work on refactoring movement.c to use the HPT for LED control and long-press timing, which should eliminate the need for "fast ticks" mode and allow the CPU to sleep more often. (It also means we could allow faces to use the 128hz RTC tick mode, if we really wanted to.) Depending on power consumption, we could even consider using this timer to handle face timeouts and low-energy timeouts, though these are not critical as they do not need subsecond precision, and they won't be affected by RTC modifications, because the user changing the time would invariably involve resetting these timeouts.

Some outstanding concerns:

See also https://github.com/joeycastillo/Sensor-Watch/discussions/381

zzm634 commented 6 months ago

Just noticed that watch_buzzer.c uses TC3 for playing note sequences. Yet another thing for HPT to support I guess.

wryun commented 2 months ago

Well, I'm a fan of higher preicions timing, and it all sounds good. But it's a little scary!

@joeycastillo - do you think this is a good approach?

joeycastillo commented 2 months ago

I think I need to reserve an opinion on this until I can run power profiling on it, but I do like the idea of a shared high precision timer that everything that needs high precision timing can share, rather than having individual watch faces make use of TC peripherals. I’m also curious about the choice to use a 32 bit counter (which ties up two TC’s) instead of a 16 bit counter. Using a 16 bit counter would mean overflowing every minute or so, but it would make handling overflows as a matter of course instead of a thing that happens once in a blue moon. Especially since high precision timing mostly relates to things happening in the near future.

@wryun your pinging me on this comes at an interesting moment too, because I’m currently doing a pretty big refactor of Movement in the background, tearing out a lot of low level technical debt and trying to free up resources for forthcoming hardware. In particular, my refactor should free up TC0 and TC1, which I think we might need to free up TC2 and TC3 for use by the accelerometer. Anyway I’m open to considering this for potential inclusion into the forthcoming second movement firmware, again, pending a more thorough power consumption analysis and a deeper review of the code.

wryun commented 2 months ago

Thanks - happy to hear it's on your radar.

I guess the main advantage of the 32-bit counter from @zzm634 's perspective is that one might be able to do away with some of the overflow handling:

The code in this PR should handle this situation properly, but it does mean there is some extra logic in play to detect and compensate for timer overflows that occur while critical operations are being performed. These "safety" checks mean more CPU cycles, more power consumed, more latency for the user.

i.e. rather than having this complexity, one could just schedule an event before the overflow every time you start the timer like (EVENT_HPT_MAX_TIME or something) and document that code has to handle it by aborting the timer action. Since it's unlikely one needs a 49+ day precision timing event, and this might have an impact on battery life (and event hpt accuracy, in that you might be able to get the event to user code faster).

But given they're in there at the moment, I would also prefer a 16-bit counter and handle the overflow as a matter of course.


One of the interesting things about this PR as well is that (from a brief look) none of the existing faces have been modified to use EVENT_HPT because they only really need an accurate time when they're reacting to another event. I'm not sure there's actually a clear use case for EVENT_HPT; it would be enough that you can easily get the differences between times when something else happens (i.e. button press).