Expand description
§urm37
no_std embedded driver for the DFRobot URM37 V4.0 ultrasonic distance sensor.

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-halimplementation - 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
| Mode | Feature | Traits | Use Case |
|---|---|---|---|
| Synchronous UART | blocking | embedded-io::Read + Write | Simple blocking I/O |
| Asynchronous UART | async | embedded-io-async::Read + Write | Embassy, RTIC, async/await |
| PWM Trigger | pwm | GPIO output + your timer | Maximum flexibility |
| Analog ADC | analog | None (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
- mega2560_uart: UART communication (dual USART)
- mega2560_pwm: PWM pulse measurement
- mega2560_analog: Analog ADC reading
§STM32F767ZI (Nucleo)
- stm32_uart_async: Async UART with Embassy
- stm32_pwm: Async PWM with InputCapture
- stm32_analog: Async ADC with Embassy
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.
§Async Mode (Embassy-based, recommended)
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;