From 75153ab12ed583ef8febe186433affa01eed06ca Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 24 Mar 2021 22:18:22 +0100 Subject: [PATCH] DATV: retrofit of leandvb changes up to Jan 9, 2020 --- plugins/channelrx/demoddatv/leansdr/dvbs2.h | 1971 ++++++++++++------- 1 file changed, 1221 insertions(+), 750 deletions(-) diff --git a/plugins/channelrx/demoddatv/leansdr/dvbs2.h b/plugins/channelrx/demoddatv/leansdr/dvbs2.h index 36c0f6cf8..b5acae897 100644 --- a/plugins/channelrx/demoddatv/leansdr/dvbs2.h +++ b/plugins/channelrx/demoddatv/leansdr/dvbs2.h @@ -14,6 +14,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +// Latest updates: +// | S2: Revert to tracking all symbols. pabr committed on Mar 6, 2019 +// | Cleanup: Remove debug code. pabr committed on Mar 6, 2019 +// | S2: Dummy PLFRAME handling pabr committed on Mar 26, 2019 +// | S2: Preliminary support for GSE pabr committed on Mar 26, 2019 +// | leandvbtx: Signal S2 MATYPE as TS. pabr committed on Mar 26, 2019 +// | DVB-S2 VCM support, suitable for ACM reception (not MIS). pabr committed on Nov 24, 2019 +// | Remove unused constants. pabr committed on Dec 4, 2019 +// | S2 RX: Capture TED decision history in sampler_state. pabr committed on Dec 5, 2019 +// | S2 RX: Print error rate on PLS symbols. pabr committed on Dec 5, 2019 +// | New DVB-S2 receiver with PL-based carrier recovery. modcod/framesize filtering for VCM. pabr committed on Jan 9, 2020 + #ifndef LEANSDR_DVBS2_H #define LEANSDR_DVBS2_H @@ -141,6 +153,22 @@ struct s2_plscodes static const uint64_t SCRAMBLING = 0x719d83c953422dfa; }; // s2_plscodes +static const int PILOT_LENGTH = 36; + +// Date about pilots. +// Mostly for consistency with s2_sof and s2_plscodes. + +template +struct s2_pilot +{ + static const int LENGTH = PILOT_LENGTH; + complex symbol; + + s2_pilot() { + symbol.re = symbol.im = cstln_amp*0.707107; + } +}; // s2_pilot + // S2 SCRAMBLING // Precomputes the symbol rotations for PL scrambling. // EN 302 307-1 section 5.5.4 Physical layer scrambling @@ -237,12 +265,18 @@ struct s2_pls int framebits() const { return sf ? 16200 : 64800; } + + bool is_dummy() { + return (modcod==0); + } }; +static const int PLSLOT_LENGTH = 90; + template struct plslot { - static const int LENGTH = 90; + static const int LENGTH = PLSLOT_LENGTH; bool is_pls; union { s2_pls pls; @@ -338,9 +372,7 @@ struct s2_frame_transmitter : runnable ) : runnable(sch, "S2 frame transmitter"), in(_in), - out(_out, modcod_info::MAX_SYMBOLS_PER_FRAME), - cstln(nullptr), - csymbols(nullptr) + out(_out, modcod_info::MAX_SYMBOLS_PER_FRAME) { float amp = cstln_amp / sqrtf(2); qsymbols[0].re = +amp; @@ -351,16 +383,15 @@ struct s2_frame_transmitter : runnable qsymbols[2].im = +amp; qsymbols[3].re = -amp; qsymbols[3].im = -amp; + + // Clear the constellation cache. + for (int i = 0; i < 32; ++i) { + pcsymbols[i] = nullptr; + } } ~s2_frame_transmitter() { - if (cstln) { - delete cstln; - } - if (csymbols) { - delete csymbols; - } } void run() @@ -389,7 +420,6 @@ struct s2_frame_transmitter : runnable break; } - update_cstln(mcinfo); int nw = run_frame(pls, mcinfo, pin + 1, nslots, out.wr()); if (nw != nsymbols) { @@ -417,6 +447,7 @@ struct s2_frame_transmitter : runnable int pls_index = (pls->modcod << 2) | (pls->sf << 1) | pls->pilots; memcpy(pout, plscodes.symbols[pls_index], plscodes.LENGTH * sizeof(*pout)); pout += plscodes.LENGTH; + complex *csymbols = get_csymbols(pls->modcod); // Slots and pilots int till_next_pilot = pls->pilots ? 16 : nslots; uint8_t *scr = &scrambling.Rn[0]; @@ -471,26 +502,26 @@ struct s2_frame_transmitter : runnable private: pipereader> in; pipewriter> out; - cstln_lut *cstln; // nullptr initially - complex *csymbols; - // Valid iff cstln is valid. RMS cstln_amp. + complex *pcsymbols[32]; // Constellations in use, indexed by modcod - void update_cstln(const modcod_info *mcinfo) + complex *get_csymbols(int modcod) { - if (!cstln || cstln->nsymbols != mcinfo->nsymbols) + if (!pcsymbols[modcod]) { - if (cstln) - { - fprintf(stderr, "Warning: Variable MODCOD is inefficient\n"); - delete cstln; - } + const modcod_info *mcinfo = check_modcod(modcod); - if (sch->debug) { - fprintf(stderr, "Building constellation %d\n", mcinfo->nsymbols); + if (sch->debug) + { + fprintf( + stderr, + "Building constellation %s ratecode %d\n", + cstln_base::names[mcinfo->c], + mcinfo->rate + ); } // TBD Different Es/N0 for short frames ? - cstln = new cstln_lut( + cstln_lut cstln( mcinfo->c, mcinfo->esn0_nf, mcinfo->g1, @@ -498,18 +529,16 @@ struct s2_frame_transmitter : runnable mcinfo->g3 ); - if (csymbols) { - delete csymbols; - } + pcsymbols[modcod] = new complex[cstln.nsymbols]; - csymbols = new complex[cstln->nsymbols]; - - for (int s = 0; s < cstln->nsymbols; ++s) + for ( int s=0; ssymbols[s].re; - csymbols[s].im = cstln->symbols[s].im; + pcsymbols[modcod][s].re = cstln.symbols[s].re; + pcsymbols[modcod][s].im = cstln.symbols[s].im; } } + + return pcsymbols[modcod]; } complex qsymbols[4]; // RMS cstln_amp @@ -520,83 +549,79 @@ struct s2_frame_transmitter : runnable // S2 FRAME RECEIVER -static int pl_errors = 0, pl_symbols = 0; - #define TEST_DIVERSITY 0 -template +template struct s2_frame_receiver : runnable { - enum { - COARSE_FREQ, - FRAME_SEARCH, - FRAME_LOCKED, - } state; - sampler_interface *sampler; int meas_decimation; - float Ftune; // Tuning bias in cycles per symbol - float Fm; // Baud rate in Hz, for debug messages only. TBD remove. - bool strongpls; - float min_freqw16, max_freqw16; - - // State during COARSE_FREQ - complex diffcorr; - int coarse_count; - - // State during FRAME_SEARCH and FRAME_LOCKED - float freqw16; // Carrier frequency initialized by COARSE_FREQ - float phase16; // Estimated phase of carrier at next symbol - - float mu; // Time to next symbol, in samples - float omega0; // Samples per symbol - + float Ftune; // Tuning bias in cycles per symbol + bool allow_drift; // Unbounded carrier tracking + float omega0; // Samples per symbol + bool strongpls; // PL symbols at max amplitude (default: RMS) + uint32_t modcods; // Bitmask of desired modcods + uint8_t framesizes; // Bitmask of desired frame sizes + bool fastlock; // Synchronize more agressively + bool fastdrift; // Carrier drift faster than pilots + float freq_tol; // Tolerance on carrier frequency + float sr_tol; // Tolerance on symbol rate static const int MAX_SYMBOLS_PER_FRAME = - (1 + modcod_info::MAX_SLOTS_PER_FRAME) * plslot::LENGTH + - ((modcod_info::MAX_SLOTS_PER_FRAME - 1) / 16) * pilot_length; + (1+modcod_info::MAX_SLOTS_PER_FRAME)*PLSLOT_LENGTH + + ((modcod_info::MAX_SLOTS_PER_FRAME-1)/16)*PILOT_LENGTH; s2_frame_receiver( scheduler *sch, sampler_interface *_sampler, - pipebuf> &_in, - pipebuf> &_out, - pipebuf *_freq_out = nullptr, - pipebuf *_ss_out = nullptr, - pipebuf *_mer_out = nullptr, - pipebuf> *_cstln_out = nullptr, - pipebuf> *_cstln_pls_out = nullptr, - pipebuf> *_symbols_out = nullptr, - pipebuf *_state_out = nullptr + pipebuf< complex > &_in, + pipebuf< plslot > &_out, + pipebuf *_freq_out=NULL, + pipebuf *_ss_out=NULL, + pipebuf *_mer_out=NULL, + pipebuf< complex > *_cstln_out=NULL, + pipebuf< complex > *_cstln_pls_out=NULL, + pipebuf< complex > *_symbols_out=NULL, + pipebuf *_state_out=NULL ) : runnable(sch, "S2 frame receiver"), sampler(_sampler), meas_decimation(1048576), - Ftune(0), Fm(0), + Ftune(0), + allow_drift(false), strongpls(false), - in_power(0), - ev_power(0), - agc_gain(1), - agc_bw(1e-3), - nsyncs(0), - cstln(nullptr), - in(_in), out(_out, 1 + modcod_info::MAX_SLOTS_PER_FRAME), + modcods(0xffffffff), + framesizes(0x03), + fastlock(false), + fastdrift(false), + freq_tol(0.25), + sr_tol(100e-6), + cstln(NULL), + in(_in), out(_out,1+modcod_info::MAX_SLOTS_PER_FRAME), meas_count(0), freq_out(opt_writer(_freq_out)), ss_out(opt_writer(_ss_out)), mer_out(opt_writer(_mer_out)), - cstln_out(opt_writer(_cstln_out, 1024)), - cstln_pls_out(opt_writer(_cstln_pls_out, 1024)), - symbols_out(opt_writer(_symbols_out, MAX_SYMBOLS_PER_FRAME)), + cstln_out(opt_writer(_cstln_out,1024)), + cstln_pls_out(opt_writer(_cstln_pls_out,1024)), + symbols_out(opt_writer(_symbols_out,MAX_SYMBOLS_PER_FRAME)), state_out(opt_writer(_state_out)), - report_state(false), + first_run(true), scrambling(0), + pls_total_errors(0), + pls_total_count(0), m_modcodType(-1), - m_modcodRate(-1) + m_modcodRate(-1), + diffs(nullptr), + sspilots(nullptr) { // Constellation for PLS - qpsk = new cstln_lut(cstln_base::QPSK); - add_syncs(qpsk); - init_coarse_freq(); + qpsk = new cstln_lut(cstln_base::QPSK); + + // Clear the constellation cache. + for (int i=0; i<32; ++i) { + cstlns[i] = NULL; + } + #if TEST_DIVERSITY fprintf(stderr, "** DEBUG: Diversity test mode (slower)\n"); #endif @@ -606,39 +631,105 @@ struct s2_frame_receiver : runnable { delete qpsk; - if (cstln) { - delete cstln; + for (int i=0; i<32; ++i) + { + if (cstlns[i]) { + delete cstlns[i]; + } + } + + if (diffs) { + delete[] diffs; + } + + if (sspilots) { + delete[] sspilots; } } + enum { + FRAME_DETECT, // Looking for PLHEADER + FRAME_PROBE, // Aligned with PLHEADER, ready to recover carrier + FRAME_LOCKED, // Demodulating + } state; + + // sampler_state holds the entire state of the PLL. + // Useful for looking ahead (e.g. next pilots/SOF) then rewinding. + + struct sampler_state { + complex *p; // Pointer to samples (update when entering run()) + float mu; // Time of next symbol, counted from p + float omega; // Samples per symbol + float gain; // Scaling factor toward cstln_amp + float ph16; // Carrier phase at next symbol (cycles * 65536) + float fw16; // Carrier frequency (cycles per symbol * 65536) + uint8_t *scr; // Position in scrambling sequence for next symbol + + void normalize() { + ph16 = fmodf(ph16, 65536.0f); // Rounding direction irrelevant + } + + void skip_symbols(int ns) + { + mu += omega * ns; + p += (int)floorf(mu); + mu -= floorf(mu); + ph16 += fw16 * ns; + normalize(); + scr += ns; + } + + char *format() { + static char buf[256]; + sprintf( + buf, + "%9.2lf %+6.0f ppm %+3.0f ° %f", + (double)((p-(complex*)NULL)&262143)+mu, // Arbitrary wrap + fw16*1e6/65536, + fmodfs(ph16,65536.0f)*360/65536, + gain + ); + return buf; + } + }; + + float min_freqw16, max_freqw16; + + // State during FRAME_SEARCH and FRAME_LOCKED + sampler_state ss_cache; + void run() { + if (strongpls) { + fail("--strongpls is broken."); + } + // Require enough samples to detect one plheader, // TBD margin ? int min_samples = (1 + MAX_SYMBOLS_PER_FRAME + - plslot::LENGTH) * - omega0 * 2; + sof.LENGTH+plscodes.LENGTH)*omega0 * 2; + while (in.readable() >= min_samples + sampler->readahead() && - out.writable() >= 1 + modcod_info::MAX_SLOTS_PER_FRAME && - opt_writable(freq_out, 1) && - opt_writable(ss_out, 1) && - opt_writable(mer_out, 1) && - opt_writable(symbols_out, MAX_SYMBOLS_PER_FRAME) && - opt_writable(state_out, 1)) + out.writable() >= 1+modcod_info::MAX_SLOTS_PER_FRAME && + opt_writable(freq_out, 1) && + opt_writable(ss_out, 1) && + opt_writable(mer_out, 1) && + opt_writable(symbols_out, MAX_SYMBOLS_PER_FRAME) && + opt_writable(state_out, 1)) { - if (report_state) + if (first_run) { - // Report unlocked state on first invocation. - opt_write(state_out, 0); - report_state = false; + enter_frame_detect(); + first_run = false; } - switch (state) + + switch ( state ) { - case COARSE_FREQ: - run_frame_coarse(); + case FRAME_DETECT: + run_frame_detect(); break; - case FRAME_SEARCH: - run_frame_search(); + case FRAME_PROBE: + run_frame_probe(); break; case FRAME_LOCKED: run_frame_locked(); @@ -647,455 +738,163 @@ struct s2_frame_receiver : runnable } } - // Initial state - void init_coarse_freq() - { - diffcorr = 0; - coarse_count = 0; - - for (int i = 0; i < 3; i++) - { - hist[i].p = 0; - hist[i].c = 0; - } - - state = COARSE_FREQ; - } - // State transtion - void enter_coarse_freq() + void enter_frame_detect() { - opt_write(state_out, 0); - init_coarse_freq(); - } + state = FRAME_DETECT; + // Setup sampler for interpolation only. + ss_cache.fw16 = 65536 * Ftune; + ss_cache.ph16 = 0; + ss_cache.mu = 0; + ss_cache.gain = 1; + ss_cache.omega = omega0; - void run_frame_coarse() - { - freqw16 = 65536 * Ftune; - min_freqw16 = freqw16 - 65536.0 / 9; - max_freqw16 = freqw16 + 65536.0 / 9; - - complex *pin = in.rd(); - complex p = *pin++; - int nsamples = MAX_SYMBOLS_PER_FRAME * omega0; - - for (int s = nsamples; s--; ++pin) + // Set frequency tracking boundaries around tuning frequency. + if (allow_drift) { - complex n = *pin; - diffcorr.re += p.re * n.re + p.im * n.im; - diffcorr.im += p.re * n.im - p.im * n.re; - p = n; + // Track across the whole baseband. + min_freqw16 = ss_cache.fw16 - omega0*65536; + max_freqw16 = ss_cache.fw16 + omega0*65536; + } + else + { + min_freqw16 = ss_cache.fw16 - freq_tol*65536.0; + max_freqw16 = ss_cache.fw16 + freq_tol*65536.0; } - in.read(nsamples); - ++coarse_count; - - if (coarse_count == 50) - { - float freqw = atan2f(diffcorr.im, diffcorr.re) * omega0; - fprintf(stderr, "COARSE(%d): %f rad/symb (%.0f Hz at %.0f baud)\n", - coarse_count, freqw, freqw * Fm / (2 * M_PI), Fm); -#if 0 - freqw16 = freqw * 65536 / (2*M_PI); -#else - fprintf(stderr, "Ignoring coarse det, using %f\n", freqw16 * Fm / 65536); -#endif - enter_frame_search(); - } - } - - // State transtion - void enter_frame_search() - { opt_write(state_out, 0); - mu = 0; - phase16 = 0; if (sch->debug) { - fprintf(stderr, "ACQ\n"); + fprintf(stderr, "DETECT\n"); } - state = FRAME_SEARCH; - } - - void run_frame_search() - { - complex *psampled; - - if (cstln_out && cstln_out->writable() >= 1024) { - psampled = cstln_out->wr(); - } else { - psampled = nullptr; - } - - // Preserve float precision - phase16 -= 65536 * floor(phase16 / 65536); - int nsymbols = MAX_SYMBOLS_PER_FRAME; // TBD Adjust after PLS decoding - sampler_state ss = {in.rd(), mu, phase16, freqw16, nullptr}; - sampler->update_freq(ss.fw16 / omega0); - - if (!in_power) { - init_agc(ss.p, 64); - } - - update_agc(); - - for (int s = 0; s < nsymbols; ++s) + if (fastlock || first_run) { - complex p0 = interp_next(&ss); - track_agc(p0); - complex p = p0 * agc_gain; - - // Constellation plot - if (psampled && s < 1024) { - *psampled++ = p; - } - - // Demodulate everything as QPSK. - // Occasionally it locks onto 8PSK at offet 2pi/16. - uint8_t symb = track_symbol(&ss, p, qpsk, 1); - - // Feed symbol into all synchronizers. - for (sync *ps = syncs; ps < syncs + nsyncs; ++ps) - { - ps->hist = (ps->hist << 1) | ((ps->tobpsk >> symb) & 1); - int errors = hamming_weight((ps->hist & sof.MASK) ^ sof.VALUE); - - if (errors <= S2_MAX_ERR_SOF_INITIAL) - { - if (sch->debug2) { - fprintf(stderr, "Found SOF+%d at %d offset %f\n", errors, s, ps->offset16); - } - - ss.ph16 += ps->offset16; - in.read(ss.p - in.rd()); - mu = ss.mu; - phase16 = ss.ph16; - freqw16 = ss.fw16; - - if (psampled) { - cstln_out->written(psampled - cstln_out->wr()); - } - - enter_frame_locked(); - return; - } - } - - ss.normalize(); + discard = 0; } - - // Write back sampler progress - in.read(ss.p - in.rd()); - mu = ss.mu; - phase16 = ss.ph16; - freqw16 = ss.fw16; - - if (psampled) { - cstln_out->written(psampled - cstln_out->wr()); + else + { + // Discard some data so that CPU usage during PLHEADER detection + // is at same level as during steady-state demodulation. + // This has no effect if the first detection is successful. + float duty_factor = 5; + discard = MAX_SYMBOLS_PER_FRAME * omega0 * (duty_factor+drand48()-0.5); } } - // State transtion + long discard; + + void run_frame_detect() + { + if ( discard ) + { + size_t d = std::min(discard, in.readable()); + in.read(d); + discard -= d; + return; + } + + sampler->update_freq(ss_cache.fw16/omega0); + + const int search_range = MAX_SYMBOLS_PER_FRAME; + ss_cache.p = in.rd(); + find_plheader(&ss_cache, search_range); +#if DEBUG_CARRIER + fprintf(stderr, "CARRIER diffcorr: %.0f%% %s\n", + q*100, ss_cache.format()); +#endif + in.read(ss_cache.p-in.rd()); + enter_frame_probe(); + } + + void enter_frame_probe() + { + if (sch->debug) { + fprintf(stderr, "PROBE\n"); + } + + state = FRAME_PROBE; + } + + void run_frame_probe() { + return run_frame_probe_locked(); + } + void enter_frame_locked() { - opt_write(state_out, 1); + state = FRAME_LOCKED; if (sch->debug) { fprintf(stderr, "LOCKED\n"); } - state = FRAME_LOCKED; + opt_write(state_out, 1); } - // Note: Starts after SOF - - struct sampler_state - { - complex *p; // Pointer to samples - float mu; // Time of next symbol, counted from p - float ph16; // Carrier phase at next symbol, cycles*65536 - float fw16; // Carrier frequency, cycles per symbol * 65536 - uint8_t *scr; // Position in scrambling sequeence - - void skip_symbols(int ns, float omega0) - { - mu += omega0 * ns; - ph16 += fw16 * ns; - scr += ns; - } - - void normalize() - { - ph16 = fmodf(ph16, 65536.0f); // Rounding direction irrelevant - } - }; - -#define xfprintf(...) \ - { \ + void run_frame_locked() { + return run_frame_probe_locked(); } - //#define xfprintf fprintf - void run_frame_locked() + // Process one frame. + // Perform additional carrier estimation if state==FRAME_PROBE. + + void run_frame_probe_locked() { - complex *psampled; - if (cstln_out && cstln_out->writable() >= 1024) { + complex *psampled; // Data symbols (one per slot) + + if (cstln_out && cstln_out->writable()>=1024) { psampled = cstln_out->wr(); } else { - psampled = nullptr; + psampled = NULL; } - complex *psampled_pls; + complex *psampled_pls; // PLHEADER symbols - if (cstln_pls_out && cstln_pls_out->writable() >= 1024) { + if (cstln_pls_out && cstln_pls_out->writable()>=1024) { psampled_pls = cstln_pls_out->wr(); } else { - psampled_pls = nullptr; + psampled_pls = NULL; } - #if TEST_DIVERSITY - complex *psymbols = symbols_out ? symbols_out->wr() : nullptr; + complex *psymbols = symbols_out ? symbols_out->wr() : NULL; float scale_symbols = 1.0 / cstln_amp; #endif - - xfprintf(stderr, "lock0step fw= %f (%.0f Hz) mu=%f\n", - freqw16, freqw16 * Fm / 65536, mu); - - sampler_state ss = {in.rd(), mu, phase16, freqw16, scrambling.Rn}; - sampler->update_freq(ss.fw16 / omega0); - - update_agc(); - - // Read PLSCODE - uint64_t plscode = 0; - complex pls_symbols[s2_plscodes::LENGTH]; - - for (int s = 0; s < plscodes.LENGTH; ++s) - { - complex p = interp_next(&ss) * agc_gain; -#if TEST_DIVERSITY - if (psymbols) - *psymbols++ = p * scale_symbols; -#endif - pls_symbols[s] = p; - - if (psampled_pls) { - *psampled_pls++ = p; - } - - int bit = (p.im < 1); // TBD suboptimal - plscode = (plscode << 1) | bit; - } - - int pls_index = -1; - int pls_errors = S2_MAX_ERR_PLSCODE + 1; // dmin=32 - // TBD: Optimiser - - for (int i = 0; i < plscodes.COUNT; ++i) - { - int e = hamming_weight(plscode ^ plscodes.codewords[i]); - - if (e < pls_errors) - { - pls_errors = e; - pls_index = i; - } - } - - if (pls_index < 0) - { - if (sch->debug2) { - fprintf(stderr, "Too many errors in plheader (%d)\n", pls_errors); - } - - in.read(ss.p - in.rd()); - enter_frame_search(); - return; - } - - // Adjust phase with PLS - complex pls_corr = conjprod(plscodes.symbols[pls_index], pls_symbols, plscodes.LENGTH); + sampler_state ss = ss_cache; + ss.p = in.rd(); ss.normalize(); - align_phase(&ss, pls_corr); + sampler->update_freq(ss.fw16/omega0); +#if DEBUG_CARRIER + fprintf(stderr, "CARRIER frame: %s\n", ss.format()); +#endif + // Interpolate PLHEADER. - s2_pls pls; - pls.modcod = pls_index >> 2; // Guaranteed 0..31 - pls.sf = pls_index & 2; - pls.pilots = pls_index & 1; - xfprintf(stderr, "PLS: modcod %d, short=%d, pilots=%d (%d errors)\n", - pls.modcod, pls.sf, pls.pilots, pls_errors); - const modcod_info *mcinfo = &modcod_infos[pls.modcod]; + const int PLH_LENGTH = sof.LENGTH + plscodes.LENGTH; + complex plh_symbols[PLH_LENGTH]; - if (!mcinfo->nslots_nf) + for (int s=0; srate == FEC910) - { - fprintf(stderr, "Unsupported or corrupted FEC\n"); - in.read(ss.p - in.rd()); - enter_frame_search(); - return; - } -#endif - // Store current MODCOD info - if (mcinfo->c != m_modcodType) { - m_modcodType = mcinfo->c; - } + complex p = interp_next(&ss) * ss.gain; + plh_symbols[s] = p; - if (mcinfo->rate != m_modcodRate) { - m_modcodRate = mcinfo->rate; - } - - // TBD Comparison of nsymbols is insufficient for DVB-S2X. - if (!cstln || cstln->nsymbols != mcinfo->nsymbols) - { - if (cstln) - { - fprintf(stderr, "Warning: Variable MODCOD is inefficient\n"); - delete cstln; - } - - fprintf(stderr, "Creating LUT for %s ratecode %d\n", - cstln_base::names[mcinfo->c], mcinfo->rate); - cstln = new cstln_lut(mcinfo->c, mcinfo->esn0_nf, - mcinfo->g1, mcinfo->g2, mcinfo->g3); - cstln->m_rateCode = (int) mcinfo->rate; - cstln->m_typeCode = (int) mcinfo->c; - cstln->m_setByModcod = true; -#if 0 - fprintf(stderr, "Dumping constellation LUT to stdout.\n"); - cstln->dump(stdout); -#endif - } - - int S = pls.sf ? mcinfo->nslots_nf / 4 : mcinfo->nslots_nf; - - plslot *pout = out.wr(), *pout0 = pout; - - // Output special slot with PLS information - pout->is_pls = true; - pout->pls = pls; - ++pout; - - // Read slots and pilots - - int pilot_errors = 0; - - // Slots to skip until next PL slot (pilot or sof) - int till_next_pls = pls.pilots ? 16 : S; - - for (int leansdr_slots = S; leansdr_slots--; ++pout, --till_next_pls) - { - if (till_next_pls == 0) - { - // Read pilot - int errors = 0; - complex corr = 0; - - for (int s = 0; s < pilot_length; ++s) - { - complex p0 = interp_next(&ss); - track_agc(p0); - complex p = p0 * agc_gain; -#if TEST_DIVERSITY - if (psymbols) - *psymbols++ = p * scale_symbols; -#endif - (void)track_symbol(&ss, p, qpsk, 1); - - if (psampled_pls) { - *psampled_pls++ = p; - } - - complex d = descramble(&ss, p); - - if (d.im < 0 || d.re < 0) { - ++errors; - } - - corr.re += d.re + d.im; - corr.im += d.im - d.re; - } - - if (errors > S2_MAX_ERR_PILOT) - { - if (sch->debug2) { - fprintf(stderr, "Too many errors in pilot (%d/36)\n", errors); - } - - in.read(ss.p - in.rd()); - enter_frame_search(); - return; - } - pilot_errors += errors; - ss.normalize(); - align_phase(&ss, corr); - till_next_pls = 16; - } - - // Read slot - pout->is_pls = false; - complex p; // Export last symbols for cstln_out - - for (int s = 0; s < pout->LENGTH; ++s) - { - p = interp_next(&ss) * agc_gain; -#if TEST_DIVERSITY - if (psymbols) - *psymbols++ = p * scale_symbols; -#endif -#if 1 || TEST_DIVERSITY - (void)track_symbol(&ss, p, cstln, 0); // SLOW -#endif - complex d = descramble(&ss, p); -#if 0 // Slow - SOFTSYMB *symb = &cstln->lookup(d.re, d.im)->ss; -#else // Avoid scaling floats. May wrap at very low SNR. - SOFTSYMB *symb = &cstln->lookup((int)d.re, (int)d.im)->ss; -#endif - pout->symbols[s] = *symb; - } - - if (psampled) { - *psampled++ = p; - } - } // slots - - // Read SOF - - for (int i = 0; i < 3; i++) - { - hist[i].p = 0; - hist[i].c = 0; - } - - complex sof_corr = 0; - uint32_t sofbits = 0; - - for (int s = 0; s < sof.LENGTH; ++s) - { - complex p0 = interp_next(&ss); - track_agc(p0); - complex p = p0 * agc_gain; -#if TEST_DIVERSITY - if (psymbols) - *psymbols++ = p * scale_symbols; -#endif if (psampled_pls) { *psampled_pls++ = p; } - - int bit = (p.im < 0); // suboptimal - sofbits = (sofbits << 1) | bit; - sof_corr += conjprod(sof.symbols[s], p); } - int sof_errors = hamming_weight(sofbits ^ sof.VALUE); + // Decode SOF. + + uint32_t sof_bits = 0; + + for (int i=0; i p = plh_symbols[i]; + float d = ( (i&1) ? p.im-p.re : p.im+p.re); + sof_bits = (sof_bits<<1) | (d<0); + } + + int sof_errors = hamming_weight(sof_bits ^ sof.VALUE); + pls_total_errors += sof_errors; + pls_total_count += sof.LENGTH; if (sof_errors >= S2_MAX_ERR_SOF) { @@ -1103,101 +902,777 @@ struct s2_frame_receiver : runnable fprintf(stderr, "Too many errors in SOF (%d/26)\n", sof_errors); } - in.read(ss.p - in.rd()); - enter_coarse_freq(); + in.read(ss.p-in.rd()); + enter_frame_detect(); return; } - ss.normalize(); - align_phase(&ss, sof_corr); + // Decode PLSCODE. - // Commit whole frame after final SOF. - out.written(pout - pout0); + uint64_t plscode = 0; - // Write back sampler progress - meas_count += ss.p - in.rd(); - in.read(ss.p - in.rd()); - mu = ss.mu; - phase16 = ss.ph16; - freqw16 = ss.fw16; - - // Measurements - if (psampled) { - cstln_out->written(psampled - cstln_out->wr()); + for (int i=0; i p = plh_symbols[sof.LENGTH+i]; + float d = ( (i&1) ? p.im-p.re : p.im+p.re); + plscode = (plscode<<1) | (d<0); } - if (psampled_pls) { - cstln_pls_out->written(psampled_pls - cstln_pls_out->wr()); + int plscode_errors = plscodes.LENGTH + 1; + int plscode_index = -1; // Avoid compiler warning. + + // TBD: Optimiser + for ( int i=0; iwritten(psymbols - symbols_out->wr()); + + pls_total_errors += plscode_errors; + pls_total_count += plscodes.LENGTH; + + if (plscode_errors >= S2_MAX_ERR_PLSCODE) + { + if (sch->debug2) { + fprintf(stderr, "Too many errors in plscode (%d)\n", plscode_errors); + } + + in.read(ss.p-in.rd()); + enter_frame_detect(); + return; } + + // ss now points to first data slot. + ss.scr = scrambling.Rn; + + complex plh_expected[PLH_LENGTH]; + + for (int i=0; i= meas_decimation) + } + + // Use known PLH symbols to estimate carrier phase and amplitude. + + float mer2 = match_ph_amp(plh_expected, plh_symbols, PLH_LENGTH, &ss); + float mer = 10*log10f(mer2); +#if DEBUG_CARRIER + fprintf(stderr, "CARRIER plheader: %s MER %.1f dB\n", ss.format(), mer); +#endif + // Parse PLSCODE. + + s2_pls pls; + pls.modcod = plscode_index >> 2; // Guaranteed 0..31 + pls.sf = plscode_index & 2; + pls.pilots = plscode_index & 1; + + plslot *pout=out.wr(), *pout0 = pout; + + if (sch->debug2) { - opt_write(freq_out, freqw16 / 65536 / omega0); - opt_write(ss_out, in_power); - // TBD Adjust if cfg.strongpls - float mer = ev_power ? (float)cstln_amp * cstln_amp / ev_power : 1; - opt_write(mer_out, 10 * log10f(mer)); - meas_count -= meas_decimation; + fprintf( + stderr, + "PLS: mc=%2d, sf=%d, pilots=%d (%2d/90) %4.1f dB ", + pls.modcod, + pls.sf, + pls.pilots, + sof_errors+plscode_errors, + mer + ); } - int all_errors = pls_errors + pilot_errors + sof_errors; - int max_errors = plscodes.LENGTH + sof.LENGTH; + // Determine contents of frame. - if (pls.pilots) { - max_errors += ((S - 1) / 16) * pilot_length; - } + int S; // Data slots in this frame + cstln_lut *dcstln; // Constellation for data slots - xfprintf(stderr, "success fw= %f (%.0f Hz) mu= %f " - "errors=%d/64+%d+%d/26 = %2d/%d\n", - freqw16, freqw16 * Fm / 65536, mu, - pls_errors, pilot_errors, sof_errors, all_errors, max_errors); - pl_errors += all_errors; - pl_symbols += max_errors; - } // run_frame_locked - - void shutdown() - { - fprintf(stderr, "PL SER: %f ppm\n", pl_errors / (pl_symbols + 1e-6) * 1e6); - } - - void init_agc(const complex *buf, int n) - { - in_power = 0; - - for (int i = 0; i < n; ++i) { - in_power += cnorm2(buf[i]); - } - - in_power /= n; - } - - void track_agc(const complex &p) - { - float in_p = p.re * p.re + p.im * p.im; - in_power = in_p * agc_bw + in_power * (1.0f - agc_bw); - } - - void update_agc() - { - float in_amp = gen_sqrt(in_power); - - if (!in_amp) { - return; - } - - if (!strongpls || !cstln) + if (pls.is_dummy()) { - // Match RMS amplitude - agc_gain = cstln_amp / in_amp; + S = 36; + dcstln = qpsk; } else { - // Match peak amplitude - agc_gain = cstln_amp / cstln->amp_max / in_amp; + const modcod_info *mcinfo = &modcod_infos[pls.modcod]; + + if (! mcinfo->nslots_nf) + { + fprintf(stderr, "Unsupported or corrupted MODCOD\n"); + in.read(ss.p-in.rd()); + enter_frame_detect(); + return; + } + + if (mer < mcinfo->esn0_nf - 1.0f) + { + // False positive from PLHEADER detection. + if ( sch->debug ) fprintf(stderr, "Insufficient MER\n"); + in.read(ss.p-in.rd()); + enter_frame_detect(); + return; + } + + if (pls.sf && mcinfo->rate==FEC910) + { // TBD use fec_infos + fprintf(stderr, "Unsupported or corrupted FEC\n"); + in.read(ss.p-in.rd()); + enter_frame_detect(); + return; + } + + // Store current MODCOD info + if (mcinfo->c != m_modcodType) { + m_modcodType = mcinfo->c; + } + + if (mcinfo->rate != m_modcodRate) { + m_modcodRate = mcinfo->rate; + } + + S = pls.sf ? mcinfo->nslots_nf/4 : mcinfo->nslots_nf; + // Constellation for data slots. + dcstln = get_cstln(pls.modcod); + cstln = dcstln; // Used by GUI + // Output special slot with PLS information. + pout->is_pls = true; + pout->pls = pls; + ++pout; + } + + // Now we know the frame structure. + // Estimate carrier over known symbols. + +#if DEBUG_LOOKAHEAD + static int plh_counter = 0; // For debugging only + fprintf(stderr, "\nLOOKAHEAD %d PLH sr %+3.0f %s %.1f dB\n", + plh_counter, (1-ss.omega/omega0)*1e6, + ss.format(), 10*log10f(mer2)); +#endif + // Next SOF + sampler_state ssnext; + + { + ssnext = ss; + int ns = (S*PLSLOT_LENGTH + + (pls.pilots?((S-1)/16)*pilot.LENGTH:0)); + // Find next SOP at expected position +- tolerance on symbol rate. + int ns_tol = lrintf(ns*sr_tol); + ssnext.omega = omega0; + ssnext.skip_symbols(ns-ns_tol); + find_plheader(&ssnext, ns_tol*2); + // Don't trust frequency from differential correlator. + // Our current estimate should be better. + ssnext.fw16 = ss.fw16; + interp_match_sof(&ssnext); +#if DEBUG_CARRIER + fprintf(stderr, "\nCARRIER next: %.0f%% %s %.1f dB\n", + q*100, ssnext.format(), 10*log10f(m2)); +#endif + // Estimate symbol rate (considered stable over whole frame) + float dist = (ssnext.p-ss.p) + (ssnext.mu-ss.mu); + // Set symbol period accordingly. + ss.omega = dist / (ns+sof.LENGTH); + } + + // Pilots + int npilots = (S-1) / 16; + + if (sspilots) { + delete[] sspilots; + } + + sspilots = new sampler_state[npilots]; + + // Detect pilots + if (pls.pilots) + { + sampler_state ssp = ss; + + for ( int i=0; i::LENGTH + sof.LENGTH; + float dph = ssnext.ph16 - (ss.ph16+ss.fw16*span); + ss.fw16 += fmodfs(dph,65536.0f) / span; + } +#if DEBUG_LOOKAHEAD + fprintf(stderr, "LOOKAHEAD %d NEXTSOF sr %+3.0f %s %.1f dB\n", + plh_counter, (1-ssnext.omega/omega0)*1e6, + ssnext.format(), 10*log10f(mer2)); + ++plh_counter; +#endif + + if (state == FRAME_PROBE) + { + float fw0 = ss.fw16; + match_frame(&ss, &pls, S, dcstln); + // Apply retroactively from midpoint of pilots and next SOF. + float fw_adj = ss.fw16 - fw0; + + if (pls.pilots) + { + for (int i=0; i freq_stats; + + // Read slots and pilots +#if DEBUG_CARRIER + fprintf(stderr, "CARRIER data: %s\n", ss.format()); +#endif + + // int pilot_errors = 0; + + for (int slot=0; slotLENGTH + pilot.LENGTH; + float dph = ssp->ph16 - (ss.ph16+ss.fw16*span); + ss.fw16 += fmodfs(dph,65536.0f) / span; + } + else if (( pls.pilots && !(slot&15) && slot+16>=S) || + (!pls.pilots && !slot )) + { + // Sequence of data slots followed by SOF + int span = (S-slot)*pout->LENGTH + sof.LENGTH; + float dph = ssnext.ph16 - (ss.ph16+ss.fw16*span); + ss.fw16 += fmodfs(dph,65536.0f) / span; + } + + // Read slot. + freq_stats.add(ss.fw16); + pout->is_pls = false; + complex p; // Export last symbols for cstln_out + + for (int s=0; sLENGTH; ++s) + { + p = interp_next(&ss) * ss.gain; +#if TEST_DIVERSITY + if ( psymbols ) + *psymbols++ = p * scale_symbols; +#endif + if (!pls.pilots || fastdrift) { + (void) track_symbol(&ss, p, dcstln); // SLOW + } + + complex d = descramble(&ss, p); +#if 1 // Slow + SOFTSYMB *symb = &dcstln->lookup(d.re, d.im)->ss; +#else // Avoid scaling floats. May wrap at very low SNR. + SOFTSYMB *symb = &dcstln->lookup((int)d.re, (int)d.im)->ss; +#endif + pout->symbols[s] = *symb; + } + + ss.normalize(); + + if (psampled) { + *psampled++ = p; + } + } // slot + + if (sch->debug2) + { + fprintf( + stderr, + "sr%+.0f fs=%.0f\n", + (1-ss.omega/omega0)*1e6, + (freq_stats.max()-freq_stats.min())*1e6/65536.0f + ); + } + + // Commit whole frame after final SOF. + if (! pls.is_dummy()) + { + if ((modcods&(1<debug2) { + fprintf(stderr, "modcod %d size %d rejected\n", pls.modcod, pls.sf); + } + } + } + + // Write back sampler progress + meas_count += ss.p - in.rd(); + in.read(ss.p-in.rd()); + ss_cache = ss; + + // Measurements + if (psampled) { + cstln_out->written(psampled-cstln_out->wr()); + } + + if (psampled_pls) { + cstln_pls_out->written(psampled_pls-cstln_pls_out->wr()); + } +#if TEST_DIVERSITY + if ( psymbols ) symbols_out->written(psymbols-symbols_out->wr()); +#endif + if (meas_count >= meas_decimation) + { + opt_write(freq_out, ss_cache.fw16/65536/ss_cache.omega); + opt_write(ss_out, cstln_amp / ss_cache.gain); + opt_write(mer_out, mer2 ? 10*log10f(mer2) : -99); + meas_count -= meas_decimation; + } + + if (state == FRAME_PROBE) + { + // First frame completed successfully. Validate the lock. + enter_frame_locked(); + } + + if (ss_cache.fw16max_freqw16) + { + if (sch->debug) { + fprintf(stderr, "Carrier out of bounds\n"); + } + + enter_frame_detect(); + } + } // run_frame_probe_locked + + // Find most likely PLHEADER between *pss and *pss+search_range + // by differential correlation. + // Align symbol timing. + // Estimate carrier frequency (to about +-10kppm). + // Estimate carrier phase (to about +-PI/4). + // Adjust *ss accordingly. + // Initialize AGC. + // Return confidence (unbounded, 0=bad, 1=nominal). + + float find_plheader(sampler_state *pss, int search_range) + { + complex best_corr = 0; + int best_imu = 0; // Avoid compiler warning. + int best_pos = 0; // Avoid compiler warning. + + // Symbol clock is not recovered yet, so we try fractional symbols. + const int interp = 8; + + for (int imu=0; imu[ndiffs]; + sampler_state ss = *pss; + ss.mu += imu * ss.omega / interp; + + // Compute rotation between consecutive symbols. + complex prev = 0; + for (int i=0; i p = interp_next(&ss); + diffs[i] = conjprod(prev, p); + prev = p; + } + + // Find best PLHEADER candidate position. + const int ncorrs = search_range; + for (int i=0; i c = correlate_plheader_diff(&diffs[i]); + //if ( cnorm2(c) > cnorm2(best_corr) ) { + // c.im>0 enforces frequency error +-Fm/4 + if (cnorm2(c)>cnorm2(best_corr) && c.im>0) + { + best_corr = c; + best_imu = imu; + best_pos = i; + } + } + } // imu + + // Setup sampler according to best match. + pss->mu += best_imu * pss->omega / interp; + pss->skip_symbols(best_pos); + pss->normalize(); +#if 0 + // Lowpass-filter the differential correlator. + // (Does not help much.) + static complex acc = 0; + static const float k = 0.05; + acc = best_corr*k + acc*(1-k); + best_corr = acc; +#endif + // Get rough estimate of carrier frequency from differential correlator. + // (best_corr is nominally +j). + float freqw = atan2f(-best_corr.re, best_corr.im); + pss->fw16 += freqw * 65536 / (2*M_PI); + // Force refresh because correction may be large. + sampler->update_freq(pss->fw16/omega0); + + // Interpolate SOF with initial frequency estimation. + // Estimate phase by correlation. + // Initialize AGC (naive). + float q; + + { + sampler_state ss = *pss; + float power = 0; + complex symbs[sof.LENGTH]; + + for (int i=0; i c = conjprod(sof.symbols, symbs, sof.LENGTH); + c *= 1.0f / sof.LENGTH; + align_phase(pss, c); + float signal_amp = sqrtf(power/sof.LENGTH); + q = sqrtf(cnorm2(c)) / (cstln_amp*signal_amp); + pss->gain = cstln_amp / signal_amp; + } + + return q; + } + + // Correlate PLHEADER. + + complex correlate_plheader_diff(complex *diffs) + { + complex csof = correlate_sof_diff(diffs); + complex cplsc = correlate_plscode_diff(&diffs[sof.LENGTH]); + // Use csof+cplsc or csof-cplsc, whichever maximizes likelihood. + complex c0 = csof + cplsc; // Best when b7==0 (pilots off) + complex c1 = csof - cplsc; // Best when b7==1 (pilots on) + complex c = (cnorm2(c0)>cnorm2(c1)) ? c0 : c1; + return c * (1.0f/(26-1+64/2)); + } + + // Correlate 25 differential transitions in SOF. + + complex correlate_sof_diff(complex *diffs) + { + complex c = 0; + const uint32_t dsof = sof.VALUE ^ (sof.VALUE>>1); + + for (int i=0; i +PI/4 + // Constant even bit => -PI/4 + // Toggled odd bit => -PI/4 + // Toggled even bit => +PI/4 + if (((dsof>>(sof.LENGTH-1-i)) ^ i) & 1) { + c += diffs[i]; + } else { + c -= diffs[i]; + } + } + + return c; + } + + // Correlate 32 data-independent transitions in PLSCODE. + + complex correlate_plscode_diff(complex *diffs) + { + complex c = 0; + uint64_t dscr = plscodes.SCRAMBLING ^ (plscodes.SCRAMBLING>>1); + + for (int i=1; i>(plscodes.LENGTH-1-i)) & 1 ) { + c -= diffs[i]; + } else { + c += diffs[i]; + } + } + + return c; + } + + // Adjust carrier frequency in *pss to match target phase after ns symbols. + + void adjust_freq(sampler_state *pss, int ns, const sampler_state *pnext) + { + // Note: Minimum deviation from current estimate. + float adj = fmodfs(pnext->ph16-(pss->ph16+pss->fw16*ns),65536.0f) / ns; + // 2sps vcm avec adj 10711K + // 2sps vcm avec adj/2 10265k + // 2sps vcm sans adj 8060K + // 5sps 2MS1 avec adj 2262K + // 5sps 2MS1 avec adj/2 3109K + // 5sps 2MS1 avec adj/3 3254K + // 5sps 2MS1 sans adj 3254K + // 1.2sps oldbeacon avec adj 5774K + // 1.2sps oldbeacon avec adj/2 15M + // 1.2sps oldbeacon avec adj/3 15.6M max 17M + // 1.2sps oldbeacon sans adj 14M malgré drift + pss->fw16 += adj; + } + + // Estimate frequency from known symbols by differential correlation. + // Adjust *ss accordingly. + // Retroactively frequency-shift the samples in-place. + // + // *ss must point after the sampled symbols. + // expect[] must be PSK with amplitude cstln_amp. + // Idempotent. + // Not affected by phase error nor by gain mismatch. + // Can handle +-25% error. + // Result is typically such that residual phase error over a PLHEADER + // spans less than 90°. + + void match_freq( + complex *expect, + complex *recv, + int ns, + sampler_state *ss) + { + complex diff = 0; + + for (int i=0; i de = conjprod(expect[i], expect[i+1]); + complex dr = conjprod(recv[i], recv[i+1]); + diff += conjprod(de, dr); + } + + float dfw16 = atan2f(diff.im,diff.re) * 65536 / (2*M_PI); + + // Derotate. + for (int i=0; ifw16 += dfw16; + ss->ph16 += dfw16 * ns; // Retroactively + } + + // Interpolate a pilot block. + // Perform ML match. + + float interp_match_pilot(sampler_state *pss) + { + complex symbols[pilot.LENGTH]; + complex expected[pilot.LENGTH]; + + for (int i=0; i p = interp_next(pss) * pss->gain; + symbols[i] = descramble(pss, p); + expected[i] = pilot.symbol; + //fprintf(stderr, "%f %f\n", symbols[i].re, symbols[i].im); + } + + return match_ph_amp(expected, symbols, pilot.LENGTH, pss); + } + + // Interpolate a SOF. + // Perform ML match. + + float interp_match_sof(sampler_state *pss) + { + complex symbols[pilot.LENGTH]; + + for (int i=0; igain; + } + + return match_ph_amp(sof.symbols, symbols, sof.LENGTH, pss); + } + + // Estimate phase and amplitude from known symbols. + // Adjust *ss accordingly. + // Retroactively derotate and scale the samples in-place. + // Return MER^2. + // + // *ss must point after the sampled symbols, with gain applied. + // expect[] must be PSK (not APSK) with amplitude cstln_amp. + // Idempotent. + + float match_ph_amp( + complex *expect, + complex *recv, + int ns, + sampler_state *ss + ) + { + complex rr = 0; + + for (int i=0; iph16 += dph16; + rr *= trig.expi(-dph16); + // rr.re is now the modulation amplitude. + float dgain = cstln_amp / rr.re; + ss->gain *= dgain; + // Rotate and scale. Compute error power. + complex adj = trig.expi(-dph16) * dgain; + float ev2 = 0; + + for (int i=0; i *dcstln + ) + { + // With pilots: Use first block of data slots. + // Without pilots: Use whole frame. + int ns = pls->pilots ? 16*90 : S*90; + // Pilots: steps of ~700 ppm (= 1 cycle between pilots) + // No pilots, normal frames: steps of ~30(QPSK) - ~80(32APSK) ppm + // No pilots, short frames: steps of ~120(QPSK) - 300(32APSK) ppm + int nwrap = pls->pilots ? 16*90+pilot.LENGTH : S*90+sof.LENGTH; + // Frequency search range. + int sliprange = pls->pilots ? 10 : 50; // TBD Customizable ? + float besterr = 1e99; + float bestslip = 0; // Avoid compiler warning + + for (int slip=-sliprange; slip<=sliprange; ++slip) + { + sampler_state ssl = *pss; + float dfw = slip * 65536.0f / nwrap; + ssl.fw16 += dfw; + // Apply retroactively from midpoint of preceeding PLHEADER, + // where the phase from match_ph_amp is most reliable. + ssl.ph16 += dfw * (plscodes.LENGTH+sof.LENGTH)/2; + float err = 0; + + for (int s=0; s p = interp_next(&ssl) * ssl.gain; + typename cstln_lut::result *cr = + dcstln->lookup(p.re, p.im); + complex &cp = dcstln->symbols[cr->symbol]; + complex ev(p.re-cp.re, p.im-cp.im); + err += cnorm2(ev); + } + + err /= ns * cstln_amp * cstln_amp; + + if (err < besterr) + { + besterr=err; + bestslip=slip; + } +#if DEBUG_CARRIER + fprintf(stderr, "slip %+3d %6.0f ppm err=%f\n", + slip, ssl.fw16*1e6/65536, err); +#endif + } + + pss->fw16 += bestslip * 65536.0f / nwrap; + } // match_frame + + void shutdown() + { + if (sch->verbose) + { + fprintf( + stderr, + "PL errors: %d/%d (%.0f ppm)\n", + pls_total_errors, + pls_total_count, + 1e6 * pls_total_errors / pls_total_count + ); } } @@ -1210,14 +1685,14 @@ struct s2_frame_receiver : runnable { case 3: res.re = -p.im; - res.im = p.re; + res.im = p.re; break; case 2: res.re = -p.re; res.im = -p.im; break; case 1: - res.re = p.im; + res.re = p.im; res.im = -p.re; break; default: @@ -1235,73 +1710,50 @@ struct s2_frame_receiver : runnable while (ss->mu >= 1) { ++ss->p; - ss->mu -= 1.0f; + ss->mu-=1.0f; } // Interpolate #if 0 - // Interpolate linearly then derotate. - // This will fail with large carrier offsets (e.g. --tune). - float cmu = 1.0f - ss->mu; - complex s(ss->p[0].re*cmu + ss->p[1].re*ss->mu, - ss->p[0].im*cmu + ss->p[1].im*ss->mu); - ss->mu += omega0; - // Derotate - const complex &rot = trig.expi(-ss->ph16); - ss->ph16 += ss->fw16; - return rot * s; + // Interpolate linearly then derotate. + // This will fail with large carrier offsets (e.g. --tune). + float cmu = 1.0f - ss->mu; + complex s(ss->p[0].re*cmu + ss->p[1].re*ss->mu, + ss->p[0].im*cmu + ss->p[1].im*ss->mu); + ss->mu += ss->omega; + // Derotate + const complex &rot = trig.expi(-ss->ph16); + ss->ph16 += ss->fw16; + return rot * s; #else // Use generic interpolator complex s = sampler->interp(ss->p, ss->mu, ss->ph16); - ss->mu += omega0; + ss->mu += ss->omega; ss->ph16 += ss->fw16; return s; #endif } + // Adjust phase in [ss] to cancel offset observed as [c]. + void align_phase(sampler_state *ss, const complex &c) { -#if 0 - // Reference implementation - float err = atan2f(c.im,c.re) * (65536/(2*M_PI)); -#else - // Same performance as atan2f, faster - if (!c.re) { - return; - } - - float err = c.im / c.re * (65536 / (2 * M_PI)); -#endif + float err = atan2f(c.im,c.re) * 65536 / (2*M_PI); ss->ph16 += err; } - inline uint8_t track_symbol(sampler_state *ss, const complex &p, - cstln_lut *c, int mode) + inline uint8_t track_symbol( + sampler_state *ss, + const complex &p, + cstln_lut *c + ) { - static struct - { - float kph; - float kfw; - float kmu; - } - gains[2] = - { - { - 4e-2, - 1e-4, - (float) 0.001 / (cstln_amp * cstln_amp) - }, - { - 4e-2, - 1e-4, - (float) 0.001 / (cstln_amp * cstln_amp) - } - }; - + static const float kph = 4e-2; + static const float kfw = 1e-4; // Decision - typename cstln_lut::result *cr = c->lookup(p.re, p.im); + typename cstln_lut::result *cr = c->lookup(p.re, p.im); // Carrier tracking - ss->ph16 += cr->phase_error * gains[mode].kph; - ss->fw16 += cr->phase_error * gains[mode].kfw; + ss->ph16 += cr->phase_error * kph; + ss->fw16 += cr->phase_error * kfw; if (ss->fw16 < min_freqw16) { ss->fw16 = min_freqw16; @@ -1311,133 +1763,71 @@ struct s2_frame_receiver : runnable ss->fw16 = max_freqw16; } - // Phase tracking - hist[2] = hist[1]; - hist[1] = hist[0]; - hist[0].p = p; - complex *cp = &c->symbols[cr->symbol]; - hist[0].c.re = cp->re; - hist[0].c.im = cp->im; - float muerr = - ((hist[0].p.re - hist[2].p.re) * hist[1].c.re + - (hist[0].p.im - hist[2].p.im) * hist[1].c.im) - - ((hist[0].c.re - hist[2].c.re) * hist[1].p.re + - (hist[0].c.im - hist[2].c.im) * hist[1].p.im); - float mucorr = muerr * gains[mode].kmu; - const float max_mucorr = 0.1; - - // TBD Optimize out statically - if (mucorr < -max_mucorr) { - mucorr = -max_mucorr; - } - - if (mucorr > max_mucorr) { - mucorr = max_mucorr; - } - - ss->mu += mucorr; - // Error vector for MER - complex ev(p.re - cp->re, p.im - cp->im); - float ev_p = ev.re * ev.re + ev.im * ev.im; - ev_power = ev_p * agc_bw + ev_power * (1.0f - agc_bw); return cr->symbol; } - struct - { - complex p; // Received symbol - complex c; // Matched constellation point - } hist[3]; - public: - float in_power, ev_power; - float agc_gain; - float agc_bw; - cstln_lut *qpsk; - static const int MAXSYNCS = 8; - - struct sync - { - uint16_t nsmask; // bitmask of cstln orders for which this sync is used - uint64_t tobpsk; // Bitmask from cstln symbols to pi/2-BPSK bits - float offset16; // Phase offset 0..65536 - uint32_t hist; // For SOF detection - } syncs[MAXSYNCS], *current_sync; - - int nsyncs; + cstln_lut *qpsk; s2_plscodes plscodes; - cstln_lut *cstln; - // Initialize synchronizers for an arbitrary constellation. + cstln_lut *cstlns[32]; // Constellations in use, by modcod - void add_syncs(cstln_lut *c) + cstln_lut *get_cstln(int modcod) { - int random_decision = 0; - int nrot = c->nrotations; -#if 0 - if ( nrot == 4 ) { - fprintf(stderr, "Special case for 8PSK locking as QPSK pi/8\n"); - nrot = 8; - } -#endif - for (int r = 0; r < nrot; ++r) + if (!cstlns[modcod]) { - if (nsyncs == MAXSYNCS) { - fail("Bug: too many syncs"); - } + const modcod_info *mcinfo = &modcod_infos[modcod]; - sync *s = &syncs[nsyncs++]; - s->offset16 = 65536.0 * r / nrot; - float angle = -2 * M_PI * r / nrot; - s->tobpsk = 0; - - for (int i = c->nsymbols; i--;) + if (sch->debug) { - complex p = c->symbols[i]; - float im = p.re * sinf(angle) + p.im * cosf(angle); - int bit; - - if (im > 1) - { - bit = 0; - } - else if (im < -1) - { - bit = 1; - } - else - { - bit = random_decision; - random_decision ^= 1; - } // Near 0 - - s->tobpsk = (s->tobpsk << 1) | bit; + fprintf( + stderr, + "Creating LUT for %s ratecode %d\n", + cstln_base::names[mcinfo->c], + mcinfo->rate + ); } - s->hist = 0; + cstlns[modcod] = new cstln_lut( + mcinfo->c, + mcinfo->esn0_nf, + mcinfo->g1, + mcinfo->g2, + mcinfo->g3 + ); +#if 0 + fprintf(stderr, "Dumping constellation LUT to stdout.\n"); + cstlns[modcod]->dump(stdout); + exit(0); +#endif } + + return cstlns[modcod]; } + cstln_lut *cstln; // Last seen, or NULL (legacy) trig16 trig; - pipereader> in; - pipewriter> out; + pipereader< complex > in; + pipewriter< plslot > out; int meas_count; pipewriter *freq_out, *ss_out, *mer_out; - pipewriter> *cstln_out; - pipewriter> *cstln_pls_out; - pipewriter> *symbols_out; + pipewriter< complex > *cstln_out; + pipewriter< complex > *cstln_pls_out; + pipewriter< complex > *symbols_out; pipewriter *state_out; - bool report_state; + bool first_run; // S2 constants s2_scrambling scrambling; s2_sof sof; + s2_pilot pilot; + // Performance stats on PL signalling + uint32_t pls_total_errors, pls_total_count; int m_modcodType; int m_modcodRate; - // Max size of one frame - // static const int MAX_SLOTS = 360; - static const int MAX_SLOTS = 240; // DEBUG match test signal - static const int MAX_SYMBOLS = - (1 + MAX_SLOTS) * plslot::LENGTH + ((MAX_SLOTS - 1) / 16) * pilot_length; -}; // s2_frame_receiver + +private: + complex *diffs; + sampler_state *sspilots; + }; // s2_frame_receiver template struct fecframe @@ -1491,6 +1881,7 @@ struct s2_interleaver : runnable { int bps = log2(mcinfo->nsymbols); int rows = pls->framebits() / bps; + if (mcinfo->nsymbols == 8 && mcinfo->rate == FEC35) { interleave(bps, rows, pbytes, nslots, false, pout); } else { @@ -1877,7 +2268,7 @@ struct s2_deinterleaver : runnable int bps = log2(mcinfo->nsymbols); int rows = pls->framebits() / bps; - if (mcinfo->nsymbols == 8 && mcinfo->rate == FEC35) { + if ((mcinfo->nsymbols == 8) && (mcinfo->rate == FEC35)) { deinterleave(bps, rows, pin + 1, nslots, false, pbytes); } else { deinterleave(bps, rows, pin + 1, nslots, true, pbytes); @@ -3176,7 +3567,12 @@ struct s2_fecdec_helper : runnable struct s2_framer : runnable { uint8_t rolloff_code; // 0=0.35, 1=0.25, 2=0.20, 3=reserved - s2_pls pls; + // User must provide pls_seq[n_pls_seq]. + // For ACM, user can change pls_seq[0] at runtime. + // For VCM with a repeating pattern, use n_pls_seq>=2. + s2_pls *pls_seq; + int n_pls_seq; + s2_framer( scheduler *sch, @@ -3184,12 +3580,11 @@ struct s2_framer : runnable pipebuf &_out ) : runnable(sch, "S2 framer"), + n_pls_seq(0), + pls_index(0), in(_in), out(_out) { - pls.modcod = 4; - pls.sf = false; - pls.pilots = true; nremain = 0; remcrc = 0; // CRC for nonexistent previous packet } @@ -3198,8 +3593,13 @@ struct s2_framer : runnable { while (out.writable() >= 1) { - const modcod_info *mcinfo = check_modcod(pls.modcod); - const fec_info *fi = &fec_infos[pls.sf][mcinfo->rate]; + if (!n_pls_seq ) { + fail("PLS not specified"); + } + + s2_pls *pls = &pls_seq[pls_index]; + const modcod_info *mcinfo = check_modcod(pls->modcod); + const fec_info *fi = &fec_infos[pls->sf][mcinfo->rate]; int framebytes = fi->Kbch / 8; if (!framebytes) { @@ -3211,12 +3611,18 @@ struct s2_framer : runnable } bbframe *pout = out.wr(); - pout->pls = pls; + pout->pls = *pls; uint8_t *buf = pout->bytes; uint8_t *end = buf + framebytes; // EN 302 307-1 section 5.1.6 Base-Band Header insertion uint8_t *bbheader = buf; - *buf++ = 0x30 | rolloff_code; // MATYPE-1: SIS, CCM + uint8_t matype1 = 0; + matype1 |= 0xc0; // TS + matype1 |= 0x20; // SIS + matype1 |= (n_pls_seq==1) ? 0x10 : 0x00; // CCM/ACM + // TBD ISSY/NPD required for ACM ? + matype1 |= rolloff_code; + *buf++ = matype1; // MATYPE-1 *buf++ = 0; // MATYPE-2 uint16_t upl = 188 * 8; *buf++ = upl >> 8; // UPL MSB @@ -3266,10 +3672,16 @@ struct s2_framer : runnable } out.written(1); + ++pls_index; + + if (pls_index == n_pls_seq) { + pls_index = 0; + } } } - private: +private: + int pls_index; // Next slot to use in pls_seq pipereader in; pipewriter out; crc8_engine crc8; @@ -3283,6 +3695,8 @@ struct s2_framer : runnable struct s2_deframer : runnable { + int fd_gse; // FD for generic streams, or -1 + s2_deframer( scheduler *sch, pipebuf &_in, @@ -3291,6 +3705,7 @@ struct s2_deframer : runnable pipebuf *_locktime_out = nullptr ) : runnable(sch, "S2 deframer"), + fd_gse(-1), missing(-1), in(_in), out(_out, MAX_TS_PER_BBFRAME), @@ -3320,10 +3735,18 @@ struct s2_deframer : runnable } } - private: +private: void run_bbframe(bbframe *pin) { uint8_t *bbh = pin->bytes; + // EN 302 307 section 5.1.6 Base-Band Header Insertion + uint8_t streamtype = bbh[0] >> 6; + bool sis = bbh[0] & 32; + bool ccm = bbh[0] & 16; + bool issyi = bbh[0] & 8; + bool npd = bbh[0] & 4; + int ro_code = bbh[0] & 3; + uint8_t isi = bbh[1]; // if !sis uint16_t upl = (bbh[2] << 8) | bbh[3]; uint16_t dfl = (bbh[4] << 8) | bbh[5]; uint8_t sync = bbh[6]; @@ -3331,19 +3754,31 @@ struct s2_deframer : runnable uint8_t crcexp = crc8.compute(bbh, 9); uint8_t crc = bbh[9]; uint8_t *data = bbh + 10; - int ro_code = bbh[0] & 3; if (sch->debug2) { + static const char *stnames[] = { "GP", "GC", "??", "TS" }; static float ro_values[] = {0.35, 0.25, 0.20, 0}; - fprintf(stderr, "BBH: crc %02x/%02x %s ma=%02x%02x ro=%.2f" - " upl=%d dfl=%d sync=%02x syncd=%d\n", - crc, crcexp, (crc == crcexp) ? "OK" : "KO", - bbh[0], bbh[1], ro_values[ro_code], upl, dfl, sync, syncd); + fprintf(stderr, "BBH: crc %02x/%02x(%s) %s %s(ISI=%d) %s%s%s ro=%.2f" + " upl=%d dfl=%d sync=%02x syncd=%d\n", + crc, + crcexp, + (crc == crcexp) ? "OK" : "KO", + stnames[streamtype], + (sis ? "SIS" : "MIS"), + isi, + (ccm ? "CCM" : "ACM"), + (issyi ? " ISSYI": ""), + (npd ? " NPD" : ""), + ro_values[ro_code], + upl, + dfl, + sync, + syncd + ); } - if (crc != crcexp || upl != 188 * 8 || sync != 0x47 || dfl > fec_info::KBCH_MAX || - syncd > dfl || (dfl & 7) || (syncd & 7)) + if (crc != crcexp || dfl > fec_info::KBCH_MAX) { // Note: Maybe accept syncd=65535 if (sch->debug) { @@ -3355,6 +3790,42 @@ struct s2_deframer : runnable return; } + // TBD: Supporting byte-oriented payloads only. + if ((dfl&7) || (syncd&7)) + { + fprintf(stderr, "Unsupported bbframe\n"); + missing = -1; + info_unlocked(); + return; + } + + if (streamtype==3 && upl==188*8 && sync==0x47 && syncd<=dfl) + { + handle_ts(data, dfl, syncd, sync); + } + else if (streamtype == 1) + { + if (fd_gse >= 0) + { + ssize_t nw = write(fd_gse, data, dfl/8); + + if (nw < 0) { + fatal("write(gse)"); + } + + if (nw != dfl/8) { + fail("partial write(gse"); + } + } + else + { + fprintf(stderr, "Unrecognized bbframe\n"); + } + } + } + + void handle_ts(uint8_t *data, uint16_t dfl, uint16_t syncd, uint8_t sync) + { // TBD Handle packets as payload+finalCRC and do crc8 before pout int pos; // Start of useful data in this bbframe