Week 5 showed that standard 5G NR cannot reach a satellite: the timing advance needed was 14× beyond the standard's limit, and 0% of a real pass fitted inside it. This project implements the fix 3GPP actually specifies — Release-17 GNSS-assisted pre-compensation — over a real satellite pass, and asks the question the fix leaves open: how fresh does the correction have to be?
This is Week 6 of a 12-week series. It is the direct sequel to ntn-5g-timing-doppler (Week 5).
pip install -r requirements.txt
python -m aiprecomp.main
Fetches live Starlink TLEs, finds a real pass over Aberdeen, runs Rel-17 pre-compensation at S-band, prints both findings, and saves ntn_precomp_residuals.png.
python -m pytest
Runs the test suite (8 tests, all passing).
The network doesn't correct each user's delay and Doppler individually — that wouldn't scale. Instead the work moves to the handset:
- The satellite broadcasts its ephemeris (position + velocity) in a System Information Block.
- The UE is required to have GNSS, so it knows its own position.
- From both, the UE computes its own distance and radial velocity to the satellite.
- It pre-computes and applies the timing advance and Doppler correction to its uplink before transmitting.
The signal arrives at the satellite already corrected, so the base station can operate as though it were serving an ordinary terrestrial cell.
With a fresh correction, the residual is essentially zero. On a real pass of STARLINK-1008 over Aberdeen the uncompensated impairment was 9,860 µs of timing advance and 39,756 Hz of Doppler; after pre-compensation, both drop to nothing.
This is worth being precise about: with perfect knowledge and zero delay the correction is exact by construction, not by discovery. That's not a cheat — it's the point. The scheme is geometrically exact. The engineering question isn't whether it works, it's what breaks it.
A LEO satellite moves several km/s, so a correction is stale the moment it's computed. Sweeping the age of the applied correction against the 3GPP tolerances:
| Constraint | Tolerance | Max refresh interval |
|---|---|---|
| Residual timing error | cyclic prefix, 4.69 µs @ 15 kHz SCS | ~100 ms |
| Residual frequency error | ±0.1 ppm = ±200 Hz @ 2 GHz (TS 38.101-5) | ~400 ms |
Timing is the binding constraint — roughly 4× stricter than frequency. That isn't obvious going in: Doppler is the impairment people talk about, but on this pass it's the timing that fails first. A correction just one second old already blows the cyclic prefix by ~8.5×.
Residual timing error grows at 2 · range_rate / c per second of staleness, so the timing-limited refresh interval should be about:
max_refresh ≈ CP · c / (2 · max_range_rate)
For the STARLINK-1008 pass this gives 118 ms, against 100 ms from the swept simulation (the largest grid point below it). Two independent methods agreeing is the reason to trust either. Both are locked into the tests.
A realistic 5–10 m GNSS position error consumes only ~3–6% of the timing budget. You'd need a position error of roughly 180 m before GNSS alone broke the cyclic prefix. Compared to staleness, which blows it in under half a second, GNSS accuracy simply isn't the problem — the refresh rate is.
| Quantity | Source |
|---|---|
| Satellite orbit, pass geometry, slant range, range rate | Real — live TLE from Celestrak, SGP4 propagation |
| Timing advance, Doppler, residuals computed from that geometry | Real — direct physics |
| Residual frequency tolerance (±0.1 ppm for NTN UE) | Published 3GPP — TS 38.101-5 |
| Cyclic prefix duration (4.69 µs @ 15 kHz SCS, normal CP) | Published 3GPP — TS 38.211 numerology |
One honest caveat. The ±0.1 ppm figure is a formally specified NTN UE requirement, defined against the ideally pre-compensated reference uplink frequency — exactly a limit on residual Doppler. The cyclic prefix, by contrast, is not a formally specified "maximum residual timing advance." It's used here as the physically meaningful yardstick: uplink symbols must land inside the CP window or they smear into the next symbol. It's the right constraint and the right order of magnitude, but it is my chosen benchmark, not a quoted 3GPP limit.
- Rel-17 UE-side pre-compensation using GNSS + broadcast ephemeris — 3GPP NTN overview (3gpp.org/technologies/ntn-overview); Ericsson, Satellite direct-to-device communication; Lin et al., 5G from Space: An Overview of 3GPP Non-Terrestrial Networks (arXiv:2103.09156).
- NTN UE residual frequency accuracy, ±0.1 ppm vs ideally pre-compensated reference — 3GPP/ETSI TS 38.101-5.
- Cyclic prefix durations by subcarrier spacing — 3GPP TS 38.211.
ntn-5g-ntn-precomp/
├── aiprecomp/
│ ├── tle_fetch.py # live TLEs from Celestrak
│ ├── pass_geometry.py # one real pass: range + range rate over time
│ ├── timing_doppler.py # TA and Doppler physics (from Week 5)
│ ├── nr_tolerances.py # the 3GPP limits residuals must fit inside
│ ├── ue_precomp.py # the Rel-17 UE: GNSS + ephemeris -> correction
│ ├── analysis.py # both findings + closed-form cross-check
│ └── main.py # ties it together, produces the plot
├── tests/
│ ├── sample_tle.py # verified real-format TLE
│ └── test_precomp.py
├── requirements.txt
├── LICENSE
└── README.md
Week 6 of a 12-week public engineering portfolio combining AI, 5G/6G and satellite communications. Week 1: link budget · Week 2: pass predictor · Week 3: multi-sat scheduler · Week 4: ML predictor · Week 5: timing & Doppler.