diff options
author | Alec Berg <alecaberg@chromium.org> | 2014-09-29 11:45:01 -0700 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2014-10-18 07:38:26 +0000 |
commit | 316f369f1cc859880485cab7dd760fa19b48e70d (patch) | |
tree | c35775e93a2cf61fac9b0f2a3490812807d145c9 | |
parent | 5d4846ee520b6f1e743a6e58537a2ab2fac0cafc (diff) | |
download | chrome-ec-316f369f1cc859880485cab7dd760fa19b48e70d.tar.gz |
samus: add tap for battery
Adds double tap detection for samus. When user double taps
in S3 or lower to show battery state of charge on lightbar.
BUG=chrome-os-partner:29041
BRANCH=samus
TEST=make buildall
Tap the lid in S3 or lower.
Change-Id: Ic5f4709bdee2472cb7e91717318337b04bae1fc8
Signed-off-by: Alec Berg <alecaberg@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/221965
Reviewed-by: David Schneider <dnschneid@chromium.org>
-rw-r--r-- | board/samus/board.h | 2 | ||||
-rw-r--r-- | board/samus/build.mk | 2 | ||||
-rw-r--r-- | board/samus/gesture.c | 334 | ||||
-rw-r--r-- | common/motion_sense.c | 43 | ||||
-rw-r--r-- | include/accelgyro.h | 2 | ||||
-rw-r--r-- | include/config.h | 8 | ||||
-rw-r--r-- | include/gesture.h | 31 |
7 files changed, 413 insertions, 9 deletions
diff --git a/board/samus/board.h b/board/samus/board.h index b4318cd0cb..394b621d74 100644 --- a/board/samus/board.h +++ b/board/samus/board.h @@ -47,6 +47,8 @@ #define CONFIG_CHARGER_INPUT_CURRENT 512 #define CONFIG_CHARGER_DISCHARGE_ON_AC #define CONFIG_FANS 2 +#define CONFIG_GESTURE_DETECTION +#define CONFIG_GESTURE_SAMPLING_INTERVAL_MS 5 #define CONFIG_PECI_TJMAX 100 #define CONFIG_PWM #define CONFIG_PWM_KBLIGHT diff --git a/board/samus/build.mk b/board/samus/build.mk index 153a280b76..3e6368aaeb 100644 --- a/board/samus/build.mk +++ b/board/samus/build.mk @@ -9,4 +9,4 @@ # the IC is TI Stellaris LM4 CHIP:=lm4 -board-y=board.o power_sequence.o panel.o extpower.o +board-y=board.o power_sequence.o panel.o extpower.o gesture.o diff --git a/board/samus/gesture.c b/board/samus/gesture.c new file mode 100644 index 0000000000..853a568bdb --- /dev/null +++ b/board/samus/gesture.c @@ -0,0 +1,334 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Board specific gesture recognition */ + +#include "accelgyro.h" +#include "common.h" +#include "console.h" +#include "lid_switch.h" +#include "lightbar.h" +#include "motion_sense.h" +#include "task.h" +#include "timer.h" +#include "util.h" + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_MOTION_SENSE, outstr) +#define CPRINTS(format, args...) cprints(CC_MOTION_SENSE, format, ## args) +#define CPRINTF(format, args...) cprintf(CC_MOTION_SENSE, format, ## args) + +/* Output datarate for tap sensor (in milli-Hz) */ +#define TAP_ODR (1000000 / CONFIG_GESTURE_SAMPLING_INTERVAL_MS) + +/* + * Double tap detection parameters + * Double tap works by looking for two isolated Z-axis accelerometer impulses + * preceded and followed by relatively calm periods of accelerometer motion. + * + * Define an outer and inner window. The inner window specifies how + * long the tap impulse is expected to last. The outer window specifies the + * period before the initial tap impluse and after the final tap impulse for + * which to check for relatively calm periods. In between the two impulses + * there is a minimum and maximum interstice time allowed. + */ +#define OUTER_WINDOW_T 200 +#define INNER_WINDOW_T 30 +#define MIN_INTERSTICE_T 120 +#define MAX_INTERSTICE_T 500 + +#define OUTER_WINDOW (OUTER_WINDOW_T / CONFIG_GESTURE_SAMPLING_INTERVAL_MS) +#define INNER_WINDOW (INNER_WINDOW_T / CONFIG_GESTURE_SAMPLING_INTERVAL_MS) +#define MIN_INTERSTICE (MIN_INTERSTICE_T / CONFIG_GESTURE_SAMPLING_INTERVAL_MS) +#define MAX_INTERSTICE (MAX_INTERSTICE_T / CONFIG_GESTURE_SAMPLING_INTERVAL_MS) +#define MAX_WINDOW OUTER_WINDOW + +/* State machine states for detecting double tap */ +enum tap_states { + /* Look for calm before the storm */ + TAP_IDLE, + /* Record first Z impulse */ + TAP_IMPULSE_1, + + /* Eye of the storm, expect Z motion to drop and then suddenly spike */ + TAP_INTERSTICE_DROP, + TAP_INTERSTICE_RISE, + + /* Record second Z impulse */ + TAP_IMPULSE_2, + /* Should be quiet after the storm */ + TAP_AFTER_EVENT +}; + +/* Tap sensor to use */ +static struct motion_sensor_t *sensor = &motion_sensors[0]; + +/* Tap state information */ +static int history_z[MAX_WINDOW]; /* Changes in Z */ +static int history_xy[MAX_WINDOW]; /* Changes in X and Y */ +static int state, history_idx; +static int history_initialized; +static int tap_debug; + +/* Tap detection flag */ +static int tap_detection; +static int saved_odr; + +/* + * TODO(crosbug.com/p/33102): Cleanup this function: break into multiple + * functions and generalize so it can be used for other boards. + */ +static int gesture_tap_for_battery(void) +{ + /* Current and previous accel x,y,z */ + int x, y, z; + static int x_p, y_p, z_p; + + /* Number of iterations in this state */ + static int state_cnt; + + /* + * Running sums of data diffs for inner and outer windows. + * Z data kept seperate from X and Y data + */ + static int sum_z_inner, sum_z_outer, sum_xy_inner, sum_xy_outer; + + /* Total variation in each signal, normalized for window size */ + int delta_z_outer, delta_z_inner, delta_xy_outer, delta_xy_inner; + + /* Max variation seen during tap event and state cnts since max */ + static int delta_z_inner_max; + static int cnts_since_max; + + /* Interstice Z motion thresholds */ + static int z_drop_thresh, z_rise_thresh; + + int history_idx_inner, state_p; + int ret = 0; + + /* Get data */ + x = sensor->xyz[0]; + y = sensor->xyz[1]; + z = sensor->xyz[2]; + + /* + * Calculate history of change in Z sensor and keeping + * running sums for the past. + */ + history_idx_inner = history_idx - INNER_WINDOW; + if (history_idx_inner < 0) + history_idx_inner += MAX_WINDOW; + sum_z_inner -= history_z[history_idx_inner]; + sum_z_outer -= history_z[history_idx]; + history_z[history_idx] = ABS(z - z_p); + sum_z_inner += history_z[history_idx]; + sum_z_outer += history_z[history_idx]; + + /* + * Calculate history of change in X and Y sensors combined + * and keep a running sum of the change over the past. + */ + sum_xy_inner -= history_xy[history_idx_inner]; + sum_xy_outer -= history_xy[history_idx]; + history_xy[history_idx] = ABS(x - x_p) + ABS(y - y_p); + sum_xy_inner += history_xy[history_idx]; + sum_xy_outer += history_xy[history_idx]; + + /* Increment history index */ + history_idx = (history_idx == MAX_WINDOW - 1) ? 0 : (history_idx + 1); + + /* Store previous X, Y, Z data */ + x_p = x; + y_p = y; + z_p = z; + + /* Ignore data until we fill history buffer and wrap around */ + if (history_idx == 0) + history_initialized = 1; + if (history_initialized == 0) + return 0; + + /* + * Normalize data based on window size and isolate outer and inner + * window data. + */ + delta_z_outer = (sum_z_outer - sum_z_inner) * 1000 / + (OUTER_WINDOW - INNER_WINDOW); + delta_z_inner = sum_z_inner * 1000 / INNER_WINDOW; + delta_xy_outer = (sum_xy_outer - sum_xy_inner) * 1000 / + (OUTER_WINDOW - INNER_WINDOW); + delta_xy_inner = sum_xy_inner * 1000 / INNER_WINDOW; + + state_cnt++; + state_p = state; + + switch (state) { + case TAP_IDLE: + /* Look for a sudden increase in Z movement */ + if (delta_z_inner > 13 * delta_z_outer && + delta_z_inner > 1 * delta_xy_inner) { + delta_z_inner_max = delta_z_inner; + state_cnt = TAP_IDLE; + state = TAP_IMPULSE_1; + } + break; + + case TAP_IMPULSE_1: + /* Find the peak inner window of Z movement */ + if (delta_z_inner > delta_z_inner_max) { + delta_z_inner_max = delta_z_inner; + cnts_since_max = state_cnt; + } + + /* After inner window has passed, move to next state */ + if (state_cnt >= INNER_WINDOW) { + state = TAP_INTERSTICE_DROP; + z_drop_thresh = delta_z_inner_max / 12; + z_rise_thresh = delta_z_inner_max / 3; + state_cnt += INNER_WINDOW - cnts_since_max; + } + break; + + case TAP_INTERSTICE_DROP: + /* Check for z motion to go back down first */ + if (delta_z_inner < z_drop_thresh) + state = TAP_INTERSTICE_RISE; + + if (state_cnt > MAX_INTERSTICE) + state = TAP_IDLE; + + break; + + case TAP_INTERSTICE_RISE: + /* Then, check for z motion to go back up */ + if (delta_z_inner > z_rise_thresh) { + if (state_cnt < MIN_INTERSTICE) { + state = TAP_IDLE; + } else { + delta_z_inner_max = delta_z_inner; + state_cnt = 0; + state = TAP_IMPULSE_2; + } + } + + if (state_cnt > MAX_INTERSTICE) + state = TAP_IDLE; + break; + + case TAP_IMPULSE_2: + /* Find the peak inner window of Z movement */ + if (delta_z_inner > delta_z_inner_max) { + delta_z_inner_max = delta_z_inner; + cnts_since_max = state_cnt; + } + + /* After inner window has passed, move to next state */ + if (state_cnt >= INNER_WINDOW) { + state = TAP_AFTER_EVENT; + state_cnt += INNER_WINDOW - cnts_since_max; + } + + case TAP_AFTER_EVENT: + /* Check for small Z movement after the event */ + if (state_cnt < OUTER_WINDOW) + break; + + if (2 * delta_z_inner_max > 3 * delta_z_outer && + delta_z_outer > 1 * delta_xy_outer) + ret = 1; + + state = TAP_IDLE; + break; + } + + /* On state transitions, print debug info */ + if (state != state_p && tap_debug) { + /* make sure we don't divide by 0 */ + if (delta_z_outer == 0 || delta_xy_inner == 0) + CPRINTS("tap st %d->%d, error div by 0", + state_p, state); + else + CPRINTS("tap st %d->%d, st_cnt %-3d, Z_in:Z_out %-3d, " + "Z_in:XY_in %-3d, dZ_in %-8.3d, " + "dZ_in_max %-8.3d, dZ_out %-8.3d", + state_p, state, state_cnt, + delta_z_inner / delta_z_outer, + delta_z_inner / delta_xy_inner, + delta_z_inner, delta_z_inner_max, + delta_z_outer); + } + + return ret; +} + +void gesture_chipset_resume(void) +{ + /* Restore ODR and disable tap detection */ + tap_detection = 0; + sensor->drv->set_data_rate(sensor, saved_odr, 1); +} + +void gesture_chipset_suspend(void) +{ + /* Record active ODR, set ODR to desired value */ + sensor->drv->get_data_rate(sensor, &saved_odr); + sensor->drv->set_data_rate(sensor, TAP_ODR, 1); + + /* + * Clear tap init and history index so that we have to + * record a whole new set of data, and enable tap detection + */ + history_initialized = 0; + state = 0; + history_idx = 0; + tap_detection = 1; +} + +void gesture_chipset_shutdown(void) +{ + sensor->odr = TAP_ODR; + saved_odr = sensor->default_odr; +} + +void gesture_calc(void) +{ + /* Only check for gesture if lid is closed and tap detection is on */ + if (!tap_detection || lid_is_open()) + return; + + if (gesture_tap_for_battery()) { + CPRINTS("Double Tap!"); + lightbar_sequence(LIGHTBAR_TAP); + + /* Don't need to run motion sense task for a while */ + task_wait_event(500 * MSEC); + } +} + +/*****************************************************************************/ +/* Console commands */ +static int command_tap_info(int argc, char **argv) +{ + int odr, val; + + ccprintf("tap: %s\n", (tap_detection && !lid_is_open()) ? + "on" : "off"); + + if (argc > 1) { + if (!parse_bool(argv[1], &val)) + return EC_ERROR_PARAM1; + tap_debug = val; + } + + ccprintf("debug: %s\n", tap_debug ? "on" : "off"); + sensor->drv->get_data_rate(sensor, &odr); + ccprintf("odr: %d\n", odr); + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(tapinfo, command_tap_info, + "debug on/off", + "Print tap information", NULL); + diff --git a/common/motion_sense.c b/common/motion_sense.c index 95934dd60d..95773e2c4c 100644 --- a/common/motion_sense.c +++ b/common/motion_sense.c @@ -6,8 +6,10 @@ /* Motion sense module to read from various motion sensors. */ #include "accelgyro.h" +#include "chipset.h" #include "common.h" #include "console.h" +#include "gesture.h" #include "hooks.h" #include "host_command.h" #include "lid_angle.h" @@ -42,9 +44,15 @@ static int lid_angle_is_reliable; #define MIN_POLLING_INTERVAL_MS 5 #define MAX_POLLING_INTERVAL_MS 1000 +/* Define sensor sampling interval in suspend. */ +#ifdef CONFIG_GESTURE_DETECTION +#define SUSPEND_SAMPLING_INTERVAL CONFIG_GESTURE_SAMPLING_INTERVAL_MS +#else +#define SUSPEND_SAMPLING_INTERVAL 100 +#endif + /* Accelerometer polling intervals based on chipset state. */ static int accel_interval_ap_on_ms = 10; -static const int accel_interval_ap_suspend_ms = 100; /* * Angle threshold for how close the hinge aligns with gravity before @@ -171,6 +179,11 @@ static void clock_chipset_shutdown(void) sensor->drv->set_data_rate(sensor, 0, 0); sensor->state = SENSOR_NOT_INITIALIZED; } + +#ifdef CONFIG_GESTURE_DETECTION + /* run gesture module hook which may override default behavior */ + gesture_chipset_shutdown(); +#endif } DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, clock_chipset_shutdown, HOOK_PRIO_DEFAULT); @@ -178,7 +191,8 @@ static void clock_chipset_suspend(void) { int i; struct motion_sensor_t *sensor; - accel_interval_ms = accel_interval_ap_suspend_ms; + + accel_interval_ms = SUSPEND_SAMPLING_INTERVAL; for (i = 0; i < motion_sensor_count; i++) { sensor = &motion_sensors[i]; @@ -191,6 +205,11 @@ static void clock_chipset_suspend(void) sensor->state = SENSOR_NOT_INITIALIZED; } } + +#ifdef CONFIG_GESTURE_DETECTION + /* run gesture module hook which may override default behavior */ + gesture_chipset_suspend(); +#endif } DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, clock_chipset_suspend, HOOK_PRIO_DEFAULT); @@ -205,6 +224,11 @@ static void clock_chipset_resume(void) sensor = &motion_sensors[i]; sensor->active = SENSOR_ACTIVE_S0; } + +#ifdef CONFIG_GESTURE_DETECTION + /* run gesture module hook which may override default behavior */ + gesture_chipset_resume(); +#endif } DECLARE_HOOK(HOOK_CHIPSET_RESUME, clock_chipset_resume, HOOK_PRIO_DEFAULT); @@ -331,7 +355,8 @@ void motion_sense_task(void) set_present(lpc_status); /* Initialize sampling interval. */ - accel_interval_ms = accel_interval_ap_suspend_ms; + accel_interval_ms = chipset_in_state(CHIPSET_STATE_ON) ? + accel_interval_ap_on_ms : SUSPEND_SAMPLING_INTERVAL; while (1) { ts0 = get_time(); @@ -364,10 +389,13 @@ void motion_sense_task(void) sizeof(vector_3_t)); } - if (rd_cnt != motion_sensor_count) { - task_wait_event(TASK_MOTION_SENSE_WAIT_TIME); - continue; - } +#ifdef CONFIG_GESTURE_DETECTION + /* Run gesture recognition engine */ + gesture_calc(); +#endif + + if (rd_cnt != motion_sensor_count) + goto motion_wait; /* Calculate angle of lid accel. */ lid_angle_is_reliable = calculate_lid_angle( @@ -405,6 +433,7 @@ void motion_sense_task(void) #endif update_sense_data(lpc_status, lpc_data, &sample_id); +motion_wait: /* Delay appropriately to keep sampling time consistent. */ ts1 = get_time(); wait_us = accel_interval_ms * MSEC - (ts1.val-ts0.val); diff --git a/include/accelgyro.h b/include/accelgyro.h index 940a094d82..6118f6dc0c 100644 --- a/include/accelgyro.h +++ b/include/accelgyro.h @@ -71,7 +71,7 @@ struct accelgyro_drv { * Setter and getter methods for the sensor output data range. As the * ODR increases, the LPF roll-off frequency also increases. * @s Pointer to sensor data. - * @rate Output data rate (units are Hz) + * @rate Output data rate (units are milli-Hz) * @rnd Rounding flag. If true, it rounds up to nearest valid * value. Otherwise, it rounds down. * @return EC_SUCCESS if successful, non-zero if error. diff --git a/include/config.h b/include/config.h index 2cc2b0f93a..4522e69b5e 100644 --- a/include/config.h +++ b/include/config.h @@ -539,6 +539,14 @@ #undef CONFIG_FW_WP_RO_SIZE /*****************************************************************************/ +/* Motion sensor based gesture recognition */ +#undef CONFIG_GESTURE_DETECTION + +/* Sensor sampling interval for gesture recognition */ +#undef CONFIG_GESTURE_SAMPLING_INTERVAL_MS + + +/*****************************************************************************/ /* * Support the host asking the EC about the status of the most recent host diff --git a/include/gesture.h b/include/gesture.h new file mode 100644 index 0000000000..d1b547d9cc --- /dev/null +++ b/include/gesture.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Header for gesture.c */ + +#ifndef __CROS_EC_GESTURE_H +#define __CROS_EC_GESTURE_H + +/** + * Run gesture detection engine. + */ +void gesture_calc(void); + +/** + * Gesture hook to call on chipset resume. + */ +void gesture_chipset_resume(void); + +/** + * Gesture hook to call on chipset suspend. + */ +void gesture_chipset_suspend(void); + +/** + * Gesture hook to call on chipset shutdown. + */ +void gesture_chipset_shutdown(void); + +#endif /* __CROS_EC_GESTURE_H */ |