Skip to content

Commit 26f7b9c

Browse files
ycsinAzamLukman
authored andcommitted
drivers: sensor: Add MH-Z19B CO2 sensor driver
Add MH-Z19B CO2 sensor driver. Signed-off-by: Yong Cong Sin <[email protected]> Co-Authored-By: Azamlukman <[email protected]>
1 parent b037054 commit 26f7b9c

File tree

9 files changed

+511
-0
lines changed

9 files changed

+511
-0
lines changed

drivers/sensor/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ add_subdirectory_ifdef(CONFIG_MAX30101 max30101)
6464
add_subdirectory_ifdef(CONFIG_MAX44009 max44009)
6565
add_subdirectory_ifdef(CONFIG_MAX6675 max6675)
6666
add_subdirectory_ifdef(CONFIG_MCP9808 mcp9808)
67+
add_subdirectory_ifdef(CONFIG_MHZ19B mhz19b)
6768
add_subdirectory_ifdef(CONFIG_MPR mpr)
6869
add_subdirectory_ifdef(CONFIG_MPU6050 mpu6050)
6970
add_subdirectory_ifdef(CONFIG_MS5607 ms5607)

drivers/sensor/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ source "drivers/sensor/mchp_tach_xec/Kconfig"
168168

169169
source "drivers/sensor/mcp9808/Kconfig"
170170

171+
source "drivers/sensor/mhz19b/Kconfig"
172+
171173
source "drivers/sensor/mpr/Kconfig"
172174

173175
source "drivers/sensor/mpu6050/Kconfig"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
zephyr_library()
4+
5+
zephyr_library_sources(mhz19b.c)

drivers/sensor/mhz19b/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) 2021 G-Technologies Sdn. Bhd.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config MHZ19B
5+
bool "Winsen CO2 sensor"
6+
depends on UART_INTERRUPT_DRIVEN
7+
help
8+
Enable driver for the MHZ19B CO2 Sensor.

drivers/sensor/mhz19b/mhz19b.c

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
/*
2+
* Copyright (c) 2021 G-Technologies Sdn. Bhd.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Datasheet:
7+
* https://www.winsen-sensor.com/sensors/co2-sensor/mh-z19b.html
8+
*/
9+
10+
#define DT_DRV_COMPAT winsen_mhz19b
11+
12+
#include <logging/log.h>
13+
#include <sys/byteorder.h>
14+
#include <drivers/sensor.h>
15+
16+
#include <drivers/sensor/mhz19b.h>
17+
#include "mhz19b.h"
18+
19+
LOG_MODULE_REGISTER(mhz19b, CONFIG_SENSOR_LOG_LEVEL);
20+
21+
/* Table of supported MH-Z19B commands with precomputed checksum */
22+
static const uint8_t mhz19b_cmds[MHZ19B_CMD_IDX_MAX][MHZ19B_BUF_LEN] = {
23+
[MHZ19B_CMD_IDX_GET_CO2] = {
24+
MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_GET_CO2, MHZ19B_NULL_COUNT(5), 0x79
25+
},
26+
[MHZ19B_CMD_IDX_GET_RANGE] = {
27+
MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_GET_RANGE, MHZ19B_NULL_COUNT(5), 0x64
28+
},
29+
[MHZ19B_CMD_IDX_GET_ABC] = {
30+
MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_GET_ABC, MHZ19B_NULL_COUNT(5), 0x82
31+
},
32+
[MHZ19B_CMD_IDX_SET_ABC_ON] = {
33+
MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_ABC, MHZ19B_ABC_ON,
34+
MHZ19B_NULL_COUNT(4), 0xE6
35+
},
36+
[MHZ19B_CMD_IDX_SET_ABC_OFF] = {
37+
MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_ABC, MHZ19B_ABC_OFF,
38+
MHZ19B_NULL_COUNT(4), 0x86
39+
},
40+
[MHZ19B_CMD_IDX_SET_RANGE_2000] = {
41+
MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_RANGE, MHZ19B_NULL_COUNT(3),
42+
MHZ19B_RANGE_2000, 0x8F
43+
},
44+
[MHZ19B_CMD_IDX_SET_RANGE_5000] = {
45+
MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_RANGE, MHZ19B_NULL_COUNT(3),
46+
MHZ19B_RANGE_5000, 0xCB
47+
},
48+
[MHZ19B_CMD_IDX_SET_RANGE_10000] = {
49+
MHZ19B_HEADER, MHZ19B_RESERVED, MHZ19B_CMD_SET_RANGE, MHZ19B_NULL_COUNT(3),
50+
MHZ19B_RANGE_10000, 0x2F
51+
},
52+
};
53+
54+
static void mhz19b_uart_flush(const struct device *uart_dev)
55+
{
56+
uint8_t c;
57+
58+
while (uart_fifo_read(uart_dev, &c, 1) > 0) {
59+
continue;
60+
}
61+
}
62+
63+
static uint8_t mhz19b_checksum(const uint8_t *data)
64+
{
65+
uint8_t cs = 0;
66+
67+
for (uint8_t i = 1; i < MHZ19B_BUF_LEN - 1; i++) {
68+
cs += data[i];
69+
}
70+
71+
return 0xff - cs + 1;
72+
}
73+
74+
static int mhz19b_send_cmd(const struct device *dev, enum mhz19b_cmd_idx cmd_idx, bool has_rsp)
75+
{
76+
struct mhz19b_data *data = dev->data;
77+
const struct mhz19b_cfg *cfg = dev->config;
78+
int ret;
79+
80+
/* Make sure last command has been transferred */
81+
ret = k_sem_take(&data->tx_sem, MHZ19B_WAIT);
82+
if (ret) {
83+
return ret;
84+
}
85+
86+
data->cmd_idx = cmd_idx;
87+
data->has_rsp = has_rsp;
88+
k_sem_reset(&data->rx_sem);
89+
90+
uart_irq_tx_enable(cfg->uart_dev);
91+
92+
if (has_rsp) {
93+
uart_irq_rx_enable(cfg->uart_dev);
94+
ret = k_sem_take(&data->rx_sem, MHZ19B_WAIT);
95+
}
96+
97+
return ret;
98+
}
99+
100+
static inline int mhz19b_send_config(const struct device *dev, enum mhz19b_cmd_idx cmd_idx)
101+
{
102+
struct mhz19b_data *data = dev->data;
103+
int ret;
104+
105+
ret = mhz19b_send_cmd(dev, cmd_idx, true);
106+
if (ret < 0) {
107+
return ret;
108+
}
109+
110+
if (data->rd_data[MHZ19B_RX_CMD_IDX] != mhz19b_cmds[data->cmd_idx][MHZ19B_TX_CMD_IDX]) {
111+
return -EINVAL;
112+
}
113+
114+
return 0;
115+
}
116+
117+
static inline int mhz19b_poll_data(const struct device *dev, enum mhz19b_cmd_idx cmd_idx)
118+
{
119+
struct mhz19b_data *data = dev->data;
120+
uint8_t checksum;
121+
int ret;
122+
123+
ret = mhz19b_send_cmd(dev, cmd_idx, true);
124+
if (ret < 0) {
125+
return ret;
126+
}
127+
128+
checksum = mhz19b_checksum(data->rd_data);
129+
if (checksum != data->rd_data[MHZ19B_CHECKSUM_IDX]) {
130+
LOG_DBG("Checksum mismatch: 0x%x != 0x%x", checksum,
131+
data->rd_data[MHZ19B_CHECKSUM_IDX]);
132+
return -EBADMSG;
133+
}
134+
135+
switch (cmd_idx) {
136+
case MHZ19B_CMD_IDX_GET_CO2:
137+
data->data = sys_get_be16(&data->rd_data[2]);
138+
break;
139+
case MHZ19B_CMD_IDX_GET_RANGE:
140+
data->data = sys_get_be16(&data->rd_data[4]);
141+
break;
142+
case MHZ19B_CMD_IDX_GET_ABC:
143+
data->data = data->rd_data[7];
144+
break;
145+
default:
146+
return -EINVAL;
147+
}
148+
149+
return 0;
150+
}
151+
152+
static int mhz19b_channel_get(const struct device *dev, enum sensor_channel chan,
153+
struct sensor_value *val)
154+
{
155+
struct mhz19b_data *data = dev->data;
156+
157+
if (chan != SENSOR_CHAN_CO2) {
158+
return -ENOTSUP;
159+
}
160+
161+
val->val1 = (int32_t)data->data;
162+
val->val2 = 0;
163+
164+
return 0;
165+
}
166+
167+
static int mhz19b_attr_full_scale_cfg(const struct device *dev, int range)
168+
{
169+
switch (range) {
170+
case 2000:
171+
LOG_DBG("Configure range to %d", range);
172+
return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_RANGE_2000);
173+
case 5000:
174+
LOG_DBG("Configure range to %d", range);
175+
return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_RANGE_5000);
176+
case 10000:
177+
LOG_DBG("Configure range to %d", range);
178+
return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_RANGE_10000);
179+
default:
180+
return -ENOTSUP;
181+
}
182+
}
183+
184+
static int mhz19b_attr_abc_cfg(const struct device *dev, bool on)
185+
{
186+
if (on) {
187+
LOG_DBG("%s ABC", "Enable");
188+
return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_ABC_ON);
189+
}
190+
191+
LOG_DBG("%s ABC", "Disable");
192+
return mhz19b_send_config(dev, MHZ19B_CMD_IDX_SET_ABC_OFF);
193+
}
194+
195+
static int mhz19b_attr_set(const struct device *dev, enum sensor_channel chan,
196+
enum sensor_attribute attr, const struct sensor_value *val)
197+
{
198+
if (chan != SENSOR_CHAN_CO2) {
199+
return -ENOTSUP;
200+
}
201+
202+
switch (attr) {
203+
case SENSOR_ATTR_FULL_SCALE:
204+
return mhz19b_attr_full_scale_cfg(dev, val->val1);
205+
206+
case SENSOR_ATTR_MHZ19B_ABC:
207+
return mhz19b_attr_abc_cfg(dev, val->val1);
208+
209+
default:
210+
return -ENOTSUP;
211+
}
212+
}
213+
214+
static int mhz19b_attr_get(const struct device *dev, enum sensor_channel chan,
215+
enum sensor_attribute attr, struct sensor_value *val)
216+
{
217+
struct mhz19b_data *data = dev->data;
218+
int ret;
219+
220+
if (chan != SENSOR_CHAN_CO2) {
221+
return -ENOTSUP;
222+
}
223+
224+
switch (attr) {
225+
case SENSOR_ATTR_FULL_SCALE:
226+
ret = mhz19b_poll_data(dev, MHZ19B_CMD_IDX_GET_RANGE);
227+
break;
228+
case SENSOR_ATTR_MHZ19B_ABC:
229+
ret = mhz19b_poll_data(dev, MHZ19B_CMD_IDX_GET_ABC);
230+
break;
231+
default:
232+
return -ENOTSUP;
233+
}
234+
235+
val->val1 = (int32_t)data->data;
236+
val->val2 = 0;
237+
238+
return ret;
239+
}
240+
241+
static int mhz19b_sample_fetch(const struct device *dev, enum sensor_channel chan)
242+
{
243+
if (chan != SENSOR_CHAN_CO2) {
244+
return -ENOTSUP;
245+
}
246+
247+
return mhz19b_poll_data(dev, MHZ19B_CMD_IDX_GET_CO2);
248+
}
249+
250+
static const struct sensor_driver_api mhz19b_api_funcs = {
251+
.attr_set = mhz19b_attr_set,
252+
.attr_get = mhz19b_attr_get,
253+
.sample_fetch = mhz19b_sample_fetch,
254+
.channel_get = mhz19b_channel_get,
255+
};
256+
257+
static void mhz19b_uart_isr(const struct device *uart_dev, void *user_data)
258+
{
259+
const struct device *dev = user_data;
260+
struct mhz19b_data *data = dev->data;
261+
262+
ARG_UNUSED(user_data);
263+
264+
if (uart_dev == NULL) {
265+
return;
266+
}
267+
268+
if (!uart_irq_update(uart_dev)) {
269+
return;
270+
}
271+
272+
if (uart_irq_rx_ready(uart_dev)) {
273+
data->xfer_bytes += uart_fifo_read(uart_dev, &data->rd_data[data->xfer_bytes],
274+
MHZ19B_BUF_LEN - data->xfer_bytes);
275+
276+
if (data->xfer_bytes == MHZ19B_BUF_LEN) {
277+
data->xfer_bytes = 0;
278+
uart_irq_rx_disable(uart_dev);
279+
k_sem_give(&data->rx_sem);
280+
if (data->has_rsp) {
281+
k_sem_give(&data->tx_sem);
282+
}
283+
}
284+
}
285+
286+
if (uart_irq_tx_ready(uart_dev)) {
287+
data->xfer_bytes +=
288+
uart_fifo_fill(uart_dev, &mhz19b_cmds[data->cmd_idx][data->xfer_bytes],
289+
MHZ19B_BUF_LEN - data->xfer_bytes);
290+
291+
if (data->xfer_bytes == MHZ19B_BUF_LEN) {
292+
data->xfer_bytes = 0;
293+
uart_irq_tx_disable(uart_dev);
294+
if (!data->has_rsp) {
295+
k_sem_give(&data->tx_sem);
296+
}
297+
}
298+
}
299+
}
300+
301+
static int mhz19b_init(const struct device *dev)
302+
{
303+
struct mhz19b_data *data = dev->data;
304+
const struct mhz19b_cfg *cfg = dev->config;
305+
int ret;
306+
307+
uart_irq_rx_disable(cfg->uart_dev);
308+
uart_irq_tx_disable(cfg->uart_dev);
309+
310+
mhz19b_uart_flush(cfg->uart_dev);
311+
312+
uart_irq_callback_user_data_set(cfg->uart_dev, cfg->cb, (void *)dev);
313+
314+
k_sem_init(&data->rx_sem, 0, 1);
315+
k_sem_init(&data->tx_sem, 1, 1);
316+
317+
/* Configure default detection range */
318+
ret = mhz19b_attr_full_scale_cfg(dev, cfg->range);
319+
if (ret != 0) {
320+
LOG_ERR("Error setting default range %d", cfg->range);
321+
return ret;
322+
}
323+
324+
/* Configure ABC logic */
325+
ret = mhz19b_attr_abc_cfg(dev, cfg->abc_on);
326+
if (ret != 0) {
327+
LOG_ERR("Error setting default ABC %s", cfg->abc_on ? "on" : "off");
328+
}
329+
330+
return ret;
331+
}
332+
333+
#define MHZ19B_INIT(inst) \
334+
\
335+
static struct mhz19b_data mhz19b_data_##inst; \
336+
\
337+
static const struct mhz19b_cfg mhz19b_cfg_##inst = { \
338+
.uart_dev = DEVICE_DT_GET(DT_INST_BUS(inst)), \
339+
.range = DT_INST_PROP(inst, maximum_range), \
340+
.abc_on = DT_INST_PROP(inst, abc_on), \
341+
.cb = mhz19b_uart_isr, \
342+
}; \
343+
\
344+
DEVICE_DT_INST_DEFINE(inst, mhz19b_init, NULL, &mhz19b_data_##inst, &mhz19b_cfg_##inst, \
345+
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &mhz19b_api_funcs);
346+
347+
DT_INST_FOREACH_STATUS_OKAY(MHZ19B_INIT)

0 commit comments

Comments
 (0)