Re: [PATCH] HC-SR04 ultrasonic ranger IIO driver

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

 



On 06/06/16 19:40, johannes@xxxxxxxxxxxxxxxxx wrote:
> 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>
Why is this device registered as a trigger?  It looks like a device
to me in IIO terms.

The measure sysfs attribute is effectively a distance channel I think...

> ---
>  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");
> +
> 

--
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