1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/drivers/sm_pwm_01c/sm_pwm_01c.c
2022-09-26 18:54:40 +02:00

206 lines
6.1 KiB
C

/*
* 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 "irq.h"
#include "log.h"
#include "periph/gpio.h"
#include "sm_pwm_01c.h"
#include "sm_pwm_01c_params.h"
#include "ztimer.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);
}