Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sensors: Add driver for Aosong AGS10 TVOC sensor #67081

Merged
merged 1 commit into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/sensor/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ add_subdirectory_ifdef(CONFIG_ADXL345 adxl345)
add_subdirectory_ifdef(CONFIG_ADXL362 adxl362)
add_subdirectory_ifdef(CONFIG_ADXL367 adxl367)
add_subdirectory_ifdef(CONFIG_ADXL372 adxl372)
add_subdirectory_ifdef(CONFIG_AGS10 ags10)
add_subdirectory_ifdef(CONFIG_AK8975 ak8975)
add_subdirectory_ifdef(CONFIG_AKM09918C akm09918c)
add_subdirectory_ifdef(CONFIG_AMD_SB_TSI amd_sb_tsi)
Expand Down
1 change: 1 addition & 0 deletions drivers/sensor/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ source "drivers/sensor/adxl345/Kconfig"
source "drivers/sensor/adxl362/Kconfig"
source "drivers/sensor/adxl367/Kconfig"
source "drivers/sensor/adxl372/Kconfig"
source "drivers/sensor/ags10/Kconfig"
source "drivers/sensor/ak8975/Kconfig"
source "drivers/sensor/akm09918c/Kconfig"
source "drivers/sensor/amd_sb_tsi/Kconfig"
Expand Down
5 changes: 5 additions & 0 deletions drivers/sensor/ags10/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0


zephyr_library()
zephyr_library_sources(ags10.c)
12 changes: 12 additions & 0 deletions drivers/sensor/ags10/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright (c) 2023 Balthazar Deliers
# SPDX-License-Identifier: Apache-2.0

# AOSONG AGS10 TVOC sensor driver options.

config AGS10
bool "AOSONG AGS10 TVOC sensor"
default y
depends on DT_HAS_AOSONG_AGS10_ENABLED
select I2C
help
Enable AOSONG AGS10 TVOC sensor driver.
134 changes: 134 additions & 0 deletions drivers/sensor/ags10/ags10.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright (c) 2023 Balthazar Deliers
*
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT aosong_ags10

#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/crc.h>

#include "ags10.h"

LOG_MODULE_REGISTER(AGS10, CONFIG_SENSOR_LOG_LEVEL);

#define AGS10_MAX_PAYLOAD_SIZE 5U /* Payload will be max 4 bytes + CRC (datasheet 3.1) */

static int ags10_read(const struct device *dev, uint8_t cmd, uint8_t *data, uint8_t rx_bytes)
{
if (rx_bytes > AGS10_MAX_PAYLOAD_SIZE) {
return -EINVAL;
}

const struct ags10_config *conf = dev->config;

uint8_t recv_buf[AGS10_MAX_PAYLOAD_SIZE] = {0};
int ret = i2c_write_read_dt(&conf->bus, &cmd, sizeof(cmd), &recv_buf, rx_bytes);

if (ret < 0) {
return ret;
}

memcpy(data, recv_buf, rx_bytes);

return 0;
}

static int ags10_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
if (chan != SENSOR_CHAN_VOC && chan != SENSOR_CHAN_ALL) {
return -ENOTSUP;
}

struct ags10_data *data = dev->data;
int ret = -ENOTSUP;
uint8_t recv_buf[5] = {0};

ret = ags10_read(dev, AGS10_CMD_DATA_ACQUISITION, recv_buf, 5);

if (ret == 0) {
/* If CRC is valid and data is valid too */
if (crc8(&recv_buf[0], 4, 0x31, 0xFF, false) == recv_buf[4] &&
((recv_buf[0] & AGS10_MSK_STATUS) == AGS10_REG_STATUS_NRDY_READY)) {
data->status = recv_buf[0] & AGS10_MSK_STATUS;
data->tvoc_ppb = sys_get_be24(&recv_buf[1]);
return 0;
}

LOG_WRN("Bad CRC or data not ready");
ret = -EIO;
}

return ret;
}

static int ags10_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
struct ags10_data *data = dev->data;

if (chan == SENSOR_CHAN_VOC) {
val->val1 = data->tvoc_ppb;
} else {
return -ENOTSUP;
}

val->val2 = 0;

return 0;
}

static int ags10_init(const struct device *dev)
{
const struct ags10_config *conf = dev->config;
struct ags10_data *data = dev->data;
int ret;

if (!i2c_is_ready_dt(&conf->bus)) {
LOG_ERR("Device not ready");
return -ENODEV;
}

/* Set initial data values */
data->tvoc_ppb = 0;
data->status = 0xFF;
data->version = 0;

/* Read firmware version and check CRC */
uint8_t recv_buf[5] = {0};

ret = ags10_read(dev, AGS10_CMD_READ_VERSION, recv_buf, 5);

/* Bytes 0 to 2 are reserved, byte 3 is version, byte 4 is CRC */
if (ret == 0 && crc8(&recv_buf[0], 4, 0x31, 0xFF, false) == recv_buf[4]) {
data->version = recv_buf[3];
LOG_DBG("Sensor detected");
} else if (ret != 0) {
LOG_ERR("No reply from sensor");
ret = -ENODEV;
} else {
LOG_WRN("Bad CRC");
ret = -EIO;
}

return ret;
}

static const struct sensor_driver_api ags10_api = {.sample_fetch = ags10_sample_fetch,
.channel_get = ags10_channel_get};

#define AGS10_INIT(n) \
static struct ags10_data ags10_data_##n; \
\
static const struct ags10_config ags10_config_##n = { \
.bus = I2C_DT_SPEC_INST_GET(n), \
}; \
\
SENSOR_DEVICE_DT_INST_DEFINE(n, ags10_init, NULL, &ags10_data_##n, &ags10_config_##n, \
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &ags10_api);

DT_INST_FOREACH_STATUS_OKAY(AGS10_INIT)
46 changes: 46 additions & 0 deletions drivers/sensor/ags10/ags10.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 Balthazar Deliers
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_AGS10_H_
#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_AGS10_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>

#define AGS10_CMD_DATA_ACQUISITION 0x00
#define AGS10_CMD_ZERO_POINT_CALIBRATION 0x01
#define AGS10_CMD_READ_VERSION 0x11
#define AGS10_CMD_READ_RESISTANCE 0x20
#define AGS10_CMD_MODIFY_SLAVE_ADDRESS 0x21

#define AGS10_REG_ZERO_POINT_CALIBRATION_RESET 0xFFFF /* Reset to the factory value */
#define AGS10_REG_ZERO_POINT_CALIBRATION_SET 0x0000 /* Set sensor resistance to zero-point */
#define AGS10_REG_STATUS_NRDY_READY 0x00 /* Device is ready */
#define AGS10_REG_STATUS_CH_PPB 0x00 /* Unit is PPB */

#define AGS10_MSK_STATUS 0x0F
#define AGS10_MSK_STATUS_NRDY 0x01
#define AGS10_MSK_STATUS_CH 0x0E

struct ags10_config {
struct i2c_dt_spec bus;
};

struct ags10_data {
uint32_t tvoc_ppb;
uint8_t status;
uint32_t version;
};

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_DRIVERS_SENSOR_AGS10_H_ */
10 changes: 10 additions & 0 deletions dts/bindings/sensor/aosong,ags10.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) 2023 Balthazar Deliers
# SPDX-License-Identifier: Apache-2.0

description: |
AOSONG AGS10 a high-performance TVOC Sensor With I2C Interface.
See: http://www.aosong.com/en/products-86.html

compatible: "aosong,ags10"

include: [sensor-device.yaml, i2c-device.yaml]
5 changes: 5 additions & 0 deletions tests/drivers/build_all/sensor/i2c.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -884,3 +884,8 @@ test_i2c_bma4xx: bma4xx@7d {
compatible = "bosch,bma4xx";
reg = <0x7d>;
};

test_i2c_ags10: ags10@7e {
compatible = "aosong,ags10";
reg = <0x7e>;
};