Skip to main content

Crate urm37

Crate urm37 

Source
Expand description

§urm37

no_std embedded driver for the DFRobot URM37 V4.0 ultrasonic distance sensor.

DFRobot URM37 V4.0

This crate provides a platform-agnostic driver supporting all sensor interface modes: UART (sync & async), PWM trigger, and analog ADC.

  • No allocations: Stack-only, suitable for embedded systems with limited memory
  • HAL-agnostic: Works with any embedded-io / embedded-hal implementation
  • Feature-gated: Include only what you need
  • Comprehensive: EEPROM configuration, temperature reading, multiple output modes
  • Tested: 45 unit and integration tests covering all protocol operations

§Supported Modes

ModeFeatureTraitsUse Case
Synchronous UARTblockingembedded-io::Read + WriteSimple blocking I/O
Asynchronous UARTasyncembedded-io-async::Read + WriteEmbassy, RTIC, async/await
PWM TriggerpwmGPIO output + your timerMaximum flexibility
Analog ADCanalogNone (math only)Direct voltage measurement

§Standardized Output Format

All examples follow this output format for easy parsing:

[DISTANCE] X cm              # Successful distance measurement
[TEMPERATURE] X.X °C         # Temperature reading
[OUT_OF_RANGE]               # Sensor reading out of valid range
[ERROR]                       # Communication or sensor error

§Examples

Ready-to-use examples for popular platforms:

§Arduino Mega 2560

§STM32F767ZI (Nucleo)

All examples output distance/temperature in the standardized format above.

§Quick start (async UART with Embassy)

[dependencies]
urm37 = { version = "0.6", features = ["uart-async"] }
use urm37::uart_async::Urm37UartAsync;

let mut sensor = Urm37UartAsync::new(uart);
let dist = sensor.read_distance().await?;    // cm
let temp = sensor.read_temperature().await?; // tenths of °C

§PWM mode

Distance measurement via ECHO pulse width. The driver manages the TRIG pin and supports both synchronous and asynchronous implementations.

Use Urm37PwmAsync for non-blocking async/await code with Embassy:

use embassy_stm32::timer::input_capture::{CapturePin, InputCapture};
use embassy_stm32::timer::Channel;
use embassy_time::Timer;
use urm37::pwm_async::{Urm37PwmAsync, PulseReaderAsync};

// Async pulse reader using InputCapture
struct AsyncPulseReader<'d> { ic: InputCapture<'d, peripherals::TIM2> }

impl<'d> PulseReaderAsync for AsyncPulseReader<'d> {
    async fn measure_pulse(&mut self) -> Option<u32> {
        self.ic.wait_for_rising_edge(Channel::Ch1).await;
        let t_fall = self.ic.wait_for_falling_edge(Channel::Ch1).await;
        let t_rise = self.ic.wait_for_rising_edge(Channel::Ch1).await;
        let duration_us = t_rise.wrapping_sub(t_fall);
        (duration_us > 0 && duration_us < 50000).then_some(duration_us)
    }
}

let trig = Output::new(p.PA0, Level::High, Speed::Low);
let mut sensor = Urm37PwmAsync::new(trig, AsyncPulseReader { ic }, Delay)?;
match sensor.read_distance_manual().await {
    Ok(Some(cm)) => defmt::info!("Distance: {} cm", cm),
    _ => {}
}

§Sync Mode (Blocking)

Use Urm37Pwm for simple blocking code without async:

use urm37::pwm::{Urm37Pwm, PulseReader};

struct SyncPulseReader { echo: Pin, timer: Timer }

impl PulseReader for SyncPulseReader {
    fn measure_pulse(&mut self) -> Option<u32> {
        while self.echo.is_low() { }
        while self.echo.is_high() { }
        let t_fall = self.timer.counter();
        while self.echo.is_low() { }
        let duration_us = self.timer.counter().wrapping_sub(t_fall);
        (duration_us > 0 && duration_us < 50000).then_some(duration_us)
    }
}

let mut sensor = Urm37Pwm::new(trig, SyncPulseReader { ... }, delay)?;
match sensor.read_distance_manual() {
    Ok(Some(cm)) => println!("Distance: {} cm", cm),
    _ => {}
}

§Analog mode

The driver provides the ADC-to-distance conversion. Reading the ADC is the caller’s responsibility.

The formula is: distance_cm = (raw / max_raw) * VCC / 0.006 V
which simplifies to roughly 2 cm per LSB on a 12-bit / 3.3 V system.

use urm37::analog::adc_to_distance_cm;

// 12-bit ADC (max = 4095), VCC = 3.3 V
let raw: u16 = adc.read(&mut pin)?;
match adc_to_distance_cm(raw, 4095) {
    Some(cm) => defmt::info!("Distance: {} cm", cm),
    None     => defmt::warn!("Out of range"),
}

// 10-bit ADC (max = 1023), VCC = 5 V
let raw: u16 = adc.read(&mut pin)?;
match adc_to_distance_cm(raw, 1023) {
    Some(cm) => defmt::info!("Distance: {} cm", cm),
    None     => defmt::warn!("Out of range"),
}

Re-exports§

pub use protocol::encode_threshold;
pub use protocol::decode_threshold;
pub use protocol::EepromRegister;

Modules§

error
Driver error types.
protocol
Low-level UART frame encoding and decoding (protocol layer).
uart
Synchronous (Blocking) UART driver (feature = "blocking").