movement: more granular button and LED timing via fast tick
This commit is contained in:
		
							parent
							
								
									812c6c2612
								
							
						
					
					
						commit
						03fb09f5b4
					
				| @ -51,7 +51,7 @@ This function is called just before your watch enters the foreground. If your wa | |||||||
| 
 | 
 | ||||||
| ### watch_face_loop | ### watch_face_loop | ||||||
| 
 | 
 | ||||||
| This is a lot like your loop() function in Arduinoland in that it is called repeatedly whenever your watch face is on screen. There is one crucial difference though: it is called less often. By default, this function is called once per second, and in response to events like button presses. You can request a more frequent tick interval by calling `movement_request_tick_frequency` with any power of 2 from 1 to 128.  | This is a lot like your loop() function in Arduinoland in that it is called repeatedly whenever your watch face is on screen. There is one crucial difference though: it is called less often. By default, this function is called once per second, and in response to events like button presses. You can request a more frequent tick interval by calling `movement_request_tick_frequency` with any power of 2 from 1 to 64. (there is a 128 Hz prescaler tick, but Movement reserves that for its own use) | ||||||
| 
 | 
 | ||||||
| In addition to the settings and context, this function receives another parameter: an `event`. This is a struct containing information about the event that triggered the update. You mostly need to check the `event_type` to determine what kind of event triggered the loop. A detailed list of all events is provided at the bottom of this document.  | In addition to the settings and context, this function receives another parameter: an `event`. This is a struct containing information about the event that triggered the update. You mostly need to check the `event_type` to determine what kind of event triggered the loop. A detailed list of all events is provided at the bottom of this document.  | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -63,6 +63,7 @@ void cb_light_btn_interrupt(); | |||||||
| void cb_alarm_btn_interrupt(); | void cb_alarm_btn_interrupt(); | ||||||
| void cb_alarm_btn_extwake(); | void cb_alarm_btn_extwake(); | ||||||
| void cb_alarm_fired(); | void cb_alarm_fired(); | ||||||
|  | void cb_fast_tick(); | ||||||
| void cb_tick(); | void cb_tick(); | ||||||
| 
 | 
 | ||||||
| static inline void _movement_reset_inactivity_countdown() { | static inline void _movement_reset_inactivity_countdown() { | ||||||
| @ -70,6 +71,20 @@ static inline void _movement_reset_inactivity_countdown() { | |||||||
|     movement_state.timeout_ticks = movement_timeout_inactivity_deadlines[movement_state.settings.bit.to_interval]; |     movement_state.timeout_ticks = movement_timeout_inactivity_deadlines[movement_state.settings.bit.to_interval]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static inline void _movement_enable_fast_tick_if_needed() { | ||||||
|  |     if (!movement_state.fast_tick_enabled) { | ||||||
|  |         movement_state.fast_ticks = 0; | ||||||
|  |         watch_rtc_register_periodic_callback(cb_fast_tick, 128); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void _movement_disable_fast_tick_if_possible() { | ||||||
|  |     if ((movement_state.light_ticks == -1) && ((movement_state.light_down_timestamp + movement_state.mode_down_timestamp + movement_state.alarm_down_timestamp) == 0)) { | ||||||
|  |         movement_state.fast_tick_enabled = false; | ||||||
|  |         watch_rtc_disable_periodic_callback(128); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void _movement_handle_background_tasks() { | void _movement_handle_background_tasks() { | ||||||
|     for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) { |     for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) { | ||||||
|         // For each face, if the watch face wants a background task...
 |         // For each face, if the watch face wants a background task...
 | ||||||
| @ -83,17 +98,20 @@ void _movement_handle_background_tasks() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void movement_request_tick_frequency(uint8_t freq) { | void movement_request_tick_frequency(uint8_t freq) { | ||||||
|     watch_rtc_disable_all_periodic_callbacks(); |     if (freq == 128) return; // Movement uses the 128 Hz tick internally
 | ||||||
|  |     RTC->MODE2.INTENCLR.reg = 0xFE; // disable all callbacks except the 128 Hz one
 | ||||||
|     movement_state.subsecond = 0; |     movement_state.subsecond = 0; | ||||||
|     movement_state.tick_frequency = freq; |     movement_state.tick_frequency = freq; | ||||||
|     watch_rtc_register_periodic_callback(cb_tick, freq); |     watch_rtc_register_periodic_callback(cb_tick, freq); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void movement_illuminate_led() { | void movement_illuminate_led() { | ||||||
|  |     if (movement_state.settings.bit.led_duration) { | ||||||
|         watch_set_led_color(movement_state.settings.bit.led_red_color ? (0xF | movement_state.settings.bit.led_red_color << 4) : 0, |         watch_set_led_color(movement_state.settings.bit.led_red_color ? (0xF | movement_state.settings.bit.led_red_color << 4) : 0, | ||||||
|                             movement_state.settings.bit.led_green_color ? (0xF | movement_state.settings.bit.led_green_color << 4) : 0); |                             movement_state.settings.bit.led_green_color ? (0xF | movement_state.settings.bit.led_green_color << 4) : 0); | ||||||
|     movement_state.led_on = true; |         movement_state.light_ticks = (movement_state.settings.bit.led_duration * 2 - 1) * 128; | ||||||
|     movement_state.light_ticks = movement_state.settings.bit.led_duration * 2; |         _movement_enable_fast_tick_if_needed(); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void movement_move_to_face(uint8_t watch_face_index) { | void movement_move_to_face(uint8_t watch_face_index) { | ||||||
| @ -113,6 +131,7 @@ void app_init() { | |||||||
|     movement_state.settings.bit.le_interval = 1; |     movement_state.settings.bit.le_interval = 1; | ||||||
|     movement_state.settings.bit.led_duration = 1; |     movement_state.settings.bit.led_duration = 1; | ||||||
|     movement_state.settings.bit.time_zone = 16; // default to GMT
 |     movement_state.settings.bit.time_zone = 16; // default to GMT
 | ||||||
|  |     movement_state.light_ticks = -1; | ||||||
|     _movement_reset_inactivity_countdown(); |     _movement_reset_inactivity_countdown(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -182,14 +201,15 @@ bool app_loop() { | |||||||
|         movement_state.watch_face_changed = false; |         movement_state.watch_face_changed = false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // if the LED is on and should be off, turn it off
 |     // if the LED should be off, turn it off
 | ||||||
|     if (movement_state.led_on && movement_state.light_ticks == 0) { |     if (movement_state.light_ticks == 0) { | ||||||
|         // unless the user is holding down the LIGHT button, in which case, give them more time.
 |         // unless the user is holding down the LIGHT button, in which case, give them more time.
 | ||||||
|         if (watch_get_pin_level(BTN_LIGHT)) { |         if (watch_get_pin_level(BTN_LIGHT)) { | ||||||
|             movement_state.light_ticks = 3; |             movement_state.light_ticks = 1; | ||||||
|         } else { |         } else { | ||||||
|             watch_set_led_off(); |             watch_set_led_off(); | ||||||
|             movement_state.led_on = false; |             movement_state.light_ticks = -1; | ||||||
|  |             _movement_disable_fast_tick_if_possible(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -238,35 +258,45 @@ bool app_loop() { | |||||||
| 
 | 
 | ||||||
|     event.subsecond = 0; |     event.subsecond = 0; | ||||||
| 
 | 
 | ||||||
|     return can_sleep && !movement_state.led_on; |     return can_sleep && (movement_state.light_ticks == 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| movement_event_type_t _figure_out_button_event(movement_event_type_t button_down_event_type, uint8_t *down_timestamp) { | movement_event_type_t _figure_out_button_event(bool pin_level, movement_event_type_t button_down_event_type, uint8_t *down_timestamp) { | ||||||
|     watch_date_time date_time = watch_rtc_get_date_time(); |     if (pin_level) { | ||||||
|     if (*down_timestamp) { |         // handle rising edge
 | ||||||
|         uint8_t diff = ((61 + date_time.unit.second) - *down_timestamp) % 60; |         _movement_enable_fast_tick_if_needed(); | ||||||
|         *down_timestamp = 0; |         *down_timestamp = movement_state.fast_ticks + 1; | ||||||
|         if (diff > 1) return button_down_event_type + 2; |  | ||||||
|         else return button_down_event_type + 1; |  | ||||||
|     } else { |  | ||||||
|         *down_timestamp = date_time.unit.second + 1; |  | ||||||
|         return button_down_event_type; |         return button_down_event_type; | ||||||
|  |     } else { | ||||||
|  |         // this line is hack but it handles the situation where the light button was held for more than 10 seconds.
 | ||||||
|  |         // fast tick is disabled by then, and the LED would get stuck on since there's no one left decrementing light_ticks.
 | ||||||
|  |         if (movement_state.light_ticks == 1) movement_state.light_ticks = 0; | ||||||
|  |         // now that that's out of the way, handle falling edge
 | ||||||
|  |         uint16_t diff = movement_state.fast_ticks - *down_timestamp; | ||||||
|  |         *down_timestamp = 0; | ||||||
|  |         _movement_disable_fast_tick_if_possible(); | ||||||
|  |         // any press over a half second is considered a long press.
 | ||||||
|  |         if (diff > 64) return button_down_event_type + 2; | ||||||
|  |         else return button_down_event_type + 1; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cb_light_btn_interrupt() { | void cb_light_btn_interrupt() { | ||||||
|  |     bool pin_level = watch_get_pin_level(BTN_LIGHT); | ||||||
|     _movement_reset_inactivity_countdown(); |     _movement_reset_inactivity_countdown(); | ||||||
|     event.event_type = _figure_out_button_event(EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); |     event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cb_mode_btn_interrupt() { | void cb_mode_btn_interrupt() { | ||||||
|  |     bool pin_level = watch_get_pin_level(BTN_MODE); | ||||||
|     _movement_reset_inactivity_countdown(); |     _movement_reset_inactivity_countdown(); | ||||||
|     event.event_type = _figure_out_button_event(EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); |     event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cb_alarm_btn_interrupt() { | void cb_alarm_btn_interrupt() { | ||||||
|  |     bool pin_level = watch_get_pin_level(BTN_ALARM); | ||||||
|     _movement_reset_inactivity_countdown(); |     _movement_reset_inactivity_countdown(); | ||||||
|     event.event_type = _figure_out_button_event(EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); |     event.event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cb_alarm_btn_extwake() { | void cb_alarm_btn_extwake() { | ||||||
| @ -278,14 +308,18 @@ void cb_alarm_fired() { | |||||||
|     movement_state.needs_background_tasks_handled = true; |     movement_state.needs_background_tasks_handled = true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void cb_fast_tick() { | ||||||
|  |     movement_state.fast_ticks++; | ||||||
|  |     if (movement_state.light_ticks > 0) movement_state.light_ticks--; | ||||||
|  |     // this is just a fail-safe; fast tick should be disabled as soon as the button is up and/or the LED times out.
 | ||||||
|  |     // but if for whatever reason it isn't, this forces the fast tick off after 10 seconds.
 | ||||||
|  |     if (movement_state.fast_ticks >= 1280) watch_rtc_disable_periodic_callback(128); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void cb_tick() { | void cb_tick() { | ||||||
|     event.event_type = EVENT_TICK; |     event.event_type = EVENT_TICK; | ||||||
|     watch_date_time date_time = watch_rtc_get_date_time(); |     watch_date_time date_time = watch_rtc_get_date_time(); | ||||||
|     if (date_time.unit.second != movement_state.last_second) { |     if (date_time.unit.second != movement_state.last_second) { | ||||||
|         // TODO: since we time the LED with the 1 Hz tick, the actual time lit can vary depending on whether the
 |  | ||||||
|         // user hit it just before or just after a tick. If we time this with the system tick we can do better.
 |  | ||||||
|         if (movement_state.light_ticks) movement_state.light_ticks--; |  | ||||||
| 
 |  | ||||||
|         // TODO: can we consolidate these two ticks?
 |         // TODO: can we consolidate these two ticks?
 | ||||||
|         if (movement_state.settings.bit.le_interval && movement_state.le_mode_ticks > 0) movement_state.le_mode_ticks--; |         if (movement_state.settings.bit.le_interval && movement_state.le_mode_ticks > 0) movement_state.le_mode_ticks--; | ||||||
|         if (movement_state.timeout_ticks > 0) movement_state.timeout_ticks--; |         if (movement_state.timeout_ticks > 0) movement_state.timeout_ticks--; | ||||||
|  | |||||||
| @ -210,10 +210,11 @@ typedef struct { | |||||||
|     int16_t current_watch_face; |     int16_t current_watch_face; | ||||||
|     int16_t next_watch_face; |     int16_t next_watch_face; | ||||||
|     bool watch_face_changed; |     bool watch_face_changed; | ||||||
|  |     bool fast_tick_enabled; | ||||||
|  |     int16_t fast_ticks; | ||||||
| 
 | 
 | ||||||
|     // LED stuff
 |     // LED stuff
 | ||||||
|     uint8_t light_ticks; |     int16_t light_ticks; | ||||||
|     bool led_on; |  | ||||||
|      |      | ||||||
|     // button tracking for long press
 |     // button tracking for long press
 | ||||||
|     uint8_t light_down_timestamp; |     uint8_t light_down_timestamp; | ||||||
|  | |||||||
| @ -131,11 +131,7 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings | |||||||
|                 break; |                 break; | ||||||
|             case 4: |             case 4: | ||||||
|                 if (settings->bit.led_duration) { |                 if (settings->bit.led_duration) { | ||||||
|                     // TODO: since we time the LED with the 1 Hz tick, the actual time lit can vary depending
 |                     sprintf(buf, " %1d SeC", settings->bit.led_duration * 2 - 1); | ||||||
|                     // on whether the user hit it just before or just after a tick. so the setting is "1-2 s",
 |  | ||||||
|                     // "3-4 s", or "5-6 s". If we time this with the system tick we can do better.
 |  | ||||||
|                     // see also cb_tick at the bottom of movement.c
 |  | ||||||
|                     sprintf(buf, " %1d-%1d s", settings->bit.led_duration * 2 - 1, settings->bit.led_duration * 2); |  | ||||||
|                     watch_display_string(buf, 4); |                     watch_display_string(buf, 4); | ||||||
|                 } else { |                 } else { | ||||||
|                     watch_display_string("no LEd", 4); |                     watch_display_string("no LEd", 4); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user