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
185 changes: 185 additions & 0 deletions capsules/extra/src/distance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// 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.
//!
//! 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: 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)
}
_ => {
// Command not supported.
CommandReturn::failure(ErrorCode::NOSUPPORT)
}
}
}

fn allocate_grant(&self, process_id: ProcessId) -> Result<(), kernel::process::Error> {
self.apps.enter(process_id, |_, _| {})
}
}
136 changes: 136 additions & 0 deletions capsules/extra/src/hc_sr04.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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 distance that can be measured by the calculated sensor in miliseconds.
cristianaprecup marked this conversation as resolved.
Show resolved Hide resolved
// As specified in the datasheet:
// https://www.handsontec.com/dataspecs/HC-SR04-Ultrasonic.pdf,
// the maximum time for the echo 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)
}
}
}

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.
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));
}
}
}
_ => {}
}
}
}
4 changes: 3 additions & 1 deletion capsules/extra/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Tock Contributors 2022.

#![forbid(unsafe_code)]
// #![forbid(unsafe_code)]
#![no_std]

pub mod test;
Expand Down Expand Up @@ -33,11 +33,13 @@ pub mod cycle_count;
pub mod dac;
pub mod date_time;
pub mod debug_process_restart;
pub mod distance;
pub mod eui64;
pub mod fm25cl;
pub mod ft6x06;
pub mod fxos8700cq;
pub mod gpio_async;
pub mod hc_sr04;
pub mod hd44780;
pub mod hmac;
pub mod hmac_sha256;
Expand Down
Loading
Loading