Skip to content

Commit

Permalink
drivers/sm_pwm_01c: initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
fjmolinas committed Apr 7, 2021
1 parent 424192f commit 240c016
Show file tree
Hide file tree
Showing 11 changed files with 716 additions and 0 deletions.
1 change: 1 addition & 0 deletions drivers/Kconfig
Expand Up @@ -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"
Expand Down
207 changes: 207 additions & 0 deletions 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 <francois-xavier.molina@inria.fr>
*/

#ifndef SM_PWM_01C_H
#define SM_PWM_01C_H

#include <inttypes.h>

#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 */
/** @} */
76 changes: 76 additions & 0 deletions 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 <francois-xavier.molina@inria.fr>
* @}
*/

#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
}
}
4 changes: 4 additions & 0 deletions drivers/saul/init_devs/init.c
Expand Up @@ -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();
Expand Down
60 changes: 60 additions & 0 deletions 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
7 changes: 7 additions & 0 deletions 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
5 changes: 5 additions & 0 deletions 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
4 changes: 4 additions & 0 deletions 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

0 comments on commit 240c016

Please sign in to comment.