Skip to content

File async_dsss_receiver_core.h

File List > async_dsss_receiver > async_dsss_receiver_core.h

Go to the documentation of this file

#ifndef DP_ASYNC_DSSS_RECEIVER_CORE_H
#define DP_ASYNC_DSSS_RECEIVER_CORE_H

#include "doppler/RateConverter/RateConverter_core.h"
#include "doppler/acq/acq_core.h"
#include "doppler/carrier_acq/carrier_acq_core.h"
#include "doppler/cic/cic_core.h"
#include "doppler/costas/costas_core.h"
#include "doppler/dll/dll_core.h"
#include "doppler/dp_state.h"
#include "doppler/hbdecim/hbdecim_core.h"
#include "doppler/lockdet/lockdet_core.h"
#include "doppler/mpsk_receiver/mpsk_receiver_core.h"
#include "doppler/resamp/resamp_core.h"
#include "doppler/resample/resample_core.h"
#include "doppler/dp_complex.h"
#include <stdbool.h>
#include <stddef.h>
#include "doppler/psd/psd_core.h"
#include "doppler/detector/detector_core.h"
#include "doppler/detection/detection_core.h"
#include "doppler/spectral/spectral_core.h"
#include "doppler/corr/corr_core.h"
#include "doppler/fft/fft_core.h"
#include "doppler/acc_trace/acc_trace_core.h"
#include "doppler/ber/ber_core.h"
#include "doppler/doppler_channel/doppler_channel_core.h"
#include "doppler/dp_tlm/dp_tlm_core.h"

#ifdef __cplusplus
extern "C"
{
#endif

  /* The pre-despread Costas's loop bandwidth (not a constructor parameter).
   * The refine stage's frozen carrier never calls costas_update(), so its
   * own bn/zeta are irrelevant -- only its tsamps and init_norm_freq matter.
   *
   * 0.04 is set by pull-in and dynamics, not jitter: the refined seed
   * leaves a residual the loop must pull in, and tracking SPEC's 500 Hz/s
   * ramp with a per-period Type-II loop needs the bandwidth to keep the
   * velocity lag (~ramp/omega_n^2) small. It rides the ramp with a clean
   * despread constellation; about 0.03 is the least that works. The phase
   * detector is |P|-normalized, so this is the true noise bandwidth, and
   * the ~5 dB Es/N0 floor is still met.
   *
   * The pull-in range is measured: native/validation/costas_pullin.c sweeps
   * it in multiples of the `bn/m` bound (`m = 2`, the squaring
   * discriminator), its spot check on every push (CI's sweep job, `make
   * test-sweep`). At bn = 0.04 over a 2046-sample code period the bound is
   * 9.775e-6 cycles/sample, 60 Hz at SPEC's 6.138 MHz front end; the loop
   * acquires 100% inside it on both signs, is reliable to 2x and dead by 4x.
   * The receiver's hand-off residual was measured at -54..+375 Hz, routinely
   * 2-6x the bound (doppler#982): check the hand-off against the bound
   * before widening bn.
   *
   * No FLL: the cross-product frequency discriminator is far too noisy on
   * this loop's data-modulated input, and the squaring phase discriminator
   * is immune to the transitions, so the plain PLL tracks the coupled
   * Doppler. The FLL is not exposed at all. */
#define ASYNC_DSSS_RX_BN_CARRIER 0.04
#define ASYNC_DSSS_RX_CARRIER_PULLIN_HZ(chip_rate, code_len)                  \
  (ASYNC_DSSS_RX_BN_CARRIER * (chip_rate) / (2.0 * (double)(code_len)))
  /* The Dll's bn, per epoch whatever the steer rate (the symbol-period aid
   * steers once per symbol and keeps bn per epoch): DsssReceiver's value,
   * narrower than dp_dll_create()'s default 0.01 for noise immunity at the
   * low-Es/N0 floor. */
#define ASYNC_DSSS_RX_DLL_BN 0.002
  /* The tracking Dll's early-late spacing, chips. */
#define ASYNC_DSSS_RX_DLL_SPACING 0.5
  /* The damping of both Dlls this receiver builds, the tracker and the
   * refine collector, and the collector's early-late spacing, chips. Named
   * once because create() checks the very arguments each build site passes
   * (dp_dll_params_ok(), doppler#2103). */
#define ASYNC_DSSS_RX_DLL_ZETA 0.707
#define ASYNC_DSSS_RX_REFINE_DLL_SPACING 0.5
  /* The demodulator, MpskReceiver, as the track chain builds it: its
   * carrier loop's bn (symbol-rate normalised) and damping, its timing
   * loop's bn (chosen on steady-state EVM, see the build site in
   * async_dsss_receiver_core.c), its lock threshold, and MpskReceiver's
   * default RRC arguments, which the integrate-and-dump pulse does not
   * read. Named once so a harness that builds the same demodulator alone
   * (native/validation/async_dsss_receiver_loss.c) cannot drift from it. */
#define ASYNC_DSSS_RX_DEMOD_RRC_BETA 0.35
#define ASYNC_DSSS_RX_DEMOD_RRC_SPAN 8
#define ASYNC_DSSS_RX_DEMOD_BN_CARRIER 0.01
#define ASYNC_DSSS_RX_DEMOD_ZETA 0.707
#define ASYNC_DSSS_RX_DEMOD_BN_TIMING 0.005
#define ASYNC_DSSS_RX_DEMOD_LOCK_THRESH 0.3

#define ASYNC_DSSS_RX_REFINE_MIN_BLOCKS 7u

#define ASYNC_DSSS_RX_MAX_PEAKS 16u

#define ASYNC_DSSS_RX_MAX_EXCLUSIONS 64u

/* Symbol-lock detector on the emitted symbols. The lock signal is the
 * BPSK phase-lock statistic (I^2 - Q^2)/(I^2 + Q^2) = cos(2*phi) per
 * symbol (locked -> +1, 45deg-rotated/noise -> 0). The lock METRIC is that
 * signal integrated proportionally to SNR -- a power-weighted running mean,
 * EMA(I^2-Q^2)/EMA(I^2+Q^2), so high-|symbol| (high-SNR) symbols dominate
 * and noisy ones contribute little -- over a dwell of LOCK_DWELL symbols
 * (>= 30). A hysteretic lockdet (lockdet_core.h) then declares `locked`
 * after LOCK_N_UP consecutive symbols with the metric strictly above
 * LOCK_UP, and drops it after LOCK_N_DOWN below LOCK_DOWN. */
#define ASYNC_DSSS_RX_LOCK_DWELL 30u
#define ASYNC_DSSS_RX_LOCK_UP 0.5
#define ASYNC_DSSS_RX_LOCK_DOWN 0.3
#define ASYNC_DSSS_RX_LOCK_N_UP 30u
#define ASYNC_DSSS_RX_LOCK_N_DOWN 15u

/* The state machine's values (`state` below, also the first byte of a
 * serialized blob's extra record). Searching -> refining -> tracking, and
 * seed() puts a searching receiver at refining; lost is reached from tracking by the release rule and left
 * only by reset(). With reacquire the release skips lost and goes straight
 * back to searching. */
/* SSOT: enum=async_dsss_rx_state */
typedef enum
{
  ASYNC_DSSS_RX_SEARCHING = 0, 
  ASYNC_DSSS_RX_REFINING  = 1, 
  ASYNC_DSSS_RX_TRACKING  = 2, 
  /* 3 was the deleted cell mode's idle. The values are persisted (a blob,
     a telemetry capture), so it stays reserved: no state reaches it, and a
     blob that names it is refused. */
  ASYNC_DSSS_RX_RESERVED = 3,
  ASYNC_DSSS_RX_LOST     = 4, 
} async_dsss_rx_state_t;

  typedef struct
  {
    dp_acq_state_t *acq; 
    /* Refine stage: a frozen-carrier collection Dll feeding
     * CarrierAcquisition via a RateConverter. Rebuilt on every real
     * acquisition hit and on reset(); otherwise untouched. */
    dp_costas_state_t         car_frozen;
    dp_dll_state_t           *refine_dll;
    dp_RateConverter_state_t *refine_rc;
    dp_carrier_acq_state_t   *ca;
    size_t refine_segments; 
    uint64_t refine_samples_fed; 
    uint64_t refine_periods;     
    /* Scratch, not state -- sized once per refine-chain (re)build (no
     * allocation in the steps() hot path): refine_dll's own emitted
     * partials (capacity refine_segments), then refine_rc's resampled
     * output (capacity refine_segments*refine_rc->rate + margin). */
    float _Complex *refine_dll_out_buf;
    size_t         refine_dll_out_cap;
    float _Complex *refine_rc_out_buf;
    size_t         refine_rc_out_cap;

    /* Track stage: DsssReceiver's own composition. dp_costas_init()'s tsamps is
     * one whole code period, and the pre-despread carrier loop updates once
     * per period from a non-data-aided (squaring) combine of that period's
     * coherent-I&D partials (see adr_track_period() in the .c file). */
    dp_costas_state_t         car;
    dp_dll_state_t           *dll;
    dp_RateConverter_state_t *rc;
    dp_mpsk_receiver_state_t *rx;
    /* Scratch, not state -- sized at every track-chain (re)build and never
     * in the steps() hot path (#1192, docs/design/async-dsss-receiver.md
     * §11.5): the live
     * Dll's partials for ONE code period, then rc's resampled output for
     * it. The chain runs a period at a time, so both are fixed by tsamps
     * and rc->rate; they only ever grow. */
    float _Complex *track_dll_out_buf;
    size_t         track_dll_out_cap;
    float _Complex *track_rc_out_buf;
    size_t         track_rc_out_cap;

    /* Shared carrier-wipe scratch/carry -- refine and track stages never
     * run concurrently and both wipe in whole `tsamps`-sample periods, so
     * one buffer set serves either. */
    size_t         tsamps; 
    float _Complex *car_wiped_buf;
    float _Complex *car_carry_buf;
    size_t         car_carry_len;

    /* Own copy of the spreading code (same rationale as DsssReceiver's
     * own copy -- neither dp_acq_create_continuous() nor dp_dll_create()'s
     * borrow-vs-copy semantics are part of their public contract). */
    uint8_t *code;
    size_t   code_len;

    /* Config carried across every chain rebuild. */
    size_t spc;
    int    m;
    int    differential;
    size_t segments; 
    size_t sps;      
    int    n;        
    double chip_rate;
    double symbol_rate;
    double cn0_dbhz; 
    double pfa;      
    double pd;       
    /* Refine-stage tuning, fixed at construction (see each on
     * dp_async_dsss_receiver_create()). */
    double refine_max_error_db;
    size_t refine_samples_per_symbol;
    double refine_design_margin_db;
    size_t refine_n_fft;
    size_t refine_zero_pad;
    bool   refine_sequential;
    size_t refine_max_n_blocks;
    size_t refine_min_blocks; 
    double carrier_freq_hz; 
    int state; 
    double   lost_confirm_s;       
    uint64_t lost_confirm_samples; 
    bool     reacquire;            
    uint64_t state_samples;        
    double   code_only_s;  
    double   doppler_rate; 
    uint64_t samples_in;   
    bool     align_pending; 
    size_t   rank;   
    size_t   n_excl; 
    double   excl[ASYNC_DSSS_RX_MAX_EXCLUSIONS]; 
    uint64_t refused_hits;         
    uint64_t both_down_samples;    
    int      had_lock;             
    int      car_coasting;         
    dp_costas_state_t car_held;       
    double   seed_chip_phase;     
    double   seed_doppler_hz_est; 
    double   doppler_hz_est;      
    double   cn0_dbhz_est;        
    /* Symbol-lock detector running state (see the ASYNC_DSSS_RX_LOCK_*
     * defines). lock_num/lock_den are the power-weighted EMAs of I^2-Q^2 and
     * I^2+Q^2; lock_metric = lock_num/lock_den = cos(2*phi); sym_lockdet is
     * the hysteretic up/down declare. Reset when the track chain is (re)built
     * (fresh lock per pass). */
    double          lock_num;
    double          lock_den;
    double          lock_metric;
    double          lock_alpha; 
    dp_lockdet_state_t sym_lockdet;

    /* Telemetry: the attach REQUEST, kept because every seed destroys and
     * rebuilds the tracking chain -- the loops it holds are re-attached
     * from here (dp_async_dsss_receiver_set_telemetry). Borrowed, and never
     * in a blob: a handle to the outside world is not state. */
    dp_tlm_t *tlm;                         
    char      tlm_prefix[DP_TLM_NAME_MAX]; 
    uint32_t  tlm_decim;                   
    int32_t   tlm_sym_lock;   
    int32_t   tlm_sym_locked; 
    int32_t   tlm_state;      
    int32_t   tlm_handover;   
    int32_t   tlm_refused;    
  } dp_async_dsss_receiver_state_t;

  dp_async_dsss_receiver_state_t *dp_async_dsss_receiver_create (
      const uint8_t *code, size_t code_len, double chip_rate,
      double symbol_rate, size_t spc, int m, double cn0_dbhz, double pfa,
      double pd, double doppler_uncertainty, size_t segments, size_t sps,
      int differential, double refine_max_error_db,
      size_t refine_samples_per_symbol, double refine_design_margin_db,
      size_t refine_n_fft, size_t refine_zero_pad, bool refine_sequential,
      size_t refine_max_n_blocks, double carrier_freq_hz,
      double lost_confirm_s, bool reacquire, double code_only_s,
      double doppler_rate);

  void dp_async_dsss_receiver_destroy (dp_async_dsss_receiver_state_t *state);

  void dp_async_dsss_receiver_reset (dp_async_dsss_receiver_state_t *state);

  int dp_async_dsss_receiver_set_telemetry (
      dp_async_dsss_receiver_state_t *state, dp_tlm_t *tlm, const char *prefix,
      uint32_t decim);

  size_t dp_async_dsss_receiver_steps_max_out (dp_async_dsss_receiver_state_t *state);

  size_t dp_async_dsss_receiver_steps (dp_async_dsss_receiver_state_t *state,
                                    const float _Complex *x, size_t x_len,
                                    float _Complex *out, size_t max_out);

  int dp_async_dsss_receiver_seed (dp_async_dsss_receiver_state_t *state,
                                double chip_phase, double doppler_hz_est,
                                double cn0_dbhz_est);

  int dp_async_dsss_receiver_seed_ok (const dp_async_dsss_receiver_state_t *state,
                                      double chip_phase,
                                      double doppler_hz_est);

  typedef struct
  {
    int state; 
    double doppler_hz; 
    double chip_phase; 
    double code_rate;  
    double cn0_dbhz_est; 
    int    code_locked;  
    int    locked;       
    double lock_metric;  
    double lock_threshold; 
    double car_last_error; 
    double mpsk_last_error; 
    uint64_t state_samples; 
    uint64_t both_down_samples; 
    uint64_t refused_hits;      
  } async_dsss_receiver_status_t;

  async_dsss_receiver_status_t dp_async_dsss_receiver_status (
      const dp_async_dsss_receiver_state_t *state);

  int dp_async_dsss_receiver_get_lost (const dp_async_dsss_receiver_state_t *state);

  int dp_async_dsss_receiver_configure_search_raw (
      dp_async_dsss_receiver_state_t *state, size_t doppler_bins,
      size_t n_noncoh);

  int dp_async_dsss_receiver_set_refine_min_blocks (
      dp_async_dsss_receiver_state_t *state, size_t n_blocks);

  int dp_async_dsss_receiver_set_threads (
      dp_async_dsss_receiver_state_t *state, int n);

  int dp_async_dsss_receiver_set_rank (dp_async_dsss_receiver_state_t *state,
                                       size_t rank);

  size_t dp_async_dsss_receiver_get_rank (
      const dp_async_dsss_receiver_state_t *state);

  int dp_async_dsss_receiver_set_exclusions (
      dp_async_dsss_receiver_state_t *state, const double *phases,
      size_t phases_len);

  size_t dp_async_dsss_receiver_get_n_exclusions (
      const dp_async_dsss_receiver_state_t *state);

  int dp_async_dsss_receiver_get_threads (
      const dp_async_dsss_receiver_state_t *state);

  size_t dp_async_dsss_receiver_get_coherent_bins (
      const dp_async_dsss_receiver_state_t *state);

  void dp_async_dsss_receiver_configure_lock_raw (
      dp_async_dsss_receiver_state_t *state, double up_thresh,
      double down_thresh, size_t n_looks, double alpha, uint32_t n_up,
      uint32_t n_down);

  int dp_async_dsss_receiver_configure_chain_raw (
      dp_async_dsss_receiver_state_t *state, size_t segments, size_t sps,
      int n);

  int    dp_async_dsss_receiver_get_tracking (
      const dp_async_dsss_receiver_state_t *state);
  int    dp_async_dsss_receiver_get_refining (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_doppler_hz (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_cn0_dbhz_est (
      const dp_async_dsss_receiver_state_t *state);
  size_t dp_async_dsss_receiver_get_segments (
      const dp_async_dsss_receiver_state_t *state);
  size_t dp_async_dsss_receiver_get_sps (const dp_async_dsss_receiver_state_t *state);
  int    dp_async_dsss_receiver_get_n (const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_chip_phase (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_code_rate (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_lock (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_norm_freq (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_nco_freq (
      const dp_async_dsss_receiver_state_t *state);
  int dp_async_dsss_receiver_get_locked (
      const dp_async_dsss_receiver_state_t *state);
  int dp_async_dsss_receiver_get_code_locked (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_car_last_error (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_car_nco_freq (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_mpsk_last_error (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_lock_metric (
      const dp_async_dsss_receiver_state_t *state);
  double dp_async_dsss_receiver_get_lock_threshold (
      const dp_async_dsss_receiver_state_t *state);

  /* ── Serializable state (standard bytes interface; see dp_state.h) ──────
   * Composition: acq + car_frozen + refine_dll + refine_rc + ca + car +
   * car_held + dll + rc + rx, a fixed shape; segments/sps/n/
   * refine_segments are the layout key. */

  typedef struct
  {
    uint8_t  state;
    uint8_t  had_lock;     
    uint8_t  car_coasting; 
    uint8_t  _pad[5]; 
    double   seed_chip_phase;
    double   seed_doppler_hz_est;
    double   doppler_hz_est;
    double   cn0_dbhz_est;
    uint64_t segments;
    uint64_t sps;
    uint64_t n;
    uint64_t refine_segments;
    uint64_t refine_samples_fed;
    uint64_t car_carry_len;
    uint64_t state_samples;
    uint64_t both_down_samples;
    double   lock_num;  
    double   lock_den;  
    double   lock_metric;         
    dp_lockdet_state_t sym_lockdet;  
    uint64_t refused_hits; 
    uint64_t refine_min_blocks; 
    uint64_t samples_in;    
    uint64_t align_pending; 
    int64_t  threads;       
    uint64_t rank;          
    uint64_t n_excl;        
    double   excl[ASYNC_DSSS_RX_MAX_EXCLUSIONS]; 
  } async_dsss_receiver_extra_t;

#define ASYNC_DSSS_RECEIVER_STATE_MAGIC DP_FOURCC ('A', 'D', 'R', 'X')
#define ASYNC_DSSS_RECEIVER_STATE_VERSION 13u /* v13: rank, exclusions; v12: samples_in, the dwell alignment, threads; v11: no cell mode; v10: the dll children grew (output level, #2495); v9: refine_min_blocks; v8: refused_hits; v7: no hand-off flavor; v6: the cell pull-in */

  size_t dp_async_dsss_receiver_state_bytes (
      const dp_async_dsss_receiver_state_t *state);
  void   dp_async_dsss_receiver_get_state (
      const dp_async_dsss_receiver_state_t *state, void *blob);
  int dp_async_dsss_receiver_set_state (dp_async_dsss_receiver_state_t *state,
                                     const void *blob);

#ifdef __cplusplus
}
#endif

#endif /* ASYNC_DSSS_RECEIVER_CORE_H */