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

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

 



On Mon, Jun 6, 2016 at 11:40 AM,  <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>
> ---
>  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"

No reason to start another path for this driver. Please use
drivers/iio/proximity

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