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