From e435969b5149bd2d692adbed8713e1c72640fe70 Mon Sep 17 00:00:00 2001 From: Joey Castillo Date: Sun, 20 Apr 2025 13:47:04 -0400 Subject: [PATCH] WIP: accelerometer activity tracking --- movement.c | 20 +++++++++++++------ movement_activity.c | 8 ++++---- movement_activity.h | 2 +- movement_config.h | 3 ++- watch-faces/demo/accel_interrupt_count_face.c | 12 +++++------ watch-faces/sensor/activity_logging_face.c | 4 ++-- .../hardware/watch/watch_deepsleep.c | 7 ++++++- watch-library/shared/driver/lis2dw.c | 4 ++++ watch-library/shared/driver/lis2dw.h | 2 ++ 9 files changed, 41 insertions(+), 21 deletions(-) diff --git a/movement.c b/movement.c index 21250ddf..dc96e284 100644 --- a/movement.c +++ b/movement.c @@ -78,6 +78,7 @@ void cb_tick(void); void cb_accelerometer_event(void); void cb_accelerometer_wake(void); uint8_t stationary_minutes = 0; +uint8_t active_minutes = 0; #endif #if __EMSCRIPTEN__ @@ -155,23 +156,30 @@ static inline void _movement_disable_fast_tick_if_possible(void) { static void _movement_handle_top_of_minute(void) { watch_date_time_t date_time = watch_rtc_get_date_time(); + static const uint8_t stationary_minutes_for_sleep = 2; #ifdef HAS_ACCELEROMETER - if (stationary_minutes < 5) { - // if the watch has been stationary for fewer than 5 minutes, find out if it's still stationary. - if (HAL_GPIO_A4_read()) stationary_minutes++; + bool accelerometer_is_alseep = HAL_GPIO_A4_read(); + if (!accelerometer_is_alseep) active_minutes++; + printf("Active minutes: %d\n", active_minutes); + + if (stationary_minutes < 2) { + // if the watch has been stationary for fewer minutes than the cutoff, find out if it's still stationary. + if (accelerometer_is_alseep) stationary_minutes++; printf("Stationary minutes: %d\n", stationary_minutes); - // does this mark five stationary minutes? and are we not already asleep? - if (stationary_minutes == 5 && movement_state.le_mode_ticks != -1) { + // should we go to sleep? and are we not already asleep? + if (stationary_minutes >= stationary_minutes_for_sleep && movement_state.le_mode_ticks != -1) { // if so, enter low energy mode. printf("Entering low energy mode due to inactivity.\n"); movement_request_sleep(); } } + // log data every five minutes, and reset the active_minutes count. if ((date_time.unit.minute % 5) == 0) { _movement_log_data(); + active_minutes = 0; } #endif @@ -718,7 +726,7 @@ void app_setup(void) { lis2dw_enable_stationary_motion_detection(); // stationary/motion detection mode keeps the data rate at 1.6 Hz even in sleep lis2dw_set_range(LIS2DW_RANGE_2_G); // Application note AN5038 recommends 2g range lis2dw_enable_sleep(); // allow acceleromter to sleep and wake on activity - lis2dw_configure_wakeup_threshold(8); // g threshold to wake up: (2 * FS / 64) where FS is "full scale" of ±2g. + lis2dw_configure_wakeup_threshold(32); // g threshold to wake up: (THS * FS / 64) where FS is "full scale" of ±2g. lis2dw_configure_6d_threshold(3); // 0-3 is 80, 70, 60, or 50 degrees. 50 is least precise, hopefully most sensitive? // set up interrupts: diff --git a/movement_activity.c b/movement_activity.c index ea2d9d6f..0b08c158 100644 --- a/movement_activity.c +++ b/movement_activity.c @@ -34,16 +34,16 @@ movement_activity_data_point movement_activity_log[MOVEMENT_NUM_DATA_POINTS] = { // the absolute number of data points logged uint32_t data_points = 0; -// hacky: we're just tapping into Movement's global state for stationary detection. +// hacky: we're just tapping into Movement's global state for activity detection. // do we need better API for this? i'm less bothered now that it's all in Movement. -extern uint8_t stationary_minutes; +extern uint8_t active_minutes; void _movement_log_data(void) { size_t pos = data_points % MOVEMENT_NUM_DATA_POINTS; movement_activity_data_point data_point = {0}; - // Movement tracks stationary minutes when deciding whether to sleep. - data_point.bit.stationary_minutes = stationary_minutes; + // Movement tracks active minutes when deciding whether to sleep. + data_point.bit.active_minutes = active_minutes; // orientation changes are counted in TC2. stash them in the data point... data_point.bit.orientation_changes = tc_count16_get_count(2); diff --git a/movement_activity.h b/movement_activity.h index 68f0f2ee..358e6626 100644 --- a/movement_activity.h +++ b/movement_activity.h @@ -33,7 +33,7 @@ typedef union { struct { - uint32_t stationary_minutes: 3; + uint32_t active_minutes: 3; uint32_t orientation_changes: 9; uint32_t measured_temperature: 10; uint32_t measured_light: 10; diff --git a/movement_config.h b/movement_config.h index b7384238..2645af5e 100644 --- a/movement_config.h +++ b/movement_config.h @@ -37,6 +37,7 @@ const watch_face_t watch_faces[] = { activity_logging_face, voltage_face, days_since_face, + accel_interrupt_count_face, preferences_face, set_time_face, }; @@ -49,7 +50,7 @@ const watch_face_t watch_faces[] = { * Some folks also like to use this to hide the preferences and time set faces from the normal rotation. * If you don't want any faces to be excluded, set this to 0 and a long Mode press will have no effect. */ -#define MOVEMENT_SECONDARY_FACE_INDEX (MOVEMENT_NUM_FACES - 4) +#define MOVEMENT_SECONDARY_FACE_INDEX (MOVEMENT_NUM_FACES - 5) /* Custom hourly chime tune. Check movement_custom_signal_tunes.h for options. */ #define SIGNAL_TUNE_DEFAULT diff --git a/watch-faces/demo/accel_interrupt_count_face.c b/watch-faces/demo/accel_interrupt_count_face.c index 53e1273c..c357191a 100644 --- a/watch-faces/demo/accel_interrupt_count_face.c +++ b/watch-faces/demo/accel_interrupt_count_face.c @@ -33,7 +33,7 @@ // hacky: we're just tapping into Movement's global state. // we should make better API for this. -extern uint8_t stationary_minutes; +extern uint8_t active_minutes; static void _accel_interrupt_count_face_update_display(accel_interrupt_count_state_t *state) { (void) state; @@ -48,7 +48,7 @@ static void _accel_interrupt_count_face_update_display(accel_interrupt_count_sta // Orientation changes / active minutes uint16_t orientation_changes = tc_count16_get_count(2); - sprintf(buf, "%-3u/%2d", orientation_changes > 999 ? 999 : orientation_changes, stationary_minutes); + sprintf(buf, "%-3u/%2d", orientation_changes > 999 ? 999 : orientation_changes, active_minutes); watch_display_text(WATCH_POSITION_BOTTOM, buf); } @@ -57,9 +57,6 @@ void accel_interrupt_count_face_setup(uint8_t watch_face_index, void ** context_ if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(accel_interrupt_count_state_t)); memset(*context_ptr, 0, sizeof(accel_interrupt_count_state_t)); - accel_interrupt_count_state_t *state = (accel_interrupt_count_state_t *)*context_ptr; - /// TODO: hook up to movement methods for tracking threshold - state->threshold = 8; } } @@ -71,6 +68,9 @@ void accel_interrupt_count_face_activate(void *context) { // update more quickly to catch changes, also to blink setting movement_request_tick_frequency(4); + + // fetch current threshold from accelerometer + state->threshold = lis2dw_get_wakeup_threshold(); } bool accel_interrupt_count_face_loop(movement_event_t event, void *context) { @@ -88,7 +88,7 @@ bool accel_interrupt_count_face_loop(movement_event_t event, void *context) { watch_display_text(WATCH_POSITION_BOTTOM, " "); } else { watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); - watch_display_text_with_fallback(WATCH_POSITION_TOP, "W_THS", "TH"); + watch_display_text_with_fallback(WATCH_POSITION_TOP, "WAKth", "TH"); watch_display_float_with_best_effort(state->new_threshold * 0.03125, " G"); printf("%s\n", buf); } diff --git a/watch-faces/sensor/activity_logging_face.c b/watch-faces/sensor/activity_logging_face.c index b662f80c..d540c323 100644 --- a/watch-faces/sensor/activity_logging_face.c +++ b/watch-faces/sensor/activity_logging_face.c @@ -76,7 +76,7 @@ static void _activity_logging_face_update_display(activity_logging_state_t *stat watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "LOG", "AC"); sprintf(buf, "%2d", state->display_index); watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); - sprintf(buf, "%-3u/%2d", data_points[pos].bit.orientation_changes > 999 ? 999 : data_points[pos].bit.orientation_changes, data_points[pos].bit.stationary_minutes); + sprintf(buf, "%-3u/%2d", data_points[pos].bit.orientation_changes > 999 ? 999 : data_points[pos].bit.orientation_changes, data_points[pos].bit.active_minutes); watch_display_text(WATCH_POSITION_BOTTOM, buf); } } @@ -140,7 +140,7 @@ bool activity_logging_face_loop(movement_event_t event, void *context) { sprintf(buf, "%3d%3d", data_points[pos].bit.measured_temperature - 300, data_points[pos].bit.orientation_changes > 999 ? 999 : data_points[pos].bit.orientation_changes); buf[6] = 0; watch_display_text(WATCH_POSITION_BOTTOM, buf); - sprintf(buf, "%2d", data_points[pos].bit.stationary_minutes); + sprintf(buf, "%2d", data_points[pos].bit.active_minutes); watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); state->data_dump_idx++; diff --git a/watch-library/hardware/watch/watch_deepsleep.c b/watch-library/hardware/watch/watch_deepsleep.c index 94d65488..93312bb2 100644 --- a/watch-library/hardware/watch/watch_deepsleep.c +++ b/watch-library/hardware/watch/watch_deepsleep.c @@ -129,6 +129,8 @@ static void _watch_disable_all_pins_except_rtc(void) { uint32_t config = RTC->MODE0.TAMPCTRL.reg; uint32_t portb_pins_to_disable = 0xFFFFFFFF; + /// FIXME: Watch library shouldn't be responsible for this, but ovement uses PB03 for orientation tracking. Keep it on. + portb_pins_to_disable &= 0xFFFFFFF7; // if there's an action set on RTC/IN[0], leave PB00 configured if (config & RTC_TAMPCTRL_IN0ACT_Msk) portb_pins_to_disable &= 0xFFFFFFFE; // same with RTC/IN[1] and PB02 @@ -150,7 +152,10 @@ static void _watch_disable_all_pins_except_rtc(void) { static void _watch_disable_all_peripherals_except_slcd(void) { _watch_disable_tcc(); watch_disable_adc(); - watch_disable_external_interrupts(); + /// FIXME: I just disabled this next line since we need the EIC's event system connection to count orientation changes. + // The TODO item: need to power profile the impact of keeping EIC enabled, as well as the UI implications. + // watch_disable_external_interrupts(); + /// TODO: Actually disable all these peripherals! #SecondMovement // watch_disable_i2c(); // SERCOM3->USART.CTRLA.reg &= ~SERCOM_USART_CTRLA_ENABLE; diff --git a/watch-library/shared/driver/lis2dw.c b/watch-library/shared/driver/lis2dw.c index 3db111b4..e7ccf2d2 100644 --- a/watch-library/shared/driver/lis2dw.c +++ b/watch-library/shared/driver/lis2dw.c @@ -288,3 +288,7 @@ lis2dw_wakeup_source_t lis2dw_get_wakeup_source() { lis2dw_interrupt_source_t lis2dw_get_interrupt_source(void) { return (lis2dw_interrupt_source_t) watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_ALL_INT_SRC); } + +uint8_t lis2dw_get_wakeup_threshold(void) { + return watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_WAKE_UP_THS) & 0b00111111; +} diff --git a/watch-library/shared/driver/lis2dw.h b/watch-library/shared/driver/lis2dw.h index 150ded1b..57ff05b2 100644 --- a/watch-library/shared/driver/lis2dw.h +++ b/watch-library/shared/driver/lis2dw.h @@ -375,4 +375,6 @@ lis2dw_interrupt_source_t lis2dw_get_interrupt_source(void); lis2dw_wakeup_source_t lis2dw_get_wakeup_source(void); +uint8_t lis2dw_get_wakeup_threshold(void); + #endif // LIS2DW_H