Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HC_SR04 Distance Sensor Driver #15

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions capsules/core/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub enum NUM {
SoundPressure = 0x60006,
AirQuality = 0x60007,
Pressure = 0x60008,
Distance = 0x60009,

// Sensor ICs
Tsl2561 = 0x70000,
Expand Down
2 changes: 2 additions & 0 deletions capsules/extra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ These implement a driver to setup and read various physical sensors.
- **[STM32 Temperature](src/temperature_stm.rs)**: Analog STM32 temperature
sensor.
- **[TSL2561](src/tsl2561.rs)**: Light sensor.
- **[HC-SR04](src/hc_sr04.rs)**: Distance sensor
cristianaprecup marked this conversation as resolved.
Show resolved Hide resolved

These drivers provide support for various ICs.

Expand Down Expand Up @@ -134,6 +135,7 @@ These provide common and better abstractions for userspace.
- **[Temperature](src/temperature.rs)**: Query temperature sensors.
- **[Text Screen](src/text_screen.rs)**: Text-based displays.
- **[Touch](src/touch.rs)**: User touch panels.
- **[Distance](src/distance.rs)**: Distance sensor.


Virtualized Sensor Capsules for Userspace
Expand Down
195 changes: 195 additions & 0 deletions capsules/extra/src/distance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// Licensed under the Apache License, Version 2.0 or the MIT License.
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Tock Contributors 2024.

//! Provides userspace with access to distance sensor.
//!
//! Userspace Interface
//! -------------------
//!
//! ### `subscribe` System Call
//!
//! The `subscribe` system call supports the single `subscribe_number` zero,
//! which is used to provide a callback that will return back the result of
//! a distance sensor reading.
//! The `subscribe` call return codes indicate the following:
//!
//! * `Ok(())`: the callback has been successfully been configured.
//! * `ENOSUPPORT`: Invalid `subscribe_number`.
//! * `NOMEM`: No sufficient memory available.
//! * `INVAL`: Invalid address of the buffer or other error.
//!
//! ### `command` System Call
//!
//! The `command` system call supports one argument `cmd` which is used to
//! specify the specific operation. Currently, the following commands are supported:
//!
//! * `0`: check whether the driver exists.
//! * `1`: read the distance.
//! * `2`: get the minimum distance that the sensor can measure based on the datasheet.
//! * `3`: get the maximum distance that the sensor can measure based on the datasheet.
//!
//! The possible returns from the `command` system call indicate the following:
//!
//! * `Ok(())`: The operation has been successful.
//! * `ENOSUPPORT`: Invalid `cmd`.
//! * `NOMEM`: Insufficient memory available.
//! * `INVAL`: Invalid address of the buffer or other error.
//!
//! Usage
//! -----
//!
//! You need a device that provides the `hil::sensors::Distance` trait.
//! Here is an example of how to set up a distance sensor with the HC-SR04.
//!
//! ```rust,ignore
//! # use kernel::static_init;
//!
//! let trig_pin = peripherals.pins.get_pin();
//! let echo_pin = peripherals.pins.get_pin();
//! echo_pin.make_input();
//! trig_pin.make_output();
//!
//! let virtual_alarm_hc_sr04 = static_init!(
//! VirtualMuxAlarm<'static, RPTimer>,
//! VirtualMuxAlarm::new(mux_alarm)
//! );
//! virtual_alarm_hc_sr04.setup();
//!
//! let hc_sr04 = static_init!(
//! hc_sr04::HcSr04<VirtualMuxAlarm<'static, RPTimer>>,
//! hc_sr04::HcSr04::new(trig_pin, echo_pin, virtual_alarm_hc_sr04)
//! );
//! virtual_alarm_hc_sr04.set_alarm_client(hc_sr04);
//!
//! echo_pin.set_client(hc_sr04);
//!
//! let distance_sensor = static_init!(
//! distance::DistanceSensor<
//! 'static,
//! hc_sr04::HcSr04<'static, VirtualMuxAlarm<'static, RPTimer>>,
//! >,
//! distance::DistanceSensor::new(
//! hc_sr04,
//! board_kernel.create_grant(distance::DRIVER_NUM, &memory_allocation_capability)
//! )
//! );
//! hc_sr04.set_client(distance_sensor);
//! ```

use core::cell::Cell;

use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
use kernel::hil;
use kernel::syscall::{CommandReturn, SyscallDriver};
use kernel::{ErrorCode, ProcessId};

/// Syscall driver number.
use capsules_core::driver;
pub const DRIVER_NUM: usize = driver::NUM::Distance as usize;

#[derive(Default)]
pub struct App {
subscribed: bool,
}

pub struct DistanceSensor<'a, T: hil::sensors::Distance<'a>> {
driver: &'a T,
apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
busy: Cell<bool>,
}

impl<'a, T: hil::sensors::Distance<'a>> DistanceSensor<'a, T> {
pub fn new(
driver: &'a T,
grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
) -> DistanceSensor<'a, T> {
DistanceSensor {
driver,
apps: grant,
busy: Cell::new(false),
}
}

fn enqueue_command(&self, processid: ProcessId) -> CommandReturn {
self.apps
.enter(processid, |app, _| {
// Unconditionally mark this client as subscribed so it will get
// a callback when we get the distance reading.
app.subscribed = true;

// If we do not already have an ongoing read, start one now.
if !self.busy.get() {
self.busy.set(true);
match self.driver.read_distance() {
Ok(()) => CommandReturn::success(),
Err(e) => CommandReturn::failure(e),
}
} else {
// Just return success and we will get the upcall when the
// distance read is ready.
CommandReturn::success()
}
})
.unwrap_or_else(|err| CommandReturn::failure(err.into()))
}
}

impl<'a, T: hil::sensors::Distance<'a>> hil::sensors::DistanceClient for DistanceSensor<'a, T> {
fn callback(&self, distance_val: Result<u32, ErrorCode>) {
// We completed the operation so we clear the busy flag in case we get
// another measurement request.
self.busy.set(false);

// Return the distance reading to any waiting client.
if let Ok(distance_val) = distance_val {
for cntr in self.apps.iter() {
cntr.enter(|app, upcalls| {
if app.subscribed {
app.subscribed = false;
upcalls
.schedule_upcall(0, (distance_val as usize, 0, 0))
.ok();
}
});
}
}
}
}

impl<'a, T: hil::sensors::Distance<'a>> SyscallDriver for DistanceSensor<'a, T> {
fn command(
&self,
command_num: usize,
_: usize,
_: usize,
processid: ProcessId,
) -> CommandReturn {
match command_num {
0 => {
// Driver existence check.
CommandReturn::success()
}
1 => {
// Read distance.
self.enqueue_command(processid)
}
2 => {
// Get minimum distance.
CommandReturn::success_u32(self.driver.get_minimum_distance_mm())
}
3 => {
// Get maximum distance.
CommandReturn::success_u32(self.driver.get_maximum_distance_mm())
}
_ => {
// Command not supported.
CommandReturn::failure(ErrorCode::NOSUPPORT)
}
}
}

fn allocate_grant(&self, process_id: ProcessId) -> Result<(), kernel::process::Error> {
self.apps.enter(process_id, |_, _| {})
}
}
158 changes: 158 additions & 0 deletions capsules/extra/src/hc_sr04.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Licensed under the Apache License, Version 2.0 or the MIT License.
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Tock Contributors 2024.

use core::cell::Cell;

use kernel::hil::gpio::{Client, InterruptEdge};
use kernel::hil::sensors::{self, Distance, DistanceClient};
use kernel::hil::time::{Alarm, Ticks};
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 38 milliseconds
// for a maximum distance of approximately 4 meters. We use a slightly
// higher value to account for possible variations in measurement.
pub const MAX_DISTANCE_ECHO: 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;

/// Syscall driver number.
use capsules_core::driver;
pub const DRIVER_NUM: usize = driver::NUM::Distance as usize;

#[derive(Copy, Clone, PartialEq)]
/// Status of the sensor.
pub enum Status {
Idle, // Sensor is idle.
TriggerPulse, // Sending ultrasonic pulse.
EchoStart, // Interrupt on the rising edge.
EchoEnd, // Interrupt on the falling edge.
}

/// HC-SR04 Ultrasonic Distance Sensor Driver
pub struct HcSr04<'a, A: Alarm<'a>> {
trig: &'a dyn kernel::hil::gpio::Pin,
echo: &'a dyn kernel::hil::gpio::InterruptPin<'a>,
alarm: &'a A,
start_time: Cell<u32>,
end_time: Cell<u32>,
status: 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),
end_time: Cell::new(0),
status: 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.status.get() == Status::Idle {
self.status.set(Status::TriggerPulse);
self.trig.set(); // Send the trigger pulse.
self.alarm
.set_alarm(self.alarm.now(), self.alarm.ticks_from_ms(10));
Ok(())
} else {
Err(ErrorCode::BUSY)
}
}
/// Get the maximum distance the sensor can measure in mm
fn get_maximum_distance_mm(&self) -> u32 {
cristianaprecup marked this conversation as resolved.
Show resolved Hide resolved
// 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_mm(&self) -> u32 {
cristianaprecup marked this conversation as resolved.
Show resolved Hide resolved
// 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.status.get() {
Status::TriggerPulse => {
self.trig.clear(); // Clear the trigger pulse.
self.echo.enable_interrupts(InterruptEdge::RisingEdge); // Enable rising edge interrupt on echo pin.
self.status.set(Status::EchoStart); // Update status to waiting for echo.
}
_ => {}
}
}
}

impl<'a, A: Alarm<'a>> Client for HcSr04<'a, A> {
/// Handle the GPIO interrupt.
fn fired(&self) {
let time = self.alarm.now().into_u32();
match self.status.get() {
Status::EchoStart => {
self.start_time.set(time); // Record start time when echo received.
self.echo.enable_interrupts(InterruptEdge::FallingEdge); // Enable falling edge interrupt on echo pin.
self.status.set(Status::EchoEnd); // Update status to waiting for echo end.
}
Status::EchoEnd => {
self.end_time.set(time); // Record end time when echo ends.
self.status.set(Status::Idle); // Update status to idle.
let duration = self.end_time.get().wrapping_sub(self.start_time.get()); // Calculate pulse duration.
if duration > self.alarm.ticks_from_ms(MAX_DISTANCE_ECHO).into_u32() {
// If duration exceeds the maximum distance, return an error.
if let Some(distance_client) = self.distance_client.get() {
distance_client.callback(Err(ErrorCode::INVAL));
}
} else {
// Calculate distance in milimeters 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 the duration includes the round trip time (to the object and back) and
// we divide by 1_000_000 to convert the duration from microseconds to seconds.
let distance = duration * SPEED_OF_SOUND / (2 * 1_000_000);
cristianaprecup marked this conversation as resolved.
Show resolved Hide resolved
if let Some(distance_client) = self.distance_client.get() {
distance_client.callback(Ok(distance));
}
}
}
_ => {}
}
}
}
Loading
Loading