File apds9960.h¶
File List > dev > apds9960.h
Go to the documentation of this file
Source Code¶
#pragma once
#ifndef DSY_APDS9960_H
#define DSY_APDS9960_H
#define APDS9960_ADDRESS (0x39)
#define APDS9960_UP 0x01
#define APDS9960_DOWN 0x02
#define APDS9960_LEFT 0x03
#define APDS9960_RIGHT 0x04
#define APDS9960_RAM 0x00
#define APDS9960_ENABLE 0x80
#define APDS9960_ATIME 0x81
#define APDS9960_WTIME 0x83
#define APDS9960_AILTIL 0x84
#define APDS9960_AILTH 0x85
#define APDS9960_AIHTL 0x86
#define APDS9960_AIHTH 0x87
#define APDS9960_PILT 0x89
#define APDS9960_PIHT 0x8B
#define APDS9960_PERS 0x8C
#define APDS9960_CONFIG1 0x8D
#define APDS9960_PPULSE 0x8E
#define APDS9960_CONTROL 0x8F
#define APDS9960_CONFIG2 0x90
#define APDS9960_ID 0x92
#define APDS9960_STATUS 0x93
#define APDS9960_CDATAL 0x94
#define APDS9960_CDATAH 0x95
#define APDS9960_RDATAL 0x96
#define APDS9960_RDATAH 0x97
#define APDS9960_GDATAL 0x98
#define APDS9960_GDATAH 0x99
#define APDS9960_BDATAL 0x9A
#define APDS9960_BDATAH 0x9B
#define APDS9960_PDATA 0x9C
#define APDS9960_POFFSET_UR 0x9D
#define APDS9960_POFFSET_DL 0x9E
#define APDS9960_CONFIG3 0x9F
#define APDS9960_GPENTH 0xA0
#define APDS9960_GEXTH 0xA1
#define APDS9960_GCONF1 0xA2
#define APDS9960_GCONF2 0xA3
#define APDS9960_GOFFSET_U 0xA4
#define APDS9960_GOFFSET_D 0xA5
#define APDS9960_GOFFSET_L 0xA7
#define APDS9960_GOFFSET_R 0xA9
#define APDS9960_GPULSE 0xA6
#define APDS9960_GCONF3 0xAA
#define APDS9960_GCONF4 0xAB
#define APDS9960_GFLVL 0xAE
#define APDS9960_GSTATUS 0xAF
#define APDS9960_IFORCE 0xE4
#define APDS9960_PICLEAR 0xE5
#define APDS9960_CICLEAR 0xE6
#define APDS9960_AICLEAR 0xE7
#define APDS9960_GFIFO_U 0xFC
#define APDS9960_GFIFO_D 0xFD
#define APDS9960_GFIFO_L 0xFE
#define APDS9960_GFIFO_R 0xFF
namespace daisy
{
class Apds9960I2CTransport
{
public:
Apds9960I2CTransport() {}
~Apds9960I2CTransport() {}
struct Config
{
I2CHandle::Config::Peripheral periph;
I2CHandle::Config::Speed speed;
Pin scl;
Pin sda;
Config()
{
periph = I2CHandle::Config::Peripheral::I2C_1;
speed = I2CHandle::Config::Speed::I2C_100KHZ;
scl = Pin(PORTB, 8);
sda = Pin(PORTB, 9);
}
};
inline bool Init(Config config)
{
I2CHandle::Config i2c_config;
i2c_config.mode = I2CHandle::Config::Mode::I2C_MASTER;
i2c_config.periph = config.periph;
i2c_config.speed = config.speed;
i2c_config.pin_config.scl = config.scl;
i2c_config.pin_config.sda = config.sda;
return I2CHandle::Result::OK != i2c_.Init(i2c_config);
}
bool Write(uint8_t *data, uint16_t size)
{
return I2CHandle::Result::OK
!= i2c_.TransmitBlocking(APDS9960_ADDRESS, data, size, 10);
}
bool Read(uint8_t *data, uint16_t size)
{
return I2CHandle::Result::OK
!= i2c_.ReceiveBlocking(APDS9960_ADDRESS, data, size, 10);
}
private:
I2CHandle i2c_;
};
template <typename Transport>
class Apds9960
{
public:
Apds9960() {}
~Apds9960() {}
struct Config
{
uint16_t integrationTimeMs;
uint8_t adcGain; // (0,3) -> {1x, 4x, 16x, 64x}
uint8_t gestureDimensions; // (0,2) -> {all, up/down, left/right}
uint8_t
gestureFifoThresh; // (0,3) -> interrupt after 1 dataset in fifo, 2, 3, 4
uint8_t gestureGain; // (0,3) -> {1x, 2x, 4x, 8x}
uint16_t gestureProximityThresh;
bool color_mode;
bool prox_mode;
bool gesture_mode;
typename Transport::Config transport_config;
Config()
{
integrationTimeMs = 10;
adcGain = 1; // 4x
gestureDimensions = 0; // gesture all
gestureFifoThresh = 1; // interrupt w/ 2 datasets in fifo
gestureGain = 2; // 4x gesture gain
gestureProximityThresh = 40;
color_mode = true;
prox_mode = true;
gesture_mode = false;
}
};
enum Result
{
OK = 0,
ERR
};
// turn on/off elements
void enable(bool en = true);
Result Init(Config config)
{
config_ = config;
transport_error_ = false;
SetTransportErr(transport_.Init(config_.transport_config));
/* Set default integration time and gain */
SetADCIntegrationTime(config_.integrationTimeMs);
SetADCGain(config.adcGain);
// disable everything to start
EnableGesture(false);
EnableProximity(false);
EnableColor(false);
SetColorInterrupt(false);
SetProximityInterrupt(false);
ClearInterrupt();
/* Note: by default, the device is in power down mode on bootup */
Enable(false);
System::Delay(10);
Enable(true);
System::Delay(10);
// default to all gesture dimensions
SetGestureDimensions(config_.gestureDimensions);
SetGestureFIFOThreshold(config_.gestureFifoThresh);
SetGestureGain(config_.gestureGain);
SetGestureProximityThreshold(config_.gestureProximityThresh);
ResetCounts();
gpulse_.GPLEN = 0x03; // 32 us
gpulse_.GPULSE = 9; // 10 pulses
Write8(APDS9960_GPULSE, gpulse_.get());
// prox / color mode by default
// only one gesture or prox can be used at a time
// in gesture mode you should have prox mode on also, the data will just not be useful
EnableGesture(config_.gesture_mode);
EnableProximity(config_.prox_mode);
EnableColor(config_.color_mode);
return GetTransportErr();
}
void SetADCIntegrationTime(uint16_t iTimeMS)
{
float temp;
// convert ms into 2.78ms increments
temp = iTimeMS;
temp /= 2.78f;
temp = 256.f - temp;
if(temp > 255.f)
temp = 255.f;
if(temp < 0.f)
temp = 0.f;
/* Update the timing register */
Write8(APDS9960_ATIME, (uint8_t)temp);
}
float GetADCIntegrationTime()
{
float temp;
temp = Read8(APDS9960_ATIME);
// convert to units of 2.78 ms
temp = 256 - temp;
temp *= 2.78;
return temp;
}
void SetADCGain(uint8_t aGain)
{
control_.AGAIN = aGain;
Write8(APDS9960_CONTROL, control_.get());
}
void SetGestureOffset(uint8_t offset_up,
uint8_t offset_down,
uint8_t offset_left,
uint8_t offset_right)
{
Write8(APDS9960_GOFFSET_U, offset_up);
Write8(APDS9960_GOFFSET_D, offset_down);
Write8(APDS9960_GOFFSET_L, offset_left);
Write8(APDS9960_GOFFSET_R, offset_right);
}
void SetGestureDimensions(uint8_t dims)
{
Write8(APDS9960_GCONF3, dims & 0x03);
}
void SetGestureFIFOThreshold(uint8_t thresh)
{
gconf1_.GFIFOTH = thresh;
Write8(APDS9960_GCONF1, gconf1_.get());
}
void SetGestureGain(uint8_t gain)
{
gconf2_.GGAIN = gain;
Write8(APDS9960_GCONF2, gconf2_.get());
}
void SetGestureProximityThreshold(uint8_t thresh)
{
Write8(APDS9960_GPENTH, thresh);
}
void Enable(bool en)
{
enable_.PON = en;
Write8(APDS9960_ENABLE, enable_.get());
}
void EnableGesture(bool en)
{
if(!en)
{
gconf4_.GMODE = 0;
Write8(APDS9960_GCONF4, gconf4_.get());
}
enable_.GEN = en;
Write8(APDS9960_ENABLE, enable_.get());
ResetCounts();
}
void EnableProximity(bool en)
{
enable_.PEN = en;
Write8(APDS9960_ENABLE, enable_.get());
}
void EnableColor(bool en)
{
enable_.AEN = en;
Write8(APDS9960_ENABLE, enable_.get());
}
void SetColorInterrupt(bool en)
{
enable_.AIEN = en;
Write8(APDS9960_ENABLE, enable_.get());
}
void SetProximityInterrupt(bool en)
{
enable_.PIEN = en;
Write8(APDS9960_ENABLE, enable_.get());
}
void ClearInterrupt()
{
uint8_t val = APDS9960_AICLEAR;
SetTransportErr(transport_.Write(&val, 1));
}
void ResetCounts()
{
gestCnt_ = 0;
UCount_ = 0;
DCount_ = 0;
LCount_ = 0;
RCount_ = 0;
}
void Write8(uint8_t reg, uint8_t data)
{
uint8_t buff[2] = {reg, data};
SetTransportErr(transport_.Write(buff, 2));
}
uint8_t Read8(uint8_t reg)
{
uint8_t buff[1] = {reg};
SetTransportErr(transport_.Write(buff, 1));
SetTransportErr(transport_.Read(buff, 1));
return buff[0];
}
uint16_t Read16R(uint8_t reg)
{
uint8_t ret[2];
SetTransportErr(transport_.Write(®, 1));
SetTransportErr(transport_.Read(ret, 2));
return (ret[1] << 8) | ret[0];
}
uint8_t ReadProximity() { return Read8(APDS9960_PDATA); }
void SetProxGain(uint8_t pGain)
{
control_.PGAIN = pGain;
/* Update the timing register */
Write8(APDS9960_CONTROL, control_.get());
}
uint8_t GetProxGain() { return ((Read8(APDS9960_CONTROL) & 0x0C) >> 2); }
void SetProxPulse(uint8_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());
}
bool GestureValid()
{
gstatus_.set(Read8(APDS9960_GSTATUS));
return gstatus_.GVALID;
}
uint8_t ReadGesture()
{
uint8_t toRead;
uint8_t buf[256];
unsigned long t = 0;
uint8_t gestureReceived;
while(true)
{
int up_down_diff = 0;
int left_right_diff = 0;
gestureReceived = 0;
if(!GestureValid())
return 0;
System::Delay(30);
toRead = Read8(APDS9960_GFLVL);
// produces sideffects needed for readGesture to work
uint8_t reg = APDS9960_GFIFO_U;
SetTransportErr(transport_.Write(®, 1));
SetTransportErr(transport_.Read(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 = System::GetNow();
if(gestureReceived || System::GetNow() - t > 300)
{
ResetCounts();
return gestureReceived;
}
}
}
void SetLED(uint8_t drive, uint8_t boost)
{
config2_.LED_BOOST = boost;
Write8(APDS9960_CONFIG2, config2_.get());
control_.LDRIVE = drive;
rite8(APDS9960_CONTROL, control_.get());
}
uint16_t 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;
}
uint16_t 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 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);
}
bool ColorDataReady()
{
status_.set(Read8(APDS9960_STATUS));
return status_.AVALID;
}
uint16_t GetColorDataRed() { return Read16R(APDS9960_RDATAL); }
uint16_t GetColorDataGreen() { return Read16R(APDS9960_GDATAL); }
uint16_t GetColorDataBlue() { return Read16R(APDS9960_BDATAL); }
uint16_t GetColorDataClear() { return Read16R(APDS9960_CDATAL); }
void GetColorData(uint16_t *r, uint16_t *g, uint16_t *b, uint16_t *c)
{
*c = GetColorDataClear();
*r = GetColorDataRed();
*g = GetColorDataGreen();
*b = GetColorDataBlue();
}
private:
uint8_t gestCnt_, UCount_, DCount_, LCount_, RCount_; // counters
uint8_t gestureReceived_;
bool transport_error_;
Config config_;
Transport transport_;
void SetTransportErr(bool err) { transport_error_ |= err; }
Result GetTransportErr()
{
Result ret = transport_error_ ? ERR : OK;
transport_error_ = false;
return ret;
}
struct gconf1
{
uint8_t GEXPERS : 2; // Gesture Exit Persistence
uint8_t GEXMSK : 4; // Gesture Exit Mask
uint8_t GFIFOTH : 2; // Gesture FIFO Threshold
uint8_t get() { return (GFIFOTH << 6) | (GEXMSK << 2) | GEXPERS; }
};
gconf1 gconf1_;
struct gconf2
{
uint8_t GWTIME : 3; // Gesture Wait Time
uint8_t GLDRIVE : 2; // Gesture LED Drive Strength
uint8_t GGAIN : 2; // Gesture Gain Control
uint8_t get() { return (GGAIN << 5) | (GLDRIVE << 3) | GWTIME; }
};
gconf2 gconf2_;
struct gconf4
{
uint8_t GMODE : 1; // Gesture mode
uint8_t GIEN : 2; // Gesture Interrupt Enable
uint8_t get() { return (GIEN << 1) | GMODE; }
void set(uint8_t data)
{
GIEN = (data >> 1) & 0x01;
GMODE = data & 0x01;
}
};
gconf4 gconf4_;
struct control
{
uint8_t AGAIN : 2; // ALS and Color gain control
uint8_t PGAIN : 2; // proximity gain control
uint8_t LDRIVE : 2; // led drive strength
uint8_t get() { return (LDRIVE << 6) | (PGAIN << 2) | AGAIN; }
};
control control_;
struct ppulse
{
uint8_t PPULSE : 6; //Proximity Pulse Count
uint8_t PPLEN : 2; // Proximity Pulse Length
uint8_t get() { return (PPLEN << 6) | PPULSE; }
};
ppulse ppulse_;
struct enable
{
uint8_t PON : 1; // power on
uint8_t AEN : 1; // ALS enable
uint8_t PEN : 1; // Proximity detect enable
uint8_t WEN : 1; // wait timer enable
uint8_t AIEN : 1; // ALS interrupt enable
uint8_t PIEN : 1; // proximity interrupt enable
uint8_t GEN : 1; // gesture enable
uint8_t get()
{
return (GEN << 6) | (PIEN << 5) | (AIEN << 4) | (WEN << 3)
| (PEN << 2) | (AEN << 1) | PON;
};
};
struct enable enable_;
struct gpulse
{
uint8_t GPULSE : 6; // Number of gesture pulses = GPULSE + 1
uint8_t GPLEN : 2; // Gesture Pulse Length
uint8_t get() { return (GPLEN << 6) | GPULSE; }
};
gpulse gpulse_;
struct gstatus
{
uint8_t GVALID : 1; // Gesture FIFO Data Are we OK to read FIFO?
uint8_t GFOV : 1; // Gesture FIFO Overflow Flag
void set(uint8_t data)
{
GFOV = (data >> 1) & 0x01;
GVALID = data & 0x01;
}
};
gstatus gstatus_;
struct config2
{
/* Additional LDR current during proximity and gesture LED pulses. Current
value, set by LDRIVE, is increased by the percentage of LED_BOOST.
*/
uint8_t LED_BOOST : 2;
uint8_t CPSIEN : 1; // clear photodiode saturation int enable
uint8_t PSIEN : 1; // proximity saturation interrupt enable
uint8_t get()
{
return (PSIEN << 7) | (CPSIEN << 6) | (LED_BOOST << 4) | 1;
}
};
config2 config2_;
struct status
{
uint8_t AVALID : 1; // ALS Valid
uint8_t PVALID : 1; // Proximity Valid
uint8_t GINT : 1; // Gesture Interrupt
uint8_t AINT : 1; // ALS Interrupt
uint8_t PINT : 1; // Proximity Interrupt
/* Indicates that an analog saturation event occurred during a previous
proximity or gesture cycle. Once set, this bit remains set until cleared by
clear proximity interrupt special function command (0xE5 PICLEAR) or by
disabling Prox (PEN=0). This bit triggers an interrupt if PSIEN is set.
*/
uint8_t PGSAT : 1;
/* Clear Photodiode Saturation. When asserted, the analog sensor was at the
upper end of its dynamic range. The bit can be de-asserted by sending a
Clear channel interrupt command (0xE6 CICLEAR) or by disabling the ADC
(AEN=0). This bit triggers an interrupt if CPSIEN is set.
*/
uint8_t CPSAT : 1;
void set(uint8_t data)
{
AVALID = data & 0x01;
PVALID = (data >> 1) & 0x01;
GINT = (data >> 2) & 0x01;
AINT = (data >> 4) & 0x01;
PINT = (data >> 5) & 0x01;
PGSAT = (data >> 6) & 0x01;
CPSAT = (data >> 7) & 0x01;
}
};
status status_;
};
using Apds9960I2C = Apds9960<Apds9960I2CTransport>;
} // namespace daisy
#endif