You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
5.3 KiB
189 lines
5.3 KiB
/* |
|
* Copyright (c) 2024 Adrien Leravat |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT hc_sr04 |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/sensor.h> |
|
#include <zephyr/logging/log.h> |
|
|
|
LOG_MODULE_REGISTER(HC_SR04, CONFIG_SENSOR_LOG_LEVEL); |
|
|
|
#define HC_SR04_MM_PER_MS 171 |
|
|
|
static const uint32_t hw_cycles_per_ms = sys_clock_hw_cycles_per_sec() / 1000; |
|
|
|
struct hcsr04_data { |
|
const struct device *dev; |
|
struct gpio_callback gpio_cb; |
|
struct k_sem sem; |
|
uint32_t start_cycles; |
|
atomic_t echo_high_cycles; |
|
}; |
|
|
|
struct hcsr04_config { |
|
struct gpio_dt_spec trigger_gpios; |
|
struct gpio_dt_spec echo_gpios; |
|
}; |
|
|
|
static void hcsr04_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins); |
|
|
|
static int hcsr04_configure_gpios(const struct hcsr04_config *cfg) |
|
{ |
|
int ret; |
|
|
|
if (!gpio_is_ready_dt(&cfg->trigger_gpios)) { |
|
LOG_ERR("GPIO '%s' not ready", cfg->trigger_gpios.port->name); |
|
return -ENODEV; |
|
} |
|
ret = gpio_pin_configure_dt(&cfg->trigger_gpios, GPIO_OUTPUT_LOW); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to configure '%s' as output: %d", cfg->trigger_gpios.port->name, |
|
ret); |
|
return ret; |
|
} |
|
|
|
if (!gpio_is_ready_dt(&cfg->echo_gpios)) { |
|
LOG_ERR("GPIO '%s' not ready", cfg->echo_gpios.port->name); |
|
return -ENODEV; |
|
} |
|
ret = gpio_pin_configure_dt(&cfg->echo_gpios, GPIO_INPUT); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to configure '%s' as output: %d", cfg->echo_gpios.port->name, ret); |
|
return ret; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int hcsr04_configure_interrupt(const struct hcsr04_config *cfg, struct hcsr04_data *data) |
|
{ |
|
int ret; |
|
|
|
/* Disable initially to avoid spurious interrupts. */ |
|
ret = gpio_pin_interrupt_configure(cfg->echo_gpios.port, cfg->echo_gpios.pin, |
|
GPIO_INT_DISABLE); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to configure '%s' as interrupt: %d", cfg->echo_gpios.port->name, |
|
ret); |
|
return -EIO; |
|
} |
|
gpio_init_callback(&data->gpio_cb, &hcsr04_gpio_callback, BIT(cfg->echo_gpios.pin)); |
|
ret = gpio_add_callback(cfg->echo_gpios.port, &data->gpio_cb); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to add callback on '%s': %d", cfg->echo_gpios.port->name, ret); |
|
return -EIO; |
|
} |
|
return 0; |
|
} |
|
|
|
static int hcsr04_init(const struct device *dev) |
|
{ |
|
const struct hcsr04_config *cfg = dev->config; |
|
struct hcsr04_data *data = dev->data; |
|
int ret; |
|
|
|
k_sem_init(&data->sem, 0, 1); |
|
|
|
ret = hcsr04_configure_gpios(cfg); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
ret = hcsr04_configure_interrupt(cfg, data); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void hcsr04_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) |
|
{ |
|
struct hcsr04_data *data = CONTAINER_OF(cb, struct hcsr04_data, gpio_cb); |
|
const struct hcsr04_config *cfg = data->dev->config; |
|
|
|
if (gpio_pin_get(dev, cfg->echo_gpios.pin) == 1) { |
|
data->start_cycles = k_cycle_get_32(); |
|
} else { |
|
atomic_set(&data->echo_high_cycles, k_cycle_get_32() - data->start_cycles); |
|
gpio_pin_interrupt_configure_dt(&cfg->echo_gpios, GPIO_INT_DISABLE); |
|
k_sem_give(&data->sem); |
|
} |
|
} |
|
|
|
static int hcsr04_sample_fetch(const struct device *dev, enum sensor_channel chan) |
|
{ |
|
const struct hcsr04_config *cfg = dev->config; |
|
struct hcsr04_data *data = dev->data; |
|
int ret; |
|
|
|
ret = gpio_pin_interrupt_configure_dt(&cfg->echo_gpios, GPIO_INT_EDGE_BOTH); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to set configure echo pin as interrupt: %d", ret); |
|
return ret; |
|
} |
|
|
|
/* Generate 10us trigger */ |
|
ret = gpio_pin_set_dt(&cfg->trigger_gpios, 1); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to set trigger pin: %d", ret); |
|
return ret; |
|
} |
|
|
|
k_busy_wait(10); |
|
|
|
ret = gpio_pin_set_dt(&cfg->trigger_gpios, 0); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to set trigger pin: %d", ret); |
|
return ret; |
|
} |
|
|
|
if (k_sem_take(&data->sem, K_MSEC(10)) != 0) { |
|
LOG_ERR("Echo signal was not received"); |
|
return -EIO; |
|
} |
|
return 0; |
|
} |
|
|
|
static int hcsr04_channel_get(const struct device *dev, enum sensor_channel chan, |
|
struct sensor_value *val) |
|
{ |
|
const struct hcsr04_data *data = dev->data; |
|
uint32_t distance_mm; |
|
|
|
if (chan != SENSOR_CHAN_DISTANCE) { |
|
return -ENOTSUP; |
|
} |
|
|
|
distance_mm = HC_SR04_MM_PER_MS * atomic_get(&data->echo_high_cycles) / |
|
hw_cycles_per_ms; |
|
return sensor_value_from_milli(val, distance_mm); |
|
} |
|
|
|
static DEVICE_API(sensor, hcsr04_driver_api) = { |
|
.sample_fetch = hcsr04_sample_fetch, |
|
.channel_get = hcsr04_channel_get |
|
}; |
|
|
|
|
|
#define HC_SR04_INIT(index) \ |
|
static struct hcsr04_data hcsr04_data_##index = { \ |
|
.dev = DEVICE_DT_INST_GET(index), \ |
|
.start_cycles = 0, \ |
|
.echo_high_cycles = ATOMIC_INIT(0), \ |
|
}; \ |
|
static struct hcsr04_config hcsr04_config_##index = { \ |
|
.trigger_gpios = GPIO_DT_SPEC_INST_GET(index, trigger_gpios), \ |
|
.echo_gpios = GPIO_DT_SPEC_INST_GET(index, echo_gpios), \ |
|
}; \ |
|
\ |
|
SENSOR_DEVICE_DT_INST_DEFINE(index, &hcsr04_init, NULL, &hcsr04_data_##index, \ |
|
&hcsr04_config_##index, POST_KERNEL, \ |
|
CONFIG_SENSOR_INIT_PRIORITY, &hcsr04_driver_api); \ |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(HC_SR04_INIT)
|
|
|