1346 lines
38 KiB
C
1346 lines
38 KiB
C
/****************************************************************************
|
|
* drivers/sensors/apds9960.c
|
|
* Character driver for the APDS9960 Gesture Sensor
|
|
*
|
|
* Copyright (C) 2017 Alan Carvalho de Assis. All rights reserved.
|
|
* Author: Alan Carvalho de Assis <acassis@gmail.com>
|
|
*
|
|
* This driver is based on APDS-9960 Arduino library developed by
|
|
* Shawn Hymel from SparkFun Electronics and released under public
|
|
* domain.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/random.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/i2c/i2c_master.h>
|
|
#include <nuttx/sensors/apds9960.h>
|
|
|
|
#if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_APDS9960)
|
|
|
|
/****************************************************************************
|
|
* Pre-process Definitions
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_APDS9960_I2C_FREQUENCY
|
|
# define CONFIG_APDS9960_I2C_FREQUENCY 400000
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct apds9960_dev_s
|
|
{
|
|
FAR struct apds9960_config_s *config; /* Hardware Configuration */
|
|
struct work_s work; /* Supports ISR "bottom half" */
|
|
struct gesture_data_s gesture_data; /* Gesture data container */
|
|
int gesture_ud_delta; /* UP/DOWN delta */
|
|
int gesture_lr_delta; /* LEFT/RIGHT delta */
|
|
int gesture_ud_count; /* UP/DOWN counter */
|
|
int gesture_lr_count; /* LEFT/RIGHT counter */
|
|
int gesture_near_count; /* Near distance counter */
|
|
int gesture_far_count; /* Far distance counter */
|
|
int gesture_state; /* Gesture machine state */
|
|
int gesture_motion; /* Gesture motion direction */
|
|
sem_t sample_sem; /* Semaphore for sample data */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Reset gesture values */
|
|
|
|
static void apds9960_resetgesture(FAR struct apds9960_dev_s *priv);
|
|
|
|
/* Setup default initial values */
|
|
|
|
static int apds9960_setdefault(FAR struct apds9960_dev_s *priv);
|
|
|
|
/* Probe function to verify if sensor is present */
|
|
|
|
static int apds9960_probe(FAR struct apds9960_dev_s *priv);
|
|
|
|
/* Work queue */
|
|
|
|
static void apds9960_worker(FAR void *arg);
|
|
|
|
/* Gesture processing/decoding functions */
|
|
|
|
static int apds9960_readgesture(FAR struct apds9960_dev_s *priv);
|
|
static bool apds9960_decodegesture(FAR struct apds9960_dev_s *priv);
|
|
static bool apds9960_processgesture(FAR struct apds9960_dev_s *priv);
|
|
static bool apds9960_isgestureavailable(FAR struct apds9960_dev_s *priv);
|
|
|
|
/* I2C Helpers */
|
|
|
|
static int apds9960_i2c_read(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, FAR uint8_t *regval, int len);
|
|
static int apds9960_i2c_read8(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, FAR uint8_t *regval);
|
|
static int apds9960_i2c_write(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const *data, int len);
|
|
static int apds9960_i2c_write8(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, uint8_t regval);
|
|
|
|
/* Character driver methods */
|
|
|
|
static int apds9960_open(FAR struct file *filep);
|
|
static int apds9960_close(FAR struct file *filep);
|
|
static ssize_t apds9960_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t apds9960_write(FAR struct file *filep,
|
|
FAR const char *buffer, size_t buflen);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_apds9960_fops =
|
|
{
|
|
apds9960_open, /* open */
|
|
apds9960_close, /* close */
|
|
apds9960_read, /* read */
|
|
apds9960_write, /* write */
|
|
NULL, /* seek */
|
|
NULL /* ioctl */
|
|
#ifndef CONFIG_DISABLE_POLL
|
|
, NULL /* poll */
|
|
#endif
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
, NULL /* unlink */
|
|
#endif
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_worker
|
|
****************************************************************************/
|
|
|
|
static void apds9960_worker(FAR void *arg)
|
|
{
|
|
FAR struct apds9960_dev_s *priv = (FAR struct apds9960_dev_s *)arg;
|
|
int ret;
|
|
|
|
DEBUGASSERT(priv != NULL);
|
|
|
|
ret = apds9960_readgesture(priv);
|
|
if (ret != DIR_NONE)
|
|
{
|
|
sninfo("Got a valid gesture!\n");
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_int_handler
|
|
*
|
|
* Description:
|
|
* Interrupt handler (ISR) for APDS-99600 INT pin.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_int_handler(int irq, FAR void *context, FAR void *arg)
|
|
{
|
|
int ret;
|
|
|
|
FAR struct apds9960_dev_s *priv = (FAR struct apds9960_dev_s *)arg;
|
|
|
|
DEBUGASSERT(priv != NULL);
|
|
|
|
/* Transfer processing to the worker thread. Since APDS-9960 interrupts
|
|
* are disabled while the work is pending, no special action should be
|
|
* required to protect the work queue.
|
|
*/
|
|
|
|
DEBUGASSERT(priv->work.worker == NULL);
|
|
ret = work_queue(HPWORK, &priv->work, apds9960_worker, priv, 0);
|
|
if (ret != 0)
|
|
{
|
|
snerr("ERROR: Failed to queue work: %d\n", ret);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_resetgesture
|
|
*
|
|
* Description:
|
|
* Reset gesture values
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void apds9960_resetgesture(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
priv->gesture_data.index = 0;
|
|
priv->gesture_data.total_gestures = 0;
|
|
|
|
priv->gesture_ud_delta = 0;
|
|
priv->gesture_lr_delta = 0;
|
|
|
|
priv->gesture_ud_count = 0;
|
|
priv->gesture_lr_count = 0;
|
|
|
|
priv->gesture_near_count = 0;
|
|
priv->gesture_far_count = 0;
|
|
|
|
priv->gesture_state = 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_setdefault
|
|
*
|
|
* Description:
|
|
* Verify if sensor is present. Check if ID is 0xAB.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_setdefault(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
/* Set default values for ambient light and proximity registers */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_ATIME, DEFAULT_ATIME);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_ATIME!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_WTIME, DEFAULT_WTIME);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_WTIME!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_PPULSE, DEFAULT_PPULSE);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_PPULSE!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_POFFSET_UR, DEFAULT_POFFSET_UR);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_POFFSET_UR!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_POFFSET_DL, DEFAULT_POFFSET_DL);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_POFFSET_DL!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_CONFIG1, DEFAULT_CONFIG1);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_CONFIG1!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Set LED driver strength to 100mA, AGAIN 4X and PGAIN 4X */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_CONTROL, DEFAULT_CONTROL);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_CONTROL!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_PILT, DEFAULT_PILT);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_PILT!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_PIHT, DEFAULT_PIHT);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_PIHT!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_AILTL, DEFAULT_AILTL);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_AILTL!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_AILTH, DEFAULT_AILTH);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_AILTH!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_AIHTL, DEFAULT_AIHTL);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_AIHTL!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_AIHTH, DEFAULT_AIHTH);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_AIHTH!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_PERS, DEFAULT_PERS);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_PERS!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_CONFIG2, DEFAULT_CONFIG2);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_CONFIG2!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_CONFIG3, DEFAULT_CONFIG3);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_CONFIG3!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GPENTH, DEFAULT_GPENTH);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GPENTH!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GEXTH, DEFAULT_GEXTH);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GEXTH!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG1, DEFAULT_GCONFIG1);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GCONFIG1!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG2, DEFAULT_GCONFIG2);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GCONFIG2!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_U, DEFAULT_GOFFSET_U);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GOFFSET_U!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_D, DEFAULT_GOFFSET_D);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GOFFSET_D!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_L, DEFAULT_GOFFSET_L);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GOFFSET_L!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_R, DEFAULT_GOFFSET_R);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GOFFSET_R!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GPULSE, DEFAULT_GPULSE);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GPULSE!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG3, DEFAULT_GCONFIG3);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GCONFIG3!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG4, DEFAULT_GCONFIG4);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GCONFIG3!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_WTIME, 0xff);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_WTIME!\n");
|
|
return ret;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_probe
|
|
*
|
|
* Description:
|
|
* Verify if sensor is present. Check if ID is 0xAB.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_probe(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
int ret;
|
|
uint8_t id;
|
|
|
|
ret = apds9960_i2c_read8(priv, APDS9960_ID, &id);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to initialize the APDS9960!\n");
|
|
return ret;
|
|
}
|
|
|
|
if (id != APDS9960_ID_VAL)
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_i2c_read
|
|
*
|
|
* Description:
|
|
* Read an arbitrary number of bytes starting at regaddr
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_i2c_read(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, FAR uint8_t *regval, int len)
|
|
{
|
|
struct i2c_config_s config;
|
|
int ret = -1;
|
|
|
|
/* Set up the I2C configuration */
|
|
|
|
config.frequency = CONFIG_APDS9960_I2C_FREQUENCY;
|
|
config.address = priv->config->i2c_addr;
|
|
config.addrlen = 7;
|
|
|
|
/* Write the register address to read from */
|
|
|
|
ret = i2c_write(priv->config->i2c_dev, &config, ®addr, 1);
|
|
if (ret < 0)
|
|
{
|
|
snerr ("i2c_write failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Read "len" bytes from regaddr */
|
|
|
|
ret = i2c_read(priv->config->i2c_dev, &config, regval, len);
|
|
if (ret < 0)
|
|
{
|
|
snerr ("i2c_read failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_i2c_read8
|
|
*
|
|
* Description:
|
|
* Read 8-bit register
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_i2c_read8(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, FAR uint8_t *regval)
|
|
{
|
|
int ret;
|
|
|
|
ret = apds9960_i2c_read(priv, regaddr, regval, 1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_i2c_write
|
|
*
|
|
* Description:
|
|
* Write an arbitrary number of bytes starting at regaddr.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_i2c_write(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const *data, int len)
|
|
{
|
|
struct i2c_config_s config;
|
|
int ret;
|
|
|
|
/* Set up the I2C configuration */
|
|
|
|
config.frequency = CONFIG_APDS9960_I2C_FREQUENCY;
|
|
config.address = priv->config->i2c_addr;
|
|
config.addrlen = 7;
|
|
|
|
/* Write the data */
|
|
|
|
ret = i2c_write(priv->config->i2c_dev, &config, data, len);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: i2c_write failed: %d\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_i2c_write8
|
|
*
|
|
* Description:
|
|
* Write an arbitrary number of bytes starting at regaddr.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_i2c_write8(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, uint8_t regval)
|
|
{
|
|
int ret;
|
|
uint8_t data[2];
|
|
|
|
/* Create the addr:val data */
|
|
|
|
data[0] = regaddr;
|
|
data[1] = regval;
|
|
|
|
ret = apds9960_i2c_write(priv, data, 2);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_isgestureavailable
|
|
*
|
|
* Description:
|
|
* Return true is gesture data is valid.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool apds9960_isgestureavailable(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
int ret;
|
|
uint8_t val;
|
|
|
|
/* Read value from GSTATUS register */
|
|
|
|
ret = apds9960_i2c_read8(priv, APDS9960_GSTATUS, &val);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to read APDS9960_GSTATUS!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Return true/false based on GVALID bit */
|
|
|
|
if ((val & GVALID) == GVALID)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_processgesture
|
|
*
|
|
* Description:
|
|
* Process the data read from the photodiodes
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool apds9960_processgesture(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
uint8_t u_first = 0;
|
|
uint8_t d_first = 0;
|
|
uint8_t l_first = 0;
|
|
uint8_t r_first = 0;
|
|
uint8_t u_last = 0;
|
|
uint8_t d_last = 0;
|
|
uint8_t l_last = 0;
|
|
uint8_t r_last = 0;
|
|
int ud_ratio_first;
|
|
int lr_ratio_first;
|
|
int ud_ratio_last;
|
|
int lr_ratio_last;
|
|
int ud_delta;
|
|
int lr_delta;
|
|
int i;
|
|
|
|
/* If we have less than 4 total gestures, that's not enough */
|
|
|
|
if (priv->gesture_data.total_gestures <= 4)
|
|
{
|
|
snerr("ERROR: We don't have enough gesture: %d\n",
|
|
priv->gesture_data.total_gestures);
|
|
return false;
|
|
}
|
|
|
|
/* Check to make sure our data isn't out of bounds */
|
|
|
|
if ((priv->gesture_data.total_gestures <= 32) && \
|
|
(priv->gesture_data.total_gestures > 0))
|
|
{
|
|
/* Find the first value in U/D/L/R above the threshold */
|
|
|
|
for (i = 0; i < priv->gesture_data.total_gestures; i++)
|
|
{
|
|
if ((priv->gesture_data.u_data[i] > GESTURE_THRESHOLD_OUT) && \
|
|
(priv->gesture_data.d_data[i] > GESTURE_THRESHOLD_OUT) && \
|
|
(priv->gesture_data.l_data[i] > GESTURE_THRESHOLD_OUT) && \
|
|
(priv->gesture_data.r_data[i] > GESTURE_THRESHOLD_OUT))
|
|
{
|
|
u_first = priv->gesture_data.u_data[i];
|
|
d_first = priv->gesture_data.d_data[i];
|
|
l_first = priv->gesture_data.l_data[i];
|
|
r_first = priv->gesture_data.r_data[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If one of the _first values is 0, then there is no good data */
|
|
|
|
if ((u_first == 0) || (d_first == 0) || \
|
|
(l_first == 0) || (r_first == 0))
|
|
{
|
|
snerr("ERROR: First value is zero! U=%d, D=%d, L=%d, R=%d\n", \
|
|
u_first, d_first, l_first, r_first);
|
|
return false;
|
|
}
|
|
|
|
/* Find the last value in U/D/L/R above the threshold */
|
|
|
|
for (i = priv->gesture_data.total_gestures - 1; i >= 0; i--)
|
|
{
|
|
sninfo("Finding last: \n");
|
|
sninfo("U: %03d\n", priv->gesture_data.u_data[i]);
|
|
sninfo("D: %03d\n", priv->gesture_data.d_data[i]);
|
|
sninfo("L: %03d\n", priv->gesture_data.l_data[i]);
|
|
sninfo("R: %03d\n", priv->gesture_data.r_data[i]);
|
|
|
|
if ((priv->gesture_data.u_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(priv->gesture_data.d_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(priv->gesture_data.l_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(priv->gesture_data.r_data[i] > GESTURE_THRESHOLD_OUT))
|
|
{
|
|
u_last = priv->gesture_data.u_data[i];
|
|
d_last = priv->gesture_data.d_data[i];
|
|
l_last = priv->gesture_data.l_data[i];
|
|
r_last = priv->gesture_data.r_data[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Calculate the first vs. last ratio of up/down and left/right */
|
|
|
|
ud_ratio_first = ((u_first - d_first) * 100) / (u_first + d_first);
|
|
lr_ratio_first = ((l_first - r_first) * 100) / (l_first + r_first);
|
|
ud_ratio_last = ((u_last - d_last) * 100) / (u_last + d_last);
|
|
lr_ratio_last = ((l_last - r_last) * 100) / (l_last + r_last);
|
|
|
|
sninfo("Last Values: \n");
|
|
sninfo("U: %03d\n", u_last);
|
|
sninfo("D: %03d\n", d_last);
|
|
sninfo("L: %03d\n", l_last);
|
|
sninfo("R: %03d\n", r_last);
|
|
|
|
sninfo("Ratios: \n");
|
|
sninfo("UD Fi: %03d\n", ud_ratio_first);
|
|
sninfo("UD La: %03d\n", ud_ratio_last);
|
|
sninfo("LR Fi: %03d\n", lr_ratio_first);
|
|
sninfo("LR La: %03d\n", lr_ratio_last);
|
|
|
|
/* Determine the difference between the first and last ratios */
|
|
|
|
ud_delta = ud_ratio_last - ud_ratio_first;
|
|
lr_delta = lr_ratio_last - lr_ratio_first;
|
|
|
|
sninfo("Deltas: \n");
|
|
sninfo("UD: %03d\n", ud_delta);
|
|
sninfo("LR: %03d\n", lr_delta);
|
|
|
|
/* Accumulate the UD and LR delta values */
|
|
|
|
priv->gesture_ud_delta += ud_delta;
|
|
priv->gesture_lr_delta += lr_delta;
|
|
|
|
sninfo("Accumulations: \n");
|
|
sninfo("UD: %03d\n", priv->gesture_ud_delta);
|
|
sninfo("LR: %03d\n", priv->gesture_lr_delta);
|
|
|
|
/* Determine U/D gesture */
|
|
|
|
if (priv->gesture_ud_delta >= GESTURE_SENSITIVITY_1)
|
|
{
|
|
priv->gesture_ud_count = 1;
|
|
}
|
|
else
|
|
{
|
|
if (priv->gesture_ud_delta <= -GESTURE_SENSITIVITY_1)
|
|
{
|
|
priv->gesture_ud_count = -1;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_ud_count = 0;
|
|
}
|
|
}
|
|
|
|
/* Determine L/R gesture */
|
|
|
|
if (priv->gesture_lr_delta >= GESTURE_SENSITIVITY_1)
|
|
{
|
|
priv->gesture_lr_count = 1;
|
|
}
|
|
else
|
|
{
|
|
if (priv->gesture_lr_delta <= -GESTURE_SENSITIVITY_1)
|
|
{
|
|
priv->gesture_lr_count = -1;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_lr_count = 0;
|
|
}
|
|
}
|
|
|
|
/* Determine Near/Far gesture */
|
|
|
|
if ((priv->gesture_ud_count == 0) && (priv->gesture_lr_count == 0))
|
|
{
|
|
if ((abs(ud_delta) < GESTURE_SENSITIVITY_2) && \
|
|
(abs(lr_delta) < GESTURE_SENSITIVITY_2))
|
|
{
|
|
if ((ud_delta == 0) && (lr_delta == 0))
|
|
{
|
|
priv->gesture_near_count++;
|
|
}
|
|
else
|
|
{
|
|
if ((ud_delta != 0) || (lr_delta != 0))
|
|
{
|
|
priv->gesture_far_count++;
|
|
}
|
|
}
|
|
|
|
if ((priv->gesture_near_count >= 10) && \
|
|
(priv->gesture_far_count >= 2))
|
|
{
|
|
if ((ud_delta == 0) && (lr_delta == 0))
|
|
{
|
|
priv->gesture_state = NEAR_STATE;
|
|
}
|
|
else
|
|
{
|
|
if ( (ud_delta != 0) && (lr_delta != 0))
|
|
{
|
|
priv->gesture_state = FAR_STATE;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((abs(ud_delta) < GESTURE_SENSITIVITY_2) && \
|
|
(abs(lr_delta) < GESTURE_SENSITIVITY_2))
|
|
{
|
|
if ((ud_delta == 0) && (lr_delta == 0))
|
|
{
|
|
priv->gesture_near_count++;
|
|
}
|
|
|
|
if (priv->gesture_near_count >= 10)
|
|
{
|
|
priv->gesture_ud_count = 0;
|
|
priv->gesture_lr_count = 0;
|
|
priv->gesture_ud_delta = 0;
|
|
priv->gesture_lr_delta = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
sninfo(" UD_CT: %03d\n", priv->gesture_ud_count);
|
|
sninfo(" LR_CT: %03d\n", priv->gesture_lr_count);
|
|
sninfo(" NEAR_CT: %03d\n", priv->gesture_near_count);
|
|
sninfo(" FAR_CT: %03d\n", priv->gesture_far_count);
|
|
sninfo("----------------------\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_decodegesture
|
|
*
|
|
* Description:
|
|
* Decode the sensor data and return true if there is some valid data
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool apds9960_decodegesture(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
/* Return if near or far event is detected */
|
|
|
|
if (priv->gesture_state == NEAR_STATE)
|
|
{
|
|
priv->gesture_motion = DIR_NEAR;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (priv->gesture_state == FAR_STATE)
|
|
{
|
|
priv->gesture_motion = DIR_FAR;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Determine swipe direction */
|
|
|
|
if ((priv->gesture_ud_count == -1) && (priv->gesture_lr_count == 0))
|
|
{
|
|
priv->gesture_motion = DIR_UP;
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == 1) && (priv->gesture_lr_count == 0))
|
|
{
|
|
priv->gesture_motion = DIR_DOWN;
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == 0) && (priv->gesture_lr_count == 1))
|
|
{
|
|
priv->gesture_motion = DIR_RIGHT;
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == 0) &&
|
|
(priv->gesture_lr_count == -1))
|
|
{
|
|
priv->gesture_motion = DIR_LEFT;
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == -1) &&
|
|
(priv->gesture_lr_count == 1))
|
|
{
|
|
if (abs(priv->gesture_ud_delta) > \
|
|
abs(priv->gesture_lr_delta))
|
|
{
|
|
priv->gesture_motion = DIR_UP;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_motion = DIR_RIGHT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == 1) && \
|
|
(priv->gesture_lr_count == -1))
|
|
{
|
|
if (abs(priv->gesture_ud_delta) > \
|
|
abs(priv->gesture_lr_delta))
|
|
{
|
|
priv->gesture_motion = DIR_DOWN;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_motion = DIR_LEFT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == -1) && \
|
|
(priv->gesture_lr_count == -1))
|
|
{
|
|
if (abs(priv->gesture_ud_delta) > \
|
|
abs(priv->gesture_lr_delta))
|
|
{
|
|
priv->gesture_motion = DIR_UP;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_motion = DIR_LEFT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == 1) && \
|
|
(priv->gesture_lr_count == 1))
|
|
{
|
|
if (abs(priv->gesture_ud_delta) > \
|
|
abs(priv->gesture_lr_delta))
|
|
{
|
|
priv->gesture_motion = DIR_DOWN;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_motion = DIR_RIGHT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_readgesture
|
|
*
|
|
* Description:
|
|
* Read the photodiode data, process/decode it and return the guess
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_readgesture(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
uint8_t fifo_level = 0;
|
|
uint8_t bytes_read = 0;
|
|
uint8_t fifo_data[128];
|
|
uint8_t gstatus;
|
|
int motion;
|
|
int ret;
|
|
int i;
|
|
|
|
/* Make sure that power and gesture is on and data is valid */
|
|
|
|
if (!apds9960_isgestureavailable(priv))
|
|
{
|
|
return DIR_NONE;
|
|
}
|
|
|
|
/* Keep looping as long as gesture data is valid */
|
|
|
|
while(1)
|
|
{
|
|
/* Wait some time to collect next batch of FIFO data */
|
|
|
|
nxsig_usleep(FIFO_PAUSE_TIME);
|
|
|
|
/* Get the contents of the STATUS register. Is data still valid? */
|
|
|
|
ret = apds9960_i2c_read8(priv, APDS9960_GSTATUS, &gstatus);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to read APDS9960_GSTATUS!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* If we have valid data, read in FIFO */
|
|
|
|
if ((gstatus & GVALID) == GVALID)
|
|
{
|
|
/* Read the current FIFO level */
|
|
|
|
ret = apds9960_i2c_read8(priv, APDS9960_GFLVL, &fifo_level);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to read APDS9960_GFLVL!\n");
|
|
return ret;
|
|
}
|
|
|
|
sninfo("FIFO Level: %d\n", fifo_level);
|
|
|
|
/* If there's stuff in the FIFO, read it into our data block */
|
|
|
|
if (fifo_level > 0)
|
|
{
|
|
bytes_read = fifo_level * 4;
|
|
ret = apds9960_i2c_read(priv, APDS9960_GFIFO_U,
|
|
(uint8_t *) fifo_data, bytes_read);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to read APDS9960_GFIFO_U!\n");
|
|
return ret;
|
|
}
|
|
|
|
sninfo("\nFIFO Dump:\n");
|
|
for (i = 0; i < fifo_level; i++)
|
|
{
|
|
sninfo("U: %03d | D: %03d | L: %03d | R: %03d\n",
|
|
fifo_data[i], fifo_data[i+1],
|
|
fifo_data[i+2], fifo_data[i+3]);
|
|
}
|
|
|
|
sninfo("\n");
|
|
|
|
/* If at least 1 set of data, sort the data into U/D/L/R */
|
|
|
|
if (bytes_read >= 4)
|
|
{
|
|
for (i = 0; i < bytes_read; i += 4)
|
|
{
|
|
priv->gesture_data.u_data[priv->gesture_data.index] = fifo_data[i + 0];
|
|
priv->gesture_data.d_data[priv->gesture_data.index] = fifo_data[i + 1];
|
|
priv->gesture_data.l_data[priv->gesture_data.index] = fifo_data[i + 2];
|
|
priv->gesture_data.r_data[priv->gesture_data.index] = fifo_data[i + 3];
|
|
priv->gesture_data.index++;
|
|
priv->gesture_data.total_gestures++;
|
|
}
|
|
|
|
sninfo("Up Data:\n");
|
|
for (i = 0; i < priv->gesture_data.total_gestures; i++)
|
|
{
|
|
sninfo("%03d\n", priv->gesture_data.u_data[i]);
|
|
}
|
|
|
|
sninfo("\n");
|
|
|
|
/* Filter and process gesture data. Decode near/far state */
|
|
|
|
if (apds9960_processgesture(priv))
|
|
{
|
|
if (apds9960_decodegesture(priv))
|
|
{
|
|
//***TODO: U-Turn Gestures
|
|
//sninfo("gesture_motion = %d\n", gesture_motion);
|
|
}
|
|
}
|
|
|
|
/* Reset data */
|
|
|
|
priv->gesture_data.index = 0;
|
|
priv->gesture_data.total_gestures = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Determine best guessed gesture and clean up */
|
|
|
|
nxsig_usleep(FIFO_PAUSE_TIME);
|
|
apds9960_decodegesture(priv);
|
|
motion = priv->gesture_motion;
|
|
|
|
snwarn("END: %d\n", priv->gesture_motion);
|
|
|
|
if (motion == DIR_LEFT)
|
|
{
|
|
snwarn("RESULT = LEFT\n");
|
|
}
|
|
|
|
if (motion == DIR_RIGHT)
|
|
{
|
|
snwarn("RESULT = RIGHT\n");
|
|
}
|
|
|
|
if (motion == DIR_UP)
|
|
{
|
|
snwarn("RESULT = UP\n");
|
|
}
|
|
|
|
if (motion == DIR_DOWN)
|
|
{
|
|
snwarn("RESULT = DOWN\n");
|
|
}
|
|
|
|
/* Increase semaphore to indicate new data */
|
|
|
|
nxsem_post(&priv->sample_sem);
|
|
|
|
apds9960_resetgesture(priv);
|
|
return motion;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_open
|
|
*
|
|
* Description:
|
|
* This function is called whenever the APDS9960 device is opened.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_open(FAR struct file *filep)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_close
|
|
*
|
|
* Description:
|
|
* This routine is called when the APDS9960 device is closed.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_close(FAR struct file *filep)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t apds9960_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct apds9960_dev_s *priv;
|
|
int ret;
|
|
|
|
DEBUGASSERT(filep);
|
|
inode = filep->f_inode;
|
|
|
|
DEBUGASSERT(inode && inode->i_private);
|
|
priv = (FAR struct apds9960_dev_s *)inode->i_private;
|
|
|
|
/* Check if the user is reading the right size */
|
|
|
|
if (buflen < 1)
|
|
{
|
|
snerr("ERROR: You need to read at least 1 byte from this sensor!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Wait for data available */
|
|
|
|
do
|
|
{
|
|
ret = nxsem_wait(&priv->sample_sem);
|
|
|
|
/* The only case that an error should occur here is if the wait was
|
|
* awakened by a signal.
|
|
*/
|
|
|
|
DEBUGASSERT(ret == OK || ret == -EINTR);
|
|
}
|
|
while (ret == -EINTR);
|
|
|
|
buffer[0] = (char) priv->gesture_motion;
|
|
buflen = 1;
|
|
|
|
return buflen;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t apds9960_write(FAR struct file *filep,
|
|
FAR const char *buffer, size_t buflen)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_register
|
|
*
|
|
* Description:
|
|
* Register the APDS9960 character device as 'devpath'
|
|
*
|
|
* Input Parameters:
|
|
* devpath - The full path to the driver to register. E.g., "/dev/gest0"
|
|
* i2c - An instance of the I2C interface to use to communicate with APDS9960
|
|
* addr - The I2C address of the APDS9960.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int apds9960_register(FAR const char *devpath,
|
|
FAR struct apds9960_config_s *config)
|
|
{
|
|
int ret;
|
|
|
|
/* Sanity check */
|
|
|
|
DEBUGASSERT(i2c != NULL);
|
|
|
|
/* Initialize the APDS9960 device structure */
|
|
|
|
FAR struct apds9960_dev_s *priv =
|
|
(FAR struct apds9960_dev_s *)kmm_malloc(sizeof(struct apds9960_dev_s));
|
|
|
|
if (priv == NULL)
|
|
{
|
|
snerr("ERROR: Failed to allocate instance\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->config = config;
|
|
nxsem_init(&priv->sample_sem, 0, 0);
|
|
priv->gesture_motion = DIR_NONE;
|
|
|
|
/* Probe APDS9960 device */
|
|
|
|
ret = apds9960_probe(priv);
|
|
if (ret != OK)
|
|
{
|
|
snerr("ERROR: APDS-9960 is not responding!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Turn the device OFF to make it sane */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_ENABLE, 0);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to initialize the APDS9960!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Wait 100ms */
|
|
|
|
nxsig_usleep(100000);
|
|
|
|
/* Initialize the device (leave RESET) */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_ENABLE, PON);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to initialize the APDS9960!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Set default initial register values */
|
|
|
|
ret = apds9960_setdefault(priv);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to initialize the APDS9960!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Reset gesture values */
|
|
|
|
apds9960_resetgesture(priv);
|
|
|
|
/* Enable the Gesture mode and interruptions */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG4, (GMODE | GIEN));
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GCONFIG4!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Enable the Gesture mode (Proximity mode is needed for gesture mode) */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_ENABLE, PON | PEN | GEN | WEN);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to initialize the APDS9960!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Register the character driver */
|
|
|
|
ret = register_driver(devpath, &g_apds9960_fops, 0666, priv);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to register driver: %d\n", ret);
|
|
kmm_free(priv);
|
|
}
|
|
|
|
/* Attach to the interrupt */
|
|
|
|
priv->config->irq_attach(priv->config, apds9960_int_handler, priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif /* CONFIG_I2C && CONFIG_SENSORS_APDS9960 */
|