From ac7c48b4dee99d4c772f133d70d8d1b38262fcd2 Mon Sep 17 00:00:00 2001 From: Author Name Date: Fri, 7 Jul 2023 12:20:59 +0930 Subject: shallow zip-file copy from codec2 e9d726bf20 --- src/fdmdv.c | 2082 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2082 insertions(+) create mode 100644 src/fdmdv.c (limited to 'src/fdmdv.c') diff --git a/src/fdmdv.c b/src/fdmdv.c new file mode 100644 index 0000000..7aeb4b8 --- /dev/null +++ b/src/fdmdv.c @@ -0,0 +1,2082 @@ +/*---------------------------------------------------------------------------*\ + + FILE........: fdmdv.c + AUTHOR......: David Rowe + DATE CREATED: April 14 2012 + + Functions that implement the FDMDV modem. + +\*---------------------------------------------------------------------------*/ + +/* + Copyright (C) 2012 David Rowe + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1, as + published by the Free Software Foundation. This program is + distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, see . +*/ + +/*---------------------------------------------------------------------------*\ + + INCLUDES + +\*---------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include + +#include "fdmdv_internal.h" +#include "codec2_fdmdv.h" +#include "comp_prim.h" +#include "rn.h" +#include "rxdec_coeff.h" +#include "test_bits.h" +#include "pilot_coeff.h" +#include "codec2_fft.h" +#include "hanning.h" +#include "os.h" +#include "machdep.h" + +#include "debug_alloc.h" + +static int sync_uw[] = {1,-1,1,-1,1,-1}; + +static const COMP pi_on_4 = { .70710678118654752439, .70710678118654752439 }; // cosf(PI/4) , sinf(PI/4) + + +/*--------------------------------------------------------------------------* \ + + FUNCTION....: fdmdv_create + AUTHOR......: David Rowe + DATE CREATED: 16/4/2012 + + Create and initialise an instance of the modem. Returns a pointer + to the modem states or NULL on failure. One set of states is + sufficient for a full duplex modem. + +\*---------------------------------------------------------------------------*/ + +struct FDMDV * fdmdv_create(int Nc) +{ + struct FDMDV *f; + int c, i, k; + + assert(NC == FDMDV_NC_MAX); /* check public and private #defines match */ + assert(Nc <= NC); + assert(FDMDV_NOM_SAMPLES_PER_FRAME == M_FAC); + assert(FDMDV_MAX_SAMPLES_PER_FRAME == (M_FAC+M_FAC/P)); + + f = (struct FDMDV*)MALLOC(sizeof(struct FDMDV)); + if (f == NULL) + return NULL; + + f->Nc = Nc; + + f->ntest_bits = Nc*NB*4; + f->current_test_bit = 0; + f->rx_test_bits_mem = (int*)MALLOC(sizeof(int)*f->ntest_bits); + assert(f->rx_test_bits_mem != NULL); + for(i=0; intest_bits; i++) + f->rx_test_bits_mem[i] = 0; + assert((sizeof(test_bits)/sizeof(int)) >= f->ntest_bits); + + f->old_qpsk_mapping = 0; + + f->tx_pilot_bit = 0; + + for(c=0; cprev_tx_symbols[c].real = 1.0; + f->prev_tx_symbols[c].imag = 0.0; + f->prev_rx_symbols[c].real = 1.0; + f->prev_rx_symbols[c].imag = 0.0; + + for(k=0; ktx_filter_memory[c][k].real = 0.0; + f->tx_filter_memory[c][k].imag = 0.0; + } + + /* Spread initial FDM carrier phase out as far as possible. + This helped PAPR for a few dB. We don't need to adjust rx + phase as DQPSK takes care of that. */ + + f->phase_tx[c].real = cosf(2.0*PI*c/(Nc+1)); + f->phase_tx[c].imag = sinf(2.0*PI*c/(Nc+1)); + + f->phase_rx[c].real = 1.0; + f->phase_rx[c].imag = 0.0; + + for(k=0; krx_filter_mem_timing[c][k].real = 0.0; + f->rx_filter_mem_timing[c][k].imag = 0.0; + } + } + f->prev_tx_symbols[Nc].real = 2.0; + + fdmdv_set_fsep(f, FSEP); + f->freq[Nc].real = cosf(2.0*PI*0.0/FS); + f->freq[Nc].imag = sinf(2.0*PI*0.0/FS); + f->freq_pol[Nc] = 2.0*PI*0.0/FS; + + f->fbb_rect.real = cosf(2.0*PI*FDMDV_FCENTRE/FS); + f->fbb_rect.imag = sinf(2.0*PI*FDMDV_FCENTRE/FS); + f->fbb_pol = 2.0*PI*FDMDV_FCENTRE/FS; + f->fbb_phase_tx.real = 1.0; + f->fbb_phase_tx.imag = 0.0; + f->fbb_phase_rx.real = 1.0; + f->fbb_phase_rx.imag = 0.0; + + /* Generate DBPSK pilot Look Up Table (LUT) */ + + generate_pilot_lut(f->pilot_lut, &f->freq[Nc]); + + /* freq Offset estimation states */ + + f->fft_pilot_cfg = codec2_fft_alloc (MPILOTFFT, 0, NULL, NULL); + assert(f->fft_pilot_cfg != NULL); + + for(i=0; ipilot_baseband1[i].real = f->pilot_baseband2[i].real = 0.0; + f->pilot_baseband1[i].imag = f->pilot_baseband2[i].imag = 0.0; + } + f->pilot_lut_index = 0; + f->prev_pilot_lut_index = 3*M_FAC; + + for(i=0; irxdec_lpf_mem[i].real = 0.0; + f->rxdec_lpf_mem[i].imag = 0.0; + } + + for(i=0; ipilot_lpf1[i].real = f->pilot_lpf2[i].real = 0.0; + f->pilot_lpf1[i].imag = f->pilot_lpf2[i].imag = 0.0; + } + + f->foff = 0.0; + f->foff_phase_rect.real = 1.0; + f->foff_phase_rect.imag = 0.0; + + for(i=0; irx_fdm_mem[i].real = 0.0; + f->rx_fdm_mem[i].imag = 0.0; + } + + f->fest_state = 0; + f->sync = 0; + f->timer = 0; + for(i=0; isync_mem[i] = 0; + + for(c=0; csig_est[c] = 0.0; + f->noise_est[c] = 0.0; + } + + f->sig_pwr_av = 0.0; + f->foff_filt = 0.0; + + return f; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: fdmdv_destroy + AUTHOR......: David Rowe + DATE CREATED: 16/4/2012 + + Destroy an instance of the modem. + +\*---------------------------------------------------------------------------*/ + +void fdmdv_destroy(struct FDMDV *fdmdv) +{ + assert(fdmdv != NULL); + codec2_fft_free(fdmdv->fft_pilot_cfg); + FREE(fdmdv->rx_test_bits_mem); + FREE(fdmdv); +} + + +void fdmdv_use_old_qpsk_mapping(struct FDMDV *fdmdv) { + fdmdv->old_qpsk_mapping = 1; +} + + +int fdmdv_bits_per_frame(struct FDMDV *fdmdv) +{ + return (fdmdv->Nc * NB); +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: fdmdv_get_test_bits() + AUTHOR......: David Rowe + DATE CREATED: 16/4/2012 + + Generate a frame of bits from a repeating sequence of random data. OK so + it's not very random if it repeats but it makes syncing at the demod easier + for test purposes. + +\*---------------------------------------------------------------------------*/ + +void fdmdv_get_test_bits(struct FDMDV *f, int tx_bits[]) +{ + int i; + int bits_per_frame = fdmdv_bits_per_frame(f); + + for(i=0; icurrent_test_bit]; + f->current_test_bit++; + if (f->current_test_bit > (f->ntest_bits-1)) + f->current_test_bit = 0; + } +} + +float fdmdv_get_fsep(struct FDMDV *f) +{ + return f->fsep; +} + +void fdmdv_set_fsep(struct FDMDV *f, float fsep) { + int c; + float carrier_freq; + + f->fsep = fsep; + + /* Set up frequency of each carrier */ + + for(c=0; cNc/2; c++) { + carrier_freq = (-f->Nc/2 + c)*f->fsep; + f->freq[c].real = cosf(2.0*PI*carrier_freq/FS); + f->freq[c].imag = sinf(2.0*PI*carrier_freq/FS); + f->freq_pol[c] = 2.0*PI*carrier_freq/FS; + } + + for(c=f->Nc/2; cNc; c++) { + carrier_freq = (-f->Nc/2 + c + 1)*f->fsep; + f->freq[c].real = cosf(2.0*PI*carrier_freq/FS); + f->freq[c].imag = sinf(2.0*PI*carrier_freq/FS); + f->freq_pol[c] = 2.0*PI*carrier_freq/FS; + } +} + + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: bits_to_dqpsk_symbols() + AUTHOR......: David Rowe + DATE CREATED: 16/4/2012 + + Maps bits to parallel DQPSK symbols. Generate Nc+1 QPSK symbols from + vector of (1,Nc*Nb) input tx_bits. The Nc+1 symbol is the +1 -1 +1 + .... BPSK sync carrier. + +\*---------------------------------------------------------------------------*/ + +void bits_to_dqpsk_symbols(COMP tx_symbols[], int Nc, COMP prev_tx_symbols[], int tx_bits[], int *pilot_bit, int old_qpsk_mapping) +{ + int c, msb, lsb; + COMP j = {0.0,1.0}; + + /* Map tx_bits to to Nc DQPSK symbols. Note legacy support for + old (suboptimal) V0.91 FreeDV mapping */ + + for(c=0; creal /= mag; + fbb_phase->imag /= mag; + + /* shift memory, inserting zeros at end */ + + for(i=0; ireal /= mag; + fbb_phase->imag /= mag; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: fdmdv_mod() + AUTHOR......: David Rowe + DATE CREATED: 26/4/2012 + + FDMDV modulator, take a frame of FDMDV_BITS_PER_FRAME bits and + generates a frame of FDMDV_SAMPLES_PER_FRAME modulated symbols. + Sync bit is returned to aid alignment of your next frame. + + The sync_bit value returned will be used for the _next_ frame. + + The output signal is complex to support single sided frequency + shifting, for example when testing frequency offsets in channel + simulation. + +\*---------------------------------------------------------------------------*/ + +void fdmdv_mod(struct FDMDV *fdmdv, COMP tx_fdm[], int tx_bits[], int *sync_bit) +{ + COMP tx_symbols[NC+1]; + PROFILE_VAR(mod_start, tx_filter_and_upconvert_start); + + PROFILE_SAMPLE(mod_start); + bits_to_dqpsk_symbols(tx_symbols, fdmdv->Nc, fdmdv->prev_tx_symbols, tx_bits, &fdmdv->tx_pilot_bit, fdmdv->old_qpsk_mapping); + memcpy(fdmdv->prev_tx_symbols, tx_symbols, sizeof(COMP)*(fdmdv->Nc+1)); + PROFILE_SAMPLE_AND_LOG(tx_filter_and_upconvert_start, mod_start, " bits_to_dqpsk_symbols"); + tx_filter_and_upconvert(tx_fdm, fdmdv->Nc, tx_symbols, fdmdv->tx_filter_memory, + fdmdv->phase_tx, fdmdv->freq, &fdmdv->fbb_phase_tx, fdmdv->fbb_rect); + PROFILE_SAMPLE_AND_LOG2(tx_filter_and_upconvert_start, " tx_filter_and_upconvert"); + + *sync_bit = fdmdv->tx_pilot_bit; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: generate_pilot_fdm() + AUTHOR......: David Rowe + DATE CREATED: 19/4/2012 + + Generate M_FAC samples of DBPSK pilot signal for Freq offset estimation. + +\*---------------------------------------------------------------------------*/ + +void generate_pilot_fdm(COMP *pilot_fdm, int *bit, float *symbol, + float *filter_mem, COMP *phase, COMP *freq) +{ + int i,j,k; + float tx_baseband[M_FAC]; + + /* +1 -1 +1 -1 DBPSK sync carrier, once filtered becomes (roughly) + two spectral lines at +/- RS/2 */ + + if (*bit) + *symbol = -*symbol; + + if (*bit) + *bit = 0; + else + *bit = 1; + + /* filter DPSK symbol to create M_FAC baseband samples */ + + filter_mem[NFILTER-1] = (sqrtf(2)/2) * *symbol; + for(i=0; ireal; + pilot_fdm[i].imag = sqrtf(2)*2*tx_baseband[i] * phase->imag; + } +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: generate_pilot_lut() + AUTHOR......: David Rowe + DATE CREATED: 19/4/2012 + + Generate a 4M sample vector of DBPSK pilot signal. As the pilot signal + is periodic in 4M samples we can then use this vector as a look up table + for pilot signal generation in the demod. + +\*---------------------------------------------------------------------------*/ + +void generate_pilot_lut(COMP pilot_lut[], COMP *pilot_freq) +{ + int pilot_rx_bit = 0; + float pilot_symbol = sqrtf(2.0); + COMP pilot_phase = {1.0, 0.0}; + float pilot_filter_mem[NFILTER]; + COMP pilot[M_FAC]; + int i,f; + + for(i=0; i= 4) + memcpy(&pilot_lut[M_FAC*(f-4)], pilot, M_FAC*sizeof(COMP)); + } + + // create complex conjugate since we need this and only this later on + for (f=0;f<4*M_FAC;f++) + { + pilot_lut[f] = cconj(pilot_lut[f]); + } + +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: lpf_peak_pick() + AUTHOR......: David Rowe + DATE CREATED: 20/4/2012 + + LPF and peak pick part of freq est, put in a function as we call it twice. + +\*---------------------------------------------------------------------------*/ + +void lpf_peak_pick(float *foff, float *max, COMP pilot_baseband[], + COMP pilot_lpf[], codec2_fft_cfg fft_pilot_cfg, COMP S[], int nin, + int do_fft) +{ + int i,j,k; + int mpilot; + float mag, imax; + int ix; + float r; + + /* LPF cutoff 200Hz, so we can handle max +/- 200 Hz freq offset */ + + for(i=0; i imax) { + imax = mag; + ix = i; + } + } + r = 2.0*200.0/MPILOTFFT; /* maps FFT bin to frequency in Hz */ + + if (ix >= MPILOTFFT/2) + *foff = (ix - MPILOTFFT)*r; + else + *foff = (ix)*r; + } + + *max = imax; + +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: rx_est_freq_offset() + AUTHOR......: David Rowe + DATE CREATED: 19/4/2012 + + Estimate frequency offset of FDM signal using BPSK pilot. Note that + this algorithm is quite sensitive to pilot tone level wrt other + carriers, so test variations to the pilot amplitude carefully. + +\*---------------------------------------------------------------------------*/ + +float rx_est_freq_offset(struct FDMDV *f, COMP rx_fdm[], int nin, int do_fft) +{ + int i; +#ifndef FDV_ARM_MATH + int j; +#endif + COMP pilot[M_FAC+M_FAC/P]; + COMP prev_pilot[M_FAC+M_FAC/P]; + float foff, foff1, foff2; + float max1, max2; + + assert(nin <= M_FAC+M_FAC/P); + + /* get pilot samples used for correlation/down conversion of rx signal */ + + for (i=0; ipilot_lut[f->pilot_lut_index]; + f->pilot_lut_index++; + if (f->pilot_lut_index >= 4*M_FAC) + f->pilot_lut_index = 0; + + prev_pilot[i] = f->pilot_lut[f->prev_pilot_lut_index]; + f->prev_pilot_lut_index++; + if (f->prev_pilot_lut_index >= 4*M_FAC) + f->prev_pilot_lut_index = 0; + } + + /* + Down convert latest M_FAC samples of pilot by multiplying by ideal + BPSK pilot signal we have generated locally. The peak of the + resulting signal is sensitive to the time shift between the + received and local version of the pilot, so we do it twice at + different time shifts and choose the maximum. + */ + + for(i=0; ipilot_baseband1[i] = f->pilot_baseband1[i+nin]; + f->pilot_baseband2[i] = f->pilot_baseband2[i+nin]; + } + +#ifndef FDV_ARM_MATH + for(i=0,j=NPILOTBASEBAND-nin; ipilot_baseband1[j] = cmult(rx_fdm[i], pilot[i]); + f->pilot_baseband2[j] = cmult(rx_fdm[i], prev_pilot[i]); + } +#else + // TODO: Maybe a handwritten mult taking advantage of rx_fdm[0] being + // used twice would be faster but this is for sure faster than + // the implementation above in any case. + arm_cmplx_mult_cmplx_f32(&rx_fdm[0].real,&pilot[0].real,&f->pilot_baseband1[NPILOTBASEBAND-nin].real,nin); + arm_cmplx_mult_cmplx_f32(&rx_fdm[0].real,&prev_pilot[0].real,&f->pilot_baseband2[NPILOTBASEBAND-nin].real,nin); +#endif + + lpf_peak_pick(&foff1, &max1, f->pilot_baseband1, f->pilot_lpf1, f->fft_pilot_cfg, f->S1, nin, do_fft); + lpf_peak_pick(&foff2, &max2, f->pilot_baseband2, f->pilot_lpf2, f->fft_pilot_cfg, f->S2, nin, do_fft); + + if (max1 > max2) + foff = foff1; + else + foff = foff2; + + return foff; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: fdmdv_freq_shift() + AUTHOR......: David Rowe + DATE CREATED: 26/4/2012 + + Frequency shift modem signal. The use of complex input and output allows + single sided frequency shifting (no images). + +\*---------------------------------------------------------------------------*/ + +void fdmdv_freq_shift(COMP rx_fdm_fcorr[], COMP rx_fdm[], float foff, + COMP *foff_phase_rect, int nin) +{ + COMP foff_rect; + float mag; + int i; + + foff_rect.real = cosf(2.0*PI*foff/FS); + foff_rect.imag = sinf(2.0*PI*foff/FS); + for(i=0; ireal /= mag; + foff_phase_rect->imag /= mag; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: fdm_downconvert + AUTHOR......: David Rowe + DATE CREATED: 22/4/2012 + + Frequency shift each modem carrier down to Nc+1 baseband signals. + +\*---------------------------------------------------------------------------*/ + +void fdm_downconvert(COMP rx_baseband[NC+1][M_FAC+M_FAC/P], int Nc, COMP rx_fdm[], COMP phase_rx[], COMP freq[], int nin) +{ + int i,c; + float mag; + + /* maximum number of input samples to demod */ + + assert(nin <= (M_FAC+M_FAC/P)); + + /* downconvert */ + + for (c=0; c + nin + |--------------------------|---------| + 1 | + phase_rx(c) + + This means winding phase(c) back from this point + to ensure phase continuity. + + */ + + //PROFILE_SAMPLE(windback_start); + windback_phase = -freq_pol[c]*NFILTER; + windback_phase_rect.real = cosf(windback_phase); + windback_phase_rect.imag = sinf(windback_phase); + phase_rx[c] = cmult(phase_rx[c],windback_phase_rect); + //PROFILE_SAMPLE_AND_LOG(downconvert_start, windback_start, " windback"); + + /* down convert all samples in buffer */ + + st = NRX_FDM_MEM-1; /* end of buffer */ + st -= nin-1; /* first new sample */ + st -= NFILTER; /* first sample used in filtering */ + + /* freq shift per dec_rate step is dec_rate times original shift */ + + f_rect = freq[c]; + for(i=0; i P) + rx_timing -= P; + if (rx_timing < -P) + rx_timing += P; + + /* rx_filter_mem_timing contains Nt*P samples (Nt symbols at rate + P), where Nt is odd. Lets use linear interpolation to resample + in the centre of the timing estimation window .*/ + + rx_timing += floorf(NT/2.0)*P; + low_sample = floorf(rx_timing); + fract = rx_timing - low_sample; + high_sample = ceilf(rx_timing); + + //printf("rx_timing: %f low_sample: %d high_sample: %d fract: %f\n", rx_timing, low_sample, high_sample, fract); + + for(c=0; c= 0) && (d.imag >= 0)) { + msb = 0; lsb = 0; + } + if ((d.real < 0) && (d.imag >= 0)) { + msb = 0; lsb = 1; + } + if ((d.real < 0) && (d.imag < 0)) { + if (old_qpsk_mapping) { + msb = 1; lsb = 0; + } else { + msb = 1; lsb = 1; + } + } + if ((d.real >= 0) && (d.imag < 0)) { + if (old_qpsk_mapping) { + msb = 1; lsb = 1; + } else { + msb = 1; lsb = 0; + } + } + rx_bits[2*c] = msb; + rx_bits[2*c+1] = lsb; + } + + /* Extract DBPSK encoded Sync bit and fine freq offset estimate */ + + norm = 1.0/(cabsolute(prev_rx_symbols[Nc])+1E-6); + phase_difference[Nc] = cmult(rx_symbols[Nc], fcmult(norm, cconj(prev_rx_symbols[Nc]))); + if (phase_difference[Nc].real < 0) { + *sync_bit = 1; + ferr = phase_difference[Nc].imag*norm; /* make f_err magnitude insensitive */ + } + else { + *sync_bit = 0; + ferr = -phase_difference[Nc].imag*norm; + } + + /* pilot carrier gets an extra pi/4 rotation to make it consistent + with other carriers, as we need it for snr_update and scatter + diagram */ + + phase_difference[Nc] = cmult(phase_difference[Nc], pi_on_4); + + return ferr; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: snr_update() + AUTHOR......: David Rowe + DATE CREATED: 17 May 2012 + + Given phase differences update estimates of signal and noise levels. + +\*---------------------------------------------------------------------------*/ + +void snr_update(float sig_est[], float noise_est[], int Nc, COMP phase_difference[]) +{ + float s[NC+1]; + COMP refl_symbols[NC+1]; + float n[NC+1]; + int c; + + + /* mag of each symbol is distance from origin, this gives us a + vector of mags, one for each carrier. */ + + for(c=0; cntest_bits; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: fdmdv_put_test_bits() + AUTHOR......: David Rowe + DATE CREATED: 24/4/2012 + + Accepts nbits from rx and attempts to sync with test_bits sequence. + If sync OK measures bit errors. + +\*---------------------------------------------------------------------------*/ + +void fdmdv_put_test_bits(struct FDMDV *f, int *sync, short error_pattern[], + int *bit_errors, int *ntest_bits, int rx_bits[]) +{ + int i,j; + float ber; + int bits_per_frame = fdmdv_bits_per_frame(f); + + /* Append to our memory */ + + for(i=0,j=bits_per_frame; intest_bits-bits_per_frame; i++,j++) + f->rx_test_bits_mem[i] = f->rx_test_bits_mem[j]; + for(i=f->ntest_bits-bits_per_frame,j=0; intest_bits; i++,j++) + f->rx_test_bits_mem[i] = rx_bits[j]; + + /* see how many bit errors we get when checked against test sequence */ + + *bit_errors = 0; + for(i=0; intest_bits; i++) { + error_pattern[i] = test_bits[i] ^ f->rx_test_bits_mem[i]; + *bit_errors += error_pattern[i]; + //printf("%d %d %d %d\n", i, test_bits[i], f->rx_test_bits_mem[i], test_bits[i] ^ f->rx_test_bits_mem[i]); + } + + /* if less than a thresh we are aligned and in sync with test sequence */ + + ber = (float)*bit_errors/f->ntest_bits; + + *sync = 0; + if (ber < 0.2) + *sync = 1; + + *ntest_bits = f->ntest_bits; + +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: freq_state(() + AUTHOR......: David Rowe + DATE CREATED: 24/4/2012 + + Freq offset state machine. Moves between coarse and fine states + based on BPSK pilot sequence. Freq offset estimator occasionally + makes mistakes when used continuously. So we use it until we have + acquired the BPSK pilot, then switch to a more robust "fine" + tracking algorithm. If we lose sync we switch back to coarse mode + for fast re-acquisition of large frequency offsets. + + The sync state is also useful for higher layers to determine when + there is valid FDMDV data for decoding. We want to reliably and + quickly get into sync, stay in sync even on fading channels, and + fall out of sync quickly if tx stops or it's a false sync. + + In multipath fading channels the BPSK sync carrier may be pushed + down in the noise, despite other carriers being at full strength. + We want to avoid loss of sync in these cases. + +\*---------------------------------------------------------------------------*/ + +int freq_state(int *reliable_sync_bit, int sync_bit, int *state, int *timer, int *sync_mem) +{ + int next_state, sync, unique_word, i, corr; + + /* look for 6 symbols (120ms) 101010 of sync sequence */ + + unique_word = 0; + for(i=0; ifbb_phase_rx, *nin); + + /* freq offset estimation and correction */ + + PROFILE_SAMPLE(demod_start); + foff_coarse = rx_est_freq_offset(fdmdv, rx_fdm_bb, *nin, !fdmdv->sync); + PROFILE_SAMPLE_AND_LOG(fdmdv_freq_shift_start, demod_start, " rx_est_freq_offset"); + + if (fdmdv->sync == 0) + fdmdv->foff = foff_coarse; + fdmdv_freq_shift(rx_fdm_fcorr, rx_fdm_bb, -fdmdv->foff, &fdmdv->foff_phase_rect, *nin); + PROFILE_SAMPLE_AND_LOG(down_convert_and_rx_filter_start, fdmdv_freq_shift_start, " fdmdv_freq_shift"); + + /* baseband processing */ + + rxdec_filter(rx_fdm_filter, rx_fdm_fcorr, fdmdv->rxdec_lpf_mem, *nin); + down_convert_and_rx_filter(rx_filt, fdmdv->Nc, rx_fdm_filter, fdmdv->rx_fdm_mem, fdmdv->phase_rx, fdmdv->freq, + fdmdv->freq_pol, *nin, M_FAC/Q); + PROFILE_SAMPLE_AND_LOG(rx_est_timing_start, down_convert_and_rx_filter_start, " down_convert_and_rx_filter"); + fdmdv->rx_timing = rx_est_timing(rx_symbols, fdmdv->Nc, rx_filt, fdmdv->rx_filter_mem_timing, env, *nin, M_FAC); + PROFILE_SAMPLE_AND_LOG(qpsk_to_bits_start, rx_est_timing_start, " rx_est_timing"); + + /* Adjust number of input samples to keep timing within bounds */ + + *nin = M_FAC; + + if (fdmdv->rx_timing > M_FAC/P) + *nin += M_FAC/P; + + if (fdmdv->rx_timing < -M_FAC/P) + *nin -= M_FAC/P; + + foff_fine = qpsk_to_bits(rx_bits, &sync_bit, fdmdv->Nc, fdmdv->phase_difference, fdmdv->prev_rx_symbols, rx_symbols, + fdmdv->old_qpsk_mapping); + memcpy(fdmdv->prev_rx_symbols, rx_symbols, sizeof(COMP)*(fdmdv->Nc+1)); + PROFILE_SAMPLE_AND_LOG(snr_update_start, qpsk_to_bits_start, " qpsk_to_bits"); + snr_update(fdmdv->sig_est, fdmdv->noise_est, fdmdv->Nc, fdmdv->phase_difference); + PROFILE_SAMPLE_AND_LOG(freq_state_start, snr_update_start, " snr_update"); + + /* freq offset estimation state machine */ + + fdmdv->sync = freq_state(reliable_sync_bit, sync_bit, &fdmdv->fest_state, &fdmdv->timer, fdmdv->sync_mem); + PROFILE_SAMPLE_AND_LOG2(freq_state_start, " freq_state"); + fdmdv->foff -= TRACK_COEFF*foff_fine; +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: calc_snr() + AUTHOR......: David Rowe + DATE CREATED: 17 May 2012 + + Calculate current SNR estimate (3000Hz noise BW) + +\*---------------------------------------------------------------------------*/ + +float calc_snr(int Nc, float sig_est[], float noise_est[]) +{ + float S, SdB; + float mean, N50, N50dB, N3000dB; + float snr_dB; + int c; + + S = 0.0; + for(c=0; cNc <= MODEM_STATS_NC_MAX); + + stats->Nc = fdmdv->Nc; + stats->snr_est = calc_snr(fdmdv->Nc, fdmdv->sig_est, fdmdv->noise_est); + stats->sync = fdmdv->sync; + stats->foff = fdmdv->foff; + stats->rx_timing = fdmdv->rx_timing; + stats->clock_offset = 0.0; /* TODO - implement clock offset estimation */ + +#ifndef __EMBEDDED__ + stats->nr = 1; + for(int c=0; cNc+1; c++) { + stats->rx_symbols[0][c] = fdmdv->phase_difference[c]; + } +#endif +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: fdmdv_8_to_16() + AUTHOR......: David Rowe + DATE CREATED: 9 May 2012 + + Changes the sample rate of a signal from 8 to 16 kHz. Support function for + SM1000. + +\*---------------------------------------------------------------------------*/ + +void fdmdv_8_to_16(float out16k[], float in8k[], int n8k) +{ + int i,k,l; + float acc; + + /* this version unrolled for specific FDMDV_OS */ + + assert(FDMDV_OS == 2); + + for(i=0; iNc; i++) + fprintf(stderr," %1.3f", (double)cabsolute(f->phase_tx[i])); + fprintf(stderr,"\nfreq[]:\n"); + for(i=0; i<=f->Nc; i++) + fprintf(stderr," %1.3f", (double)cabsolute(f->freq[i])); + fprintf(stderr,"\nfoff_phase_rect: %1.3f", (double)cabsolute(f->foff_phase_rect)); + fprintf(stderr,"\nphase_rx[]:\n"); + for(i=0; i<=f->Nc; i++) + fprintf(stderr," %1.3f", (double)cabsolute(f->phase_rx[i])); + fprintf(stderr, "\n\n"); +} + + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: randn() + AUTHOR......: David Rowe + DATE CREATED: 2 August 2014 + + Simple approximation to normal (gaussian) random number generator + with 0 mean and unit variance. + +\*---------------------------------------------------------------------------*/ + +#define RANDN_IT 12 /* This magic number of iterations gives us a + unit variance. I think because var = + (b-a)^2/12 for one uniform random variable, so + for a sum of n random variables it's + n(b-a)^2/12, or for b=1, a = 0, n=12, we get + var = 12(1-0)^2/12 = 1 */ + +static float randn() { + int i; + float rn = 0.0; + + for(i=0; isig_pwr_av: %e target_snr_linear: %f noise_pwr_4000Hz: %e noise_gain: %e\n", + sig_pwr, *sig_pwr_av, target_snr_linear, noise_pwr_4000Hz, noise_gain); + */ +} -- cgit v1.2.3