blob: 35eddd0daae09184a13abfe08c35f07200fdf212 [file] [log] [blame]
/* Copyright 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 "hooks.h"
#include "gesture.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_GESTURE, outstr)
#define CPRINTS(format, args...) cprints(CC_GESTURE, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_GESTURE, format, ## args)
/*
* 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 \
(CONFIG_GESTURE_TAP_OUTER_WINDOW_T / \
CONFIG_GESTURE_SAMPLING_INTERVAL_MS)
#define INNER_WINDOW \
(CONFIG_GESTURE_TAP_INNER_WINDOW_T / \
CONFIG_GESTURE_SAMPLING_INTERVAL_MS)
#define MIN_INTERSTICE \
(CONFIG_GESTURE_TAP_MIN_INTERSTICE_T / \
CONFIG_GESTURE_SAMPLING_INTERVAL_MS)
#define MAX_INTERSTICE \
(CONFIG_GESTURE_TAP_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[CONFIG_GESTURE_SENSOR_BATTERY_TAP];
/* 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, history_init_index;
static int tap_debug;
/* Tap detection flag */
static int tap_detection;
/*
* 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 separate 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
* detection is paused, history_init_index will store the index
* when paused, so that when re-started, we will wait until we
* wrap around again.
*/
if (history_idx == history_init_index)
history_initialized = 1;
if (!history_initialized)
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 > 30000 &&
delta_z_inner > 13 * delta_z_outer &&
delta_z_inner > 1 * delta_xy_inner) {
delta_z_inner_max = delta_z_inner;
state_cnt = 0;
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 (tap_debug &&
(state != state_p ||
(state_cnt % 10000 == 9999))) {
/* 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
CPRINTF("[%T tap st %d->%d, st_cnt %-3d ",
state_p, state, state_cnt);
CPRINTF("Z_in:Z_out %-3d, Z_in:XY_in %-3d ",
delta_z_inner / delta_z_outer,
delta_z_inner / delta_xy_inner);
CPRINTF("dZ_in %-8.3d, dZ_in_max %-8.3d, "
"dZ_out %-8.3d]\n",
delta_z_inner, delta_z_inner_max,
delta_z_outer);
}
return ret;
}
static void gesture_chipset_resume(void)
{
/* disable tap detection */
tap_detection = 0;
}
DECLARE_HOOK(HOOK_CHIPSET_RESUME, gesture_chipset_resume,
GESTURE_HOOK_PRIO);
static void gesture_chipset_suspend(void)
{
/*
* Clear tap init and history initialized so that we have to
* record a whole new set of data, and enable tap detection
*/
history_initialized = 0;
history_init_index = history_idx;
state = TAP_IDLE;
tap_detection = 1;
}
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, gesture_chipset_suspend,
GESTURE_HOOK_PRIO);
void gesture_calc(uint32_t *event)
{
/* 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())
*event |= TASK_EVENT_MOTION_ACTIVITY_INTERRUPT(
MOTIONSENSE_ACTIVITY_DOUBLE_TAP);
}
/*****************************************************************************/
/* Console commands */
static int command_tap_info(int argc, char **argv)
{
int 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");
ccprintf("odr: %d\n", sensor->drv->get_data_rate(sensor));
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(tapinfo, command_tap_info,
"debug on/off",
"Print tap information");