static void _movement_set_top_of_minute_alarm() {
uint32_t counter = watch_rtc_get_counter();
+ uint32_t next_minute_counter;
watch_date_time_t date_time = watch_rtc_get_date_time();
uint32_t freq = watch_rtc_get_frequency();
+ uint32_t half_freq = freq >> 1;
+ uint32_t subsecond_mask = freq - 1;
+ uint32_t ticks_per_minute = watch_rtc_get_ticks_per_minute();
- // remove subsecond from counter
- counter &= ~(freq - 1);
+ // get the counter at the last second tick
+ next_minute_counter = counter & (~subsecond_mask);
+ // add/subtract half second shift to sync up second tick with the 1Hz interrupt
+ next_minute_counter += (counter & subsecond_mask) >= half_freq ? half_freq : -half_freq;
// counter at the next top of the minute
- counter += (60 - date_time.unit.second) * freq;
+ next_minute_counter += (60 - date_time.unit.second) * freq;
+
+ // Since the minute alarm is very important, double/triple check to make sure that it will fire.
+ // These are theoretical corner cases that probably can't even happen, but since we do a subtraction
+ // above I wanna be certain that we don't schedule the next alarm at a counter value just before the
+ // current counter, which would result in the alarm firing after more than one year.
+ // This should be robust to the counter overflow, and we should ever iterate once at most.
+ if (next_minute_counter == counter) {
+ next_minute_counter += ticks_per_minute;
+ }
+
+ while ((next_minute_counter - counter) > ticks_per_minute) {
+ next_minute_counter += ticks_per_minute;
+ }
- movement_volatile_state.minute_counter = counter;
+ movement_volatile_state.minute_counter = next_minute_counter;
- watch_rtc_register_comp_callback(cb_minute_alarm_fired, counter, MINUTE_TIMEOUT);
+ watch_rtc_register_comp_callback(cb_minute_alarm_fired, next_minute_counter, MINUTE_TIMEOUT);
}
static bool _movement_update_dst_offset_cache(void) {
watch_rtc_set_date_time(date_time);
}
- // set up the 1 minute alarm (for background tasks and low power updates)
- _movement_set_top_of_minute_alarm();
-
// register callbacks to be notified when buzzer starts/stops playing.
// this is so movement can be notified even when triggered by a face bypassing movement
watch_buzzer_register_global_callbacks(cb_buzzer_start, cb_buzzer_stop);
movement_state.light_on = false;
movement_state.next_available_backup_register = 2;
_movement_reset_inactivity_countdown();
+
+ // set up the 1 minute alarm (for background tasks and low power updates)
+ _movement_set_top_of_minute_alarm();
}
void app_wake_from_backup(void) {
movement_volatile_state.turn_led_off = false;
movement_force_led_off();
}
- }
-
- // actually play the note sequence we were asked to play while in deep sleep.
- if (movement_volatile_state.has_pending_sequence) {
- movement_volatile_state.has_pending_sequence = false;
- watch_buzzer_play_sequence_with_volume(_pending_sequence, movement_request_sleep, movement_button_volume());
- // When this sequence is done playing, movement_request_sleep is invoked and the watch will go,
- // back to sleep (unless the user interacts with it in the meantime)
- _pending_sequence = NULL;
- }
-
- // handle top-of-minute tasks, if the alarm handler told us we need to
- if (movement_volatile_state.minute_alarm_fired) {
- movement_volatile_state.minute_alarm_fired = false;
- _movement_handle_top_of_minute();
}
// if we have a scheduled background task, handle that here:
event_type++;
}
+ // handle top-of-minute tasks, if the alarm handler told us we need to
+ if (movement_volatile_state.minute_alarm_fired) {
+ movement_volatile_state.minute_alarm_fired = false;
+ _movement_handle_top_of_minute();
+ }
+
// Now handle the EVENT_TIMEOUT
if (resign_timeout && movement_state.current_face_idx != 0) {
event.event_type = EVENT_TIMEOUT;
// // this is a hack tho: waking from sleep mode, app_setup does get called, but it happens before we have reset our ticks.
// // need to figure out if there's a better heuristic for determining how we woke up.
app_setup();
+
+ // If we woke up to play a note sequence, actually play the note sequence we were asked to play while in deep sleep.
+ if (movement_volatile_state.has_pending_sequence) {
+ movement_volatile_state.has_pending_sequence = false;
+ watch_buzzer_play_sequence_with_volume(_pending_sequence, movement_request_sleep, movement_button_volume());
+ // When this sequence is done playing, movement_request_sleep is invoked and the watch will go,
+ // back to sleep (unless the user interacts with it in the meantime)
+ _pending_sequence = NULL;
+ }
}
#endif
void cb_tick(void) {
rtc_counter_t counter = watch_rtc_get_counter();
uint32_t freq = watch_rtc_get_frequency();
+ uint32_t half_freq = freq >> 1;
uint32_t subsecond_mask = freq - 1;
movement_volatile_state.pending_events |= 1 << EVENT_TICK;
- movement_volatile_state.subsecond = (counter & subsecond_mask) >> movement_state.tick_pern;
+ movement_volatile_state.subsecond = ((counter + half_freq) & subsecond_mask) >> movement_state.tick_pern;
}
void cb_accelerometer_event(void) {
static const uint32_t RTC_OSC_HZ = 1 << RTC_OSC_DIV; // 2^10 = 1024
static const uint32_t RTC_PRESCALER_DIV = 3;
static const uint32_t RTC_CNT_HZ = RTC_OSC_HZ >> RTC_PRESCALER_DIV; // 1024 / 2^3 = 128
+static const uint32_t RTC_CNT_SUBSECOND_MASK = RTC_CNT_HZ - 1;
static const uint32_t RTC_CNT_DIV = RTC_OSC_DIV - RTC_PRESCALER_DIV; // 7
static const uint32_t RTC_CNT_TICKS_PER_MINUTE = RTC_CNT_HZ * 60;
static const uint32_t RTC_CNT_TICKS_PER_HOUR = RTC_CNT_TICKS_PER_MINUTE * 60;
}
void watch_rtc_set_unix_time(unix_timestamp_t unix_time) {
- // time_backup + counter / RTC_CNT_HZ = unix_time
+ /* unix_time = time_backup + counter / RTC_CNT_HZ - 0.5
+ *
+ * Because of the way the hardware is designed, the periodic interrupts fire at the subsecond tick values
+ * according to the table below (for a 128Hz counter).
+ * since the 1Hz periodic interrupt is the most important, we shift the conversion from counter to timestamp by 64 ticks,
+ * so that the second changes at the top of the 1Hz interrupt. Hence the 0.5 factor in the equation above.
+ * 1Hz: 64
+ * 2Hz: 32, 96
+ * 4Hz: 16, 48, 80, 112
+ * 8Hz: 8, 24, 40, 56, 72, 88, 104, 120
+ * 16Hz: 4, 12, 20, ..., 124
+ * 32Hz: 2, 6, 10, ..., 126
+ * 64Hz: 1, 3, 5, ..., 127
+ * 128Hz: 0, 1, 2, ..., 127
+ */
rtc_counter_t counter = rtc_get_counter();
- unix_timestamp_t tb = unix_time - (counter >> RTC_CNT_DIV);
+ unix_timestamp_t tb = unix_time - (counter >> RTC_CNT_DIV) - ((counter & RTC_CNT_SUBSECOND_MASK) >> (RTC_CNT_DIV - 1)) + 1;
watch_store_backup_data(tb, TB_BKUP_REG);
}
unix_timestamp_t watch_rtc_get_unix_time(void) {
- // time_backup + counter / RTC_CNT_HZ = unix_time
+ // unix_time = time_backup + counter / RTC_CNT_HZ - 0.5
rtc_counter_t counter = rtc_get_counter();
unix_timestamp_t tb = watch_get_backup_data(TB_BKUP_REG);
- return tb + (counter >> RTC_CNT_DIV);
+ return tb + (counter >> RTC_CNT_DIV) + ((counter & RTC_CNT_SUBSECOND_MASK) >> (RTC_CNT_DIV - 1)) - 1;
}
rtc_counter_t watch_rtc_get_counter(void) {
#include <emscripten/html5.h>
static const uint32_t RTC_CNT_HZ = 128;
+static const uint32_t RTC_CNT_SUBSECOND_MASK = RTC_CNT_HZ - 1;
static const uint32_t RTC_CNT_DIV = 7;
static const uint32_t RTC_CNT_TICKS_PER_MINUTE = RTC_CNT_HZ * 60;
static const uint32_t RTC_CNT_TICKS_PER_HOUR = RTC_CNT_TICKS_PER_MINUTE * 60;
}
void watch_rtc_set_unix_time(unix_timestamp_t unix_time) {
- // time_backup + counter / RTC_CNT_HZ = unix_time
+ // unix_time = time_backup + counter / RTC_CNT_HZ - 0.5
rtc_counter_t counter = watch_rtc_get_counter();
- reference_timestamp = unix_time - (counter >> RTC_CNT_DIV);
+ reference_timestamp = unix_time - (counter >> RTC_CNT_DIV) - ((counter & RTC_CNT_SUBSECOND_MASK) >> (RTC_CNT_DIV - 1)) + 1;
}
unix_timestamp_t watch_rtc_get_unix_time(void) {
- // time_backup + counter / RTC_CNT_HZ = unix_time
+ // unix_time = time_backup + counter / RTC_CNT_HZ - 0.5
rtc_counter_t counter = watch_rtc_get_counter();
- return reference_timestamp + (counter >> RTC_CNT_DIV);
+ return reference_timestamp + (counter >> RTC_CNT_DIV) + ((counter & RTC_CNT_SUBSECOND_MASK) >> (RTC_CNT_DIV - 1)) - 1;
}
rtc_counter_t watch_rtc_get_counter(void) {