]> git.earman.xyz Git - sensor-watch.git/commitdiff
accelrometer: add tap event, test by adding tap to set countdown
authorJoey Castillo <joeycastillo@utexas.edu>
Wed, 27 Nov 2024 23:04:17 +0000 (18:04 -0500)
committerJoey Castillo <joeycastillo@utexas.edu>
Wed, 27 Nov 2024 23:04:17 +0000 (18:04 -0500)
movement.c
movement.h
watch-faces/complication/countdown_face.c
watch-faces/complication/countdown_face.h
watch-library/shared/driver/lis2dw.c
watch-library/shared/driver/lis2dw.h

index aa08cadcc759f0361b95713e93d35b4cf602af37..8826ff01c7356883492f190404493e33b3d499e3 100644 (file)
@@ -519,6 +519,47 @@ void movement_set_alarm_enabled(bool value) {
     movement_state.settings.bit.alarm_enabled = value;
 }
 
+void movement_enable_tap_detection_if_available(void) {
+#ifdef HAS_ACCELEROMETER
+    // disable event on INT1/A3 (normally tracks orientation changes)
+    eic_disable_event(HAL_GPIO_A3_pin());
+
+    // configure tap duration threshold and enable Z axis
+    lis2dw_configure_tap_threshold(0, 0, 12, LIS2DW_REG_TAP_THS_Z_Z_AXIS_ENABLE);
+    lis2dw_configure_tap_duration(10, 2, 2);
+
+    // ramp data rate up to 400 Hz and high performance mode
+    lis2dw_set_low_noise_mode(true);
+    lis2dw_set_data_rate(LIS2DW_DATA_RATE_HP_400_HZ);
+    lis2dw_set_mode(LIS2DW_MODE_HIGH_PERFORMANCE);
+
+    // Settling time (1 sample duration, i.e. 1/400Hz)
+    delay_ms(3);
+
+    // enable tap detection on INT1/A3.
+    lis2dw_configure_int1(LIS2DW_CTRL4_INT1_SINGLE_TAP | LIS2DW_CTRL4_INT1_6D);
+    // and enable the cb_accelerometer_event interrupt callback, so we can catch tap events.
+    watch_register_interrupt_callback(HAL_GPIO_A3_pin(), cb_accelerometer_event, INTERRUPT_TRIGGER_RISING);
+#endif
+}
+
+void movement_disable_tap_detection_if_available(void) {
+#ifdef HAS_ACCELEROMETER
+    // Ramp data rate back down to the usual lowest rate to save power.
+    lis2dw_set_low_noise_mode(false);
+    lis2dw_set_data_rate(LIS2DW_DATA_RATE_LOWEST);
+    lis2dw_set_mode(LIS2DW_MODE_LOW_POWER);
+    // disable the interrupt on INT1/A3...
+    eic_disable_interrupt(HAL_GPIO_A3_pin());
+    // ...disable Z axis (not sure if this is needed, does this save power?)...
+    lis2dw_configure_tap_threshold(0, 0, 0, 0);
+    // ...re-enable tracking of orientation changes...
+    lis2dw_configure_int1(LIS2DW_CTRL4_INT1_6D);
+    // ...and re-enable the event.
+    eic_enable_event(HAL_GPIO_A3_pin());
+#endif
+}
+
 void app_init(void) {
     _watch_init();
 
@@ -958,18 +999,19 @@ void cb_tick(void) {
 
 #ifdef HAS_ACCELEROMETER
 void cb_accelerometer_event(void) {
-    // This callback is not currently in use! Tap tracking will require using an interrupt on A3 instead of an event.
-    // I imagine watch faces will request tap tracking in a specific context, and we'll expose a Movement function
-    // that swaps out the mode for as long as the watch face needs taps. (the sampling rate required for tap tracking
-    // will to consume a lot more power, so we can't leave it on all the time.)
     uint8_t int_src = lis2dw_get_interrupt_source();
 
-    if (int_src & LIS2DW_REG_ALL_INT_SRC_DOUBLE_TAP) event.event_type = EVENT_DOUBLE_TAP;
-    if (int_src & LIS2DW_REG_ALL_INT_SRC_SINGLE_TAP) event.event_type = EVENT_SINGLE_TAP;
+    if (int_src & LIS2DW_REG_ALL_INT_SRC_DOUBLE_TAP) {
+        event.event_type = EVENT_DOUBLE_TAP;
+        printf("Double tap!\n");
+    }
+    if (int_src & LIS2DW_REG_ALL_INT_SRC_SINGLE_TAP) {
+        event.event_type = EVENT_SINGLE_TAP;
+        printf("Single tap!\n");
+    }
 }
 
 void cb_accelerometer_wake(void) {
-    printf("Woke up from accelerometer!\n");
     event.event_type = EVENT_ACCELEROMETER_WAKE;
     // reset the stationary minutes counter; we're counting consecutive stationary minutes.
     stationary_minutes = 0;
index bc95742736d5a953d35d98b83dfdd25bc5cd401a..55a3b39170023b628a4d7fad74f530cd493c0f06 100644 (file)
@@ -375,3 +375,7 @@ void movement_store_settings(void);
 /// Worth considering a better way to handle this.
 bool movement_alarm_enabled(void);
 void movement_set_alarm_enabled(bool value);
+
+// if the board has an accelerometer, these functions will enable or disable tap detection.
+void movement_enable_tap_detection_if_available(void);
+void movement_disable_tap_detection_if_available(void);
index 4de0592042aa72de3018184402b04cb537767e96..1ab819fd0a33bf7bfac9b122a42edae5f629ddb1 100644 (file)
@@ -32,6 +32,7 @@
 
 #define CD_SELECTIONS 3
 #define DEFAULT_MINUTES 3
+#define TAP_DETECTION_SECONDS 5
 
 static bool quick_ticks_running;
 
@@ -45,6 +46,11 @@ static void abort_quick_ticks(countdown_state_t *state) {
     }
 }
 
+static void abort_tap_detection(countdown_state_t *state) {
+    state->tap_detection_ticks = 0;
+    movement_disable_tap_detection_if_available();
+}
+
 static inline void store_countdown(countdown_state_t *state) {
     /* Store set countdown time */
     state->set_hours = state->hours;
@@ -132,7 +138,14 @@ static void draw(countdown_state_t *state, uint8_t subsecond) {
             }
             break;
     }
+
     watch_display_text(WATCH_POSITION_BOTTOM, buf);
+
+    if (state->tap_detection_ticks) {
+        watch_set_indicator(WATCH_INDICATOR_SIGNAL);
+    } else {
+        watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
+    }
 }
 
 static void pause(countdown_state_t *state) {
@@ -206,6 +219,13 @@ void countdown_face_activate(void *context) {
 
     movement_request_tick_frequency(1);
     quick_ticks_running = false;
+#if HAS_ACCELEROMETER
+    if (state->mode != cd_running) {
+        state->tap_detection_ticks = TAP_DETECTION_SECONDS;
+        state->has_tapped_once = false;
+        movement_enable_tap_detection_if_available();
+    }
+#endif
 }
 
 bool countdown_face_loop(movement_event_t event, void *context) {
@@ -227,6 +247,12 @@ bool countdown_face_loop(movement_event_t event, void *context) {
             if (state->mode == cd_running) {
                 state->now_ts++;
             }
+
+            if (state->tap_detection_ticks > 0) {
+                state->tap_detection_ticks--;
+                if (state->tap_detection_ticks == 0) movement_disable_tap_detection_if_available();
+            }
+
             draw(state, event.subsecond);
             break;
         case EVENT_MODE_BUTTON_UP:
@@ -264,8 +290,9 @@ bool countdown_face_loop(movement_event_t event, void *context) {
                     break;
                 case cd_reset:
                 case cd_paused:
+                    // Only start the timer if we have a valid time.
                     if (!(state->hours == 0 && state->minutes == 0 && state->seconds == 0)) {
-                        // Only start the timer if we have a valid time.
+                        abort_tap_detection(state);
                         start(state);
                         button_beep();
                         watch_set_indicator(WATCH_INDICATOR_SIGNAL);
@@ -281,6 +308,7 @@ bool countdown_face_loop(movement_event_t event, void *context) {
             switch(state->mode) {
                 case cd_reset:
                     // long press in reset mode enters settings
+                    abort_tap_detection(state);
                     state->mode = cd_setting;
                     movement_request_tick_frequency(4);
                     button_beep();
@@ -338,6 +366,21 @@ bool countdown_face_loop(movement_event_t event, void *context) {
         case EVENT_LIGHT_BUTTON_DOWN:
             // intentionally squelch the light default event; we only show the light when cd is running or reset
             break;
+        case EVENT_SINGLE_TAP:
+            if (state->has_tapped_once == false) {
+                // on first tap, set the countdown to 1 minute
+                state->has_tapped_once = true;
+                state->hours = 0;
+                state->minutes = 1;
+                state->seconds = 0;
+            } else {
+                // on subsequent taps, increment the countdown by 1 minute, up to 59 taps
+                state->minutes = state->minutes < 59 ? state->minutes + 1 : state->minutes;
+            }
+            // reset the tap detection timer
+            state->tap_detection_ticks = TAP_DETECTION_SECONDS;
+            draw(state, event.subsecond);
+            break;
         default:
             movement_default_loop_handler(event);
             break;
@@ -353,4 +396,7 @@ void countdown_face_resign(void *context) {
         state->mode = cd_reset;
         store_countdown(state);
     }
+
+    // return accelerometer to the state it was in before
+    abort_tap_detection(state);
 }
index dc8b1bfc79f48d7f76181f7d440a55ac224d7ba5..8759ee7f8ab2b6c4f47a23b78120127f2c9692ed 100644 (file)
@@ -61,6 +61,8 @@ typedef struct {
     uint8_t set_minutes;
     uint8_t set_seconds;
     uint8_t selection;
+    uint8_t tap_detection_ticks;
+    bool has_tapped_once;
     countdown_mode_t mode;
     bool repeat;
     uint8_t watch_face_index;
index d4c560707b3836cd9556dfb0b2842015164f4a1d..fa12bd5180576f616e6722478758632db7dd77db 100644 (file)
@@ -253,6 +253,24 @@ void lis2dw_configure_6d_threshold(uint8_t threshold) {
     watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_TAP_THS_X, configuration | ((threshold & 0b11) << 5));
 }
 
+void lis2dw_configure_tap_threshold(uint8_t threshold_x, uint8_t threshold_y, uint8_t threshold_z, uint8_t axes_to_enable) {
+    uint8_t configuration;
+    // if (axes_to_enable & LIS2DW_REG_TAP_THS_Z_X_AXIS_ENABLE);   // X axis tap not implemented
+    // if (axes_to_enable & LIS2DW_REG_TAP_THS_Z_Y_AXIS_ENABLE);   // Y axis tap not implemented
+    // tap enable bitmask is the high bits of LIS2DW_REG_TAP_THS_Z
+    configuration = axes_to_enable & 0b00100000; // NOTE: should be 0b11100000 to allow use of all three axes, but we're not using X or Y.
+    if (axes_to_enable & LIS2DW_REG_TAP_THS_Z_Z_AXIS_ENABLE) {
+        // mask out high bits if set
+        configuration |= (threshold_z & 0b00011111);
+    }
+    watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_TAP_THS_Z, configuration);
+}
+
+void lis2dw_configure_tap_duration(uint8_t latency, uint8_t quiet, uint8_t shock) {
+    uint8_t configuration = (latency << 4) | ((quiet & 0b11) << 2) | (shock & 0b11);
+    watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_INT1_DUR, configuration);
+}
+
 void lis2dw_configure_int1(uint8_t sources) {
     watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL4_INT1, sources);
 }
index e4d7544d138c35150cd9c5d624c0080ed112c510..150ded1b817aaea26549c373788bdcecf721b214 100644 (file)
@@ -227,8 +227,14 @@ typedef enum {
 #define LIS2DW_FIFO_SAMPLE_COUNT (0b00111111)
 
 #define LIS2DW_REG_TAP_THS_X 0x30
+
 #define LIS2DW_REG_TAP_THS_Y 0x31
+
 #define LIS2DW_REG_TAP_THS_Z 0x32
+#define LIS2DW_REG_TAP_THS_Z_X_AXIS_ENABLE 0b10000000
+#define LIS2DW_REG_TAP_THS_Z_Y_AXIS_ENABLE 0b01000000
+#define LIS2DW_REG_TAP_THS_Z_Z_AXIS_ENABLE 0b00100000
+
 #define LIS2DW_REG_INT1_DUR 0x33
 
 #define LIS2DW_REG_WAKE_UP_THS 0x34
@@ -353,6 +359,10 @@ void lis2dw_configure_wakeup_threshold(uint8_t threshold);
 
 void lis2dw_configure_6d_threshold(uint8_t threshold);
 
+void lis2dw_configure_tap_threshold(uint8_t threshold_x, uint8_t threshold_y, uint8_t threshold_z, uint8_t axes_to_enable);
+
+void lis2dw_configure_tap_duration(uint8_t latency, uint8_t quiet, uint8_t shock);
+
 void lis2dw_configure_int1(uint8_t sources);
 
 void lis2dw_configure_int2(uint8_t sources);