com.diozero.devices.HCSR04 Maven / Gradle / Ivy
The newest version!
package com.diozero.devices;
/*-
* #%L
* Organisation: diozero
* Project: diozero - Core
* Filename: HCSR04.java
*
* This file is part of the diozero project. More information about this project
* can be found at https://www.diozero.com/.
* %%
* Copyright (C) 2016 - 2024 diozero
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* #L%
*/
import org.tinylog.Logger;
import com.diozero.api.*;
import com.diozero.internal.spi.GpioDeviceFactoryInterface;
import com.diozero.sbc.DeviceFactoryHelper;
import com.diozero.util.SleepUtil;
/**
* User's manual:
* https://docs.google.com/document/d/1Y-yZnNhMYy7rwhAgyL_pfa39RsB-x2qR4vP8saG73rE/edit#
* Product specification:
* http://www.micropik.com/PDF/HCSR04.pdf
*
* Provides 2cm - 400cm non-contact measurement function, the ranging accuracy
* can reach to 3mm You only need to supply a short 10uS pulse to the trigger
* input to start the ranging, and then the module will send out an 8 cycle
* burst of ultrasound at 40 kHz and raise its echo. The Echo is a distance
* object that is pulse width and the range in proportion. We suggest to use over
* 60ms measurement cycle, in order to prevent trigger signal to the echo signal
*/
public class HCSR04 implements DistanceSensorInterface {
// Spec says #10us pulse (min) = 10,000 ns
private static final int PULSE_NS = 10_000;
private static final double MAX_DISTANCE_CM = 400; // Max distance measurement
private static final double SPEED_OF_SOUND_CM_PER_S = 34029; // Approx Speed of Sound at sea level and 15 degC
// Calculate the max time (in ns) that the echo pulse stays high
private static final int MAX_ECHO_TIME_NS = (int) Math.round(MAX_DISTANCE_CM * 2 * SleepUtil.NS_IN_SEC / SPEED_OF_SOUND_CM_PER_S);
private DigitalOutputDevice trigger;
private DigitalInputDeviceInterface echo;
private DigitalInputOutputDevice triggerAndEcho;
/**
* Initialise GPIO to echo and trigger pins
*
* @param triggerGpioNum GPIO connected to the HC-SR04 trigger pin
* @param echoGpioNum GPIO connected to the HC-SR04 echo pin
* @throws RuntimeIOException if an I/O error occurs
*/
public HCSR04(int triggerGpioNum, int echoGpioNum) throws RuntimeIOException {
this(DeviceFactoryHelper.getNativeDeviceFactory(),triggerGpioNum, echoGpioNum);
}
/**
* Initialise GPIO to echo and trigger pins
*
* @param deviceFactory the device factory to create the pins with
* @param triggerGpioNum GPIO connected to the HC-SR04 trigger pin
* @param echoGpioNum GPIO connected to the HC-SR04 echo pin
* @throws RuntimeIOException if an I/O error occurs
*/
public HCSR04(GpioDeviceFactoryInterface deviceFactory, int triggerGpioNum, int echoGpioNum) {
if (triggerGpioNum != echoGpioNum) {
// Define device for trigger pin at HCSR04
trigger = new DigitalOutputDevice(deviceFactory, triggerGpioNum, true, false);
// Define device for echo pin at HCSR04
echo = new DigitalInputDevice(deviceFactory, echoGpioNum, GpioPullUpDown.NONE, GpioEventTrigger.BOTH);
} else {
triggerAndEcho = new DigitalInputOutputDevice(deviceFactory, triggerGpioNum, DeviceMode.DIGITAL_OUTPUT);
echo = triggerAndEcho;
}
// Sleep for 20 ms - let the device settle?
SleepUtil.sleepMillis(20);
}
/**
* Send a pulse to HCSR04 and compute the echo to obtain distance
*
* @return distance in cm
* @throws RuntimeIOException if an I/O error occurs
*/
@Override
public float getDistanceCm() throws RuntimeIOException {
long start = System.nanoTime();
if (triggerAndEcho == null) {
// Send a pulse trigger of 10 us duration
trigger.setValue(true);
SleepUtil.busySleep(PULSE_NS);// wait 10 us (10,000ns)
trigger.setValue(false);
} else {
triggerAndEcho.setMode(DeviceMode.DIGITAL_OUTPUT);
// Send a pulse trigger of 10 us duration
triggerAndEcho.setValue(true);
SleepUtil.busySleep(PULSE_NS);// wait 10 us (10,000ns)
triggerAndEcho.setValue(false);
triggerAndEcho.setMode(DeviceMode.DIGITAL_INPUT);
}
// Need to include as little code as possible here to avoid missing pin state changes
while (! echo.getValue()) {
if (System.nanoTime() - start > 500_000_000) {
Logger.error("Timeout exceeded waiting for echo to go high");
return -1;
}
}
long echo_on_time = System.nanoTime();
while (echo.getValue()) {
if (System.nanoTime() - echo_on_time > 500_000_000) {
Logger.error("Timeout exceeded waiting for echo to go low");
return -1;
}
}
long echo_off_time = System.nanoTime();
Logger.info("Time from echo on to echo off = {}ns, max allowable time={}ns",
Long.valueOf(echo_off_time - echo_on_time), Long.valueOf(MAX_ECHO_TIME_NS));
double ping_duration_s = (echo_off_time - echo_on_time) / (double) SleepUtil.NS_IN_SEC;
// Distance = velocity * time taken
// Half the ping duration as it is the time to the object and back
double distance = SPEED_OF_SOUND_CM_PER_S * (ping_duration_s / 2.0);
if (distance > MAX_DISTANCE_CM) {
distance = MAX_DISTANCE_CM;
}
return (float)distance;
}
/**
* Free device GPIOs
*/
@Override
public void close() {
Logger.trace("close()");
if (triggerAndEcho == null) {
if (trigger != null) { trigger.close(); }
if (echo != null) { echo.close(); }
} else {
triggerAndEcho.close();
}
}
}