/* * 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 "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); }