gr-apollo/src/apollo/ranging_source.py
Ryan Malloy 86a5b08e9d Add PRN ranging system: code generator, modulator, demodulator, and demo
Implements the Apollo composite PRN ranging code (5,456,682 chips) from
five component sequences (CL, X, A, B, C) combined via majority-vote
logic, matching Ken Shirriff's Teensy rangeGenerator.ino bit-for-bit.

LFSR taps corrected to produce maximal-length sequences:
  A: 5-bit, taps [2,0] (x^5+x^2+1, period 31)
  B: 6-bit, taps [1,0] (x^6+x+1, period 63)
  C: 7-bit, taps [1,0] (x^7+x+1, period 127)

New files:
  src/apollo/ranging.py          -- pure-Python code generator and correlator
  src/apollo/ranging_source.py   -- GR sync_block streaming PRN chips
  src/apollo/ranging_mod.py      -- GR hier_block2 NRZ chip modulator
  src/apollo/ranging_demod.py    -- GR basic_block FFT-based range correlator
  grc/apollo_ranging_*.block.yml -- GRC block definitions (3 files)
  examples/ranging_demo.py       -- standalone demo with delay simulation
2026-02-24 14:21:02 -07:00

61 lines
1.9 KiB
Python

"""
Apollo Ranging Source -- streams PRN ranging chips.
Outputs a continuous stream of bytes (0 or 1) representing the composite
PRN ranging code. The code repeats every 5,456,682 chips (~5.49 seconds
at the nominal 993,963 chip/s rate).
The full code period is pre-generated and cycled through, so startup cost
is paid once and streaming is zero-allocation.
Reference: Ken Shirriff's Apollo ranging analysis
http://www.righto.com/2022/04/the-digital-ranging-system-that.html
"""
import numpy as np
from apollo.ranging import RANGING_CODE_LENGTH, RangingCodeGenerator
# ---------------------------------------------------------------------------
# GNU Radio block (optional -- only if gnuradio is available)
# ---------------------------------------------------------------------------
try:
from gnuradio import gr
class ranging_source(gr.sync_block):
"""GNU Radio source: continuous PRN ranging chip stream.
Outputs bytes (0 or 1) at the chip rate. Pre-generates the full
code period and cycles through it.
"""
def __init__(self):
gr.sync_block.__init__(
self,
name="apollo_ranging_source",
in_sig=None,
out_sig=[np.byte],
)
self._gen = RangingCodeGenerator()
self._code = self._gen.generate_sequence()
self._pos = 0
def work(self, input_items, output_items):
out = output_items[0]
n = len(out)
produced = 0
while produced < n:
remaining = RANGING_CODE_LENGTH - self._pos
chunk = min(n - produced, remaining)
out[produced : produced + chunk] = self._code[self._pos : self._pos + chunk]
self._pos = (self._pos + chunk) % RANGING_CODE_LENGTH
produced += chunk
return produced
except ImportError:
pass