diff --git a/movement.c b/movement.c index 917c2881..f0d08c2f 100644 --- a/movement.c +++ b/movement.c @@ -77,6 +77,8 @@ void cb_tick(void); #ifdef HAS_ACCELEROMETER void cb_motion_interrupt_1(void); +void cb_motion_interrupt_2(void); +uint32_t orientation_changes = 0; #endif #if __EMSCRIPTEN__ @@ -638,25 +640,16 @@ void app_setup(void) { // set up interrupts: // INT1 is on A4 which can wake from deep sleep. Wake on 6D orientation change. - lis2dw_configure_int1(LIS2DW_CTRL4_INT1_6D); - watch_register_interrupt_callback(HAL_GPIO_A4_pin(), cb_motion_interrupt_1, INTERRUPT_TRIGGER_FALLING); + lis2dw_configure_int1(LIS2DW_CTRL4_INT1_6D | LIS2DW_CTRL4_INT1_WU | LIS2DW_CTRL4_INT1_TAP | LIS2DW_CTRL4_INT1_SINGLE_TAP); + watch_register_extwake_callback(HAL_GPIO_A4_pin(), cb_motion_interrupt_1, true); - // configure the accelerometer to fire INT2 when its sleep state changes. + // configure the accelerometer to output the sleep state on INT2. lis2dw_configure_int2(LIS2DW_CTRL5_INT2_SLEEP_STATE | LIS2DW_CTRL5_INT2_SLEEP_CHG); // INT2 is wired to pin A3. set it up on the external interrupt controller. HAL_GPIO_A3_in(); HAL_GPIO_A3_pmuxen(HAL_GPIO_PMUX_EIC); - eic_configure_pin(HAL_GPIO_A3_pin(), INTERRUPT_TRIGGER_FALLING); - // but rather than firing an interrupt, we'll have it generate an event instead. - eic_enable_event(HAL_GPIO_A3_pin()); - // we can route the EXTINT3 event generator to the TC2 event user... - evsys_configure_channel(0, EVSYS_ID_GEN_EIC_EXTINT_3, EVSYS_ID_USER_TC2_EVU, true, true); - // and use the TC2 event to count the number of times the sleep state changes. - // note that this doesn't actually wake the watch — we can maintain this count even in standby. - tc_init(2, GENERIC_CLOCK_3, TC_PRESCALER_DIV1); - tc_set_event_action(2, TC_EVENT_ACTION_COUNT); - tc_set_counter_mode(2, TC_COUNTER_MODE_16BIT); - tc_enable(2); + eic_configure_pin(HAL_GPIO_A3_pin(), INTERRUPT_TRIGGER_BOTH); + watch_register_interrupt_callback(HAL_GPIO_A3_pin(), cb_motion_interrupt_2, INTERRUPT_TRIGGER_BOTH); lis2dw_enable_interrupts(); } @@ -698,6 +691,17 @@ static void _sleep_mode_app_loop(void) { bool app_loop(void) { const watch_face_t *wf = &watch_faces[movement_state.current_face_idx]; bool woke_up_for_buzzer = false; + + // REMOVE THIS before shipping the accelerometer board: test beeps. + if (movement_state.settings.bit.button_should_sound && event.event_type == EVENT_ACCELEROMETER_WAKE) { + watch_buzzer_play_note_with_volume(BUZZER_NOTE_C6, 20, WATCH_BUZZER_VOLUME_SOFT); + } + if (movement_state.settings.bit.button_should_sound && event.event_type == EVENT_ACCELEROMETER_SLEEP) { + watch_buzzer_play_note_with_volume(BUZZER_NOTE_C5, 15, WATCH_BUZZER_VOLUME_SOFT); + watch_buzzer_play_note_with_volume(BUZZER_NOTE_REST, 10, WATCH_BUZZER_VOLUME_SOFT); + watch_buzzer_play_note_with_volume(BUZZER_NOTE_C5, 15, WATCH_BUZZER_VOLUME_SOFT); + } + if (movement_state.watch_face_changed) { if (movement_state.settings.bit.button_should_sound) { // low note for nonzero case, high note for return to watch_face 0 @@ -928,7 +932,35 @@ void cb_tick(void) { #ifdef HAS_ACCELEROMETER void cb_motion_interrupt_1(void) { - // TODO: Find out what motion event woke us up. - printf("INT1\n"); + uint8_t int_src = lis2dw_get_interrupt_source(); + if (int_src & LIS2DW_REG_ALL_INT_SRC_6D_IA) { + event.event_type = EVENT_ORIENTATION_CHANGE; + orientation_changes++; + } + 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_FF_IA) event.event_type = EVENT_FREE_FALL; + + // These are handled on INT2, which is not available in low energy mode. + // If we want wakeup events on INT1, we could ask for LIS2DW_CTRL4_INT1_WU and get wake events here. + // If we want sleep change events on INT1, we'd have to set LIS2DW_CTRL5_INT2_SLEEP_CHG and LIS2DW_CTRL7_VAL_INT2_ON_INT1 + // That would give us these two cases: + // if (int_src & LIS2DW_REG_ALL_INT_SRC_WU_IA) printf(" Wake up"); + // if (int_src & LIS2DW_REG_ALL_INT_SRC_SLEEP_CHANGE_IA) printf(" Sleep change"); +} + +void cb_motion_interrupt_2(void) { + if (HAL_GPIO_A3_read()) { + event.event_type = EVENT_ACCELEROMETER_SLEEP; + printf("Sleep on INT2\n"); + } else { + event.event_type = EVENT_ACCELEROMETER_WAKE; + printf("Wake on INT2\n"); + // Not sure if it's useful to know what axis exceeded the threshold, but here's that: + // uint8_t int_src = lis2dw_get_wakeup_source(); + // if (int_src & LIS2DW_WAKE_UP_SRC_VAL_X_WU) printf("Wake on X"); + // if (int_src & LIS2DW_WAKE_UP_SRC_VAL_Y_WU) printf("Wake on Y"); + // if (int_src & LIS2DW_WAKE_UP_SRC_VAL_Z_WU) printf("Wake on Z"); + } } #endif diff --git a/movement.h b/movement.h index 879ec3b7..9e76dc47 100644 --- a/movement.h +++ b/movement.h @@ -143,6 +143,13 @@ typedef enum { EVENT_ALARM_BUTTON_UP, // The alarm button was pressed for less than half a second, and released. EVENT_ALARM_LONG_PRESS, // The alarm button was held for over half a second, but not yet released. EVENT_ALARM_LONG_UP, // The alarm button was held for over half a second, and released. + + EVENT_ACCELEROMETER_WAKE, // The accelerometer has detected motion and woken up. + EVENT_ACCELEROMETER_SLEEP, // The accelerometer has returned to sleep. + EVENT_ORIENTATION_CHANGE, // The orientation of the watch has changed. Available in low energy mode. + EVENT_SINGLE_TAP, // Accelerometer detected a single tap. This event is not yet implemented. + EVENT_DOUBLE_TAP, // Accelerometer detected a double tap. This event is not yet implemented. + EVENT_FREE_FALL, // Accelerometer detected the watch in free fall. This event is not yet implemented. } movement_event_type_t; typedef struct { diff --git a/watch-faces/demo/accel_interrupt_count_face.c b/watch-faces/demo/accel_interrupt_count_face.c index 5445a85a..3417282c 100644 --- a/watch-faces/demo/accel_interrupt_count_face.c +++ b/watch-faces/demo/accel_interrupt_count_face.c @@ -31,6 +31,10 @@ #ifdef HAS_ACCELEROMETER +// hacky: we're just tapping into Movement's orientation changes. +// we should make better API for this. +extern uint32_t orientation_changes; + static void _accel_interrupt_count_face_update_display(accel_interrupt_count_state_t *state) { (void) state; char buf[7]; @@ -38,8 +42,7 @@ static void _accel_interrupt_count_face_update_display(accel_interrupt_count_sta // "AC"celerometer "IN"terrupts watch_display_text(WATCH_POSITION_TOP_LEFT, "AC"); watch_display_text(WATCH_POSITION_TOP_RIGHT, "1N"); - uint16_t count = tc_count16_get_count(2); - snprintf(buf, 7, "%6d", count); + snprintf(buf, 7, "%6lu", orientation_changes); watch_display_text(WATCH_POSITION_BOTTOM, buf); printf("%s\n", buf); } @@ -90,10 +93,6 @@ bool accel_interrupt_count_face_loop(movement_event_t event, void *context) { } } else { switch (event.event_type) { - case EVENT_ALARM_BUTTON_UP: - // reset the counter - tc_count16_set_count(2, 0); - // fall through case EVENT_ACTIVATE: case EVENT_TICK: _accel_interrupt_count_face_update_display(state); diff --git a/watch-faces/sensor/activity_logging_face.c b/watch-faces/sensor/activity_logging_face.c index b0411c2e..a4fcab7f 100644 --- a/watch-faces/sensor/activity_logging_face.c +++ b/watch-faces/sensor/activity_logging_face.c @@ -29,20 +29,26 @@ #ifdef HAS_ACCELEROMETER +// hacky: we're just tapping into Movement's orientation changes. +// we should make better API for this. +extern uint32_t orientation_changes; + static void _activity_logging_face_log_data(activity_logging_state_t *state) { watch_date_time_t date_time = watch_rtc_get_date_time(); size_t pos = state->data_points % ACTIVITY_LOGGING_NUM_DATA_POINTS; state->data[pos].timestamp.reg = date_time.reg; state->data[pos].active_minutes = state->active_minutes; + state->data[pos].orientation_changes = orientation_changes; state->active_minutes = 0; + orientation_changes = 0; state->data_points++; } static void _activity_logging_face_update_display(activity_logging_state_t *state, bool clock_mode_24h) { int8_t pos = (state->data_points - 1 - state->display_index) % ACTIVITY_LOGGING_NUM_DATA_POINTS; - char buf[7]; + char buf[16]; watch_clear_indicator(WATCH_INDICATOR_24H); watch_clear_indicator(WATCH_INDICATOR_PM); @@ -71,11 +77,11 @@ static void _activity_logging_face_update_display(activity_logging_state_t *stat sprintf(buf, "%2d%02d%02d", date_time.unit.hour, date_time.unit.minute, date_time.unit.second); watch_display_text(WATCH_POSITION_BOTTOM, buf); } else { - // we are displaying the number of accelerometer wakeups - watch_display_text_with_fallback(WATCH_POSITION_TOP, "ACT L", "AC"); + // we are displaying the number of accelerometer wakeups and orientation changes + watch_display_text(WATCH_POSITION_TOP, "WO"); sprintf(buf, "%2d", state->display_index); watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); - sprintf(buf, "%d", state->data[pos].active_minutes); + sprintf(buf, "%2d=%lu", state->data[pos].active_minutes, state->data[pos].orientation_changes); watch_display_text(WATCH_POSITION_BOTTOM, buf); } } @@ -139,7 +145,7 @@ movement_watch_face_advisory_t activity_logging_face_advise(void *context) { activity_logging_state_t *state = (activity_logging_state_t *)context; movement_watch_face_advisory_t retval = { 0 }; - // every minute, we want to log whether the accelerometer is alseeep or awake. + // every minute, we want to log whether the accelerometer is asleep or awake. if (!HAL_GPIO_A3_read()) { state->active_minutes++; } diff --git a/watch-faces/sensor/activity_logging_face.h b/watch-faces/sensor/activity_logging_face.h index 2d73c4bd..39747a31 100644 --- a/watch-faces/sensor/activity_logging_face.h +++ b/watch-faces/sensor/activity_logging_face.h @@ -44,6 +44,7 @@ typedef struct { watch_date_time_t timestamp; uint8_t active_minutes; + uint32_t orientation_changes; } activity_logging_data_point_t; typedef struct { diff --git a/watch-library/hardware/watch/watch_deepsleep.c b/watch-library/hardware/watch/watch_deepsleep.c index 94d65488..1143850f 100644 --- a/watch-library/hardware/watch/watch_deepsleep.c +++ b/watch-library/hardware/watch/watch_deepsleep.c @@ -133,6 +133,10 @@ static void _watch_disable_all_pins_except_rtc(void) { if (config & RTC_TAMPCTRL_IN0ACT_Msk) portb_pins_to_disable &= 0xFFFFFFFE; // same with RTC/IN[1] and PB02 if (config & RTC_TAMPCTRL_IN1ACT_Msk) portb_pins_to_disable &= 0xFFFFFFFB; +#ifdef HAS_ACCELEROMETER + // if we're using the Motion board, keep A3 configured (it tracks the accelerometer sleep state) + portb_pins_to_disable &= 0xFFFFFFF7; +#endif // port A: that last B is to always keep PA02 configured as-is; that's our ALARM button. PORT->Group[0].DIRCLR.reg = 0xFFFFFFFB;