File despreader_core.h¶
FileList > despreader > despreader_core.h
Go to the source code of this file
Continuous DSSS despreader — Costas carrier loop + DLL code loop. More...
#include "clib_common.h"#include "costas/costas_core.h"#include "detection/detection_core.h"#include "dll/dll_core.h"#include "dp_state.h"#include "jm_perf.h"#include "lo/lo_core.h"#include "lockdet/lockdet_core.h"#include "loop_filter/loop_filter_core.h"#include "dp_tlm/dp_tlm_core.h"#include <complex.h>#include "telemetry/telemetry_core.h"
Classes¶
| Type | Name |
|---|---|
| struct | despreader_state_t Despreader state. |
Public Functions¶
| Type | Name |
|---|---|
| size_t | despreader_bits (despreader_state_t * state, const float _Complex * x, size_t x_len, uint8_t * out, size_t max_out) Despread a CF32 block and bit-sync the prompts into hard data bits. |
| size_t | despreader_bits_max_out (despreader_state_t * state) |
| void | despreader_configure_carrier_lock (despreader_state_t * state, double up_thresh, double down_thresh, uint32_t n_up, uint32_t n_down) Re-tune the embedded carrier loop's lock detector directly. |
| int | despreader_configure_code_lock (despreader_state_t * state, double pfa, size_t n_looks, double ref_snr_db) Re-tune the embedded code loop's lock detector. |
| despreader_state_t * | despreader_create (const uint8_t * code, size_t code_len, size_t sps, double init_norm_freq, double init_chip, double bn_carrier, double bn_code, double bn_fll, double zeta, double spacing, size_t periods_per_bit) Create a continuous DSSS despreader (COPIES code ). |
| void | despreader_destroy (despreader_state_t * state) Destroy a despreader and release all memory. |
| size_t | despreader_get_bit_phase (const despreader_state_t * state) |
| double | despreader_get_bn_carrier (const despreader_state_t * state) |
| double | despreader_get_bn_code (const despreader_state_t * state) |
| int | despreader_get_carrier_locked (const despreader_state_t * state) Carrier lock decision (1 = locked): the embedded Costas loop's verify-counted detector on its lock-metric EMA (see costas_configure_lock). |
| int | despreader_get_code_locked (const despreader_state_t * state) Code lock decision (1 = locked): the embedded DLL's verify-counted CFAR detector (see dll_configure_lock); live in composition — the despreader runs the same always-on detector dll_steps does. |
| double | despreader_get_code_phase (const despreader_state_t * state) |
| double | despreader_get_code_rate (const despreader_state_t * state) |
| double | despreader_get_lock_metric (const despreader_state_t * state) |
| double | despreader_get_norm_freq (const despreader_state_t * state) |
| void | despreader_get_state (const despreader_state_t * state, void * blob) |
| void | despreader_init (despreader_state_t * ch, const uint8_t * code, size_t code_len, size_t sps, double init_norm_freq, double init_chip, double bn_carrier, double bn_code, double bn_fll, double zeta, double spacing, size_t periods_per_bit) Initialise a despreader in place; BORROWS code . |
| void | despreader_reset (despreader_state_t * state) Re-seed both loops to the create-time frequency/phase; keep config. |
| void | despreader_set_bn_carrier (despreader_state_t * state, double val) |
| void | despreader_set_bn_code (despreader_state_t * state, double val) |
| void | despreader_set_norm_freq (despreader_state_t * state, double val) |
| int | despreader_set_state (despreader_state_t * state, const void * blob) |
| int | despreader_set_telemetry (despreader_state_t * state, dp_tlm_t * tlm, const char * prefix, uint32_t decim) Attach (or detach) a telemetry context across the despreader. Pure forwarder — the despreader registers no probes of its own: the carrier loop registers "<prefix>.car.lock" / ".e" / ".freq" / ".locked" and the code loop registers "<prefix>.code.e" / ".rate" / ".lock" / ".locked" (the ".locked" pair are the loops' verify-counted lockdet decisions, 0/1) — eight probes, all thinned by decim and emitted once per code period (the despreader flushes both loops at its per-period update). Passing NULL detaches both loops. Setup path, never hot; the context is borrowed and must outlive the attachment (SPSC rules indp_tlm/dp_tlm_core.h ). |
| size_t | despreader_state_bytes (const despreader_state_t * state) |
| size_t | despreader_steps (despreader_state_t * state, const float _Complex * x, size_t x_len, float _Complex * out, size_t max_out) Track carrier and code and despread a CF32 block, one prompt symbol per code period. |
| size_t | despreader_steps_max_out (despreader_state_t * state) |
Macros¶
| Type | Name |
|---|---|
| define | DESPREADER_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('D', 'S', 'P', 'R') |
| define | DESPREADER_STATE_VERSION /* multi line expression */ |
Detailed Description¶
A complete continuous despreader for a DSSS-BPSK signal: it composes a costas_state_t carrier loop and a dll_state_t code loop on a single shared per-sample integrate-and-dump. Per sample it wipes the carrier (costas_wipeoff, integer NCO) and feeds the de-rotated sample to the DLL's early/prompt/late correlators (dll_accumulate); per code period it dumps the prompt and updates both loops — the code loop on the early/late envelopes, the carrier loop on the same prompt symbol. steps() emits one prompt per period; bits() bit-syncs the prompts into hard data bits (a data bit spans periods_per_bit code periods).
It is seeded by acquisition (the FFT search supplies the coarse carrier frequency + code phase); the loops then track the residual. Set bn_fll > 0 for FLL-assisted carrier pull-in.
Lifecycle: despreader_create -> (steps / bits / reset)* -> despreader_destroy.
uint8_t code[127] = { ... }; // one code period, 0/1 chips
despreader_state_t *ch = despreader_create(code, 127, 8, 0.0, 0.0,
0.05, 0.005, 0.0, 0.707, 0.5, 1);
float _Complex sym[64];
size_t k = despreader_steps(ch, rx, rx_len, sym, 64); // prompt per period
despreader_destroy(ch);
Public Functions Documentation¶
function despreader_bits¶
Despread a CF32 block and bit-sync the prompts into hard data bits.
size_t despreader_bits (
despreader_state_t * state,
const float _Complex * x,
size_t x_len,
uint8_t * out,
size_t max_out
)
The same tracking kernel as despreader_steps(), followed by bit synchronisation: the per-period prompts are coherently summed across each detected bit boundary (a data bit spans periods_per_bit code periods) and one hard 0/1 bit is emitted per data bit. The bit boundary is estimated on-line from the prompt sign-flip histogram, so the phase is a BPSK ambiguity — a globally inverted decision stream is equally correct.
Parameters:
stateMust be non-NULL.xInput CF32 samples.x_lenNumber of input samples.outOutput buffer for hard bits, 0/1 (>=max_out).max_outCapacity ofoutin bits.
Returns:
Number of data bits written into out.
>>> import numpy as np
>>> from doppler.dsss import Despreader
>>> rng = np.random.default_rng(3)
>>> code = rng.integers(0, 2, 31).astype(np.uint8)
>>> chips = np.where(code & 1, -1.0, 1.0)
>>> bits = rng.integers(0, 2, 40).astype(np.uint8)
>>> syms = np.where(bits == 1, -1.0, 1.0)
>>> rx = np.concatenate(
... [s * np.repeat(chips, 4) for s in syms]).astype(np.complex64)
>>> d = Despreader(code=code, sps=4)
>>> data = d.bits(rx) # hard data bits
>>> e = np.mean(data != bits[:data.size]) # up to a BPSK sign flip
>>> round(float(min(e, 1.0 - e)), 4)
0.0
function despreader_bits_max_out¶
function despreader_configure_carrier_lock¶
Re-tune the embedded carrier loop's lock detector directly.
void despreader_configure_carrier_lock (
despreader_state_t * state,
double up_thresh,
double down_thresh,
uint32_t n_up,
uint32_t n_down
)
Thin forwarder to costas_configure_lock() on the embedded Costas loop — symmetric with despreader_get_carrier_locked() exposing its state: state is readable, so config should be writable too, rather than forcing a caller who needs this control to drop to raw Dll+Costas composition instead of Despreader. See costas_configure_lock() for the parameter semantics.
Parameters:
stateMust be non-NULL.up_threshDeclare threshold on the lock-metric EMA.down_threshDrop threshold (<= up_thresh for level hysteresis).n_upConsecutive above-threshold symbols to declare.n_downConsecutive below-threshold symbols to drop.
function despreader_configure_code_lock¶
Re-tune the embedded code loop's lock detector.
int despreader_configure_code_lock (
despreader_state_t * state,
double pfa,
size_t n_looks,
double ref_snr_db
)
Thin forwarder to dll_configure_lock() on the embedded DLL — the derived (pfa-style) entry point, matching Despreader's role as the "easy" composed API (Dll's raw escape hatch, dll_configure_lock_raw(), stays a Dll-only control for a caller that composes Dll+Costas directly). See dll_configure_lock() for the parameter semantics.
Parameters:
stateMust be non-NULL.pfaPer-decision false-alarm probability, in (0, 1).n_looksNon-coherent integration depth N (looks); clamped >= 1.ref_snr_dbNoise-reference estimator SNR in dB (> 0), or 0 to derive fromn_looks(see dll_configure_lock()).
Returns:
DP_OK, or DP_ERR_INVALID when pfa is outside (0, 1).
>>> import numpy as np
>>> from doppler.dsss import Despreader
>>> d = Despreader(code=np.zeros(31, dtype=np.uint8), sps=2)
>>> d.configure_code_lock(1e-3, 20)
>>> d.code_locked
False
>>> d.configure_code_lock(2.0, 20)
Traceback (most recent call last):
...
ValueError: configure_code_lock failed (rc=-4)
function despreader_create¶
Create a continuous DSSS despreader (COPIES code ).
despreader_state_t * despreader_create (
const uint8_t * code,
size_t code_len,
size_t sps,
double init_norm_freq,
double init_chip,
double bn_carrier,
double bn_code,
double bn_fll,
double zeta,
double spacing,
size_t periods_per_bit
)
A complete tracking despreader for a continuous DSSS-BPSK stream: it composes a Costas carrier loop and an early/prompt/late DLL code loop over a single shared per-sample integrate-and-dump. Seed it from acquisition (the coarse carrier frequency and code phase) and the loops track the residual; steps() emits one prompt symbol per code period, and bits() bit-syncs those prompts into hard data bits (a data bit spans periods_per_bit code periods).
Parameters:
codeSpreading code (0/1 chips), one period; copied.code_lenCode length (chips per period); >= 1.spsSamples per chip.init_norm_freqSeed carrier frequency, cycles/sample (the acquisition estimate).init_chipSeed code phase, chips (the acquisition estimate).bn_carrierCarrier loop noise bandwidth, normalized to the code-period (symbol) rate.bn_codeCode loop noise bandwidth, normalized to the code-period rate.bn_fllCarrier FLL-assist bandwidth (0 = pure PLL); set > 0 for FLL-assisted carrier pull-in.zetaDamping factor shared by both second-order loops.spacingDLL early/late correlator tap offset, chips.periods_per_bitCode periods per data bit (1 = one bit per period).
Returns:
Heap-allocated state, or NULL on allocation failure.
Note:
Caller must call despreader_destroy() when done.
>>> import numpy as np
>>> from doppler.dsss import Despreader
>>> rng = np.random.default_rng(3)
>>> code = rng.integers(0, 2, 31).astype(np.uint8) # one code period
>>> chips = np.where(code & 1, -1.0, 1.0) # 0 -> +1, 1 -> -1
>>> bits = rng.integers(0, 2, 40).astype(np.uint8) # 1 bit/period
>>> syms = np.where(bits == 1, -1.0, 1.0)
>>> rx = np.concatenate(
... [s * np.repeat(chips, 4) for s in syms]).astype(np.complex64)
>>> d = Despreader(code, sps=4) # seed a fresh tracking loop
>>> data = d.bits(rx) # hard data bits, 1/period
>>> e = np.mean(data != bits[:data.size]) # up to a global BPSK flip
>>> round(float(min(e, 1.0 - e)), 4)
0.0
function despreader_destroy¶
Destroy a despreader and release all memory.
Parameters:
stateMay be NULL.
function despreader_get_bit_phase¶
function despreader_get_bn_carrier¶
function despreader_get_bn_code¶
function despreader_get_carrier_locked¶
Carrier lock decision (1 = locked): the embedded Costas loop's verify-counted detector on its lock-metric EMA (see costas_configure_lock).
function despreader_get_code_locked¶
Code lock decision (1 = locked): the embedded DLL's verify-counted CFAR detector (see dll_configure_lock); live in composition — the despreader runs the same always-on detector dll_steps does.
function despreader_get_code_phase¶
function despreader_get_code_rate¶
function despreader_get_lock_metric¶
function despreader_get_norm_freq¶
function despreader_get_state¶
function despreader_init¶
Initialise a despreader in place; BORROWS code .
void despreader_init (
despreader_state_t * ch,
const uint8_t * code,
size_t code_len,
size_t sps,
double init_norm_freq,
double init_chip,
double bn_carrier,
double bn_code,
double bn_fll,
double zeta,
double spacing,
size_t periods_per_bit
)
The by-value counterpart to despreader_create(): the caller retains ownership of code (it is not copied or freed). Seeds the carrier NCO at init_norm_freq and the code phase at init_chip (the acquisition estimate). The carrier loop's update period is one code period (code_len * sps samples).
Parameters:
chState to initialise. Must be non-NULL.codeSpreading code (0/1 chips), one period; borrowed.code_lenCode length (chips per period); >= 1.spsSamples per chip.init_norm_freqSeed carrier frequency, cycles/sample.init_chipSeed code phase, chips.bn_carrierCarrier loop noise bandwidth.bn_codeCode loop noise bandwidth.bn_fllCarrier FLL-assist bandwidth (0 = pure PLL).zetaDamping factor for both loops.spacingDLL early/late tap offset, chips.periods_per_bitCode periods per data bit (1 = one bit per period).
function despreader_reset¶
Re-seed both loops to the create-time frequency/phase; keep config.
Restores the carrier NCO to init_norm_freq and the code phase to init_chip, zeroes the loop-filter accumulators and the bit-sync histogram, and clears the lock detectors — the spreading code and every configured bandwidth are preserved. Use it to re-run the same despreader over an independent stream and get a fresh instance's result.
Parameters:
stateMust be non-NULL.>>> import numpy as np >>> from doppler.dsss import Despreader >>> rng = np.random.default_rng(3) >>> code = rng.integers(0, 2, 31).astype(np.uint8) >>> chips = np.where(code & 1, -1.0, 1.0) >>> syms = np.where(rng.integers(0, 2, 40) == 1, -1.0, 1.0) >>> rx = np.concatenate( ... [s * np.repeat(chips, 4) for s in syms]).astype(np.complex64) >>> d = Despreader(code=code, sps=4) >>> first = d.bits(rx) >>> d.reset() # re-seed to acquisition >>> np.array_equal(first, d.bits(rx)) # same result as a fresh object True
function despreader_set_bn_carrier¶
function despreader_set_bn_code¶
function despreader_set_norm_freq¶
function despreader_set_state¶
function despreader_set_telemetry¶
Attach (or detach) a telemetry context across the despreader. Pure forwarder — the despreader registers no probes of its own: the carrier loop registers "<prefix>.car.lock" / ".e" / ".freq" / ".locked" and the code loop registers "<prefix>.code.e" / ".rate" / ".lock" / ".locked" (the ".locked" pair are the loops' verify-counted lockdet decisions, 0/1) — eight probes, all thinned by decim and emitted once per code period (the despreader flushes both loops at its per-period update). Passing NULL detaches both loops. Setup path, never hot; the context is borrowed and must outlive the attachment (SPSC rules indp_tlm/dp_tlm_core.h ).
int despreader_set_telemetry (
despreader_state_t * state,
dp_tlm_t * tlm,
const char * prefix,
uint32_t decim
)
Parameters:
stateMust be non-NULL.tlmTelemetry context to attach, or NULL to detach.prefixProbe-name prefix, e.g. "ch0".decimEmit every decim-th code period; >= 1.
Returns:
DP_OK, or DP_ERR_INVALID when the probe table cannot take all eight probes (the attach fails whole; everything detached).
>>> import numpy as np
>>> from doppler.dsss import Despreader
>>> from doppler.telemetry import Telemetry
>>> tlm = Telemetry(1 << 12)
>>> code = (np.arange(31) % 2).astype(np.uint8)
>>> ch = Despreader(code=code, sps=4)
>>> ch.set_telemetry(tlm, "ch0")
>>> names = sorted(tlm.probe_names)
>>> names[:4]
['ch0.car.e', 'ch0.car.freq', 'ch0.car.lock', 'ch0.car.locked']
>>> names[4:]
['ch0.code.e', 'ch0.code.lock', 'ch0.code.locked', 'ch0.code.rate']
>>> chips = 1.0 - 2.0 * (np.arange(31) % 2)
>>> x = np.tile(np.repeat(chips, 4), 40).astype(np.complex64)
>>> _ = ch.steps(x)
>>> recs = tlm.read() # eight records per code period
>>> len(recs) > 0 and len(recs) % 8 == 0
True
function despreader_state_bytes¶
function despreader_steps¶
Track carrier and code and despread a CF32 block, one prompt symbol per code period.
size_t despreader_steps (
despreader_state_t * state,
const float _Complex * x,
size_t x_len,
float _Complex * out,
size_t max_out
)
The continuous kernel: per input sample it wipes the carrier (Costas NCO) and correlates the de-rotated sample against the early/prompt/late code taps (DLL); per code period it dumps the prompt integrate-and-dump, updates the code loop on the early/late envelopes and the carrier loop on the same prompt, and emits that prompt. A partial period is carried in state across calls, so a long stream can be fed in blocks. Each emitted symbol's sign is the BPSK decision; its phase and magnitude are the soft information.
Parameters:
stateMust be non-NULL.xInput CF32 samples.x_lenNumber of input samples.outOutput buffer for prompt symbols (>=max_out).max_outCapacity ofoutin symbols.
Returns:
Number of prompt symbols written into out.
>>> import numpy as np
>>> from doppler.dsss import Despreader
>>> rng = np.random.default_rng(3)
>>> code = rng.integers(0, 2, 31).astype(np.uint8) # one code period
>>> chips = np.where(code & 1, -1.0, 1.0) # 0 -> +1, 1 -> -1
>>> bits = rng.integers(0, 2, 40).astype(np.uint8) # 1 bit / period
>>> syms = np.where(bits == 1, -1.0, 1.0)
>>> rx = np.concatenate(
... [s * np.repeat(chips, 4) for s in syms]).astype(np.complex64)
>>> d = Despreader(code=code, sps=4)
>>> prompt = d.steps(rx) # one prompt per code period
>>> hard = (prompt.real < 0).astype(np.uint8)
>>> e = np.mean(hard != bits[:hard.size]) # payload recovered
>>> round(float(min(e, 1.0 - e)), 4)
0.0
function despreader_steps_max_out¶
Macro Definition Documentation¶
define DESPREADER_STATE_MAGIC¶
define DESPREADER_STATE_VERSION¶
The documentation for this class was generated from the following file native/inc/despreader/despreader_core.h