Aaron Fischer
f24858d162
This step seems bold, but is saves us so much hassle. Even better, we have a reliable codebase, with all the dependencies (and their versions) we need in order to build the project. If a library got an update, we can replace it inplace if the code is still compatible.
577 lines
15 KiB
C++
577 lines
15 KiB
C++
/**************************************************************************/
|
|
/*!
|
|
@file Adafruit_APDS9960.cpp
|
|
@author Ladyada, Dean Miller (Adafruit Industries)
|
|
|
|
@section LICENSE
|
|
|
|
Software License Agreement (BSD License)
|
|
|
|
Copyright (c) 2017, Adafruit Industries
|
|
All rights reserved.
|
|
|
|
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 of the copyright holders 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 ''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 HOLDER 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.
|
|
*/
|
|
/**************************************************************************/
|
|
|
|
#ifdef __AVR
|
|
#include <avr/pgmspace.h>
|
|
#elif defined(ESP8266)
|
|
#include <pgmspace.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#include "Adafruit_APDS9960.h"
|
|
|
|
/*========================================================================*/
|
|
/* PRIVATE FUNCTIONS */
|
|
/*========================================================================*/
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
@brief Implements missing powf function
|
|
*/
|
|
/**************************************************************************/
|
|
float powf(const float x, const float y)
|
|
{
|
|
return (float)(pow((double)x, (double)y));
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
Enables the device
|
|
Disables the device (putting it in lower power sleep mode)
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_APDS9960::enable(boolean en)
|
|
{
|
|
_enable.PON = en;
|
|
this->write8(APDS9960_ENABLE, _enable.get());
|
|
}
|
|
|
|
/*========================================================================*/
|
|
/* PUBLIC FUNCTIONS */
|
|
/*========================================================================*/
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
Initializes I2C and configures the sensor (call this function before
|
|
doing anything else)
|
|
*/
|
|
/**************************************************************************/
|
|
boolean Adafruit_APDS9960::begin(uint16_t iTimeMS, apds9960AGain_t aGain, uint8_t addr)
|
|
{
|
|
_i2c_init();
|
|
_i2caddr = addr;
|
|
|
|
/* Make sure we're actually connected */
|
|
uint8_t x = read8(APDS9960_ID);
|
|
if (x != 0xAB)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* Set default integration time and gain */
|
|
setADCIntegrationTime(iTimeMS);
|
|
setADCGain(aGain);
|
|
|
|
// disable everything to start
|
|
enableGesture(false);
|
|
enableProximity(false);
|
|
enableColor(false);
|
|
|
|
disableColorInterrupt();
|
|
disableProximityInterrupt();
|
|
clearInterrupt();
|
|
|
|
/* Note: by default, the device is in power down mode on bootup */
|
|
enable(false);
|
|
delay(10);
|
|
enable(true);
|
|
delay(10);
|
|
|
|
//default to all gesture dimensions
|
|
setGestureDimensions(APDS9960_DIMENSIONS_ALL);
|
|
setGestureFIFOThreshold(APDS9960_GFIFO_4);
|
|
setGestureGain(APDS9960_GGAIN_4);
|
|
setGestureProximityThreshold(50);
|
|
resetCounts();
|
|
|
|
_gpulse.GPLEN = APDS9960_GPULSE_32US;
|
|
_gpulse.GPULSE = 9; //10 pulses
|
|
this->write8(APDS9960_GPULSE, _gpulse.get());
|
|
|
|
return true;
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
Sets the integration time for the ADC of the APDS9960, in millis
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_APDS9960::setADCIntegrationTime(uint16_t iTimeMS)
|
|
{
|
|
float temp;
|
|
|
|
// convert ms into 2.78ms increments
|
|
temp = iTimeMS;
|
|
temp /= 2.78;
|
|
temp = 256 - temp;
|
|
if (temp > 255) temp = 255;
|
|
if (temp < 0) temp = 0;
|
|
|
|
/* Update the timing register */
|
|
write8(APDS9960_ATIME, (uint8_t)temp);
|
|
}
|
|
|
|
float Adafruit_APDS9960::getADCIntegrationTime(void)
|
|
{
|
|
float temp;
|
|
|
|
temp = read8(APDS9960_ATIME);
|
|
|
|
// convert to units of 2.78 ms
|
|
temp = 256 - temp;
|
|
temp *= 2.78;
|
|
return temp;
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
Adjusts the color/ALS gain on the APDS9960 (adjusts the sensitivity to light)
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_APDS9960::setADCGain(apds9960AGain_t aGain)
|
|
{
|
|
_control.AGAIN = aGain;
|
|
|
|
/* Update the timing register */
|
|
write8(APDS9960_CONTROL, _control.get());
|
|
}
|
|
|
|
apds9960AGain_t Adafruit_APDS9960::getADCGain(void)
|
|
{
|
|
return (apds9960AGain_t) ( read8(APDS9960_CONTROL) & 0x03 );
|
|
}
|
|
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
Adjusts the Proximity gain on the APDS9960
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_APDS9960::setProxGain(apds9960PGain_t pGain)
|
|
{
|
|
_control.PGAIN = pGain;
|
|
|
|
/* Update the timing register */
|
|
write8(APDS9960_CONTROL, _control.get());
|
|
}
|
|
|
|
apds9960PGain_t Adafruit_APDS9960::getProxGain(void)
|
|
{
|
|
return (apds9960PGain_t) ( read8(APDS9960_CONTROL) & 0x0C );
|
|
}
|
|
|
|
void Adafruit_APDS9960::setProxPulse(apds9960PPulseLen_t pLen, uint8_t pulses) {
|
|
if (pulses < 1) pulses = 1;
|
|
if (pulses > 64) pulses = 64;
|
|
pulses--;
|
|
|
|
_ppulse.PPLEN = pLen;
|
|
_ppulse.PPULSE = pulses;
|
|
|
|
write8(APDS9960_PPULSE, _ppulse.get());
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
Enable proximity readings on APDS9960
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_APDS9960::enableProximity(boolean en)
|
|
{
|
|
_enable.PEN = en;
|
|
|
|
write8(APDS9960_ENABLE, _enable.get());
|
|
}
|
|
|
|
void Adafruit_APDS9960::enableProximityInterrupt() {
|
|
_enable.PIEN = 1;
|
|
write8(APDS9960_ENABLE, _enable.get());
|
|
clearInterrupt();
|
|
}
|
|
|
|
void Adafruit_APDS9960::disableProximityInterrupt() {
|
|
_enable.PIEN = 0;
|
|
write8(APDS9960_ENABLE, _enable.get());
|
|
}
|
|
|
|
void Adafruit_APDS9960::setProximityInterruptThreshold(uint8_t low, uint8_t high, uint8_t persistance){
|
|
write8(APDS9960_PILT, low);
|
|
write8(APDS9960_PIHT, high);
|
|
|
|
if (persistance > 7) persistance = 7;
|
|
_pers.PPERS = persistance;
|
|
write8(APDS9960_PERS,_pers.get());
|
|
}
|
|
|
|
bool Adafruit_APDS9960::getProximityInterrupt()
|
|
{
|
|
_status.set(this->read8(APDS9960_STATUS));
|
|
return _status.PINT;
|
|
};
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
Read proximity data
|
|
*/
|
|
/**************************************************************************/
|
|
uint8_t Adafruit_APDS9960::readProximity(void)
|
|
{
|
|
return read8(APDS9960_PDATA);
|
|
}
|
|
|
|
|
|
bool Adafruit_APDS9960::gestureValid()
|
|
{
|
|
_gstatus.set(this->read8(APDS9960_GSTATUS));
|
|
return _gstatus.GVALID;
|
|
}
|
|
|
|
void Adafruit_APDS9960::setGestureDimensions(uint8_t dims)
|
|
{
|
|
_gconf3.GDIMS = dims;
|
|
this->write8(APDS9960_GCONF3, _gconf3.get());
|
|
}
|
|
|
|
void Adafruit_APDS9960::setGestureFIFOThreshold(uint8_t thresh)
|
|
{
|
|
_gconf1.GFIFOTH = thresh;
|
|
this->write8(APDS9960_GCONF1, _gconf1.get());
|
|
}
|
|
|
|
void Adafruit_APDS9960::setGestureGain(uint8_t gain)
|
|
{
|
|
_gconf2.GGAIN = gain;
|
|
this->write8(APDS9960_GCONF2, _gconf2.get());
|
|
}
|
|
|
|
void Adafruit_APDS9960::setGestureProximityThreshold(uint8_t thresh)
|
|
{
|
|
this->write8(APDS9960_GPENTH, thresh);
|
|
}
|
|
|
|
void Adafruit_APDS9960::setGestureOffset(uint8_t offset_up, uint8_t offset_down, uint8_t offset_left, uint8_t offset_right)
|
|
{
|
|
this->write8(APDS9960_GOFFSET_U, offset_up);
|
|
this->write8(APDS9960_GOFFSET_D, offset_down);
|
|
this->write8(APDS9960_GOFFSET_L, offset_left);
|
|
this->write8(APDS9960_GOFFSET_R, offset_right);
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
Enable gesture readings on APDS9960
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_APDS9960::enableGesture(boolean en)
|
|
{
|
|
if(!en){
|
|
_gconf4.GMODE = 0;
|
|
write8(APDS9960_GCONF4, _gconf4.get());
|
|
}
|
|
_enable.GEN = en;
|
|
write8(APDS9960_ENABLE, _enable.get());
|
|
resetCounts();
|
|
}
|
|
|
|
void Adafruit_APDS9960::resetCounts()
|
|
{
|
|
gestCnt = 0;
|
|
UCount = 0;
|
|
DCount = 0;
|
|
LCount = 0;
|
|
RCount = 0;
|
|
}
|
|
|
|
uint8_t Adafruit_APDS9960::readGesture(void)
|
|
{
|
|
uint8_t toRead, bytesRead;
|
|
uint8_t buf[256];
|
|
unsigned long t;
|
|
uint8_t gestureReceived;
|
|
while(1){
|
|
int up_down_diff = 0;
|
|
int left_right_diff = 0;
|
|
gestureReceived = 0;
|
|
if(!gestureValid()) return 0;
|
|
|
|
delay(30);
|
|
toRead = this->read8(APDS9960_GFLVL);
|
|
|
|
bytesRead = this->read(APDS9960_GFIFO_U, buf, toRead);
|
|
|
|
if(abs((int)buf[0] - (int)buf[1]) > 13)
|
|
up_down_diff += (int)buf[0] - (int)buf[1];
|
|
|
|
if(abs((int)buf[2] - (int)buf[3]) > 13)
|
|
left_right_diff += (int)buf[2] - (int)buf[3];
|
|
|
|
if(up_down_diff != 0){
|
|
if(up_down_diff < 0){
|
|
if( DCount > 0){
|
|
gestureReceived = APDS9960_UP;
|
|
}
|
|
else UCount++;
|
|
}
|
|
else if(up_down_diff > 0){
|
|
if( UCount > 0){
|
|
gestureReceived = APDS9960_DOWN;
|
|
}
|
|
else DCount++;
|
|
}
|
|
}
|
|
|
|
if(left_right_diff != 0){
|
|
if(left_right_diff < 0){
|
|
if( RCount > 0){
|
|
gestureReceived = APDS9960_LEFT;
|
|
}
|
|
else LCount++;
|
|
}
|
|
else if(left_right_diff > 0){
|
|
if( LCount > 0){
|
|
gestureReceived = APDS9960_RIGHT;
|
|
}
|
|
else RCount++;
|
|
}
|
|
}
|
|
|
|
if(up_down_diff != 0 || left_right_diff != 0) t = millis();
|
|
|
|
if(gestureReceived || millis() - t > 300){
|
|
resetCounts();
|
|
return gestureReceived;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
Set LED brightness for proximity/gesture
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_APDS9960::setLED(apds9960LedDrive_t drive, apds9960LedBoost_t boost) {
|
|
// set BOOST
|
|
_config2.LED_BOOST = boost;
|
|
write8(APDS9960_CONFIG2, _config2.get());
|
|
|
|
_control.LDRIVE = drive;
|
|
write8(APDS9960_CONTROL, _control.get());
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
Enable proximity readings on APDS9960
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_APDS9960::enableColor(boolean en)
|
|
{
|
|
_enable.AEN = en;
|
|
write8(APDS9960_ENABLE, _enable.get());
|
|
}
|
|
|
|
bool Adafruit_APDS9960::colorDataReady()
|
|
{
|
|
_status.set(this->read8(APDS9960_STATUS));
|
|
return _status.AVALID;
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
@brief Reads the raw red, green, blue and clear channel values
|
|
*/
|
|
/**************************************************************************/
|
|
void Adafruit_APDS9960::getColorData (uint16_t *r, uint16_t *g, uint16_t *b, uint16_t *c)
|
|
{
|
|
|
|
*c = read16R(APDS9960_CDATAL);
|
|
*r = read16R(APDS9960_RDATAL);
|
|
*g = read16R(APDS9960_GDATAL);
|
|
*b = read16R(APDS9960_BDATAL);
|
|
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
@brief Converts the raw R/G/B values to color temperature in degrees
|
|
Kelvin
|
|
*/
|
|
/**************************************************************************/
|
|
uint16_t Adafruit_APDS9960::calculateColorTemperature(uint16_t r, uint16_t g, uint16_t b)
|
|
{
|
|
float X, Y, Z; /* RGB to XYZ correlation */
|
|
float xc, yc; /* Chromaticity co-ordinates */
|
|
float n; /* McCamy's formula */
|
|
float cct;
|
|
|
|
/* 1. Map RGB values to their XYZ counterparts. */
|
|
/* Based on 6500K fluorescent, 3000K fluorescent */
|
|
/* and 60W incandescent values for a wide range. */
|
|
/* Note: Y = Illuminance or lux */
|
|
X = (-0.14282F * r) + (1.54924F * g) + (-0.95641F * b);
|
|
Y = (-0.32466F * r) + (1.57837F * g) + (-0.73191F * b);
|
|
Z = (-0.68202F * r) + (0.77073F * g) + ( 0.56332F * b);
|
|
|
|
/* 2. Calculate the chromaticity co-ordinates */
|
|
xc = (X) / (X + Y + Z);
|
|
yc = (Y) / (X + Y + Z);
|
|
|
|
/* 3. Use McCamy's formula to determine the CCT */
|
|
n = (xc - 0.3320F) / (0.1858F - yc);
|
|
|
|
/* Calculate the final CCT */
|
|
cct = (449.0F * powf(n, 3)) + (3525.0F * powf(n, 2)) + (6823.3F * n) + 5520.33F;
|
|
|
|
/* Return the results in degrees Kelvin */
|
|
return (uint16_t)cct;
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*!
|
|
@brief Calculate ambient light values
|
|
*/
|
|
/**************************************************************************/
|
|
|
|
uint16_t Adafruit_APDS9960::calculateLux(uint16_t r, uint16_t g, uint16_t b)
|
|
{
|
|
float illuminance;
|
|
|
|
/* This only uses RGB ... how can we integrate clear or calculate lux */
|
|
/* based exclusively on clear since this might be more reliable? */
|
|
illuminance = (-0.32466F * r) + (1.57837F * g) + (-0.73191F * b);
|
|
|
|
return (uint16_t)illuminance;
|
|
}
|
|
|
|
void Adafruit_APDS9960::enableColorInterrupt() {
|
|
_enable.AIEN = 1;
|
|
write8(APDS9960_ENABLE, _enable.get());
|
|
}
|
|
|
|
void Adafruit_APDS9960::disableColorInterrupt() {
|
|
_enable.AIEN = 0;
|
|
write8(APDS9960_ENABLE, _enable.get());
|
|
}
|
|
|
|
void Adafruit_APDS9960::clearInterrupt(void) {
|
|
this->write(APDS9960_AICLEAR, NULL, 0);
|
|
}
|
|
|
|
|
|
void Adafruit_APDS9960::setIntLimits(uint16_t low, uint16_t high) {
|
|
write8(APDS9960_AILTIL, low & 0xFF);
|
|
write8(APDS9960_AILTH, low >> 8);
|
|
write8(APDS9960_AIHTL, high & 0xFF);
|
|
write8(APDS9960_AIHTH, high >> 8);
|
|
}
|
|
|
|
void Adafruit_APDS9960::write8(byte reg, byte value)
|
|
{
|
|
this->write(reg, &value, 1);
|
|
}
|
|
|
|
uint8_t Adafruit_APDS9960::read8(byte reg)
|
|
{
|
|
uint8_t ret;
|
|
this->read(reg, &ret, 1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
uint32_t Adafruit_APDS9960::read32(uint8_t reg)
|
|
{
|
|
uint8_t ret[4];
|
|
this->read(reg, ret, 4);
|
|
|
|
return (ret[0] << 24) | (ret[1] << 16) | (ret[2] << 8) | ret[3];
|
|
}
|
|
|
|
uint16_t Adafruit_APDS9960::read16(uint8_t reg)
|
|
{
|
|
uint8_t ret[2];
|
|
this->read(reg, ret, 2);
|
|
|
|
return (ret[0] << 8) | ret[1];
|
|
}
|
|
|
|
uint16_t Adafruit_APDS9960::read16R(uint8_t reg)
|
|
{
|
|
uint8_t ret[2];
|
|
this->read(reg, ret, 2);
|
|
|
|
return (ret[1] << 8) | ret[0];
|
|
}
|
|
|
|
void Adafruit_APDS9960::_i2c_init()
|
|
{
|
|
Wire.begin();
|
|
}
|
|
|
|
uint8_t Adafruit_APDS9960::read(uint8_t reg, uint8_t *buf, uint8_t num)
|
|
{
|
|
uint8_t value;
|
|
uint8_t pos = 0;
|
|
bool eof = false;
|
|
|
|
//on arduino we need to read in 32 byte chunks
|
|
while(pos < num && !eof){
|
|
|
|
uint8_t read_now = min(32, num - pos);
|
|
Wire.beginTransmission((uint8_t)_i2caddr);
|
|
Wire.write((uint8_t)reg + pos);
|
|
Wire.endTransmission();
|
|
|
|
Wire.requestFrom((uint8_t)_i2caddr, read_now);
|
|
|
|
for(int i=0; i<read_now; i++){
|
|
if(!Wire.available()){
|
|
eof = true;
|
|
break;
|
|
}
|
|
buf[pos] = Wire.read();
|
|
pos++;
|
|
}
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
void Adafruit_APDS9960::write(uint8_t reg, uint8_t *buf, uint8_t num)
|
|
{
|
|
Wire.beginTransmission((uint8_t)_i2caddr);
|
|
Wire.write((uint8_t)reg);
|
|
Wire.write((uint8_t *)buf, num);
|
|
Wire.endTransmission();
|
|
}
|