diff --git a/movement.c b/movement.c index aa08cadc..8826ff01 100644 --- a/movement.c +++ b/movement.c @@ -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; diff --git a/movement.h b/movement.h index bc957427..55a3b391 100644 --- a/movement.h +++ b/movement.h @@ -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); diff --git a/watch-faces/complication/countdown_face.c b/watch-faces/complication/countdown_face.c index 4de05920..1ab819fd 100644 --- a/watch-faces/complication/countdown_face.c +++ b/watch-faces/complication/countdown_face.c @@ -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); } diff --git a/watch-faces/complication/countdown_face.h b/watch-faces/complication/countdown_face.h index dc8b1bfc..8759ee7f 100644 --- a/watch-faces/complication/countdown_face.h +++ b/watch-faces/complication/countdown_face.h @@ -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; diff --git a/watch-library/shared/driver/lis2dw.c b/watch-library/shared/driver/lis2dw.c index d4c56070..fa12bd51 100644 --- a/watch-library/shared/driver/lis2dw.c +++ b/watch-library/shared/driver/lis2dw.c @@ -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); } diff --git a/watch-library/shared/driver/lis2dw.h b/watch-library/shared/driver/lis2dw.h index e4d7544d..150ded1b 100644 --- a/watch-library/shared/driver/lis2dw.h +++ b/watch-library/shared/driver/lis2dw.h @@ -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);