One of the more subtle problems in the badge mesh isn't communication — it's agreement.
Each badge runs its own local clock and steps through lighting effects based solely on that value. ESP-NOW gives us low-latency broadcast, but it doesn’t give us a shared notion of time. Even tiny timing differences (a few milliseconds) quickly turn into visible phase drift in animations.
The Problem
If every node advances its animation based purely on millis(), then:
- Two badges starting “together” won’t stay together
- Packet latency introduces small offsets
- Clock drift accumulates over time
The result: effects that should look synchronised.....don't.
Don’t Fight Time — Quantise It.
Instead of trying to perfectly synchronise clocks (hard), I switched to quantising time into fixed slices.
All animation timing snaps to a global step size:
50 ms per tick (20 Hz)
Each node converts its local time into a shared “tick number”:
tick = millis() - (millis() % 50)
Now the key idea: Nodes don’t need identical clocks — they just need to agree on the same tick.
Why 50 ms?
It’s a sweet spot:
- Fast enough for reasonable animation speeds (20Hz)
- Slow enough to tolerate packet jitter
- Divides nicely into common effect durations
I experimented with smaller steps (10 ms), but network jitter started to dominate. Larger steps made animations visibly steppy.
Mesh Alignment
When a badge receives a packet, it doesn’t try to syncit's millis() value. Instead it just adds a simple offset:
alignedMillis = (millis() + offset)
Incoming messages carry the sender’s tick. The receiver nudges its offset to match.
Because everything is quantised:
- Small timing errors collapse into the same tick
- Nodes naturally “snap” back into alignment
- No complex clock sync required
The Effect
This one change made a huge visual difference. Each badge calulates it's current effect from local aligned time.
effect = alignedTime % numEffects;
And each effect calculates it's current state in a similar fashion.
Before:
- Effects drift apart
- Synchronisation feels “loose”
After:
- Effects lock together cleanly after a less than a few seconds.
- The system feels intentional and coordinated
Takeaway
Perfect synchronisation is expensive.
But quantised synchronisation is cheap, robust, and good enough to look perfect.
Sometimes the trick isn’t making time continuous — it’s making everyone agree on the same discrete version of it.
Tony Goacher
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.