1 /*
2  * Copyright (c) 2024 Adrien Leravat
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT hc_sr04
8 
9 #include <zephyr/kernel.h>
10 #include <zephyr/drivers/gpio.h>
11 #include <zephyr/drivers/sensor.h>
12 #include <zephyr/logging/log.h>
13 
14 LOG_MODULE_REGISTER(HC_SR04, CONFIG_SENSOR_LOG_LEVEL);
15 
16 #define HC_SR04_MM_PER_MS     171
17 
18 static const uint32_t hw_cycles_per_ms = sys_clock_hw_cycles_per_sec() / 1000;
19 
20 struct hcsr04_data {
21 	const struct device *dev;
22 	struct gpio_callback gpio_cb;
23 	struct k_sem sem;
24 	uint32_t start_cycles;
25 	atomic_t echo_high_cycles;
26 };
27 
28 struct hcsr04_config {
29 	struct gpio_dt_spec trigger_gpios;
30 	struct gpio_dt_spec echo_gpios;
31 };
32 
33 static void hcsr04_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins);
34 
hcsr04_configure_gpios(const struct hcsr04_config * cfg)35 static int hcsr04_configure_gpios(const struct hcsr04_config *cfg)
36 {
37 	int ret;
38 
39 	if (!gpio_is_ready_dt(&cfg->trigger_gpios)) {
40 		LOG_ERR("GPIO '%s' not ready", cfg->trigger_gpios.port->name);
41 		return -ENODEV;
42 	}
43 	ret = gpio_pin_configure_dt(&cfg->trigger_gpios, GPIO_OUTPUT_LOW);
44 	if (ret < 0) {
45 		LOG_ERR("Failed to configure '%s' as output: %d", cfg->trigger_gpios.port->name,
46 			ret);
47 		return ret;
48 	}
49 
50 	if (!gpio_is_ready_dt(&cfg->echo_gpios)) {
51 		LOG_ERR("GPIO '%s' not ready", cfg->echo_gpios.port->name);
52 		return -ENODEV;
53 	}
54 	ret = gpio_pin_configure_dt(&cfg->echo_gpios, GPIO_INPUT);
55 	if (ret < 0) {
56 		LOG_ERR("Failed to configure '%s' as output: %d", cfg->echo_gpios.port->name, ret);
57 		return ret;
58 	}
59 
60 	return 0;
61 }
62 
hcsr04_configure_interrupt(const struct hcsr04_config * cfg,struct hcsr04_data * data)63 static int hcsr04_configure_interrupt(const struct hcsr04_config *cfg, struct hcsr04_data *data)
64 {
65 	int ret;
66 
67 	/* Disable initially to avoid spurious interrupts. */
68 	ret = gpio_pin_interrupt_configure(cfg->echo_gpios.port, cfg->echo_gpios.pin,
69 		GPIO_INT_DISABLE);
70 	if (ret < 0) {
71 		LOG_ERR("Failed to configure '%s' as interrupt: %d", cfg->echo_gpios.port->name,
72 			ret);
73 		return -EIO;
74 	}
75 	gpio_init_callback(&data->gpio_cb, &hcsr04_gpio_callback, BIT(cfg->echo_gpios.pin));
76 	ret = gpio_add_callback(cfg->echo_gpios.port, &data->gpio_cb);
77 	if (ret < 0) {
78 		LOG_ERR("Failed to add callback on '%s': %d", cfg->echo_gpios.port->name, ret);
79 		return -EIO;
80 	}
81 	return 0;
82 }
83 
hcsr04_init(const struct device * dev)84 static int hcsr04_init(const struct device *dev)
85 {
86 	const struct hcsr04_config *cfg = dev->config;
87 	struct hcsr04_data *data = dev->data;
88 	int ret;
89 
90 	k_sem_init(&data->sem, 0, 1);
91 
92 	ret = hcsr04_configure_gpios(cfg);
93 	if (ret < 0) {
94 		return ret;
95 	}
96 
97 	ret = hcsr04_configure_interrupt(cfg, data);
98 	if (ret < 0) {
99 		return ret;
100 	}
101 
102 	return 0;
103 }
104 
hcsr04_gpio_callback(const struct device * dev,struct gpio_callback * cb,uint32_t pins)105 static void hcsr04_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
106 {
107 	struct hcsr04_data *data = CONTAINER_OF(cb, struct hcsr04_data, gpio_cb);
108 	const struct hcsr04_config *cfg = data->dev->config;
109 
110 	if (gpio_pin_get(dev, cfg->echo_gpios.pin) == 1) {
111 		data->start_cycles = k_cycle_get_32();
112 	} else {
113 		atomic_set(&data->echo_high_cycles, k_cycle_get_32() - data->start_cycles);
114 		gpio_pin_interrupt_configure_dt(&cfg->echo_gpios, GPIO_INT_DISABLE);
115 		k_sem_give(&data->sem);
116 	}
117 }
118 
hcsr04_sample_fetch(const struct device * dev,enum sensor_channel chan)119 static int hcsr04_sample_fetch(const struct device *dev, enum sensor_channel chan)
120 {
121 	const struct hcsr04_config *cfg = dev->config;
122 	struct hcsr04_data *data = dev->data;
123 	int ret;
124 
125 	ret = gpio_pin_interrupt_configure_dt(&cfg->echo_gpios, GPIO_INT_EDGE_BOTH);
126 	if (ret < 0) {
127 		LOG_ERR("Failed to set configure echo pin as interrupt: %d", ret);
128 		return ret;
129 	}
130 
131 	/* Generate 10us trigger */
132 	ret = gpio_pin_set_dt(&cfg->trigger_gpios, 1);
133 	if (ret < 0) {
134 		LOG_ERR("Failed to set trigger pin: %d", ret);
135 		return ret;
136 	}
137 
138 	k_busy_wait(10);
139 
140 	ret = gpio_pin_set_dt(&cfg->trigger_gpios, 0);
141 	if (ret < 0) {
142 		LOG_ERR("Failed to set trigger pin: %d", ret);
143 		return ret;
144 	}
145 
146 	if (k_sem_take(&data->sem, K_MSEC(10)) != 0) {
147 		LOG_ERR("Echo signal was not received");
148 		return -EIO;
149 	}
150 	return 0;
151 }
152 
hcsr04_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)153 static int hcsr04_channel_get(const struct device *dev, enum sensor_channel chan,
154 	struct sensor_value *val)
155 {
156 	const struct hcsr04_data *data = dev->data;
157 	uint32_t distance_mm;
158 
159 	if (chan != SENSOR_CHAN_DISTANCE) {
160 		return -ENOTSUP;
161 	}
162 
163 	distance_mm = HC_SR04_MM_PER_MS * atomic_get(&data->echo_high_cycles) /
164 			hw_cycles_per_ms;
165 	return sensor_value_from_milli(val, distance_mm);
166 }
167 
168 static DEVICE_API(sensor, hcsr04_driver_api) = {
169 	.sample_fetch = hcsr04_sample_fetch,
170 	.channel_get = hcsr04_channel_get
171 };
172 
173 
174 #define HC_SR04_INIT(index)                                                           \
175 	static struct hcsr04_data hcsr04_data_##index = {                             \
176 		.dev = DEVICE_DT_INST_GET(index),                                     \
177 		.start_cycles = 0,                                                    \
178 		.echo_high_cycles = ATOMIC_INIT(0),                                   \
179 	};                                                                            \
180 	static struct hcsr04_config hcsr04_config_##index = {                         \
181 		.trigger_gpios = GPIO_DT_SPEC_INST_GET(index, trigger_gpios),         \
182 		.echo_gpios = GPIO_DT_SPEC_INST_GET(index, echo_gpios),               \
183 	};                                                                            \
184                                                                                       \
185 	SENSOR_DEVICE_DT_INST_DEFINE(index, &hcsr04_init, NULL, &hcsr04_data_##index, \
186 				&hcsr04_config_##index, POST_KERNEL,                  \
187 				CONFIG_SENSOR_INIT_PRIORITY, &hcsr04_driver_api);     \
188 
189 DT_INST_FOREACH_STATUS_OKAY(HC_SR04_INIT)
190