File dsss_burst_receiver_core.h¶
FileList > dsss_burst_receiver > dsss_burst_receiver_core.h
Go to the source code of this file
DsssBurstReceiver — the burst chain composed in C. More...
#include "clib_common.h"#include "jm_perf.h"#include "buffer/buffer.h"#include "dp_state.h"#include "burst_capture/burst_capture_core.h"#include "burst_acq/burst_acq_core.h"#include "acq/acq_core.h"#include "burst_demod/burst_demod_core.h"#include "burst_despreader/burst_despreader_core.h"#include "ppe/ppe_core.h"#include "corr/corr_core.h"#include "corr2d/corr2d_core.h"#include "fft2d/fft2d_core.h"#include "spectral/spectral_core.h"#include "loop_filter/loop_filter_core.h"#include "detection/detection_core.h"#include "fft/fft_core.h"#include "pn/pn_core.h"#include "conv/conv_core.h"#include "rs/rs_core.h"#include "gold/gold_core.h"#include "mpsk/mpsk_core.h"
Classes¶
| Type | Name |
|---|---|
| struct | dsss_br_event_t One completed burst's event, as events() hands it back. |
| struct | dsss_burst_receiver_state_t DsssBurstReceiver state. |
Public Functions¶
| Type | Name |
|---|---|
| int | dsss_burst_receiver_configure_search_raw (dsss_burst_receiver_state_t * state, size_t doppler_bins, size_t n_noncoh) Pin the acquisition search grid, bypassing the auto-sizing. |
| dsss_burst_receiver_state_t * | dsss_burst_receiver_create (const uint8_t * acq_code, size_t acq_code_len, const uint8_t * data_code, size_t data_code_len, const uint8_t * sync, size_t sync_len, size_t reps, size_t spc, double chip_rate, size_t frame_syms, double cn0_dbhz, double doppler_uncertainty, double pfa, double pd, double carrier_hz, double max_rate, size_t est_segments) Create a burst receiver: acquisition, refine and demodulation composed behind one push(). |
| void | dsss_burst_receiver_destroy (dsss_burst_receiver_state_t * state) Destroy a dsss_burst_receiver instance and release all memory. |
| size_t | dsss_burst_receiver_events (dsss_burst_receiver_state_t * state, size_t n, dsss_br_event_t * out, size_t max_out) The event record for each burst the last push() returned. |
| size_t | dsss_burst_receiver_events_max_out (dsss_burst_receiver_state_t * state) Max records events() writes: one per burst the last push() returned. |
| double | dsss_burst_receiver_get_cn0_dbhz_est (const dsss_burst_receiver_state_t * state) |
| double | dsss_burst_receiver_get_doppler_hz_est (const dsss_burst_receiver_state_t * state) |
| double | dsss_burst_receiver_get_doppler_res_hz (const dsss_burst_receiver_state_t * state) |
| uint64_t | dsss_burst_receiver_get_dropped (const dsss_burst_receiver_state_t * state) |
| double | dsss_burst_receiver_get_est_freq_hz (const dsss_burst_receiver_state_t * state) |
| double | dsss_burst_receiver_get_est_rate_hz (const dsss_burst_receiver_state_t * state) |
| double | dsss_burst_receiver_get_est_snr_db (const dsss_burst_receiver_state_t * state) |
| bool | dsss_burst_receiver_get_frame_valid (const dsss_burst_receiver_state_t * state) |
| size_t | dsss_burst_receiver_get_min_gap (const dsss_burst_receiver_state_t * state) |
| uint64_t | dsss_burst_receiver_get_n_bursts (const dsss_burst_receiver_state_t * state) |
| size_t | dsss_burst_receiver_get_pending (const dsss_burst_receiver_state_t * state) |
| uint64_t | dsss_burst_receiver_get_preamble_start (const dsss_burst_receiver_state_t * state) |
| double | dsss_burst_receiver_get_refine_margin (const dsss_burst_receiver_state_t * state) |
| size_t | dsss_burst_receiver_get_refine_span (const dsss_burst_receiver_state_t * state) |
| size_t | dsss_burst_receiver_get_retain_span (const dsss_burst_receiver_state_t * state) |
| void | dsss_burst_receiver_get_state (const dsss_burst_receiver_state_t * state, void * blob) Serialize state's cross-call state intoblob (caller-owned,dsss_burst_receiver_state_bytes() long). |
| size_t | dsss_burst_receiver_llrs (dsss_burst_receiver_state_t * state, size_t n, float * out, size_t max_out) The SOFT bits of every burst the last push() returned. |
| size_t | dsss_burst_receiver_llrs_max_out (dsss_burst_receiver_state_t * state, size_t n) Max LLRs llrs() writes: frame bits x the bursts the last push returned. |
| size_t | dsss_burst_receiver_push (dsss_burst_receiver_state_t * state, const float _Complex * x, size_t x_len, uint8_t * out, size_t max_out) Stream samples; return the payload of EVERY burst that completed. |
| size_t | dsss_burst_receiver_push_max_out (dsss_burst_receiver_state_t * state, size_t x_len) Max bits push() can write for an input of x_len samples. |
| void | dsss_burst_receiver_reset (dsss_burst_receiver_state_t * state) Return to searching: drop the history and clear every read-back. |
| int | dsss_burst_receiver_set_state (dsss_burst_receiver_state_t * state, const void * blob) Restore cross-call state from blob (replacing it). |
| size_t | dsss_burst_receiver_state_bytes (const dsss_burst_receiver_state_t * state) Byte size of state's blob (envelope + payload + child). |
Macros¶
| Type | Name |
|---|---|
| define | DSSS_BURST_RECEIVER_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc)('D', 'B', 'R', 'X')Per-object envelope tag: "DBRX" (DsssBurstReceiver). |
| define | DSSS_BURST_RECEIVER_STATE_VERSION 5u |
Detailed Description¶
Composes the three certified burst objects behind one push(): acquisition SEARCHES the stream, a refine stage recovers the exact preamble start, and the demodulator produces the payload. It owns the hand-off between them the epoch, the fold, and the look-back reaching back to a burst start that has already gone past which is the part every caller previously redid by hand. See docs/design/dsss-burst-receiver.md.
Lifecycle: create, then push() repeatedly, then destroy. There is no step()/steps(): a burst is a frame, not a sample.
uint8_t acq[31], data[8], sync[13];
dsss_burst_receiver_state_t *rx = dsss_burst_receiver_create (
acq, 31, data, 8, sync, 13, 4, 4, 1.0e6, 61,
55.0, 0.0, 1e-3, 0.9, 0.0, 0.0, 10);
uint8_t bits[61]; // frame_syms per burst: sync | payload | CRC
size_t n = dsss_burst_receiver_push (rx, samples, n_samples, bits, 61);
// the bits are the FRAME as received; undoing it is a Frame's job
dsss_burst_receiver_destroy (rx);
Public Functions Documentation¶
function dsss_burst_receiver_configure_search_raw¶
Pin the acquisition search grid, bypassing the auto-sizing.
int dsss_burst_receiver_configure_search_raw (
dsss_burst_receiver_state_t * state,
size_t doppler_bins,
size_t n_noncoh
)
The escape hatch for a caller who wants a specific (doppler_bins, n_noncoh) rather than the grid the cn0_dbhz/pfa/pd sizing chooses. Forwards to the embedded engine unchanged.
Parameters:
stateMust be non-NULL.doppler_binsCoherent depth to pin, in[1, reps].n_noncohNon-coherent looks to combine.
Returns:
0 on success, non-zero if the grid is out of range.
>>> import numpy as np
>>> from doppler.dsss import DsssBurstReceiver
>>> rng = np.random.default_rng(0)
>>> rx = DsssBurstReceiver(
... rng.integers(0, 2, 31).astype(np.uint8),
... rng.integers(0, 2, 8).astype(np.uint8),
... np.zeros(13, dtype=np.uint8), reps=4, spc=4, frame_syms=32)
>>> rx.configure_search_raw(doppler_bins=1, n_noncoh=1)
function dsss_burst_receiver_create¶
Create a burst receiver: acquisition, refine and demodulation composed behind one push().
dsss_burst_receiver_state_t * dsss_burst_receiver_create (
const uint8_t * acq_code,
size_t acq_code_len,
const uint8_t * data_code,
size_t data_code_len,
const uint8_t * sync,
size_t sync_len,
size_t reps,
size_t spc,
double chip_rate,
size_t frame_syms,
double cn0_dbhz,
double doppler_uncertainty,
double pfa,
double pd,
double carrier_hz,
double max_rate,
size_t est_segments
)
Give it the waveform the two codes and the frame sync word plus the geometry, and stream samples in. It searches blindly for a burst, recovers the exact preamble start, and demodulates, publishing one detection event per burst through the read-back fields.
The look-back buffer is NOT a parameter. Its span is derived from the geometry here (detection lag + refine search + the burst itself), because every term is already known and a caller asked to size a history buffer is a caller handed a way to lose bursts silently.
Parameters:
acq_codePreamble PN chips (0/1), lengthacq_code_len.acq_code_lenPreamble code length, chips.data_codePayload spreading chips (0/1),data_code_lenlong.data_code_lenData code length, chips.syncFrame sync word (0/1 symbols),sync_lenlong.sync_lenSync word length, symbols.repsPreamble code repetitions (>= 1).spcSamples per chip (>= 1).chip_rateChip rate in Hz (> 0).frame_symsFrame symbols per burst (>= 1) — what push() returns, bit for bit.cn0_dbhzCarrier-to-noise density in dB-Hz (> 0), sizing the acquisition search.doppler_uncertaintyOne-sided Doppler half-range, Hz.pfaTarget false-alarm probability, in (0, 1).pdTarget detection probability, in (0, 1).carrier_hzRF carrier (Hz) for code-Doppler; 0 = ignore.max_rateChirp-rate search half-span (cycles/sample^2).est_segmentsSegments the feedforward estimator fits over.
Returns:
Heap-allocated state, or NULL if any argument is invalid.
Note:
Caller must call dsss_burst_receiver_destroy() when done.
>>> import numpy as np
>>> from doppler.dsss import DsssBurstReceiver
>>> rng = np.random.default_rng(0)
>>> acq = rng.integers(0, 2, 31).astype(np.uint8)
>>> dat = rng.integers(0, 2, 8).astype(np.uint8)
>>> syn = np.zeros(13, dtype=np.uint8)
>>> rx = DsssBurstReceiver(acq, dat, syn, reps=4, spc=4,
... frame_syms=32)
>>> rx.n_bursts
0
function dsss_burst_receiver_destroy¶
Destroy a dsss_burst_receiver instance and release all memory.
Parameters:
stateMay be NULL.
function dsss_burst_receiver_events¶
The event record for each burst the last push() returned.
size_t dsss_burst_receiver_events (
dsss_burst_receiver_state_t * state,
size_t n,
dsss_br_event_t * out,
size_t max_out
)
Row i describes the payload at out[i*frame_syms ...] of that push. A single push can complete many bursts and each needs its own event, so these are a list rather than the scalar read-backs those still exist and still describe the LAST burst, but they cannot speak for the others.
Valid until the next push(), reset() or set_state(). Deliberately not serialized: it describes one call, and keeping it out of the blob is what holds state_bytes() to a pure function of configuration.
Parameters:
stateMust be non-NULL.nIgnored. The record count is whatever the last push() produced, not something a caller chooses; this parameter exists because every variable-output method carries one, and the binding uses it only as a floor on the buffer it allocates.outRecords, caller-owned,max_outlong.max_outCapacity ofout; see events_max_out().
Returns:
Records written to out min(events_max_out(), max_out).
>>> import numpy as np
>>> from doppler.dsss import DsssBurstReceiver
>>> rng = np.random.default_rng(0)
>>> rx = DsssBurstReceiver(
... rng.integers(0, 2, 31).astype(np.uint8),
... rng.integers(0, 2, 8).astype(np.uint8),
... np.zeros(13, dtype=np.uint8), reps=4, spc=4, frame_syms=32)
>>> bits = rx.push(np.zeros(4096, dtype=np.complex64))
>>> len(rx.events()) == bits.size // 32 # one record per payload
True
function dsss_burst_receiver_events_max_out¶
Max records events() writes: one per burst the last push() returned.
Parameters:
stateMust be non-NULL.
Returns:
The number of bursts the most recent push() completed.
function dsss_burst_receiver_get_cn0_dbhz_est¶
function dsss_burst_receiver_get_doppler_hz_est¶
function dsss_burst_receiver_get_doppler_res_hz¶
function dsss_burst_receiver_get_dropped¶
function dsss_burst_receiver_get_est_freq_hz¶
function dsss_burst_receiver_get_est_rate_hz¶
function dsss_burst_receiver_get_est_snr_db¶
function dsss_burst_receiver_get_frame_valid¶
function dsss_burst_receiver_get_min_gap¶
function dsss_burst_receiver_get_n_bursts¶
function dsss_burst_receiver_get_pending¶
function dsss_burst_receiver_get_preamble_start¶
function dsss_burst_receiver_get_refine_margin¶
function dsss_burst_receiver_get_refine_span¶
function dsss_burst_receiver_get_retain_span¶
function dsss_burst_receiver_get_state¶
Serialize state's cross-call state intoblob (caller-owned,dsss_burst_receiver_state_bytes() long).
function dsss_burst_receiver_llrs¶
The SOFT bits of every burst the last push() returned.
size_t dsss_burst_receiver_llrs (
dsss_burst_receiver_state_t * state,
size_t n,
float * out,
size_t max_out
)
crealf(sym * derot) IS the log-likelihood ratio up to a scale, and the demodulator used to compute it, slice it to one bit and free it. A hard decision throws away roughly 2 dB of the coding gain a soft-input decoder exists to deliver (mpsk_soft_demap's own docstring), which is what makes a coded burst worth coding.
Concatenated the same way push()'s payloads are, one row of frame_bits per burst: burst i starts at i * frame_bits, in the order events() reports. The convention is mpsk_soft_demap's — positive means bit 0, so L < 0 reproduces exactly the bits push() returned. Spans the WHOLE frame rather than the payload alone, because a code covers what its description says it covers.
Valid until the next push(), reset() or set_state(); deliberately not serialized, for the same reason events() is not: it describes one call.
Parameters:
stateReceiver handle.nIgnored — the count is the last push's, not a request.outReceives the LLRs.max_outCapacity ofout; see llrs_max_out().
Returns:
LLRs written.
>>> import numpy as np
>>> from doppler.dsss import DsssBurstReceiver
>>> rng = np.random.default_rng(0)
>>> rx = DsssBurstReceiver(
... rng.integers(0, 2, 31).astype(np.uint8),
... rng.integers(0, 2, 8).astype(np.uint8),
... np.zeros(13, dtype=np.uint8), reps=4, spc=4, frame_syms=32)
>>> bits = rx.push(np.zeros(4096, dtype=np.complex64))
>>> len(bits), len(rx.llrs(rx.llrs_max_out(1))) # nothing decoded
(0, 0)
function dsss_burst_receiver_llrs_max_out¶
Max LLRs llrs() writes: frame bits x the bursts the last push returned.
Parameters:
stateReceiver handle.nIgnored, as in llrs().
function dsss_burst_receiver_push¶
Stream samples; return the payload of EVERY burst that completed.
size_t dsss_burst_receiver_push (
dsss_burst_receiver_state_t * state,
const float _Complex * x,
size_t x_len,
uint8_t * out,
size_t max_out
)
Retains x in the history ring and feeds the embedded acquisition. When a detection fires, the refine stage correlates the whole preamble to recover the exact preamble start the quantity acquisition structurally cannot report, its code phase being a lag modulo one code period and the burst is demodulated once its last sample has arrived.
EVERY SAMPLE OF x IS CONSUMED, and every burst that completes is returned by the call that completed it. Payloads are concatenated, so burst i occupies out from i*frame_syms, and events() returns the matching record for each. Returning 0 is normal, not an error: it means no burst completed in this call.
This is the contract doppler#1008 broke. push() used to return at most one burst per call AND abandon the rest of its input to do it, so a block carrying several bursts lost all but the first measured at 6/6 decoded with 333-sample blocks against 1/6 with one large one. The history ring is a contiguous window over the stream and is never reset between bursts, so a payload whose tail falls outside one call is completed by a later one.
Parameters:
stateMust be non-NULL.xInput samples (cf32),x_lenlong.x_lenNumber of input samples.outPayload bits, caller-owned,max_outlong.max_outCapacity ofout; see push_max_out().
Returns:
Bits written to out n_bursts_returned * frame_syms.
>>> import numpy as np
>>> from doppler.dsss import DsssBurstReceiver
>>> rng = np.random.default_rng(0)
>>> rx = DsssBurstReceiver(
... rng.integers(0, 2, 31).astype(np.uint8),
... rng.integers(0, 2, 8).astype(np.uint8),
... np.zeros(13, dtype=np.uint8), reps=4, spc=4, frame_syms=32)
>>> bits = rx.push(np.zeros(4096, dtype=np.complex64))
>>> bits.size # silence carries no burst
0
function dsss_burst_receiver_push_max_out¶
Max bits push() can write for an input of x_len samples.
push() returns EVERY burst it completed, so the bound scales with the input: distinct bursts cannot overlap, so they are at least burst_len apart, and a push of x_len samples can complete at most x_len/burst_len + 1 of them plus every detection already queued from an earlier call, which is q_cap.
Parameters:
stateMust be non-NULL.x_lenNumber of input samples the caller is about to push.
Returns:
(x_len/burst_len + 1 + q_cap) * frame_syms.
function dsss_burst_receiver_reset¶
Return to searching: drop the history and clear every read-back.
Resets the embedded acquisition, discards the retained look-back, and clears all the event fields, so a fresh stream cannot inherit the previous burst's verdict. The lifetime counters (n_bursts, dropped) deliberately survive a reset that zeroed them could hide that this receiver had already lost samples. Construction parameters are untouched.
Parameters:
stateMust be non-NULL.>>> import numpy as np >>> from doppler.dsss import DsssBurstReceiver >>> rng = np.random.default_rng(0) >>> rx = DsssBurstReceiver( ... rng.integers(0, 2, 31).astype(np.uint8), ... rng.integers(0, 2, 8).astype(np.uint8), ... np.zeros(13, dtype=np.uint8), reps=4, spc=4, frame_syms=32) >>> _ = rx.push(np.zeros(1024, dtype=np.complex64)) >>> rx.reset()
function dsss_burst_receiver_set_state¶
Restore cross-call state from blob (replacing it).
Returns:
DP_OK, or DP_ERR_INVALID if the envelope or any child rejects.
function dsss_burst_receiver_state_bytes¶
Byte size of state's blob (envelope + payload + child).
Macro Definition Documentation¶
define DSSS_BURST_RECEIVER_STATE_MAGIC¶
Per-object envelope tag: "DBRX" (DsssBurstReceiver).
define DSSS_BURST_RECEIVER_STATE_VERSION¶
The documentation for this class was generated from the following file native/inc/dsss_burst_receiver/dsss_burst_receiver_core.h