Skip to content

File carrier_mpsk_core.h

FileList > carrier_mpsk > carrier_mpsk_core.h

Go to the source code of this file

M-PSK carrier-tracking loop (integer-NCO de-rotation + decision PLL). More...

  • #include "clib_common.h"
  • #include "dp_state.h"
  • #include "jm_perf.h"
  • #include "lo/lo_core.h"
  • #include "loop_filter/loop_filter_core.h"
  • #include "mpsk/mpsk_core.h"
  • #include <math.h>

Classes

Type Name
struct carrier_mpsk_state_t
M-PSK carrier loop state.

Public Functions

Type Name
void carrier_mpsk_configure (carrier_mpsk_state_t * state, double bn, double zeta)
carrier_mpsk_state_t * carrier_mpsk_create (double bn, double zeta, double init_norm_freq, size_t tsamps, double bn_fll, int m)
Create an M-PSK carrier loop instance.
void carrier_mpsk_destroy (carrier_mpsk_state_t * state)
Destroy an M-PSK carrier loop instance and release all memory.
double carrier_mpsk_get_bn (const carrier_mpsk_state_t * state)
double carrier_mpsk_get_bn_fll (const carrier_mpsk_state_t * state)
double carrier_mpsk_get_last_error (const carrier_mpsk_state_t * state)
double carrier_mpsk_get_lock_metric (const carrier_mpsk_state_t * state)
int carrier_mpsk_get_m (const carrier_mpsk_state_t * state)
double carrier_mpsk_get_norm_freq (const carrier_mpsk_state_t * state)
void carrier_mpsk_get_state (const carrier_mpsk_state_t * state, void * blob)
Serialize the full loop state into blob .
void carrier_mpsk_init (carrier_mpsk_state_t * s, double bn, double zeta, double init_norm_freq, size_t tsamps, double bn_fll, int m)
Initialise an M-PSK carrier loop in place (no allocation).
void carrier_mpsk_reset (carrier_mpsk_state_t * state)
Re-seed the loop to its create-time frequency/phase; keep config.
void carrier_mpsk_set_bn (carrier_mpsk_state_t * state, double val)
void carrier_mpsk_set_bn_fll (carrier_mpsk_state_t * state, double val)
void carrier_mpsk_set_norm_freq (carrier_mpsk_state_t * state, double val)
int carrier_mpsk_set_state (carrier_mpsk_state_t * state, const void * blob)
Restore state; DP_OK, or DP_ERR_INVALID if the envelope rejects.
size_t carrier_mpsk_state_bytes (const carrier_mpsk_state_t * state)
Serialized-state byte size.
size_t carrier_mpsk_steps (carrier_mpsk_state_t * state, const float complex * x, size_t x_len, float complex * out, size_t max_out)
size_t carrier_mpsk_steps_max_out (carrier_mpsk_state_t * state)
JM_FORCEINLINE JM_HOT void carrier_mpsk_update (carrier_mpsk_state_t * s, float complex P)
Per-symbol carrier update: decision discriminator -> loop -> NCO.
JM_FORCEINLINE JM_HOT float complex carrier_mpsk_wipeoff (carrier_mpsk_state_t * s, float complex x)
Per-sample carrier wipe-off: de-rotate x by the NCO, advance it.

Macros

Type Name
define CARRIER_MPSK_EPS 1e-12f
define CARRIER_MPSK_LOCK_ALPHA 0.1
define CARRIER_MPSK_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc)('C', 'M', 'P', 'K')
define CARRIER_MPSK_STATE_VERSION 1u

Detailed Description

The M-ary generalization of the Costas loop (costas_core.h): per sample it de-rotates the input with the integer-phase lo NCO (carrier wipe-off); every tsamps samples it dumps the coherent integrate-and-dump prompt, runs a decision-directed M-PSK phase discriminator, filters the error through an embedded 2nd-order loop_filter_state_t, and steers the NCO frequency + phase. It tracks a small residual carrier (bulk Doppler is removed upstream by acquisition); the steering NCO is lo, so the phase is bounded and exactly reproducible.

The discriminator slices the prompt to the nearest constellation point ahat = mpsk_slice(P, m) and uses e = Im(P * conj(ahat)) / |P| (= sin of the phase error near lock). At m = 2 this reduces exactly to the BPSK Costas discriminator. An optional decision-directed cross-product FLL assist (bn_fll > 0) widens the frequency pull-in.

The loop locks to one of m phases — an M-fold ambiguity on absolute phase. Resolve it downstream with differential demapping (mpsk_diff_demap) or a sync word; this loop only recovers the carrier and emits the prompts.

The block API (carrier_mpsk_steps) is the Python face; the JM_FORCEINLINE carrier_mpsk_wipeoff()/carrier_mpsk_update() are the C composition API a receiver inlines into its own sample loop.

// QPSK carrier loop, 64 samples/symbol, FLL-assisted
carrier_mpsk_state_t *c = carrier_mpsk_create(0.05, 0.707, 0.0, 64, 0.01, 4);
float complex sym[16];
size_t k = carrier_mpsk_steps(c, rx, rx_len, sym, 16);
double f = c->nco.norm_freq;                 // tracked residual carrier
carrier_mpsk_destroy(c);

Public Functions Documentation

function carrier_mpsk_configure

void carrier_mpsk_configure (
    carrier_mpsk_state_t * state,
    double bn,
    double zeta
) 

function carrier_mpsk_create

Create an M-PSK carrier loop instance.

carrier_mpsk_state_t * carrier_mpsk_create (
    double bn,
    double zeta,
    double init_norm_freq,
    size_t tsamps,
    double bn_fll,
    int m
) 

Parameters:

  • bn Loop noise bandwidth (default 0.05).
  • zeta Damping factor (default 0.707).
  • init_norm_freq Seed carrier frequency, cycles/sample (default 0.0).
  • tsamps Samples per symbol (default 64).
  • bn_fll FLL-assist bandwidth (default 0.0 = pure PLL).
  • m Constellation order M, 2/4/8 (default 4 = QPSK).

Returns:

Heap-allocated state, or NULL on allocation failure.

Note:

Caller must call carrier_mpsk_destroy() when done.


function carrier_mpsk_destroy

Destroy an M-PSK carrier loop instance and release all memory.

void carrier_mpsk_destroy (
    carrier_mpsk_state_t * state
) 

Parameters:

  • state May be NULL.

function carrier_mpsk_get_bn

double carrier_mpsk_get_bn (
    const carrier_mpsk_state_t * state
) 

function carrier_mpsk_get_bn_fll

double carrier_mpsk_get_bn_fll (
    const carrier_mpsk_state_t * state
) 

function carrier_mpsk_get_last_error

double carrier_mpsk_get_last_error (
    const carrier_mpsk_state_t * state
) 

function carrier_mpsk_get_lock_metric

double carrier_mpsk_get_lock_metric (
    const carrier_mpsk_state_t * state
) 

function carrier_mpsk_get_m

int carrier_mpsk_get_m (
    const carrier_mpsk_state_t * state
) 

function carrier_mpsk_get_norm_freq

double carrier_mpsk_get_norm_freq (
    const carrier_mpsk_state_t * state
) 

function carrier_mpsk_get_state

Serialize the full loop state into blob .

void carrier_mpsk_get_state (
    const carrier_mpsk_state_t * state,
    void * blob
) 


function carrier_mpsk_init

Initialise an M-PSK carrier loop in place (no allocation).

void carrier_mpsk_init (
    carrier_mpsk_state_t * s,
    double bn,
    double zeta,
    double init_norm_freq,
    size_t tsamps,
    double bn_fll,
    int m
) 

Seeds the NCO at init_norm_freq and the loop integrator to the matching per-symbol frequency so de-rotation is correct from the first sample.

Parameters:

  • s State to initialise. Must be non-NULL.
  • bn Loop noise bandwidth, normalised to the symbol rate.
  • zeta Damping factor (0.707 = critically damped).
  • init_norm_freq Seed carrier frequency, cycles/sample.
  • tsamps Samples per symbol (the integrate-and-dump period).
  • bn_fll FLL-assist bandwidth (0 = pure PLL).
  • m Constellation order M (2, 4, 8).

function carrier_mpsk_reset

Re-seed the loop to its create-time frequency/phase; keep config.

void carrier_mpsk_reset (
    carrier_mpsk_state_t * state
) 

Parameters:

  • state Must be non-NULL.

function carrier_mpsk_set_bn

void carrier_mpsk_set_bn (
    carrier_mpsk_state_t * state,
    double val
) 

function carrier_mpsk_set_bn_fll

void carrier_mpsk_set_bn_fll (
    carrier_mpsk_state_t * state,
    double val
) 

function carrier_mpsk_set_norm_freq

void carrier_mpsk_set_norm_freq (
    carrier_mpsk_state_t * state,
    double val
) 

function carrier_mpsk_set_state

Restore state; DP_OK, or DP_ERR_INVALID if the envelope rejects.

int carrier_mpsk_set_state (
    carrier_mpsk_state_t * state,
    const void * blob
) 


function carrier_mpsk_state_bytes

Serialized-state byte size.

size_t carrier_mpsk_state_bytes (
    const carrier_mpsk_state_t * state
) 


function carrier_mpsk_steps

size_t carrier_mpsk_steps (
    carrier_mpsk_state_t * state,
    const float complex * x,
    size_t x_len,
    float complex * out,
    size_t max_out
) 

function carrier_mpsk_steps_max_out

size_t carrier_mpsk_steps_max_out (
    carrier_mpsk_state_t * state
) 

function carrier_mpsk_update

Per-symbol carrier update: decision discriminator -> loop -> NCO.

JM_FORCEINLINE  JM_HOT void carrier_mpsk_update (
    carrier_mpsk_state_t * s,
    float complex P
) 

Slices the prompt P to the nearest M-PSK point ahat, forms the decision-directed phase error e = Im(P conj(ahat)) / |P|, optionally runs a decision-directed cross-product FLL on the data-wiped prompts, filters, and steers the NCO frequency + a proportional phase nudge. Updates the lock metric (decision-aligned Re(P conj(ahat))/|P|) and last_error. Inline.

Parameters:

  • s Carrier loop state. Must be non-NULL.
  • P The dumped integrate-and-dump prompt for this symbol.

function carrier_mpsk_wipeoff

Per-sample carrier wipe-off: de-rotate x by the NCO, advance it.

JM_FORCEINLINE  JM_HOT float complex carrier_mpsk_wipeoff (
    carrier_mpsk_state_t * s,
    float complex x
) 

x * conj(lo_step(nco)) — strips the (tracked) carrier ahead of the matched-filter integrate-and-dump. Inline, zero call overhead.

Parameters:

  • s Carrier loop state. Must be non-NULL.
  • x One input sample.

Returns:

The de-rotated sample to feed the integrator.


Macro Definition Documentation

define CARRIER_MPSK_EPS

#define CARRIER_MPSK_EPS `1e-12f`

define CARRIER_MPSK_LOCK_ALPHA

#define CARRIER_MPSK_LOCK_ALPHA `0.1`

define CARRIER_MPSK_STATE_MAGIC

#define CARRIER_MPSK_STATE_MAGIC `DP_FOURCC ('C', 'M', 'P', 'K')`

define CARRIER_MPSK_STATE_VERSION

#define CARRIER_MPSK_STATE_VERSION `1u`


The documentation for this class was generated from the following file native/inc/carrier_mpsk/carrier_mpsk_core.h