RSSI gives you a coarse distance, but in a real room multipath and the human body push the error to ±3-5 m. If a BLE Tag needs sub-meter accuracy for find-my-item or zone entry, you need ranging. This article covers Two-Way Ranging (TWR), why clock drift dominates, and how to fuse it with RSSI.
The ranging idea
Instead of inferring distance from received power, measure the flight time of a radio packet. For a round trip:
d = c * (T_round – T_reply) / 2
where T_round is measured at the tag (send to receive-echo) and T_reply is the anchor’s known processing delay. Light travels 0.3 m/ns, so a 1 ns timing error is 0.15 m of distance error.
Single-Sided TWR (SS-TWR)
The tag sends at t0, the anchor replies at t1 after a fixed delay Δ, and the tag receives at t2.
T_round = t2 – t0
T_reply = t1 – (t0 + Δ) -> known
d = c * (T_round – T_reply) / 2
If the two clocks are not perfectly synchronized, a frequency offset f_off corrupts the result:
error = c * f_off * T_round / 2
With 20 ppm crystals and T_round = 10 µs, the clock error is 20e-6 * 10e-6 = 2e-10 s → 6 cm. Acceptable, but at 100 µs round trip it grows to 60 cm. That is why you must minimize round-trip time or cancel the offset.
Symmetric Double-Sided TWR (SDS-TWR)
Add a second exchange so the tag’s and anchor’s clock offsets cancel:
d = c/4 * ( (t4-t1) – (t3-t2) + (t6-t3) – (t5-t4) ) …
In practice the formula reduces to a symmetric pair of round trips whose offset terms subtract out, leaving only asymmetry in processing delays (typically <1 ns). SDS-TWR is the standard for BLE/UWB ranging because it removes the crystal-accuracy requirement.
BLE implementation reality
Bluetooth LE has no native RTT primitive like UWB’s fine-time Ranging. You implement it with:
- Connection event timing: measure the interval between a master command and slave response at the Link Layer.
- CTE / AoA frames: use the Constant Tone Extension for phase-based timing.
- GATT request/response: simplest, but the host stack latency (1-10 ms) dominates, giving coarse resolution.
A 1 µs timer gives 150 m of ambiguity, so you need timestamping at the PHY/Radio layer, not the application layer.
RSSI fusion
TWR is accurate but power-hungry (several ms of active TX/RX at a few mA per measurement). RSSI is passive and cheap but biased. Fuse them in a Kalman filter:
x_k = A x_{k-1} + w (motion model)
z_RSSI = d_RSSI + v1 (noisy, biased)
z_TWR = d_TWR + v2 (sparse, accurate)
The filter trusts TWR when available and falls back to RSSI between measurements, smoothing the track and cutting the power budget by ranging only every Nth slot.
Power cost
| Method | Active time | Current | Accuracy |
|---|---|---|---|
| RSSI passive | 0.5 ms listen | 5-8 mA | ±3-5 m |
| SS-TWR | ~4 ms | 5-8 mA | ±0.5 m |
| SDS-TWR | ~8 ms | 5-8 mA | ±0.1-0.3 m |
Ranging every 1 s roughly triples the average current versus RSSI-only, so budget the interval against battery life.
Practical checklist
- Use SDS-TWR to cancel crystal offset.
- Calibrate antenna + processing delay once at the factory.
- Timestamp at the radio, not the app layer.
- Fuse with RSSI in a Kalman filter to save power.
- Keep round-trip time short to limit drift error.
A well-designed BLE Tag achieves 0.1-0.3 m with SDS-TWR and keeps battery life intact by ranging sparingly and leaning on RSSI between fixes.