mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-01-18 12:52:44 +01:00
6d61381d2a
The expandable GPIO API requires the comparison of structured GPIO types. This means that inline functions must be used instead of direct comparisons. For the migration process, drivers must first be changed so that they use the inline comparison functions.
540 lines
15 KiB
C
540 lines
15 KiB
C
/*
|
|
* 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 <gunar@schorcht.net>
|
|
* @file
|
|
* @}
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#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;
|
|
}
|
|
|
|
if (_update_reg(dev, APDS99XX_REG_CONFIG, APDS99XX_REG_WLONG,
|
|
dev->params.wait_long)) {
|
|
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(gpio_is_valid(dev->params.int_pin));
|
|
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;
|
|
}
|