1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 04:52:59 +01:00

Merge pull request #16217 from fjmolinas/pr_driver_sm_pwm_01c

drivers/sm_pwm_01c: initial import
This commit is contained in:
Leandro Lanzieri 2021-04-08 09:04:38 +02:00 committed by GitHub
commit e7732d9a00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 918 additions and 38 deletions

View File

@ -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"

View File

@ -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 */
/** @} */

View File

@ -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
}
}

View File

@ -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();

View File

@ -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

View File

@ -0,0 +1,7 @@
SRC := sm_pwm_01c.c
ifneq (,$(filter saul,$(USEMODULE)))
SRC += sm_pwm_01c_saul.c
endif
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,5 @@
USEMODULE += ztimer_usec
DEFAULT_MODULE += sm_pwm_01c_ma
FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_gpio_irq

View File

@ -0,0 +1,4 @@
USEMODULE_INCLUDES_sm_pwm_01c := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_sm_pwm_01c)
PSEUDOMODULES += sm_pwm_01c_ma

View File

@ -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 <francois-xavier.molina@inria.fr>
*
*/
#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 */
/** @} */

View File

@ -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 <francois-xavier.molina@inria.fr>
* @}
*/
#include <assert.h>
#include <string.h>
#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);
}

View File

@ -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 <francois-xavier.molina@inria.fr>
* @}
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#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,
};

View File

@ -42,6 +42,7 @@ rsource "posix/Kconfig"
rsource "oneway-malloc/Kconfig"
rsource "phydat/Kconfig"
rsource "pm_layered/Kconfig"
rsource "progress_bar/Kconfig"
rsource "ps/Kconfig"
rsource "random/Kconfig"
rsource "saul_reg/Kconfig"

View File

@ -29,50 +29,50 @@ extern "C" {
/**
* @brief Progress bar maximum characters length
*/
#ifndef PROGRESS_BAR_LENGTH
#define PROGRESS_BAR_LENGTH (25U)
#ifndef CONFIG_PROGRESS_BAR_LENGTH
#define CONFIG_PROGRESS_BAR_LENGTH (25U)
#endif
/**
* @brief Progress bar character
*/
#ifndef PROGRESS_BAR_FULL_CHARACTER
#define PROGRESS_BAR_FULL_CHARACTER "█"
#ifndef CONFIG_PROGRESS_BAR_FULL_CHARACTER
#define CONFIG_PROGRESS_BAR_FULL_CHARACTER "█"
#endif
/**
* @brief Progress bar empty character
*/
#ifndef PROGRESS_BAR_EMPTY_CHARACTER
#define PROGRESS_BAR_EMPTY_CHARACTER " "
#ifndef CONFIG_PROGRESS_BAR_EMPTY_CHARACTER
#define CONFIG_PROGRESS_BAR_EMPTY_CHARACTER " "
#endif
/**
* @brief Character displayed on the left of the progress bar
*/
#ifndef PROGRESS_BAR_PREFIX_CHARACTER
#define PROGRESS_BAR_PREFIX_CHARACTER "|"
#ifndef CONFIG_PROGRESS_BAR_PREFIX_CHARACTER
#define CONFIG_PROGRESS_BAR_PREFIX_CHARACTER "|"
#endif
/**
* @brief Character displayed on the left of the progress bar
*/
#ifndef PROGRESS_BAR_SUFFIX_CHARACTER
#define PROGRESS_BAR_SUFFIX_CHARACTER "|"
#ifndef CONFIG_PROGRESS_BAR_SUFFIX_CHARACTER
#define CONFIG_PROGRESS_BAR_SUFFIX_CHARACTER "|"
#endif
/**
* @brief Progress bar prefix max length
*/
#ifndef PROGRESS_BAR_PREFIX_MAX_LENGTH
#define PROGRESS_BAR_PREFIX_MAX_LENGTH (32U)
#ifndef CONFIG_PROGRESS_BAR_PREFIX_MAX_LENGTH
#define CONFIG_PROGRESS_BAR_PREFIX_MAX_LENGTH (32U)
#endif
/**
* @brief Progress bar suffix max length
*/
#ifndef PROGRESS_BAR_SUFFIX_MAX_LENGTH
#define PROGRESS_BAR_SUFFIX_MAX_LENGTH (32U)
#ifndef CONFIG_PROGRESS_BAR_SUFFIX_MAX_LENGTH
#define CONFIG_PROGRESS_BAR_SUFFIX_MAX_LENGTH (32U)
#endif
/**
@ -82,9 +82,9 @@ typedef struct {
/** Current value of the progress bar. Must be between 0 and 100 (included) */
uint8_t value;
/** Prefix displayed on the left of the progress bar */
char prefix[PROGRESS_BAR_PREFIX_MAX_LENGTH];
char prefix[CONFIG_PROGRESS_BAR_PREFIX_MAX_LENGTH];
/** Suffix displayed on the right of the progress bar */
char suffix[PROGRESS_BAR_SUFFIX_MAX_LENGTH];
char suffix[CONFIG_PROGRESS_BAR_SUFFIX_MAX_LENGTH];
} progress_bar_t;
/**

58
sys/progress_bar/Kconfig Normal file
View File

@ -0,0 +1,58 @@
# 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.
#
config MODULE_PROGRESS_BAR
bool "A simple CLI progress bar"
depends on TEST_KCONFIG
menuconfig KCONFIG_USEMODULE_PROGRESS_BAR
bool "Configure progress bar module"
depends on USEMODULE_PROGRESS_BAR
help
Configure the progress bar module using Kconfig.
if KCONFIG_USEMODULE_PROGRESS_BAR
config PROGRESS_BAR_LENGTH
int "Progress bar length"
default 25
help
Progress bar maximum characters length
config PROGRESS_BAR_FULL_CHARACTER
string "Progress bar character"
default "█"
help
The character that will be printed when the progress bar fills up.
config PROGRESS_BAR_EMPTY_CHARACTER
string "Progress bar empty character"
default " "
help
The character that will be printed when the progress bar empties.
config PROGRESS_BAR_PREFIX_CHARACTER
string "Progress bar prefix"
default "|"
help
Character displayed on the left of the progress bar
config PROGRESS_BAR_SUFFIX_CHARACTER
string "Progress bar suffix"
default "|"
help
Character displayed on the left of the progress bar
config PROGRESS_BAR_PREFIX_MAX_LENGTH
int "Progress bar prefix max length"
default 32
config PROGRESS_BAR_SUFFIX_MAX_LENGTH
int "Progress bar suffix max length"
default 32
endif # KCONFIG_USEMODULE_PROGRESS_BAR

View File

@ -44,19 +44,19 @@ void progress_bar_print(char *prefix, char *suffix, uint8_t value)
printf("%s", prefix);
}
printf(PROGRESS_BAR_PREFIX_CHARACTER);
printf(CONFIG_PROGRESS_BAR_PREFIX_CHARACTER);
/* Fully reprint the progress bar */
for (unsigned i = 0; i < PROGRESS_BAR_LENGTH; ++i) {
if (100 * i < (uint16_t)(value * PROGRESS_BAR_LENGTH)) {
printf(PROGRESS_BAR_FULL_CHARACTER);
for (unsigned i = 0; i < CONFIG_PROGRESS_BAR_LENGTH; ++i) {
if (100 * i < (uint16_t)(value * CONFIG_PROGRESS_BAR_LENGTH)) {
printf(CONFIG_PROGRESS_BAR_FULL_CHARACTER);
}
else {
printf(PROGRESS_BAR_EMPTY_CHARACTER);
printf(CONFIG_PROGRESS_BAR_EMPTY_CHARACTER);
}
}
printf(PROGRESS_BAR_SUFFIX_CHARACTER);
printf(CONFIG_PROGRESS_BAR_SUFFIX_CHARACTER);
/* Display progress bar suffix if any */
if (suffix) {

View File

@ -0,0 +1,8 @@
BOARD ?= samr21-xpro
include ../Makefile.tests_common
USEMODULE += progress_bar
USEMODULE += sm_pwm_01c
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,3 @@
BOARD_INSUFFICIENT_MEMORY := \
nucleo-l011k4
#

View File

@ -0,0 +1,27 @@
# Test Application for the Amphenol SM_PWM_01C infrared dust sensor
## About
This is a simple test application for the SM_PWM_01C driver.
## Expected result
If everything works then you should see the progress bar vary if dust
concentration changes in the room, or manually block the infrared inputs
to force a change.
Overly exaggerated values (blocked infrared sensor) can be seen here:
```
# main(): This is RIOT! (Version: 2021.04-devel-1044-gfd36c-HEAD)
# sm_pwm_01c driver test application
# starting weighted average PM2.5 and PM10 measurements
#
# PM2.5 level: 1272 ug/m3|███████████ |
# PM10 level: 490 ug/m3|████ |
````
## Details
By default a moving average over a 30s window is returned ad the
read value. The progress bar is updated every 200ms.

View File

@ -0,0 +1,6 @@
# this file enables modules defined in Kconfig. Do not use this file for
# application configuration. This is only needed during migration.
CONFIG_MODULE_SM_PWM_01C=y
CONFIG_MODULE_ZTIMER=y
CONFIG_MODULE_ZTIMER_USEC=y
CONFIG_MODULE_PROGRESS_BAR=y

View File

@ -0,0 +1,61 @@
/*
* 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 tests
* @{
* @file
* @brief Test application for SM_PWM_01C driver
*
* @author Francisco Molina <francois-xavier.molina@inria.fr>
* @}
*/
#include <stdio.h>
#include "ztimer.h"
#include "sm_pwm_01c.h"
#include "sm_pwm_01c_params.h"
#include "progress_bar.h"
static progress_bar_t progress_bar_list[2];
int main(void)
{
sm_pwm_01c_t dev;
puts("sm_pwm_01c driver test application");
if (sm_pwm_01c_init(&dev, &sm_pwm_01c_params[0]) != 0) {
puts("init device [ERROR]");
return -1;
}
puts("starting weighted average PM2.5 and PM10 measurements\n");
sm_pwm_01c_start(&dev);
progress_bar_prepare_multi(2);
while (1) {
ztimer_sleep(ZTIMER_USEC, 200 * US_PER_MS);
sm_pwm_01c_data_t data;
sm_pwm_01c_read_data(&dev, &data);
sprintf(progress_bar_list[0].prefix,
"%s %4d ug/m3", "PM2.5 level:",
data.mc_pm_2p5);
sprintf(progress_bar_list[1].prefix, "%s %4d ug/m3", "PM10 level:",
data.mc_pm_10);
progress_bar_list[0].value = data.mc_pm_2p5 >
3000 ? 100 : (data.mc_pm_2p5 * 100) / 3000;
progress_bar_list[1].value = data.mc_pm_10 >
3000 ? 100 : (data.mc_pm_10 * 100) / 3000;
progress_bar_update_multi(progress_bar_list, 2);
}
return 0;
}

View File

@ -3,24 +3,24 @@ include ../Makefile.tests_common
USEMODULE += xtimer
USEMODULE += progress_bar
PROGRESS_BAR_LENGTH ?= 50
PROGRESS_BAR_FULL_CHARACTER ?= "█"
PROGRESS_BAR_EMPTY_CHARACTER ?= " "
CONFIG_PROGRESS_BAR_LENGTH ?= 50
CONFIG_PROGRESS_BAR_FULL_CHARACTER ?= "█"
CONFIG_PROGRESS_BAR_EMPTY_CHARACTER ?= " "
# Other nice progress bar characters:
#PROGRESS_BAR_FULL_CHARACTER ?= "◉"
#PROGRESS_BAR_EMPTY_CHARACTER ?= "◯"
#PROGRESS_BAR_FULL_CHARACTER ?= "▣"
#PROGRESS_BAR_EMPTY_CHARACTER ?= "▢"
#CONFIG_PROGRESS_BAR_FULL_CHARACTER ?= "◉"
#CONFIG_PROGRESS_BAR_EMPTY_CHARACTER ?= "◯"
#CONFIG_PROGRESS_BAR_FULL_CHARACTER ?= "▣"
#CONFIG_PROGRESS_BAR_EMPTY_CHARACTER ?= "▢"
CFLAGS += -DPROGRESS_BAR_FULL_CHARACTER=\"$(PROGRESS_BAR_FULL_CHARACTER)\"
CFLAGS += -DPROGRESS_BAR_EMPTY_CHARACTER=\"$(PROGRESS_BAR_EMPTY_CHARACTER)\"
CFLAGS += -DPROGRESS_BAR_LENGTH=$(PROGRESS_BAR_LENGTH)
CFLAGS += -DCONFIG_PROGRESS_BAR_FULL_CHARACTER=\"$(CONFIG_PROGRESS_BAR_FULL_CHARACTER)\"
CFLAGS += -DCONFIG_PROGRESS_BAR_EMPTY_CHARACTER=\"$(CONFIG_PROGRESS_BAR_EMPTY_CHARACTER)\"
CFLAGS += -DCONFIG_PROGRESS_BAR_LENGTH=$(CONFIG_PROGRESS_BAR_LENGTH)
include $(RIOTBASE)/Makefile.include
# Make custom progress bar characters available in Python test script via
# environment variables
export PROGRESS_BAR_FULL_CHARACTER
export PROGRESS_BAR_EMPTY_CHARACTER
export PROGRESS_BAR_LENGTH
export CONFIG_PROGRESS_BAR_FULL_CHARACTER
export CONFIG_PROGRESS_BAR_EMPTY_CHARACTER
export CONFIG_PROGRESS_BAR_LENGTH

View File

@ -12,9 +12,9 @@ from testrunner import run
TIMEOUT = 60
LENGTH = int(os.getenv('PROGRESS_BAR_LENGTH'))
FULL_CHARACTER = os.getenv('PROGRESS_BAR_FULL_CHARACTER')[1:-1]
EMPTY_CHARACTER = os.getenv('PROGRESS_BAR_EMPTY_CHARACTER')[1:-1]
LENGTH = int(os.getenv('CONFIG_PROGRESS_BAR_LENGTH'))
FULL_CHARACTER = os.getenv('CONFIG_PROGRESS_BAR_FULL_CHARACTER')[1:-1]
EMPTY_CHARACTER = os.getenv('CONFIG_PROGRESS_BAR_EMPTY_CHARACTER')[1:-1]
def testfunc(child):