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();
#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;
/// 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);
#define CD_SELECTIONS 3
#define DEFAULT_MINUTES 3
+#define TAP_DETECTION_SECONDS 5
static bool quick_ticks_running;
}
}
+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;
}
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) {
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) {
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:
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);
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();
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;
state->mode = cd_reset;
store_countdown(state);
}
+
+ // return accelerometer to the state it was in before
+ abort_tap_detection(state);
}
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;
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);
}
#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
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);