ping-rs: Servo-Swept Ultrasonic Radar
ping-rs is a small robotics sensing stack on STM32F411RE in async Rust. An HC-SR04 ultrasonic rangefinder rides on an SG90 servo, sweeps the forward field, renders a polar view on an SSD1306 OLED, and streams timestamped samples over USB CDC.
The useful part is not the sensor. It is the whole embedded loop: deterministic pulse timing, servo position tracking, bounded channels, display refresh, host telemetry, and explicit handling for stale samples.
Goal
Build a readable no-std example of a scanning range sensor that can feed later robotics projects:
- generate servo PWM without blocking range capture,
- measure ultrasonic echo pulse width with microsecond timing,
- associate each range sample with the best-known servo angle,
- render a live polar plot on-device,
- export JSON lines to a host for plotting and log review,
- keep scanning even if USB or display output stalls.
Hardware
| Part | Purpose |
|---|---|
| STM32 Nucleo-F411RE | Cortex-M4F target and onboard ST-Link |
| HC-SR04 | Ultrasonic time-of-flight rangefinder |
| SG90 | 180 degree micro servo |
| SSD1306 0.96 inch OLED | 128 by 64 polar display over I2C |
| USB CDC ACM | Host telemetry stream |
| Logic analyzer | Pulse, PWM, and I2C timing capture |
Wiring
| Signal | Nucleo pin | MCU pin |
|---|---|---|
| Servo PWM | CN5 D1 | PA0, TIM2_CH1 |
| HC-SR04 trigger | CN8 A1 | PA1 |
| HC-SR04 echo | CN9 A3 | PB0 |
| OLED SCL | CN10 D15 | PB8, I2C1_SCL |
| OLED SDA | CN10 D14 | PB9, I2C1_SDA |
| USB D+/D- | USB | PA11 / PA12 |
| Status LED | onboard | PA5, LD2 |
The HC-SR04 is powered from 5 V. Its echo output is divided to 3.3 V with a 1 kΩ / 2 kΩ divider before PB0.
Scan loop
The servo sweeps 0 to 180 to 0 degrees over roughly six seconds per full scan. The range task fires one ultrasonic ping every 50 ms. Each sample carries:
{"t_us":1234567,"angle_deg":45,"dist_mm":823}
Angle is sampled from an atomic value maintained by the servo task. That is accurate enough for this sensor because the sonar beam width and servo backlash dominate sub-degree timing error.
Software architecture
servo::sweep_task
|
v
CURRENT_ANGLE, AtomicU8
|
v
ultrasonic::ping_task
|
v
Sample channel, depth 64
| |
v v
display::render_task telemetry::usb_task
SSD1306 polar plot JSON over USB CDC
The sample channel uses nonblocking send. If display or USB stalls, new samples may be dropped rather than backing up the scan loop. For a live radar view, freshness is more valuable than replaying stale points.
Timing constraints
| Path | Constraint |
|---|---|
| Servo PWM | stable 50 Hz frame, pulse width mapped to angle |
| Trigger pulse | at least 10 µs high |
| Echo capture | timeout on no return, no unbounded wait |
| Ping period | at least 50 ms to avoid acoustic overlap |
| Display | bounded redraw, no blocking in range task |
| Telemetry | best-effort stream, no scan backpressure |
The logic-analyzer proof point is a capture with trigger pulse, echo pulse, servo PWM, and one debug GPIO marker around the range measurement. That separates sensor timing bugs from rendering or USB bugs.
Error sources
- HC-SR04 beam width creates angular smear.
- Soft targets and angled surfaces return weak or late echoes.
- Servo backlash means commanded angle is not exact shaft angle.
- Temperature changes sound speed.
- USB logging can perturb timing if it is allowed to block the measurement path.
The code treats the output as a proximity map, not a metrology instrument.
Build and flash
rustup target add thumbv7em-none-eabihf
cargo install probe-rs-tools
cargo run --release
RTT logs use the ST-Link probe. USB CDC is reserved for sample telemetry.
Related notes
STM32 Bare-Metal Bring-Up, Interrupts, DMA, and Ring Buffers, Measurement and Instrumentation, and PID Tuning and Step Response.