If you’ve ever tried tracking something inside a building, you know GPS just flounders. That’s where Ultra-Wideband (UWB) steps in; it can pinpoint positions down to about 10cm. Yep, centimeters. Pretty wild.
I’ve been messing with the Qorvo DWM3000 UWB modules paired with ESP32 boards, and honestly, it’s better than I expected. In this ESP32 project, we'll build a working indoor positioning system that actually tracks movement in real time.
Why UWB?Unlike GPS, which relies on satellites miles away, UWB just measures how long radio pulses take to travel between devices. The DWM3000 can measure timing down to nanoseconds, which translates to centimeter-level accuracy. That’s the difference between knowing someone’s in the room versus knowing exactly which desk they’re at.
Quick indoor tracking comparison:
- UWB: ~10cm, up to 200m
- Bluetooth: 1–3m, okay for “nearby” but not precise
- Wi-Fi: 5–15m, basically room-level
- GPS indoors: 5–10m if it works at all
Here’s what you need:
- 4× DWM3000 modules (1 tag + 3 anchors)
- 4× ESP32-WROOM boards
- Micro-USB cables for power/programming
- Breadboards or custom PCBs to mount everything
The DWM3000 is super nice because it’s a complete module—antenna, RF components, crystal, power management—all in one. No need to be an RF expert. Just hook it up via SPI to the ESP32 and you’re mostly done.
Pin connections:- DWM3000 → ESP32
- VCC → 3.3V (not 5V!)
- GND → GND
- SCK → GPIO18
- MOSI → GPIO23
- MISO → GPIO19
- CS → GPIO4
- RST → GPIO27
- IRQ → GPIO34 (optional but handy)
Conceptually, it’s simpler than it sounds. Place three anchors at known positions, then the tag communicates with each anchor using Double-Sided Two-Way Ranging (DS-TWR):
- Tag pings Anchor 1
- Anchor 1 responds
- Tag sends another message
- Anchor replies with timing
- Repeat with Anchors 2 and 3
Each exchange measures round-trip time with nanosecond precision. With distances from three points, you can calculate the position using trilateration, basically circles intersecting.
DS-TWR also compensates for clock differences between devices, which is why it’s more accurate than simpler approaches.
AnchorsI set my three anchors in a triangle:
- Anchor 1: Bottom-left (15, 5) cm
- Anchor 2: Bottom-right (290, 5) cm
- Anchor 3: Top-center (165, 625) cm
These coordinates matter and they need to match what your Python visualization uses. Each anchor runs the same firmware but gets a unique ID (1, 2, 3). The code is basically a state machine: waits for requests, responds with precise timing. Simple, solid, reliable.
Tag FirmwareThe tag does the interesting part. It cycles through all three anchors continuously:
For each anchor:
Stage 0: Send initial ranging frame
Stage 1: Wait for response
Stage 2: Send second ranging frame
Stage 3: Wait for final response
Stage 4: Calculate distance, update filtered value
I use median filtering because raw UWB data can be noisy. Rolling buffer of the last 50 measurements, median kills outliers but keeps things responsive. Once the tag has fresh distances for all anchors, it packages everything into JSON and sends it via WiFi. Distances, signal strength, timing, clock offsets, all in there for debugging.
VisualizingOn the PC side, Python handles it:
- TCP server receives tag data
- Trilateration math via
scipy
- Plots tag on floor plan in real-time
Floor plan is the background, anchors are triangles, tag is a moving red dot, signal strength floats above each anchor. Watching it move and updating in real-time is super satisfying.
Getting It Running1. Flash ESP32s:
- Three anchors (IDs 1–3)
- One tag, update WiFi credentials
2. Position the hardware:
- Mount anchors at chosen coordinates
- Power everything, tag starts ranging automatically
3. Start Python script:
- Install dependencies: matplotlib, scipy, numpy
- Update IP, place floor plan in folder, run script
4. Move the tag:
- Updates ~every 100ms
- Blue trail shows history
- RSSI shows signal quality
Accuracy: consistently 10–15cm in good conditions.
Good setup:
- Clear line of sight
- Stable anchors
- Proper antenna delay calibration (16350 works for me)
Bad setup:
- Metal objects between tag and anchors
- Moving anchors
- Power noise
- Antenna misalignment
UWB handles multipath better than most, but big metal surfaces can still mess readings occasionally.
Room for Improvement- Add a 4th anchor for reliability / 3D
- Implement TDoA for multiple tags
- Web interface instead of matplotlib
- NLOS detection using RSSI
- Optimize power for battery use
DWM3000 can handle all these, it’s mostly firmware work.
Trouble?- Antenna delay calibration: Took forever. 16350 works for me; test yours.
- WiFi interference: Heavy traffic can slow updates. Keep ranging and networking separate.
- Power supply: Use clean 3.3V with decoupling caps.
- Coordinate systems: Python script and anchor positions must match. I lost an hour flipping Y coordinates.
- Low power: sleep modes for battery
- Secure: anti-spoofing built in
- Fast: ranging in milliseconds
- Scalable: many tags if MAC-managed
Not perfect: needs line of sight, calibration, pricier than Bluetooth. But for precise indoor positioning, it’s tough to beat.
Final ThoughtsHonestly, this was easier than I expected. DWM3000 being a full module is a lifesaver. Hardest part was timing math and weird WiFi issues.
Tips if you try it:
- Start with the basic ranging example
- Nail positioning with one anchor first
- Log everything while debugging
- Test in an open area before moving to your real space
This piece is entirely based on: UWB Indoor Positioning System using ESP32
Comments