diff --git a/drivers/Kconfig b/drivers/Kconfig index c9fba8bd03f6..2f4ac0b58762 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -121,6 +121,7 @@ rsource "shtc1/Kconfig" rsource "si70xx/Kconfig" rsource "si114x/Kconfig" rsource "si1133/Kconfig" +rsource "sm_pwm_01c/Kconfig" rsource "sps30/Kconfig" rsource "srf02/Kconfig" rsource "srf04/Kconfig" diff --git a/drivers/include/sm_pwm_01c.h b/drivers/include/sm_pwm_01c.h new file mode 100644 index 000000000000..89fb12eb2c29 --- /dev/null +++ b/drivers/include/sm_pwm_01c.h @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2021 Inria + * + * 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_sm_pwm_01c SM_PWM_01C dust sensor + * @ingroup drivers_sensors + * @brief Driver for Amphenol SM_PWM_01C infrared dust sensor + * @{ + * + * + * * About + * ===== + * + * This driver provides an interface for the Amphenol SM-PWM-Sensor. + * The Datasheet can be found [here](https://www.cdiweb.com/datasheets/telaire-amphenol/01c%20dust%20sensor%20datasheet.pdf). + * and the more complete application note [here](https://www.sgbotic.com/products/datasheets/sensors/app-SM-PWM-01C.pdf) + * + * The device can measure small particles (1~ 2μm) and large particle (3 ~10μm), + * so similar to PM2.5 and PM10. The dust sensor cannot count particles only + * measure estimated concentrations. + * + * It is recommended to compute values over a 30s moving average. By default + * a moving average is used since the module MODULE_SM_PWM_01C_MA is + * activated by default. To save memory an exponential average can be used + * by disabling this module. + * + * @file + * @brief SM_PWM_01C Device Driver + * + * @author Francisco Molina + */ + +#ifndef SM_PWM_01C_H +#define SM_PWM_01C_H + +#include + +#include "timex.h" +#include "ztimer.h" +#include "periph/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup drivers_sm_pwm_01c_conf SM_PWM_01C compile configurations + * @ingroup drivers_sm_pwm_01c + * @ingroup config + * @{ + */ + +/** + * @def CONFIG_SM_PWM_01C_SAMPLE_TIME + * + * @brief Frequency at witch LPO % is calculated + */ +#ifndef CONFIG_SM_PWM_01C_SAMPLE_TIME +#define CONFIG_SM_PWM_01C_SAMPLE_TIME (100 * US_PER_MS) +#endif + +/** + * @def CONFIG_SM_PWM_01C_WINDOW_TIME + * + * @brief Length in time of the measuring window, recommended 5-30s + */ +#ifndef CONFIG_SM_PWM_01C_WINDOW_TIME +#define CONFIG_SM_PWM_01C_WINDOW_TIME (10 * US_PER_SEC) +#endif + +#if defined(MODULE_SM_PWM_01C_MA) || defined(DOXYGEN) +/** + * @def SM_PWM_01C_BUFFER_LEN + * + * @brief Length in time of the measuring window + */ +#define SM_PWM_01C_BUFFER_LEN (CONFIG_SM_PWM_01C_WINDOW_TIME / \ + CONFIG_SM_PWM_01C_SAMPLE_TIME) +#else + +/** + * @def CONFIG_SM_PWM_01C_EXP_WEIGHT + * + * @brief Weight of the exponential average filter where: + * CONFIG_SM_PWM_01C_EXP_WEIGHT = 1 / (1 - alpha). + * + * @note Should be chosen wisely, it can be done my minimizing MSE + * or other algorithms as Marquardt procedure. + */ +#ifndef CONFIG_SM_PWM_01C_EXP_WEIGHT +#define CONFIG_SM_PWM_01C_EXP_WEIGHT (5) +#endif +#endif + +/** @} */ + +#if defined(MODULE_SM_PWM_01C_MA) || defined(DOXYGEN) +/** + * @brief Circular buffer holding moving average values + * @internal + * + */ +typedef struct { + uint16_t buf[SM_PWM_01C_BUFFER_LEN]; /**< circular buffer memory */ + size_t head; /**< current buffer head */ +} circ_buf_t; +#endif + +/** + * @brief Parameters for the SM_PWM_01c sensor + * + * These parameters are needed to configure the device at startup. + */ +typedef struct { + gpio_t tsp_pin; /**< Low Pulse Signal Output (P1) of small Particle, + active low, PM2.5 equivalent */ + gpio_t tlp_pin; /**< Low Pulse Signal Output (P2) of large Particle, + active low, PM10 equivalent */ +} sm_pwm_01c_params_t; + +/** + * @brief LPO and concentration (ug/m3) values for small and large particles + * + * @note Actual measured particle size are: 1~ 2μm for small particles and 3 ~10μm, + * for large particles, but this values are exposed as standard PM2.5 and + * PM10 measurements. + */ +typedef struct { + uint16_t mc_pm_2p5; /**< Small particle concentration ug/m3 */ + uint16_t mc_pm_10; /**< Large particle concentration ug/m3 */ +} sm_pwm_01c_data_t; + +/** + * @brief LPO and concentration (ug/m3) values for small and large particles + * @internal + */ +typedef struct { + uint32_t tsp_lpo; /**< Small particle low Pulse active time us */ + uint32_t tlp_lpo; /**< Large Particle low Pulse active time us */ + uint32_t tlp_start_time; /**< Last time tlp pin went low */ + uint32_t tsp_start_time; /**< Last time tsp pin went low */ +#ifdef MODULE_SM_PWM_01C_MA + circ_buf_t tsp_circ_buf; /**< Small particle moving average values */ + circ_buf_t tlp_circ_buf; /**< Large particle moving average values */ +#else + sm_pwm_01c_data_t data; /**< Current value for the exponentially averaged + particle concentration values */ +#endif +} sm_pwm_01c_values_t; + +/** + * @brief Device descriptor for the SM_PWM_01c sensor + */ +typedef struct { + sm_pwm_01c_params_t params; /**< Device driver parameters */ + sm_pwm_01c_values_t _values; /**< Internal data to calculate concentration + from tsl/tsp low Pulse Output Occupancy */ + ztimer_t _sampler; /**< internal sampling timer */ +} sm_pwm_01c_t; + +/** + * @brief Initialize the given SM_PWM_01C device + * + * @param[out] dev Initialized device descriptor of SM_PWM_01C device + * @param[in] params The parameters for the SM_PWM_01C device + * + * @retval 0 on success + * @retval -EIO GPIO error + */ +int sm_pwm_01c_init(sm_pwm_01c_t *dev, const sm_pwm_01c_params_t *params); + +/** + * @brief Start continuous measurement of Large and Small particle + * concentrations + * + * @param[in] dev Device descriptor of SM_PWM_01C device + */ +void sm_pwm_01c_start(sm_pwm_01c_t *dev); + +/** + * @brief Stops continuous measurement of Large and Small particle + * concentration + * + * @param[in] dev Device descriptor of SM_PWM_01C device + */ +void sm_pwm_01c_stop(sm_pwm_01c_t *dev); + +/** + * @brief Reads particle concentration values + * + * @param[in] dev Device descriptor of SM_PWM_01C device + * @param[out] data Pre-allocated memory to hold measured concentrations + * + */ +void sm_pwm_01c_read_data(sm_pwm_01c_t *dev, sm_pwm_01c_data_t *data); + +#ifdef __cplusplus +} +#endif + +#endif /* SM_PWM_01C_H */ +/** @} */ diff --git a/drivers/saul/init_devs/auto_init_sm_pwm_01c.c b/drivers/saul/init_devs/auto_init_sm_pwm_01c.c new file mode 100644 index 000000000000..150957c79103 --- /dev/null +++ b/drivers/saul/init_devs/auto_init_sm_pwm_01c.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 Inria + * + * 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 for SM_PWM_01C dust sensor + * + * @author Francisco Molina + * @} + */ + +#include "assert.h" +#include "log.h" +#include "saul_reg.h" +#include "sm_pwm_01c_params.h" +#include "sm_pwm_01c.h" + +/** + * @brief Allocate memory for the device descriptors + */ +static sm_pwm_01c_t sm_pwm_01c_devs[SM_PWM_01C_NUMOF]; + +#if IS_ACTIVE(MODULE_SAUL) +/** + * @brief Memory for the SAUL registry entries + */ +static saul_reg_t saul_entries[SM_PWM_01C_NUMOF * 2]; + +/** + * @brief Define the number of saul info + */ +#define SM_PWM_01C_INFO_NUM ARRAY_SIZE(sm_pwm_01c_saul_info) + +/** + * @name Import SAUL endpoints + * @{ + */ +extern const saul_driver_t sm_pwm_01c_saul_driver_mc_pm_2p5; +extern const saul_driver_t sm_pwm_01c_saul_driver_mc_pm_10; +/** @} */ +#endif + +void auto_init_sm_pwm_01c(void) +{ +#if IS_ACTIVE(MODULE_SAUL) + assert(SM_PWM_01C_INFO_NUM == SM_PWM_01C_NUMOF); +#endif + + for (unsigned int i = 0; i < SM_PWM_01C_NUMOF; i++) { + LOG_DEBUG("[auto_init_saul] initializing sm_pwm_01c #%u\n", i); + + if (sm_pwm_01c_init(&sm_pwm_01c_devs[i], &sm_pwm_01c_params[i])) { + LOG_ERROR("[auto_init_saul] error initializing sm_pwm_01c #%u\n", + i); + continue; + } + sm_pwm_01c_start(&sm_pwm_01c_devs[i]); +#if IS_ACTIVE(MODULE_SAUL) + saul_entries[(i * 2)].dev = &(sm_pwm_01c_devs[i]); + saul_entries[(i * 2)].name = sm_pwm_01c_saul_info[i].name; + saul_entries[(i * 2)].driver = &sm_pwm_01c_saul_driver_mc_pm_2p5; + saul_entries[(i * 2) + 1].dev = &(sm_pwm_01c_devs[i]); + saul_entries[(i * 2) + 1].name = sm_pwm_01c_saul_info[i].name; + saul_entries[(i * 2) + 1].driver = &sm_pwm_01c_saul_driver_mc_pm_10; + saul_reg_add(&(saul_entries[(i * 2)])); + saul_reg_add(&(saul_entries[(i * 2) + 1])); +#endif + } +} diff --git a/drivers/saul/init_devs/init.c b/drivers/saul/init_devs/init.c index bf97cd30ba98..f765529599b7 100644 --- a/drivers/saul/init_devs/init.c +++ b/drivers/saul/init_devs/init.c @@ -271,6 +271,10 @@ void saul_init_devs(void) extern void auto_init_si70xx(void); auto_init_si70xx(); } + if (IS_USED(MODULE_SM_PWM_01C)) { + extern void auto_init_sm_pwm_01c(void); + auto_init_sm_pwm_01c(); + } if (IS_USED(MODULE_SPS30)) { extern void auto_init_sps30(void); auto_init_sps30(); diff --git a/drivers/sm_pwm_01c/Kconfig b/drivers/sm_pwm_01c/Kconfig new file mode 100644 index 000000000000..dc22036d6c6d --- /dev/null +++ b/drivers/sm_pwm_01c/Kconfig @@ -0,0 +1,60 @@ +# Copyright (c) 2021 Inria +# +# 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 MODULE_SM_PWM_01C + bool "SM_PWM_01C Amphenol infrared dust sensor" + depends on HAS_PERIPH_GPIO + depends on HAS_PERIPH_GPIO_IRQ + depends on TEST_KCONFIG + select MODULE_CHECKSUM + select MODULE_PERIPH_GPIO + select MODULE_PERIPH_GPIO_IRQ + select MODULE_ZTIMER + select MODULE_ZTIMER_USEC + select MODULE_ZTIMER_PERIPH_TIMER + +config MODULE_SM_PWM_01C_MA + bool "Use a moving average for sensor values" + depends on MODULE_SM_PWM_01C + default y + +menuconfig KCONFIG_USEMODULE_SM_PWM_01C + bool "Configure SM_PWM_01C driver" + depends on USEMODULE_SM_PWM_01C + help + Configure the SM_PWM_01C driver using Kconfig. + +if KCONFIG_USEMODULE_SM_PWM_01C + +config SM_PWM_01C_WINDOW_TIME + int "Measuring Window length" + default 10000000 + help + Length in time of the measuring window in microseconds, + recommended 5-30s. + +config SM_PWM_01C_SAMPLE_TIME + int "PWM occupancy sampling period" + default 100000 + help + Time, expressed in microseconds, at witch LPO is occupancy is + sampled and converted into particle matter concentration + +if !USEMODULE_SM_PWM_01C_MA + +config SM_PWM_01C_EXP_WEIGHT + int "Weight of the exponential" + default 100000 + help + Weight of the exponential average filter where: + SM_PWM_01C_EXP_WEIGHT = 1 / (1 - alpha). + + Should be chosen wisely, it can be done my minimizing MSE + or other algorithms as Marquardt procedure. + +endif # USEMODULE_SM_PWM_01C_MA + +endif # KCONFIG_USEMODULE_SM_PWM_01C diff --git a/drivers/sm_pwm_01c/Makefile b/drivers/sm_pwm_01c/Makefile new file mode 100644 index 000000000000..e10aefd443ea --- /dev/null +++ b/drivers/sm_pwm_01c/Makefile @@ -0,0 +1,7 @@ +SRC := sm_pwm_01c.c + +ifneq (,$(filter saul,$(USEMODULE))) + SRC += sm_pwm_01c_saul.c +endif + +include $(RIOTBASE)/Makefile.base diff --git a/drivers/sm_pwm_01c/Makefile.dep b/drivers/sm_pwm_01c/Makefile.dep new file mode 100644 index 000000000000..fa9d57f57728 --- /dev/null +++ b/drivers/sm_pwm_01c/Makefile.dep @@ -0,0 +1,5 @@ +USEMODULE += ztimer_usec +DEFAULT_MODULE += sm_pwm_01c_ma + +FEATURES_REQUIRED += periph_gpio +FEATURES_REQUIRED += periph_gpio_irq diff --git a/drivers/sm_pwm_01c/Makefile.include b/drivers/sm_pwm_01c/Makefile.include new file mode 100644 index 000000000000..8e61347bbb38 --- /dev/null +++ b/drivers/sm_pwm_01c/Makefile.include @@ -0,0 +1,4 @@ +USEMODULE_INCLUDES_sm_pwm_01c := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_sm_pwm_01c) + +PSEUDOMODULES += sm_pwm_01c_ma diff --git a/drivers/sm_pwm_01c/include/sm_pwm_01c_params.h b/drivers/sm_pwm_01c/include/sm_pwm_01c_params.h new file mode 100644 index 000000000000..904bdf6d9a09 --- /dev/null +++ b/drivers/sm_pwm_01c/include/sm_pwm_01c_params.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 Inria + * + * 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_sm_pwm_01c + * @{ + * @file + * @brief Default configuration for SM_PWM_01C driver + * + * @author Francisco Molina + * + */ + +#ifndef SM_PWM_01C_PARAMS_H +#define SM_PWM_01C_PARAMS_H + +#include "board.h" +#include "saul_reg.h" +#include "sm_pwm_01c.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Set default configuration parameters for the SM_PWM_01C + * @{ + */ +#ifndef SM_PWM_01C_TSP_PIN +#define SM_PWM_01C_TSP_PIN GPIO_PIN(0, 13) +#endif +#ifndef SM_PWM_01C_TLP_PIN +#define SM_PWM_01C_TLP_PIN GPIO_PIN(0, 28) +#endif +#ifndef SM_PWM_01C_SAUL_INFO +#define SM_PWM_01C_SAUL_INFO { .name = "sm-pwm-01c" } +#endif + +#ifndef SM_PWM_01C_PARAMS_DEFAULT +#define SM_PWM_01C_PARAMS_DEFAULT { .tsp_pin = SM_PWM_01C_TSP_PIN, \ + .tlp_pin = SM_PWM_01C_TLP_PIN } +#endif +/**@}*/ + +/** + * @brief Configure SM_PWM_01C + */ +static const sm_pwm_01c_params_t sm_pwm_01c_params[] = +{ +#ifdef SM_PWM_01C_PARAMS_BOARD + SM_PWM_01C_PARAMS_BOARD, +#else + SM_PWM_01C_PARAMS_DEFAULT +#endif +}; + +/** + * @brief Additional meta information to keep in the SAUL registry + */ +static const saul_reg_info_t sm_pwm_01c_saul_info[] = +{ + SM_PWM_01C_SAUL_INFO +}; + +/** + * @brief The number of configured sensors + */ +#define SM_PWM_01C_NUMOF ARRAY_SIZE(sm_pwm_01c_params) + +#ifdef __cplusplus +} +#endif + +#endif /* SM_PWM_01C_PARAMS_H */ +/** @} */ diff --git a/drivers/sm_pwm_01c/sm_pwm_01c.c b/drivers/sm_pwm_01c/sm_pwm_01c.c new file mode 100644 index 000000000000..d52feb7e71b0 --- /dev/null +++ b/drivers/sm_pwm_01c/sm_pwm_01c.c @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2021 Inria + * + * 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_sm_pwm_01c + * @{ + * @file + * @brief Implementation of SM_PWM_01C dust sensor + * + * @author Francisco Molina + * @} + */ + +#include +#include + +#include "log.h" +#include "ztimer.h" + +#include "periph/gpio.h" + +#include "sm_pwm_01c.h" +#include "sm_pwm_01c_params.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/* Scaling value to get 1/100 of a % resolution for lpo values */ +#define LPO_SCALING (100) + +/* Circular average for moving average calculation, this is always + called in irq context */ +#ifdef MODULE_SM_PWM_01C_MA +static void _circ_buf_push(circ_buf_t *buf, uint16_t data) +{ + buf->buf[buf->head] = data; + buf->head = (buf->head + 1) % (SM_PWM_01C_BUFFER_LEN); +} + +static uint16_t _circ_buf_avg(circ_buf_t *buf) +{ + uint32_t sum = 0; + + for (size_t i = 0; i < SM_PWM_01C_BUFFER_LEN; i++) { + sum += buf->buf[i]; + } + return (uint16_t)(sum / SM_PWM_01C_BUFFER_LEN); +} +#endif + +/* Interval approximation of theoretical Dust Concentration / LPO % curve + https://www.sgbotic.com/products/datasheets/sensors/app-SM-PWM-01C.pdf */ +static uint16_t _lpo_to_dust_cons(uint16_t lpo) +{ + if (lpo <= (2 * LPO_SCALING)) { + return (143 * lpo) / (2 * LPO_SCALING); + } + else if (lpo <= (4 * LPO_SCALING)) { + return (208 * lpo + 130) / (3 * LPO_SCALING); + } + else if (lpo <= (15 * LPO_SCALING)) { + return (1155 * lpo - 1572) / (10 * LPO_SCALING); + } + else { + return (2354 * lpo - 19560) / (10 * LPO_SCALING); + } +} + +static void _sample_timer_cb(void *arg) +{ + sm_pwm_01c_t *dev = (sm_pwm_01c_t *)arg; + + /* schedule next sample */ + ztimer_set(ZTIMER_USEC, &dev->_sampler, CONFIG_SM_PWM_01C_SAMPLE_TIME); + DEBUG("[sm_pwm_01c] tsp_lpo %" PRIu32 "\n", dev->_values.tsp_lpo); + DEBUG("[sm_pwm_01c] tlp_lpo %" PRIu32 "\n", dev->_values.tlp_lpo); + + /* calculate low Pulse Output Occupancy in (% * LPO_SCALING), + e.g. 1% -> 100 */ + uint16_t tsp_ratio = + (uint16_t)((uint64_t)(100 * LPO_SCALING * dev->_values.tsp_lpo) / + CONFIG_SM_PWM_01C_SAMPLE_TIME); + uint16_t tlp_ratio = + (uint16_t)((uint64_t)(100 * LPO_SCALING * dev->_values.tlp_lpo) / + CONFIG_SM_PWM_01C_SAMPLE_TIME); + DEBUG("[sm_pwm_01c] tsp_ratio %" PRIu16 "/%d %%\n", tsp_ratio, LPO_SCALING); + DEBUG("[sm_pwm_01c] tlp_ratio %" PRIu16 "/%d %%\n", tlp_ratio, LPO_SCALING); + + /* convert lpo to particle concentration */ + uint16_t tsp = _lpo_to_dust_cons(tsp_ratio); + uint16_t tlp = _lpo_to_dust_cons(tlp_ratio); + DEBUG("[sm_pwm_01c] new sample tsp conc: %" PRIu16 " ug/m3\n", tsp); + DEBUG("[sm_pwm_01c] new sample tlp conc: %" PRIu16 " ug/m3\n", tlp); + + /* update concentration values*/ +#ifdef MODULE_SM_PWM_01C_MA + _circ_buf_push(&dev->_values.tsp_circ_buf, tsp); + _circ_buf_push(&dev->_values.tlp_circ_buf, tlp); +#else + dev->_values.data.mc_pm_10 = + (uint16_t)((tlp + (uint32_t)(CONFIG_SM_PWM_01C_EXP_WEIGHT - 1) * + dev->_values.data.mc_pm_10) / CONFIG_SM_PWM_01C_EXP_WEIGHT); + dev->_values.data.mc_pm_2p5 = + (uint16_t)((tsp + (uint32_t)(CONFIG_SM_PWM_01C_EXP_WEIGHT - 1) * + dev->_values.data.mc_pm_2p5) / CONFIG_SM_PWM_01C_EXP_WEIGHT); +#endif + + /* reset lpo */ + dev->_values.tlp_lpo = 0; + dev->_values.tsp_lpo = 0; +} + +static void _tsp_pin_cb(void *arg) +{ + sm_pwm_01c_t *dev = (sm_pwm_01c_t *)arg; + uint32_t now = ztimer_now(ZTIMER_USEC); + + if (gpio_read(dev->params.tsp_pin) == 0) { + dev->_values.tsp_start_time = now; + } + else { + dev->_values.tsp_lpo += (now - dev->_values.tsp_start_time); + } +} + +static void _tlp_pin_cb(void *arg) +{ + sm_pwm_01c_t *dev = (sm_pwm_01c_t *)arg; + uint32_t now = ztimer_now(ZTIMER_USEC); + + if (gpio_read(dev->params.tlp_pin) == 0) { + dev->_values.tlp_start_time = now; + } + else { + dev->_values.tlp_lpo += (now - dev->_values.tlp_start_time); + } +} + +int sm_pwm_01c_init(sm_pwm_01c_t *dev, const sm_pwm_01c_params_t *params) +{ + dev->params = *params; + + /* set up irq */ + if (gpio_init_int(dev->params.tsp_pin, GPIO_IN_PU, GPIO_BOTH, _tsp_pin_cb, + dev) < 0) { + DEBUG("[sm_pwm_01c] init_int of tsp_pin failed [ERROR]\n"); + return -EIO; + } + if (gpio_init_int(dev->params.tlp_pin, GPIO_IN_PU, GPIO_BOTH, _tlp_pin_cb, + dev) < 0) { + DEBUG("[sm_pwm_01c] init_int of tlp_pin failed [ERROR]\n"); + return -EIO; + } + + /* setup timer */ + dev->_sampler.callback = _sample_timer_cb; + dev->_sampler.arg = dev; + +#ifdef MODULE_SM_PWM_01C_MA + memset(&dev->_values.tsp_circ_buf, 0, sizeof(circ_buf_t)); + memset(&dev->_values.tlp_circ_buf, 0, sizeof(circ_buf_t)); +#endif + + return 0; +} + +void sm_pwm_01c_start(sm_pwm_01c_t *dev) +{ + assert(dev); + /* reset old values */ + memset((void *)&dev->_values, 0, sizeof(sm_pwm_01c_values_t)); + /* enable irq and set timer */ + ztimer_set(ZTIMER_USEC, &dev->_sampler, CONFIG_SM_PWM_01C_SAMPLE_TIME); + gpio_irq_enable(dev->params.tsp_pin); + gpio_irq_enable(dev->params.tlp_pin); + DEBUG("[sm_pwm_01c] started average measurements\n"); +} + +void sm_pwm_01c_stop(sm_pwm_01c_t *dev) +{ + assert(dev); + /* disable irq and remove timer */ + ztimer_remove(ZTIMER_USEC, &dev->_sampler); + gpio_irq_disable(dev->params.tsp_pin); + gpio_irq_disable(dev->params.tlp_pin); + DEBUG("[sm_pwm_01c] stopped average measurements\n"); +} + +void sm_pwm_01c_read_data(sm_pwm_01c_t *dev, sm_pwm_01c_data_t *data) +{ + assert(dev); + unsigned int state = irq_disable(); +#ifdef MODULE_SM_PWM_01C_MA + data->mc_pm_10 = _circ_buf_avg(&dev->_values.tlp_circ_buf); + data->mc_pm_2p5 = _circ_buf_avg(&dev->_values.tsp_circ_buf); +#else + data->mc_pm_10 = dev->_values.data.mc_pm_10; + data->mc_pm_2p5 = dev->_values.data.mc_pm_2p5; +#endif + irq_restore(state); +} diff --git a/drivers/sm_pwm_01c/sm_pwm_01c_saul.c b/drivers/sm_pwm_01c/sm_pwm_01c_saul.c new file mode 100644 index 000000000000..b959a2273f68 --- /dev/null +++ b/drivers/sm_pwm_01c/sm_pwm_01c_saul.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 Inria + * + * 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_sm_pwm_01c + * @{ + * @file + * @brief SAUL adaption of the SM_PWM_01C dust sensor driver + * + * @author Francisco Molina + * @} + */ + +#include +#include +#include + +#include "phydat.h" +#include "saul.h" +#include "sm_pwm_01c.h" +#include "sm_pwm_01c_params.h" + +static int read_mc_pm_2p5(const void *_dev, phydat_t *data) +{ + sm_pwm_01c_data_t values; + sm_pwm_01c_t *dev = (sm_pwm_01c_t *)_dev; + + sm_pwm_01c_read_data(dev, &values); + data->unit = UNIT_GPM3; + data->scale = -6; + uint32_t value = values.mc_pm_2p5; + phydat_fit(data, (int32_t *)&value, 1); + return 1; +} + +static int read_mc_pm_10(const void *_dev, phydat_t *data) +{ + sm_pwm_01c_data_t values; + sm_pwm_01c_t *dev = (sm_pwm_01c_t *)_dev; + + sm_pwm_01c_read_data(dev, &values); + data->unit = UNIT_GPM3; + data->scale = -6; + uint32_t value = values.mc_pm_10; + phydat_fit(data, (int32_t *)&value, 1); + return 1; +} + +const saul_driver_t sm_pwm_01c_saul_driver_mc_pm_10 = { + .read = read_mc_pm_10, + .write = saul_notsup, + .type = SAUL_SENSE_PM, + .subtype = SAUL_SENSE_PM_10, +}; + +const saul_driver_t sm_pwm_01c_saul_driver_mc_pm_2p5 = { + .read = read_mc_pm_2p5, + .write = saul_notsup, + .type = SAUL_SENSE_PM, + .subtype = SAUL_SENSE_PM_2p5, +};