From 7548a6e84988a14d1d8a0ab392e7ceb0d3515d89 Mon Sep 17 00:00:00 2001 From: Schorcht Date: Sat, 17 Nov 2018 11:47:21 +0100 Subject: [PATCH] drivers: add APDS99XX ALS and proximity --- drivers/Makefile.dep | 9 + drivers/Makefile.include | 4 + drivers/apds99xx/Makefile | 1 + drivers/apds99xx/apds99xx.c | 534 ++++++++++++++++ drivers/apds99xx/apds99xx_saul.c | 95 +++ drivers/apds99xx/include/apds99xx_params.h | 112 ++++ drivers/apds99xx/include/apds99xx_regs.h | 138 +++++ drivers/include/apds99xx.h | 687 +++++++++++++++++++++ makefiles/pseudomodules.inc.mk | 8 + 9 files changed, 1588 insertions(+) create mode 100644 drivers/apds99xx/Makefile create mode 100644 drivers/apds99xx/apds99xx.c create mode 100644 drivers/apds99xx/apds99xx_saul.c create mode 100644 drivers/apds99xx/include/apds99xx_params.h create mode 100644 drivers/apds99xx/include/apds99xx_regs.h create mode 100644 drivers/include/apds99xx.h diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index e8d4d2e8c5..928a15c4e1 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -29,6 +29,15 @@ ifneq (,$(filter apa102,$(USEMODULE))) FEATURES_REQUIRED += periph_gpio endif +ifneq (,$(filter apds99%full,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio_irq +endif + +ifneq (,$(filter apds99%,$(USEMODULE))) + FEATURES_REQUIRED += periph_i2c + USEMODULE += apds99xx +endif + ifneq (,$(filter at,$(USEMODULE))) FEATURES_REQUIRED += periph_uart USEMODULE += fmt diff --git a/drivers/Makefile.include b/drivers/Makefile.include index 576f964adb..018a75d2f4 100644 --- a/drivers/Makefile.include +++ b/drivers/Makefile.include @@ -20,6 +20,10 @@ ifneq (,$(filter apa102,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/apa102/include endif +ifneq (,$(filter apds99xx,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/drivers/apds99xx/include +endif + ifneq (,$(filter at24cxxx,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/at24cxxx/include endif diff --git a/drivers/apds99xx/Makefile b/drivers/apds99xx/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/apds99xx/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/apds99xx/apds99xx.c b/drivers/apds99xx/apds99xx.c new file mode 100644 index 0000000000..46ef388ce2 --- /dev/null +++ b/drivers/apds99xx/apds99xx.c @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_apds99xx + * @{ + * @brief Device driver for the Broadcom APDS99XX proximity and ambient light sensor + * @author Gunar Schorcht + * @file + * @} + */ + +#include +#include + +#include "apds99xx_regs.h" +#include "apds99xx.h" + +#include "irq.h" +#include "log.h" +#include "xtimer.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#if ENABLE_DEBUG + +#define DEBUG_DEV(f, d, ...) \ + DEBUG("[apds99xx] %s i2c dev=%d addr=%02x: " f "\n", \ + __func__, d->params.dev, APDS99XX_I2C_ADDRESS, ## __VA_ARGS__); + +#else /* ENABLE_DEBUG */ + +#define DEBUG_DEV(f, d, ...) + +#endif /* ENABLE_DEBUG */ + +#define ERROR_DEV(f, d, ...) \ + LOG_ERROR("[apds99xx] %s i2c dev=%d addr=%02x: " f "\n", \ + __func__, d->params.dev, APDS99XX_I2C_ADDRESS, ## __VA_ARGS__); + +/** Forward declaration of functions for internal use */ + +static int _is_available(const apds99xx_t *dev); +static void _set_reg_bit(uint8_t *byte, uint8_t mask, uint8_t bit); +static int _reg_read(const apds99xx_t *dev, uint8_t reg, uint8_t *data, uint16_t len); +static int _reg_write(const apds99xx_t *dev, uint8_t reg, uint8_t *data, uint16_t len); +static int _update_reg(const apds99xx_t *dev, uint8_t reg, uint8_t mask, uint8_t val); + +int apds99xx_init(apds99xx_t *dev, const apds99xx_params_t *params) +{ + /* some parameter sanity checks */ + assert(dev != NULL); + assert(params != NULL); + assert(params->als_steps <= 256); + assert(params->wait_steps <= 256); +#if MODULE_APDS9960 + assert(params->prx_pulses <= 15); +#endif + + DEBUG_DEV("params=%p", dev, params); + + /* init sensor data structure */ + dev->params = *params; + +#if MODULE_APDS99XX_FULL + dev->isr = NULL; + dev->isr_arg = NULL; + dev->gpio_init = false; +#endif + + /* + * the sensor should be operational 5.7 ms after power on; try to check + * its availability for some time (maximum 500 times/I2C address writes) + */ + int res = 0; + int count = 500; + while (count--) { + res = _is_available(dev); + if (res == APDS99XX_OK) { + break; + } + } + if (res != APDS99XX_OK) { + return res; + } + + uint8_t reg; + + /* disable and power down the sensor */ + reg = 0; + if (_reg_write(dev, APDS99XX_REG_ENABLE, ®, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } + + /* write ALS integration time and gain parameter */ + uint8_t atime = 256 - dev->params.als_steps; + if (_reg_write(dev, APDS99XX_REG_ATIME, &atime, 1) || + _update_reg(dev, APDS99XX_REG_CONTROL, + APDS99XX_REG_AGAIN, dev->params.als_gain)) { + return -APDS99XX_ERROR_I2C; + } + /* write PRX LED pulses LED drive strength and gain parameter */ +#if MODULE_APDS9900 || MODULE_APDS9901 || MODULE_APDS9930 + uint8_t ptime = 0xff; /* PTIME is always 0xff as recommended in datasheet */ + if (_reg_write(dev, APDS99XX_REG_PTIME, &ptime, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } +#endif +#if MODULE_APDS9960 + if (dev->params.prx_pulses > 0 && + _update_reg(dev, APDS99XX_REG_PPCOUNT, + APDS99XX_REG_PPULSE, + dev->params.prx_pulses-1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } +#else + if (_reg_write(dev, APDS99XX_REG_PPCOUNT, &dev->params.prx_pulses, 1) || + _update_reg(dev, APDS99XX_REG_CONTROL, APDS99XX_REG_PDIODE, 2)) { + return -APDS99XX_ERROR_I2C; + } +#endif + if (_update_reg(dev, APDS99XX_REG_CONTROL, APDS99XX_REG_PDRIVE, + dev->params.prx_drive) || + _update_reg(dev, APDS99XX_REG_CONTROL, APDS99XX_REG_PGAIN, + dev->params.prx_gain)) { + return -APDS99XX_ERROR_I2C; + } + + /* write the waiting time */ + uint8_t wtime = 256 - dev->params.wait_steps; + if (_reg_write(dev, APDS99XX_REG_WTIME, &wtime, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } + + reg = 0; + _set_reg_bit(®, APDS99XX_REG_PON, 1); /* power on */ + _set_reg_bit(®, APDS99XX_REG_AEN, dev->params.als_steps != 0); /* enable ALS */ + _set_reg_bit(®, APDS99XX_REG_PEN, dev->params.prx_pulses != 0); /* enable PRX */ + _set_reg_bit(®, APDS99XX_REG_WEN, dev->params.wait_steps != 0); /* enable Wait */ + if (_reg_write(dev, APDS99XX_REG_ENABLE, ®, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } + + return APDS99XX_OK; +} + +int apds99xx_data_ready_als (const apds99xx_t *dev) +{ + assert(dev != NULL); + DEBUG_DEV("", dev); + + uint8_t reg; + if (_reg_read(dev, APDS99XX_REG_STATUS, ®, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } + + return (reg & APDS99XX_REG_AVALID) ? APDS99XX_OK : -APDS99XX_ERROR_NO_DATA; +} + +int apds99xx_read_als_raw(const apds99xx_t *dev, uint16_t *raw) +{ + assert(dev != NULL); + assert(raw != NULL); + DEBUG_DEV("raw=%p", dev, raw); + + uint8_t data[2]; + + if (_reg_read(dev, APDS99XX_REG_CDATAL, data, 2) != APDS99XX_OK) { + return -APDS99XX_ERROR_RAW_DATA; + } + + /* data LSB @ lower address */ + *raw = (data[1] << 8) | data[0]; + + return APDS99XX_OK; +} + +#if MODULE_APDS9900 || MODULE_APDS9901 || MODULE_APDS9930 +static uint8_t apds99xx_gains[] = { 1, 8, 16, 120 }; + +int apds99xx_read_illuminance(const apds99xx_t *dev, uint16_t *lux) +{ + assert(dev != NULL); + assert(lux != NULL); + DEBUG_DEV("lux=%p", dev, lux); + + uint8_t data[4]; + + if (_reg_read(dev, APDS99XX_REG_CDATAL, data, 4) != APDS99XX_OK) { + return -APDS99XX_ERROR_RAW_DATA; + } + + /* data LSB @ lower address */ + uint16_t ch0 = (data[1] << 8) | data[0]; + uint16_t ch1 = (data[3] << 8) | data[2]; + + /* define some device dependent constants */ + double df = 52; +#if MODULE_APDS9900 || MODULE_APDS9901 + double ga = 0.48; /* glas or lens attenuation factor */ + double b = 2.23; + double c = 0.7; + double d = 1.42; +#else + /* APDS_9930 */ + double ga = 0.49; /* glas or lens attenuation factor */ + double b = 1.862; + double c = 0.746; + double d = 1.291; +#endif + + /* algorithm from datasheet */ + double iac1 = ch0 - b * ch1; + double iac2 = c * ch0 - d * ch1; + + /* iac = max(iac1, iac2, 0); */ + double iac = 0; + iac = (iac1 > iac) ? iac1 : iac; + iac = (iac2 > iac) ? iac2 : iac; + + double lpc = ga * df / (apds99xx_gains[dev->params.als_gain] * + dev->params.als_steps); + double luxd = iac * lpc; + *lux = luxd; + + return APDS99XX_OK; +} +#endif /* MODULE_APDS9900 || MODULE_APDS9901 || MODULE_APDS9930 */ + +#if MODULE_APDS9950 || MODULE_APDS9960 +int apds99xx_read_rgb_raw(const apds99xx_t *dev, apds99xx_rgb_t *rgb) +{ + assert(dev != NULL); + assert(rgb != NULL); + DEBUG_DEV("rgb=%p", dev, rgb); + + uint8_t data[6] = { }; /* initialize with 0 */ + + if (_reg_read(dev, APDS99XX_REG_RDATAL, data, 6) != APDS99XX_OK) { + return -APDS99XX_ERROR_RAW_DATA; + } + + /* data LSB @ lower address */ + rgb->val[0] = (data[1] << 8) | data[0]; + rgb->val[1] = (data[3] << 8) | data[2]; + rgb->val[2] = (data[5] << 8) | data[4]; + + return APDS99XX_OK; +} +#endif /* MODULE_APDS9950 || MODULE_APDS9960 */ + +int apds99xx_data_ready_prx (const apds99xx_t *dev) +{ + assert(dev != NULL); + DEBUG_DEV("", dev); + + uint8_t reg; + + if (_reg_read(dev, APDS99XX_REG_STATUS, ®, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } + return (reg & APDS99XX_REG_PVALID) ? APDS99XX_OK : -APDS99XX_ERROR_NO_DATA; +} + +int apds99xx_read_prx_raw (const apds99xx_t *dev, uint16_t *prox) +{ + assert(dev != NULL); + assert(prox != NULL); + DEBUG_DEV("prox=%p", dev, prox); + + uint8_t data[2] = { }; /* initialize with 0 */ + +#if MODULE_APDS9900 || MODULE_APDS9901 || MODULE_APDS9930 || MODULE_APDS9950 + if (_reg_read(dev, APDS99XX_REG_PDATAL, data, 2) != APDS99XX_OK) { + return -APDS99XX_ERROR_RAW_DATA; + } +#endif +#if MODULE_APDS9960 + if (_reg_read(dev, APDS99XX_REG_PDATA, data, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_RAW_DATA; + } +#endif + + /* data LSB @ lower address */ + *prox = (data[1] << 8) | data[0]; + + return APDS99XX_OK; +} + +int apds99xx_power_down(const apds99xx_t *dev) +{ + return _update_reg(dev, APDS99XX_REG_ENABLE, APDS99XX_REG_PON, 0); +} + +int apds99xx_power_up(const apds99xx_t *dev) +{ + return _update_reg(dev, APDS99XX_REG_ENABLE, APDS99XX_REG_PON, 1); +} + +#if MODULE_APDS99XX_FULL + +void _apds99xx_isr(void *arg) +{ + apds99xx_t* dev = (apds99xx_t*)arg; + unsigned state = irq_disable(); + + DEBUG_DEV("", dev); + + /* call registered interrupt service routine */ + if (dev->isr) { + dev->isr(dev->isr_arg); + } + + irq_restore (state); +} + +int apds99xx_int_source(apds99xx_t *dev, apds99xx_int_source_t* source) +{ + assert(dev != NULL); + assert(source != NULL); + DEBUG_DEV("", dev); + + uint8_t reg; + + /* get interrupt status */ + if (_reg_read(dev, APDS99XX_REG_STATUS, ®, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } + + /* set triggered interrupts */ + source->als_int = reg & APDS99XX_REG_AINT; + source->prx_int = reg & APDS99XX_REG_PINT; + + /* clear interrupt status */ + if (_reg_write(dev, APDS99XX_REG_CLI_CMD, 0, 0) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } + + return APDS99XX_OK; +} + +int apds99xx_int_config(apds99xx_t *dev, apds99xx_int_config_t* cfg, + apds99xx_isr_t isr, void *isr_arg) +{ + assert(dev != NULL); + assert(cfg != NULL); + assert(dev->params.int_pin != GPIO_UNDEF); + assert(cfg->als_pers <= 15); + assert(cfg->prx_pers <= 15); + + DEBUG_DEV("", dev); + + if (!dev->gpio_init) { + dev->gpio_init = true; + gpio_init_int(dev->params.int_pin, GPIO_IN_PU, GPIO_FALLING, + _apds99xx_isr, dev); + } + + /* LSB @ lower address */ + uint8_t ailtx[2] = { cfg->als_thresh_low & 0xff, cfg->als_thresh_low >> 8 }; + uint8_t aihtx[2] = { cfg->als_thresh_high & 0xff, cfg->als_thresh_high >> 8 }; +#if MODULE_APDS9960 + /* for APDS9960 the one byte thresholds is used for APDS99XX_REG_PIxTH */ + uint8_t pilth = cfg->prx_thresh_low & 0xff; + uint8_t pihth = cfg->prx_thresh_high & 0xff; +#else + uint8_t piltx[2] = { cfg->prx_thresh_low & 0xff, cfg->prx_thresh_low >> 8 }; + uint8_t pihtx[2] = { cfg->prx_thresh_high & 0xff, cfg->prx_thresh_high >> 8 }; +#endif + + if (_reg_write(dev, APDS99XX_REG_AILTL, ailtx, 2) || + _reg_write(dev, APDS99XX_REG_AIHTL, aihtx, 2) || +#if MODULE_APDS9960 + _reg_write(dev, APDS99XX_REG_PILTH, &pilth, 1) || + _reg_write(dev, APDS99XX_REG_PIHTH, &pihth, 1) || +#else + _reg_write(dev, APDS99XX_REG_PILTL, piltx, 2) || + _reg_write(dev, APDS99XX_REG_PIHTL, pihtx, 2) || +#endif + _update_reg(dev, APDS99XX_REG_PERS, APDS99XX_REG_APERS, cfg->als_pers) || + _update_reg(dev, APDS99XX_REG_PERS, APDS99XX_REG_PPERS, cfg->prx_pers) || + _update_reg(dev, APDS99XX_REG_ENABLE, APDS99XX_REG_AIEN, cfg->als_int_en) || + _reg_write(dev, APDS99XX_REG_CLI_CMD, 0, 0) || + _update_reg(dev, APDS99XX_REG_ENABLE, APDS99XX_REG_PIEN, cfg->prx_int_en) || + _reg_write(dev, APDS99XX_REG_CLI_CMD, 0, 0)) { + return -APDS99XX_ERROR_I2C; + } + + dev->isr = isr; + dev->isr_arg = isr_arg; + + return APDS99XX_OK; +} + +#endif /* MODULE_APDS99XX_FULL */ + +/** Functions for internal use only */ + +/** + * @brief Check the chip ID to test whether sensor is available + */ +static int _is_available(const apds99xx_t *dev) +{ + DEBUG_DEV("", dev); + + uint8_t reg; + + /* read the chip id from APDS99XX_REG_ID_X */ + if (_reg_read(dev, APDS99XX_REG_ID, ®, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } + + if (reg != APDS99XX_ID) { + DEBUG_DEV("sensor is not available, wrong device id %02x, " + "should be %02x", dev, reg, APDS99XX_ID); + return -APDS99XX_ERROR_WRONG_ID; + } + + return APDS99XX_OK; +} + +static void _set_reg_bit(uint8_t *byte, uint8_t mask, uint8_t bit) +{ + assert(byte != NULL); + + uint8_t shift = 0; + while (!((mask >> shift) & 0x01)) { + shift++; + } + *byte = ((*byte & ~mask) | ((bit << shift) & mask)); +} + +static int _update_reg(const apds99xx_t *dev, uint8_t reg, uint8_t mask, uint8_t val) +{ + DEBUG_DEV("reg=%02x mask=%02x val=%02x", dev, reg, mask, val); + + uint8_t reg_val; + uint8_t shift = 0; + + while (!((mask >> shift) & 0x01)) { + shift++; + } + + /* read current register value */ + if (_reg_read(dev, reg, ®_val, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } + + /* set masked bits to the given value */ + reg_val = (reg_val & ~mask) | ((val << shift) & mask); + + /* write back new register value */ + if (_reg_write(dev, reg, ®_val, 1) != APDS99XX_OK) { + return -APDS99XX_ERROR_I2C; + } + + return APDS99XX_OK; +} + +static int _reg_read(const apds99xx_t *dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + assert(dev != NULL); + assert(data != NULL); + assert(len != 0); + + if (i2c_acquire(dev->params.dev)) { + DEBUG_DEV("could not acquire I2C bus", dev); + return -APDS99XX_ERROR_I2C; + } + int res = i2c_read_regs(dev->params.dev, APDS99XX_I2C_ADDRESS, reg, data, len, 0); + i2c_release(dev->params.dev); + + if (res != 0) { + DEBUG_DEV("could not read %d bytes from sensor registers " + "starting at addr %02x, reason %d", + dev, len, reg, res); + return -APDS99XX_ERROR_I2C; + } + + if (ENABLE_DEBUG) { + printf("[apds99xx] %s i2c dev=%d addr=%02x: read from reg 0x%02x: ", + __func__, dev->params.dev, APDS99XX_I2C_ADDRESS, reg); + for (uint16_t i = 0; i < len; i++) { + printf("%02x ", data[i]); + } + printf("\n"); + } + + return res; +} + +static int _reg_write(const apds99xx_t *dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + assert(dev != NULL); + + if (ENABLE_DEBUG) { + printf("[apds99xx] %s i2c dev=%d addr=%02x: write to reg 0x%02x: ", + __func__, dev->params.dev, APDS99XX_I2C_ADDRESS, reg); + for (uint16_t i = 0; i < len; i++) { + printf("%02x ", data[i]); + } + printf("\n"); + } + + if (i2c_acquire(dev->params.dev)) { + DEBUG_DEV("could not acquire I2C bus", dev); + return -APDS99XX_ERROR_I2C; + } + + int res; + + if (!data || !len) { + res = i2c_write_byte(dev->params.dev, APDS99XX_I2C_ADDRESS, reg, 0); + } + else { + res = i2c_write_regs(dev->params.dev, APDS99XX_I2C_ADDRESS, reg, data, len, 0); + } + i2c_release(dev->params.dev); + + if (res != 0) { + DEBUG_DEV("could not write %d bytes to sensor registers " + "starting at addr 0x%02x, reason %d", + dev, len, reg, res); + return -APDS99XX_ERROR_I2C; + } + + return res; +} diff --git a/drivers/apds99xx/apds99xx_saul.c b/drivers/apds99xx/apds99xx_saul.c new file mode 100644 index 0000000000..960f2dad36 --- /dev/null +++ b/drivers/apds99xx/apds99xx_saul.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_apds99xx + * @brief APDS99XX adaption to the RIOT actuator/sensor interface + * @author Gunar Schorcht + * @file + */ + +#include + +#include "saul.h" +#include "apds99xx.h" + +static int read_prx(const void *dev, phydat_t *res) +{ + if (apds99xx_read_prx_raw((const apds99xx_t *)dev, (uint16_t*)res) == 0) { + res->unit = UNIT_CTS; + res->scale = 0; + return 1; + } + return -ECANCELED; +} + +static int read_als(const void *dev, phydat_t *res) +{ + if (apds99xx_read_als_raw((const apds99xx_t *)dev, (uint16_t*)res) == 0) { + res->unit = UNIT_CTS; + res->scale = 0; + return 1; + } + return -ECANCELED; +} + +#if MODULE_APDS9900 || MODULE_APDS9901 || MODULE_APDS9930 +static int read_lux(const void *dev, phydat_t *res) +{ + if (apds99xx_read_illuminance((const apds99xx_t *)dev, (uint16_t*)res) == 0) { + res->unit = UNIT_LUX; + res->scale = 0; + return 1; + } + return -ECANCELED; +} +#endif + +#if MODULE_APDS9950 || MODULE_APDS9960 +static int read_rgb(const void *dev, phydat_t *res) +{ + apds99xx_rgb_t rgb; + if (apds99xx_read_rgb_raw((const apds99xx_t *)dev, &rgb) == 0) { + res->val[0] = (int16_t)rgb.red; + res->val[1] = (int16_t)rgb.green; + res->val[2] = (int16_t)rgb.blue; + res->unit = UNIT_CTS; + res->scale = 0; + return 3; + } + return -ECANCELED; +} +#endif + +const saul_driver_t apds99xx_saul_prx_driver = { + .read = read_prx, + .write = saul_notsup, + .type = SAUL_SENSE_PROXIMITY, +}; + +const saul_driver_t apds99xx_saul_als_driver = { + .read = read_als, + .write = saul_notsup, + .type = SAUL_SENSE_LIGHT, +}; + +#if MODULE_APDS9900 || MODULE_APDS9901 || MODULE_APDS9930 +const saul_driver_t apds99xx_saul_lux_driver = { + .read = read_lux, + .write = saul_notsup, + .type = SAUL_SENSE_LIGHT, +}; +#endif + +#if MODULE_APDS9950 || MODULE_APDS9960 +const saul_driver_t apds99xx_saul_rgb_driver = { + .read = read_rgb, + .write = saul_notsup, + .type = SAUL_SENSE_LIGHT, +}; +#endif diff --git a/drivers/apds99xx/include/apds99xx_params.h b/drivers/apds99xx/include/apds99xx_params.h new file mode 100644 index 0000000000..619f779e16 --- /dev/null +++ b/drivers/apds99xx/include/apds99xx_params.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_apds99xx + * @brief Default configuration for Broadcom APDS99XX proximity and ambient light sensor + * @author Gunar Schorcht + * @file + * @{ + */ + +#ifndef APDS99XX_PARAMS_H +#define APDS99XX_PARAMS_H + +#include "board.h" +#include "apds99xx.h" +#include "saul_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Set default configuration parameters + * @{ + */ +#ifndef APDS99XX_PARAM_DEV +/** device is I2C_DEV(0) */ +#define APDS99XX_PARAM_DEV I2C_DEV(0) +#endif + +#ifndef APDS99XX_PARAM_ALS_STEPS +/** ALS measurement: enabled with integration time of 64 steps */ +#define APDS99XX_PARAM_ALS_STEPS (64) +#endif +#ifndef APDS99XX_PARAM_ALS_GAIN +/** ALS gain: 1 x gain */ +#define APDS99XX_PARAM_ALS_GAIN (APDS99XX_ALS_GAIN_1) +#endif + +#ifndef APDS99XX_PARAM_PRX_PULSES +/** PRX LED pulse count: 8 pulses as recommended in datasheet */ +#define APDS99XX_PARAM_PRX_PULSES (8) +#endif +#ifndef APDS99XX_PARAM_PRX_DRIVE +/** PRX LED drive strength: 100 mA as recommended in datasheet */ +#define APDS99XX_PARAM_PRX_DRIVE (APDS99XX_PRX_DRIVE_100) +#endif +#ifndef APDS99XX_PARAM_PRX_GAIN +/** PRX gain: 1 x gain */ +#define APDS99XX_PARAM_PRX_GAIN (APDS99XX_PRX_GAIN_1) +#endif +#ifndef APDS99XX_PARAM_WAIT_STEPS +/** Waiting time: disabled */ +#define APDS99XX_PARAM_WAIT_STEPS (0) +#endif +#ifndef APDS99XX_PARAM_WAIT_LONG +/** Wait long: false */ +#define APDS99XX_PARAM_WAIT_LONG (false) +#endif + +#ifndef APDS99XX_PARAM_INT_PIN +/** Interrupt pin */ +#define APDS99XX_PARAM_INT_PIN (GPIO_PIN(0, 0)) +#endif + +#ifndef APDS99XX_PARAMS +#define APDS99XX_PARAMS { \ + .dev = APDS99XX_PARAM_DEV, \ + .als_steps = APDS99XX_PARAM_ALS_STEPS, \ + .als_gain = APDS99XX_PARAM_ALS_GAIN, \ + .prx_pulses = APDS99XX_PARAM_PRX_PULSES, \ + .prx_gain = APDS99XX_PARAM_PRX_GAIN, \ + .prx_drive = APDS99XX_PARAM_PRX_DRIVE, \ + .wait_steps = APDS99XX_PARAM_WAIT_STEPS, \ + .wait_long = APDS99XX_PARAM_WAIT_LONG, \ + .int_pin = APDS99XX_PARAM_INT_PIN, \ + } +#endif + +#ifndef APDS99XX_SAUL_INFO +#define APDS99XX_SAUL_INFO { .name = "apds99xx" } +#endif +/**@}*/ + +/** + * @brief Allocate some memory to store the actual configuration + */ +static const apds99xx_params_t apds99xx_params[] = +{ + APDS99XX_PARAMS +}; + +/** + * @brief Additional meta information to keep in the SAUL registry + */ +static const saul_reg_info_t apds99xx_saul_info[] = +{ + APDS99XX_SAUL_INFO +}; + +#ifdef __cplusplus +} +#endif + +#endif /* APDS99XX_PARAMS_H */ +/** @} */ diff --git a/drivers/apds99xx/include/apds99xx_regs.h b/drivers/apds99xx/include/apds99xx_regs.h new file mode 100644 index 0000000000..474b4555b8 --- /dev/null +++ b/drivers/apds99xx/include/apds99xx_regs.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_apds99xx + * @brief Register definitions for Broadcom APDS99XX proximity and ambient light sensor + * @author Gunar Schorcht + * @file + * @{ + */ + +#ifndef APDS99XX_REGS_H +#define APDS99XX_REGS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @name Register addresses + * @{ + */ +#if MODULE_APDS9960 +#define APDS99XX_REG_BASE (0x80) /**< Register base address is 0x80 */ +#else +#define APDS99XX_REG_BASE (0xa0) /**< Register base address for autoincrement is 0xa0 */ +#endif + +#define APDS99XX_REG_ENABLE (APDS99XX_REG_BASE + 0x00) /**< Enable states and interrupts */ +#define APDS99XX_REG_ATIME (APDS99XX_REG_BASE + 0x01) /**< ALS timing register */ +#if MODULE_APDS9900 || MODULE_APDS9901 || MODULE_APDS9930 +#define APDS99XX_REG_PTIME (APDS99XX_REG_BASE + 0x02) /**< Proximity timing register */ +#endif +#define APDS99XX_REG_WTIME (APDS99XX_REG_BASE + 0x03) /**< Wait timing register */ +#define APDS99XX_REG_AILTL (APDS99XX_REG_BASE + 0x04) /**< ALS interrupt low threshold low byte */ +#define APDS99XX_REG_AILTH (APDS99XX_REG_BASE + 0x05) /**< ALS interrupt low threshold high byte */ +#define APDS99XX_REG_AIHTL (APDS99XX_REG_BASE + 0x06) /**< ALS interrupt high threshold low byte */ +#define APDS99XX_REG_AIHTH (APDS99XX_REG_BASE + 0x07) /**< ALS interrupt high threshold high byte */ +#if !MODULE_APDS9960 +#define APDS99XX_REG_PILTL (APDS99XX_REG_BASE + 0x08) /**< PRX interrupt low threshold low byte */ +#endif +#define APDS99XX_REG_PILTH (APDS99XX_REG_BASE + 0x09) /**< PRX interrupt low threshold high byte */ +#if !MODULE_APDS9960 +#define APDS99XX_REG_PIHTL (APDS99XX_REG_BASE + 0x0a) /**< PRX interrupt high threshold low byte */ +#endif +#define APDS99XX_REG_PIHTH (APDS99XX_REG_BASE + 0x0b) /**< PRX interrupt high threshold high byte */ +#define APDS99XX_REG_PERS (APDS99XX_REG_BASE + 0x0c) /**< Interrupt persistence filters */ +#define APDS99XX_REG_CONFIG (APDS99XX_REG_BASE + 0x0d) /**< Configuration register (one) */ +#define APDS99XX_REG_PPCOUNT (APDS99XX_REG_BASE + 0x0e) /**< Proximity pulse count */ +#define APDS99XX_REG_CONTROL (APDS99XX_REG_BASE + 0x0f) /**< Control */ +#define APDS99XX_REG_ID (APDS99XX_REG_BASE + 0x12) /**< Device ID */ +#define APDS99XX_REG_STATUS (APDS99XX_REG_BASE + 0x13) /**< Device status */ + +#define APDS99XX_REG_CDATAL (APDS99XX_REG_BASE + 0x14) /**< Clear channel / Ch0 ADC data low byte */ +#define APDS99XX_REG_CDATAH (APDS99XX_REG_BASE + 0x15) /**< Clear channel / Ch0 ADC data high byte */ + +#if MODULE_APDS9900 || MODULE_APDS9901 || MODULE_APDS9930 +#define APDS99XX_REG_IRDATAL (APDS99XX_REG_BASE + 0x16) /**< Ch1(IR) ADC data low byte */ +#define APDS99XX_REG_IRDATAH (APDS99XX_REG_BASE + 0x17) /**< Ch1(IR) ADC data high byte */ +#define APDS99XX_REG_PDATAL (APDS99XX_REG_BASE + 0x18) /**< Proximity ADC data low byte */ +#define APDS99XX_REG_PDATAH (APDS99XX_REG_BASE + 0x19) /**< Proximity ADC data high byte */ +#endif +#if MODULE_APDS9950 || MODULE_APDS9960 +#define APDS99XX_REG_RDATAL (APDS99XX_REG_BASE + 0x16) /**< Red channel data low byte */ +#define APDS99XX_REG_RDATAH (APDS99XX_REG_BASE + 0x17) /**< Red channel data high byte */ +#define APDS99XX_REG_GDATAL (APDS99XX_REG_BASE + 0x18) /**< Green channel data low byte */ +#define APDS99XX_REG_GDATAH (APDS99XX_REG_BASE + 0x19) /**< Green channel data high byte */ +#define APDS99XX_REG_BDATAL (APDS99XX_REG_BASE + 0x1a) /**< Blue channel data low byte */ +#define APDS99XX_REG_BDATAH (APDS99XX_REG_BASE + 0x1b) /**< Blue channel data high byte */ +#endif +#if MODULE_APDS9950 +#define APDS99XX_REG_PDATAL (APDS99XX_REG_BASE + 0x1c) /**< Proximity ADC data low byte */ +#define APDS99XX_REG_PDATAH (APDS99XX_REG_BASE + 0x1d) /**< Proximity ADC data high byte */ +#endif +#if MODULE_APDS9960 +#define APDS99XX_REG_PDATA (APDS99XX_REG_BASE + 0x1c) /**< Proximity ADC data (only one byte) */ +#endif + +#define APDS99XX_REG_CLI_CMD (0xe7) /**< Clear ALS and proximity interrupt command */ +/** @} */ + +/** + * @name Register structure definitions + * @{ + */ + +/* Enable states and interrupts register (APDS99XX_REG_ENABLE) */ +#define APDS99XX_REG_GEN (0x40) /**< Gesture Enable */ +#define APDS99XX_REG_PIEN (0x20) /**< Proximity Interrupt Enable */ +#define APDS99XX_REG_AIEN (0x10) /**< ALS Interrupt Enable */ +#define APDS99XX_REG_WEN (0x08) /**< Wait Enable */ +#define APDS99XX_REG_PEN (0x04) /**< Proximity Detect Enable */ +#define APDS99XX_REG_AEN (0x02) /**< ALS Enable */ +#define APDS99XX_REG_PON (0x01) /**< Power ON */ + +/* Device status register (APDS99XX_REG_STATUS) */ +#define APDS99XX_REG_CPSAT (0x80) /**< Clear Photodiode Saturation */ +#define APDS99XX_REG_PGSAT (0x40) /**< Analog saturation event occurred */ +#define APDS99XX_REG_PINT (0x20) /**< Proximity Interrupt */ +#define APDS99XX_REG_AINT (0x10) /**< ALS Interrupt */ +#define APDS99XX_REG_GINT (0x04) /**< Gesture Interrupt */ +#define APDS99XX_REG_PVALID (0x02) /**< Proximity Valid */ +#define APDS99XX_REG_AVALID (0x01) /**< ALS Valid */ + +/** Control register (APDS99XX_REG_CONTROL) */ +#define APDS99XX_REG_PDRIVE (0xc0) /**< LED Drive Strength */ +#if !MODULE_APDS9960 +#define APDS99XX_REG_PDIODE (0x30) /**< Proximity Diode Select */ +#endif +#define APDS99XX_REG_PGAIN (0x0c) /**< Proximity Gain Control */ +#define APDS99XX_REG_AGAIN (0x03) /**< ALS (and Color) Gain Control */ + +/** Configuration register (APDS99XX_REG_CONFIG) */ +#define APDS99XX_REG_WLONG (0x02) /**< Wait Long */ + +/** Interrupt persistence filter register (APDS99XX_REG_PERS) */ +#define APDS99XX_REG_PPERS (0xf0) /**< Proximity Interrupt persistence. */ +#define APDS99XX_REG_APERS (0x0f) /**< ALS Interrupt persistence. */ + +#if MODULE_APDS9960 +/** Proximity pulse count register (APDS99XX_REG_PPCOUNT) for APDS9960 only */ +#define APDS99XX_REG_PPLEN (0xc0) /**< Proximity Pulse Length */ +#define APDS99XX_REG_PPULSE (0x3f) /**< Proximity Pulse Count */ +#endif + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* APDS99XX_REGS_H */ +/** @} */ diff --git a/drivers/include/apds99xx.h b/drivers/include/apds99xx.h new file mode 100644 index 0000000000..2fe37d68b9 --- /dev/null +++ b/drivers/include/apds99xx.h @@ -0,0 +1,687 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_apds99xx APDS99XX proximity and ambient light sensors + * @ingroup drivers_sensors + * @ingroup drivers_saul + * @brief Device driver for Broadcom APDS99XX proximity and ambient light sensors + * + * The driver can be used with following Broadcom sensors: + * APDS9900, APDS9901, APDS9930, APDS9950, APDS9960 + * + * The driver is divided into two parts: + * + * - **Basic driver** ```apds99xx``` + * + * The base driver ```apds99xx``` only supports a basic set of functions and + * has therefore a small size. The procedure for retrieving new data is + * polling. Ambient light and proximity sensing are supported. + * + * - **Fully functional driver** ```apds99xx_full``` + * + * The fully functional driver ```apds99xx_full``` supports all the features + * supported by the base driver, as well as all other sensor features, + * including interrupts and their configuration. Data-ready interrupts can be + * used to retrieve data. In addition, threshold interrupts can be used and + * configured. + * + * @note In addition to specifying whether the base driver ```apds99xx``` or + * the fully featured driver ```apds99xx_full``` should be used, the + * application has to declare used sensor by means of various + * pseudomodules as follows: + * + * APDS9900: USEMODULE=apds9900 + * APDS9901: USEMODULE=apds9901 + * APDS9930: USEMODULE=apds9930 + * APDS9950: USEMODULE=apds9950 + * APDS9960: USEMODULE=apds9960 + * + * This driver provides @ref drivers_saul capabilities. + * + * # Measurement Cycle + * + * APDS99XX sensor **measurement cycles** consists of the + * following **three steps** in the given order: + * + * - **Proximity Sensing** + * + * The sensor generates a number of IR LED pulses and measures the amount + * of the IR energy reflected by an object to determine its distance. The + * time required for proximity sensing (```t_prx``` ) results from the + * time it takes to generate the IR LED pulses and to accumulate the + * reflected IR energy (```t_prx_acc```) as well as the time for the ADC + * conversion (```t_prx_cnv```). + * + * t_prx = t_prx_acc + t_prx_cnv + * + * The time to generate the IR LED pulses and accumulate reflected IR + * energy ```t_prx_acc``` is defined by the number of pulses (parameter + * apds99xx_params_t::prx_pulses) and the period of one pulse ```t_prx_pulse```. + * + * t_prx_acc = prx_pulses * t_prx_pulse + * + * The ADC conversion time ```t_prx_cnv``` and the period of one + * pulse ```t_prx_pulse``` depend on used sensor and are available as the + * defines #APDS99XX_T_PRX_CNV and #APDS99XX_T_PRX_PULSE for calculations + * by the application. + * Sensor
| t_prx_pulse
APDS99XX_T_PRX_PULSE | t_prx_cnv
APDS99XX_T_PRX_CNV + * ---------- | -------- | ----------------------- + * APDS9900 | 0.016 ms | 2.720 ms + * APDS9901 | 0.016 ms | 2.720 ms + * APDS9930 | 0.016 ms | 2.730 ms + * APDS9950 | 0.014 ms | 2.400 ms + * APDS9960 | 0.036 ms | 0.814 ms + *
+ * + * Proximity sensing uses the gain specified by parameter + * apds99xx_params_t::prx_gain and the LED current specified by + * parameter apds99xx_params_t::prx_drive. + * + * - **Wait** + * + * The sensor waits for ```t_wait``` which is defined by the number of + * waiting steps ```wait_steps``` (apds99xx_params_t::wait_steps), the time + * per step ```t_step```, and the wait long flag + * (apds99xx_params_t::wait_long); + * + * t_wait = wait_steps * t_wait_step (if wait_long is false) + * t_wait = wait_steps * t_wait_step * 12 (if wait_long is true) + * + * Parameter apds99xx_params_t::wait_steps can range from 0 to 256. + * If apds99xx_params_t::wait_steps is 0, waiting is disabled. The time per + * step ```t_wait_step``` depends on used sensor and is available as the + * define #APDS99XX_T_WAIT_STEP for calculations by the application. + * Sensor
| t_als_step
APDS99XX_T_WAIT_STEP | t_wait (wait_long=0)
| t_wait (wait_long=1)
+ * ---------- | ------- | ---------------------------| ------------------------ + * APDS9900 | 2.72 ms | ```wait_steps``` * 2.72 ms | ```wait_steps``` * 12 * 2.72 ms + * APDS9901 | 2.72 ms | ```wait_steps``` * 2.72 ms | ```wait_steps``` * 12 * 2.72 ms + * APDS9930 | 2.73 ms | ```wait_steps``` * 2.73 ms | ```wait_steps``` * 12 * 2.73 ms + * APDS9950 | 2.40 ms | ```wait_steps``` * 2.40 ms | ```wait_steps``` * 12 * 2.40 ms + * APDS9960 | 2.78 ms | ```wait_steps``` * 2.78 ms | ```wait_steps``` * 12 * 2.78 ms + *
+ * + * - **Ambient Light Sensing (ALS)** + * + * The sensor converts the amplified photodiode currents using integrating + * ADCs. The time required for ALS is determined by the ALS integration + * time ```t_als_int```, which is defined as the number of integration + * steps ```als_steps``` (parameter apds99xx_params_t::als_steps) and the + * time per step ```t_als_step```. + * + * t_als_int = als_steps * t_als_step + * + * Parameter apds99xx_params_t::als_steps can range from 0 to 256. + * If apds99xx_params_t::als_steps is 0, ALS is disabled. The time per + * step ```t_als_step``` depends on used sensor and is available as the + * define #APDS99XX_T_ALS_STEP for calculations by the application. + * + * The ALS integration time in steps ```als_steps``` (parameter + * apds99xx_params_t::als_steps) affects the resolution and the full scale + * range ```cnt_als_max``` of ALS data. + * + * cnt_als_max = als_steps * cnts_p_step + * + * The counts per step ```cnts_p_step``` depend on used sensor and is + * available as defines #APDS99XX_CNTS_P_STEP for calculations by the + * application + * + * ALS uses the gain specified by parameter apds99xx_params_t::als_gain. + * Sensor
| t_als_step
APDS99XX_T_ALS_STEP | cnts_p_step
APDS99XX_CNTS_P_STEP | t_als_int
| cnt_als_max
+ * -------- | ------- | ---- | ----------------------- | ---------------- + * APDS9900 | 2.72 ms | 1023 | ```als_steps``` * 2.72 ms | ```als_steps``` * 1023 + * APDS9901 | 2.72 ms | 1023 | ```als_steps``` * 2.72 ms | ```als_steps``` * 1023 + * APDS9930 | 2.73 ms | 1023 | ```als_steps``` * 2.73 ms | ```als_steps``` * 1023 + * APDS9950 | 2.40 ms | 1024 | ```als_steps``` * 2.40 ms | ```als_steps``` * 1024 + * APDS9960 | 2.78 ms | 1025 | ```als_steps``` * 2.78 ms | ```als_steps``` * 1025 + * + * + * The overall measurement cycle time is given by: + * + * t_cycle = t_prx + t_wait + t_als_int + * + * For a given ALS integration time and a given proximity sensing time, + * the waiting time can be used to tune the overall measurement cycle time. + * + * + * # Interrupts + * + * With the ```apds99xx_full``` driver, interrupts can be used either to + * fetch ALS and proximity data or to detect when these data exceed + * defined thresholds. + * + * To use interrupts, the application must call the #apds99xx_int_config + * function to specify an #apds99xx_int_config_t interrupt configuration and + * an ISR with an optional argument that is invoked when an interrupt is + * raised. For details about configuring and enabling the interrupts, see + * #apds99xx_int_config_t. + * + * @note The ISR of the application is executed in the interrupt context. + * Therefore, it must not be blocking or time consuming. In addition, it + * must not access the sensor directly via I2C. It should only indicate to + * the waiting application that an interrupt has occurred, which is then + * handled in the thread context. + * + * While an interrupt is being serviced, the application can use the + * #apds99xx_int_source function to query the type of interrupts triggered + * by the sensor. In addition, the function resets the interrupt signal. + * + * @note Since the interrupt signal is only reset by the function + * #apds99xx_int_source, this function #apds99xx_int_source must be executed + * by application, even if the type of the triggered interrupt is not of + * interest. + * + * For using interrupts, the GPIO to which the sensor's **INT** output pin + * is connected has to be defined by the application in configuration + * parameters. The GPIO is initialized by the driver as soon as the interrupt + * configuration with the function # apds99xx_int_config is specified. + * + * # Illuminance in Lux and RGB count values + * + * For all sensors, the clear channel and the RGB channels provide only count + * values. APDS9900, APDS9901, and APDS9930 have an IR photodiode in addition + * to the clear channel photodiode, which can be used to determine the + * illuminance in Lux with an algorithm described in their datasheets. + * + * Unfortunately, APDS9950 and APDS9960 do not have such an additional IR + * photodiode, and there is no document which describes an approach to + * calculate the illuminance from the RGB channels. Therefore, it is not + * possible to determine the illuminance for these sensors. + * + * @{ + * + * @author Gunar Schorcht + * @file + */ + +#ifndef APDS99XX_H +#define APDS99XX_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +#include "periph/gpio.h" +#include "periph/i2c.h" + +/** + * @name APDS99XX device properties + * + * Defines for different device properties for supported sensor types. These + * properties can be used by the application for calculations. + * @{ + */ +#if MODULE_APDS9900 +#define APDS99XX_ID (0x29) /**< Device ID of ADPS-9900 */ +#define APDS99XX_T_PRX_PULSE (16) /**< LED IR pulse period ```t_pulse``` in us */ +#define APDS99XX_T_PRX_CNV (2720) /**< Proximity ADC conversion time ```t_prx_conv``` in us */ +#define APDS99XX_T_WAIT_STEP (2720) /**< Wait time step size ```t_step``` in us */ +#define APDS99XX_T_ALS_STEP (2720) /**< ALS integration time step size ```t_step``` in */ +#define APDS99XX_CNTS_P_STEP (1023) /**< Counts per step ```cnts_p_step``` */ + +#elif MODULE_APDS9901 +#define APDS99XX_ID (0x20) /**< Device ID of ADPS-9901 */ +#define APDS99XX_T_PRX_PULSE (16) /**< LED IR pulse period ```t_pulse``` in us */ +#define APDS99XX_T_PRX_CNV (2720) /**< Proximity ADC conversion time ```t_prx_conv``` in us */ +#define APDS99XX_T_WAIT_STEP (2720) /**< Wait time step size ```t_step``` in us */ +#define APDS99XX_T_ALS_STEP (2720) /**< ALS integration time step size ```t_step``` in */ +#define APDS99XX_CNTS_P_STEP (1023) /**< Counts per step ```cnts_p_step``` */ + +#elif MODULE_APDS9930 +#define APDS99XX_ID (0x39) /**< Device ID of ADPS-9930 */ +#define APDS99XX_T_PRX_PULSE (16) /**< LED IR pulse period ```t_pulse``` in us */ +#define APDS99XX_T_PRX_CNV (2730) /**< Proximity ADC conversion time ```t_prx_conv``` in us */ +#define APDS99XX_T_WAIT_STEP (2730) /**< Wait time step size ```t_step``` in us */ +#define APDS99XX_T_ALS_STEP (2730) /**< ALS integration time step size ```t_step``` in */ +#define APDS99XX_CNTS_P_STEP (1023) /**< Counts per step ```cnts_p_step``` */ + +#elif MODULE_APDS9950 +#define APDS99XX_ID (0x69) /**< Device ID of ADPS-9950 */ +#define APDS99XX_T_PRX_PULSE (14) /**< LED IR pulse period ```t_pulse``` in us */ +#define APDS99XX_T_PRX_CNV (2400) /**< Proximity ADC conversion time ```t_prx_conv``` in us */ +#define APDS99XX_T_WAIT_STEP (2400) /**< Wait time step size ```t_step``` in us */ +#define APDS99XX_T_ALS_STEP (2400) /**< ALS integration time step size ```t_step``` in */ +#define APDS99XX_CNTS_P_STEP (1024) /**< Counts per step ```cnts_p_step``` */ + +#elif MODULE_APDS9960 || DOXYGEN +#define APDS99XX_ID (0xab) /**< Device ID of ADPS-9960 */ +#define APDS99XX_T_PRX_PULSE (36) /**< LED IR pulse period ```t_pulse``` in us + (for PPLEN=8 us) */ +#define APDS99XX_T_PRX_CNV (841) /**< Proximity ADC conversion time ```t_prx_conv``` in us + (tINIT + tCNVT for PPLEN=8 us) */ +#define APDS99XX_T_WAIT_STEP (2780) /**< Wait time step size ```t_step``` in us */ +#define APDS99XX_T_ALS_STEP (2780) /**< ALS integration time step size ```t_step``` in */ +#define APDS99XX_CNTS_P_STEP (1025) /**< Counts per step ```cnts_p_step``` */ + +#else +#error "Please provide a valid aps99xx variant (apds9900, apds9901, adps9930, apds9950, apds9960)" +#endif +/** @} */ + +/** + * @brief APDS99XX I2C addresses + */ +#define APDS99XX_I2C_ADDRESS (0x39) + +/** + * @brief Definition of error codes + */ +typedef enum { + APDS99XX_OK, /**< success */ + APDS99XX_ERROR_I2C, /**< I2C communication error */ + APDS99XX_ERROR_WRONG_ID, /**< wrong id read */ + APDS99XX_ERROR_NO_DATA, /**< no data are available */ + APDS99XX_ERROR_RAW_DATA, /**< reading raw data failed */ +} apds99xx_error_codes_t; + +/** + * @brief Ambient light sensing (ALS) gain + */ +typedef enum { + APDS99XX_ALS_GAIN_1 = 0, /**< 1 x gain (default) */ + APDS99XX_ALS_GAIN_8, /**< 8 x gain */ + APDS99XX_ALS_GAIN_16, /**< 16 x gain */ + #if MODULE_APDS9950 || MODULE_APDS9960 || DOXYGEN + APDS99XX_ALS_GAIN_64, /**< 64 x gain (APDS9950, APDS9960 only) */ + #endif /* MODULE_APDS9950 || MODULE_APDS9960 || DOXYGEN */ + #if MODULE_APDS9900 || MODULE_APDS9901 || APDS9930 || DOXYGEN + APDS99XX_ALS_GAIN_120, /**< 120 x gain (APDS9900, APDS9901, APDS9930 only) */ + #endif /* MODULE_APDS9900 || MODULE_APDS9901 || APDS9930 || DOXYGEN */ +} apds99xx_als_gain_t; + +/** + * @brief Proximity sensing (PRX) gain + */ +typedef enum { + APDS99XX_PRX_GAIN_1 = 0, /**< 1 x gain (default) */ + #if MODULE_APDS9930 || MODULE_APDS9960 || DOXYGEN + APDS99XX_PRX_GAIN_2, /**< 2 x gain (APDS9930, APDS9960 only) */ + APDS99XX_PRX_GAIN_4, /**< 4 x gain (APDS9930, APDS9960 only) */ + APDS99XX_PRX_GAIN_8, /**< 8 x gain (APDS9930, APDS9960 only) */ + #endif /* MODULE_APDS9930 || MODULE_APDS9960 || DOXYGEN */ +} apds99xx_prx_gain_t; + +/** + * @brief Proximity sensing (PRX) LED drive strength + */ +typedef enum { + APDS99XX_PRX_DRIVE_100 = 0, /**< 100.0 mA (default) */ + APDS99XX_PRX_DRIVE_50, /**< 50.0 mA */ + APDS99XX_PRX_DRIVE_25, /**< 25.0 mA */ + APDS99XX_PRX_DRIVE_12_5, /**< 12.5 mA */ +} apds99xx_prx_drive_t; + +/** + * @brief APDS99XX device initialization parameters + */ +typedef struct { + + unsigned dev; /**< I2C device (default I2C_DEV(0)) */ + + uint16_t als_steps; /**< ALS integration time in steps. If 0, + ALS is disabled. (default 64) */ + apds99xx_als_gain_t als_gain; /**< Gain used for ALS. + (default #APDS99XX_ALS_GAIN_1) */ + + uint8_t prx_pulses; /**< IR LED pulses for proximity sensing. + If 0, proximity sensing is disabled. + (default 8 as recommended) */ + apds99xx_prx_drive_t prx_drive; /**< IR LED current for proximity sensing + (default #APDS99XX_PRX_DRIVE_100) */ + apds99xx_prx_gain_t prx_gain; /**< Gain used for proximity sensing. + (default #APDS99XX_PRX_GAIN_1) */ + + uint16_t wait_steps; /**< Waiting time in steps. If 0, waiting is + disabled. (default 0) */ + bool wait_long; /**< Long waiting time. If true, waitng time is + increased by a factor 12. (default false) */ + + gpio_t int_pin; /**< interrupt pin: #GPIO_UNDEF if not used */ + +} apds99xx_params_t; + +#if MODULE_APDS99XX_FULL || DOXYGEN + +/** + * @brief Interrupt configuration + * + * The interrupt enable flags apds99xx_int_config_t::als_int_en and + * apds99xx_int_config_t::prx_int_en control whether ALS and proximity sensor + * interrupts are enable. + * + * The persistence values apds99xx_int_config_t::als_pers and + * apds99xx_int_config_t::prx_pers specify how many ALS or proximity + * values have to be outside of the thresholds defined by + * apds99xx_int_config_t::als_thresh_low and + * apds99xx_int_config_t::als_thresh_high or + * apds99xx_int_config_t::prx_thresh_low and + * apds99xx_int_config_t::prx_thresh_high. + * + * @note If the persistence values are 0, an interrupt is generated in each + * cycle at the end of the corresponding measurement step, regardless of the + * values and the defined threshold. The corresponding interrupt is thus used + * as a data-ready interrupt. + */ +typedef struct { + bool als_int_en; /**< ALS interrupt enabled */ + uint8_t als_pers; /**< Number of consecutive ALS values that have to be + outside the thresholds to generate an interrupt: + Value | Interrupt is generated + --------| ------- + 0 | every cycle (ALS data-ready interrupt) + 1...15 | when n values are outside the thresholds */ + uint16_t als_thresh_low; /**< Low threshold value for ALS interrupts */ + uint16_t als_thresh_high; /**< High threshold value for ALS interrupts */ + + bool prx_int_en; /**< Proximity interrupt enabled */ + uint8_t prx_pers; /**< Number of consecutive proximity values that have + to be outside the thresholds to generate an + interrupt: + Value | Interrupt is generated + --------| ------- + 0 | every cycle (PRX data-ready interrupt) + 1, 2, 3 | when 1, 2, or 3 values are outside the thresholds + 4...15 | when (n - 3) * 5 values are outside the thresholds */ + + uint16_t prx_thresh_low; /**< Low threshold for proximity values + (only the low byte is used for APDS9960) */ + uint16_t prx_thresh_high; /**< High threshold for proximity values + (only the low byte is used for APDS9960) */ + +} apds99xx_int_config_t; + +/** + * @brief Interrupt source + * + * The type is used to + */ +typedef struct { + bool als_int; /**< ALS interrupt happened */ + bool prx_int; /**< Proximity interrupt happened */ +} apds99xx_int_source_t; + +/** + * @brief Interrupt service routine function prototype + */ +typedef void (*apds99xx_isr_t)(void *arg); + +#endif /* MODULE_APDS99XX_FULL */ + +/** + * @brief APDS99XX sensor device data structure type + */ +typedef struct { + + apds99xx_params_t params; /**< device initialization parameters */ + +#if MODULE_APDS99XX_FULL || DOXYGEN + apds99xx_isr_t isr; /**< user ISR */ + void* isr_arg; /**< user ISR argument */ + + bool gpio_init; /**< GPIO is already initialized */ +#endif /* MODULE_APDS99XX_FULL */ + +} apds99xx_t; + +#if MODULE_APDS9950 || MODULE_APDS9960 || DOXYGEN +/** + * @brief RGB count value data structure (APDS9950 and APDS9960 only) + */ +typedef union { + + struct { + uint16_t red; /**< R photodiode count value (red) */ + uint16_t green; /**< G photodiode count value (green) */ + uint16_t blue; /**< B photodiode count value (blue) */ + }; + uint16_t val[3]; /**< RGB count values as array */ + +} apds99xx_rgb_t; +#endif + +/** + * @brief Initialize the APDS99XX sensor device + * + * This function resets the sensor and initializes the sensor according to + * given initialization parameters. All registers are reset to default values. + * + * @param[in] dev device descriptor of APDS99XX sensor to be initialized + * @param[in] params configuration parameters, see #apds99xx_params_t + * + * @retval APDS99XX_OK on success + * @retval APDS99XX_ERROR_* a negative error code on error, + * see #apds99xx_error_codes_t + */ +int apds99xx_init(apds99xx_t *dev, const apds99xx_params_t *params); + +/** + * @brief Ambient light sensing (ALS) data-ready status function + * + * The function reads the ALS valid flag in status register and returns. + * It can be used for polling new ambient light sensing data. + * + * @param[in] dev device descriptor of APDS99XX sensor + * + * @retval APDS99XX_OK new abmient light data available + * @retval APDS99XX_ERROR_NO_DATA no new abmient light data available + * @retval APDS99XX_ERROR_* negative error code, + * see #apds99xx_error_codes_t + */ +int apds99xx_data_ready_als(const apds99xx_t *dev); + +/** + * @brief Read one raw data sample of ambient light sensing (ALS) + * + * Raw ambient light sensing (ALS) data are available as 16-bit count values + * (cnt_als). The range of these count values depends on the ALS integration + * time apds99xx_params_t::als_steps and the ALS gain + * apds99xx_params_t::als_gain. The maximum count value (cnt_als_max) is: + * + * cnt_als_max = APDS99XX_CNTS_P_STEP * als_steps * als_gain + * + * If there are no new data ready to read, last valid data sample is returned. + * Function #apds99xx_data_ready_als could be used to check whether new data + * are available before this function is called. + * + * @param[in] dev device descriptor of APDS99XX sensor + * @param[out] raw raw ambient light sensing data as count value + * + * @retval APDS99XX_OK on success + * @retval APDS99XX_ERROR_* negative error code, + * see #apds99xx_error_codes_t + */ +int apds99xx_read_als_raw(const apds99xx_t *dev, uint16_t *raw); + +#if MODULE_APDS9900 || MODULE_APDS9901 || MODULE_APDS9930 || DOXYGEN +/** + * @brief Read one data sample of illuminance in lux + * + * Illuminance in lux is computed from raw ambient light sensing (ALS) data + * which are measured in counts. Since the range of ALS data depend on ALS + * integration time apds99xx_params_t::als_steps and the ALS gain + * apds99xx_params_t::als_gain, these parameters also affect the sensitivity + * of the illuminance. + * + * @note This function is only available for APDS9900, APDS9901 and APD9930. + * + * @param[in] dev device descriptor of APDS99XX sensor + * @param[out] lux illuminance in lux + * + * @retval APDS99XX_OK on success + * @retval APDS99XX_ERROR_* negative error code, + * see #apds99xx_error_codes_t + */ +int apds99xx_read_illuminance(const apds99xx_t *dev, uint16_t *lux); + +#endif /* MODULE_APDS9900 || MODULE_APDS9901 || MODULE_APDS9930 || DOXYGEN */ + +#if MODULE_APDS9950 || MODULE_APDS9960 || DOXYGEN +/** + * @brief Read one raw RGB color data sample (APDS9950 and APDS9960 only) + * + * In APDS9950 and APDS9960 sensors, ambient light sensing (ALS) also detects + * spectral components of the light as RGB count values. This function can + * be used to fetch raw RGB data. + * + * Raw RGB data are available as 16-bit count values (cnt_als). + * The range of these count values depends on the ALS integration time + * apds99xx_params_t::als_steps and the ALS gain apds99xx_params_t::als_gain. + * The maximum count value (cnt_rgb_max) is: + * + * cnt_rgb_max = APDS99XX_CNTS_P_STEP * als_steps * als_gain + * + * If there are no data ready to read, last valid data sample is returned. + * Function #apds99xx_data_ready_als could be used to check whether new data + * are available before this function is called. + * + * @note This function is only available for APDS9950 and APD9960. + * + * @param[in] dev device descriptor of APDS99XX sensor + * @param[out] rgb RGB color data sample as count values + * + * @retval APDS99XX_OK on success + * @retval APDS99XX_ERROR_* negative error code, + * see #apds99xx_error_codes_t + */ +int apds99xx_read_rgb_raw(const apds99xx_t *dev, apds99xx_rgb_t *rgb); + +#endif /* MODULE_APDS9950 || MODULE_APDS9960 || DOXYGEN */ + +/** + * @brief Proximity sensing (PRX) data-ready status function + * + * The function reads the proximity valid flag in status register and returns. + * It can be used for polling new proximity sensing data. + * + * @param[in] dev device descriptor of APDS99XX sensor + * + * @retval APDS99XX_OK new proximity data available + * @retval APDS99XX_ERROR_NO_DATA no new proximity data available + * @retval APDS99XX_ERROR_* negative error code, + * see #apds99xx_error_codes_t + */ +int apds99xx_data_ready_prx(const apds99xx_t *dev); + +/** + * @brief Read one data sample of proximity sensing (PRX) + * + * Proximity data samples are given as a 16-bit count values (cnt_prx). + * + * @note APDS9960 returns only 8-bit values in the range of 0 to 255. + * + * The range of the count values depends on the PRX LED drive strength + * apds99xx_params_t::prx_drive the PRX gain apds99xx_params_t::prx_gain, + * and if used, the PRX integration time apds99xx_params_t::prx_time. + * + * @note A number of disturbing effects such as DC noise, sensor coverage, + * or surrounding objects cause an offset in the measured proximity values. + * The application should remove this offset, for example, by finding the + * minimum value ever measured and subtracting it from the current reading. + * The minimum value is then assumed to be 0 (no proximity). + * + * @param[in] dev device descriptor of APDS99XX sensor + * @param[out] prx proximity sensing data as count value + * + * @retval APDS99XX_OK on success + * @retval APDS99XX_ERROR_* a negative error code on error, + * see #apds99xx_error_codes_t + */ +int apds99xx_read_prx_raw(const apds99xx_t *dev, uint16_t *prx); + +/** + * @brief Power down the sensor + * + * The sensor is switched into sleep mode. It remains operational on the I2C + * interface. Depending on the sensor used, it consumes only about 1 to 3 uA + * in this mode. + * + * @param[in] dev device descriptor of APDS99XX sensor + * + * @retval APDS99XX_OK on success + * @retval APDS99XX_ERROR_* a negative error code on error, + * see #apds99xx_error_codes_t + */ +int apds99xx_power_down(const apds99xx_t *dev); + +/** + * @brief Power up the sensor + * + * The sensor is woken up from sleep mode. + * + * @param[in] dev device descriptor of APDS99XX sensor + * + * @retval APDS99XX_OK on success + * @retval APDS99XX_ERROR_* a negative error code on error, + * see #apds99xx_error_codes_t + */ +int apds99xx_power_up(const apds99xx_t *dev); + +#if MODULE_APDS99XX_FULL || DOXYGEN + +/** + * @brief Configure the interrupts of the sensor + * + * The function configures the interrupts of the sensor and sets the ISR + * as well as its argument for handling the interrupts. + * + * If any interrupt is enabled by the configuration + * (apds99xx_int_config_t::als_int_en or apds99xx_int_config_t::als_int_en are + * set), the function + * + * - initializes the GPIO defined by apds99xx_params_t::int_pin, and + * - attaches the ISR specified by the @p isr parameter to the interrupt. + * + * @note Since the ISR is executed in the interrupt context, it must not be + * blocking or time consuming. In addition, it must not access the sensor + * directly via I2C. It should only indicate to a waiting thread that an + * interrupt has occurred, which is then handled in the thread context. + * + * @param[in] dev device descriptor of APDS99XX sensor + * @param[in] cfg interrupt configuration, see #apds99xx_int_config_t + * @param[in] isr ISR called for all types of interrupts + * @param[in] isr_arg ISR argument, can be NULL + * + * @retval APDS99XX_OK on success + * @retval APDS99XX_ERROR_* negative error code on error, + * see #apds99xx_error_codes_t + */ +int apds99xx_int_config(apds99xx_t *dev, apds99xx_int_config_t* cfg, + apds99xx_isr_t isr, void *isr_arg); + +/** + * @brief Get the source of an interrupt + * + * The function clears the interrupt signal and returns the source of the + * interrupt. Since the interrupt signal is only reset by this function, + * it must be executed to reset the interrupt signal, even if the type of the + * triggered interrupt is not of interest. + * + * @note It must not be called from the ISR to avoid I2C bus accesses in + * the interrupt context. + * + * @param[in] dev device descriptor of APDS99XX sensor + * @param[out] src interrupt source, see #apds99xx_int_source_t + * + * @retval APDS99XX_OK on success + * @retval APDS99XX_ERROR_* negative error code on error, + * see #apds99xx_error_codes_t + */ +int apds99xx_int_source(apds99xx_t *dev, apds99xx_int_source_t* src); + +#endif /* MODULE_APDS99XX_FULL */ + +#ifdef __cplusplus +} +#endif + +#endif /* APDS99XX_H */ +/** @} */ diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index beacafb4d3..6c39aaf7b2 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -128,6 +128,14 @@ PSEUDOMODULES += adc081c PSEUDOMODULES += adc101c PSEUDOMODULES += adc121c +# include variants of APDS99XX drivers as pseudo modules +PSEUDOMODULES += apds9900 +PSEUDOMODULES += apds9901 +PSEUDOMODULES += apds9930 +PSEUDOMODULES += apds9950 +PSEUDOMODULES += apds9960 +PSEUDOMODULES += apds99xx_full + # full featured version of CCS811 driver as pseudo module PSEUDOMODULES += ccs811_full