/*---------------------------------------------------------------------------*\ FILE........: cohpsk.c AUTHOR......: David Rowe DATE CREATED: March 2015 Functions that implement a coherent PSK FDM modem. \*---------------------------------------------------------------------------*/ /* Copyright (C) 2015 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 "codec2_cohpsk.h" #include "cohpsk_defs.h" #include "cohpsk_internal.h" #include "fdmdv_internal.h" #include "pilots_coh.h" #include "comp_prim.h" #include "kiss_fft.h" #include "linreg.h" #include "rn_coh.h" #include "test_bits_coh.h" namespace FreeDV { static COMP qpsk_mod[] = { { 1.0, 0.0}, { 0.0, 1.0}, { 0.0,-1.0}, {-1.0, 0.0} }; static int sampling_points[] = {0, 1, 6, 7}; void corr_with_pilots_comp(float *corr_out, float *mag_out, struct COHPSK *coh, int t, COMP f_fine); void update_ct_symb_buf(COMP ct_symb_buf[][COHPSK_NC*ND], COMP ch_symb[][COHPSK_NC*ND]); /*---------------------------------------------------------------------------*\ FUNCTIONS \*---------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------* \ FUNCTION....: cohpsk_create AUTHOR......: David Rowe DATE CREATED: Marcg 2015 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 COHPSK *cohpsk_create(void) { struct COHPSK *coh; struct FDMDV *fdmdv; int r,c,p,i; float freq_hz; assert(COHPSK_NC == PILOTS_NC); assert(COHPSK_NOM_SAMPLES_PER_FRAME == (COHPSK_M*NSYMROWPILOT)); assert(COHPSK_MAX_SAMPLES_PER_FRAME == (COHPSK_M*NSYMROWPILOT+COHPSK_M/P)); assert(COHPSK_ND == ND); assert(COHPSK_NSYM == NSYM); /* as we want to use the tx sym mem on fdmdv */ assert(COHPSK_NT == NT); coh = (struct COHPSK*) malloc(sizeof(struct COHPSK)); if (coh == NULL) return NULL; /* set up buffer of tx pilot symbols for coh demod on rx */ for(r=0; r<2*NPILOTSFRAME; ) { for(p=0; ppilot2[r][c] = pilots_coh[p][c]; } } } /* Clear symbol buffer memory */ for (r=0; rct_symb_buf[r][c].real = 0.0; coh->ct_symb_buf[r][c].imag = 0.0; } } coh->ff_phase.real = 1.0; coh->ff_phase.imag = 0.0; coh->sync = 0; coh->frame = 0; coh->ratio = 0.0; coh->nin = COHPSK_M; /* clear sync window buffer */ for (i=0; ich_fdm_frame_buf[i].real = 0.0; coh->ch_fdm_frame_buf[i].imag = 0.0; } /* set up fdmdv states so we can use those modem functions */ fdmdv = fdmdv_create(COHPSK_NC*ND - 1); fdmdv->fsep = COHPSK_RS*(1.0 + COHPSK_EXCESS_BW); for(c=0; cphase_tx[c].real = 1.0; fdmdv->phase_tx[c].imag = 0.0; /* note non-linear carrier spacing to help PAPR, works v well in conjunction with CLIP */ freq_hz = fdmdv->fsep*( -(COHPSK_NC*ND)/2 - 0.5 + pow(c + 1.0, 0.98) ); fdmdv->freq[c].real = cosf(2.0*M_PI*freq_hz/COHPSK_FS); fdmdv->freq[c].imag = sinf(2.0*M_PI*freq_hz/COHPSK_FS); fdmdv->freq_pol[c] = 2.0*M_PI*freq_hz/COHPSK_FS; //printf("c: %d %f %f\n",c,freq_hz,fdmdv->freq_pol[c]); for(i=0; irx_filter_memory[c][i].real = 0.0; coh->rx_filter_memory[c][i].imag = 0.0; } /* optional per-carrier amplitude weighting for testing */ coh->carrier_ampl[c] = 1.0; } fdmdv->fbb_rect.real = cosf(2.0*PI*FDMDV_FCENTRE/COHPSK_FS); fdmdv->fbb_rect.imag = sinf(2.0*PI*FDMDV_FCENTRE/COHPSK_FS); fdmdv->fbb_pol = 2.0*PI*FDMDV_FCENTRE/COHPSK_FS; coh->fdmdv = fdmdv; coh->sig_rms = coh->noise_rms = 0.0; for(c=0; crx_symb[r][c].real = 0.0; coh->rx_symb[r][c].imag = 0.0; } } coh->verbose = 0; /* disable optional logging by default */ coh->rx_baseband_log = NULL; coh->rx_baseband_log_col_index = 0; coh->rx_filt_log = NULL; coh->rx_filt_log_col_index = 0; coh->ch_symb_log = NULL; coh->ch_symb_log_r = 0; coh->rx_timing_log = NULL; coh->rx_timing_log_index = 0; /* test frames */ coh->ptest_bits_coh_tx = coh->ptest_bits_coh_rx[0] = coh->ptest_bits_coh_rx[1] = (int*)test_bits_coh; coh->ptest_bits_coh_end = (int*)test_bits_coh + sizeof(test_bits_coh)/sizeof(int); /* Disable 'reduce' frequency estimation mode */ coh->freq_est_mode_reduced = 0; return coh; } /*---------------------------------------------------------------------------*\ FUNCTION....: cohpsk_destroy AUTHOR......: David Rowe DATE CREATED: March 2015 Destroy an instance of the modem. \*---------------------------------------------------------------------------*/ void cohpsk_destroy(struct COHPSK *coh) { assert(coh != NULL); fdmdv_destroy(coh->fdmdv); free(coh); } /*---------------------------------------------------------------------------*\ FUNCTION....: bits_to_qpsk_symbols() AUTHOR......: David Rowe DATE CREATED: March 2015 Rate Rs modulator. Maps bits to parallel DQPSK symbols and inserts pilot symbols. \*---------------------------------------------------------------------------*/ void bits_to_qpsk_symbols(COMP tx_symb[][COHPSK_NC*ND], int tx_bits[], int nbits) { int i, r, c, p_r, data_r, d, diversity; short bits; /* check allowed number of bits supplied matches number of QPSK symbols in the frame */ assert( (NSYMROW*COHPSK_NC*2 == nbits) || (NSYMROW*COHPSK_NC*2*ND == nbits)); /* if we input twice as many bits we don't do diversity */ if (NSYMROW*COHPSK_NC*2 == nbits) { diversity = 1; /* diversity mode */ } else { diversity = 2; /* twice as many bits, non diversity mode */ } /* Insert two rows of Nc pilots at beginning of data frame. Organise QPSK symbols into a NSYMBROWS rows by PILOTS_NC*ND cols matrix, each column is a carrier, time flows down the cols...... Note: the "& 0x1" prevents and non binary tx_bits[] screwing up our lives. Call me defensive. sqrtf(ND) term ensures the same energy/symbol for different diversity factors. */ r = 0; for(p_r=0; p_r<2; p_r++) { for(c=0; cpilot2[p][pc], ct_symb_buf[sampling_points[p]][c]); } linreg(&m, &b, x, y, NPILOTSFRAME+2); for(r=0; rphi_[r][c] = atan2(yfit.imag, yfit.real); } /* amplitude estimation */ mag = 0.0; for(p=0; pamp_[r][c] = amp_; } } /* now correct phase of data symbols */ for(c=0; cphi_[r][c]); phi_rect.imag = -sinf(coh->phi_[r][c]); coh->rx_symb[r][c] = cmult(ct_symb_buf[NPILOTSFRAME + r][c], phi_rect); i = c*NSYMROW + r; rx_symb_linear[i] = coh->rx_symb[r][c]; } } /* and finally optional diversity combination, note output is soft decn a "1" is < 0 */ for(c=0; crx_symb[r][c]; for (d=1; drx_symb[r][c + COHPSK_NC*d]); } rot = cmult(div_symb, pi_on_4); i = c*NSYMROW + r; rx_bits[2*i+1] = rot.real; rx_bits[2*i] = rot.imag; /* demodulate bits from upper and lower carriers separately for test purposes */ assert(ND == 2); i = c*NSYMROW + r; rot = cmult(coh->rx_symb[r][c], pi_on_4); coh->rx_bits_lower[2*i+1] = rot.real; coh->rx_bits_lower[2*i] = rot.imag; rot = cmult(coh->rx_symb[r][c + COHPSK_NC], pi_on_4); coh->rx_bits_upper[2*i+1] = rot.real; coh->rx_bits_upper[2*i] = rot.imag; } } /* estimate RMS signal and noise */ mag = 0.0; for(i=0; isig_rms = mag/(NSYMROW*COHPSK_NC*ND); sum_x = 0; sum_xx = 0; n = 0; for (i=0; i coh->sig_rms) { sum_x += s.imag; sum_xx += s.imag*s.imag; n++; } } noise_var = 0; if (n > 1) { noise_var = (n*sum_xx - sum_x*sum_x)/(n*(n-1)); } coh->noise_rms = sqrtf(noise_var); } /*---------------------------------------------------------------------------*\ FUNCTION....: tx_filter_and_upconvert_coh() AUTHOR......: David Rowe DATE CREATED: May 2015 Given NC symbols construct M samples (1 symbol) of NC filtered and upconverted symbols. TODO: work out a way to merge with fdmdv version, e.g. run time define M/NSYM, and run unittests on fdmdv and cohpsk modem afterwards. \*---------------------------------------------------------------------------*/ void tx_filter_and_upconvert_coh(COMP tx_fdm[], int Nc,const COMP tx_symbols[], COMP tx_filter_memory[COHPSK_NC*ND][COHPSK_NSYM], COMP phase_tx[], COMP freq[], COMP *fbb_phase, COMP fbb_rect) { int c; int i,j,k; COMP gain; COMP tx_baseband; COMP two = {2.0, 0.0}; float mag; gain.real = sqrtf(2.0)/2.0; gain.imag = 0.0; for(i=0; ireal /= mag; fbb_phase->imag /= mag; /* shift memory, inserting zeros at end */ for(i=0; ict_symb_buf[t+sampling_points[p]][c]); pc = c % COHPSK_NC; acorr = cadd(acorr, fcmult(coh->pilot2[p][pc], f_corr)); mag += cabsolute(f_corr); } corr += cabsolute(acorr); } *corr_out = corr; *mag_out = mag; } /*---------------------------------------------------------------------------*\ FUNCTION....: frame_sync_fine_freq_est() AUTHOR......: David Rowe DATE CREATED: April 2015 Returns an estimate of frame sync (coarse timing) offset and fine frequency offset, advances to next sync state if we have a reliable match for frame sync. \*---------------------------------------------------------------------------*/ void frame_sync_fine_freq_est(struct COHPSK *coh, COMP ch_symb[][COHPSK_NC*ND], int sync, int *next_sync) { int t; float f_fine, mag, max_corr, max_mag, corr, delta_f_fine, f_fine_range ; COMP f_fine_d_ph; if(coh->freq_est_mode_reduced){ delta_f_fine = 1.3; f_fine_range = 10; }else{ delta_f_fine = .25; f_fine_range = 20; } /* Represent f_fine scan as delta2-phase */ const COMP f_fine_d2_ph = comp_exp_j(2*M_PI*delta_f_fine/COHPSK_RS); f_fine = -f_fine_range; update_ct_symb_buf(coh->ct_symb_buf, ch_symb); /* sample pilots at start of this frame and start of next frame */ if (sync == 0) { /* Represent f_fine as complex delta-phase instead of frequency */ f_fine_d_ph = comp_exp_j(2*M_PI*f_fine/COHPSK_RS); /* sample correlation over 2D grid of time and fine freq points */ max_corr = max_mag = 0; for (f_fine=-f_fine_range; f_fine<=f_fine_range; f_fine+=delta_f_fine) { for (t=0; t= max_corr) { max_corr = corr; max_mag = mag; coh->ct = t; coh->f_fine_est = f_fine; } } /* Advance f_fine */ f_fine_d_ph = cmult(f_fine_d_ph,f_fine_d2_ph); } coh->ff_rect.real = cosf(coh->f_fine_est*2.0*M_PI/COHPSK_RS); coh->ff_rect.imag = -sinf(coh->f_fine_est*2.0*M_PI/COHPSK_RS); if (coh->verbose) fprintf(stderr, " [%d] fine freq f: %6.2f max_ratio: %f ct: %d\n", coh->frame, (double)coh->f_fine_est, (double)(max_corr/max_mag), coh->ct); if (max_corr/max_mag > 0.9) { if (coh->verbose) fprintf(stderr, " [%d] encouraging sync word!\n", coh->frame); coh->sync_timer = 0; *next_sync = 1; } else { *next_sync = 0; } coh->ratio = max_corr/max_mag; } } void update_ct_symb_buf(COMP ct_symb_buf[][COHPSK_NC*ND], COMP ch_symb[][COHPSK_NC*ND]) { int r, c, i; /* update memory in symbol buffer */ for(r=0; rct, comp_exp_j(2*M_PI*coh->f_fine_est/COHPSK_RS)); coh->ratio = fabsf(corr)/mag; // printf("%f\n", cabsolute(corr)/mag); if (fabsf(corr)/mag < 0.8) coh->sync_timer++; else coh->sync_timer = 0; if (coh->sync_timer == 10) { if (coh->verbose) fprintf(stderr," [%d] lost sync ....\n", coh->frame); next_sync = 0; } } sync = next_sync; return sync; } /*---------------------------------------------------------------------------*\ FUNCTION....: cohpsk_mod() AUTHOR......: David Rowe DATE CREATED: 5/4/2015 COHPSK modulator, take a frame of COHPSK_BITS_PER_FRAME or 2*COHPSK_BITS_PER_FRAME bits and generates a frame of COHPSK_NOM_SAMPLES_PER_FRAME modulated symbols. if nbits == COHPSK_BITS_PER_FRAME, diveristy mode is used, if nbits == 2*COHPSK_BITS_PER_FRAME diversity mode is not used. The output signal is complex to support single sided frequency shifting, for example when testing frequency offsets in channel simulation. \*---------------------------------------------------------------------------*/ void cohpsk_mod(struct COHPSK *coh, COMP tx_fdm[], int tx_bits[], int nbits) { struct FDMDV *fdmdv = coh->fdmdv; COMP tx_symb[NSYMROWPILOT][COHPSK_NC*ND]; COMP tx_onesym[COHPSK_NC*ND]; int r,c; bits_to_qpsk_symbols(tx_symb, tx_bits, nbits); for(r=0; rcarrier_ampl[c], tx_symb[r][c]); tx_filter_and_upconvert_coh(&tx_fdm[r*COHPSK_M], COHPSK_NC*ND , tx_onesym, fdmdv->tx_filter_memory, fdmdv->phase_tx, fdmdv->freq, &fdmdv->fbb_phase_tx, fdmdv->fbb_rect); } } /*---------------------------------------------------------------------------*\ FUNCTION....: cohpsk_clip() AUTHOR......: David Rowe DATE CREATED: May 2015 Hard clips a cohpsk modulator signal to improve PAPR, CLIP threshold hard coded and will need to be changed if NC*ND does. \*---------------------------------------------------------------------------*/ void cohpsk_clip(COMP tx_fdm[], float clip_thresh, int n) { COMP sam; float mag; int i; for(i=0; i clip_thresh) { sam = fcmult(clip_thresh/mag, sam); } tx_fdm[i] = sam; } } /*---------------------------------------------------------------------------*\ FUNCTION....: fdm_downconvert_coh AUTHOR......: David Rowe DATE CREATED: May 2015 Frequency shift each modem carrier down to NC baseband signals. TODO: try to combine with fdmdv version, carefully re-test fdmdv modem. \*---------------------------------------------------------------------------*/ void fdm_downconvert_coh(COMP rx_baseband[COHPSK_NC][COHPSK_M+COHPSK_M/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 <= (COHPSK_M+COHPSK_M/P)); /* downconvert */ for (c=0; creal /= mag; foff_phase_rect->imag /= mag; } void rate_Fs_rx_processing(struct COHPSK *coh, COMP ch_symb[][COHPSK_NC*ND], COMP ch_fdm_frame[], float *f_est, int nsymb, int nin, int freq_track) { struct FDMDV *fdmdv = coh->fdmdv; int r, c, i, ch_fdm_frame_index; COMP rx_fdm_frame_bb[COHPSK_M+COHPSK_M/P]; COMP rx_baseband[COHPSK_NC*ND][COHPSK_M+COHPSK_M/P]; COMP rx_filt[COHPSK_NC*ND][P+1]; float env[NT*P], rx_timing; COMP rx_onesym[COHPSK_NC*ND]; float beta, g; COMP adiff, amod_strip, mod_strip; ch_fdm_frame_index = 0; rx_timing = 0; for (r=0; rfbb_phase_rx, nin); ch_fdm_frame_index += nin; fdm_downconvert_coh(rx_baseband, COHPSK_NC*ND, rx_fdm_frame_bb, fdmdv->phase_rx, fdmdv->freq, nin); rx_filter_coh(rx_filt, COHPSK_NC*ND, rx_baseband, coh->rx_filter_memory, nin); rx_timing = rx_est_timing(rx_onesym, fdmdv->Nc, rx_filt, fdmdv->rx_filter_mem_timing, env, nin, COHPSK_M); for(c=0; cNc+1; c++) { //printf("rx_onesym[%d] %f %f prev_rx_symbols[%d] %f %f\n", c, rx_onesym[c].real, rx_onesym[c].imag, // fdmdv->prev_rx_symbols[c].real, fdmdv->prev_rx_symbols[c].imag); adiff = cmult(rx_onesym[c], cconj(fdmdv->prev_rx_symbols[c])); fdmdv->prev_rx_symbols[c] = rx_onesym[c]; /* 4th power strips QPSK modulation, by multiplying phase by 4 Using the abs value of the real coord was found to help non-linear issues when noise power was large. */ amod_strip = cmult(adiff, adiff); amod_strip = cmult(amod_strip, amod_strip); amod_strip.real = fabsf(amod_strip.real); mod_strip = cadd(mod_strip, amod_strip); } //printf("modstrip: %f %f\n", mod_strip.real, mod_strip.imag); /* loop filter made up of 1st order IIR plus integrator. Integerator was found to be reqd */ fdmdv->foff_filt = (1.0-beta)*fdmdv->foff_filt + beta*atan2(mod_strip.imag, mod_strip.real); //printf("foff_filt: %f angle: %f\n", fdmdv->foff_filt, atan2(mod_strip.imag, mod_strip.real)); *f_est += g*fdmdv->foff_filt; } /* Optional logging used for testing against Octave version */ if (coh->rx_baseband_log) { assert(nin <= (COHPSK_M+COHPSK_M/P)); for(c=0; crx_baseband_log[c*coh->rx_baseband_log_col_sz + coh->rx_baseband_log_col_index + i] = rx_baseband[c][i]; } } coh->rx_baseband_log_col_index += nin; assert(coh->rx_baseband_log_col_index <= coh->rx_baseband_log_col_sz); } if (coh->rx_filt_log) { for(c=0; crx_filt_log[c*coh->rx_filt_log_col_sz + coh->rx_filt_log_col_index + i] = rx_filt[c][i]; } } coh->rx_filt_log_col_index += nin/(COHPSK_M/P); } if (coh->ch_symb_log) { for(c=0; cch_symb_log[coh->ch_symb_log_r*COHPSK_NC*ND + c] = ch_symb[r][c]; } coh->ch_symb_log_r++; } if (coh->rx_timing_log) { coh->rx_timing_log[coh->rx_timing_log_index] = rx_timing; coh->rx_timing_log_index++; //printf("rx_timing_log_index: %d\n", coh->rx_timing_log_index); } /* we only allow a timing shift on one symbol per frame */ if (nin != COHPSK_M) nin = COHPSK_M; } coh->rx_timing = rx_timing; } /*---------------------------------------------------------------------------*\ FUNCTION....: cohpsk_set_freq_est_mode() AUTHOR......: Brady O'Brien DATE CREATED: 12 Dec 2017 Enables or disables a 'simple' frequency estimation mode. Simple frequency estimation uses substantially less CPU when cohpsk modem is not sunk than default mode, but may take many frames to sync. \*---------------------------------------------------------------------------*/ void cohpsk_set_freq_est_mode(struct COHPSK *coh, int use_simple_mode){ if(use_simple_mode){ coh->freq_est_mode_reduced = 1; }else{ coh->freq_est_mode_reduced = 0; } } /*---------------------------------------------------------------------------*\ FUNCTION....: cohpsk_demod() AUTHOR......: David Rowe DATE CREATED: 5/4/2015 COHPSK demodulator, takes an array of (nominally) nin_frame = COHPSK_NOM_SAMPLES_PER_FRAME modulated samples, returns an array of COHPSK_BITS_PER_FRAME bits. The input signal is complex to support single sided frequency shifting before the demod input (e.g. click to tune feature). \*---------------------------------------------------------------------------*/ void cohpsk_demod(struct COHPSK *coh, float rx_bits[], int *sync_good, COMP rx_fdm[], int *nin_frame) { COMP ch_symb[NSW*NSYMROWPILOT][COHPSK_NC*ND]; int i, j, sync, anext_sync, next_sync, nin, r, c, ns_done; float max_ratio, f_est; assert(*nin_frame <= COHPSK_MAX_SAMPLES_PER_FRAME); next_sync = sync = coh->sync; for (i=0; ich_fdm_frame_buf[i] = coh->ch_fdm_frame_buf[i+*nin_frame]; //printf("nin_frame: %d i: %d i+nin_frame: %d\n", *nin_frame, i, i+*nin_frame); for (j=0; ich_fdm_frame_buf[i] = rx_fdm[j]; //printf("i: %d j: %d rx_fdm[0]: %f %f\n", i,j, rx_fdm[0].real, rx_fdm[0].imag); /* if out of sync do Initial Freq offset estimation using NSW frames to flush out filter memories */ if (sync == 0) { max_ratio = 0.0; f_est = 0.0; coh->f_est -= 20; if(coh->f_est < FDMDV_FCENTRE - 60.0){ coh->f_est = FDMDV_FCENTRE + 60; } if(!coh->freq_est_mode_reduced){ coh->f_est = FDMDV_FCENTRE-40.0; } ns_done = 0; //for (coh->f_est = FDMDV_FCENTRE-40.0; coh->f_est <= FDMDV_FCENTRE+40.0; coh->f_est += 40.0) while(!ns_done){ /* Use slower freq estimator; only do one chunk of freq range */ if(coh->freq_est_mode_reduced){ coh->f_est -= 20; if(coh->f_est < FDMDV_FCENTRE - 60.0){ coh->f_est = FDMDV_FCENTRE + 60; } ns_done = 1; }else{ /* we can test +/- 20Hz, so we break this up into 3 tests to cover +/- 60Hz */ if(coh->f_est > FDMDV_FCENTRE+40.0) ns_done = 1; } if (coh->verbose) fprintf(stderr, " [%d] acohpsk.f_est: %f +/- 20\n", coh->frame, (double)coh->f_est); /* we are out of sync so reset f_est and process two frames to clean out memories */ rate_Fs_rx_processing(coh, ch_symb, coh->ch_fdm_frame_buf, &coh->f_est, NSW*NSYMROWPILOT, COHPSK_M, 0); for (i=0; ict_symb_buf, &ch_symb[i*NSYMROWPILOT]); } frame_sync_fine_freq_est(coh, &ch_symb[(NSW-1)*NSYMROWPILOT], sync, &anext_sync); if (anext_sync == 1) { //printf(" [%d] acohpsk.ratio: %f\n", f, coh->ratio); if (coh->ratio > max_ratio) { max_ratio = coh->ratio; f_est = coh->f_est - coh->f_fine_est; next_sync = anext_sync; } } if(!coh->freq_est_mode_reduced){ coh->f_est += 40; } } if (next_sync == 1) { /* we've found a sync candidate! re-process last NSW frames with adjusted f_est then check again */ coh->f_est = f_est; if (coh->verbose) fprintf(stderr, " [%d] trying sync and f_est: %f\n", coh->frame, (double)coh->f_est); rate_Fs_rx_processing(coh, ch_symb, coh->ch_fdm_frame_buf, &coh->f_est, NSW*NSYMROWPILOT, COHPSK_M, 0); for (i=0; ict_symb_buf, &ch_symb[i*NSYMROWPILOT]); } /* for(i=0; ict_symb_buf[i][0].real, coh->ct_symb_buf[i][0].imag); } */ frame_sync_fine_freq_est(coh, &ch_symb[(NSW-1)*NSYMROWPILOT], sync, &next_sync); if (fabs(coh->f_fine_est) > 2.0) { if (coh->verbose) fprintf(stderr, " [%d] Hmm %f is a bit big :(\n", coh->frame, (double)coh->f_fine_est); next_sync = 0; } } if (next_sync == 1) { /* OK we are in sync! demodulate first frame (demod completed below) */ if (coh->verbose) fprintf(stderr, " [%d] in sync! f_est: %f ratio: %f \n", coh->frame, (double)coh->f_est, (double)coh->ratio); for(r=0; rct_symb_ff_buf[r][c] = coh->ct_symb_buf[coh->ct+r][c]; } } /* If in sync just do sample rate processing on latest frame */ if (sync == 1) { rate_Fs_rx_processing(coh, ch_symb, rx_fdm, &coh->f_est, NSYMROWPILOT, coh->nin, 1); frame_sync_fine_freq_est(coh, ch_symb, sync, &next_sync); for(r=0; r<2; r++) for(c=0; cct_symb_ff_buf[r][c] = coh->ct_symb_ff_buf[r+NSYMROWPILOT][c]; for(; rct_symb_ff_buf[r][c] = coh->ct_symb_buf[coh->ct+r][c]; } /* if we are in sync complete demodulation with symbol rate processing */ *sync_good = 0; if ((next_sync == 1) || (sync == 1)) { qpsk_symbols_to_bits(coh, rx_bits, coh->ct_symb_ff_buf); *sync_good = 1; } sync = sync_state_machine(coh, sync, next_sync); coh->sync = sync; /* work out how many samples we need for the next call to account for differences in tx and rx sample clocks */ nin = COHPSK_M; if (sync == 1) { if (coh->rx_timing > COHPSK_M/P) nin = COHPSK_M + COHPSK_M/P; if (coh->rx_timing < -COHPSK_M/P) nin = COHPSK_M - COHPSK_M/P; } coh->nin = nin; *nin_frame = (NSYMROWPILOT-1)*COHPSK_M + nin; //if (coh->verbose) // fprintf(stderr, "%f %d %d\n", coh->rx_timing, nin, *nin_frame); } /*---------------------------------------------------------------------------*\ FUNCTION....: cohpsk_fs_offset() AUTHOR......: David Rowe DATE CREATED: May 2015 Simulates small Fs offset between mod and demod. \*---------------------------------------------------------------------------*/ int cohpsk_fs_offset(COMP out[], COMP in[], int n, float sample_rate_ppm) { double tin, f; int tout, t1, t2; tin = 0.0; tout = 0; while (tin < n) { t1 = floor(tin); t2 = ceil(tin); f = tin - t1; out[tout].real = (1.0-f)*in[t1].real + f*in[t2].real; out[tout].imag = (1.0-f)*in[t1].imag + f*in[t2].imag; tout += 1; tin += 1.0 + sample_rate_ppm/1E6; //printf("tin: %f tout: %d f: %f\n", tin, tout, f); } return tout; } /*---------------------------------------------------------------------------*\ FUNCTION....: cohpsk_get_demod_stats() AUTHOR......: David Rowe DATE CREATED: 14 June 2015 Fills stats structure with a bunch of demod information. \*---------------------------------------------------------------------------*/ void cohpsk_get_demod_stats(struct COHPSK *coh, struct MODEM_STATS *stats) { int c,r; COMP pi_4; float new_snr_est; pi_4.real = cosf(M_PI/4.0); pi_4.imag = sinf(M_PI/4.0); stats->Nc = COHPSK_NC*ND; assert(stats->Nc <= MODEM_STATS_NC_MAX); new_snr_est = 20*log10((coh->sig_rms+1E-6)/(coh->noise_rms+1E-6)) - 10*log10(3000.0/700.0); stats->snr_est = 0.9*stats->snr_est + 0.1*new_snr_est; //fprintf(stderr, "sig_rms: %f noise_rms: %f snr_est: %f\n", coh->sig_rms, coh->noise_rms, stats->snr_est); stats->sync = coh->sync; stats->foff = coh->f_est - FDMDV_FCENTRE; stats->rx_timing = coh->rx_timing; stats->clock_offset = 0.0; /* TODO - implement clock offset estimation */ assert(NSYMROW <= MODEM_STATS_NR_MAX); stats->nr = NSYMROW; for(c=0; crx_symbols[r][c] = cmult(coh->rx_symb[r][c], pi_4); } } } void cohpsk_set_verbose(struct COHPSK *coh, int verbose) { assert(coh != NULL); coh->verbose = verbose; } void cohpsk_set_frame(struct COHPSK *coh, int frame) { assert(coh != NULL); coh->frame = frame; } /*---------------------------------------------------------------------------*\ FUNCTION....: cohpsk_get_test_bits() AUTHOR......: David Rowe DATE CREATED: June 2015 Returns a frame of known test bits. \*---------------------------------------------------------------------------*/ void cohpsk_get_test_bits(struct COHPSK *coh, int rx_bits[]) { memcpy(rx_bits, coh->ptest_bits_coh_tx, sizeof(int)*COHPSK_BITS_PER_FRAME); coh->ptest_bits_coh_tx += COHPSK_BITS_PER_FRAME; if (coh->ptest_bits_coh_tx >=coh->ptest_bits_coh_end) { coh->ptest_bits_coh_tx = (int*)test_bits_coh; } } /*---------------------------------------------------------------------------*\ FUNCTION....: cohpsk_put_test_bits() AUTHOR......: David Rowe DATE CREATED: June 2015 Accepts bits from demod and attempts to sync with the known test_bits sequence. When synced measures bit errors. Has states to track two separate received test sequences based on channel 0 or 1. \*---------------------------------------------------------------------------*/ void cohpsk_put_test_bits(struct COHPSK *coh, int *state, short error_pattern[], int *bit_errors, char rx_bits_char[], int channel) { int i, next_state, anerror; int rx_bits[COHPSK_BITS_PER_FRAME]; assert((channel == 0) || (channel == 1)); int *ptest_bits_coh_rx = coh->ptest_bits_coh_rx[channel]; for(i=0; i 1)) { fprintf(stderr, "i: %d rx_bits: %d ptest_bits_coh_rx: %d\n", i, rx_bits[i], ptest_bits_coh_rx[i]); } *bit_errors += anerror; error_pattern[i] = anerror; } /* state logic */ next_state = *state; if (*state == 0) { if (*bit_errors < 4) { next_state = 1; ptest_bits_coh_rx += COHPSK_BITS_PER_FRAME; if (ptest_bits_coh_rx >= coh->ptest_bits_coh_end) { ptest_bits_coh_rx = (int*)test_bits_coh; } } } /* if 5 frames with large BER reset test frame sync */ if (*state > 0) { if (*bit_errors > 8) { if (*state == 6) next_state = 0; else next_state = *state+1; } else next_state = 1; } if (*state > 0) { ptest_bits_coh_rx += COHPSK_BITS_PER_FRAME; if (ptest_bits_coh_rx >= coh->ptest_bits_coh_end) { ptest_bits_coh_rx = (int*)test_bits_coh; } } //fprintf(stderr, "state: %d next_state: %d bit_errors: %d\n", *state, next_state, *bit_errors); *state = next_state; coh->ptest_bits_coh_rx[channel] = ptest_bits_coh_rx; } int cohpsk_error_pattern_size(void) { return COHPSK_BITS_PER_FRAME; } float *cohpsk_get_rx_bits_lower(struct COHPSK *coh) { return coh->rx_bits_lower; } float *cohpsk_get_rx_bits_upper(struct COHPSK *coh) { return coh->rx_bits_upper; } void cohpsk_set_carrier_ampl(struct COHPSK *coh, int c, float ampl) { assert(c < COHPSK_NC*ND); coh->carrier_ampl[c] = ampl; fprintf(stderr, "cohpsk_set_carrier_ampl: %d %f\n", c, (double)ampl); } } // FreeDV