Zero to Photon:
Timekeeping
2024 . 3 . 17
A core feature of Photon is the ability to capture photos at precise times, potentially far in the future.

Photon intentionally doesn't have an internet connection, so it has no way to synchronize its internal time with an atomic clock. It therefore needs to track the passage of time the old fashioned way: with a quartz crystal and a prayer.


Basic Timekeeping

Photon tracks time in units of Ticks, where one Tick is arbitrarily defined as 1/16 of a second. This value was chosen for two reasons: it provides a reasonable level of precision (62.5ms), and it's a power of 2 (which reduces multiplications/divisions to simple bitshifts).

Photon uses a standard 32768 Hz quartz crystal to track time. This crystal is wired to a MSP430 microprocessor, which uses its real-time clock peripheral to wake itself every 215 Ticks (34.1 minutes), increment its time variable, and go back to sleep.

64-bits of Timely Goodness

One question that arose during Photon's development was whether time should be tracked as a 32-bit or 64-bit integer.

On one hand, tracking time with 32 bits is a lot cheaper in code size than tracking time with 64 bits. The MSP430 is a 16-bit microprocessor, so doing math with 64-bit integers is expensive, particularly in code size. (And there's only 15.5 KB of codespace available on Photon's MSP430, and as of writing this post, it's within a few bytes of being 100% full!)

On the other hand, tracking time with 64 bits frees us from having to deal with timestamp overflow. Given that Photon tracks time in units of Ticks (62.5ms), a signed 32-bit integer can only represent ±4 years, while a signed 64-bit integer can represent ±18 billion years.

With this tradeoff in mind, it was chosen to track time as a 64-bit integer. We could switch to 32-bit integers (or perhaps 48-bit integers?) in the future to free up some codespace, but for now, the simplicity of not having to handle timestamp overflow is worth the hit to codespace.


Fine Tuning

Photon's quartz crystal is spec'd with 20ppm of frequency tolerance, which means it'll track time within 0.002% at 25°C.

At first glance, that might sound like it's pretty accurate. But in terms of timekeeping, the error sure adds up: over the span of a year, 20ppm means (20/1e6)*365 = 0.0073 days = 10.5 minutes could be gained or lost. In other words, if you wanted to capture a photo of the New Year's ball drop, and you configured your Photon a year in advance, you could miss the big moment by 10 whole minutes. In those terms, 20ppm doesn't seem very good at all!

Improving Photon's timekeeping beyond the crystal's native 20ppm accuracy calls for a self-correction strategy, which consists of two parts: quantifying the device's clock drift, and compensating for it.

Drift Quantification

Quantifying the device's clock drift requires knowing three values:
  • StartTime: the ground truth time when the device started tracking time (constant)
  • DeviceTime: the device's current time, tracked internally using its quartz crystal
  • Time: the current ground truth time
For convenience, we'll also define:
  • Adjustment = Time - DeviceTime
  • Interval = Time - StartTime
With these values, we can define CompensationRatio, which corrects the device's time to match the ground truth time:
  • CompensationRatio = Adjustment / Interval
Photon chooses to limit Adjustment (the numerator) to the integer range [-16, +16] while manipulating Interval (the denominator, also an integer) to maintain the same CompensationRatio. By imposing this constraint, we gain the benefit that the device's time isn't allowed to drift by more than ±1 second (ie -16 to +16 Ticks) from the ground truth time before compensation occurs, at the cost of the precision of the compensation. (That is, if we permitted a larger range for Adjustment, we could more accurately represent the true CompensationRatio, at the cost of more temporary inaccuracy between the ground truth time and the device's time.)

This quantification procedure is implemented by Photon Transfer, and occurs each time you plug in a Photon. That means that a Photon's timekeeping becomes more accurate every time you run Photon Transfer with the device plugged in.

Drift Compensation

Putting CompensationRatio into words: the device's time is corrected by adding Adjustment Ticks to the device's time every Interval Ticks. And that's all Photon needs to do!

The plot below helps visualize how the drift compensation corrects the device's time to match the ground truth time:

Results

This drift compensation strategy improves Photon's timekeeping accuracy significantly, but precisely quantifying its accuracy takes a fair amount of time to allow the uncompensated error to accumulate. With the data available at the time of writing this post, the accuracy appears to be around 0.1ppm in indoor conditions where the temperature is fairly constant. Outdoor conditions or places with varying temperatures would have worse accuracy — see the caveats below.

Going back to the New Year's ball drop example, 0.1ppm correlates to 3 seconds of inaccuracy after one year. Not bad — plenty accurate to catch the big moment!

Caveats

One caveat of this drift compensation strategy is that, because Adjustment can be negative, the device's time isn't monotonically increasing. That is, if two photos are captured back-to-back, it's possible that the second photo has an earlier timestamp than the first. Because of the constraints that we place on Adjustment though, the second photo's timestamp can't precede the first photo's timestamp by more than one second.

Another caveat of this strategy is that it's only as effective as the calibration environment is representative of the usage environment. For example, if the device has been sitting indoors for most of its life, but is then moved outdoors in the winter, the drift compensation will reflect the indoor environment and won't be as effective outdoors in the cold. In the future, a Photon software update may fix this by only considering the most recent N days when quantifying the device's drift.


Reliability

There are two failure modes that are worth considering in the context of Photon's ability to maintain the correct time: crashes and power loss.

Crashes

Photon's MSP430 is responsible for timekeeping, and like any chip, it can crash. If it does, it restarts itself and recovers gracefully. However what happens to the MSP430's notion of time when it crashes?

The MSP430's RTC peripheral is separate from the CPU core and is designed to remain unaffected when the CPU core resets, including all the various ways it can crash (eg watchdog or bad memory accesses). In fact the RTC is unaffected by every kind of reset (PUC, POR, BOR) other than an actual brownout (when the chip's supply voltage drops below a certain threshold).

The RTC peripheral is only half of the equation, however, because it's not responsible for tracking the full 64-bit absolute time. Rather, the RTC peripheral is only responsible for repeatedly counting from 0 to 215 Ticks; once it reaches 215, it triggers an interrupt, at which point software is responsible for updating the total accumulated 64-bit time.

That's where the second half of the equation comes in: the RAM that holds this 64-bit time integer needs to be as resilient as the RTC peripheral itself. To accomplish that goal, the 64-bit time is stored in a special RAM region that the MSP430 calls "backup memory". This region is a mere 32-bytes, and is designed to persist across CPU resets and all sleep modes (except LPM4.5).

Together, these two pieces are all Photon needs to keep track of time even if its dear MSP430 crashes!

Power Loss

Photon's battery dying is the only way that it can lose track of time — neglecting silicon bugs / software bugs / cosmic rays, of course! Once its battery dies, Photon needs to be charged and connected to Photon Transfer to restore its timekeeping.

To minimize the likelihood of losing track of time, Photon will stop capturing photos when its battery reaches 2%, to reserve the remaining battery for the sole purpose of timekeeping. Since Photon uses very little power when it's not taking photos, 2% battery will sustain timekeeping for quite a while.

When Photon's battery is charged after losing power, Photon doesn't know what time it is until it's connected to Photon Transfer (which automatically sets its time). Before its time has been set, Photon's time-based features (time-based photo triggers and time-constrained motion triggers) won't function. The photos that are captured when Photon doesn't know the current time are timestamped with "relative" times, which is the amount of time since Photon started charging.


Daylight Savings Time

Daylight savings time occurred on March 10, 2024. Also on March 10, 2024, completely coincidentally (😆), it was discovered that support for daylight savings time wasn't added to Photon. That is, if a photo was scheduled to be captured at 9AM every day, the photo would actually be captured at 10AM on March 10th and thereafter.

Photon Transfer now generates the appropriate information for DST and supplies it to Photon, and Photon acts accordingly on the appropriate days in March / November. However if Photon Transfer is running in an enlightened place without DST (eg Hawaii), no DST information is generated, and Photon does nothing for DST (as you'd expect).