From e920a2e645c40561307511a617e326b6b1b2fbc7 Mon Sep 17 00:00:00 2001 From: Jean Pierre Dudey Date: Thu, 5 Nov 2020 19:44:58 +0100 Subject: [PATCH] drivers: add gp2y10xx dust sensor Signed-off-by: Jean Pierre Dudey --- drivers/Kconfig | 1 + drivers/gp2y10xx/Kconfig | 23 ++++ drivers/gp2y10xx/Makefile | 1 + drivers/gp2y10xx/Makefile.dep | 3 + drivers/gp2y10xx/Makefile.include | 2 + drivers/gp2y10xx/gp2y10xx.c | 144 ++++++++++++++++++++ drivers/gp2y10xx/gp2y10xx_saul.c | 46 +++++++ drivers/gp2y10xx/include/gp2y10xx_params.h | 112 +++++++++++++++ drivers/include/gp2y10xx.h | 128 +++++++++++++++++ drivers/saul/init_devs/auto_init_gp2y10xx.c | 70 ++++++++++ drivers/saul/init_devs/init.c | 4 + tests/driver_gp2y10xx/Makefile | 6 + tests/driver_gp2y10xx/README.md | 6 + tests/driver_gp2y10xx/main.c | 59 ++++++++ 14 files changed, 605 insertions(+) create mode 100644 drivers/gp2y10xx/Kconfig create mode 100644 drivers/gp2y10xx/Makefile create mode 100644 drivers/gp2y10xx/Makefile.dep create mode 100644 drivers/gp2y10xx/Makefile.include create mode 100644 drivers/gp2y10xx/gp2y10xx.c create mode 100644 drivers/gp2y10xx/gp2y10xx_saul.c create mode 100644 drivers/gp2y10xx/include/gp2y10xx_params.h create mode 100644 drivers/include/gp2y10xx.h create mode 100644 drivers/saul/init_devs/auto_init_gp2y10xx.c create mode 100644 tests/driver_gp2y10xx/Makefile create mode 100644 tests/driver_gp2y10xx/README.md create mode 100644 tests/driver_gp2y10xx/main.c diff --git a/drivers/Kconfig b/drivers/Kconfig index b6d7dac8535c..675909b6b458 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -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" diff --git a/drivers/gp2y10xx/Kconfig b/drivers/gp2y10xx/Kconfig new file mode 100644 index 000000000000..e54eb805cf90 --- /dev/null +++ b/drivers/gp2y10xx/Kconfig @@ -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 diff --git a/drivers/gp2y10xx/Makefile b/drivers/gp2y10xx/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/drivers/gp2y10xx/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/gp2y10xx/Makefile.dep b/drivers/gp2y10xx/Makefile.dep new file mode 100644 index 000000000000..9246e3a21c16 --- /dev/null +++ b/drivers/gp2y10xx/Makefile.dep @@ -0,0 +1,3 @@ +FEATURES_REQUIRED += periph_gpio +FEATURES_REQUIRED += periph_adc +USEMODULE += xtimer diff --git a/drivers/gp2y10xx/Makefile.include b/drivers/gp2y10xx/Makefile.include new file mode 100644 index 000000000000..9b14dd6d0fef --- /dev/null +++ b/drivers/gp2y10xx/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_gp2y10xx := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_gp2y10xx) diff --git a/drivers/gp2y10xx/gp2y10xx.c b/drivers/gp2y10xx/gp2y10xx.c new file mode 100644 index 000000000000..c1270d26cf02 --- /dev/null +++ b/drivers/gp2y10xx/gp2y10xx.c @@ -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 + * @} + */ + +#include + +#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; +} diff --git a/drivers/gp2y10xx/gp2y10xx_saul.c b/drivers/gp2y10xx/gp2y10xx_saul.c new file mode 100644 index 000000000000..0932f6ee327f --- /dev/null +++ b/drivers/gp2y10xx/gp2y10xx_saul.c @@ -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 + * + * @} + */ + +#include +#include + +#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, +}; diff --git a/drivers/gp2y10xx/include/gp2y10xx_params.h b/drivers/gp2y10xx/include/gp2y10xx_params.h new file mode 100644 index 000000000000..3cb48d34a602 --- /dev/null +++ b/drivers/gp2y10xx/include/gp2y10xx_params.h @@ -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 + * @} + */ + +#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 */ diff --git a/drivers/include/gp2y10xx.h b/drivers/include/gp2y10xx.h new file mode 100644 index 000000000000..4979de7df811 --- /dev/null +++ b/drivers/include/gp2y10xx.h @@ -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 + */ + +#ifndef GP2Y10XX_H +#define GP2Y10XX_H + +#include +#include + +#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 */ +/** @} */ diff --git a/drivers/saul/init_devs/auto_init_gp2y10xx.c b/drivers/saul/init_devs/auto_init_gp2y10xx.c new file mode 100644 index 000000000000..8b8e07614799 --- /dev/null +++ b/drivers/saul/init_devs/auto_init_gp2y10xx.c @@ -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 + * + * @} + */ + +#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])); + } +} diff --git a/drivers/saul/init_devs/init.c b/drivers/saul/init_devs/init.c index c068657d740a..45b95e7f3c6b 100644 --- a/drivers/saul/init_devs/init.c +++ b/drivers/saul/init_devs/init.c @@ -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(); diff --git a/tests/driver_gp2y10xx/Makefile b/tests/driver_gp2y10xx/Makefile new file mode 100644 index 000000000000..b6c2030858c5 --- /dev/null +++ b/tests/driver_gp2y10xx/Makefile @@ -0,0 +1,6 @@ +include ../Makefile.tests_common + +USEMODULE += gp2y10xx +USEMODULE += xtimer + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_gp2y10xx/README.md b/tests/driver_gp2y10xx/README.md new file mode 100644 index 000000000000..a147acb83a03 --- /dev/null +++ b/tests/driver_gp2y10xx/README.md @@ -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. diff --git a/tests/driver_gp2y10xx/main.c b/tests/driver_gp2y10xx/main.c new file mode 100644 index 000000000000..6d7bf594d76f --- /dev/null +++ b/tests/driver_gp2y10xx/main.c @@ -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 + * @} + */ + +#include + +#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; +}