All case studies
IOT · 2026 · Projekt prywatny

Ultrasonic distance meter on ATtiny1616 (2.4 mm RMSE)

Ultrasonic distance meter on an ATtiny1616, measuring 10–50 cm with ~2.4 mm RMSE. A hybrid method: coarse time-of-flight from the 40 kHz echo envelope, refined by carrier phase (Goertzel filter), with NTC temperature compensation. Firmware in pure register-level AVR-GCC (no Arduino), ~0.9 mW sleep power. Bill of materials: €10.

Błąd RMSE
2,4 mm
Zakres
10–50 cm
Pobór mocy
0,9 mW
IOT

Overview

The goal was a cheap, accurate ultrasonic rangefinder on a single 8-bit MCU — millimetre accuracy and power low enough for battery operation. Built for the "Wygraj Indeks WETI 2026" engineering contest.

Method

I used a Pulse-Phase Ranging hybrid: coarse time-of-flight (ToF) from the echo envelope, refined by the 40 kHz carrier phase computed with a Goertzel filter.

ATtiny1616 ──40 kHz burst──▶ BC817 ──▶ TX transducer ))) target ((( RX transducer ──▶ MCP6002 ──▶ ADC ──▶ ToF + phase ──▶ distance

How the measurement works

  1. The MCU emits a 40 kHz burst (exact transducer resonance), driven by a BC817.
  2. The echo is amplified by a two-stage MCP6002 front-end (band-pass ~40 kHz), gated by a P-MOSFET to save power.
  3. The ADC samples the echo envelope; the echo onset (40 % of peak, interpolated) gives the flight time, measured by the TCB0 counter (100 ns/tick).
  4. A Goertzel filter extracts the carrier phase, snapping the result to the λ/2 ≈ 4.3 mm grid.
  5. An NTC thermistor corrects the speed of sound: c(T) = 331.3 + 0.606·T.
Key accuracy trick: onset detected at 40 % of the envelope peak (amplitude- independent) plus a median of 9 shots with outlier rejection (MAD). This cut the random error from ~4 mm down to ~1.5 mm.

Goertzel filter (phase)

// Goertzel @ 40 kHz over the echo sample buffer -> carrier phase float cw = cosf(omega), coeff = 2.0f * cw, s1 = 0, s2 = 0; for (int k = 0; k < W; k++) { float x = echo[k] - baseline; float s0 = x + coeff * s1 - s2; s2 = s1; s1 = s0; } float phase = atan2f(s2 * sinf(omega), s1 - s2 * cw);

Power

Between measurements the MCU sleeps in Standby, woken by the RTC/PIT; the analog front-end is powered only during a measurement. Idle draw: 0.9 mW.

Outcome

Measured RMSE ≈ 2.4 mm across the full 10–50 cm range, 0.9 mW idle, ~€10 in parts. All on a single ATtiny1616, register-level firmware (no Arduino), with a 2-point calibration stored in EEPROM.

Results

2,4 mm
Błąd RMSE
10–50 cm
Zakres
0,9 mW
Pobór mocy
43,73 zł
Koszt BOM

Similar problem? Let's talk.

Book a call