After much effort, I finally achieved running 7 T-Beams for 17 minutes, and having them create internal clocks based on satellite pulse per second, aka “PPS”, and finding they spanned by 166 µs.1
One hurdle I had to overcome was a physical one: 1) the top of the T-Beam contains the GPS antenna which is like a fat mint and it is inside the case next to the base of the black LoRa antenna protruding from the case 2) the GPS antenna needs to point towards the sky, 3) the T-Beam USB connector comes out of the bottom of the unit, opposite of the side with the antenna. So you cannot stand the T-Beam on its bottom when you have a USB cable attached to it. I therefore decided I needed to suspend the T-Beams and accomplished that quickly with some scrap Port-Orford-cedar and my latest tool: an air gun stapler. So I rigged a suspension unit and then tied with twine each of the T-Beams so their USB connector could freely exit the base. What I found was that if I leaned the T-Beams at a 45 degree angle, any movement could cause a RESET because of wobble between the unit and the USB-C cable.


I was able to capture over the USB-C interface the UART messages without any interference or RESETS — a problem that arose often in earlier tests.
I logged twice: 1) internal logging in the T-Beam to is static on-board RAM memory, and 2) logging to the serial console which my laptop captures with a Perl script that wraps each received transmission with a high precision date-time stamp. My laptop is synchronized to my local stratum-1 time source, providing a stable reference for the Perl capture timestamps.
After the run, e.g. 17½ minutes, I combine both types of logs into a directory and run a Perl script which parses and digests the log contents and then populates a SQLite database. Here are three SQL summaries documenting the capture integrity, measured inter-unit agreement, and stability across the run.
SQL 1 – Audit
Every unit should show exactly one START, COMPLETE, and FROZEN event, write_errors must be zero.
| unit_name | capture_starts | capture_completes | capture_frozen | capture_duration_ms | records | write_errors | ram_overwrites |
|---|---|---|---|---|---|---|---|
| AMY | 1 | 1 | 1 | 1020015 | 3295 | 0 | 2911 |
| BOB | 1 | 1 | 1 | 1020021 | 3272 | 0 | 2888 |
| CY | 1 | 1 | 1 | 1020017 | 3272 | 0 | 2888 |
| DAN | 1 | 1 | 1 | 1020001 | 3278 | 0 | 2894 |
| ED | 1 | 1 | 1 | 1020007 | 3292 | 0 | 2908 |
| FLO | 1 | 1 | 1 | 1020010 | 3272 | 0 | 2888 |
| GUY | 1 | 1 | 1 | 1020028 | 5316 | 0 | 4932 |
SQL 2: primary seven-unit synchronization result
This is the main query supporting the 166 µs statement.
| unit_name | central_samples | median_host_minus_tbeam_ms | seven_unit_median_span_ms |
|---|---|---|---|
| AMY | 214 | -3.651 | 0.166 |
| BOB | 219 | -3.644 | 0.166 |
| CY | 221 | -3.562 | 0.166 |
| DAN | 218 | -3.653 | 0.166 |
| ED | 218 | -3.728 | 0.166 |
| FLO | 220 | -3.678 | 0.166 |
| GUY | 225 | -3.688 | 0.166 |
Negative values mean that, under this calculation, the T-Beam’s reported disciplined time was approximately 3.6 ms ahead of the laptop-side capture timestamp; the important synchronization figure is the 0.166 ms spread among the seven units, not the common offset from the host.
3. show stability across the entire run
This query avoids relying only on one whole-run median. It divides the run into host-time minute bins and calculates the median offset per unit in each bin, then reports the spread among all seven units.
| minute_bin | units_present | seven_unit_median_spread_ms |
|---|---|---|
| 0 | 7 | 0.475 |
| 1 | 7 | 0.364 |
| 2 | 7 | 0.632 |
| 3 | 7 | 0.194 |
| 4 | 7 | 0.376 |
| 5 | 7 | 0.343 |
| 6 | 7 | 0.481 |
| 7 | 7 | 0.413 |
| 8 | 7 | 0.411 |
| 9 | 7 | 0.48 |
| 10 | 7 | 0.267 |
| 11 | 7 | 0.32 |
| 12 | 7 | 0.223 |
| 13 | 7 | 0.356 |
| 14 | 7 | 0.573 |
| 15 | 7 | 0.384 |
| 16 | 7 | 0.33 |
| 17 | 7 | 0.144 |
| 18 | 7 | 0.606 |
The Perl script (contains the schema), log files, and database are available upon request.
Leave a Reply