[PATCH] HC-SR04 ultrasonic ranger IIO driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Johannes Thoma <johannes@xxxxxxxxxxxxxxxxx>

The HC-SR04 is an ultrasonic distance sensor attached to two GPIO
pins. The driver is based on the Industrial I/O (iio) subsystem and is
controlled via configfs and sysfs. It supports an (in theory) unlimited
number of HC-SR04 devices.

A datasheet to the device can be found at:

http://www.micropik.com/PDF/HCSR04.pdf
Signed-off-by: Johannes Thoma <johannes@xxxxxxxxxxxxxxxxx>
---
 MAINTAINERS                               |   7 +
 drivers/iio/Kconfig                       |   1 +
 drivers/iio/Makefile                      |   1 +
 drivers/iio/ultrasonic-distance/Kconfig   |  17 ++
 drivers/iio/ultrasonic-distance/Makefile  |   6 +
 drivers/iio/ultrasonic-distance/hc-sr04.c | 466 ++++++++++++++++++++++++++++++
 6 files changed, 498 insertions(+)
 create mode 100644 drivers/iio/ultrasonic-distance/Kconfig
 create mode 100644 drivers/iio/ultrasonic-distance/Makefile
 create mode 100644 drivers/iio/ultrasonic-distance/hc-sr04.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 7304d2e..3bd640e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5211,6 +5211,13 @@ W:	http://www.kernel.org/pub/linux/kernel/people/fseidel/hdaps/
 S:	Maintained
 F:	drivers/platform/x86/hdaps.c
 
+
+HC-SR04 ULTRASONIC DISTANCE SENSOR DRIVER
+M:	Johannes Thoma <johannes@xxxxxxxxxxxxxxxxx>
+S:	Maintained
+F:	drivers/iio/ultrasonic-distance/hc-sr04.c
+
+
 HDPVR USB VIDEO ENCODER DRIVER
 M:	Hans Verkuil <hverkuil@xxxxxxxxx>
 L:	linux-media@xxxxxxxxxxxxxxx
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 505e921..3c82aad 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -82,5 +82,6 @@ source "drivers/iio/potentiometer/Kconfig"
 source "drivers/iio/pressure/Kconfig"
 source "drivers/iio/proximity/Kconfig"
 source "drivers/iio/temperature/Kconfig"
+source "drivers/iio/ultrasonic-distance/Kconfig"
 
 endif # IIO
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index 20f6490..0f1c00c 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -32,3 +32,4 @@ obj-y += pressure/
 obj-y += proximity/
 obj-y += temperature/
 obj-y += trigger/
+obj-y += ultrasonic-distance/
diff --git a/drivers/iio/ultrasonic-distance/Kconfig b/drivers/iio/ultrasonic-distance/Kconfig
new file mode 100644
index 0000000..46e848d
--- /dev/null
+++ b/drivers/iio/ultrasonic-distance/Kconfig
@@ -0,0 +1,17 @@
+#
+# Ultrasonic range sensors
+#
+
+menu "Ultrasonic ranger devices"
+
+config HC_SR04
+	tristate "HC-SR04 ultrasonic distance sensor on GPIO"
+	select IIO_SW_TRIGGER
+	depends on GPIOLIB
+	help
+	  Say Y here if you want to support the HC-SR04 ultrasonic distance
+	  sensor which is attached on two runtime-configurable GPIO pins.
+
+  	  To compile this driver as a module, choose M here: the
+	  module will be called hc-sr04.
+endmenu
diff --git a/drivers/iio/ultrasonic-distance/Makefile b/drivers/iio/ultrasonic-distance/Makefile
new file mode 100644
index 0000000..1f01d50c
--- /dev/null
+++ b/drivers/iio/ultrasonic-distance/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for IIO proximity sensors
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_HC_SR04)		+= hc-sr04.o
diff --git a/drivers/iio/ultrasonic-distance/hc-sr04.c b/drivers/iio/ultrasonic-distance/hc-sr04.c
new file mode 100644
index 0000000..e7da37b
--- /dev/null
+++ b/drivers/iio/ultrasonic-distance/hc-sr04.c
@@ -0,0 +1,466 @@
+/*
+ * hc-sr04.c - Support for HC-SR04 ultrasonic range sensor
+ *
+ * Copyright (C) 2016 Johannes Thoma <johannes@xxxxxxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/* Precise measurements of time delta between sending a trigger signal
+ * to the HC-SR04 distance sensor and receiving the echo signal from
+ * the sensor back. This has to be precise in the usecs range. We
+ * use trigger interrupts to measure the signal, so no busy wait :)
+ *
+ * This supports an (in theory) unlimited number of HC-SR04 devices.
+ * It uses IIO software triggers to interface with userland.
+ *
+ * To configure a device do a
+ *
+ *    mkdir /sys/kernel/config/iio/triggers/hc-sr04/sensor0
+ *
+ * (you need to mount configfs to /sys/kernel/config first unless it isn't
+ * mounted already)
+ *
+ * Then configure the ECHO and TRIG pins (this also accepts symbolic names
+ * configured in the device tree)
+ *
+ *    echo 23 > /config/iio/triggers/hc-sr04/sensor0/trig_pin
+ *    echo 24 > /config/iio/triggers/hc-sr04/sensor0/echo_pin
+ *
+ * Then you can measure distance with:
+ *
+ *    cat /sys/devices/trigger0/measure
+ *
+ * (trigger0 is the device name as reported by
+ *  /config/iio/triggers/hc-sr04/sensor0/dev_name
+ *
+ * This reports the length of the ECHO signal in microseconds, which is
+ * related linearily to the distance measured.
+ *
+ * To convert to centimeters, multiply by 17150 and divide by 1000000 (air)
+ *
+ * DO NOT attach your HC-SR04's echo pin directly to the raspberry, since
+ * it runs with 5V while raspberry expects 3V on the GPIO inputs.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/timekeeping.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/sw_trigger.h>
+
+#define DEFAULT_TIMEOUT 1000
+
+enum hc_sr04_state {
+	DEVICE_IDLE,
+	DEVICE_TRIGGERED,
+	DEVICE_ECHO_RECEIVED
+};
+
+struct hc_sr04 {
+		/* the GPIOs of ECHO and TRIG */
+	struct gpio_desc *trig_desc;
+	struct gpio_desc *echo_desc;
+		/* Used to measure length of ECHO signal */
+	struct timeval time_triggered;
+	struct timeval time_echoed;
+		/* protects against starting multiple measurements */
+	struct mutex measurement_mutex;
+		/* Current state of measurement */
+	enum hc_sr04_state state;
+		/* Used by interrupt to wake measurement routine up */
+	wait_queue_head_t wait_for_echo;
+		/* timeout in ms, fail when no echo received within that time */
+	unsigned long timeout;
+		/* Our IIO interface */
+	struct iio_sw_trigger swt;
+		/* Used to compute device settle time */
+	struct timeval last_measurement;
+};
+
+static inline struct hc_sr04 *to_hc_sr04(struct config_item *item)
+{
+	struct iio_sw_trigger *trig = to_iio_sw_trigger(item);
+
+	return container_of(trig, struct hc_sr04, swt);
+}
+
+static irqreturn_t echo_received_irq(int irq, void *data)
+{
+	struct hc_sr04 *device = (struct hc_sr04 *)data;
+	int val;
+	struct timeval irq_tv;
+
+	do_gettimeofday(&irq_tv);
+
+	if (device->state != DEVICE_TRIGGERED)
+		return IRQ_HANDLED;
+
+	val = gpiod_get_value(device->echo_desc);
+	if (val == 1) {
+		device->time_triggered = irq_tv;
+	} else {
+		device->time_echoed = irq_tv;
+		device->state = DEVICE_ECHO_RECEIVED;
+		wake_up_interruptible(&device->wait_for_echo);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int do_measurement(struct hc_sr04 *device,
+			  long long *usecs_elapsed)
+{
+	long timeout;
+	int irq;
+	int ret;
+	struct timeval now;
+	long long time_since_last_measurement;
+
+	*usecs_elapsed = -1;
+
+	if (!device->echo_desc || !device->trig_desc) {
+		dev_dbg(&device->swt.trigger->dev, "Please configure GPIO pins first.\n");
+		return -EINVAL;
+	}
+	if (!mutex_trylock(&device->measurement_mutex))
+		return -EBUSY;
+
+	do_gettimeofday(&now);
+	if (device->last_measurement.tv_sec || device->last_measurement.tv_usec)
+		time_since_last_measurement =
+	(now.tv_sec - device->last_measurement.tv_sec) * 1000000 +
+	(now.tv_usec - device->last_measurement.tv_usec);
+	else
+		time_since_last_measurement = 60000;
+
+		/* wait 60 ms between measurements.
+		 * now, a while true ; do cat measure ; done should work
+		 */
+
+	if (time_since_last_measurement < 60000 &&
+	    time_since_last_measurement >= 0)
+		msleep(60 - (int)time_since_last_measurement / 1000);
+
+	irq = gpiod_to_irq(device->echo_desc);
+	if (irq < 0) {
+		ret = -EIO;
+		goto out_mutex;
+	}
+
+	ret = request_any_context_irq(irq, echo_received_irq,
+				      IRQF_SHARED | IRQF_TRIGGER_FALLING |
+				      IRQF_TRIGGER_RISING,
+				      "hc_sr04", device);
+
+	if (ret < 0)
+		goto out_mutex;
+
+	gpiod_set_value(device->trig_desc, 1);
+	usleep_range(10, 20);
+	device->state = DEVICE_TRIGGERED;
+	gpiod_set_value(device->trig_desc, 0);
+
+	ret = gpiochip_lock_as_irq(gpiod_to_chip(device->echo_desc),
+				   desc_to_gpio(device->echo_desc));
+	if (ret < 0)
+		goto out_irq;
+
+	timeout = wait_event_interruptible_timeout(
+			device->wait_for_echo,
+			device->state == DEVICE_ECHO_RECEIVED,
+			device->timeout * HZ / 1000);
+
+	device->state = DEVICE_IDLE;
+
+	if (timeout == 0) {
+		ret = -ETIMEDOUT;
+	} else if (timeout < 0) {
+		ret = timeout;
+	} else {
+		*usecs_elapsed =
+	(device->time_echoed.tv_sec - device->time_triggered.tv_sec) * 1000000 +
+	(device->time_echoed.tv_usec - device->time_triggered.tv_usec);
+		ret = 0;
+		do_gettimeofday(&device->last_measurement);
+	}
+	gpiochip_unlock_as_irq(gpiod_to_chip(device->echo_desc),
+			       desc_to_gpio(device->echo_desc));
+out_irq:
+	free_irq(irq, device);
+out_mutex:
+	mutex_unlock(&device->measurement_mutex);
+
+	return ret;
+}
+
+static ssize_t sysfs_do_measurement(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct hc_sr04 *sensor = dev_get_drvdata(dev);
+	long long usecs_elapsed;
+	int status;
+
+	status = do_measurement(sensor, &usecs_elapsed);
+
+	if (status < 0)
+		return status;
+
+	return sprintf(buf, "%lld\n", usecs_elapsed);
+}
+
+DEVICE_ATTR(measure, 0444, sysfs_do_measurement, NULL);
+
+static struct attribute *sensor_attrs[] = {
+	&dev_attr_measure.attr,
+	NULL,
+};
+
+static const struct attribute_group sensor_group = {
+	.attrs = sensor_attrs
+};
+
+static const struct attribute_group *sensor_groups[] = {
+	&sensor_group,
+	NULL
+};
+
+static ssize_t configure_pin(struct gpio_desc **desc, struct config_item *item,
+			     const char *buf, size_t len, struct device *dev)
+{
+	int err;
+	int echo;
+
+	if (*desc)
+		gpiod_put(*desc);
+
+	*desc = gpiod_get(dev, buf, GPIOD_ASIS);
+	if (IS_ERR(*desc)) {
+		err = PTR_ERR(*desc);
+		*desc = NULL;
+
+		if (err == -ENOENT) {	/* fallback: use GPIO numbers */
+			err = kstrtoint(buf, 10, &echo);
+			if (err < 0)
+				return -ENOENT;
+			*desc = gpio_to_desc(echo);
+			if (*desc)
+				return len;
+			return -ENOENT;
+		}
+
+		return err;
+	}
+	return len;
+}
+
+static ssize_t hc_sr04_echo_pin_store(struct config_item *item,
+				      const char *buf, size_t len)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+	ssize_t ret;
+	int err;
+
+	ret = configure_pin(&sensor->echo_desc, item, buf, len,
+			    &sensor->swt.trigger->dev);
+
+	if (ret >= 0 && sensor->echo_desc) {
+		err = gpiod_direction_input(sensor->echo_desc);
+		if (err < 0)
+			return err;
+	}
+	return ret;
+}
+
+static ssize_t hc_sr04_echo_pin_show(struct config_item *item,
+				     char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	if (sensor->echo_desc)
+		return sprintf(buf, "%d\n", desc_to_gpio(sensor->echo_desc));
+	return 0;
+}
+
+static ssize_t hc_sr04_trig_pin_store(struct config_item *item,
+				      const char *buf, size_t len)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+	ssize_t ret;
+	int err;
+
+	ret = configure_pin(&sensor->trig_desc, item, buf, len,
+			    &sensor->swt.trigger->dev);
+
+	if (ret >= 0 && sensor->trig_desc) {
+		err = gpiod_direction_output(sensor->trig_desc, 0);
+		if (err >= 0)
+			gpiod_set_value(sensor->trig_desc, 0);
+		else
+			return err;
+	}
+	return ret;
+}
+
+static ssize_t hc_sr04_trig_pin_show(struct config_item *item,
+				     char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	if (sensor->trig_desc)
+		return sprintf(buf, "%d\n", desc_to_gpio(sensor->trig_desc));
+	return 0;
+}
+
+static ssize_t hc_sr04_timeout_store(struct config_item *item,
+				     const char *buf, size_t len)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+	unsigned long t;
+	int ret;
+
+	ret = kstrtol(buf, 10, &t);
+	if (ret < 0)
+		return ret;
+
+	sensor->timeout = t;
+	return len;
+}
+
+static ssize_t hc_sr04_timeout_show(struct config_item *item,
+				    char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	return sprintf(buf, "%ld\n", sensor->timeout);
+}
+
+static ssize_t hc_sr04_dev_name_show(struct config_item *item,
+				     char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	return sprintf(buf, "%s", dev_name(&sensor->swt.trigger->dev));
+}
+
+CONFIGFS_ATTR(hc_sr04_, echo_pin);
+CONFIGFS_ATTR(hc_sr04_, trig_pin);
+CONFIGFS_ATTR(hc_sr04_, timeout);
+CONFIGFS_ATTR_RO(hc_sr04_, dev_name);
+
+static struct configfs_attribute *hc_sr04_config_attrs[] = {
+	&hc_sr04_attr_echo_pin,
+	&hc_sr04_attr_trig_pin,
+	&hc_sr04_attr_timeout,
+	&hc_sr04_attr_dev_name,
+	NULL
+};
+
+static struct config_item_type iio_hc_sr04_type = {
+	.ct_owner = THIS_MODULE,
+	.ct_attrs = hc_sr04_config_attrs
+};
+
+static int iio_trig_hc_sr04_set_state(struct iio_trigger *trig, bool state)
+{
+	return 0;
+}
+
+static const struct iio_trigger_ops iio_hc_sr04_trigger_ops = {
+	.owner = THIS_MODULE,
+	.set_trigger_state = iio_trig_hc_sr04_set_state,
+};
+
+static struct iio_sw_trigger *iio_trig_hc_sr04_probe(const char *name)
+{
+	struct hc_sr04 *sensor;
+	int ret;
+
+	sensor = kzalloc(sizeof(*sensor), GFP_KERNEL);
+	if (!sensor)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&sensor->measurement_mutex);
+	init_waitqueue_head(&sensor->wait_for_echo);
+	sensor->timeout = DEFAULT_TIMEOUT;
+
+	sensor->swt.trigger = iio_trigger_alloc("%s", name);
+	if (!sensor->swt.trigger) {
+		ret = -ENOMEM;
+		goto err_free_sensor;
+	}
+	iio_trigger_set_drvdata(sensor->swt.trigger, sensor);
+	sensor->swt.trigger->ops = &iio_hc_sr04_trigger_ops;
+	sensor->swt.trigger->dev.groups = sensor_groups;
+
+	ret = iio_trigger_register(sensor->swt.trigger);
+	if (ret)
+		goto err_free_trigger;
+
+	iio_swt_group_init_type_name(&sensor->swt, name, &iio_hc_sr04_type);
+	return &sensor->swt;
+
+err_free_trigger:
+	iio_trigger_free(sensor->swt.trigger);
+err_free_sensor:
+	kfree(sensor);
+
+	return ERR_PTR(ret);
+}
+
+static int iio_trig_hc_sr04_remove(struct iio_sw_trigger *swt)
+{
+	struct hc_sr04 *rip_sensor;
+
+	rip_sensor = iio_trigger_get_drvdata(swt->trigger);
+
+	iio_trigger_unregister(swt->trigger);
+
+	/* Wait for measurement to be finished. */
+	mutex_lock(&rip_sensor->measurement_mutex);
+
+	iio_trigger_free(swt->trigger);
+	kfree(rip_sensor);
+
+	return 0;
+}
+
+static const struct iio_sw_trigger_ops iio_trig_hc_sr04_ops = {
+	.probe          = iio_trig_hc_sr04_probe,
+	.remove         = iio_trig_hc_sr04_remove,
+};
+
+static struct iio_sw_trigger_type iio_trig_hc_sr04 = {
+	.name = "hc-sr04",
+	.owner = THIS_MODULE,
+	.ops = &iio_trig_hc_sr04_ops,
+};
+
+module_iio_sw_trigger_driver(iio_trig_hc_sr04);
+
+MODULE_AUTHOR("Johannes Thoma");
+MODULE_DESCRIPTION("Distance measurement for the HC-SR04 ultrasonic distance sensor");
+MODULE_LICENSE("GPL");
+
-- 
2.8.0-rc4

--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux