drivers: sensor: SGP40 Added support
This adds support for Sensirion's SGP40 multipixel gas sensor. Signed-off-by: Leonard Pollak <leonardp@tr-host.de>
This commit is contained in:
parent
ee7773fb46
commit
0d7cb32c58
10 changed files with 438 additions and 0 deletions
|
@ -70,6 +70,7 @@ add_subdirectory_ifdef(CONFIG_QDEC_NRFX qdec_nrfx)
|
|||
add_subdirectory_ifdef(CONFIG_QDEC_SAM qdec_sam)
|
||||
add_subdirectory_ifdef(CONFIG_TEMP_NRF5 nrf5)
|
||||
add_subdirectory_ifdef(CONFIG_SBS_GAUGE sbs_gauge)
|
||||
add_subdirectory_ifdef(CONFIG_SGP40 sgp40)
|
||||
add_subdirectory_ifdef(CONFIG_SHTCX shtcx)
|
||||
add_subdirectory_ifdef(CONFIG_SHT3XD sht3xd)
|
||||
add_subdirectory_ifdef(CONFIG_SI7006 si7006)
|
||||
|
|
|
@ -180,6 +180,8 @@ source "drivers/sensor/qdec_sam/Kconfig"
|
|||
|
||||
source "drivers/sensor/sbs_gauge/Kconfig"
|
||||
|
||||
source "drivers/sensor/sgp40/Kconfig"
|
||||
|
||||
source "drivers/sensor/shtcx/Kconfig"
|
||||
|
||||
source "drivers/sensor/sht3xd/Kconfig"
|
||||
|
|
5
drivers/sensor/sgp40/CMakeLists.txt
Normal file
5
drivers/sensor/sgp40/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library()
|
||||
|
||||
zephyr_library_sources(sgp40.c)
|
10
drivers/sensor/sgp40/Kconfig
Normal file
10
drivers/sensor/sgp40/Kconfig
Normal file
|
@ -0,0 +1,10 @@
|
|||
# SGP40 multipixel gas sensor configuration options
|
||||
|
||||
# Copyright (c) 2021 Leonard Pollak
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config SGP40
|
||||
bool "SGP40 Multipixel Gas Sensor"
|
||||
depends on I2C
|
||||
help
|
||||
Enable driver for SGP40 Multipixel Gas Sensor.
|
306
drivers/sensor/sgp40/sgp40.c
Normal file
306
drivers/sensor/sgp40/sgp40.c
Normal file
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Leonard Pollak
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT sensirion_sgp40
|
||||
|
||||
#include <device.h>
|
||||
#include <drivers/i2c.h>
|
||||
#include <kernel.h>
|
||||
#include <drivers/sensor.h>
|
||||
#include <logging/log.h>
|
||||
#include <sys/byteorder.h>
|
||||
#include <sys/crc.h>
|
||||
|
||||
#include <drivers/sensor/sgp40.h>
|
||||
#include "sgp40.h"
|
||||
|
||||
LOG_MODULE_REGISTER(SGP40, CONFIG_SENSOR_LOG_LEVEL);
|
||||
|
||||
static uint8_t sgp40_compute_crc(uint16_t value)
|
||||
{
|
||||
uint8_t buf[2];
|
||||
|
||||
sys_put_be16(value, buf);
|
||||
|
||||
return crc8(buf, 2, SGP40_CRC_POLY, SGP40_CRC_INIT, false);
|
||||
}
|
||||
|
||||
static int sgp40_write_command(const struct device *dev, uint16_t cmd)
|
||||
{
|
||||
|
||||
const struct sgp40_config *cfg = dev->config;
|
||||
uint8_t tx_buf[2];
|
||||
|
||||
sys_put_be16(cmd, tx_buf);
|
||||
|
||||
return i2c_write(cfg->bus, tx_buf, sizeof(tx_buf),
|
||||
cfg->i2c_addr);
|
||||
}
|
||||
|
||||
static int sgp40_start_measurement(const struct device *dev)
|
||||
{
|
||||
const struct sgp40_config *cfg = dev->config;
|
||||
struct sgp40_data *data = dev->data;
|
||||
uint8_t tx_buf[8];
|
||||
|
||||
sys_put_be16(SGP40_CMD_MEASURE_RAW, tx_buf);
|
||||
sys_put_be24(sys_get_be24(data->rh_param), &tx_buf[2]);
|
||||
sys_put_be24(sys_get_be24(data->t_param), &tx_buf[5]);
|
||||
|
||||
return i2c_write(cfg->bus, tx_buf, sizeof(tx_buf),
|
||||
cfg->i2c_addr);
|
||||
}
|
||||
|
||||
static int sgp40_attr_set(const struct device *dev,
|
||||
enum sensor_channel chan,
|
||||
enum sensor_attribute attr,
|
||||
const struct sensor_value *val)
|
||||
{
|
||||
struct sgp40_data *data = dev->data;
|
||||
|
||||
/*
|
||||
* Temperature and RH conversion to ticks as explained in datasheet
|
||||
* in section "I2C commands"
|
||||
*/
|
||||
|
||||
switch ((enum sensor_attribute_sgp40)attr) {
|
||||
case SENSOR_ATTR_SGP40_TEMPERATURE:
|
||||
{
|
||||
int16_t t_ticks;
|
||||
int32_t tmp;
|
||||
|
||||
tmp = CLAMP(val->val1, SGP40_COMP_MIN_T, SGP40_COMP_MAX_T);
|
||||
t_ticks = ((tmp + 45U) * 0xFFFF / 175U) + 0.5;
|
||||
sys_put_be16(t_ticks, data->t_param);
|
||||
data->t_param[2] = sgp40_compute_crc(t_ticks);
|
||||
}
|
||||
break;
|
||||
case SENSOR_ATTR_SGP40_HUMIDITY:
|
||||
{
|
||||
int16_t rh_ticks;
|
||||
int32_t tmp;
|
||||
|
||||
tmp = CLAMP(val->val1, SGP40_COMP_MIN_RH, SGP40_COMP_MAX_RH);
|
||||
rh_ticks = (tmp * 0xFFFF / 100U) + 0.5;
|
||||
sys_put_be16(rh_ticks, data->rh_param);
|
||||
data->rh_param[2] = sgp40_compute_crc(rh_ticks);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sgp40_selftest(const struct device *dev)
|
||||
{
|
||||
const struct sgp40_config *cfg = dev->config;
|
||||
uint8_t rx_buf[3];
|
||||
uint16_t raw_sample;
|
||||
int rc;
|
||||
|
||||
rc = sgp40_write_command(dev, SGP40_CMD_MEASURE_TEST);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Failed to start selftest!");
|
||||
return rc;
|
||||
}
|
||||
|
||||
k_sleep(K_MSEC(SGP40_TEST_WAIT_MS));
|
||||
|
||||
rc = i2c_read(cfg->bus, rx_buf, sizeof(rx_buf), cfg->i2c_addr);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Failed to read data sample.");
|
||||
return rc;
|
||||
}
|
||||
|
||||
raw_sample = sys_get_be16(rx_buf);
|
||||
if (sgp40_compute_crc(raw_sample) != rx_buf[2]) {
|
||||
LOG_ERR("Received invalid CRC from selftest.");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (raw_sample != SGP40_TEST_OK) {
|
||||
LOG_ERR("Selftest failed.");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sgp40_sample_fetch(const struct device *dev,
|
||||
enum sensor_channel chan)
|
||||
{
|
||||
struct sgp40_data *data = dev->data;
|
||||
const struct sgp40_config *cfg = dev->config;
|
||||
uint8_t rx_buf[3];
|
||||
uint16_t raw_sample;
|
||||
int rc;
|
||||
|
||||
if (chan != SENSOR_CHAN_GAS_RES && chan != SENSOR_CHAN_ALL) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
rc = sgp40_start_measurement(dev);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Failed to start measurement.");
|
||||
return rc;
|
||||
}
|
||||
|
||||
k_sleep(K_MSEC(SGP40_MEASURE_WAIT_MS));
|
||||
|
||||
rc = i2c_read(cfg->bus, rx_buf, sizeof(rx_buf), cfg->i2c_addr);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Failed to read data sample.");
|
||||
return rc;
|
||||
}
|
||||
|
||||
raw_sample = sys_get_be16(rx_buf);
|
||||
if (sgp40_compute_crc(raw_sample) != rx_buf[2]) {
|
||||
LOG_ERR("Invalid CRC8 for data sample.");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
data->raw_sample = raw_sample;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sgp40_channel_get(const struct device *dev,
|
||||
enum sensor_channel chan,
|
||||
struct sensor_value *val)
|
||||
{
|
||||
const struct sgp40_data *data = dev->data;
|
||||
|
||||
if (chan != SENSOR_CHAN_GAS_RES) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
val->val1 = data->raw_sample;
|
||||
val->val2 = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
static int sgp40_set_power_state(const struct device *dev,
|
||||
enum pm_device_state power_state)
|
||||
{
|
||||
struct sgp40_data *data = dev->data;
|
||||
uint16_t cmd;
|
||||
int rc;
|
||||
|
||||
if (data->pm_state == power_state) {
|
||||
LOG_DBG("Device already in requested PM_STATE.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (power_state == PM_DEVICE_STATE_ACTIVE) {
|
||||
/* activate the hotplate by sending a measure command */
|
||||
cmd = SGP40_CMD_MEASURE_RAW;
|
||||
} else if (power_state == PM_DEVICE_STATE_SUSPEND) {
|
||||
cmd = SGP40_CMD_HEATER_OFF;
|
||||
} else {
|
||||
LOG_DBG("Power state not implemented.");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
rc = sgp40_write_command(dev, cmd);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Failed to set power state.");
|
||||
return rc;
|
||||
}
|
||||
|
||||
data->pm_state = power_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t sgp40_get_power_state(const struct device *dev,
|
||||
enum pm_device_state *state)
|
||||
{
|
||||
struct sgp40_data *data = dev->data;
|
||||
|
||||
*state = data->pm_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sgp40_pm_ctrl(const struct device *dev,
|
||||
uint32_t ctrl_command,
|
||||
enum pm_device_state *state)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (ctrl_command == PM_DEVICE_STATE_SET) {
|
||||
rc = sgp40_set_power_state(dev, *state);
|
||||
} else if (ctrl_command == PM_DEVICE_STATE_GET) {
|
||||
rc = sgp40_get_power_state(dev, state);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
|
||||
static int sgp40_init(const struct device *dev)
|
||||
{
|
||||
const struct sgp40_config *cfg = dev->config;
|
||||
struct sensor_value comp_data;
|
||||
|
||||
if (!device_is_ready(cfg->bus)) {
|
||||
LOG_ERR("Device not ready.");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (cfg->selftest) {
|
||||
int rc = sgp40_selftest(dev);
|
||||
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Selftest failed!");
|
||||
return rc;
|
||||
}
|
||||
LOG_DBG("Selftest succeded!");
|
||||
}
|
||||
|
||||
comp_data.val1 = SGP40_COMP_DEFAULT_T;
|
||||
sensor_attr_set(dev,
|
||||
SENSOR_CHAN_GAS_RES,
|
||||
(enum sensor_attribute) SENSOR_ATTR_SGP40_TEMPERATURE,
|
||||
&comp_data);
|
||||
comp_data.val1 = SGP40_COMP_DEFAULT_RH;
|
||||
sensor_attr_set(dev,
|
||||
SENSOR_CHAN_GAS_RES,
|
||||
(enum sensor_attribute) SENSOR_ATTR_SGP40_HUMIDITY,
|
||||
&comp_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct sensor_driver_api sgp40_api = {
|
||||
.sample_fetch = sgp40_sample_fetch,
|
||||
.channel_get = sgp40_channel_get,
|
||||
.attr_set = sgp40_attr_set,
|
||||
};
|
||||
|
||||
#define SGP40_INIT(n) \
|
||||
static struct sgp40_data sgp40_data_##n; \
|
||||
\
|
||||
static const struct sgp40_config sgp40_config_##n = { \
|
||||
.bus = DEVICE_DT_GET(DT_INST_BUS(n)), \
|
||||
.i2c_addr = DT_INST_REG_ADDR(n), \
|
||||
.selftest = DT_INST_PROP(n, enable_selftest), \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(n, \
|
||||
sgp40_init, \
|
||||
sgp40_pm_ctrl,\
|
||||
&sgp40_data_##n, \
|
||||
&sgp40_config_##n, \
|
||||
POST_KERNEL, \
|
||||
CONFIG_SENSOR_INIT_PRIORITY, \
|
||||
&sgp40_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(SGP40_INIT)
|
56
drivers/sensor/sgp40/sgp40.h
Normal file
56
drivers/sensor/sgp40/sgp40.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Leonard Pollak
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_DRIVERS_SENSOR_SGP40_SGP40_H_
|
||||
#define ZEPHYR_DRIVERS_SENSOR_SGP40_SGP40_H_
|
||||
|
||||
#include <device.h>
|
||||
|
||||
#define SGP40_CMD_MEASURE_RAW 0x260F
|
||||
#define SGP40_CMD_MEASURE_TEST 0x280E
|
||||
#define SGP40_CMD_HEATER_OFF 0x3615
|
||||
|
||||
#define SGP40_TEST_OK 0xD400
|
||||
#define SGP40_TEST_FAIL 0x4B00
|
||||
|
||||
#define SGP40_RESET_WAIT_MS 10
|
||||
#define SGP40_MEASURE_WAIT_MS 30
|
||||
#define SGP40_TEST_WAIT_MS 250
|
||||
|
||||
/*
|
||||
* CRC parameters were taken from the
|
||||
* "Checksum Calculation" section of the datasheet.
|
||||
*/
|
||||
#define SGP40_CRC_POLY 0x31
|
||||
#define SGP40_CRC_INIT 0xFF
|
||||
|
||||
/*
|
||||
* Value range of compensation data parameters
|
||||
*/
|
||||
#define SGP40_COMP_MIN_RH 0
|
||||
#define SGP40_COMP_MAX_RH 100
|
||||
#define SGP40_COMP_MIN_T -45
|
||||
#define SGP40_COMP_MAX_T 130
|
||||
#define SGP40_COMP_DEFAULT_T 25
|
||||
#define SGP40_COMP_DEFAULT_RH 50
|
||||
|
||||
struct sgp40_config {
|
||||
const struct device *bus;
|
||||
uint8_t i2c_addr;
|
||||
bool selftest;
|
||||
};
|
||||
|
||||
struct sgp40_data {
|
||||
uint16_t raw_sample;
|
||||
int8_t rh_param[3];
|
||||
int8_t t_param[3];
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
enum pm_device_state pm_state;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* ZEPHYR_DRIVERS_SENSOR_SGP40_SGP40_H_ */
|
17
dts/bindings/sensor/sensirion,sgp40.yaml
Normal file
17
dts/bindings/sensor/sensirion,sgp40.yaml
Normal file
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# Copyright (c) 2021, Leonard Pollak
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: Sensirion SGP40 Multipixel Gas Sensor
|
||||
|
||||
compatible: "sensirion,sgp40"
|
||||
|
||||
include: i2c-device.yaml
|
||||
|
||||
properties:
|
||||
enable-selftest:
|
||||
type: boolean
|
||||
description: |
|
||||
Enabling this will run a selftest when the driver initializes.
|
||||
The selftest takes ~250ms.
|
33
include/drivers/sensor/sgp40.h
Normal file
33
include/drivers/sensor/sgp40.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Leonard Pollak
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Extended public API for Sensirion's SGP40 gas sensor
|
||||
*
|
||||
* This exposes two attributes for the SGP40 which can be used for
|
||||
* setting the on-chip Temperature and Humidity compensation parameters.
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_SGP40_H_
|
||||
#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_SGP40_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum sensor_attribute_sgp40 {
|
||||
/* Temperature in degC; only the integer part is used */
|
||||
SENSOR_ATTR_SGP40_TEMPERATURE = SENSOR_ATTR_PRIV_START,
|
||||
/* Relative Humidity in %; only the integer part is used */
|
||||
SENSOR_ATTR_SGP40_HUMIDITY
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_DRIVERS_SENSOR_SGP40_H_ */
|
|
@ -230,6 +230,13 @@ test_i2c_sx9500: sx9500@22 {
|
|||
int-gpios = <&test_gpio 0 0>;
|
||||
};
|
||||
|
||||
test_i2c_sgp40: sgp40@59 {
|
||||
compatible = "sensirion,sgp40";
|
||||
label = "SGP40";
|
||||
reg = <0x59>;
|
||||
enable-selftest;
|
||||
};
|
||||
|
||||
test_i2c_sht3xd: sht3xd@23 {
|
||||
compatible = "sensirion,sht3xd";
|
||||
label = "SHT3XD";
|
||||
|
|
|
@ -73,6 +73,7 @@ CONFIG_MS5607=y
|
|||
CONFIG_MS5837=y
|
||||
CONFIG_OPT3001=y
|
||||
CONFIG_SBS_GAUGE=y
|
||||
CONFIG_SGP40=y
|
||||
CONFIG_SHT3XD=y
|
||||
CONFIG_SHTCX=y
|
||||
CONFIG_SI7006=y
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue