weatherstation/firmware/libraries/Adafruit_APDS9960_Library/Adafruit_APDS9960.cpp

727 lines
18 KiB
C++
Executable File

/*!
* @file Adafruit_APDS9960.cpp
*
* @mainpage Adafruit APDS9960 Proximity, Light, RGB, and Gesture Sensor
*
* @section author Author
*
* Ladyada, Dean Miller (Adafruit Industries)
*
* @section license 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 <math.h>
#include <stdlib.h>
#include "Adafruit_APDS9960.h"
/*!
* @brief Implements missing powf function
* @param x
* Base number
* @param y
* Exponent
* @return x raised to the power of y
*/
float powf(const float x, const float y) {
return (float)(pow((double)x, (double)y));
}
/*!
* @brief Enables the device
* Disables the device (putting it in lower power sleep mode)
* @param en
* Enable (True/False)
*/
void Adafruit_APDS9960::enable(boolean en) {
_enable.PON = en;
this->write8(APDS9960_ENABLE, _enable.get());
}
/*!
* @brief Initializes I2C and configures the sensor
* @param iTimeMS
* Integration time
* @param aGain
* Gain
* @param addr
* I2C address
* @param *theWire
* Wire object
* @return True if initialization was successful, otherwise false.
*/
boolean Adafruit_APDS9960::begin(uint16_t iTimeMS, apds9960AGain_t aGain,
uint8_t addr, TwoWire *theWire) {
_wire = theWire;
_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;
}
/*!
* @brief Sets the integration time for the ADC of the APDS9960, in millis
* @param iTimeMS
* Integration time
*/
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);
}
/*!
* @brief Returns the integration time for the ADC of the APDS9960, in millis
* @return Integration time
*/
float Adafruit_APDS9960::getADCIntegrationTime() {
float temp;
temp = read8(APDS9960_ATIME);
// convert to units of 2.78 ms
temp = 256 - temp;
temp *= 2.78;
return temp;
}
/*!
* @brief Adjusts the color/ALS gain on the APDS9960 (adjusts the sensitivity
* to light)
* @param aGain
* Gain
*/
void Adafruit_APDS9960::setADCGain(apds9960AGain_t aGain) {
_control.AGAIN = aGain;
/* Update the timing register */
write8(APDS9960_CONTROL, _control.get());
}
/*!
* @brief Returns the ADC gain
* @return ADC gain
*/
apds9960AGain_t Adafruit_APDS9960::getADCGain() {
return (apds9960AGain_t)(read8(APDS9960_CONTROL) & 0x03);
}
/*!
* @brief Adjusts the Proximity gain on the APDS9960
* @param pGain
* Gain
*/
void Adafruit_APDS9960::setProxGain(apds9960PGain_t pGain) {
_control.PGAIN = pGain;
/* Update the timing register */
write8(APDS9960_CONTROL, _control.get());
}
/*!
* @brief Returns the Proximity gain on the APDS9960
* @return Proxmity gain
*/
apds9960PGain_t Adafruit_APDS9960::getProxGain() {
return (apds9960PGain_t)(read8(APDS9960_CONTROL) & 0x0C);
}
/*!
* @brief Sets number of proxmity pulses
* @param pLen
* Pulse Length
* @param pulses
* Number of pulses
*/
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());
}
/*!
* @brief Enable proximity readings on APDS9960
* @param en
* Enable (True/False)
*/
void Adafruit_APDS9960::enableProximity(boolean en) {
_enable.PEN = en;
write8(APDS9960_ENABLE, _enable.get());
}
/*!
* @brief Enable proximity interrupts
*/
void Adafruit_APDS9960::enableProximityInterrupt() {
_enable.PIEN = 1;
write8(APDS9960_ENABLE, _enable.get());
clearInterrupt();
}
/*!
* @brief Disable proximity interrupts
*/
void Adafruit_APDS9960::disableProximityInterrupt() {
_enable.PIEN = 0;
write8(APDS9960_ENABLE, _enable.get());
}
/*!
* @brief Set proxmity interrupt thresholds
* @param low
* Low threshold
* @param high
* High threshold
* @param persistance
* Persistance
*/
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());
}
/*!
* @brief Returns proxmity interrupt status
* @return True if enabled, false otherwise.
*/
bool Adafruit_APDS9960::getProximityInterrupt() {
_status.set(this->read8(APDS9960_STATUS));
return _status.PINT;
};
/*!
* @brief Read proximity data
* @return Proximity
*/
uint8_t Adafruit_APDS9960::readProximity() { return read8(APDS9960_PDATA); }
/*!
* @brief Returns validity status of a gesture
* @return Status (True/False)
*/
bool Adafruit_APDS9960::gestureValid() {
_gstatus.set(this->read8(APDS9960_GSTATUS));
return _gstatus.GVALID;
}
/*!
* @brief Sets gesture dimensions
* @param dims
* Dimensions (APDS9960_DIMENSIONS_ALL, APDS9960_DIMENSIONS_UP_DOWM,
* APDS9960_DIMENSIONS_UP_DOWN, APGS9960_DIMENSIONS_LEFT_RIGHT)
*/
void Adafruit_APDS9960::setGestureDimensions(uint8_t dims) {
_gconf3.GDIMS = dims;
this->write8(APDS9960_GCONF3, _gconf3.get());
}
/*!
* @brief Sets gesture FIFO Threshold
* @param thresh
* Threshold (APDS9960_GFIFO_1, APDS9960_GFIFO_4, APDS9960_GFIFO_8,
* APDS9960_GFIFO_16)
*/
void Adafruit_APDS9960::setGestureFIFOThreshold(uint8_t thresh) {
_gconf1.GFIFOTH = thresh;
this->write8(APDS9960_GCONF1, _gconf1.get());
}
/*!
* @brief Sets gesture sensor gain
* @param gain
* Gain (APDS9960_GAIN_1, APDS9960_GAIN_2, APDS9960_GAIN_4,
* APDS9960_GAIN_8)
*/
void Adafruit_APDS9960::setGestureGain(uint8_t gain) {
_gconf2.GGAIN = gain;
this->write8(APDS9960_GCONF2, _gconf2.get());
}
/*!
* @brief Sets gesture sensor threshold
* @param thresh
* Threshold
*/
void Adafruit_APDS9960::setGestureProximityThreshold(uint8_t thresh) {
this->write8(APDS9960_GPENTH, thresh);
}
/*!
* @brief Sets gesture sensor offset
* @param offset_up
* Up offset
* @param offset_down
* Down offset
* @param offset_left
* Left offset
* @param offset_right
* Right offset
*/
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);
}
/*!
* @brief Enable gesture readings on APDS9960
* @param en
* Enable (True/False)
*/
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();
}
/*!
* @brief Resets gesture counts
*/
void Adafruit_APDS9960::resetCounts() {
gestCnt = 0;
UCount = 0;
DCount = 0;
LCount = 0;
RCount = 0;
}
/*!
* @brief Reads gesture
* @return Received gesture (APDS9960_DOWN APDS9960_UP, APDS9960_LEFT
* APDS9960_RIGHT)
*/
uint8_t Adafruit_APDS9960::readGesture() {
uint8_t toRead, bytesRead;
uint8_t buf[256];
unsigned long t = 0;
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 is unused but produces sideffects needed for readGesture to work
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;
}
}
}
/*!
* @brief Set LED brightness for proximity/gesture
* @param drive
* LED Drive
* @param boost
* LED Boost
*/
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());
}
/*!
* @brief Enable proximity readings on APDS9960
* @param en
* Enable (True/False)
*/
void Adafruit_APDS9960::enableColor(boolean en) {
_enable.AEN = en;
write8(APDS9960_ENABLE, _enable.get());
}
/*!
* @brief Returns status of color data
* @return True if color data ready, False otherwise
*/
bool Adafruit_APDS9960::colorDataReady() {
_status.set(this->read8(APDS9960_STATUS));
return _status.AVALID;
}
/*!
* @brief Reads the raw red, green, blue and clear channel values
* @param *r
* Red value
* @param *g
* Green value
* @param *b
* Blue value
* @param *c
* Clear channel value
*/
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
* @param r
* Red value
* @param g
* Green value
* @param b
* Blue value
* @return Color temperature
*/
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
* @param r
* Red value
* @param g
* Green value
* @param b
* Blue value
* @return LUX value
*/
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;
}
/*!
* @brief Enables color interrupt
*/
void Adafruit_APDS9960::enableColorInterrupt() {
_enable.AIEN = 1;
write8(APDS9960_ENABLE, _enable.get());
}
/*!
* @brief Disables color interrupt
*/
void Adafruit_APDS9960::disableColorInterrupt() {
_enable.AIEN = 0;
write8(APDS9960_ENABLE, _enable.get());
}
/*!
* @brief Clears interrupt
*/
void Adafruit_APDS9960::clearInterrupt() {
this->write(APDS9960_AICLEAR, NULL, 0);
}
/*!
* @brief Sets interrupt limits
* @param low
* Low limit
* @param high
* High limit
*/
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);
}
/*!
* @brief Writes specified value to given register
* @param reg
* Register to write to
* @param value
* Value to write
*/
void Adafruit_APDS9960::write8(byte reg, byte value) {
this->write(reg, &value, 1);
}
/*!
* @brief Reads 8 bits from specified register
* @param reg
* Register to write to
* @return Value in register
*/
uint8_t Adafruit_APDS9960::read8(byte reg) {
uint8_t ret;
this->read(reg, &ret, 1);
return ret;
}
/*!
* @brief Reads 32 bits from specified register
* @param reg
* Register to write to
* @return Value in register
*/
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];
}
/*!
* @brief Reads 16 bites from specified register
* @param reg
* Register to write to
* @return Value in register
*/
uint16_t Adafruit_APDS9960::read16(uint8_t reg) {
uint8_t ret[2];
this->read(reg, ret, 2);
return (ret[0] << 8) | ret[1];
}
/*!
* @brief Reads 16 bites from specified register
* @param reg
* Register to write to
* @return Value in register
*/
uint16_t Adafruit_APDS9960::read16R(uint8_t reg) {
uint8_t ret[2];
this->read(reg, ret, 2);
return (ret[1] << 8) | ret[0];
}
/*!
* @brief Begins I2C communication
*/
void Adafruit_APDS9960::_i2c_init() { _wire->begin(); }
/*!
* @brief Reads num bytes from specified register into a given buffer
* @param reg
* Register
* @param *buf
* Buffer
* @param num
* Number of bytes
* @return Position after reading
*/
uint8_t Adafruit_APDS9960::read(uint8_t reg, uint8_t *buf, uint8_t num) {
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;
}
/*!
* @brief Writes num bytes from specified buffer into a given register
* @param reg
* Register
* @param *buf
* Buffer
* @param num
* Number of bytes
*/
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();
}