]> git.earman.xyz Git - sensor-watch.git/commitdiff
Fix more corner case that could cause the top of minute alarm to stop firing
authorAlessandro Genova <ales.genova@gmail.com>
Fri, 8 Aug 2025 13:19:14 +0000 (09:19 -0400)
committerAlessandro Genova <ales.genova@gmail.com>
Sat, 3 Jan 2026 01:02:36 +0000 (20:02 -0500)
movement.c
watch-library/hardware/watch/watch_rtc.c
watch-library/shared/watch/watch_rtc.h
watch-library/simulator/watch/watch_rtc.c

index 4f49fb8a241e05ad1cd4deab60a61530ad46654e..7e4a23bf016a59951077a0896469e5cbea09562f 100644 (file)
@@ -220,7 +220,7 @@ static inline void _movement_reset_inactivity_countdown(void) {
     rtc_counter_t counter = watch_rtc_get_counter();
     uint32_t freq = watch_rtc_get_frequency();
 
-    watch_rtc_register_comp_callback(
+    watch_rtc_register_comp_callback_no_schedule(
         cb_resign_timeout_interrupt,
         counter + movement_timeout_inactivity_deadlines[movement_state.settings.bit.to_interval] * freq,
         RESIGN_TIMEOUT
@@ -228,16 +228,19 @@ static inline void _movement_reset_inactivity_countdown(void) {
 
     movement_volatile_state.enter_sleep_mode = false;
 
-    watch_rtc_register_comp_callback(
+    watch_rtc_register_comp_callback_no_schedule(
         cb_sleep_timeout_interrupt,
         counter + movement_le_inactivity_deadlines[movement_state.settings.bit.le_interval] * freq,
         SLEEP_TIMEOUT
     );
+
+    watch_rtc_schedule_next_comp();
 }
 
 static inline void _movement_disable_inactivity_countdown(void) {
-    watch_rtc_disable_comp_callback(RESIGN_TIMEOUT);
-    watch_rtc_disable_comp_callback(SLEEP_TIMEOUT);
+    watch_rtc_disable_comp_callback_no_schedule(RESIGN_TIMEOUT);
+    watch_rtc_disable_comp_callback_no_schedule(SLEEP_TIMEOUT);
+    watch_rtc_schedule_next_comp();
 }
 
 static void _movement_handle_top_of_minute(void) {
@@ -1194,9 +1197,11 @@ bool app_loop(void) {
 }
 
 static movement_event_type_t _process_button_event(bool pin_level, movement_button_t* button) {
+    movement_event_type_t event_type = EVENT_NONE;
+
     // This shouldn't happen normally
     if (pin_level == button->is_down) {
-        return EVENT_NONE;
+        return event_type;
     }
 
     uint32_t counter = watch_rtc_get_counter();
@@ -1206,43 +1211,42 @@ static movement_event_type_t _process_button_event(bool pin_level, movement_butt
     if (pin_level) {
         // We schedule a timeout to fire the longpress event
         button->down_timestamp = counter;
-        watch_rtc_register_comp_callback(button->cb_longpress, counter + MOVEMENT_LONG_PRESS_TICKS, button->timeout_index);
+        watch_rtc_register_comp_callback_no_schedule(button->cb_longpress, counter + MOVEMENT_LONG_PRESS_TICKS, button->timeout_index);
         // force alarm off if the user pressed a button.
         watch_buzzer_abort_sequence();
-        return button->down_event;
+        event_type = button->down_event;
     } else {
         // We cancel the timeout if it hasn't fired yet
-        watch_rtc_disable_comp_callback(button->timeout_index);
+        watch_rtc_disable_comp_callback_no_schedule(button->timeout_index);
         if ((counter - button->down_timestamp) >= MOVEMENT_LONG_PRESS_TICKS) {
-            return button->down_event + 3;
+            event_type = button->down_event + 3;
         } else {
-            return button->down_event + 1;
+            event_type = button->down_event + 1;
         }
     }
+
+    // This will also schedule the comp callbacks above
+    _movement_reset_inactivity_countdown();
+
+    return event_type;
 }
 
 void cb_light_btn_interrupt(void) {
     bool pin_level = HAL_GPIO_BTN_LIGHT_read();
 
     movement_volatile_state.pending_events |= 1 << _process_button_event(pin_level, &movement_volatile_state.light_button);
-
-    _movement_reset_inactivity_countdown();
 }
 
 void cb_mode_btn_interrupt(void) {
     bool pin_level = HAL_GPIO_BTN_MODE_read();
 
     movement_volatile_state.pending_events |= 1 << _process_button_event(pin_level, &movement_volatile_state.mode_button);
-
-    _movement_reset_inactivity_countdown();
 }
 
 void cb_alarm_btn_interrupt(void) {
     bool pin_level = HAL_GPIO_BTN_ALARM_read();
 
     movement_volatile_state.pending_events |= 1 << _process_button_event(pin_level, &movement_volatile_state.alarm_button);
-
-    _movement_reset_inactivity_countdown();
 }
 
 static movement_event_type_t _process_button_longpress_timeout(movement_button_t* button) {
index 1fee9917f7483d680bc8babaf76e529d3966573a..8d5255ad33992f2bfe889b9e740dc6c4954b007f 100644 (file)
@@ -198,7 +198,7 @@ void watch_rtc_disable_all_periodic_callbacks(void) {
     watch_rtc_disable_matching_periodic_callbacks(0xFF);
 }
 
-static void _watch_rtc_schedule_next_comp(void) {
+void watch_rtc_schedule_next_comp(void) {
     rtc_counter_t curr_counter = watch_rtc_get_counter();
     // If there is already a pending comp interrupt for this very tick, let it fire
     // And this function will be called again as soon as the interrupt fires.
@@ -245,7 +245,17 @@ void watch_rtc_register_comp_callback(watch_cb_t callback, rtc_counter_t counter
     comp_callbacks[index].callback = callback;
     comp_callbacks[index].enabled = true;
 
-    _watch_rtc_schedule_next_comp();
+    watch_rtc_schedule_next_comp();
+}
+
+void watch_rtc_register_comp_callback_no_schedule(watch_cb_t callback, rtc_counter_t counter, uint8_t index) {
+    if (index >= WATCH_RTC_N_COMP_CB) {
+        return;
+    }
+
+    comp_callbacks[index].counter = counter;
+    comp_callbacks[index].callback = callback;
+    comp_callbacks[index].enabled = true;
 }
 
 void watch_rtc_disable_comp_callback(uint8_t index) {
@@ -255,13 +265,21 @@ void watch_rtc_disable_comp_callback(uint8_t index) {
 
     comp_callbacks[index].enabled = false;
 
-    _watch_rtc_schedule_next_comp();
+    watch_rtc_schedule_next_comp();
+}
+
+void watch_rtc_disable_comp_callback_no_schedule(uint8_t index) {
+    if (index >= WATCH_RTC_N_COMP_CB) {
+        return;
+    }
+
+    comp_callbacks[index].enabled = false;
 }
 
 void watch_rtc_callback(uint16_t interrupt_cause) {
     // First read all relevant registers, to ensure no changes occurr during the callbacks
     uint16_t interrupt_enabled = RTC->MODE0.INTENSET.reg;
-    rtc_counter_t comp_counter = RTC->MODE0.COMP[0].reg;
+    rtc_counter_t counter = watch_rtc_get_counter();
 
 
     if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_INTFLAG_PER_Msk) {
@@ -291,15 +309,18 @@ void watch_rtc_callback(uint16_t interrupt_cause) {
 
     if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_INTFLAG_CMP0) {
         // The comp interrupt is generated one tick after the matched counter
-        // rtc_counter_t comp_counter = watch_rtc_get_counter() - 1;
+        rtc_counter_t comp_counter = counter - 1;
 
         for (uint8_t index = 0; index < WATCH_RTC_N_COMP_CB; ++index) {
-            if (comp_callbacks[index].enabled && comp_counter == comp_callbacks[index].counter) {
+            // Give it a little bit of wiggle room, if a comp callback is enabled and is just passed
+            if (comp_callbacks[index].enabled &&
+                (comp_counter - comp_callbacks[index].counter) <= RTC_CNT_HZ
+            ) {
                 comp_callbacks[index].enabled = false;
                 comp_callbacks[index].callback();
             }
         }
-        _watch_rtc_schedule_next_comp();
+        watch_rtc_schedule_next_comp();
     }
 
     if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_INTFLAG_OVF) {
index e268ad28c699a64b9833b55e09cb60ca7715f9e7..504c37f33ef67188759482b7dec76ba4b7c5d7af 100644 (file)
@@ -114,10 +114,38 @@ uint32_t watch_rtc_get_ticks_per_minute(void);
   */
 void watch_rtc_register_comp_callback(watch_cb_t callback, rtc_counter_t counter, uint8_t index);
 
+/** @brief Just like watch_rtc_register_comp_callback but doesn't actually schedule the callback
+  *
+  *  Useful if you need register multiple callbacks at once, avoids multiple calls to the expensive watch_rtc_schedule_next_comp:
+  *  Usage:
+  *  watch_rtc_register_comp_callback_no_schedule(cb0, counter0, index0);
+  *  watch_rtc_register_comp_callback_no_schedule(cb1, counter1, index1);
+  *  watch_rtc_schedule_next_comp();
+  */
+void watch_rtc_register_comp_callback_no_schedule(watch_cb_t callback, rtc_counter_t counter, uint8_t index);
+
 /** @brief Disables the specified comp callback.
   */
 void watch_rtc_disable_comp_callback(uint8_t index);
 
+/** @brief Just like watch_rtc_disable_comp_callback but doesn't actually schedule the callback
+  *
+  *  Useful if you need disable multiple callbacks at once, avoids multiple calls to the expensive watch_rtc_schedule_next_comp:
+  *  Usage:
+  *  watch_rtc_disable_comp_callback_no_schedule(index0);
+  *  watch_rtc_disable_comp_callback_no_schedule(index1);
+  *  watch_rtc_schedule_next_comp();
+  */
+/** @brief Disables the specified comp callback.
+  */
+void watch_rtc_disable_comp_callback_no_schedule(uint8_t index);
+
+/** @brief Determines the first comp callback that should fire and schedule it with the RTC
+  *
+  * You would never need to call this manually, unless you used the 'no_schedule' functions above.
+  */
+void watch_rtc_schedule_next_comp(void);
+
 /** @brief Disables the alarm callback.
   */
 // void watch_rtc_disable_alarm_callback(void);
index d9cfb9f2a948fc55c3e3c766263ae2152d21f9bd..1347d797f662ec567a607b8aeec70ae9bd6589fc 100644 (file)
@@ -65,7 +65,6 @@ watch_cb_t a4_callback;
 static void _watch_increase_counter(void *userData);
 static void _watch_process_periodic_callbacks(void);
 static void _watch_process_comp_callbacks(void);
-static void _watch_rtc_schedule_next_comp(void);
 
 bool _watch_rtc_is_enabled(void) {
     return counter_interval;
@@ -228,7 +227,7 @@ static void _watch_process_comp_callbacks(void) {
             }
         }
 
-        _watch_rtc_schedule_next_comp();
+        watch_rtc_schedule_next_comp();
     }
 }
 
@@ -272,7 +271,17 @@ void watch_rtc_register_comp_callback(watch_cb_t callback, rtc_counter_t counter
     comp_callbacks[index].callback = callback;
     comp_callbacks[index].enabled = true;
 
-    _watch_rtc_schedule_next_comp();
+    watch_rtc_schedule_next_comp();
+}
+
+void watch_rtc_register_comp_callback_no_schedule(watch_cb_t callback, rtc_counter_t counter, uint8_t index) {
+    if (index >= WATCH_RTC_N_COMP_CB) {
+        return;
+    }
+
+    comp_callbacks[index].counter = counter;
+    comp_callbacks[index].callback = callback;
+    comp_callbacks[index].enabled = true;
 }
 
 void watch_rtc_disable_comp_callback(uint8_t index) {
@@ -282,10 +291,18 @@ void watch_rtc_disable_comp_callback(uint8_t index) {
 
     comp_callbacks[index].enabled = false;
 
-    _watch_rtc_schedule_next_comp();
+    watch_rtc_schedule_next_comp();
+}
+
+void watch_rtc_disable_comp_callback_no_schedule(uint8_t index) {
+    if (index >= WATCH_RTC_N_COMP_CB) {
+        return;
+    }
+
+    comp_callbacks[index].enabled = false;
 }
 
-static void _watch_rtc_schedule_next_comp(void) {
+void watch_rtc_schedule_next_comp(void) {
     rtc_counter_t curr_counter = watch_rtc_get_counter();
     // If there is already a pending comp interrupt for this very tick, let it fire
     // And this function will be called again as soon as the interrupt fires.