336 lines
8.9 KiB
C
336 lines
8.9 KiB
C
/* 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
|
|
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;
|
|
}
|
|
|
|
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");
|
|
|