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 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
// Licensed under the Apache License, Version 2.0 or the MIT License.
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Tock Contributors 2024.
//! HC-SR04 Ultrasonic Distance Sensor
//! Product Link: [HC-SR04 Product Page](https://www.sparkfun.com/products/15569)
//! Datasheet: [HC-SR04 Datasheet](https://www.handsontec.com/dataspecs/HC-SR04-Ultrasonic.pdf)
//!
//! HC-SR04 ultrasonic sensor provides a very low-cost and easy method of distance measurement. It measures distance using sonar,
//! an ultrasonic (well above human hearing) pulse (~40KHz) is transmitted from the unit and distance-to-target is determined by
//! measuring the time required for the echo return. This sensor offers excellent range accuracy and stable readings in an easy-to-use
//! package.
use core::cell::Cell;
use kernel::hil::gpio;
use kernel::hil::sensors::{self, Distance, DistanceClient};
use kernel::hil::time::Alarm;
use kernel::hil::time::{AlarmClient, ConvertTicks};
use kernel::utilities::cells::OptionalCell;
use kernel::ErrorCode;
/// Maximum duration for the echo pulse to be measured in milliseconds.
// As specified in the datasheet:
// https://www.handsontec.com/dataspecs/HC-SR04-Ultrasonic.pdf,
// the maximum time for the echo pulse to return is around 23 milliseconds
// for a maximum distance of approximately 4 meters under standard temperature
// and pressure conditions, but we use 38 milliseconds to account for variations
// in real-world conditions. We use a slightly higher the value to account for
// possible variations in measurement.
pub const MAX_ECHO_DELAY_MS: u32 = 50;
/// Speed of sound in air in mm/s.
// The speed of sound is approximately 343 meters per second, which
// translates to 343,000 millimeters per second. This value is used
// to calculate the distance based on the time it takes for the echo
// to return.
pub const SPEED_OF_SOUND: u32 = 343000;
#[derive(Copy, Clone, PartialEq)]
/// Status of the sensor.
pub enum Status {
/// Sensor is idle.
Idle,
/// Sending ultrasonic pulse.
TriggerPulse,
/// Interrupt on the rising edge.
EchoStart,
/// Interrupt on the falling edge.
EchoEnd,
}
/// HC-SR04 Ultrasonic Distance Sensor Driver
pub struct HcSr04<'a, A: Alarm<'a>> {
trig: &'a dyn gpio::Pin,
echo: &'a dyn gpio::InterruptPin<'a>,
alarm: &'a A,
start_time: Cell<u64>,
state: Cell<Status>,
distance_client: OptionalCell<&'a dyn sensors::DistanceClient>,
}
impl<'a, A: Alarm<'a>> HcSr04<'a, A> {
/// Create a new HC-SR04 driver.
pub fn new(
trig: &'a dyn kernel::hil::gpio::Pin,
echo: &'a dyn kernel::hil::gpio::InterruptPin<'a>,
alarm: &'a A,
) -> HcSr04<'a, A> {
// Setup and return struct.
HcSr04 {
trig,
echo,
alarm,
start_time: Cell::new(0),
state: Cell::new(Status::Idle),
distance_client: OptionalCell::empty(),
}
}
}
impl<'a, A: Alarm<'a>> Distance<'a> for HcSr04<'a, A> {
/// Set the client for distance measurement results.
fn set_client(&self, distance_client: &'a dyn DistanceClient) {
self.distance_client.set(distance_client);
}
/// Start a distance measurement.
fn read_distance(&self) -> Result<(), ErrorCode> {
if self.state.get() == Status::Idle {
self.state.set(Status::TriggerPulse);
self.trig.set();
// Setting the alarm to send the trigger pulse.
// According to the HC-SR04 datasheet, a 10 µs pulse should be sufficient
// to trigger the measurement. However, in practical tests, using this
// 10 µs value led to inaccurate measurements.
// We have chosen to use a 1 ms pulse instead because it provides stable
// operation and accurate measurements, even though it is slightly longer
// than the datasheet recommendation. While this adds a small delay to the
// triggering process, it does not significantly affect the overall performance
// of the sensor.
self.alarm
.set_alarm(self.alarm.now(), self.alarm.ticks_from_ms(1));
Ok(())
} else {
Err(ErrorCode::BUSY)
}
}
/// Get the maximum distance the sensor can measure in mm
fn get_maximum_distance(&self) -> u32 {
// The maximum distance is determined by the maximum pulse width the sensor can detect.
// As specified in the datasheet: https://www.handsontec.com/dataspecs/HC-SR04-Ultrasonic.pdf,
// the maximum measurable distance is approximately 4 meters.
// Convert this to millimeters.
4000
}
/// Get the minimum distance the sensor can measure in mm.
fn get_minimum_distance(&self) -> u32 {
// The minimum distance is determined by the minimum pulse width the sensor can detect.
// As specified in the datasheet: https://www.handsontec.com/dataspecs/HC-SR04-Ultrasonic.pdf,
// the minimum measurable distance is approximately 2 cm.
// Convert this to millimeters.
20
}
}
impl<'a, A: Alarm<'a>> AlarmClient for HcSr04<'a, A> {
/// Handle the alarm event.
fn alarm(&self) {
match self.state.get() {
Status::TriggerPulse => {
self.state.set(Status::EchoStart); // Update status to waiting for echo.
self.echo.enable_interrupts(gpio::InterruptEdge::RisingEdge); // Enable rising edge interrupt on echo pin.
self.trig.clear(); // Clear the trigger pulse.
self.alarm.set_alarm(
self.alarm.now(),
self.alarm.ticks_from_ms(MAX_ECHO_DELAY_MS),
); // Set alarm for maximum echo delay.
}
// Timeout for echo pulse.
Status::EchoStart => {
self.state.set(Status::Idle); // Update status to idle.
if let Some(distance_client) = self.distance_client.get() {
// NOACK indicates that no echo was received within the expected time.
distance_client.callback(Err(ErrorCode::NOACK));
}
}
_ => {}
}
}
}
impl<'a, A: Alarm<'a>> gpio::Client for HcSr04<'a, A> {
/// Handle the GPIO interrupt.
fn fired(&self) {
// Convert current ticks to microseconds using `ticks_to_us`,
// which handles the conversion based on the timer frequency.
let time = self.alarm.ticks_to_us(self.alarm.now()) as u64;
match self.state.get() {
Status::EchoStart => {
let _ = self.alarm.disarm(); // Disarm the alarm.
self.state.set(Status::EchoEnd); // Update status to waiting for echo end.
self.echo
.enable_interrupts(gpio::InterruptEdge::FallingEdge); // Enable falling edge interrupt on echo pin.
self.start_time.set(time); // Record start time when echo received.
}
Status::EchoEnd => {
let end_time = time; // Use a local variable for the end time.
self.state.set(Status::Idle); // Update status to idle.
let duration = end_time.wrapping_sub(self.start_time.get()) as u32; // Calculate pulse duration.
if duration > MAX_ECHO_DELAY_MS * 1000 {
// If the duration exceeds the maximum distance, return an error indicating invalid measurement.
// This means that the object is out of range or no valid echo was received.
if let Some(distance_client) = self.distance_client.get() {
distance_client.callback(Err(ErrorCode::INVAL));
}
} else {
// Calculate distance in millimeters based on the duration of the echo.
// The formula for calculating distance is:
// Distance = (duration (µs) * SPEED_OF_SOUND (mm/s)) / (2 * 1_000_000), where
// - `duration` is the time taken for the echo to travel to the object and back, in microseconds,
// - SPEED_OF_SOUND is the speed of sound in air, in millimeters per second.
// We divide by 2 because `duration` includes the round-trip time (to the object and back),
// and we divide by 1,000,000 to convert from microseconds to seconds.
//
// To avoid using 64-bit arithmetic (u64), we restructure this equation as:
// ((SPEED_OF_SOUND / 1000) * duration) / (2 * 1000).
// This rearrangement reduces the scale of intermediate values, keeping them within u32 limits:
// - SPEED_OF_SOUND is divided by 1000, reducing it to 343 (in mm/ms), and
// - duration remains in microseconds (µs).
// The final division by 2000 adjusts for the round trip and scales to the correct unit.
//
// This form is less intuitive, but it ensures all calculations stay within 32-bit size (u32).
// Given the HC-SR04 sensor's maximum `duration` of ~23,000 µs (datasheet limit), this u32 approach
// is sufficient for accurate distance calculations without risking overflow.
let distance = ((SPEED_OF_SOUND / 1000) * duration) / (2 * 1000);
if let Some(distance_client) = self.distance_client.get() {
distance_client.callback(Ok(distance));
}
}
}
_ => {}
}
}
}