1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
// hc-sr04: Raspberry Pi Rust driver for the HC-SR04 ultrasonic distance sensor.
// Copyright (C) 2022 Marco Radocchia
//
// 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 3 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.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see https://www.gnu.org/licenses/.
//
//! **HC-SR04** ultrasonic distance sensor driver.
//!
//! This crate provides a driver for the **HC-SR04**/**HC-SR04P** ultrasonic distance sensor on
//! *Raspberry Pi*, using [rppal](https://docs.rs/rppal/0.13.1/rppal/) to access Raspberry Pi's
//! GPIO.
//!
//! ## Examples
//!
//! Usage examples can be found in the
//! [examples](https://github.com/marcoradocchia/hc-sr04/tree/master/examples) folder.
//!
//! ## Measure distance
//! ```rust
//! use hc_sr04::{HcSr04, Unit};
//!
//! // Initialize driver.
//! let mut ultrasonic = HcSr04::new(
//! 24, // TRIGGER -> Gpio pin 24
//! 23, // ECHO -> Gpio pin 23
//! Some(23_f32) // Ambient temperature (if `None` defaults to 20.0C)
//! ).unwrap();
//!
//! // Perform distance measurement, specifying measuring unit of return value.
//! match ultrasonic.measure_distance(Unit::Meters).unwrap() {
//! Some(dist) => println!("Distance: {.2}m", dist),
//! None => println!("Object out of range"),
//! }
//! ```
//!
//! ## Calibrate measurement
//!
//! Distance measurement can be calibrated at runtime using the [`HcSr04::calibrate`] method that
//! this library exposes, passing the current ambient temperature as `f32`.
//!
//! ```rust
//! use hc_sr04::{HcSr04, Unit};
//!
//! // Initialize driver.
//! let mut ultrasonic = HcSr04::new(24, 23, None).unwrap();
//!
//! // Calibrate measurement with ambient temperature.
//! ultrasonic.calibrate(23_f32);
//!
//! // Perform distance measurement.
//! match ultrasonic.measure_distance(Unit::Centimeters).unwrap() {
//! Some(dist) => println!("Distance: {.1}cm", dist),
//! None => println!("Object out of range"),
//! }
//! ```
pub mod error;
use error::Error;
use rppal::gpio::{Gpio, InputPin, Level, OutputPin, Trigger};
use std::{
thread,
time::{Duration, Instant},
};
pub type Result<T> = std::result::Result<T, Error>;
/// Measuring unit (defaults to [`Unit::Meters`]).
pub enum Unit {
Millimeters,
Centimeters,
Decimeters,
Meters,
}
/// **HC-SR04** ultrasonic sensor on *Raspberry Pi*.
///
/// # Fileds
///
/// - `trig`: **TRIGGER** output GPIO pin
/// - `echo`: **ECHO** input GPIO pin
/// - `temp`: ambient **Temperature** measure calibration
/// - `sound_speed`: speed of sound given the ambient **Temperature**
/// - `timeout`: **ECHO** pin polling timeout, considering the maximum measuring range of 4m for
/// the sensor and the speed of sound given the ambient **Temperature**
#[derive(Debug)]
pub struct HcSr04 {
trig: OutputPin,
echo: InputPin,
sound_speed: f32,
timeout: Duration,
}
impl HcSr04 {
/// Perform `sound_speed` and `timeout` calculations required to calibrate the sensor,
/// based on **ambient temperature**.
fn calibration_calc(temp: f32) -> (f32, Duration) {
/// Speed of sound at 0C in m/s.
const SOUND_SPEED_0C: f32 = 331.3;
/// Increase speed of sound over temperature factor m/[sC].
const SOUND_SPEED_INC_OVER_TEMP: f32 = 0.606;
/// Maximum measuring range for HC-SR04 sensor in m.
const MAX_RANGE: f32 = 4.0;
// Speed of sound, depending on ambient temperature (if `temp` is `None`, default to 20C).
let sound_speed = SOUND_SPEED_0C + (SOUND_SPEED_INC_OVER_TEMP * temp);
// Polling timeout for **ECHO** pin: since max range for HC-SR04 is 4m, it doesn't make
// sense to wait longer than the time required to the ultrasonic sound wave to cover the
// max range distance. In other words, if the timeout is reached, the measurement was not
// successfull or the object is located too far away from the sensor in order to be
// detected.
let timeout = Duration::from_secs_f32(MAX_RANGE / sound_speed * 2.);
(sound_speed, timeout)
}
/// Initialize HC-SR04 sensor and register GPIO interrupt on `echo` pin for RisingEdge events
/// in order to poll it for bouncing UltraSonic waves detection.
///
/// # Parameters
///
/// - `trig`: **TRIGGER** output GPIO pin
/// - `echo`: **ECHO** input GPIO pin
/// - `temp`: ambient **TEMPERATURE** used for calibration (if `None` defaults to `20.0`)
pub fn new(trig: u8, echo: u8, temp: Option<f32>) -> Result<Self> {
let gpio = Gpio::new()?;
let mut echo = gpio.get(echo)?.into_input_pulldown();
echo.set_interrupt(Trigger::Both)?;
let (sound_speed, timeout) = Self::calibration_calc(temp.unwrap_or(20.));
Ok(Self {
trig: gpio.get(trig)?.into_output_low(),
echo,
sound_speed,
timeout,
})
}
/// Calibrate the sensor with the given **ambient temperature** (`temp`) expressed as *Celsius
/// degrees*.
pub fn calibrate(&mut self, temp: f32) {
(self.sound_speed, self.timeout) = Self::calibration_calc(temp);
}
/// Perform **distance measurement**.
///
/// Returns `Ok` variant if measurement succedes. Inner `Option` value is `None` if no object
/// is present within maximum measuring range (*4m*); otherwhise, on `Some` variant instead,
/// contained value represents distance expressed as the specified `unit`
/// (**unit of measure**).
pub fn measure_distance(&mut self, unit: Unit) -> Result<Option<f32>> {
self.trig.set_high();
thread::sleep(Duration::from_micros(10));
self.trig.set_low();
// Wait for the `RisingEdge` by ensuring the resulting level is `Level::High`.
while self.echo.poll_interrupt(false, None)? != Some(Level::High) {}
let instant = Instant::now();
// Wait for the `FallingEdge` by ensuring the resulting level is `Level::Low`.
if self.echo.poll_interrupt(false, Some(self.timeout))? != Some(Level::Low) {
// Timeout reached: object out of range (distance > maximum range).
return Ok(None);
}
// Distance in m.
let distance = (self.sound_speed * instant.elapsed().as_secs_f32()) / 2.;
Ok(Some(match unit {
Unit::Millimeters => distance * 1000.,
Unit::Centimeters => distance * 100.,
Unit::Decimeters => distance * 10.,
Unit::Meters => distance,
}))
}
}