Sync ESP32 sensors over BLE without GPS
Let one ESP32-S3 set the pace. A software phase-locked loop helps nearby sensor boards put their readings on the same timeline without a UTC reference.
Put two accelerometers on opposite ends of a workbench and tap it. If one records the vibration before the other, did the vibration really arrive earlier, or are the clocks on the two boards out of step?
To compare the readings, both boards need to use the same time axis. They can use one ESP32-S3 as a reference and measure elapsed time from an arbitrary starting point. The reference runs on its own oscillator, so the boards can do this indoors without GPS or a network time server. The group follows that oscillator’s pace even if it drifts against UTC, the world’s shared time standard. This is the setup in Example 01, Wireless BLE SoftPLL.
Match two timestamps to one radio event
The reference board sends a Bluetooth Low Energy (BLE) timing packet 16 times a second. Both followers listen to the same broadcasts. Each saves a local timestamp from the controller when it hears an original packet. A later broadcast carries the reference board’s estimate of when that original went out. The follower matches the two by publication number, radio event counter and channel within the same source session. That gives it a pair of readings for the earlier event.
The timestamp has to describe the radio event. By the time an application callback runs, the packet has already arrived and other work may have delayed the callback. The example reads a controller receive timestamp in 500 ns increments. On the transmit side, it estimates the event time from the controller’s schedule. Radio and path delays can still shift the pair, even when the timestamps repeat closely. The timestamp adapter is specific to the ESP32-S3 and the ESP-IDF version pinned in the repo. ESP32-S3 timestamp adapter · Event matching and session rules
Learn the pace, then close the gap
Imagine both follower clocks agree at the start. If one runs 20 parts per million fast and the other runs 15 parts per million slow, ten seconds later their uncorrected readings differ by 350 microseconds. The longer the interval, the more that rate difference matters.
A software phase-locked loop, or SoftPLL, uses a rolling window of timestamp pairs to estimate how much time each local tick should represent. A steady rise in clock error tells you the clock is gaining. The gap between the current clock reading and the fitted reference time is the phase error the loop needs to close.
The fit estimates the pace. The loop then updates the software clock and gradually closes the remaining phase gap by briefly speeding up or slowing down the reported time. This is the same slew from Part 1, now driven by observations. The clock keeps advancing and the crystal runs at its own pace. Building a basic ESP32 clock · SoftPLL implementation
Put sensor readings on the shared timeline
When a sensor records an event, capture the local tick as close to the sampling instant as the hardware allows. Use that tick and the currently valid software clock to label the reading in shared time. Each board applies its own conversion. Now you can compare the two vibration traces on one time axis, even if the data reaches your laptop in the opposite order.
That comparison still depends on when the sensors actually sample and how well you capture that instant. Scheduling both sensors to sample together is a separate job. If you need to verify simultaneous sampling, measure the sampling edges against a common instrument while BLE continues to train the clocks.
Know when the clock is ready
Before applying a follow-up, the example compares its reference value with the prediction the follower saved at the original event. Its LOOP LOCK indicator requires enough of those prediction errors to stay small, a trained rate and little phase correction left. It tells you how well the software clock tracks its BLE observations. A fixed delay shared by those observations can still bias the clock.
If usable pairs stop arriving, the clock becomes unavailable 500 ms after the last accepted pair’s original receive event. A new source session starts a new acquisition. Keep the raw tick and source session with your data so a gap or restart stays visible when you compare the traces later. Lock checks and expiry in the code
Try it with no boards attached
Run the host exercise on your computer. It uses the firmware’s loop with one simulated leader and two followers that run at +20 and −15 parts per million. Predict the sign of each follower’s correction, then check the output. After that, make one follower miss more packets and watch how many events you can still compare. The output tracks missing opportunities as well as timing spread, so you can see both effects. Run Example 01 · Change the simulation