1 /*
2  * Copyright (c) 2024 Nathan Olff
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include <zephyr/device.h>
7 #include <zephyr/drivers/i2c.h>
8 #include <zephyr/kernel.h>
9 #if defined(CONFIG_DHT20_CRC)
10 #include <zephyr/sys/crc.h>
11 #endif
12 #include <zephyr/drivers/sensor.h>
13 #include <zephyr/sys/__assert.h>
14 #include <zephyr/logging/log.h>
15 #include <zephyr/sys/byteorder.h>
16 
17 #define DHT20_STATUS_REGISTER 0x71
18 
19 #define DHT20_STATUS_MASK (BIT(0) | BIT(1))
20 
21 #define DHT20_STATUS_MASK_CHECK      0x18
22 #define DHT20_STATUS_MASK_POLL_STATE 0x80
23 
24 #define DHT20_MASK_RESET_REGISTER 0xB0
25 
26 #define DHT20_TRIGGER_MEASUREMENT_COMMAND 0xAC, 0x33, 0x00
27 
28 #define DHT20_TRIGGER_MEASUREMENT_BUFFER_LENGTH 3
29 
30 /** CRC polynom (1 + X^4 + X^5 + X^8) */
31 #define DHT20_CRC_POLYNOM (BIT(0) | BIT(4) | BIT(5))
32 
33 /*
34  * According to datasheet 7.4
35  * Reset register 0x1B, 0x1C and 0x1E
36  */
37 #define DHT20_RESET_REGISTER_0 0x1B
38 #define DHT20_RESET_REGISTER_1 0x1C
39 #define DHT20_RESET_REGISTER_2 0x1E
40 
41 /** Length of the buffer used for data measurement */
42 #define DHT20_MEASUREMENT_BUFFER_LENGTH 7
43 
44 /** Wait some time after reset sequence (in ms) */
45 #define DHT20_RESET_SEQUENCE_WAIT_MS 10
46 
47 /** Wait after power on (in ms) */
48 #define DHT20_POWER_ON_WAIT_MS         75
49 /** Wait during polling after power on (in ms) */
50 #define DHT20_INIT_POLL_STATUS_WAIT_MS 5
51 
52 LOG_MODULE_REGISTER(DHT20, CONFIG_SENSOR_LOG_LEVEL);
53 
54 struct dht20_config {
55 	struct i2c_dt_spec bus;
56 };
57 
58 struct dht20_data {
59 	uint32_t t_sample;
60 	uint32_t rh_sample;
61 };
62 
63 /**
64  * @brief Read status register
65  *
66  * @param dev Pointer to the sensor device
67  * @param[out] status Pointer to which the status will be stored
68  * @return 0 if successful
69  */
read_status(const struct device * dev,uint8_t * status)70 static inline int read_status(const struct device *dev, uint8_t *status)
71 {
72 	const struct dht20_config *cfg = dev->config;
73 	int rc;
74 	uint8_t tx_buf[] = {DHT20_STATUS_REGISTER};
75 	uint8_t rx_buf[1];
76 
77 	/* Write DHT20_STATUS_REGISTER then read to get status */
78 	rc = i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
79 	if (rc < 0) {
80 		LOG_ERR("Failed to start measurement.");
81 		return rc;
82 	}
83 
84 	rc = i2c_read_dt(&cfg->bus, rx_buf, sizeof(rx_buf));
85 	if (rc < 0) {
86 		LOG_ERR("Failed to read data from device.");
87 		return rc;
88 	}
89 
90 	/* Retrieve status from rx_buf */
91 	*status = rx_buf[0];
92 	return rc;
93 }
94 
reset_register(const struct device * dev,uint8_t reg)95 static inline int reset_register(const struct device *dev, uint8_t reg)
96 {
97 	const struct dht20_config *cfg = dev->config;
98 	int rc;
99 	uint8_t tx_buf[] = {reg, 0, 0};
100 	uint8_t rx_buf[3];
101 
102 	/* Write and then read 3 bytes from device */
103 	rc = i2c_write_read_dt(&cfg->bus, tx_buf, sizeof(tx_buf), rx_buf, sizeof(rx_buf));
104 	if (rc < 0) {
105 		LOG_ERR("Failed to reset register.");
106 		return rc;
107 	}
108 
109 	/* Write register again, using values read earlier */
110 	tx_buf[0] = DHT20_MASK_RESET_REGISTER | reg;
111 	tx_buf[1] = rx_buf[1];
112 	tx_buf[2] = rx_buf[2];
113 	rc = i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
114 	if (rc < 0) {
115 		LOG_ERR("Failed to reset register.");
116 		return rc;
117 	}
118 
119 	return rc;
120 }
121 
reset_sensor(const struct device * dev)122 static inline int reset_sensor(const struct device *dev)
123 {
124 	int rc;
125 	uint8_t status;
126 
127 	rc = read_status(dev, &status);
128 	if (rc < 0) {
129 		LOG_ERR("Failed to read status");
130 		return rc;
131 	}
132 
133 	if ((status & DHT20_STATUS_MASK_CHECK) != DHT20_STATUS_MASK_CHECK) {
134 		/*
135 		 * According to datasheet 7.4
136 		 * Reset register 0x1B, 0x1C and 0x1E if status does not match expected value
137 		 */
138 		rc = reset_register(dev, DHT20_RESET_REGISTER_0);
139 		if (rc < 0) {
140 			return rc;
141 		}
142 		rc = reset_register(dev, DHT20_RESET_REGISTER_1);
143 		if (rc < 0) {
144 			return rc;
145 		}
146 		rc = reset_register(dev, DHT20_RESET_REGISTER_2);
147 		if (rc < 0) {
148 			return rc;
149 		}
150 		/* Wait 10ms after reset sequence */
151 		k_msleep(DHT20_RESET_SEQUENCE_WAIT_MS);
152 	}
153 
154 	return 0;
155 }
156 
dht20_read_sample(const struct device * dev,uint32_t * t_sample,uint32_t * rh_sample)157 static int dht20_read_sample(const struct device *dev, uint32_t *t_sample, uint32_t *rh_sample)
158 {
159 	const struct dht20_config *cfg = dev->config;
160 	/*
161 	 * Datasheet shows content of the measurement data as follow
162 	 *
163 	 * +------+----------------------------------------+
164 	 * | Byte | Content                                |
165 	 * +------+----------------------------------------+
166 	 * | 0    | State                                  |
167 	 * | 1    | Humidity                               |
168 	 * | 2    | Humidity                               |
169 	 * | 3    | Humidity (4 MSb) | Temperature (4 LSb) |
170 	 * | 4    | Temperature                            |
171 	 * | 5    | Temperature                            |
172 	 * | 6    | CRC                                    |
173 	 * +------+----------------------------------------+
174 	 */
175 	uint8_t rx_buf[DHT20_MEASUREMENT_BUFFER_LENGTH];
176 	int rc;
177 	uint8_t status;
178 
179 	rc = i2c_read_dt(&cfg->bus, rx_buf, sizeof(rx_buf));
180 	if (rc < 0) {
181 		LOG_ERR("Failed to read data from device.");
182 		return rc;
183 	}
184 
185 	status = rx_buf[0];
186 	/* Extract 20 bits for humidity data */
187 	*rh_sample = sys_get_be24(&rx_buf[1]) >> 4;
188 	/* Extract 20 bits for temperature data */
189 	*t_sample = sys_get_be24(&rx_buf[3]) & 0x0FFFFF;
190 
191 #if defined(CONFIG_DHT20_CRC)
192 	/* Compute and check CRC with last byte of measurement data */
193 	uint8_t crc = crc8(rx_buf, 6, DHT20_CRC_POLYNOM, 0xFF, false);
194 
195 	if (crc != rx_buf[6]) {
196 		rc = -EIO;
197 	}
198 #endif
199 
200 	return rc;
201 }
202 
dht20_sample_fetch(const struct device * dev,enum sensor_channel chan)203 static int dht20_sample_fetch(const struct device *dev, enum sensor_channel chan)
204 {
205 	struct dht20_data *data = dev->data;
206 	const struct dht20_config *cfg = dev->config;
207 	int rc;
208 	uint8_t tx_buf[DHT20_TRIGGER_MEASUREMENT_BUFFER_LENGTH] = {
209 		DHT20_TRIGGER_MEASUREMENT_COMMAND};
210 	uint8_t status;
211 
212 	if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP &&
213 	    chan != SENSOR_CHAN_HUMIDITY) {
214 		return -ENOTSUP;
215 	}
216 
217 	/* Reset sensor if needed */
218 	reset_sensor(dev);
219 
220 	/* Send trigger measurement command */
221 	rc = i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
222 	if (rc < 0) {
223 		LOG_ERR("Failed to start measurement.");
224 		return rc;
225 	}
226 
227 	/*
228 	 * According to datasheet maximum time to make temperature and humidity
229 	 * measurements is 80ms
230 	 */
231 	k_msleep(DHT20_POWER_ON_WAIT_MS);
232 
233 	do {
234 		k_msleep(DHT20_INIT_POLL_STATUS_WAIT_MS);
235 
236 		rc = read_status(dev, &status);
237 		if (rc < 0) {
238 			LOG_ERR("Failed to read status.");
239 			return rc;
240 		}
241 	} while ((status & DHT20_STATUS_MASK_POLL_STATE) != 0);
242 
243 	rc = dht20_read_sample(dev, &data->t_sample, &data->rh_sample);
244 	if (rc < 0) {
245 		LOG_ERR("Failed to fetch data.");
246 		return rc;
247 	}
248 
249 	return 0;
250 }
251 
dht20_temp_convert(struct sensor_value * val,uint32_t raw)252 static void dht20_temp_convert(struct sensor_value *val, uint32_t raw)
253 {
254 	int32_t micro_c;
255 
256 	/*
257 	 * Convert to micro Celsius
258 	 * DegCT = (S / 2^20) * 200 - 50
259 	 * uDegCT = (S * 1e6 * 200 - 50 * 1e6) / (1 << 20)
260 	 */
261 	micro_c = ((int64_t)raw * 1000000 * 200) / BIT(20) - 50 * 1000000;
262 
263 	val->val1 = micro_c / 1000000;
264 	val->val2 = micro_c % 1000000;
265 }
266 
dht20_rh_convert(struct sensor_value * val,uint32_t raw)267 static void dht20_rh_convert(struct sensor_value *val, uint32_t raw)
268 {
269 	int32_t micro_rh;
270 
271 	/*
272 	 * Convert to micro %RH
273 	 * %RH = (S / 2^20) * 100%
274 	 * u%RH = (S * 1e6 * 100) / (1 << 20)
275 	 */
276 	micro_rh = ((uint64_t)raw * 1000000 * 100) / BIT(20);
277 
278 	val->val1 = micro_rh / 1000000;
279 	val->val2 = micro_rh % 1000000;
280 }
281 
dht20_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)282 static int dht20_channel_get(const struct device *dev, enum sensor_channel chan,
283 			     struct sensor_value *val)
284 {
285 	const struct dht20_data *data = dev->data;
286 
287 	if (chan == SENSOR_CHAN_AMBIENT_TEMP) {
288 		dht20_temp_convert(val, data->t_sample);
289 	} else if (chan == SENSOR_CHAN_HUMIDITY) {
290 		dht20_rh_convert(val, data->rh_sample);
291 	} else {
292 		return -ENOTSUP;
293 	}
294 
295 	return 0;
296 }
297 
dht20_init(const struct device * dev)298 static int dht20_init(const struct device *dev)
299 {
300 	const struct dht20_config *cfg = dev->config;
301 
302 	if (!i2c_is_ready_dt(&cfg->bus)) {
303 		LOG_ERR("I2C dev %s not ready", cfg->bus.bus->name);
304 		return -ENODEV;
305 	}
306 
307 	return 0;
308 }
309 
310 static DEVICE_API(sensor, dht20_driver_api) = {
311 	.sample_fetch = dht20_sample_fetch,
312 	.channel_get = dht20_channel_get,
313 };
314 
315 #define DT_DRV_COMPAT aosong_dht20
316 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
317 
318 #define DEFINE_DHT20(n)                                                                            \
319 	static struct dht20_data dht20_data_##n;                                                   \
320                                                                                                    \
321 	static const struct dht20_config dht20_config_##n = {.bus = I2C_DT_SPEC_INST_GET(n)};      \
322                                                                                                    \
323 	SENSOR_DEVICE_DT_INST_DEFINE(n, dht20_init, NULL, &dht20_data_##n, &dht20_config_##n,      \
324 				     POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &dht20_driver_api);
325 
326 DT_INST_FOREACH_STATUS_OKAY(DEFINE_DHT20)
327 
328 #endif
329 #undef DT_DRV_COMPAT
330 
331 #define DT_DRV_COMPAT aosong_aht20
332 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
333 
334 #define DEFINE_AHT20(n)                                                                            \
335 	static struct dht20_data aht20_data_##n;                                                   \
336                                                                                                    \
337 	static const struct dht20_config aht20_config_##n = {.bus = I2C_DT_SPEC_INST_GET(n)};      \
338                                                                                                    \
339 	SENSOR_DEVICE_DT_INST_DEFINE(n, dht20_init, NULL, &aht20_data_##n, &aht20_config_##n,      \
340 				     POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &dht20_driver_api);
341 
342 DT_INST_FOREACH_STATUS_OKAY(DEFINE_AHT20)
343 
344 #endif
345 #undef DT_DRV_COMPAT
346 
347 #define DT_DRV_COMPAT aosong_am2301b
348 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
349 
350 #define DEFINE_AM2301B(n)                                                                          \
351 	static struct dht20_data am2301b_data_##n;                                                 \
352                                                                                                    \
353 	static const struct dht20_config am2301b_config_##n = {.bus = I2C_DT_SPEC_INST_GET(n)};    \
354                                                                                                    \
355 	SENSOR_DEVICE_DT_INST_DEFINE(n, dht20_init, NULL, &am2301b_data_##n, &am2301b_config_##n,  \
356 				     POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &dht20_driver_api);
357 
358 DT_INST_FOREACH_STATUS_OKAY(DEFINE_AM2301B)
359 
360 #endif
361 #undef DT_DRV_COMPAT
362