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.
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.
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.
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.
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 Adjustment = Time - DeviceTime
Interval = Time - StartTime
CompensationRatio
, which corrects the device's time to match the ground truth time: CompensationRatio = Adjustment / Interval
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.
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:
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!
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.
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!
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.
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).