Let's start with the property that shapes everything else about GPS. Every satellite in the constellation transmits on the same carrier, 1575.42 MHz, at the same time. Nothing separates them in frequency and nothing separates them in time. What separates them is a code, and each satellite has its own. That single decision explains the shape of a GPS receiver. It explains why the signal arrives below the noise floor, why a receiver has to search before it can listen, and why the search runs over two dimensions instead of one. I will build the note in that order. First the code itself, then how a receiver finds a satellite using it, then how it decides that what it found is real. The numbers I quote come from a software receiver I wrote against an SDR, so they are measurements rather than textbook values. One more thing before starting. Almost all of this is about a single signal, the civilian C/A code on L1, because that is what an ordinary receiver uses and what I was able to measure. GPS broadcasts several others, and they have a section of their own near the end.
- Executive Summary
- Acronym
- Why do all GPS satellites transmit on the same frequency?
- How is the C/A Gold code generated?
- What makes a Gold code good for this job?
- How does a PRN number identify a satellite?
- What is inside the navigation message?
- How do the ephemeris and almanac fit into the message frame?
- Is the navigation message protected by channel coding?
- How is the message spread into chips?
- How does the navigation message become a transmitted signal?
- How does a receiver get from IQ samples to a decoded message?
- How do you acquire a signal that sits below the noise?
- Why is the code phase searched with an FFT instead of by sliding?
- How do you tell a real satellite from the loudest noise in the search?
- How long should you integrate, and why does more eventually hurt?
- What can you work out from Doppler alone?
- What other GPS signals are there?
- How would you build this receiver, one prompt at a time?
- Reference
Executive Summary
This table is the short version of the note. Each row names one idea, states it in a sentence, and says what follows from it for a receiver.
Shared carrier |
Every satellite transmits on 1575.42 MHz at the same time, separated only by its own code. |
The receiver cannot tune to one satellite. It must correlate against a known code to pull one out. |
C/A code |
1023 chips at 1.023 Mcps, so the code repeats exactly every millisecond. One code per satellite. |
One millisecond of samples already contains a whole code period, which sets the natural block size. |
PRN as identity |
32 codes are assigned to satellites out of a family of 1025, and there is no identity field anywhere in the signal. |
The satellite is named by whichever replica produces a peak, which is safe because no two codes agree better than -23.9 dB. |
Navigation message |
A 50 bps payload rides on the code, in 30 bit words, 300 bit subframes and 1500 bit frames. |
Every wait the receiver makes is one of these. Six seconds for a subframe, thirty for a usable orbit, twelve and a half minutes for the whole almanac. |
Channel coding |
Six parity bits per 30 bit word, an extended Hamming code, and nothing else. |
There is no forward error correction on L1 C/A. A failed word is discarded and read again 6 s later. |
Spreading |
Every data bit is multiplied by 20460 code chips, so 50 bps occupies about 2 MHz. |
That trade buys 43 dB of processing gain, which is the entire reason a buried signal can be recovered. |
One reference clock |
Carrier, chip rate and data rate all come from 10.23 MHz by integer ratios. |
Carrier and code Doppler are locked at 1540 to 1, so a measurement of one checks a measurement of the other. |
Receiver chain |
Seven stages undo the transmit chain in reverse, each one reducing the rate it works at. |
3.84 million samples per second go in and 50 bits per second come out, and the first fix waits on the data rather than the signal. |
Processing gain |
The signal arrives roughly 20 dB below the thermal noise and is invisible in a spectrum. |
Nothing can be measured before correlation. Acquisition comes first, and everything else follows it. |
Two-dimensional search |
Code phase is unknown over 1023 chips, and Doppler is unknown over roughly 10 kHz. |
The receiver searches a grid. The FFT collapses the code phase axis; Doppler stays a loop. |
Detection test |
A peak measured against the average floor is easy to fool. A peak measured against the next highest peak is not. |
On a real antenna the best noise ratio reached 1.4, while a satellite reached 12.2. |
Signal family |
L1 C/A is one of seven GPS signals across three frequencies, and the oldest of them. |
The newer ones carry a dataless pilot and ten times the chips, which removes two of the limits this note runs into. |
Build order |
The order you build a receiver in is not the order the signal flows through it. |
The code generator and a synthetic signal come first, because nothing after them can be debugged until they are right. |
Integration limit |
The code drifts as well as the carrier, by the Doppler divided by 1540. |
Past roughly 150 ms the peak smears faster than the noise averages down, so longer is worse. |
Acronym
These are the terms this note uses without expanding them again after the first time.
Analogue to Digital Converter |
|
Aeronautical Radio Navigation Service, an internationally protected band |
|
Bose Chaudhuri Hocquenghem, a family of block codes |
|
Binary Offset Carrier, a modulation that splits the spectrum into two lobes |
|
Binary Phase Shift Keying |
|
Coarse/Acquisition, the civilian GPS code on L1 |
|
Code Division Multiple Access |
|
Carrier to noise density ratio, in dB-Hz |
|
The navigation message on the modernized L2C and L5 signals |
|
Cyclic Redundancy Check |
|
Delay Lock Loop, the loop that tracks code phase |
|
Forward Error Correction |
|
Fast Fourier Transform |
|
Hand Over Word, the second word of every subframe |
|
High Power Amplifier |
|
In-phase and Quadrature, the two components of a complex baseband sample |
|
The GPS carrier at 1575.42 MHz |
|
The civilian signal on L2, added from 2005 |
|
Low Density Parity Check code |
|
Linear Feedback Shift Register |
|
Low Noise Amplifier |
|
The legacy navigation message, the one carried on L1 C/A |
|
The GPS navigation message, the payload carried on the code |
|
Numerically Controlled Oscillator |
|
Phase Lock Loop |
|
Pseudo Random Noise. Used for the spreading sequence itself, and as a number for the satellite that transmits it |
|
Power Spectral Density |
|
Position, Velocity and Time, the navigation solution |
|
The encrypted military GPS code, carried in quadrature with C/A on L1 |
|
Satellite Based Augmentation System, such as WAAS or EGNOS |
|
Software Defined Radio |
|
Space Vehicle Number, the permanent identity of the satellite hardware |
|
Telemetry Word, the first word of every subframe |
|
Time Multiplexed BOC, the modulation used by the L1C pilot |
|
Time Of Week, carried in the handover word |
Why do all GPS satellites transmit on the same frequency?
You could separate satellites by frequency, and some systems do. GPS does not. Every satellite uses L1 at 1575.42 MHz, and they all transmit continuously. The separation is CDMA: each satellite multiplies its data by a different fast code, and the receiver recovers one satellite by multiplying with the matching code again.
That choice buys three things at once, which is why it survives. Every satellite can use the full bandwidth, so nothing is wasted dividing the band. A receiver needs one filter and one front end, not one per satellite. And the wide code spreads the signal, which is what lets it sit under the noise and still be recovered.
The cost is that the receiver has real work to do before it can hear anything. With frequency separation you tune and listen. With code separation you must already know the code, and you must know where in the code you are. A GPS receiver spends its first seconds searching for exactly that, and the rest of this note is about that search.
The code is the address : Nothing in the frequency or the timing tells you which satellite you are hearing. Only the code does, so PRN 15 means the satellite whose code you matched.Spreading is what hides the signal : The same wide code that separates satellites also spreads the power thin. That is why a GPS signal is invisible in a spectrum and appears only after correlation.Searching is not a startup delay to be optimised away : It is the direct consequence of choosing code separation. Any CDMA receiver pays it.
How is the C/A Gold code generated?
Three names get used for one sequence, and they are worth settling before generating it. The sequence a satellite spreads with is its C/A code, which is what the signal calls it. It is built by a method called a Gold code, and that construction is what this section is about. It is also a PRN code, meaning a sequence that looks random but is entirely deterministic. PRN doubles as the satellite name, so PRN 15 is both the satellite and the code it transmits. Generating the C/A Gold code for satellite 15 and generating PRN 15 are the same operation.
The three terms are not interchangeable in general, only here. Every C/A code is a Gold code and every Gold code is a PRN code, but neither statement reverses. The military P(Y) code is a PRN code and is not a Gold code, and Galileo uses PRN codes that are stored in a table rather than generated by registers at all.
The C/A code is a Gold code, and a Gold code is built from two maximal-length sequences rather than one. Each sequence comes from a 10 stage LFSR, so each runs for 1023 chips before repeating. The C/A code runs at 1.023 Mcps, which means one full code period takes exactly one millisecond. That millisecond is worth remembering, because it becomes the natural processing block later.
Both registers start with every stage set to one. The first register, G1, feeds back the exclusive OR of stages 3 and 10. The second, G2, feeds back the exclusive OR of stages 2, 3, 6, 8, 9 and 10. Those two polynomials are fixed and identical for every satellite, so on their own they cannot tell satellites apart.
Written as generator polynomials, those two feedback arrangements are the following. The exponents are exactly the stage numbers that are fed back.
g1(x) = 1 ⊕ x3 ⊕ x10
g2(x) = 1 ⊕ x2 ⊕ x3 ⊕ x6 ⊕ x8 ⊕ x9 ⊕ x10
The same thing as a recurrence is what you actually write in code. Each new output bit is the exclusive OR of earlier output bits at the tapped distances.
G1[n] = G1[n − 3] ⊕ G1[n − 10]
G2[n] = G2[n − 2] ⊕ G2[n − 3] ⊕ G2[n − 6] ⊕ G2[n − 8] ⊕ G2[n − 9] ⊕ G2[n − 10]
with every stage of both registers set to 1 before the first clock, and ⊕ meaning addition modulo 2
Both registers are maximal length, so each runs through every non-zero 10 bit state before repeating. That fixes the period and everything that follows from it.
N = 210 − 1 = 1023 chips
Tcode = N / Rc = 1023 / 1.023 × 106 = 1 ms
The difference between satellites comes from one place only. G1 always contributes its last stage. G2 contributes the exclusive OR of two stages, and which two depends on the satellite. PRN 1 uses stages 2 and 6, PRN 2 uses stages 3 and 7, and so on through the 32 assignments. Choosing a different pair delays G2 relative to G1, and a different delay produces a different Gold code.
That gives two ways to write the same output, and both appear in practice. The first is the definition, where the satellite is a delay applied to G2.
Ci[n] = G1[n] ⊕ G2[(n + Ni) mod 1023]
Ni is the code phase delay assigned to satellite i
The second is how the delay is realised in hardware, and it is the form a generator implements. Taking two stages of G2 instead of one produces that delay without a delay line.
Ci[n] = G1(10)[n] ⊕ G2(s1(i))[n] ⊕ G2(s2(i))[n]
G(k) is the output of stage k, and (s1, s2) is the tap pair for satellite i, so (2, 6) for PRN 1 and (3, 7) for PRN 2
One more step is needed before the code can be correlated. The generator produces bits, and correlation needs a signal that is symmetric about zero, so each bit is mapped to plus or minus one.
ci[n] = 1 − 2 Ci[n]
so a 0 bit becomes +1 and a 1 bit becomes −1, which makes a sum of 1023 chips the correlation value
Figure 1 puts all of that into one picture. The two registers run side by side with their feedback taps marked. The tap pair for PRN 1 is drawn on G2, and the exclusive OR at the right is the one that produces Ci.
Figure 1. The per-satellite choice is small and late. Both registers and both feedback polynomials are common to the whole constellation, and only the G2 tap pair distinguishes one satellite from another.
C/A code, Gold code and PRN name the same sequence here : The first says what it is for, the second how it is built, and the third how it behaves. Generating PRN 15 is generating that satellite's C/A code.Two registers, not one : A single maximal-length sequence would give one code. Combining two, with a relative delay, gives a family of 1023 codes from the same hardware.The tap pair is the satellite identity : PRN 1 selects G2 stages 2 and 6, PRN 2 selects 3 and 7. The registers themselves never change.One millisecond per period : 1023 chips at 1.023 Mcps is exactly 1 ms. That is why receivers process in 1 ms blocks and why the correlation can be circular.Two equivalent forms of the same output : The definition delays G2 by Ni chips. An implementation instead exclusive ORs two stages of G2, which produces that delay with no delay line.Check the generator before trusting it : IS-GPS-200 publishes the first ten chips of every PRN in octal. My generator reproduced all 32, which is a cheap test that catches a wrong tap or an off-by-one in the shift.
What makes a Gold code good for this job?
Any code would separate satellites if the receiver had infinite time and no noise. Real receivers have neither, so the code has to satisfy three properties at once. Gold codes were chosen because they satisfy all three, not because they are the only codes that work.
The first property is balance. Across 1023 chips the number of ones and zeros differs by exactly one, so the code carries almost no direct current. If it did, the transmitter would waste power on a component that carries no information, and the receiver would see a bias it has to remove.
The second is a sharp autocorrelation. Correlated against itself the code gives a large value at zero delay and a small value everywhere else. That sharpness is what turns a correlation peak into a timing measurement, and it is the reason a receiver can resolve arrival time to a fraction of a chip.
The third is low cross-correlation. Correlated against a different satellite's code the result stays near zero. This matters more than it first appears, because all 32 satellites arrive together on one frequency. When you correlate for one satellite, the other 31 are still present, and low cross-correlation is what stops them from summing into a false peak.
These are measurable rather than theoretical. Correlating PRN 1 against PRN 2 over a full period gave 1 out of a possible 1023, and the balance of each code came out as the expected single chip. Both numbers fall straight out of the generator and are worth checking whenever a code table is entered by hand.
Balance keeps the carrier clean : Ones and zeros differ by one chip in 1023, so the spread signal carries essentially no direct current.Autocorrelation gives the timing : A sharp peak at zero delay and little elsewhere is what makes a code phase measurable, and one chip corresponds to about 293 m of range.Cross-correlation protects against the other satellites : PRN 1 against PRN 2 measured 1 out of 1023, so the 31 satellites you are not looking for stay in the noise.Neither property survives a typo : A single wrong tap still produces a code that looks random. Only the published chips and these two measurements will catch it.
How does a PRN number identify a satellite?
There is no identity field anywhere in the GPS signal at this level. Nothing is transmitted that says "this is satellite 15". The receiver names the satellite by which replica produced a peak, and that is the entire mechanism. It works only if no two codes in the set can be mistaken for each other, and whether they can is a measurable property rather than an assumption.
The set is smaller than the family it comes from. A Gold family built on two 10 stage registers has 1025 members, which is the 1023 possible relative delays of G2 against G1 plus the two original m-sequences. GPS assigns only 32 of those to satellites. The rest are spare capacity, and some of it is spent elsewhere.
1 to 32 |
GPS satellites |
The only ones a plain GPS receiver searches. The constellation holds about 31 active satellites, so the set is sized to the constellation with almost nothing to spare. |
33 to 37 |
Reserved for other uses, including ground transmitters |
IS-GPS-200 notes that the codes for 34 and 37 are identical. That is harmless here precisely because neither number is a satellite assignment. |
120 to 158 |
SBAS satellites, such as WAAS, EGNOS, MSAS and GAGAN |
Same construction and same chip rate, so an ordinary C/A correlator can receive them once it is given the extra replicas. |
Choosing 32 out of 1025 is not arbitrary. The whole family is not equally good, and the assignments were selected so that every pair in the set behaves well against every other pair. Gold codes have a property that makes this checkable rather than a matter of luck. For a family built on registers of length n, the cross-correlation between any two members, at any relative shift, can only ever take one of three values.
t(n) = 1 + 2(n+2)/2 = 1 + 26 = 65 for n = 10
Rij(k) ∈ { −t(n), −1, t(n)−2 } = { −65, −1, +63 }
out of a maximum of 1023, which is what a code correlated against itself at zero shift returns
Three values, and nothing in between. That is a strong claim, so it is worth checking rather than quoting. Generating all 32 codes and correlating every pair at every shift is 496 pairs at 1023 shifts, which is 507408 correlations, and it takes a second to compute.
Figure 2 is that measurement. Every one of the half million results took one of the three predicted values.
Figure 2. The identity claim reduces to one number. A wrong code can reach at most 65 against a right code's 1023, so a peak identifies its satellite with about 24 dB to spare.
Three quarters of the time the answer is exactly -1, which is the best a sequence of odd length can do. Out of 1023 chips, 511 agree and 512 disagree. The two codes are as close to orthogonal as they can get without being the same length as an even number.
The same bound covers the other half of the identity question. A code must not be confusable with a shifted copy of itself either. A receiver would then read the right satellite at the wrong code phase, and that becomes a range error of hundreds of kilometres. Correlating each code against itself at every non-zero shift gives the same three values and the same worst case of 65. Both halves of the claim rest on that single number.
What -23.9 dB means in practice is a limit rather than a guarantee. If one satellite arrives more than about 24 dB stronger than another, its cross-correlation against the weak satellite's code can reach the same height as the weak satellite's own peak. The receiver then has two candidates of equal size and no way to choose between them.
GPS mostly avoids this by arriving evenly. The shaped transmit antenna and the small spread in range keep the satellites within a few dB of each other, so 24 dB of margin is never approached in open sky. It does bite indoors, under foliage, and anywhere a repeater or a jammer puts one strong signal alongside weak ones. That is one reason the modernized signals moved to 10230 chip codes, because a ten times longer code pushes the same bound roughly 10 dB further down.
One more thing about PRN numbers catches people out. A PRN is not a permanent property of a spacecraft. It is a code assignment, and when a satellite is retired its PRN can be handed to a replacement. The hardware has a separate and permanent identity, the SVN, and the two are tracked independently. A receiver reports PRN because that is what it actually measured, while constellation status listings give both.
In this note's own measurements the identity claim is what made validation possible. The detections were PRN 10, 15, 18, 20, 23, 24 and 32, and every one of them was independently predicted to be above the horizon by the published almanac. That check only means something because a peak on replica 15 really does imply satellite 15.
The code is the only identity : Nothing in the signal announces a satellite number. The receiver names the satellite by which replica produced a peak.32 chosen from 1025 : The Gold family has 1025 members and GPS uses 32 for satellites, 33 to 37 for other purposes, and 120 to 158 for SBAS.Three values and nothing else : Across all 496 pairs at all 1023 shifts, the cross-correlation was only ever -65, -1 or +63, and it was -1 three quarters of the time.The whole claim is one number : A wrong code reaches at most 65 against a right code's 1023, which is -23.9 dB, and shifted copies of the same code are bounded identically.24 dB is a limit, not a guarantee : A satellite 24 dB stronger than another can forge that other satellite's peak, which is why longer codes were chosen for the modern signals.PRN is not SVN : A PRN is a reassignable code slot and the SVN is the spacecraft. A retired satellite's PRN can reappear on a new one.
What is inside the navigation message?
The code separates the satellites, but it carries no information on its own. A receiver that has locked onto PRN 15 knows which satellite it is hearing and when the signal arrived. It still does not know where that satellite was, or what its clock was reading. Both of those come from the payload, and the payload is a very slow data stream riding on top of the code.
Slow is the right word. The navigation message runs at 50 bits per second, which is roughly the rate of a 1960s teleprinter. That is not an oversight. The data rate was chosen to be low enough that each bit spans many code periods. The receiver needs that redundancy to recover a bit at all from a signal buried under the noise.
The message is organised in a strict hierarchy, and the sizes matter to a receiver because they set how long it has to wait for each thing it needs. Thirty bits make a word, ten words make a subframe, and five subframes make a frame.
1 word = 30 bits = 0.6 s | 1 subframe = 10 words = 300 bits = 6 s
1 frame = 5 subframes = 1500 bits = 30 s
Each subframe carries a different part of what the receiver needs. Subframe 1 holds the satellite's own clock correction, the week number and a health flag. Subframes 2 and 3 hold the ephemeris, which is a precise description of this one satellite's orbit. Subframes 4 and 5 hold the almanac, a coarse description of every satellite in the constellation, together with the ionospheric model and the offset between GPS time and UTC.
The difference between the ephemeris and the almanac is the one worth holding onto, because it decides what a receiver can do and how quickly. The ephemeris is accurate to metres but describes one satellite and expires in a few hours. The almanac is accurate to kilometres but describes all of them and stays usable for months. Ranging needs the ephemeris. Knowing which satellites to look for needs only the almanac.
Subframes 4 and 5 are too small to hold the whole constellation at once, so they are paged. Each one cycles through 25 different pages, one page per frame. A receiver therefore sees a complete ephemeris after 30 seconds but must wait 25 frames, or 12.5 minutes, to collect the entire almanac.
Two housekeeping words open every single subframe, which is what lets a receiver find its place in a stream it joined partway through. The telemetry word begins with the fixed preamble 10001011, and that pattern is what frame synchronisation searches for. The handover word that follows carries the time of week, so once those two words are read the receiver knows exactly where it is in GPS time.
Figure 3 lays the hierarchy out at all three scales, from the 30 second frame down to the individual word.
Figure 3. Every wait a GPS receiver makes is visible in this hierarchy. Six seconds for a subframe, thirty for an orbit it can range with, and twelve and a half minutes for a picture of the whole constellation.
The code and the payload are separate problems : The code tells you which satellite and when the signal arrived. The payload tells you where that satellite was and what its clock read.50 bps is a deliberate choice : A slow bit spans many code periods, and that redundancy is what allows a bit to be recovered from a signal below the noise.Ephemeris and almanac are not the same thing : The ephemeris is metre accurate for one satellite and expires in hours. The almanac is kilometre accurate for all of them and lasts months.The preamble is how a receiver finds its place : Every subframe opens with 10001011 followed by the time of week, so a receiver that joined partway through can synchronise without waiting for a frame boundary.My receiver never read any of this : Acquisition stops at code phase and Doppler. That is why the orbits in this note came from the published almanac rather than from decoded subframes.
How do the ephemeris and almanac fit into the message frame?
The previous section said which subframes carry the orbit data. This one is about how it is packed into them, because the packing is what a receiver has to unpick, and it explains two things that otherwise look arbitrary: why the ephemeris needs two whole subframes while a satellite's almanac fits in a single page, and how a receiver knows the pieces it has collected belong together.
Start with the space available. Every word is 30 bits, but six of them are parity, so only 24 carry data. Ten words make a subframe, and the first two are always the telemetry and handover words, so the payload of any subframe is eight words, or 192 data bits. Everything below has to fit in that budget.
WN — week number |
3 |
10 |
1 |
weeks |
URA — SV accuracy index |
3 |
4 |
— |
index |
SV health |
3 |
6 |
— |
flags |
IODC — issue of data, clock |
3 + 8 |
10 |
— |
— |
TGD — group delay |
7 |
8 signed |
2−31 |
seconds |
toc — clock reference time |
8 |
16 |
24 |
seconds |
af2 — clock drift rate |
9 |
8 signed |
2−55 |
s/s2 |
af1 — clock drift |
9 |
16 signed |
2−43 |
s/s |
af0 — clock bias |
10 |
22 signed |
2−31 |
seconds |
IODE — issue of data, ephemeris |
3 |
8 |
— |
— |
Crs — radius, sine harmonic |
3 |
16 signed |
2−5 |
metres |
Δn — mean-motion difference |
4 |
16 signed |
2−43 |
semicircles/s |
M0 — mean anomaly at toe |
4–5 |
32 signed |
2−31 |
semicircles |
Cuc — latitude, cosine harmonic |
6 |
16 signed |
2−29 |
radians |
e — eccentricity |
6–7 |
32 |
2−33 |
dimensionless |
Cus — latitude, sine harmonic |
8 |
16 signed |
2−29 |
radians |
√A — square root of semi-major axis |
8–9 |
32 |
2−19 |
metres1/2 |
toe — ephemeris reference time |
10 |
16 |
24 |
seconds |
Cic — inclination, cosine harmonic |
3 |
16 signed |
2−29 |
radians |
Ω0 — ascending node at weekly epoch |
3–4 |
32 signed |
2−31 |
semicircles |
Cis — inclination, sine harmonic |
5 |
16 signed |
2−29 |
radians |
i0 — inclination at toe |
5–6 |
32 signed |
2−31 |
semicircles |
Crc — radius, cosine harmonic |
7 |
16 signed |
2−5 |
metres |
ω — argument of perigee |
7–8 |
32 signed |
2−31 |
semicircles |
ΩDOT — rate of right ascension |
9 |
24 signed |
2−43 |
semicircles/s |
IODE — issue of data (repeated) |
10 |
8 |
— |
— |
IDOT — rate of inclination |
10 |
14 signed |
2−43 |
semicircles/s |
The complete broadcast ephemeris and clock parameters, subframes 1 to 3. Angles are in semicircles (multiply by π for radians); signed fields are two’s complement; each raw integer is multiplied by its scale factor to give the value in the unit shown. IODE ties the two orbit halves to the clock.
Several of these fields are larger than a single word. The square root of the semi-major axis and the mean anomaly are 32-bit quantities, for instance, so they are split across two words, the high bits in one and the low bits in the next, and the receiver reassembles them before use. Every field also has a fixed scale factor and unit set by IS-GPS-200: the raw integer is multiplied by its scale to recover the physical value. Getting a single scale or bit position wrong puts the satellite kilometres from where it really is, which is why this part of a decoder is checked against a known reference rather than trusted. Figure 4 shows which word each parameter rides in, and which of them straddle two.
IODE(subframe 2) = IODE(subframe 3) = IODC(subframe 1) low 8 bits → one consistent orbit
When they disagree, an upload landed mid-collection, and the receiver simply waits for a clean, matching set on the next pass rather than mixing the two.
Figure 4. The orbit parameters packed word by word. The wide fields — √A, e, M0 and the three angles Ω0, i0 and ω — each straddle two words and are reassembled before scaling. The IODE tag, shaded, is what proves the two subframes describe the same orbit.
A single almanac page holds a stripped-down version of the same orbit: eccentricity, a reference time, an inclination offset, the node and its rate, the square root of the semi-major axis, the argument of perigee, the mean anomaly, and a two-term clock. Fewer parameters than the ephemeris and coarser quantisation, and no harmonic corrections at all, which is exactly why it is kilometre accurate rather than metre accurate — and why it fits in a page where the ephemeris does not. The full page contents are listed below.
e — eccentricity |
16 |
2−21 |
dimensionless |
toa — almanac reference time |
8 |
212 |
seconds |
δi — inclination offset from 0.30 |
16 signed |
2−19 |
semicircles |
ΩDOT — rate of right ascension |
16 signed |
2−38 |
semicircles/s |
SV health |
8 |
— |
flags |
√A — square root of semi-major axis |
24 |
2−11 |
metres1/2 |
Ω0 — right ascension at toa |
24 signed |
2−23 |
semicircles |
ω — argument of perigee |
24 signed |
2−23 |
semicircles |
M0 — mean anomaly |
24 signed |
2−23 |
semicircles |
af0 — clock bias |
11 signed |
2−20 |
seconds |
af1 — clock drift |
11 signed |
2−38 |
s/s |
The complete almanac parameters, one satellite per page of subframes 4 and 5. The inclination is sent as an offset δi from a nominal 0.30 semicircles, so i0 = 0.30 + δi. The coarser scale factors and the absence of any harmonic terms are why the almanac is kilometre accurate, not metre accurate.
Not every page is an almanac. Subframe 4 reserves particular pages for other things a receiver needs only occasionally. Page 18 carries the ionospheric model coefficients and the offset between GPS time and UTC; page 25 carries configuration and health for the upper satellites; several pages are reserved. Page 18 is the one the receiver in this note would need in order to correct for the ionosphere, and because it appears once per cycle it is the slowest single item in the whole message to collect.
The shape of the whole arrangement is the one already drawn in Figure 3. What this section adds is the reason for it: the ephemeris is large, describes one satellite, and repeats every frame so a receiver can range within thirty seconds; the almanac is small per satellite, spread across the constellation, and paged slowly because nothing about it needs to be fresh.
Only 24 of every 30 bits are data : six are parity and the first two words are housekeeping, so each subframe has 192 data bits to spend.The orbit is elements, not a position : the ephemeris sends the shape and orientation of the orbit plus correction terms, and the receiver computes the position from them at whatever instant it needs.Two subframes, one orbit, tied by IODE : subframes 2 and 3 must carry the same issue-of-data tag as the clock in subframe 1, or the halves are from different uploads and the set is discarded.The almanac buys coverage with time : one satellite per page, 25 pages, so the whole constellation takes 12.5 minutes, and each entry is a coarse orbit with no corrections.Page 18 is the slow one : the ionospheric and UTC parameters ride once per almanac cycle, so a receiver that wants them waits longer than for anything else in the message.
Is the navigation message protected by channel coding?
There is some, and it is far less than a modern link would carry. The six parity bits at the end of every word are the whole of it. There is no convolutional code, no LDPC, no interleaving and no CRC anywhere in the legacy message, which surprises most people the first time they look.
What is there is an extended Hamming code, written as (32, 26). The two numbers do not match the 30 bits of a word, and the reason is worth following. Twenty four of the information bits are the new data. The other two are the last two bits of the previous word, which the encoder reuses but does not send again. So 26 bits go into the encoder, 32 come out, and 30 of them are transmitted.
That reuse chains the words together. A receiver cannot check a word in isolation, because it needs the tail of the word before it. That is a deliberate part of the design rather than an accident of the arithmetic.
Two operations produce a transmitted word. The data bits are first inverted or left alone, depending on the last bit of the previous word. The six parity bits are then computed over the result.
Di = di ⊕ D30* for i = 1 … 24
D25 = D29* ⊕ d1 ⊕ d2 ⊕ d3 ⊕ d5 ⊕ d6 ⊕ d10 ⊕ d11 ⊕ d12 ⊕ d13 ⊕ d14 ⊕ d17 ⊕ d18 ⊕ d20 ⊕ d23
D29* and D30* are the last two transmitted bits of the previous word. The remaining five parity bits follow the same shape over different subsets of d, and IS-GPS-200 lists all six.
The minimum distance of this code is 4, which means it could correct one error in a word or detect two. Receivers normally take the second option and do not correct anything. A word that fails parity makes its whole subframe suspect, so the subframe is thrown away and read again on the next pass 6 seconds later. The ephemeris does not change for hours, so waiting costs nothing that matters.
The inversion term does a second job that has nothing to do with errors. A carrier tracking loop on a BPSK signal cannot tell a correct lock from one 180 degrees out, so the whole bit stream may arrive complemented. Because every word is inverted by the previous word's last bit, an inverted stream still passes parity, so the parity check cannot detect the ambiguity on its own. The receiver resolves it by testing the telemetry preamble against its own complement, which is one more job that fixed 10001011 pattern is doing.
None of this looks like enough protection, and on its own it would not be. The margin comes from somewhere else entirely. Spreading buys about 43 dB of processing gain, and each data bit is carried by 20 full code periods. By the time a bit reaches the decision stage it has already been averaged hard. Channel coding was never asked to close the link, only to catch the bits that slipped through.
Figure 5 follows one word through both operations, and shows where the two bits from the previous word enter.
Figure 5. The two bits fed back from the previous word are doing two different jobs at once. They extend the parity check across the word boundary, and they make the encoding differential, which is what keeps an inverted carrier lock readable.
The modernized signals were designed four decades later and look nothing like this. They carry real forward error correction, and they carry a CRC rather than relying on the code itself to flag a bad message.
L1 C/A, LNAV |
50 bps |
Extended Hamming (32, 26) per 30 bit word. No interleaving. |
The parity itself, used to detect rather than to correct. |
L2C and L5, CNAV |
25 and 50 bps |
Rate 1/2 convolutional, constraint length 7, generators 171 and 133 octal, Viterbi decoded. |
CRC-24Q over each 300 bit message. |
L1C, CNAV-2 |
50 bps |
BCH for the short time field, rate 1/2 LDPC for the data subframes, with block interleaving. |
CRC-24Q, carried inside the LDPC protected subframes. |
The practical difference is what happens at low signal level. An LNAV receiver that cannot read a subframe cleanly has no option but to try again, so its data threshold sits several dB above its tracking threshold. A CNAV receiver decodes through the same conditions, which is why the modern signals are usable indoors and under foliage where the legacy message is not.
There is no FEC on L1 C/A : Six parity bits per word is the entire channel coding, and the code is used for detection rather than correction.The (32, 26) does not fit in 30 bits by design : Two of the 26 information bits are the previous word's last two, reused by the encoder and never sent twice.The chain also fixes the phase ambiguity : Inverting each word by D30* makes the encoding differential, so an inverted carrier lock still passes parity and the preamble settles the polarity.Retransmission is the error strategy : A bad subframe is dropped and read again 6 s later, which works because the ephemeris is unchanged for hours.Spreading is what closes the link : About 43 dB of processing gain and 20 code periods per bit do the work that coding does in a modern system.The modern signals are conventional : L2C and L5 use a rate 1/2 convolutional code with CRC-24Q, and L1C uses LDPC with interleaving.
How is the message spread into chips?
Everything so far has been about bits. The message is 50 of them per second, and the parity encoder turned 24 into 30 without changing that scale. Spreading is the step that leaves that scale behind. It takes the encoded bit stream and turns it into a chip stream running 20460 times faster, and it is the operation that makes GPS work the way it does.
The operation itself is trivial. Each data bit is added modulo 2 to the code chips that fall underneath it, which is the same exclusive OR that built the code in the first place.
di[n] = D[n / 20460] ⊕ Ci[n mod 1023]
D is the encoded 50 bps message, Ci the C/A code of satellite i, and n counts chips
The two divisions in that line are the whole of the timing. Dividing n by 20460 says which data bit this chip belongs to, and 20460 chips is 20 complete code periods, so the code runs through itself 20 times inside every bit. Taking n modulo 1023 says which chip of the code this is, and the modulo is there because the code repeats rather than continuing.
A receiver does not work in bits, so it is worth writing the same operation the other way. Mapping both sequences to plus and minus one turns the exclusive OR into an ordinary multiplication, and this is the form that appears in the correlator.
s[n] = D±[n / 20460] ⋅ ci[n mod 1023]
with each sequence mapped by 1 − 2b, so that a modulo 2 sum becomes a product of ±1 values
That form makes the effect obvious. A data bit of 0 leaves the code untouched, and a data bit of 1 inverts every chip of it. The message is not carried alongside the code. It is carried as the sign of the code, which is why a receiver that has found the code has also, without any extra work, found the data.
Figure 6 shows one bit boundary at both scales, using the real first chips of PRN 1.
Figure 6. The spread signal is the code itself, flipped in sign wherever the data bit is 1. A receiver that correlates against the plain code therefore recovers the data as the sign of its own correlation peak.
What that costs and what it buys are both visible in the frequency domain. A 50 bps stream is a very narrow signal, a few tens of hertz wide. Multiplying it by a 1.023 Mcps sequence spreads it into a main lobe 2.046 MHz wide. The bandwidth of a modulated signal follows the rate of its fastest transitions, and the chips are now the fastest thing present.
The total power does not change. It is the same transmitter and the same amplifier, so the power is simply redistributed over a much wider band. The power spectral density falls by the ratio of the two rates.
Gp = Rc / Rd = 1.023 × 106 / 50 = 20460
Gp in dB = 10 log10(20460) = 43.1 dB
That number is the processing gain, and it is the same 43 dB quoted earlier as the reason weak channel coding is sufficient. It is not free power. It is the payment for having spread the signal in the first place, collected back at the receiver when the correlation folds 2 MHz of bandwidth into 50 Hz again.
The asymmetry is the point. Despreading collapses the wanted signal because the receiver multiplies by the matching code, and the noise is not correlated with that code, so it stays spread. Signal power concentrates and noise power does not, and the difference is exactly the ratio above. Figure 7 shows both sides of the trade on one axis.
Figure 7. Spreading is a deliberate loss. It pushes the signal 43 dB into the noise, and the receiver gets that 43 dB back only for the one satellite whose code it is currently multiplying by.
One consequence is worth stating plainly, because it is easy to miss. My receiver never generates a chip stream for transmission, but it builds the same sequence anyway. The replica it correlates against is exactly the plus and minus one code above, and correlating is despreading. The acquisition search described later in this note is nothing more than this operation tried at every code phase and every Doppler until one of them lines up.
Spreading is one multiply : Modulo 2 on bits, or a product of plus and minus one values, and 20460 chips fall inside each data bit.The data rides as the sign of the code : A 0 bit passes the code through and a 1 bit inverts every chip, so finding the code also finds the data.The two divisions carry the timing : n over 20460 selects the data bit and n modulo 1023 selects the chip, and the modulo is there because the code repeats.Bandwidth is traded for gain : 50 bps becomes a 2.046 MHz main lobe, the PSD drops 43.1 dB, and the receiver takes that 43.1 dB back by correlating.The gain applies to one satellite at a time : Noise and the other 31 codes are not correlated with the replica, so they stay spread while the wanted signal collapses.Despreading and acquisition are the same operation : Correlating against the replica is despreading, and searching is just doing it at every code phase and Doppler.
How does the navigation message become a transmitted signal?
Three things have to end up in one waveform. There is the payload at 50 bits per second, the C/A code at 1.023 million chips per second, and the L1 carrier at 1575.42 MHz. What is striking about the transmit chain is that none of those three numbers is independent. All of them are derived from a single atomic reference by simple integer division or multiplication.
That reference is 10.23 MHz, and every other rate on the satellite falls out of it.
f0 = 10.23 MHz
fL1 = 154 f0 = 1575.42 MHz
Rc = f0 / 10 = 1.023 Mcps
Rd = f0 / 204600 = 50 bps
Deriving everything from one oscillator is what makes the system coherent. A measurement of the carrier frequency and a measurement of the code rate are then measurements of the same physical clock. The two agree by construction, so a receiver can use one to check the other. The Doppler seen on the code is the Doppler seen on the carrier divided by 1540, and that ratio is 154 times 10 rather than a coincidence.
Spreading has already happened by this point, so what arrives at the modulator is the chip stream at 1.023 Mcps rather than the 50 bps data. The modulator itself is therefore working at the chip rate, and the data rate no longer appears anywhere in the transmit chain.
The combined stream then modulates the carrier. L1 actually carries two signals in quadrature. The civilian C/A code sits on one phase and the military P(Y) code on the other, so a receiver for either one sees the other as noise.
s(t) = AP P(t) D(t) cos(2π fL1 t) + AC C(t) D(t) sin(2π fL1 t)
the C/A term is the one a civilian receiver uses, and it is transmitted about 3 dB stronger than the P term
After the modulator the signal is amplified and fed to a helical array pointed at the earth. That array is deliberately not a pencil beam. A satellite overhead is about 20000 km away, while one near the horizon is nearly 26000 km away. So the antenna is shaped to put more power at the edge of its pattern than at the centre. The result is that received power varies far less with elevation than the distance alone would suggest.
What arrives is still extremely weak. The specification guarantees no less than -158.5 dBW at the earth's surface, which is -128.5 dBm. In the roughly 2 MHz the C/A signal occupies, that sits about 20 dB below the thermal noise. Figure 8 follows the whole chain from the clock to the antenna.
Figure 8. The chain has no frequency synthesiser in it that is independent of the clock. That is what lets a receiver treat a carrier measurement and a code measurement as two views of the same quantity.
One oscillator sets every rate : 10.23 MHz gives the carrier by multiplying by 154, the chip rate by dividing by 10, and the data rate by dividing by 204600.Coherence is a design output, not an accident : Because carrier and code share a clock, code Doppler is carrier Doppler divided by 1540, which is exactly the 154 times 10 from those two dividers.L1 carries two signals in quadrature : C/A sits on one phase and P(Y) on the other, about 3 dB apart, and each looks like noise to a receiver built for the other.The antenna is shaped, not pointed : Edge of earth is 6000 km further away than nadir, so the pattern puts more gain at its edge to even out the received power.
How does a receiver get from IQ samples to a decoded message?
Everything up to here has been the transmitter. The rest of this note is the receiver, and before going into any one stage it helps to see the whole chain at once. There is a pattern to it. Every stage undoes something the transmitter did, and they undo it in the reverse of the order it was applied.
The transmitter spread, then modulated, then radiated. So the receiver receives, then strips the carrier, then despreads, and only after that does it see bits again. The parity and the framing come off last, because they went on first.
There is a second pattern that is easier to miss, and it is the one that makes the chain make sense. Each stage works at a lower rate than the one before it. The front end hands over millions of samples per second, and seven stages later what comes out is a position once per second, computed from 50 bits per second of data. Almost all of the work is throwing away rate while keeping information.
Figure 9 lays the seven stages out with the rate at each hand-off.
Figure 9. The chain is a rate ladder as much as a signal chain. A stage that cannot reduce its input rate is a stage that has not yet understood its input.
The same seven stages, with what each one costs in waiting, are worth setting out separately. The waiting is what dominates a cold start, and it is not caused by the signal processing.
RF front end and ADC |
Complex IQ at the sampling rate |
None |
Yes, the SDR does it |
Acquisition |
Coarse code phase and Doppler for each satellite present |
0.1 s of samples, more if the signal is weak |
Yes, and it is what the rest of this note is about |
Tracking |
Continuous prompt correlator values at 1 kHz |
Loops settle in well under a second |
No |
Bit synchronisation |
A 50 bps bit stream |
1 to 2 s of tracked signal |
No |
Frame synchronisation |
Subframe boundaries and the correct polarity |
Up to 6 s, since that is the subframe spacing |
No |
Parity check and field extraction |
Ephemeris, clock correction and almanac |
30 s for a complete ephemeris |
No, the published almanac was used instead |
Pseudorange and PVT solve |
Position, velocity and time |
Needs four satellites through every stage above |
No, a Doppler-only solve was used instead |
Stage seven is where the message hierarchy pays off, and the way it does so is neat enough to be worth spelling out. A pseudorange is the time the signal spent in flight, so the receiver has to know the exact instant the signal left the satellite. No single field carries that. It is assembled from every level of the hierarchy at once, each level supplying a finer digit than the one above it.
ttx = TOW × 6 s + nword × 0.6 s + nbit × 20 ms + ncode × 1 ms + φcode / 1.023 × 106
ρ = ( trx − ttx ) c
φcode is the code phase in chips, the same quantity acquisition estimates, and c is the speed of light
Read that from the top and every term comes from a different stage of the receiver. The time of week comes from frame synchronisation, the word and bit counts from framing and bit synchronisation, the code period count from tracking, and the fractional code phase from the correlator itself. One chip is about 293 m and the correlator resolves a fraction of it, which is where metre level ranging comes from.
The word pseudorange is doing real work in that equation. The receiver's own clock is a cheap crystal, so trx carries an unknown bias. That bias is the same for every satellite measured at the same instant, which is why it is solvable rather than fatal. It becomes a fourth unknown alongside the three coordinates, and that is the actual reason four satellites are needed rather than three.
The waiting in the table adds up to the number a user notices. A receiver starting with nothing must reach stage six before it can position at all, and stage six needs 30 seconds of clean data. That is the floor on a cold start, and it is set by the 50 bps data rate rather than by anything in the signal processing. A receiver that already holds a valid ephemeris skips straight from acquisition to a fix in about a second. That is why assisted GPS delivers the ephemeris over the network. It is also why a phone fixes so much faster than a standalone receiver that has been switched off for a week.
My own receiver stops after stage two, and stops deliberately. Acquisition alone answers which satellites are overhead and what their Doppler is, and that was enough to validate the front end and to attempt a position. What it gives up is ranging, because without tracking and framing there is no transmit time to subtract, so there is no pseudorange to solve with. The Doppler-only fix later in this note is the workaround, and its 25 km accuracy against a pseudorange fix's metres is a fair measure of what stages three to seven are worth.
The receiver runs the transmitter backwards : Carrier first, then code, then bits, then framing, because that is the reverse of the order they were applied.Every stage drops the rate : 3.84 Msps to 1 kHz at the correlator, 50 Hz after bit sync, one subframe per 6 s, and a fix per second.Transmit time is assembled, not read : TOW, word, bit, code period and code phase each supply one digit, from 6 s down to a fraction of a chip.The receiver clock bias is the fourth unknown : It is common to every satellite at one instant, so it is solved rather than calibrated, and that is why four satellites are needed.A cold start waits on data, not on signal : The 30 s floor is the ephemeris arriving at 50 bps, which is exactly what assisted GPS removes.Acquisition alone cannot range : Without tracking and framing there is no transmit time, so there is no pseudorange, which is why this note falls back on Doppler.
How do you acquire a signal that sits below the noise?
A GPS signal arrives at the antenna roughly 20 dB below the thermal noise. You cannot see it in a spectrum, and no amount of gain will change that, because gain lifts the noise with the signal. The only thing that separates them is the code, and using the code means correlating.
Correlation works because the code is known and the noise is not. Multiply the incoming samples by the correct code, aligned correctly, and every chip of the signal adds in step while the noise adds at random. Over a full 1023 chip period the signal grows faster than the noise, and the buried signal becomes a peak. That gain is what makes the whole system possible.
The catch is the word aligned. The receiver does not know when the code period started, because it does not know the distance to the satellite. It also does not know the exact carrier frequency, because the satellite is moving at several kilometres per second and the receiver's own oscillator is imperfect. So there are two unknowns before any measurement can be made, and both have to be searched.
Code phase is the first unknown, and it spans 1023 chips. Doppler is the second, and for a receiver on the ground it spans roughly plus or minus 5 kHz from satellite motion, plus whatever the local oscillator contributes. Figure 10 shows the resulting search space and how a receiver crosses it.
Figure 10. The two axes are not searched the same way. Code phase collapses into a single transform pair, so the cost of a search is set almost entirely by how many Doppler rows you ask for.
Gain comes from the code, not the amplifier : Correlating a full 1023 chip period lifts the signal out of noise it was buried in. An LNA cannot do this, because it raises both together.Two unknowns, not one : Code phase spans 1023 chips and Doppler spans roughly 10 kHz. Neither can be measured until the other is close enough.The Doppler axis is what costs time : In my receiver, moving from 33 Doppler bins to 201 took the same search from 5 seconds to 45.Acquisition is coarse on purpose : Its job is to find the cell, not to measure it precisely. Tracking loops refine both numbers afterwards.
Why is the code phase searched with an FFT instead of by sliding?
The direct way to search code phase is to slide. Shift the replica by one sample, correlate over a whole period, record the result, shift again. At 3.84 Msps a millisecond holds 3840 samples, so that is 3840 full correlations for one satellite at one Doppler. Multiply by 32 satellites and 201 Doppler bins and the arithmetic stops being practical.
The FFT removes that loop entirely. Correlation in the time domain is multiplication in the frequency domain. So the receiver takes one forward transform of the samples, multiplies by the conjugated transform of the replica, and takes one inverse transform. That produces the correlation at every delay at once. The output is not a single number. It is the whole sliding search, 3840 lags of it, and it costs a transform pair rather than 3840 correlations.
One detail makes this exact rather than approximate. The FFT gives circular correlation, where the ends wrap around, and that is usually a problem to be padded away. Here it is physically correct. The C/A code repeats every millisecond, and the block is exactly one code period, so a signal arriving at any delay is still one complete period of the code, merely rotated. Every one of the 3840 outputs is valid, and nothing needs to be discarded.
That is worth contrasting with a one-shot sequence. When I built a 5G NR SSB detector on the same radio, the PSS was a single 256 sample burst inside a continuous stream, so the correlation had to be linear. That needed overlap-save, a longer block, and the discarding of the first 255 outputs of every block. GPS needs none of that machinery, purely because its code is periodic.
One transform pair replaces 3840 correlations : The inverse transform output is indexed by code phase, so the peak search is a maximum over an array the FFT already produced.Circular is correct here, not a shortcut : A 1 ms block is exactly one code period, so wrap-around is real signal rather than an artefact, and all outputs are usable.The replica is built once : The conjugated transform of all 32 codes is a fixed matched filter bank. Per block the work is one forward transform and 32 inverse transforms.Periodic codes are cheaper than one-shot sequences : A non-repeating sequence in a stream forces linear correlation and overlap-save. The periodic C/A code avoids both.
How do you tell a real satellite from the loudest noise in the search?
A search over 32 satellites, 201 Doppler bins and 3840 code phases examines about 25 million cells. Something is always the largest. The question is not whether a peak exists, but whether the largest peak means anything, and this is where a first attempt usually goes wrong.
The obvious test is to compare the peak against the average of the rest, and it is not enough. With 25 million cells drawn from noise, the largest will stand well above the average by chance alone. My first version used a fixed ratio of 2.5 and confidently reported five satellites from an antenna that was receiving nothing at all. The threshold has to account for how many cells were searched, and even then it rests on the noise behaving as the model assumes.
A better test needs no model. Take the largest peak, exclude one chip either side of it, and find the next largest cell anywhere else in that satellite's surface. A real satellite puts all of its energy at one code phase, so everything outside its main lobe stays at the noise level and the ratio is large. Noise has no such structure: its two largest cells are neighbours in value, so the ratio sits just above one however low the floor happens to be.
The difference is not subtle when you measure it. On the same hardware, with the antenna's amplifier unpowered so that only noise was present, the best ratio across many runs never exceeded 1.4. With the amplifier powered, a satellite reached 12.2. A synthetic signal injected at a known 42 dB-Hz gave 5.5, which sits sensibly between the two.
Two further checks cost nothing and catch the cases a single number misses. A real satellite keeps its identity and its Doppler from one run to the next, while noise picks a different satellite every time. And a real Doppler drifts slowly and consistently, because the satellite is in orbit. Over fifteen minutes I watched one satellite move from -2000 Hz to -2500 Hz, which is exactly the rate orbital motion produces.
The two correlation surfaces are easy to tell apart once they are drawn side by side. The shape is the point rather than the height, because the height depends on gain and the shape does not.
Figure 11. Measuring the peak against the next highest cell rather than against the average removes the need for a noise model. The measured values were 1.4 with no signal and 12.2 with a satellite, which leaves no useful threshold in between to argue about.
Peak against average is a weak test : Over millions of cells the largest noise value clears it easily. A fixed ratio with no reference to the search size will report satellites that are not there.Peak against second peak needs no noise model : A satellite concentrates its energy at one code phase. Noise spreads it, so its two best cells are almost equal and the ratio stays near one.Measure your own noise floor : Running the same search with no signal present gives an empirical null. Mine topped out at 1.4, which made a reading of 12.2 unambiguous.Repeatability separates signal from luck : The same satellite at the same Doppler across consecutive runs is a satellite. A different one each time is noise.
How long should you integrate, and why does more eventually hurt?
Integration is the obvious way to find a weaker satellite. One millisecond of correlation gives a certain gain, and accumulating the squared magnitude over many milliseconds averages the noise down while the signal peak stays where it is. Up to a point this works exactly as expected, and a weak satellite that was invisible at 20 ms appears at 100 ms.
Past a certain length it stops working, and then it reverses. The reason is that Doppler affects the code as well as the carrier. The chip rate shifts by the Doppler divided by 1540, so a satellite with 2 kHz of Doppler has its code running about 1.3 chips per second fast or slow. The correlation peak is only about one chip wide, so after half a second the peak has walked more than half its own width.
Once that happens, accumulation is adding energy from a peak that is no longer in the same place. The peak smears across several cells while the noise continues to average down at the same rate, and the ratio that detection depends on gets worse rather than better. Longer integration is now actively harmful.
The effect is easy to see rather than merely predict. On my receiver, 60 to 150 ms reliably found two satellites, and 500 ms of the same signal found none at all. That is why the useful range is bounded at both ends, and why a receiver that needs more sensitivity compensates for the code drift rather than simply integrating for longer.
Plotting detection quality against integration length shows the two effects working against each other. Noise averaging pulls the curve up on the left, and code drift pulls it down on the right, with a usable window in between.
Figure 12. Integration length has an optimum rather than a direction. The falling right-hand side is the reason a receiver that needs more sensitivity has to compensate for code drift instead of simply accumulating for longer.
The code drifts, not just the carrier : Code rate error is the Doppler divided by 1540, which is about 1.3 chips per second at 2 kHz of Doppler.One chip is the whole budget : The correlation peak is roughly one chip wide, so drift of a fraction of a chip already spreads it across cells.There is a ceiling, and it is not far up : 60 to 150 ms worked on my signal, 500 ms found nothing. More integration is not a free improvement.Beyond the ceiling you must compensate : Real receivers advance the replica's code rate with the estimated Doppler, which is part of what a tracking loop does.
What can you work out from Doppler alone?
A textbook position fix uses pseudoranges. The receiver measures how long each signal took to arrive, converts that to a distance, and solves for its own position from four of them. That needs the navigation message decoded, which needs continuous tracking for at least thirty seconds, and it needs four satellites strong enough for the code phase to be trusted.
Doppler offers a cheaper route, and it uses only what acquisition already measures. Each satellite moves on a known orbit, so its velocity is known from a published almanac. The Doppler you measure depends on how that velocity projects onto the line between the satellite and you, which means every Doppler measurement constrains where you can be standing.
The receiver's own oscillator offset is unknown and shifts every measurement by the same amount, so it is removed as a term common to all satellites. That costs one measurement, which is why four satellites are needed rather than three. What remains is enough to place the receiver.
The accuracy is modest and the reason is simple. Doppler changes by roughly 1 Hz per kilometre, so the resolution of the Doppler search sets a floor on the position. Searching in 500 Hz steps cannot do better than a few hundred kilometres no matter how many satellites are found. With 100 Hz steps and five satellites my receiver placed itself 25 km from its true position, which is representative of the method rather than a limit of the implementation.
That is far worse than a pseudorange fix, which reaches metres. It is also achievable from a hundred milliseconds of samples, and from satellites far too weak to range with. That makes it a useful check that the acquisition is producing real measurements. It is not a practical way to navigate.
Doppler carries position information : Satellite velocity is known from the almanac, so the measured Doppler constrains where the receiver can be.The oscillator offset costs one satellite : It is common to every measurement and is removed as a shared term, so four satellites are needed instead of three.Resolution sets the floor : At about 1 Hz per kilometre, a 500 Hz search step cannot give better than a few hundred kilometres.Good enough to validate, not to navigate : 25 km from five satellites confirms the acquisition is real. Metre-level positioning still requires pseudoranges and a decoded navigation message.
What other GPS signals are there?
Everything up to this point has described one signal. That is the honest scope of this note, and it is also the scope of most GPS receivers ever built, but it is not the scope of GPS. The system broadcasts seven distinct signals across three frequencies, and L1 C/A is the oldest and the narrowest of them.
They accumulated rather than being designed together. L1 C/A dates from 1978 and carries every compromise that a 1970s receiver imposed, including the 50 bps data rate and the 1023 chip code. Every signal since has been added to fix a specific shortcoming of what was already flying, under a constraint that never relaxes. L1 C/A can never be switched off, because too much of the world depends on it, so each new signal has to fit into a band that is already occupied.
The first thing to understand is why there is more than one frequency at all, because the answer is not capacity. It is the ionosphere. The delay a signal picks up crossing it is inversely proportional to the square of the frequency, which makes the error dispersive and therefore measurable.
Δtiono ∝ 1 / f2
ρIF = ( f12 ρ1 − f22 ρ2 ) / ( f12 − f22 )
two pseudoranges at different frequencies combine into one from which the ionospheric delay has cancelled
A single frequency receiver cannot do this and has to use a broadcast model instead, which removes only about half the error. That leaves several metres on the table, and it is the largest single error source a civilian receiver faces. A second frequency is not a refinement. It is the difference between metres and tens of centimetres.
The three frequencies themselves come from the same place as everything else in the system. Each is an integer multiple of the 10.23 MHz reference, so they stay coherent with each other and with the codes.
fL1 = 154 × 10.23 MHz = 1575.42 MHz
fL2 = 120 × 10.23 MHz = 1227.60 MHz
fL5 = 115 × 10.23 MHz = 1176.45 MHz
Figure 13 shows what is actually transmitted in each of those three bands, drawn to a common scale within each panel.
Figure 13. Nothing here was allocated a clear channel. Every addition had to be squeezed alongside signals that were already flying and could not be moved, which is what BOC modulation exists to solve.
The same seven signals with their parameters are easier to compare in a list than in a picture.
L1 C/A |
1575.42 MHz |
1.023 Mcps |
BPSK |
The original civilian signal, flying since 1978. Still the only one every receiver in the world can use, which is why it can never be withdrawn. |
L1 P(Y) |
1575.42 MHz |
10.23 Mcps |
BPSK |
The encrypted military signal, sharing L1 in quadrature with C/A. Ten times the chip rate, so ten times the ranging resolution. |
L2 P(Y) |
1227.60 MHz |
10.23 Mcps |
BPSK |
The second frequency, and for two decades the only way to measure the ionosphere. Civilian receivers reached it by semi-codeless tracking, at a heavy loss in sensitivity. |
L2C |
1227.60 MHz |
1.023 Mcps |
BPSK |
Civilian access to that second frequency, from 2005. Its CL component carries no data at all and runs for 1.5 s, which is the first GPS pilot. |
L5 |
1176.45 MHz |
10.23 Mcps |
BPSK, on I and Q |
From 2010, in a band protected for aviation. Higher power, ten times the bandwidth, and a full pilot on the quadrature component. |
L1C |
1575.42 MHz |
1.023 Mcps |
BOC and TMBOC |
From 2018, sharing a modulation core with Galileo E1 so that one receiver front end can take both. Three quarters of its power is pilot. |
L1M and L2M |
1575.42 and 1227.60 |
5.115 Mcps |
BOC(10,5) |
Military, from 2005, deliberately placed in lobes far from the band centre so that it and the civil signals can be handled independently. |
What were the actual design criteria?
Data rate is the first thing anyone looks at when comparing generations of a radio system, and here it is the wrong thing. Across forty years and seven signals it has not moved at all.
LNAV |
L1 C/A |
1978 |
50 bps |
CNAV |
L2C |
2005 |
25 bps |
CNAV |
L5 |
2010 |
50 bps |
CNAV-2 |
L1C |
2018 |
50 bps |
That flatness is not an oversight. It says throughput was never the figure of merit, and the reason is that there is almost nothing to send. An ephemeris is about 900 bits and stays valid for hours, and an almanac changes over weeks. Nothing here is a stream.
The one thing a higher data rate would have bought is a faster cold start, and that was obtained a different way. Assisted GPS delivers the ephemeris over the network, so the 30 second wait disappears without touching the signal at all. The single benefit of more throughput arrived for free from somewhere else.
What is being optimised instead is the quality of a measurement. GPS is a ruler rather than a pipe. The product is the time of arrival, and the data is only the small amount of side information needed to interpret it. Every generation has therefore worked on how precisely and how reliably that arrival time can be measured.
For code tracking that reduces to one relationship, and it is worth writing down because most of the table above falls out of it. The precision depends on the root mean square bandwidth, which is how far the signal's energy sits from the carrier.
στ ∝ 1 / ( βrms √( C/N0 ⋅ T ) )
βrms is the root mean square bandwidth of the signal and T the integration time. Neither the data rate nor the occupied bandwidth appears.
Reading the evolution through that relationship, and through the two terms beside it, gives the criteria that were actually being traded.
Ranging precision |
Root mean square bandwidth |
Ten times the chip rate on P(Y) and L5. BOC on L1C and the M code, which hollows out the middle and puts the energy at the subcarrier offset instead. |
Multipath rejection |
Chip length and peak sharpness |
A 293 m chip becomes 29.3 m on L5, so a reflection has to be ten times closer before it merges with the direct path. |
Ionospheric error |
Dispersion between two bands |
A second and then a third civil frequency, which turns a modelled error into a measured one. |
Sensitivity |
Usable coherent integration time |
Pilot components with no data on them, so the 20 ms ceiling does not apply and T in the expression above can grow. |
Data robustness |
Decoding threshold, not capacity |
Convolutional coding with CRC-24Q, then LDPC. The same bits, delivered at a lower carrier to noise ratio. |
Near-far margin |
Cross-correlation isolation |
Codes of 10230 chips instead of 1023, which moves the bound from about 24 dB to about 34 dB. |
Spectrum protection |
Regulatory status, not a technical property |
L5 placed inside the ARNS band, so it is usable for aviation where an unprotected band is not. |
Interoperability |
Whether one front end serves two systems |
L1C built around the same BOC(1,1) core as Galileo E1, a criterion that did not exist in 1978. |
L1C is the cleanest evidence that this is the right reading. It has the same 1.023 Mcps chip rate as C/A and carries the same 50 bps. By any measure a communication engineer would reach for, the two signals are identical. L1C is substantially better anyway, purely because BOC puts its energy away from the band centre, which raises the root mean square bandwidth and sharpens the correlation peak. The entire improvement is in how well the signal can be measured.
Even the coding change turns out not to be about data. A rate one half convolutional code carries no more information than the six parity bits it replaced. It carries the same information at a lower carrier to noise ratio, which is a statement about availability rather than about capacity. It lets a receiver decode indoors, not faster.
So the ranking runs roughly as measurement precision first, then availability, then robustness, then interoperability, with backward compatibility as a constraint that never relaxes. Every one of those is about making the measurement, or about being able to make it at all. Not one of them is about moving information.
What the newer signals fix in this note's own results
Two of those additions are worth returning to, because both of them dissolve a limit this note ran into.
The first is the pilot component. A pilot carries no data, so it has no bit transitions, and the 20 ms ceiling on coherent integration simply does not apply to it. A receiver can integrate a pilot coherently for a second or more, limited only by oscillator stability and by the code Doppler drift described earlier. That is worth roughly 10 dB of sensitivity, and it is why modern receivers work indoors where a C/A-only receiver does not.
The second is code length. L5 and L1C both use 10230 chip codes instead of 1023, and a ten times longer code pushes the cross-correlation bound about 10 dB further down. The 23.9 dB of isolation measured earlier becomes roughly 34 dB, which is what makes the near-far case survivable rather than merely rare.
BOC modulation is the third idea and it solves a different problem. Splitting the spectrum into two lobes either side of the centre lets a new signal occupy a band that is already full, because its energy sits where the old signal has little. It also sharpens the correlation peak, since the energy is further from the carrier, which improves both ranging accuracy and multipath rejection. The cost is a correlation function with several peaks instead of one, so acquisition has to work harder not to lock onto the wrong one.
What a receiver actually uses depends entirely on what it is for. A cheap navigation chip uses L1 C/A alone and nothing in this note's later sections would surprise it. A phone built since about 2018 usually adds L5, which is where its sudden improvement in urban accuracy came from. A survey receiver uses every frequency it can reach, because the carrier phase on three bands is what makes centimetre positioning possible.
The SDR used for this note could receive any of them. It is a 3.84 Msps capture at 1575.42 MHz, so L1C is already present in the samples that were recorded, and L5 would need only a different centre frequency. What is missing is not the hardware but the replicas and the acquisition logic for a BOC waveform, which is a fair description of how much of GPS is software.
L1 C/A is the oldest and the narrowest : Seven signals exist across three frequencies, and the one this note measures is the 1978 original.The second frequency exists for the ionosphere : Delay scales as one over frequency squared, so two bands cancel it, and that is worth metres rather than percentages.All three centres share the 10.23 MHz reference : 154, 120 and 115 times it, so every signal stays coherent with every other.Nothing could be moved, so everything overlaps : L1 C/A can never be withdrawn, which is why new signals use BOC to sit beside it rather than replace it.Data rate was never a criterion : It has been 50 bps or less since 1978, because the payload is nearly static and the only thing more of it would buy was solved by assisted GPS instead.The figure of merit is root mean square bandwidth : Ranging precision depends on how far the energy sits from the carrier, which is why a tenfold chip rate and BOC both work and why L1C beats C/A at the same chip rate.A pilot removes the integration ceiling : With no data bits there are no transitions, so coherent integration runs far past 20 ms and buys roughly 10 dB.Longer codes move the near-far bound : 10230 chips instead of 1023 takes the cross-correlation isolation from about 24 dB to about 34 dB.
How would you build this receiver, one prompt at a time?
Reading the seven stages is one thing and building them is another. The difficulty is not that any single stage is hard. It is that nothing in the middle of the chain can be tested until everything before it already works, so a receiver that is wrong somewhere gives you silence rather than a clue.
That makes it a poor fit for asking an assistant to write the whole thing. A single prompt saying "write a GPS receiver" produces something that looks complete, runs without error, and detects nothing, and there is no way to tell which of the seven stages is wrong. The fix is not a better single prompt. It is eleven prompts, each one ending in a number you can check.
The build order that results is not the signal order, and that is the first thing worth noticing. The C/A code generator and a synthetic signal generator both come before anything else, even though one of them belongs to stage two and the other is not part of a receiver at all.
Figure 14 is the build order with the gate that has to pass before each step can be considered finished.
Figure 14. The gate column is the part that matters. A step without a numeric gate is a step you will later have to debug from scratch, because you never established that it worked.
Getting a usable gate out of an assistant takes five habits. They are worth stating before the steps, because every step below relies on them.
Give the constants, never the name : Asking for "the GPS C/A code" gets a confident guess at the tap table. Asking for stages 3 and 10, stages 2, 3, 6, 8, 9 and 10, and the phase selection table gets the code.Put the test in the same prompt as the code : A self test written afterwards tends to assert whatever the code already does. Asked for together, it has to assert what the specification says.One stage per prompt : Acquisition and tracking in one request gives you two untested things and one failure.Build the signal before the detector : A synthetic satellite with a known PRN, Doppler and code phase turns every later step from guesswork into a pass or fail.Demand a number, not a plot : "It looks like a peak" is not a gate. "Recovered code phase within half a chip" is.
The prompts below are written to be used in order. Each one assumes the previous steps exist and are passing their gates, and each one names the constants it needs rather than referring to them.
Gate : The file is exactly ms x 3840 x 8 bytes, nothing clips, and the spectrum is flat. A flat spectrum is the correct result here, not a failure, because the signal is below the noise.
Gate : All 32 PRNs match the published octals. If even one fails, stop here. Every later step will produce noise that looks like a bug somewhere else.
Gate : You can now produce a signal whose PRN, Doppler and code phase you already know. Every gate from here on is a comparison against those three numbers.
Gate : The recovered cell equals the injected cell. Test at several Doppler values including negative ones, since a sign error passes a single zero Doppler test.
Gate : Nothing is detected on noise, and the largest ratio seen on noise is recorded. That number is the empirical floor every later detection is measured against.
Gate : The loops hold on a drifting synthetic signal. Log the discriminators from the start, because a loop that has lost lock and a loop that never locked look identical in the output.
Gate : The histogram has one clear winner out of 20, and the recovered bits match the injected ones up to a possible inversion. An ambiguous histogram means tracking is not as good as step 5 claimed.
Gate : Parity passes on 20 consecutive words and the TOW increments by one. A single accepted subframe proves nothing, because a chance preamble can survive one check.
Gate : Ephemeris and almanac positions agree to a few kilometres. They should not agree more closely than that, because the almanac is deliberately coarse.
Gate : The between-satellite spread is under about 6000 km. The absolute values will be wrong by whatever the receiver clock bias is, and that is expected rather than a bug.
Gate : A simulated position comes back to under a metre with an arbitrary injected clock bias. Only then is a solution from real data worth looking at.
Two of those steps are much harder than the rest, and it is worth knowing which before starting. Step 5 is where projects stall, because a tracking loop has several free parameters and no obvious symptom when one of them is wrong. It either holds or it does not, and the output looks similar either way. That is why the prompt asks for the discriminator outputs to be logged from the beginning, rather than added later during debugging.
Step 8 is the other one, though for a different reason. Nothing about it is conceptually hard. It is fifty or so fields, each with its own scale factor and its own sign convention, and a single wrong exponent produces an orbit that looks entirely plausible and is wrong by kilometres. That is exactly why its gate compares against an independent source rather than against itself.
This note's own receiver stops after step 4, which is why everything that follows acquisition here is about detection statistics and Doppler rather than about ranging. That was a deliberate limit rather than an abandoned attempt. Steps 0 to 4 are enough to answer which satellites are overhead, and answering that was enough to prove the antenna, the cabling and the front end were all working.
Build order is not signal order : The code generator and the synthetic signal come first, though one belongs to stage two and the other is not in a receiver at all.Every step ends in a number : A gate that reads "looks like a peak" will be re-debugged later. A gate that reads "within half a chip" will not.The generator gate is absolute : If the 32 octal values do not all match, nothing downstream can be trusted, and its failures will look like bugs in other steps.Test against noise as well as against signal : A detector that has never been shown a signal-free input has not been tested, only demonstrated.Name the constants in the prompt : Tap tables, octal values, the preamble and the scale factors are all things an assistant will otherwise produce from memory and get subtly wrong.Expect steps 5 and 8 to take the longest : One has parameters with no visible failure mode, and the other has fifty fields where one wrong exponent still looks reasonable.
Reference
This note is built on one specification and one public data product, both of which I used directly while writing the receiver the measurements come from.
- IS-GPS-200 : Navstar GPS Space Segment / Navigation User Interfaces. Table 3-I gives the G2 tap pair and the first ten chips of every PRN, which is what the generator is checked against.
- Navcen GPS almanac : The US Coast Guard publishes the current constellation almanac in YUMA format. The Keplerian elements in it are what the satellite positions and predicted Doppler in this note are computed from.