drivers: sensor: Add MH-Z19B CO2 sensor driver

Add MH-Z19B CO2 sensor driver.

Signed-off-by: Yong Cong Sin <yongcong.sin@gmail.com>
Co-Authored-By: Azamlukman <azamlukmanabdullah@gmail.com>
This commit is contained in:
Yong Cong Sin 2021-10-30 01:15:56 +08:00 committed by Carles Cufí
parent b037054f21
commit 26f7b9c1ea
9 changed files with 511 additions and 0 deletions

View File

@ -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)

View File

@ -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"

View File

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(mhz19b.c)

View File

@ -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.

View File

@ -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 <logging/log.h>
#include <sys/byteorder.h>
#include <drivers/sensor.h>
#include <drivers/sensor/mhz19b.h>
#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)

View File

@ -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 <kernel.h>
#include <device.h>
#include <drivers/uart.h>
#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 */

View File

@ -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

View File

@ -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.

View File

@ -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 <drivers/sensor.h>
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_ */