From 26f7b9c1ea58708376f891a612c78a42a5223a15 Mon Sep 17 00:00:00 2001 From: Yong Cong Sin Date: Sat, 30 Oct 2021 01:15:56 +0800 Subject: [PATCH] drivers: sensor: Add MH-Z19B CO2 sensor driver Add MH-Z19B CO2 sensor driver. Signed-off-by: Yong Cong Sin Co-Authored-By: Azamlukman --- drivers/sensor/CMakeLists.txt | 1 + drivers/sensor/Kconfig | 2 + drivers/sensor/mhz19b/CMakeLists.txt | 5 + drivers/sensor/mhz19b/Kconfig | 8 + drivers/sensor/mhz19b/mhz19b.c | 347 +++++++++++++++++++++++++ drivers/sensor/mhz19b/mhz19b.h | 90 +++++++ dts/bindings/sensor/winsen,mhz19b.yaml | 24 ++ dts/bindings/vendor-prefixes.txt | 1 + include/drivers/sensor/mhz19b.h | 33 +++ 9 files changed, 511 insertions(+) create mode 100644 drivers/sensor/mhz19b/CMakeLists.txt create mode 100644 drivers/sensor/mhz19b/Kconfig create mode 100644 drivers/sensor/mhz19b/mhz19b.c create mode 100644 drivers/sensor/mhz19b/mhz19b.h create mode 100644 dts/bindings/sensor/winsen,mhz19b.yaml create mode 100644 include/drivers/sensor/mhz19b.h diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index 5228f684f53..3244ac0d001 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -64,6 +64,7 @@ add_subdirectory_ifdef(CONFIG_MAX30101 max30101) add_subdirectory_ifdef(CONFIG_MAX44009 max44009) add_subdirectory_ifdef(CONFIG_MAX6675 max6675) add_subdirectory_ifdef(CONFIG_MCP9808 mcp9808) +add_subdirectory_ifdef(CONFIG_MHZ19B mhz19b) add_subdirectory_ifdef(CONFIG_MPR mpr) add_subdirectory_ifdef(CONFIG_MPU6050 mpu6050) add_subdirectory_ifdef(CONFIG_MS5607 ms5607) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index 2f60b3c4354..6681449292f 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -168,6 +168,8 @@ source "drivers/sensor/mchp_tach_xec/Kconfig" source "drivers/sensor/mcp9808/Kconfig" +source "drivers/sensor/mhz19b/Kconfig" + source "drivers/sensor/mpr/Kconfig" source "drivers/sensor/mpu6050/Kconfig" diff --git a/drivers/sensor/mhz19b/CMakeLists.txt b/drivers/sensor/mhz19b/CMakeLists.txt new file mode 100644 index 00000000000..10edd7e597b --- /dev/null +++ b/drivers/sensor/mhz19b/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(mhz19b.c) diff --git a/drivers/sensor/mhz19b/Kconfig b/drivers/sensor/mhz19b/Kconfig new file mode 100644 index 00000000000..cb4303441b1 --- /dev/null +++ b/drivers/sensor/mhz19b/Kconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2021 G-Technologies Sdn. Bhd. +# SPDX-License-Identifier: Apache-2.0 + +config MHZ19B + bool "Winsen CO2 sensor" + depends on UART_INTERRUPT_DRIVEN + help + Enable driver for the MHZ19B CO2 Sensor. diff --git a/drivers/sensor/mhz19b/mhz19b.c b/drivers/sensor/mhz19b/mhz19b.c new file mode 100644 index 00000000000..cdad2878431 --- /dev/null +++ b/drivers/sensor/mhz19b/mhz19b.c @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2021 G-Technologies Sdn. Bhd. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Datasheet: + * https://www.winsen-sensor.com/sensors/co2-sensor/mh-z19b.html + */ + +#define DT_DRV_COMPAT winsen_mhz19b + +#include +#include +#include + +#include +#include "mhz19b.h" + +LOG_MODULE_REGISTER(mhz19b, CONFIG_SENSOR_LOG_LEVEL); + +/* Table of supported MH-Z19B commands with precomputed checksum */ +static const uint8_t mhz19b_cmds[MHZ19B_CMD_IDX_MAX][MHZ19B_BUF_LEN] = { + [MHZ19B_CMD_IDX_GET_CO2] = { + MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_GET_CO2, MHZ19B_NULL_COUNT(5), 0x79 + }, + [MHZ19B_CMD_IDX_GET_RANGE] = { + MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_GET_RANGE, MHZ19B_NULL_COUNT(5), 0x64 + }, + [MHZ19B_CMD_IDX_GET_ABC] = { + MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_GET_ABC, MHZ19B_NULL_COUNT(5), 0x82 + }, + [MHZ19B_CMD_IDX_SET_ABC_ON] = { + MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_ABC, MHZ19B_ABC_ON, + MHZ19B_NULL_COUNT(4), 0xE6 + }, + [MHZ19B_CMD_IDX_SET_ABC_OFF] = { + MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_ABC, MHZ19B_ABC_OFF, + MHZ19B_NULL_COUNT(4), 0x86 + }, + [MHZ19B_CMD_IDX_SET_RANGE_2000] = { + MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_RANGE, MHZ19B_NULL_COUNT(3), + MHZ19B_RANGE_2000, 0x8F + }, + [MHZ19B_CMD_IDX_SET_RANGE_5000] = { + MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_RANGE, MHZ19B_NULL_COUNT(3), + MHZ19B_RANGE_5000, 0xCB + }, + [MHZ19B_CMD_IDX_SET_RANGE_10000] = { + MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_RANGE, MHZ19B_NULL_COUNT(3), + MHZ19B_RANGE_10000, 0x2F + }, +}; + +static void mhz19b_uart_flush(const struct device *uart_dev) +{ + uint8_t c; + + while (uart_fifo_read(uart_dev, &c, 1) > 0) { + continue; + } +} + +static uint8_t mhz19b_checksum(const uint8_t *data) +{ + uint8_t cs = 0; + + for (uint8_t i = 1; i < MHZ19B_BUF_LEN - 1; i++) { + cs += data[i]; + } + + return 0xff - cs + 1; +} + +static int mhz19b_send_cmd(const struct device *dev, enum mhz19b_cmd_idx cmd_idx, bool has_rsp) +{ + struct mhz19b_data *data = dev->data; + const struct mhz19b_cfg *cfg = dev->config; + int ret; + + /* Make sure last command has been transferred */ + ret = k_sem_take(&data->tx_sem, MHZ19B_WAIT); + if (ret) { + return ret; + } + + data->cmd_idx = cmd_idx; + data->has_rsp = has_rsp; + k_sem_reset(&data->rx_sem); + + uart_irq_tx_enable(cfg->uart_dev); + + if (has_rsp) { + uart_irq_rx_enable(cfg->uart_dev); + ret = k_sem_take(&data->rx_sem, MHZ19B_WAIT); + } + + return ret; +} + +static inline int mhz19b_send_config(const struct device *dev, enum mhz19b_cmd_idx cmd_idx) +{ + struct mhz19b_data *data = dev->data; + int ret; + + ret = mhz19b_send_cmd(dev, cmd_idx, true); + if (ret < 0) { + return ret; + } + + if (data->rd_data[MHZ19B_RX_CMD_IDX] != mhz19b_cmds[data->cmd_idx][MHZ19B_TX_CMD_IDX]) { + return -EINVAL; + } + + return 0; +} + +static inline int mhz19b_poll_data(const struct device *dev, enum mhz19b_cmd_idx cmd_idx) +{ + struct mhz19b_data *data = dev->data; + uint8_t checksum; + int ret; + + ret = mhz19b_send_cmd(dev, cmd_idx, true); + if (ret < 0) { + return ret; + } + + checksum = mhz19b_checksum(data->rd_data); + if (checksum != data->rd_data[MHZ19B_CHECKSUM_IDX]) { + LOG_DBG("Checksum mismatch: 0x%x != 0x%x", checksum, + data->rd_data[MHZ19B_CHECKSUM_IDX]); + return -EBADMSG; + } + + switch (cmd_idx) { + case MHZ19B_CMD_IDX_GET_CO2: + data->data = sys_get_be16(&data->rd_data[2]); + break; + case MHZ19B_CMD_IDX_GET_RANGE: + data->data = sys_get_be16(&data->rd_data[4]); + break; + case MHZ19B_CMD_IDX_GET_ABC: + data->data = data->rd_data[7]; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mhz19b_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct mhz19b_data *data = dev->data; + + if (chan != SENSOR_CHAN_CO2) { + return -ENOTSUP; + } + + val->val1 = (int32_t)data->data; + val->val2 = 0; + + return 0; +} + +static int mhz19b_attr_full_scale_cfg(const struct device *dev, int range) +{ + switch (range) { + case 2000: + LOG_DBG("Configure range to %d", range); + return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_RANGE_2000); + case 5000: + LOG_DBG("Configure range to %d", range); + return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_RANGE_5000); + case 10000: + LOG_DBG("Configure range to %d", range); + return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_RANGE_10000); + default: + return -ENOTSUP; + } +} + +static int mhz19b_attr_abc_cfg(const struct device *dev, bool on) +{ + if (on) { + LOG_DBG("%s ABC", "Enable"); + return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_ABC_ON); + } + + LOG_DBG("%s ABC", "Disable"); + return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_ABC_OFF); +} + +static int mhz19b_attr_set(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, const struct sensor_value *val) +{ + if (chan != SENSOR_CHAN_CO2) { + return -ENOTSUP; + } + + switch (attr) { + case SENSOR_ATTR_FULL_SCALE: + return mhz19b_attr_full_scale_cfg(dev, val->val1); + + case SENSOR_ATTR_MHZ19B_ABC: + return mhz19b_attr_abc_cfg(dev, val->val1); + + default: + return -ENOTSUP; + } +} + +static int mhz19b_attr_get(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, struct sensor_value *val) +{ + struct mhz19b_data *data = dev->data; + int ret; + + if (chan != SENSOR_CHAN_CO2) { + return -ENOTSUP; + } + + switch (attr) { + case SENSOR_ATTR_FULL_SCALE: + ret = mhz19b_poll_data(dev, MHZ19B_CMD_IDX_GET_RANGE); + break; + case SENSOR_ATTR_MHZ19B_ABC: + ret = mhz19b_poll_data(dev, MHZ19B_CMD_IDX_GET_ABC); + break; + default: + return -ENOTSUP; + } + + val->val1 = (int32_t)data->data; + val->val2 = 0; + + return ret; +} + +static int mhz19b_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + if (chan != SENSOR_CHAN_CO2) { + return -ENOTSUP; + } + + return mhz19b_poll_data(dev, MHZ19B_CMD_IDX_GET_CO2); +} + +static const struct sensor_driver_api mhz19b_api_funcs = { + .attr_set = mhz19b_attr_set, + .attr_get = mhz19b_attr_get, + .sample_fetch = mhz19b_sample_fetch, + .channel_get = mhz19b_channel_get, +}; + +static void mhz19b_uart_isr(const struct device *uart_dev, void *user_data) +{ + const struct device *dev = user_data; + struct mhz19b_data *data = dev->data; + + ARG_UNUSED(user_data); + + if (uart_dev == NULL) { + return; + } + + if (!uart_irq_update(uart_dev)) { + return; + } + + if (uart_irq_rx_ready(uart_dev)) { + data->xfer_bytes += uart_fifo_read(uart_dev, &data->rd_data[data->xfer_bytes], + MHZ19B_BUF_LEN - data->xfer_bytes); + + if (data->xfer_bytes == MHZ19B_BUF_LEN) { + data->xfer_bytes = 0; + uart_irq_rx_disable(uart_dev); + k_sem_give(&data->rx_sem); + if (data->has_rsp) { + k_sem_give(&data->tx_sem); + } + } + } + + if (uart_irq_tx_ready(uart_dev)) { + data->xfer_bytes += + uart_fifo_fill(uart_dev, &mhz19b_cmds[data->cmd_idx][data->xfer_bytes], + MHZ19B_BUF_LEN - data->xfer_bytes); + + if (data->xfer_bytes == MHZ19B_BUF_LEN) { + data->xfer_bytes = 0; + uart_irq_tx_disable(uart_dev); + if (!data->has_rsp) { + k_sem_give(&data->tx_sem); + } + } + } +} + +static int mhz19b_init(const struct device *dev) +{ + struct mhz19b_data *data = dev->data; + const struct mhz19b_cfg *cfg = dev->config; + int ret; + + uart_irq_rx_disable(cfg->uart_dev); + uart_irq_tx_disable(cfg->uart_dev); + + mhz19b_uart_flush(cfg->uart_dev); + + uart_irq_callback_user_data_set(cfg->uart_dev, cfg->cb, (void *)dev); + + k_sem_init(&data->rx_sem, 0, 1); + k_sem_init(&data->tx_sem, 1, 1); + + /* Configure default detection range */ + ret = mhz19b_attr_full_scale_cfg(dev, cfg->range); + if (ret != 0) { + LOG_ERR("Error setting default range %d", cfg->range); + return ret; + } + + /* Configure ABC logic */ + ret = mhz19b_attr_abc_cfg(dev, cfg->abc_on); + if (ret != 0) { + LOG_ERR("Error setting default ABC %s", cfg->abc_on ? "on" : "off"); + } + + return ret; +} + +#define MHZ19B_INIT(inst) \ + \ + static struct mhz19b_data mhz19b_data_##inst; \ + \ + static const struct mhz19b_cfg mhz19b_cfg_##inst = { \ + .uart_dev = DEVICE_DT_GET(DT_INST_BUS(inst)), \ + .range = DT_INST_PROP(inst, maximum_range), \ + .abc_on = DT_INST_PROP(inst, abc_on), \ + .cb = mhz19b_uart_isr, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, mhz19b_init, NULL, &mhz19b_data_##inst, &mhz19b_cfg_##inst, \ + POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &mhz19b_api_funcs); + +DT_INST_FOREACH_STATUS_OKAY(MHZ19B_INIT) diff --git a/drivers/sensor/mhz19b/mhz19b.h b/drivers/sensor/mhz19b/mhz19b.h new file mode 100644 index 00000000000..f98b0d5934b --- /dev/null +++ b/drivers/sensor/mhz19b/mhz19b.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2021 G-Technologies Sdn. Bhd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_SENSOR_MHZ19B_MHZ19B +#define ZEPHYR_DRIVERS_SENSOR_MHZ19B_MHZ19B + +#include +#include +#include + +#define MHZ19B_BUF_LEN 9 + +#define MHZ19B_TX_CMD_IDX 2 +#define MHZ19B_RX_CMD_IDX 1 +#define MHZ19B_CHECKSUM_IDX 8 + +/* Arbitrary max duration to wait for the response */ +#define MHZ19B_WAIT K_SECONDS(1) + +enum mhz19b_cmd_idx { + /* Command to poll for CO2 */ + MHZ19B_CMD_IDX_GET_CO2, + /* Read range */ + MHZ19B_CMD_IDX_GET_RANGE, + /* Get ABC status */ + MHZ19B_CMD_IDX_GET_ABC, + /* Enable ABC */ + MHZ19B_CMD_IDX_SET_ABC_ON, + /* Disable ABC */ + MHZ19B_CMD_IDX_SET_ABC_OFF, + /* Set detection range to 2000 ppm */ + MHZ19B_CMD_IDX_SET_RANGE_2000, + /* Set detection range to 5000 ppm */ + MHZ19B_CMD_IDX_SET_RANGE_5000, + /* Set detection range to 10000 ppm */ + MHZ19B_CMD_IDX_SET_RANGE_10000, + /* Number of supported commands */ + MHZ19B_CMD_IDX_MAX, +}; + +struct mhz19b_data { + /* Max data length is 16 bits */ + uint16_t data; + /* Command buf length is 9 */ + uint8_t xfer_bytes; + bool has_rsp; + + uint8_t rd_data[MHZ19B_BUF_LEN]; + + struct k_sem tx_sem; + struct k_sem rx_sem; + + enum mhz19b_cmd_idx cmd_idx; +}; + +struct mhz19b_cfg { + const struct device *uart_dev; + uint16_t range; + bool abc_on; + uart_irq_callback_user_data_t cb; +}; + +#define MHZ19B_HEADER 0xff +#define MHZ19B_RESERVED 0x01 +#define MHZ19B_NULL 0x00 +#define MHZ19B_NULL_1 MHZ19B_NULL +#define MHZ19B_NULL_2 MHZ19B_NULL, MHZ19B_NULL_1 +#define MHZ19B_NULL_3 MHZ19B_NULL, MHZ19B_NULL_2 +#define MHZ19B_NULL_4 MHZ19B_NULL, MHZ19B_NULL_3 +#define MHZ19B_NULL_5 MHZ19B_NULL, MHZ19B_NULL_4 +#define MHZ19B_NULL_COUNT(c) MHZ19B_NULL_##c + +#define MHZ19B_ABC_ON 0xA0 +#define MHZ19B_ABC_OFF 0x00 +#define MHZ19B_RANGE_2000 0x07, 0xD0 +#define MHZ19B_RANGE_5000 0x13, 0x88 +#define MHZ19B_RANGE_10000 0x27, 0x10 + +enum mhz19b_cmd { + MHZ19B_CMD_SET_ABC = 0x79, + MHZ19B_CMD_GET_ABC = 0x7D, + MHZ19B_CMD_GET_CO2 = 0x86, + MHZ19B_CMD_SET_RANGE = 0x99, + MHZ19B_CMD_GET_RANGE = 0x9B, +}; + +#endif /* ZEPHYR_DRIVERS_SENSOR_MHZ19B_MHZ19B */ diff --git a/dts/bindings/sensor/winsen,mhz19b.yaml b/dts/bindings/sensor/winsen,mhz19b.yaml new file mode 100644 index 00000000000..3a503f427b5 --- /dev/null +++ b/dts/bindings/sensor/winsen,mhz19b.yaml @@ -0,0 +1,24 @@ +# Copyright (c) 2021 G-Technologies Sdn. Bhd. +# SPDX-License-Identifier: Apache-2.0 + +description: Winsen MHZ-19B CO2 Sensor + +compatible: "winsen,mhz19b" + +include: uart-device.yaml + +properties: + maximum-range: + type: int + required: true + description: CO2 detection range. + enum: + - 2000 + - 5000 + - 10000 + + abc-on: + type: boolean + required: false + description: | + Enable ABC self-calibration function diff --git a/dts/bindings/vendor-prefixes.txt b/dts/bindings/vendor-prefixes.txt index 2a3609912a3..373cbd83f9d 100644 --- a/dts/bindings/vendor-prefixes.txt +++ b/dts/bindings/vendor-prefixes.txt @@ -650,6 +650,7 @@ wexler Wexler whwave Shenzhen whwave Electronics, Inc. wi2wi Wi2Wi, Inc. winbond Winbond Electronics corp. +winsen Zhengzhou Winsen Electronics Technology Co., Ltd. winstar Winstar Display Corp. wits Shenzhen Merrii Technology Co., Ltd. (WITS) wiznet WIZnet Co., Ltd. diff --git a/include/drivers/sensor/mhz19b.h b/include/drivers/sensor/mhz19b.h new file mode 100644 index 00000000000..9656724bf19 --- /dev/null +++ b/include/drivers/sensor/mhz19b.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 G-Technologies Sdn. Bhd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Extended public API for MH-Z19B CO2 Sensor + * + * Some capabilities and operational requirements for this sensor + * cannot be expressed within the sensor driver abstraction. + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_MHZ19B_H_ +#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_MHZ19B_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +enum sensor_attribute_mhz19b { + /** Automatic Baseline Correction Self Calibration Function. */ + SENSOR_ATTR_MHZ19B_ABC = SENSOR_ATTR_PRIV_START, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_SENSOR_MHZ19B_H_ */