1 /*
2  * Copyright (c) 2023 SteadConnect
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Datasheet:
7  * https://wiki.dfrobot.com/A01NYUB%20Waterproof%20Ultrasonic%20Sensor%20SKU:%20SEN0313
8  *
9  */
10 
11 #define DT_DRV_COMPAT dfrobot_a01nyub
12 
13 #include <zephyr/kernel.h>
14 #include <zephyr/device.h>
15 #include <zephyr/drivers/uart.h>
16 #include <zephyr/logging/log.h>
17 #include <zephyr/sys/byteorder.h>
18 #include <zephyr/drivers/sensor.h>
19 
20 LOG_MODULE_REGISTER(a01nyub_sensor, CONFIG_SENSOR_LOG_LEVEL);
21 
22 #define A01NYUB_BUF_LEN 4
23 #define A01NYUB_CHECKSUM_IDX 3
24 #define A01NYUB_HEADER 0xff
25 
26 const struct uart_config uart_cfg_a01nyub = {
27 	.baudrate = 9600,
28 	.parity = UART_CFG_PARITY_NONE,
29 	.stop_bits = UART_CFG_STOP_BITS_1,
30 	.data_bits = UART_CFG_DATA_BITS_8,
31 	.flow_ctrl = UART_CFG_FLOW_CTRL_NONE
32 };
33 
34 struct a01nyub_data {
35 	/* Max data length is 16 bits */
36 	uint16_t data;
37 	uint8_t xfer_bytes;
38 	uint8_t rd_data[A01NYUB_BUF_LEN];
39 };
40 
41 struct a01nyub_cfg {
42 	const struct device *uart_dev;
43 	uart_irq_callback_user_data_t cb;
44 };
45 
a01nyub_uart_flush(const struct device * uart_dev)46 static void a01nyub_uart_flush(const struct device *uart_dev)
47 {
48 	uint8_t c;
49 
50 	while (uart_fifo_read(uart_dev, &c, 1) > 0) {
51 		continue;
52 	}
53 }
54 
a01nyub_checksum(const uint8_t * data)55 static uint8_t a01nyub_checksum(const uint8_t *data)
56 {
57 	uint16_t cs = 0;
58 
59 	for (uint8_t i = 0; i < A01NYUB_BUF_LEN - 1; i++) {
60 		cs += data[i];
61 	}
62 
63 	return (uint8_t) (cs & 0x00FF);
64 }
65 
a01nyub_poll_data(const struct device * dev)66 static inline int a01nyub_poll_data(const struct device *dev)
67 {
68 	struct a01nyub_data *data = dev->data;
69 	uint8_t checksum;
70 
71 	checksum = a01nyub_checksum(data->rd_data);
72 	if (checksum != data->rd_data[A01NYUB_CHECKSUM_IDX]) {
73 		LOG_DBG("Checksum mismatch: calculated 0x%x != data checksum 0x%x",
74 			checksum,
75 			data->rd_data[A01NYUB_CHECKSUM_IDX]);
76 		LOG_DBG("Data bytes: (%x,%x,%x,%x)",
77 						data->rd_data[0],
78 						data->rd_data[1],
79 						data->rd_data[2],
80 						data->rd_data[3]);
81 
82 		return -EBADMSG;
83 	}
84 
85 	data->data = (data->rd_data[1]<<8) + data->rd_data[2];
86 
87 	return 0;
88 }
89 
a01nyub_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)90 static int a01nyub_channel_get(const struct device *dev, enum sensor_channel chan,
91 			      struct sensor_value *val)
92 {
93 	struct a01nyub_data *data = dev->data;
94 
95 	if (chan != SENSOR_CHAN_DISTANCE) {
96 		return -ENOTSUP;
97 	}
98 	/* val1 is meters, val2 is microns. Both are int32_t
99 	 * data->data is in mm and units of uint16_t
100 	 */
101 	val->val1 = (uint32_t) (data->data / (uint16_t) 1000);
102 	val->val2 = (uint32_t) ((data->data % 1000) * 1000);
103 	return 0;
104 }
105 
a01nyub_sample_fetch(const struct device * dev,enum sensor_channel chan)106 static int a01nyub_sample_fetch(const struct device *dev, enum sensor_channel chan)
107 {
108 	__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL);
109 
110 	if (chan == SENSOR_CHAN_DISTANCE || chan == SENSOR_CHAN_ALL) {
111 		return a01nyub_poll_data(dev);
112 	}
113 
114 	return -ENOTSUP;
115 }
116 
117 static DEVICE_API(sensor, a01nyub_api_funcs) = {
118 	.sample_fetch = a01nyub_sample_fetch,
119 	.channel_get = a01nyub_channel_get,
120 };
121 
a01nyub_uart_isr(const struct device * uart_dev,void * user_data)122 static void a01nyub_uart_isr(const struct device *uart_dev, void *user_data)
123 {
124 	const struct device *dev = user_data;
125 	struct a01nyub_data *data = dev->data;
126 
127 	if (uart_dev == NULL) {
128 		LOG_DBG("UART device is NULL");
129 		return;
130 	}
131 
132 	if (!uart_irq_update(uart_dev)) {
133 		LOG_DBG("Unable to start processing interrupts");
134 		return;
135 	}
136 
137 	if (uart_irq_rx_ready(uart_dev)) {
138 		data->xfer_bytes += uart_fifo_read(uart_dev, &data->rd_data[data->xfer_bytes],
139 						   A01NYUB_BUF_LEN - data->xfer_bytes);
140 
141 		/* The first byte should be A01NYUB_HEADER for a valid read.
142 		 * If we do not read A01NYUB_HEADER on what we think is the
143 		 * first byte, then reset the number of bytes read until we do
144 		 */
145 		if ((data->rd_data[0] != A01NYUB_HEADER) & (data->xfer_bytes == 1)) {
146 			LOG_DBG("First byte not header! Resetting # of bytes read.");
147 			data->xfer_bytes = 0;
148 		}
149 
150 		if (data->xfer_bytes == A01NYUB_BUF_LEN) {
151 			LOG_DBG("Read (0x%x,0x%x,0x%x,0x%x)",
152 				data->rd_data[0],
153 				data->rd_data[1],
154 				data->rd_data[2],
155 				data->rd_data[3]);
156 			a01nyub_uart_flush(uart_dev);
157 			data->xfer_bytes = 0;
158 		}
159 	}
160 }
161 
a01nyub_init(const struct device * dev)162 static int a01nyub_init(const struct device *dev)
163 {
164 	const struct a01nyub_cfg *cfg = dev->config;
165 	int ret = 0;
166 
167 	uart_irq_rx_disable(cfg->uart_dev);
168 	uart_irq_tx_disable(cfg->uart_dev);
169 
170 	a01nyub_uart_flush(cfg->uart_dev);
171 
172 	LOG_DBG("Initializing A01NYUB driver");
173 
174 	ret = uart_configure(cfg->uart_dev, &uart_cfg_a01nyub);
175 	if (ret == -ENOSYS) {
176 		LOG_ERR("Unable to configure UART port");
177 		return -ENOSYS;
178 	}
179 
180 	ret = uart_irq_callback_user_data_set(cfg->uart_dev, cfg->cb, (void *)dev);
181 
182 	if (ret < 0) {
183 		if (ret == -ENOTSUP) {
184 			LOG_ERR("Interrupt-driven UART API support not enabled");
185 		} else if (ret == -ENOSYS) {
186 			LOG_ERR("UART device does not support interrupt-driven API");
187 		} else {
188 			LOG_ERR("Error setting UART callback: %d", ret);
189 		}
190 		return ret;
191 	}
192 
193 	uart_irq_rx_enable(cfg->uart_dev);
194 
195 	return ret;
196 }
197 
198 #define A01NYUB_INIT(inst)							\
199 										\
200 	static struct a01nyub_data a01nyub_data_##inst;				\
201 										\
202 	static const struct a01nyub_cfg a01nyub_cfg_##inst = {			\
203 		.uart_dev = DEVICE_DT_GET(DT_INST_BUS(inst)),			\
204 		.cb = a01nyub_uart_isr,						\
205 	};									\
206 										\
207 	SENSOR_DEVICE_DT_INST_DEFINE(inst, a01nyub_init, NULL,			\
208 		&a01nyub_data_##inst, &a01nyub_cfg_##inst,			\
209 		POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &a01nyub_api_funcs);
210 
211 DT_INST_FOREACH_STATUS_OKAY(A01NYUB_INIT)
212