Hardware Academy
Robotics

Sensors

Ultrasonic, infrared, encoders, IMUs and lidar: how each works and how it lies to you.

Day 7 · 55 min

After this lesson

  • You can explain time-of-flight ranging and quadrature encoding.
  • You can fuse a gyro and accelerometer with a complementary filter.
  • You can filter a noisy sensor with an exponential moving average.

How robot sensors work, and how they lie

Every sensor turns a physical quantity into a number through a model, and every model has conditions under which it fails. Knowing those conditions is most of the job: the code that reads a sensor is ten lines, the code that copes with its failures is the rest.

Ultrasonic ranging: the HC-SR04

The HC-SR04 measures time of flight. Hold TRIG high for 10 µs and the module sends a burst of eight 40 kHz cycles, then holds ECHO high until it hears the reflection. Distance is d = t · 343 / 2, with t in seconds and 343 m/s the speed of sound at 20 °C. The round trip costs about 58 µs per centimetre, so an object 1 m away gives a 5.8 ms pulse. The module is rated for 2-400 cm with a measuring angle of about 15°. If nothing comes back it gives up after about 38 ms, and you should wait at least 60 ms between pings so a late echo is not taken for the next one.

It lies in four predictable ways:

  • Angled smooth surfaces. At 40 kHz the wavelength is 8.6 mm, so painted walls, glass and boxes act as mirrors. A wall tilted more than about 15° from square sends the echo away and the sensor reports nothing at all.
  • Soft surfaces. Fabric, foam and fur absorb sound, so echoes become weak and intermittent.
  • Temperature. Sound travels at about 331.3 + 0.606·T m/s, so its speed changes 5.5% between 0 °C and 30 °C.
  • Crosstalk. Two sensors pinging together hear each other. Fire them in turn.

The module runs on 5 V and its ECHO output is 5 V, so a 3.3 V microcontroller needs a divider on that pin.

HC-SR04 timing: a 10 microsecond trigger, an 8-cycle 40 kHz burst, and an echo pulse as long as the round tripTRIGburstECHO10 µs8 cycles × 40 kHz = 200 µst = round trip, 58 µs per cmecho arrivesno echo: ~38 msnext TRIG no sooner than 60 ms laterd = t × 343 m/s ÷ 2 → 1 m ↔ 5.83 ms, 10 cm ↔ 583 µsnot to scale
HC-SR04 timing: a 10 µs trigger, an 8-cycle 40 kHz burst, then an echo pulse whose width is the round-trip time.

Infrared reflectance: the TCRT5000

A TCRT5000 puts a 950 nm infrared LED beside a phototransistor. A white floor reflects the light and turns the transistor on; black tape absorbs it. The sensor works best about 2.5 mm from the surface, and sunlight, which is rich in infrared, can swamp it. Read the analog output, record each sensor's black and white values, and set the threshold halfway between them rather than trusting a fixed number.

Quadrature encoders

An encoder turns rotation into pulses. A quadrature encoder has two channels, A and B, offset by a quarter cycle (90 electrical degrees). Whichever channel leads gives the direction, and counting both edges of both channels (×4 decoding) gives four counts per pulse. Datasheets quote pulses per revolution (PPR) of one channel; counts per revolution (CPR) includes the decoding. The course robot's N20 motors carry 7 PPR magnetic encoders on the motor shaft, so ×4 decoding gives 28 counts per motor turn and 1,400 per wheel turn behind the 50:1 gearbox. Count edges in hardware (the ESP32's pulse-counter unit) or in short interrupts; a polled loop misses edges at speed.

IMUs: accelerometer, gyroscope, magnetometer

  • The accelerometer measures specific force, gravity included, over ±2-16 g. At rest it gives tilt from the direction of gravity, but vibration and acceleration corrupt it.
  • The gyroscope measures angular rate, typically ±250-2000 °/s. It is smooth, but its zero-rate offset (up to ±20 °/s uncalibrated on an MPU-6050) integrates into drift: an uncorrected 0.5 °/s bias is 30° of heading error per minute. Average it for two seconds at rest and subtract it.
  • The magnetometer senses Earth's 25-65 µT field for heading, but motors, steel and wiring distort it, so it needs hard- and soft-iron calibration.

The complementary filter fuses gyro and accelerometer for tilt: angle = α·(angle + rate·dt) + (1 − α)·accelAngle. With α ≈ 0.98 at 100 Hz, the crossover time constant α·dt/(1 − α) is about 0.5 s: the gyro tracks fast motion and the accelerometer removes slow drift. It cannot fix yaw, because gravity carries no heading information.

Lidar and cameras

Lidar times light instead of sound: 6.7 ns per metre of range. A spinning 2D unit such as the RPLIDAR A1 gives 360° scans out to 12 m at about 5.5 Hz; single-point sensors such as the VL53L1X reach about 4 m over I2C. Both struggle with glass, mirrors and black surfaces. Cameras give the richest data at the highest compute cost; they need lens calibration (focal length and distortion from about 20 checkerboard images) and suffer from motion blur and lighting changes.

Sampling, noise and filtering

Sample at least twice as fast as the fastest change you care about (Nyquist), and in practice 5-10 times faster. Averaging N independent samples cuts noise by √N, so 16 samples give a 4× improvement. The workhorse filter is the exponential moving average, y += α·(x − y): one multiply, with α = dt/(τ + dt) setting the time constant τ. Its price is lag: with τ = 40 ms it delays a slow signal by about 40 ms, and lag inside a control loop costs stability. For spikes and dropouts, such as an ultrasonic timeout, a median of three readings beats any average.

Calibration and fusion

Calibration maps raw readings to truth: offsets (gyro bias at rest), scale (drive exactly 1 m and count ticks to find the effective wheel diameter) and ranges (black and white values per line sensor). Fusion then combines sensors whose errors differ. Encoders are precise over short distances but slip; a gyro holds heading through a slip but drifts; an occasional absolute fix, such as a wall, a line or a lidar scan, resets both. The Kalman filter is the formal, optimal version of that idea; the complementary filter is its simplest cousin.

Interactive: ping a wall, spin an encoder

Ultrasonic ping: time of flight and the lost echo

An HC-SR04 sends eight 40 kHz cycles and times the echo. Sound is slowed about 170× here. Tilt the wall past about 15° and the echo misses the receiver.

Top view of an ultrasonic sensor pinging a wall, with trigger and echo timing belowsmooth wallTRHC-SR04listening…TRIGECHOecho high for 6989 µs → d = t × 343 / 2 = 119.9 cm
Echo pulse
6989 µs
Firmware reads (343 m/s)
119.9 cm
True distance to the wall
120.0 cm
Speed of sound
343.4 m/s, error -0.1 cm

Tilt the wall to 10° and the reading barely changes; at 20° the echo is gone. Then drop the temperature to 0 °C at 380 cm: the firmware's fixed 343 m/s overestimates the range by about 13 cm.

Quadrature encoder: two channels, direction and count

The N20's magnetic disc has 7 pole pairs, so each Hall sensor gives 7 pulses per motor turn. B sits a quarter cycle behind A. Watch which channel leads when you reverse.

Encoder disc with two Hall sensors and the A and B square waves they produceABred = N pole, blue = S poleclockwiseAB−2 s−1 snowAB state, clockwise →10110100Counter-clockwise walks the same ring backwards.Only one bit changes per edge (a Gray code), so everytransition tells the decoder which way the shaft moved.
Decoding
Direction
clockwise (A leads B)
Count (×4)
0
Resolution at the wheel
10.13 counts/mm
Wheel travel (50:1, 44 mm)
0.00 mm

Pause, then press Step to walk through one edge at a time and watch the AB state move around the ring. Reverse the speed and the order of edges, not their shape, changes.

In practice

Practice

Filter a noisy range reading

Write an exponential moving average for the HC-SR04 that starts from the first valid sample and ignores timeouts. Pick α for a 40 ms time constant at 100 Hz.

Answer. α = dt/(τ + dt) = 0.01/0.05 = 0.2. Invalid readings are rejected before they reach the filter, so a single timeout cannot drag the estimate toward 0.

ema.hcpp
// Exponential moving average for a sensor read at a fixed rate.
// alpha = dt / (tau + dt). At 100 Hz (dt = 0.01 s) and tau = 0.04 s, alpha = 0.2.
class Ema {
 public:
  explicit Ema(float alpha) : alpha_(alpha) {}

  float update(float x) {
    if (!primed_) {            // start from the first real sample, not from 0
      y_ = x;
      primed_ = true;
    } else {
      y_ += alpha_ * (x - y_);
    }
    return y_;
  }

  float value() const { return y_; }

 private:
  float alpha_;
  float y_ = 0.0f;
  bool primed_ = false;
};

Ema rangeCm(0.2f);

// Called whenever a new HC-SR04 reading arrives (about every 50 ms).
void onUltrasonic(float cm) {
  if (cm < 2.0f || cm > 400.0f) {  // 0 means "timeout": unknown, not near and not far
    return;                        // keep the last estimate, count the miss elsewhere
  }
  rangeCm.update(cm);
}

Practice

Ticks per millimetre for the course robot

The N20 encoder gives 7 pulses per motor revolution, the gearbox is 50:1 and the wheel is 44 mm across. How many ticks per millimetre do you get, and what does that mean for measuring speed at 100 Hz?

worked answertext
Encoder on the motor shaft     7 pulses per revolution (one channel)
Gearbox                        50 : 1
Wheel                          44 mm diameter -> circumference = pi x 44 = 138.2 mm

x1 decoding (A rising edges):  7 x 50          =   350 ticks per wheel turn
                               350 / 138.2     =  2.53 ticks per mm   (0.40 mm per tick)
x4 decoding (every edge):      7 x 4 x 50      = 1,400 counts per wheel turn
                               1,400 / 138.2   = 10.1 counts per mm   (0.10 mm per count)

Speed measurement at 0.2 m/s with a 100 Hz loop (2 mm of travel per cycle):
x1 -> about 5 ticks per cycle  -> one tick is a 20 % speed step
x4 -> about 20 counts per cycle -> one count is a 5 % speed step

Answer. About 2.5 ticks per mm counting only A's rising edges, or 10.1 counts per mm with ×4 decoding. At low speed a 100 Hz loop sees only a handful of ticks per cycle, so use ×4 decoding, or measure the time between edges instead of counting them.

Real-world scenario

Real-world scenario

Robot drives into angled walls its ultrasonic sensor never reported

An obstacle-avoiding robot uses one forward-facing HC-SR04 and turns away whenever the reading drops below 25 cm. In square-on bench tests it never touches a wall. During a hallway demo it drives straight into walls it meets at a shallow angle, and into a fabric-covered partition. Early in the project the team made the firmware convert a pulseIn() timeout, which returns 0, into 400 cm, because zeros were causing spurious stops in open rooms.

Symptoms

  • Square-on walls are detected reliably from a few centimetres to beyond 2 m.
  • Walls met roughly 30-45° off square are never reported; the log shows 0 (converted to 400 cm) for the last second before every impact.
  • The fabric-covered partition gives readings that jump between a plausible 40-60 cm and 0.
  • A second HC-SR04 angled 30° outward and fired at the same moment as the first made the readings noisier, not better.

Interview questions

screenHow does an HC-SR04 measure distance, and what limits it?
You give the trigger pin a 10 µs pulse, the module sends eight cycles of 40 kHz sound, and it holds its echo pin high until the reflection comes back. Distance is the pulse width times the speed of sound divided by two, about 58 µs per centimetre at 20 °C. It works from about 2 cm to 4 m inside a cone of roughly 15°. It fails on soft materials that absorb sound and on smooth surfaces at an angle, which reflect the sound away like a mirror. The speed of sound also changes by about 0.6 m/s per °C, so a 30 °C swing is roughly a 5% error unless you compensate.
screenWhat is a quadrature encoder, and how do you get direction from it?
It has two outputs, A and B, that produce square waves a quarter cycle apart. If A's edges come before B's the shaft turns one way; if B leads, it turns the other way. Counting every rising and falling edge of both channels, called ×4 decoding, gives four counts per pulse period, so a 7 PPR encoder gives 28 counts per motor revolution. I would count in hardware, such as the ESP32's pulse-counter unit, or in short interrupts that run a 2-bit state machine so reversals and glitches are handled correctly.
deepExplain how a complementary filter estimates tilt from a gyro and an accelerometer. How do you choose α, and what can it not fix?
The gyro measures angular rate with little noise, but integrating it accumulates bias, so the angle drifts; the accelerometer measures the direction of gravity without drift, but it is noisy and fooled by vibration and acceleration. The filter blends them: angle = α·(angle + rate·dt) + (1 − α)·accelAngle, which high-passes the gyro and low-passes the accelerometer. The crossover time constant is about α·dt/(1 − α), so α = 0.98 at 100 Hz gives roughly 0.5 s: the gyro wins for anything faster and the accelerometer for anything slower. It cannot fix yaw, because gravity carries no heading information, so heading needs a magnetometer, a camera or another absolute reference. It also assumes the accelerometer mostly sees gravity, so during long accelerations you lower its weight or move to a Kalman filter.
deepYou need to filter a noisy sensor that feeds a control loop. How do you choose the filter?
First I look at the noise: spikes and dropouts call for a median of three or five readings, while broadband noise calls for a low-pass such as an exponential moving average. Then I size the filter against the loop, because an EMA with α = dt/(τ + dt) delays slow signals by about τ and that delay eats phase margin. At 100 Hz, α = 0.2 gives τ = 40 ms, which is fine for a 1 Hz heading loop but too slow for a 20 Hz balance loop. If the lag is unacceptable I fix the noise at the source, with better wiring, a better sensor or oversampling, or move to a model-based estimator.
screenWhy does position from an IMU alone drift so fast?
Position comes from integrating acceleration twice, so any constant error grows with the square of time. A bias of only 0.01 m/s², about a thousandth of g, becomes ½·a·t² = 18 m of position error after one minute. Tilt makes it worse, because a 1° attitude error leaks 0.17 m/s² of gravity into the horizontal axes. That is why robots use the IMU for orientation and short-term motion, and correct position with wheel odometry, GPS, lidar or cameras.

Quiz

1. An HC-SR04's echo pulse lasts 2.9 ms at 20 °C. How far away is the object?
2. A quadrature encoder's channel A has been leading channel B. Now B leads A. What happened?
3. A 7 PPR motor encoder drives a 44 mm wheel through a 50:1 gearbox. How many counts per millimetre do you get with ×4 decoding?
4. In a complementary filter with α = 0.98, what is the 0.02 weight on the accelerometer angle for?
5. You change an EMA filter's α from 0.5 to 0.1 at the same sample rate. What happens?
6. Why does an HC-SR04 often report no echo from a smooth wall 40° off square?
7. At what height does a TCRT5000 reflective sensor work best?

References and further reading

Where the ideas in this lesson come from, and what to read next. Books are the standard texts; papers are the original sources; standards are what employers work to.

  1. Book

    Roland Siegwart, Illah R. Nourbakhsh and Davide Scaramuzza (2011). Introduction to Autonomous Mobile Robots. MIT Press, 2nd edition.

    Read: The perception chapter: sensor classification, ultrasonic, encoders, IMUs.

  2. Book

    Sebastian Thrun, Wolfram Burgard and Dieter Fox (2005). Probabilistic Robotics. MIT Press.

    Read: Chapter 6 on measurement models and sensor noise.

  3. Paper

    R. E. Kalman (1960). A New Approach to Linear Filtering and Prediction Problems. Journal of Basic Engineering 82(1), 35-45.doi:10.1115/1.3662552

    Read: Read the abstract and the idea of blending prediction with measurement.