drivers: add gp2y10xx dust sensor

Signed-off-by: Jean Pierre Dudey <me@jeandudey.tech>
This commit is contained in:
Jean Pierre Dudey 2020-11-05 19:44:58 +01:00
parent dbfbe2a9e8
commit e920a2e645
14 changed files with 605 additions and 0 deletions

View File

@ -24,6 +24,7 @@ menu "Sensor Device Drivers"
rsource "ads101x/Kconfig"
rsource "bmx055/Kconfig"
rsource "fxos8700/Kconfig"
rsource "gp2y10xx/Kconfig"
rsource "hdc1000/Kconfig"
rsource "isl29020/Kconfig"
rsource "l3g4200d/Kconfig"

23
drivers/gp2y10xx/Kconfig Normal file
View File

@ -0,0 +1,23 @@
# Copyright (c) 2020 Locha Inc
#
# 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.
#
menuconfig KCONFIG_USEMODULE_GP2Y10XX
bool "Configure GP2Y10xx driver"
depends on USEMODULE_GP2Y10XX
help
Configure the GP2Y10XX driver using Kconfig.
if KCONFIG_USEMODULE_GP2Y10XX
config GP2Y10XX_MAX_READINGS
int "Numbers of readings to use for average ADC value"
default 10
help
This configures the maximum number of ADC readings to calculate
the average ADC value.
endif # KCONFIG_USEMODULE_GP2Y10XX

View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,3 @@
FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_adc
USEMODULE += xtimer

View File

@ -0,0 +1,2 @@
USEMODULE_INCLUDES_gp2y10xx := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_gp2y10xx)

144
drivers/gp2y10xx/gp2y10xx.c Normal file
View File

@ -0,0 +1,144 @@
/*
* Copyright (C) 2020 Locha Inc
*
* 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_gp2y10xx
* @{
*
* @file
* @brief GP2Y10xx Compact Optical Dust Sensor device driver
*
* @author Jean Pierre Dudey <jeandudey@hotmail.com>
* @}
*/
#include <assert.h>
#include "gp2y10xx.h"
#include "gp2y10xx_params.h"
#include "periph/adc.h"
#include "periph/gpio.h"
#include "xtimer.h"
#define ENABLE_DEBUG 0
#include "debug.h"
#define ILED_PULSE_WAIT_US (280U)
#define ILED_PULSE_OFF_US (20U)
#define NO_DUST_VOLTAGE (500)
static inline void _iled_on(const gp2y10xx_t *dev)
{
gpio_write(dev->params.iled_pin,
dev->params.iled_level == GP2Y10XX_ILED_LEVEL_HIGH);
}
static inline void _iled_off(const gp2y10xx_t *dev)
{
gpio_write(dev->params.iled_pin,
dev->params.iled_level == GP2Y10XX_ILED_LEVEL_LOW);
}
static inline int _adc_resolution(adc_res_t res)
{
/* get the resolution the ADC can read */
int exp = 0;
switch (res) {
case ADC_RES_6BIT:
exp = 6;
break;
case ADC_RES_8BIT:
exp = 8;
break;
case ADC_RES_10BIT:
exp = 10;
break;
case ADC_RES_12BIT:
exp = 12;
break;
case ADC_RES_14BIT:
exp = 14;
break;
case ADC_RES_16BIT:
exp = 16;
break;
default:
assert(0);
break;
}
return exp;
}
int gp2y10xx_init(gp2y10xx_t *dev, const gp2y10xx_params_t *params)
{
assert(dev && params);
dev->params = *params;
if (adc_init(dev->params.aout) < 0) {
return GP2Y10XX_ERR_ADC;
}
if (gpio_init(dev->params.iled_pin, GPIO_OUT) < 0) {
return GP2Y10XX_ERR_ILED;
}
_iled_off(dev);
return GP2Y10XX_OK;
}
int gp2y10xx_read_density(const gp2y10xx_t *dev, uint16_t *density)
{
uint32_t adc_sum = 0;
int32_t adc_value;
uint32_t voltage;
assert(dev && density);
for (unsigned i = 0; i < CONFIG_GP2Y10XX_MAX_READINGS; i++) {
/* turn ILED on/off and wait a little bit to read the ADC */
_iled_on(dev);
xtimer_usleep(ILED_PULSE_WAIT_US);
if ((adc_value = adc_sample(dev->params.aout,
dev->params.adc_res)) < 0) {
_iled_off(dev);
return GP2Y10XX_ERR_ADC;
}
_iled_off(dev);
xtimer_usleep(ILED_PULSE_OFF_US);
DEBUG("[gp2y10xx] read: unfiltered adc_value=%"PRIi32"\n", adc_value);
adc_sum += adc_value;
}
/* calculate the average between all readings */
adc_value = adc_sum / CONFIG_GP2Y10XX_MAX_READINGS;
DEBUG("[gp2y10xx] read: filtered adc_value=%"PRIi32"\n", adc_value);
/* convert ADC reading to a voltage */
voltage = (dev->params.vref * adc_value);
voltage >>= _adc_resolution(dev->params.adc_res);
/* check if the voltage provides us meaningful data */
if (voltage <= NO_DUST_VOLTAGE) {
*density = 0;
}
else {
voltage -= NO_DUST_VOLTAGE;
}
DEBUG("[gp2y10xx] read: voltage=%"PRIi32"\n", voltage);
/* multiply by the magic eleven.
*
* XXX: find out why 11 is magic and the shenanigans behind it
*/
voltage *= 11;
/* convert "voltage" to ug/m3 */
*density = (uint16_t)((voltage * 2) / 10);
return GP2Y10XX_OK;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 Locha Inc
*
* 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_gp2y10xx
* @{
*
* @file
* @brief GP2Y10xxAU0F adaption to the RIOT actuator/sensor interface
*
* @author Jean Pierre Dudey <jeandudey@hotmail.com>
*
* @}
*/
#include <string.h>
#include <stdio.h>
#include "saul.h"
#include "gp2y10xx.h"
static int _read(const void *dev, phydat_t *res)
{
uint16_t val;
if (gp2y10xx_read_density((const gp2y10xx_t *)dev, &val) != GP2Y10XX_OK) {
return -ECANCELED;
}
/* ug/m3 so it's g/m3 with a -6 scale */
res->unit = UNIT_GPM3;
res->scale = -6;
res->val[0] = val;
return 1;
}
const saul_driver_t gp2y10xx_saul_driver = {
.read = _read,
.write = saul_notsup,
.type = SAUL_SENSE_PM,
};

View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2020 Locha Inc
*
* 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_gp2y10xx
* @{
*
* @file
* @brief Default configuration for GP2Y10xx devices
*
* @author Jean Pierre Dudey <jeandudey@hotmail.com>
* @}
*/
#ifndef GP2Y10XX_PARAMS_H
#define GP2Y10XX_PARAMS_H
#include "board.h"
#include "saul_reg.h"
#include "gp2y10xx.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup drivers_gp2y10xx_config GP2Y10xx driver compile configurations
* @ingroup drivers_gp2y10xx
* @ingroup config_drivers_sensors
* @{
*/
/**
* @brief ADC line to use
*/
#ifndef GP2Y10XX_PARAM_AOUT
#define GP2Y10XX_PARAM_AOUT (ADC_LINE(0))
#endif
/**
* @brief ADC line resolution
*/
#ifndef GP2Y10XX_PARAM_ADC_RES
#define GP2Y10XX_PARAM_ADC_RES (ADC_RES_10BIT)
#endif
/**
* @brief Reference voltage used for the VCC supply of the sensor, in mV.
*/
#ifndef GP2Y10XX_PARAM_VREF
#define GP2Y10XX_PARAM_VREF (3300)
#endif
/**
* @brief ILED GPIO pin
*/
#ifndef GP2Y10XX_PARAM_ILED_PIN
#define GP2Y10XX_PARAM_ILED_PIN (GPIO_UNDEF)
#endif
/**
* @brief ILED level, can be active-high or active-low.
*/
#ifndef GP2Y10XX_PARAM_ILED_LEVEL
#define GP2Y10XX_PARAM_ILED_LEVEL (GP2Y10XX_ILED_LEVEL_HIGH)
#endif
/**
* @brief GP2Y10xx driver configuration parameters
*/
#ifndef GP2Y10XX_PARAMS
#define GP2Y10XX_PARAMS { .aout = GP2Y10XX_PARAM_AOUT, \
.adc_res = GP2Y10XX_PARAM_ADC_RES, \
.vref = GP2Y10XX_PARAM_VREF, \
.iled_pin = GP2Y10XX_PARAM_ILED_PIN, \
.iled_level = GP2Y10XX_PARAM_ILED_LEVEL, \
}
#endif
/** @} */
/**
* @brief GP2Y10xx driver SAUL registry information structures
*/
#ifndef GP2Y10XX_SAUL_INFO
#define GP2Y10XX_SAUL_INFO { .name = "gp2y1010" }
#endif
/**
* @brief GP2Y1010 configuration
*/
static const gp2y10xx_params_t gp2y10xx_params[] =
{
GP2Y10XX_PARAMS
};
/**
* @brief Additional meta information to keep in the SAUL registry
*/
static const saul_reg_info_t gp2y10xx_saul_info[] =
{
GP2Y10XX_SAUL_INFO
};
#ifdef __cplusplus
}
#endif
#endif /* GP2Y10XX_PARAMS_H */

128
drivers/include/gp2y10xx.h Normal file
View File

@ -0,0 +1,128 @@
/*
* Copyright (C) 2020 Locha Inc
*
* 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_gp2y10xx GP2Y10xx Optical Dust Sensor device driver
* @ingroup drivers_sensors
* @ingroup drivers_saul
* @brief GP2Y10xx Optical Dust Sensor Converter device driver
*
* This driver works with GP2Y1010AU0F and GP2Y1014AU0F versions.
*
* This driver provides @ref drivers_saul capabilities.
*
* # Usage
*
* ```make
* USEMODULE += gp2y10xx
* ```
*
* The device can be initialized with @ref gp2y10xx_init and
* @ref gp2y10xx_read_density is used to read the current dust density that
* the sensor can detect.
*
* @{
*
* @file
* @brief GP2Y10xx device driver
*
* @author Jean Pierre Dudey <jeandudey@hotmail.com>
*/
#ifndef GP2Y10XX_H
#define GP2Y10XX_H
#include <stdint.h>
#include <stdbool.h>
#include "periph/adc.h"
#include "periph/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief This configures the maximum number of ADC readings stored to
* calculate the average ADC value.
*
* The bigger the number of readings the bigger each device descriptor will be.
*/
#ifndef CONFIG_GP2Y10XX_MAX_READINGS
#define CONFIG_GP2Y10XX_MAX_READINGS (10)
#endif
/**
* @brief Driver error values
*/
enum {
GP2Y10XX_OK = 0, /**< Everything is ok */
GP2Y10XX_ERR_ADC = -1, /**< ADC error */
GP2Y10XX_ERR_ILED = -2, /**< ILED pin error */
};
/**
* @brief ILED pin level.
*
* This specifies how the ILED pin behaves, if it's on when it's
* active-low/high. Waveshare breakout board is active-high, that is, that the
* voltage is 3.3v to turn ILED on (logic 1) and 0v to turn it off (logic 0).
*/
typedef enum {
GP2Y10XX_ILED_LEVEL_HIGH, /**< Active high */
GP2Y10XX_ILED_LEVEL_LOW, /**< Active low */
} gp2y10xx_level_t;
/**
* @brief GP2Y10xx device parameters
*/
typedef struct {
adc_t aout; /**< ADC line connected to device AOUT pin. */
adc_res_t adc_res; /**< ADC line resolution. */
uint16_t vref; /**< Reference voltage used for the VCC supply of the
sensor, in mV. */
gpio_t iled_pin; /**< ILED pin */
gp2y10xx_level_t iled_level; /**< ILED pin level */
} gp2y10xx_params_t;
/**
* @brief GP2Y10xx device descriptor
*/
typedef struct {
gp2y10xx_params_t params; /**< device driver configuration */
} gp2y10xx_t;
/**
* @brief Initialize an GP2Y10xx device.
*
* @param[in,out] dev Device descriptor.
* @param[in] params Device configuration.
*
* @return GP2Y10XX_OK on successful initialization.
* @return GP2Y10XX_ERR_ADC if ADC line initialization failed.
* @return GP2Y10XX_ERR_ILED if the ILED pin initialization failed.
*/
int gp2y10xx_init(gp2y10xx_t *dev, const gp2y10xx_params_t *params);
/**
* @brief Read a raw ADC value
*
* @param[in] dev Device descriptor.
* @param[out] density Dust density value in ug/m3.
*
* @return GP2Y10XX_OK on successful read.
* @return Any other value on error.
*/
int gp2y10xx_read_density(const gp2y10xx_t *dev, uint16_t *density);
#ifdef __cplusplus
}
#endif
#endif /* GP2Y10XX_H */
/** @} */

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2020 Locha Inc
*
* 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 sys_auto_init_saul
* @{
*
* @file
* @brief Auto initialization of GP2Y10xx ADC
*
* @author Jean Pierre Dudey <jeandudey@hotmail.com>
*
* @}
*/
#include "assert.h"
#include "log.h"
#include "saul_reg.h"
#include "gp2y10xx.h"
#include "gp2y10xx_params.h"
/**
* @brief Define the number of configured sensors
*/
#define GP2Y10XX_NUM ARRAY_SIZE(gp2y10xx_params)
/**
* @brief Allocate memory for the device descriptors
*/
static gp2y10xx_t gp2y10xx_devs[GP2Y10XX_NUM];
/**
* @brief Memory for the SAUL registry entries
*/
static saul_reg_t saul_entries[GP2Y10XX_NUM];
/**
* @brief Define the number of saul info
*/
#define GP2Y10XX_INFO_NUM ARRAY_SIZE(gp2y10xx_saul_info)
/**
* @brief Reference the driver struct
*/
extern saul_driver_t gp2y10xx_saul_driver;
void auto_init_gp2y10xx(void)
{
assert(GP2Y10XX_INFO_NUM == GP2Y10XX_NUM);
for (unsigned i = 0; i < GP2Y10XX_NUM; i++) {
LOG_DEBUG("[auto_init_saul] initializing gp2y10xx #%d\n", i);
if (gp2y10xx_init(&gp2y10xx_devs[i], &gp2y10xx_params[i]) < 0) {
LOG_ERROR("[auto_init_saul] error initializing gp2y10xx #%d\n", i);
continue;
}
saul_entries[i].dev = &(gp2y10xx_devs[i]);
saul_entries[i].name = gp2y10xx_saul_info[i].name;
saul_entries[i].driver = &gp2y10xx_saul_driver;
saul_reg_add(&(saul_entries[i]));
}
}

View File

@ -99,6 +99,10 @@ void saul_init_devs(void)
extern void auto_init_fxos8700(void);
auto_init_fxos8700();
}
if (IS_USED(MODULE_GP2Y10XX)) {
extern void auto_init_gp2y10xx(void);
auto_init_gp2y10xx();
}
if (IS_USED(MODULE_GROVE_LEDBAR)) {
extern void auto_init_grove_ledbar(void);
auto_init_grove_ledbar();

View File

@ -0,0 +1,6 @@
include ../Makefile.tests_common
USEMODULE += gp2y10xx
USEMODULE += xtimer
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,6 @@
# About
This is a test application for the GP2Y10xx Compact Optical Dust Sensors.
# Usage
This test application will initialize the sensor for measuring dust density,
after that it will print measurements on STDOUT periodically.

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2020 Locha Inc
*
* 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 tests
* @{
*
* @file
* @brief Test application for the GP2Y10xx Compact Dust Density Sensors.
*
* @author Jean Pierre Dudey <jeandudey@hotmail.com>
* @}
*/
#include <stdio.h>
#include "xtimer.h"
#include "timex.h"
#include "gp2y10xx.h"
#include "gp2y10xx_params.h"
static gp2y10xx_t dev;
int main(void)
{
int res;
uint16_t density;
puts("GP2Y10xx driver test application\n");
printf("Initializing GP2Y10xx at ADC_LINE(%i)...\n",
gp2y10xx_params->aout);
if ((res = gp2y10xx_init(&dev, gp2y10xx_params)) == GP2Y10XX_OK) {
puts("[OK]");
}
else {
printf("[Failed] res=%i\n", res);
return -1;
}
while (1) {
res = gp2y10xx_read_density(&dev, &density);
if (res == GP2Y10XX_OK) {
printf("Dust density %"PRIu16" ug/m3\n", density);
}
else {
printf("Error reading data. err: %d\n", res);
}
xtimer_msleep(250);
}
return 0;
}