Skip to content

Signal Architecture

The Apollo Unified S-Band (USB) system multiplexes voice, telemetry, and ranging onto a single 2287.5 MHz carrier using nested modulation layers. Understanding these layers — and the reasons behind each design choice — is the key to understanding what gr-apollo does and why its blocks are structured the way they are.

All parameters in this section are from the NAA Telecommunication Systems Study Guide (Course A-624).

The spacecraft transmitter begins with a stable carrier at 2287.5 MHz. Multiple information streams are combined onto this carrier through a two-level modulation scheme: subcarriers are first individually modulated with their data, then the composite subcarrier signal phase-modulates the RF carrier.

graph TD
    A["PCM Telemetry<br/>51.2 kbps NRZ"] -->|BPSK| B["1.024 MHz<br/>Subcarrier"]
    C["Voice Audio<br/>300-3000 Hz"] -->|FM<br/>+/-29 kHz dev| D["1.25 MHz<br/>Subcarrier"]
    E["Ranging Code<br/>PRN sequence"] --> F["Ranging<br/>Subcarrier"]

    B --> G["Composite<br/>Baseband Signal"]
    D --> G
    F --> G

    G -->|PM<br/>0.133 rad peak| H["2287.5 MHz<br/>RF Carrier"]

    H --> I["Transmitted<br/>Downlink"]

    style A fill:#2d5016,stroke:#4a8c2a
    style C fill:#2d5016,stroke:#4a8c2a
    style E fill:#2d5016,stroke:#4a8c2a
    style H fill:#1a3a5c,stroke:#3a7abd
    style I fill:#1a3a5c,stroke:#3a7abd

This is a frequency-division multiplexing system. The PCM subcarrier sits at 1.024 MHz, voice at 1.25 MHz, and ranging at its own frequency. Because these subcarriers are well-separated in frequency, the receiver can extract each one independently with bandpass filters. The entire system uses a single RF carrier — hence “Unified” S-Band.

The PM peak deviation of 0.133 rad (7.6 degrees) is one of the most important numbers in the system. It seems absurdly small — barely a wiggle on the carrier phase. That is the point.

At small modulation indices, the relationship between the modulating signal and the recovered phase is very nearly linear. The small-angle approximation sin(theta) ~= theta holds to within 0.3% at 0.133 rad:

Angle (rad)sin(angle)Error
0.1330.13260.29%
0.50.47944.1%
1.00.841515.9%

This linearity means the PM demodulator output is a faithful reproduction of the composite subcarrier signal. There is no need for pre-distortion or nonlinear correction — the receiver just extracts the phase and gets the subcarriers back, ready for filtering and demodulation.

The tradeoff is signal power. Most of the transmitted power remains in the carrier rather than the sidebands. The spacecraft burns roughly 20 watts of RF power (via the traveling-wave tube amplifier), but only a fraction of a watt ends up in each subcarrier. The Deep Space Network’s 26-meter dishes made up the difference with raw antenna gain.

Every timing parameter in the Apollo PCM system derives from a single 512 kHz master clock in the Central Timing Equipment (CTE). This is not a coincidence — it is the entire design philosophy. When all frequencies share a common root, the receiver can exploit integer relationships for synchronization.

graph TD
    M["512 kHz<br/>Master Clock"] --> A["51.2 kbps bit clock<br/>divide by 10"]
    M --> B["1.6 kbps bit clock<br/>divide by 320"]
    A --> C["6,400 words/sec<br/>8 bits/word"]
    B --> D["200 words/sec<br/>8 bits/word"]
    C --> E["50 frames/sec<br/>128 words/frame"]
    D --> F["1 frame/sec<br/>200 words/frame"]
    E --> G["1 subframe/sec<br/>50 frames"]

    M --> H["1.024 MHz subcarrier<br/>multiply by 2"]

    style M fill:#5c3a1a,stroke:#bd7a3a
    style H fill:#1a3a5c,stroke:#3a7abd

At high rate: 512,000 / 10 = 51,200 bits/sec. Divide by 8 bits/word = 6,400 words/sec. Divide by 128 words/frame = 50 frames/sec. Multiply 50 frames by the frame period and you get exactly 1 second per subframe.

The 1.024 MHz PCM subcarrier is 512 kHz times 2. This means exactly 20 subcarrier cycles fit in one bit period (1,024,000 / 51,200 = 20). The BPSK demodulator can use this integer relationship for more stable symbol timing recovery.

gr-apollo’s default baseband sample rate is 5.12 MHz — exactly 10 times the master clock. This choice is deliberate:

RelationshipValueInteger?
Samples per master clock cycle5,120,000 / 512,000 = 10Yes
Samples per PCM subcarrier cycle5,120,000 / 1,024,000 = 5Yes
Samples per bit (high rate)5,120,000 / 51,200 = 100Yes
Samples per voice subcarrier cycle5,120,000 / 1,250,000 = 4.096No

Every PCM-related rate divides evenly into the sample rate. This eliminates fractional sample offsets in the bit timing recovery loop, making synchronization faster and more reliable. The voice subcarrier does not divide evenly, but voice is FM — it tolerates timing imprecision far better than digital data.

The receiver follows the signal structure in reverse. Each modulation layer gets its own block, and the blocks chain together in the same order the signal was built:

graph LR
    A["Complex<br/>Baseband<br/>5.12 MHz"] --> B["pm_demod"]
    B --> C["subcarrier_extract<br/>1.024 MHz"]
    C --> D["bpsk_demod"]
    D --> E["pcm_frame_sync"]
    E --> F["pcm_demux"]

    B --> G["voice_subcarrier_demod<br/>1.25 MHz"]

    F -->|"frames PDU"| H["AGC Data"]
    F -->|"telemetry PDU"| I["Telemetry Words"]
    F -->|"agc_data PDU"| J["downlink_decoder"]
    J -->|"downlink PDU"| K["AGC Bridge"]

    G --> L["Audio Out<br/>8 kHz"]

    style A fill:#1a3a5c,stroke:#3a7abd
    style B fill:#3a1a5c,stroke:#7a3abd
    style C fill:#3a1a5c,stroke:#7a3abd
    style D fill:#3a1a5c,stroke:#7a3abd
    style E fill:#5c3a1a,stroke:#bd7a3a
    style F fill:#5c3a1a,stroke:#bd7a3a
    style G fill:#2d5016,stroke:#4a8c2a

The first four blocks form a streaming signal processing chain, each transforming the signal’s representation:

  1. pm_demod — Carrier PLL locks to the residual carrier, then atan2(Im, Re) extracts instantaneous phase. Output is a float representing the composite modulating signal.
  2. subcarrier_extract — Frequency-translating FIR filter shifts the 1.024 MHz subcarrier to DC and applies a 150 kHz bandpass (matching the spec’s 949-1099 kHz range). Output is complex baseband.
  3. bpsk_demod — Costas loop resolves phase, Mueller & Muller TED recovers symbol timing, binary slicer makes hard decisions. Output is one byte per bit (0 or 1).
  4. pcm_frame_sync — Sliding-window correlator finds the 32-bit sync word. Transitions through SEARCH, VERIFY, and LOCKED states. Emits complete frames as PDU messages.

The usb_downlink_receiver hierarchical block wires the PCM chain together as a convenience — connect complex baseband in, get telemetry PDUs out. For finer control over PLL bandwidths or to tap intermediate signals, the individual blocks can be used directly.

The Costas loop in bpsk_demod has a fundamental 180-degree phase ambiguity. A 2nd-order Costas loop locks to one of two stable equilibria that are 180 degrees apart. If it locks to the wrong one, every recovered bit is inverted.

The system resolves this at the frame sync layer rather than at the Costas loop. The 32-bit sync word has a known pattern: [5-bit A][15-bit core][6-bit B][6-bit frame ID]. The A, B, and core fields are fixed (the core complements on odd frames, but this is deterministic). If the correlator finds the complement of the expected pattern everywhere, it knows the Costas loop locked to the wrong phase, and can invert the bit stream.

This is a well-known technique in BPSK systems. It is more reliable than trying to resolve the ambiguity in the Costas loop itself, because the frame sync pattern provides a definitive answer rather than a probabilistic one.

The Apollo USB system maintains a coherent frequency relationship between uplink and downlink. The spacecraft’s receiver locks to the uplink at 2106.40625 MHz, and the transmitter generates its downlink carrier by multiplying the received frequency by 240/221:

2106.40625 MHz x 240/221 = 2287.5 MHz

This coherent turnaround allows the ground station to measure the two-way Doppler shift with extreme precision — the ratio is exact, so any frequency difference between transmitted uplink and received downlink is entirely due to spacecraft velocity. This is how NASA tracked the spacecraft’s range rate to centimeter-per-second precision using 1960s technology.

gr-apollo does not implement the coherent turnaround (it is a receiver, not a transponder), but the frequency plan explains why the numbers are what they are. The 2287.5 MHz downlink frequency is not arbitrary — it is locked to the uplink via a ratio that was carefully chosen to avoid ambiguities in the Doppler measurement.

gr-apollo’s TX blocks mirror the receive path. Where the receiver disassembles the signal layer by layer, the transmitter builds it up. Each TX block has a direct RX counterpart, and the internal architecture reflects this symmetry:

graph LR
    A["PCM Frame Source<br/>32-bit sync<br/>128 words/frame"] --> B["NRZ Encoder<br/>0/1 → +1/-1<br/>100 samp/bit"]
    B --> C["BPSK Mod<br/>× cos(1.024 MHz)"]
    C --> D["Σ"]

    E["Voice Mod<br/>FM → 1.25 MHz<br/>±29 kHz dev"] --> F["× 0.764"]
    F --> D

    D --> G["PM Mod<br/>exp(j·φ)"]
    G --> H["Complex<br/>Baseband<br/>5.12 MHz"]

    style A fill:#2d5016,stroke:#4a8c2a
    style B fill:#5c3a1a,stroke:#bd7a3a
    style C fill:#3a1a5c,stroke:#7a3abd
    style E fill:#2d5016,stroke:#4a8c2a
    style G fill:#1a3a5c,stroke:#3a7abd
    style H fill:#1a3a5c,stroke:#3a7abd

The PCM transmit path generates telemetry frames and modulates them onto the BPSK subcarrier:

  1. pcm_frame_source — Generates 128-word PCM frames with 32-bit sync words. Frame IDs cycle 1 through 50 (one subframe per second). Odd frames get a complemented sync core automatically. Dynamic payloads can be injected via the frame_data message port.
  2. nrz_encoder — Converts the bit stream (byte values 0/1) to a float NRZ waveform (+1.0/-1.0). Each bit is repeated for 100 samples at the default 5.12 MHz rate.
  3. bpsk_subcarrier_mod — Multiplies the NRZ data by a 1.024 MHz cosine carrier. This BPSK modulation flips the subcarrier phase 180 degrees on each bit transition — the inverse of the Costas loop recovery in bpsk_demod.

The usb_signal_source hierarchical block wires the entire TX chain together as a convenience — the transmit-side counterpart to usb_downlink_receiver. For scenarios that need finer control (like injecting external audio), the individual blocks can be assembled manually. The full_downlink_demo.py example shows this approach.