/* * SRF04: ultrasonic sensor for distance measuring by using GPIOs * * Copyright (c) 2017 Andreas Klinger * * 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. * * For details about the device see: * http://www.robot-electronics.co.uk/htm/srf04tech.htm * * the measurement cycle as timing diagram looks like: * * +---+ * GPIO | | * trig: --+ +------------------------------------------------------ * ^ ^ * |<->| * udelay(10) * * ultra +-+ +-+ +-+ * sonic | | | | | | * burst: ---------+ +-+ +-+ +----------------------------------------- * . * ultra . +-+ +-+ +-+ * sonic . | | | | | | * echo: ----------------------------------+ +-+ +-+ +---------------- * . . * +------------------------+ * GPIO | | * echo: -------------------+ +--------------- * ^ ^ * interrupt interrupt * (ts_rising) (ts_falling) * |<---------------------->| * pulse time measured * --> one round trip of ultra sonic waves */ #include #include #include #include #include #include #include #include #include #include #include #include struct srf04_data { struct device *dev; struct gpio_desc *gpiod_trig; struct gpio_desc *gpiod_echo; struct mutex lock; int irqnr; ktime_t ts_rising; ktime_t ts_falling; struct completion rising; struct completion falling; }; static irqreturn_t srf04_handle_irq(int irq, void *dev_id) { struct iio_dev *indio_dev = dev_id; struct srf04_data *data = iio_priv(indio_dev); ktime_t now = ktime_get(); if (gpiod_get_value(data->gpiod_echo)) { data->ts_rising = now; complete(&data->rising); } else { data->ts_falling = now; complete(&data->falling); } return IRQ_HANDLED; } static int srf04_read(struct srf04_data *data) { int ret; ktime_t ktime_dt; u64 dt_ns; u32 time_ns, distance_mm; /* * just one read-echo-cycle can take place at a time * ==> lock against concurrent reading calls */ mutex_lock(&data->lock); reinit_completion(&data->rising); reinit_completion(&data->falling); gpiod_set_value(data->gpiod_trig, 1); udelay(10); gpiod_set_value(data->gpiod_trig, 0); /* it cannot take more than 20 ms */ ret = wait_for_completion_killable_timeout(&data->rising, HZ/50); if (ret < 0) { mutex_unlock(&data->lock); return ret; } else if (ret == 0) { mutex_unlock(&data->lock); return -ETIMEDOUT; } ret = wait_for_completion_killable_timeout(&data->falling, HZ/50); if (ret < 0) { mutex_unlock(&data->lock); return ret; } else if (ret == 0) { mutex_unlock(&data->lock); return -ETIMEDOUT; } ktime_dt = ktime_sub(data->ts_falling, data->ts_rising); mutex_unlock(&data->lock); dt_ns = ktime_to_ns(ktime_dt); /* * measuring more than 3 meters is beyond the capabilities of * the sensor * ==> filter out invalid results for not measuring echos of * another us sensor * * formula: * distance 3 m * time = ---------- = --------- = 9404389 ns * speed 319 m/s * * using a minimum speed at -20 °C of 319 m/s */ if (dt_ns > 9404389) return -EIO; time_ns = dt_ns; /* * the speed as function of the temperature is approximately: * * speed = 331,5 + 0,6 * Temp * with Temp in °C * and speed in m/s * * use 343 m/s as ultrasonic speed at 20 °C here in absence of the * temperature * * therefore: * time 343 * distance = ------ * ----- * 10^6 2 * with time in ns * and distance in mm (one way) * * because we limit to 3 meters the multiplication with 343 just * fits into 32 bit */ distance_mm = time_ns * 343 / 2000000; return distance_mm; } static int srf04_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *channel, int *val, int *val2, long info) { struct srf04_data *data = iio_priv(indio_dev); int ret; if (channel->type != IIO_DISTANCE) return -EINVAL; switch (info) { case IIO_CHAN_INFO_RAW: ret = srf04_read(data); if (ret < 0) return ret; *val = ret; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: /* * theoretical maximum resolution is 3 mm * 1 LSB is 1 mm */ *val = 0; *val2 = 1000; return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } } static const struct iio_info srf04_iio_info = { .driver_module = THIS_MODULE, .read_raw = srf04_read_raw, }; static const struct iio_chan_spec srf04_chan_spec[] = { { .type = IIO_DISTANCE, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), }, }; static int srf04_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct srf04_data *data; struct iio_dev *indio_dev; int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(struct srf04_data)); if (!indio_dev) { dev_err(dev, "failed to allocate IIO device\n"); return -ENOMEM; } data = iio_priv(indio_dev); data->dev = dev; mutex_init(&data->lock); init_completion(&data->rising); init_completion(&data->falling); data->gpiod_trig = devm_gpiod_get(dev, "trig", GPIOD_OUT_LOW); if (IS_ERR(data->gpiod_trig)) { dev_err(dev, "failed to get trig-gpios: err=%ld\n", PTR_ERR(data->gpiod_trig)); return PTR_ERR(data->gpiod_trig); } data->gpiod_echo = devm_gpiod_get(dev, "echo", GPIOD_IN); if (IS_ERR(data->gpiod_echo)) { dev_err(dev, "failed to get echo-gpios: err=%ld\n", PTR_ERR(data->gpiod_echo)); return PTR_ERR(data->gpiod_echo); } if (gpiod_cansleep(data->gpiod_echo)) { dev_err(data->dev, "cansleep-GPIOs not supported\n"); return -ENODEV; } data->irqnr = gpiod_to_irq(data->gpiod_echo); if (data->irqnr < 0) { dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr); return data->irqnr; } ret = devm_request_irq(dev, data->irqnr, srf04_handle_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, pdev->name, indio_dev); if (ret < 0) { dev_err(data->dev, "request_irq: %d\n", ret); return ret; } platform_set_drvdata(pdev, indio_dev); indio_dev->name = "srf04"; indio_dev->dev.parent = &pdev->dev; indio_dev->info = &srf04_iio_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = srf04_chan_spec; indio_dev->num_channels = ARRAY_SIZE(srf04_chan_spec); return devm_iio_device_register(dev, indio_dev); } static const struct of_device_id of_srf04_match[] = { { .compatible = "devantech,srf04", }, {}, }; MODULE_DEVICE_TABLE(of, of_srf04_match); static struct platform_driver srf04_driver = { .probe = srf04_probe, .driver = { .name = "srf04-gpio", .of_match_table = of_srf04_match, }, }; module_platform_driver(srf04_driver); MODULE_AUTHOR("Andreas Klinger "); MODULE_DESCRIPTION("SRF04 ultrasonic sensor for distance measuring using GPIOs"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:srf04");