aboutsummaryrefslogtreecommitdiff
path: root/src/fsk.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fsk.c')
-rw-r--r--src/fsk.c1052
1 files changed, 1052 insertions, 0 deletions
diff --git a/src/fsk.c b/src/fsk.c
new file mode 100644
index 0000000..7bb009e
--- /dev/null
+++ b/src/fsk.c
@@ -0,0 +1,1052 @@
+/*---------------------------------------------------------------------------*\
+
+ FILE........: fsk.c
+ AUTHOR......: Brady O'Brien & David Rowe
+ DATE CREATED: 7 January 2016
+
+ C Implementation of 2/4FSK modulator/demodulator, based on octave/fsk_lib.m
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2016-2020 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 <http://www.gnu.org/licenses/>.
+*/
+
+/*---------------------------------------------------------------------------*\
+
+ DEFINES
+
+\*---------------------------------------------------------------------------*/
+
+/* Define this to enable EbNodB estimate */
+/* This needs square roots, may take more cpu time than it's worth */
+#define EST_EBNO
+
+/* This is a flag for the freq. estimator to use a precomputed/rt computed hann window table
+ On platforms with slow cosf, this will produce a substantial speedup at the cost of a small
+ amount of memory
+*/
+#define USE_HANN_TABLE
+
+/* This flag turns on run-time hann table generation. If USE_HANN_TABLE is unset,
+ this flag has no effect. If USE_HANN_TABLE is set and this flag is set, the
+ hann table will be allocated and generated when fsk_init or fsk_init_hbr is
+ called. If this flag is not set, a hann function table of size fsk->Ndft MUST
+ be provided. On small platforms, this can be used with a precomputed table to
+ save memory at the cost of flash space.
+*/
+#define GENERATE_HANN_TABLE_RUNTIME
+
+/* Turn off table generation if on cortex M4 to save memory */
+#ifdef CORTEX_M4
+#undef USE_HANN_TABLE
+#endif
+
+/*---------------------------------------------------------------------------*\
+
+ INCLUDES
+
+\*---------------------------------------------------------------------------*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <math.h>
+
+#include "fsk.h"
+#include "comp_prim.h"
+#include "kiss_fftr.h"
+#include "modem_probe.h"
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTIONS
+
+\*---------------------------------------------------------------------------*/
+
+static void stats_init(struct FSK *fsk);
+
+#ifdef USE_HANN_TABLE
+/*
+ This is used by fsk_create and fsk_create_hbr to generate a hann function
+ table
+*/
+static void fsk_generate_hann_table(struct FSK* fsk){
+ int Ndft = fsk->Ndft;
+ size_t i;
+
+ for(i=0; i<Ndft; i++){
+ fsk->hann_table[i] = 0.5 - 0.5 * cosf(2.0 * M_PI * (float)i / (float) (Ndft-1));
+ }
+}
+#endif
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: fsk_create_core
+ AUTHOR......: Brady O'Brien
+ DATE CREATED: 7 January 2016
+
+ In this version of the demod the standard/hbr modes have been
+ largely combined at they shared so much common code. The
+ fsk_create/fsk_create_hbr function interface has been retained to
+ maximise compatibility with existing applications.
+
+\*---------------------------------------------------------------------------*/
+
+struct FSK * fsk_create_core(int Fs, int Rs, int M, int P, int Nsym, int f1_tx, int tone_spacing)
+{
+ struct FSK *fsk;
+ int i;
+
+ /* Check configuration validity */
+ assert(Fs > 0);
+ assert(Rs > 0);
+ assert(P > 0);
+ assert(Nsym > 0);
+ /* Ts (Fs/Rs) must be an integer */
+ assert( (Fs%Rs) == 0 );
+ /* Ts/P (Fs/Rs/P) must be an integer */
+ assert( ((Fs/Rs)%P) == 0 );
+ /* If P is too low we don't have a good choice of timing offsets to choose from */
+ assert( P >= 4 );
+ assert( M==2 || M==4);
+
+ fsk = (struct FSK*) calloc(1, sizeof(struct FSK)); assert(fsk != NULL);
+
+ // Need enough bins to within 10% of tone centre
+ float bin_width_Hz = 0.1*Rs;
+ float Ndft = (float)Fs/bin_width_Hz;
+ Ndft = pow(2.0, ceil(log2(Ndft)));
+
+ /* Set constant config parameters */
+ fsk->Fs = Fs;
+ fsk->Rs = Rs;
+ fsk->Ts = Fs/Rs;
+ fsk->burst_mode = 0;
+ fsk->P = P;
+ fsk->Nsym = Nsym;
+ fsk->N = fsk->Ts*fsk->Nsym;
+ fsk->Ndft = Ndft;
+ fsk->tc = 0.1;
+ fsk->Nmem = fsk->N+(2*fsk->Ts);
+ fsk->f1_tx = f1_tx;
+ fsk->tone_spacing = tone_spacing;
+ fsk->nin = fsk->N;
+ fsk->lock_nin = 0;
+ fsk->mode = M==2 ? MODE_2FSK : MODE_4FSK;
+ fsk->Nbits = M==2 ? fsk->Nsym : fsk->Nsym*2;
+ fsk->est_min = 0;
+ fsk->est_max = Fs;
+ fsk->est_space = 0.75*Rs;
+ fsk->freq_est_type = 0;
+
+ //printf("C.....: M: %d Fs: %d Rs: %d Ts: %d nsym: %d nbit: %d N: %d Ndft: %d fmin: %d fmax: %d\n",
+ // M, fsk->Fs, fsk->Rs, fsk->Ts, fsk->Nsym, fsk->Nbits, fsk->N, fsk->Ndft, fsk->est_min, fsk->est_max);
+ /* Set up rx state */
+ for(i=0; i<M; i++)
+ fsk->phi_c[i] = comp_exp_j(0);
+ fsk->f_dc = (COMP*)malloc(M*fsk->Nmem*sizeof(COMP)); assert(fsk->f_dc != NULL);
+ for(i=0; i<M*fsk->Nmem; i++)
+ fsk->f_dc[i] = comp0();
+
+ fsk->fft_cfg = kiss_fft_alloc(Ndft,0,NULL,NULL); assert(fsk->fft_cfg != NULL);
+ fsk->Sf = (float*)malloc(sizeof(float)*fsk->Ndft); assert(fsk->Sf != NULL);
+ for(i=0;i<Ndft;i++)fsk->Sf[i] = 0;
+
+ #ifdef USE_HANN_TABLE
+ #ifdef GENERATE_HANN_TABLE_RUNTIME
+ fsk->hann_table = (float*)malloc(sizeof(float)*fsk->Ndft); assert(fsk->hann_table != NULL);
+ fsk_generate_hann_table(fsk);
+ #else
+ fsk->hann_table = NULL;
+ #endif
+ #endif
+
+
+ fsk->norm_rx_timing = 0;
+
+ /* Set up tx state */
+ fsk->tx_phase_c = comp_exp_j(0);
+
+ /* Set up demod stats */
+ fsk->EbNodB = 0;
+
+ for( i=0; i<M; i++)
+ fsk->f_est[i] = 0;
+
+ fsk->ppm = 0;
+
+ fsk->stats = (struct MODEM_STATS*)malloc(sizeof(struct MODEM_STATS)); assert(fsk->stats != NULL);
+ stats_init(fsk);
+ fsk->normalise_eye = 1;
+
+ return fsk;
+}
+
+/*---------------------------------------------------------------------------* \
+
+ FUNCTION....: fsk_create
+ AUTHOR......: Brady O'Brien
+ DATE CREATED: 7 January 2016
+
+ Create and initialize an instance of the FSK modem. Returns a pointer
+ to the modem state/config struct. One modem config struct may be used
+ for both mod and demod.
+
+ If you are not intending to use the modulation functions, you can
+ set f1_tx to FSK_NONE.
+
+\*---------------------------------------------------------------------------*/
+
+struct FSK * fsk_create(int Fs, int Rs, int M, int tx_f1, int tx_fs) {
+ return fsk_create_core(Fs, Rs, M, FSK_DEFAULT_P, FSK_DEFAULT_NSYM, tx_f1, tx_fs);
+}
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: fsk_create_hbr
+ AUTHOR......: Brady O'Brien
+ DATE CREATED: 11 February 2016
+
+ Alternate version of create allows user defined oversampling P and
+ averaging window Nsym. In the current version of the demod it's
+ simply an alias for the default core function.
+
+ P is the oversampling rate of the internal demod processing, which
+ happens at Rs*P Hz. We filter the tones at P different timing
+ offsets, and choose the best one. P should be >=8, so we have a
+ choice of at least 8 timing offsets. This may require some
+ adjustment of Fs and Rs, as Fs/Rs/P must be an integer.
+
+ Nsym is the number of symbols we average demod parameters like
+ symbol timing over.
+
+\*---------------------------------------------------------------------------*/
+
+struct FSK * fsk_create_hbr(int Fs, int Rs, int M, int P, int Nsym, int f1_tx, int tone_spacing) {
+ return fsk_create_core(Fs, Rs, M, P, Nsym, f1_tx, tone_spacing);
+}
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: fsk_destroy
+ AUTHOR......: Brady O'Brien
+ DATE CREATED: 11 February 2016
+
+ Call this to free all memory and shut down the modem.
+
+\*---------------------------------------------------------------------------*/
+
+void fsk_destroy(struct FSK *fsk){
+ free(fsk->Sf);
+ free(fsk->f_dc);
+ free(fsk->fft_cfg);
+ free(fsk->stats);
+ free(fsk->hann_table);
+ free(fsk);
+}
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: fsk_mod
+ AUTHOR......: Brady O'Brien
+ DATE CREATED: 11 February 2016
+
+ FSK modulator function, real valued output samples with amplitude 2.
+
+\*---------------------------------------------------------------------------*/
+
+void fsk_mod(struct FSK *fsk,float fsk_out[], uint8_t tx_bits[], int nbits) {
+ COMP tx_phase_c = fsk->tx_phase_c; /* Current complex TX phase */
+ int f1_tx = fsk->f1_tx; /* '0' frequency */
+ int tone_spacing = fsk->tone_spacing; /* space between frequencies */
+ int Ts = fsk->Ts; /* samples-per-symbol */
+ int Fs = fsk->Fs; /* sample freq */
+ int M = fsk->mode;
+ COMP dosc_f[M]; /* phase shift per sample */
+ COMP dph; /* phase shift of current bit */
+ size_t i,j,m,bit_i,sym;
+
+ /* trap these parametrs being set to FSK_UNUSED, then calling mod */
+ assert(f1_tx > 0);
+ assert(tone_spacing > 0);
+
+ /* Init the per sample phase shift complex numbers */
+ for( m=0; m<M; m++){
+ dosc_f[m] = comp_exp_j(2*M_PI*((float)(f1_tx+(tone_spacing*m))/(float)(Fs)));
+ }
+
+ bit_i = 0;
+ int nsym = nbits/(M>>1);
+ for(i=0; i<nsym; i++) {
+ sym = 0;
+ /* Pack the symbol number from the bit stream */
+ for( m=M; m>>=1; ){
+ uint8_t bit = tx_bits[bit_i];
+ bit = (bit==1)?1:0;
+ sym = (sym<<1)|bit;
+ bit_i++;
+ }
+ /* Look up symbol phase shift */
+ dph = dosc_f[sym];
+ /* Spin the oscillator for a symbol period */
+ for(j=0; j<Ts; j++){
+ tx_phase_c = cmult(tx_phase_c,dph);
+ fsk_out[i*Ts+j] = 2*tx_phase_c.real;
+ }
+
+ }
+
+ /* Normalize TX phase to prevent drift */
+ tx_phase_c = comp_normalize(tx_phase_c);
+
+ /* save TX phase */
+ fsk->tx_phase_c = tx_phase_c;
+
+}
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: fsk_mod_c
+ AUTHOR......: Brady O'Brien
+ DATE CREATED: 11 February 2016
+
+ FSK modulator function, complex valued output samples with magnitude 1.
+
+\*---------------------------------------------------------------------------*/
+
+void fsk_mod_c(struct FSK *fsk,COMP fsk_out[], uint8_t tx_bits[], int nbits) {
+ COMP tx_phase_c = fsk->tx_phase_c; /* Current complex TX phase */
+ int f1_tx = fsk->f1_tx; /* '0' frequency */
+ int tone_spacing = fsk->tone_spacing; /* space between frequencies */
+ int Ts = fsk->Ts; /* samples-per-symbol */
+ int Fs = fsk->Fs; /* sample freq */
+ int M = fsk->mode;
+ COMP dosc_f[M]; /* phase shift per sample */
+ COMP dph; /* phase shift of current bit */
+ size_t i,j,bit_i,sym;
+ int m;
+
+ /* trap these parametrs being set to FSK_UNUSED, then calling mod */
+ assert(f1_tx > 0);
+ assert(tone_spacing > 0);
+
+ /* Init the per sample phase shift complex numbers */
+ for( m=0; m<M; m++){
+ dosc_f[m] = comp_exp_j(2*M_PI*((float)(f1_tx+(tone_spacing*m))/(float)(Fs)));
+ }
+
+ bit_i = 0;
+ int nsym = nbits/(M>>1);
+ for(i=0; i<nsym; i++) {
+ sym = 0;
+ /* Pack the symbol number from the bit stream */
+ for( m=M; m>>=1; ){
+ uint8_t bit = tx_bits[bit_i];
+ bit = (bit==1)?1:0;
+ sym = (sym<<1)|bit;
+ bit_i++;
+ }
+ /* Look up symbol phase shift */
+ dph = dosc_f[sym];
+ /* Spin the oscillator for a symbol period */
+ for(j=0; j<Ts; j++){
+ tx_phase_c = cmult(tx_phase_c,dph);
+ fsk_out[i*Ts+j] = tx_phase_c;
+ }
+ }
+
+ /* Normalize TX phase to prevent drift */
+ tx_phase_c = comp_normalize(tx_phase_c);
+
+ /* save TX phase */
+ fsk->tx_phase_c = tx_phase_c;
+
+}
+
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: fsk_mod_ext_vco
+ AUTHOR......: David Rowe
+ DATE CREATED: February 2018
+
+ Modulator that assume an external VCO. The output is a voltage
+ that changes for each symbol.
+
+\*---------------------------------------------------------------------------*/
+
+void fsk_mod_ext_vco(struct FSK *fsk, float vco_out[], uint8_t tx_bits[], int nbits) {
+ int f1_tx = fsk->f1_tx; /* '0' frequency */
+ int tone_spacing = fsk->tone_spacing; /* space between frequencies */
+ int Ts = fsk->Ts; /* samples-per-symbol */
+ int M = fsk->mode;
+ int i, j, m, sym, bit_i;
+
+ /* trap these parametrs being set to FSK_UNUSED, then calling mod */
+ assert(f1_tx > 0);
+ assert(tone_spacing > 0);
+
+ bit_i = 0;
+ int nsym = nbits/(M>>1);
+ for(i=0; i<nsym; i++) {
+ /* generate the symbol number from the bit stream,
+ e.g. 0,1 for 2FSK, 0,1,2,3 for 4FSK */
+
+ sym = 0;
+
+ /* unpack the symbol number from the bit stream */
+
+ for( m=M; m>>=1; ){
+ uint8_t bit = tx_bits[bit_i];
+ bit = (bit==1)?1:0;
+ sym = (sym<<1)|bit;
+ bit_i++;
+ }
+
+ /*
+ Map 'sym' to VCO frequency
+ Note: drive is inverted, a higher tone drives VCO voltage lower
+ */
+
+ //fprintf(stderr, "i: %d sym: %d freq: %f\n", i, sym, f1_tx + tone_spacing*(float)sym);
+ for(j=0; j<Ts; j++) {
+ vco_out[i*Ts+j] = f1_tx + tone_spacing*(float)sym;
+ }
+ }
+}
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: fsk_nin
+ AUTHOR......: Brady O'Brien
+ DATE CREATED: 11 February 2016
+
+ Call me before each call to fsk_demod() to determine how many new
+ samples you should pass in. the number of samples will vary due to
+ timing variations.
+
+\*---------------------------------------------------------------------------*/
+
+uint32_t fsk_nin(struct FSK *fsk){
+ return (uint32_t)fsk->nin;
+}
+
+/*
+ * Internal function to estimate the frequencies of the FSK tones.
+ * This is split off because it is fairly complicated, needs a bunch of memory, and probably
+ * takes more cycles than the rest of the demod.
+ * Parameters:
+ * fsk - FSK struct from demod containing FSK config
+ * fsk_in - block of samples in this demod cycles, must be nin long
+ * freqs - Array for the estimated frequencies
+ * M - number of frequency peaks to find
+ */
+void fsk_demod_freq_est(struct FSK *fsk, COMP fsk_in[], float *freqs, int M) {
+ int Ndft = fsk->Ndft;
+ int Fs = fsk->Fs;
+ int nin = fsk->nin;
+ size_t i,j;
+ float hann;
+ float max;
+ int imax;
+ kiss_fft_cfg fft_cfg = fsk->fft_cfg;
+ int freqi[M];
+ int st,en,f_zero;
+
+ kiss_fft_cpx *fftin = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx)*Ndft);
+ kiss_fft_cpx *fftout = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx)*Ndft);
+
+ st = (fsk->est_min*Ndft)/Fs + Ndft/2; if (st < 0) st = 0;
+ en = (fsk->est_max*Ndft)/Fs + Ndft/2; if (en > Ndft) en = Ndft;
+ //fprintf(stderr, "min: %d max: %d st: %d en: %d\n", fsk->est_min, fsk->est_max, st, en);
+
+ f_zero = (fsk->est_space*Ndft)/Fs;
+ //fprintf(stderr, "fsk->est_space: %d f_zero = %d\n", fsk->est_space, f_zero);
+
+ int numffts = floor((float)nin/(Ndft/2)) - 1;
+ for(j=0; j<numffts; j++){
+ int a = j*Ndft/2;
+ //fprintf(stderr, "numffts: %d j: %d a: %d\n", numffts, (int)j, a);
+ /* Copy FSK buffer into reals of FFT buffer and apply a hann window */
+ for(i=0; i<Ndft; i++){
+ #ifdef USE_HANN_TABLE
+ hann = fsk->hann_table[i];
+ #else
+ hann = 0.5 - 0.5 * cosf(2.0 * M_PI * (float)i / (float) (fft_samps-1));
+ #endif
+ fftin[i].r = hann*fsk_in[i+a].real;
+ fftin[i].i = hann*fsk_in[i+a].imag;
+ }
+
+ /* Do the FFT */
+ kiss_fft(fft_cfg,fftin,fftout);
+
+ /* FFT shift to put DC bin at Ndft/2 */
+ kiss_fft_cpx tmp;
+ for(i=0; i<Ndft/2; i++) {
+ tmp = fftout[i];
+ fftout[i] = fftout[i+Ndft/2];
+ fftout[i+Ndft/2] = tmp;
+ }
+
+ /* Find the magnitude^2 of each freq slot */
+ for(i=0; i<Ndft; i++) {
+ fftout[i].r = (fftout[i].r*fftout[i].r) + (fftout[i].i*fftout[i].i) ;
+ }
+
+ /* Mix back in with the previous fft block */
+ /* Copy new fft est into imag of fftout for frequency divination below */
+ float tc = fsk->tc;
+ for(i=0; i<Ndft; i++){
+ fsk->Sf[i] = (fsk->Sf[i]*(1-tc)) + (sqrtf(fftout[i].r)*tc);
+ fftout[i].i = fsk->Sf[i];
+ }
+ }
+
+ modem_probe_samp_f("t_Sf",fsk->Sf,Ndft);
+
+ max = 0;
+ /* Find the M frequency peaks here */
+ for(i=0; i<M; i++){
+ imax = 0;
+ max = 0;
+ for(j=st;j<en;j++){
+ if(fftout[j].i > max){
+ max = fftout[j].i;
+ imax = j;
+ }
+ }
+ /* Blank out FMax +/-Fspace/2 */
+ int f_min, f_max;
+ f_min = imax - f_zero;
+ f_min = f_min < 0 ? 0 : f_min;
+ f_max = imax + f_zero;
+ f_max = f_max > Ndft ? Ndft : f_max;
+ for(j=f_min; j<f_max; j++)
+ fftout[j].i = 0;
+
+ /* Stick the freq index on the list */
+ freqi[i] = imax - Ndft/2;
+ }
+
+ /* Gnome sort the freq list */
+ /* My favorite sort of sort*/
+ i = 1;
+ while(i<M){
+ if(freqi[i] >= freqi[i-1]) i++;
+ else{
+ j = freqi[i];
+ freqi[i] = freqi[i-1];
+ freqi[i-1] = j;
+ if(i>1) i--;
+ }
+ }
+
+ /* Convert freqs from indices to frequencies */
+ for(i=0; i<M; i++){
+ freqs[i] = (float)(freqi[i])*((float)Fs/(float)Ndft);
+ }
+
+ /* Search for each tone method 2 - correlate with mask with non-zero entries at tone spacings ----- */
+
+ /* construct mask */
+ float mask[Ndft];
+ for(i=0; i<Ndft; i++) mask[i] = 0.0;
+ for(i=0;i<3; i++) mask[i] = 1.0;
+ int bin=0;
+ for(int m=1; m<=M-1; m++) {
+ bin = round((float)m*fsk->tone_spacing*Ndft/Fs)-1;
+ for(i=bin; i<=bin+2; i++) mask[i] = 1.0;
+ }
+ int len_mask = bin+2+1;
+
+ #ifdef MODEMPROBE_ENABLE
+ modem_probe_samp_f("t_mask",mask,len_mask);
+ #endif
+
+ /* drag mask over Sf, looking for peak in correlation */
+ int b_max = st; float corr_max = 0.0;
+ float *Sf = fsk->Sf;
+ for (int b=st; b<en-len_mask; b++) {
+ float corr = 0.0;
+ for(i=0; i<len_mask; i++)
+ corr += mask[i] * Sf[b+i];
+ if (corr > corr_max) {
+ corr_max = corr;
+ b_max = b;
+ }
+ }
+ float foff = (b_max-Ndft/2)*Fs/Ndft;
+ //fprintf(stderr, "fsk->tone_spacing: %d\n",fsk->tone_spacing);
+ for (int m=0; m<M; m++)
+ fsk->f2_est[m] = foff + m*fsk->tone_spacing;
+
+ #ifdef MODEMPROBE_ENABLE
+ modem_probe_samp_f("t_f2_est",fsk->f2_est,M);
+ #endif
+
+ free(fftin);
+ free(fftout);
+}
+
+/* core demodulator function */
+void fsk_demod_core(struct FSK *fsk, uint8_t rx_bits[], float rx_filt[], COMP fsk_in[]){
+ int N = fsk->N;
+ int Ts = fsk->Ts;
+ int Rs = fsk->Rs;
+ int Fs = fsk->Fs;
+ int nsym = fsk->Nsym;
+ int nin = fsk->nin;
+ int P = fsk->P;
+ int Nmem = fsk->Nmem;
+ int M = fsk->mode;
+ size_t i,j,m;
+ float ft1;
+
+ COMP t[M]; /* complex number temps */
+ COMP t_c; /* another complex temp */
+ COMP *phi_c = fsk->phi_c;
+ COMP *f_dc = fsk->f_dc;
+ COMP phi_ft;
+ int nold = Nmem-nin;
+
+ COMP dphift;
+ float rx_timing,norm_rx_timing,old_norm_rx_timing,d_norm_rx_timing,appm;
+
+ float fc_avg,fc_tx;
+ float meanebno,stdebno,eye_max;
+ int neyesamp,neyeoffset;
+
+ #ifdef MODEMPROBE_ENABLE
+ #define NMP_NAME 26
+ char mp_name_tmp[NMP_NAME+1]; /* Temporary string for modem probe trace names */
+ #endif
+
+ /* Estimate tone frequencies */
+ fsk_demod_freq_est(fsk,fsk_in,fsk->f_est,M);
+ #ifdef MODEMPROBE_ENABLE
+ modem_probe_samp_f("t_f_est",fsk->f_est,M);
+ #endif
+ float *f_est;
+ if (fsk->freq_est_type)
+ f_est = fsk->f2_est;
+ else
+ f_est = fsk->f_est;
+
+ /* update filter (integrator) memory by shifting in nin samples */
+ for(m=0; m<M; m++) {
+ for(i=0,j=Nmem-nold; i<nold; i++,j++)
+ f_dc[m*Nmem+i] = f_dc[m*Nmem+j];
+ }
+
+ /* freq shift down to around DC, ensuring continuous phase from last frame */
+ COMP dphi_m;
+ for(m=0; m<M; m++) {
+ dphi_m = comp_exp_j(2*M_PI*((f_est[m])/(float)(Fs)));
+ for(i=nold,j=0; i<Nmem; i++,j++) {
+ phi_c[m] = cmult(phi_c[m],dphi_m);
+ f_dc[m*Nmem+i] = cmult(fsk_in[j],cconj(phi_c[m]));
+ }
+ phi_c[m] = comp_normalize(phi_c[m]);
+ #ifdef MODEMPROBE_ENABLE
+ snprintf(mp_name_tmp,NMP_NAME,"t_f%zd_dc",m+1);
+ modem_probe_samp_c(mp_name_tmp,&f_dc[m*Nmem],Nmem);
+ #endif
+ }
+
+ /* integrate over symbol period at a variety of offsets */
+ COMP f_int[M][(nsym+1)*P];
+ for(i=0; i<(nsym+1)*P; i++) {
+ int st = i*Ts/P;
+ int en = st+Ts-1;
+ for(m=0; m<M; m++) {
+ f_int[m][i] = comp0();
+ for(j=st; j<=en; j++)
+ f_int[m][i] = cadd(f_int[m][i], f_dc[m*Nmem+j]);
+ }
+ }
+
+ #ifdef MODEMPROBE_ENABLE
+ for(m=0; m<M; m++) {
+ snprintf(mp_name_tmp,NMP_NAME,"t_f%zd_int",m+1);
+ modem_probe_samp_c(mp_name_tmp,&f_int[m][0],(nsym+1)*P);
+ }
+ #endif
+
+ /* Fine Timing Estimation */
+ /* Apply magic nonlinearity to f1_int and f2_int, shift down to 0,
+ * extract angle */
+
+ /* Figure out how much to spin the oscillator to extract magic spectral line */
+ dphift = comp_exp_j(2*M_PI*((float)(Rs)/(float)(P*Rs)));
+ phi_ft.real = 1;
+ phi_ft.imag = 0;
+ t_c=comp0();
+ for(i=0; i<(nsym+1)*P; i++){
+ /* Get abs^2 of fx_int[i], and add 'em */
+ ft1 = 0;
+ for( m=0; m<M; m++){
+ ft1 += (f_int[m][i].real*f_int[m][i].real) + (f_int[m][i].imag*f_int[m][i].imag);
+ }
+
+ /* Down shift and accumulate magic line */
+ t_c = cadd(t_c,fcmult(ft1,phi_ft));
+
+ /* Spin the oscillator for the magic line shift */
+ phi_ft = cmult(phi_ft,dphift);
+ }
+
+ /* Check for NaNs in the fine timing estimate, return if found */
+ /* otherwise segfaults happen */
+ if( isnan(t_c.real) || isnan(t_c.imag)){
+ return;
+ }
+
+ /* Get the magic angle */
+ norm_rx_timing = atan2f(t_c.imag,t_c.real)/(2*M_PI);
+ rx_timing = norm_rx_timing*(float)P;
+
+ old_norm_rx_timing = fsk->norm_rx_timing;
+ fsk->norm_rx_timing = norm_rx_timing;
+
+ /* Estimate sample clock offset */
+ d_norm_rx_timing = norm_rx_timing - old_norm_rx_timing;
+
+ /* Filter out big jumps in due to nin change */
+ if(fabsf(d_norm_rx_timing) < .2){
+ appm = 1e6*d_norm_rx_timing/(float)nsym;
+ fsk->ppm = .9*fsk->ppm + .1*appm;
+ }
+
+ /* Figure out how many samples are needed the next modem cycle */
+ /* Unless we're in burst mode or nin locked */
+ if(!fsk->burst_mode && !fsk->lock_nin) {
+ if(norm_rx_timing > 0.25)
+ fsk->nin = N+Ts/4;
+ else if(norm_rx_timing < -0.25)
+ fsk->nin = N-Ts/4;
+ else
+ fsk->nin = N;
+ }
+
+ modem_probe_samp_f("t_norm_rx_timing",&(norm_rx_timing),1);
+ modem_probe_samp_i("t_nin",&(fsk->nin),1);
+
+ /* Re-sample the integrators with linear interpolation magic */
+ int low_sample = (int)floorf(rx_timing);
+ float fract = rx_timing - (float)low_sample;
+ int high_sample = (int)ceilf(rx_timing);
+
+ /* Vars for finding the max-of-4 for each bit */
+ float tmax[M];
+
+ #ifdef EST_EBNO
+ meanebno = 0;
+ stdebno = 0;
+ #endif
+
+ float rx_nse_pow = 1E-12; float rx_sig_pow = 0.0;
+ for(i=0; i<nsym; i++) {
+
+ /* resample at ideal sampling instant */
+ int st = (i+1)*P;
+ for( m=0; m<M; m++) {
+ t[m] = fcmult(1-fract,f_int[m][st+low_sample]);
+ t[m] = cadd(t[m],fcmult( fract,f_int[m][st+high_sample]));
+ /* Figure mag^2 of each resampled fx_int */
+ tmax[m] = (t[m].real*t[m].real) + (t[m].imag*t[m].imag);
+ }
+
+ /* hard decision decoding of bits */
+ float max = tmax[0]; /* Maximum for figuring correct symbol */
+ float min = tmax[0];
+ int sym = 0; /* Index of maximum */
+ for( m=0; m<M; m++){
+ if(tmax[m]>max){
+ max = tmax[m];
+ sym = m;
+ }
+ if(tmax[m]<min){
+ min = tmax[m];
+ }
+ }
+
+ if(rx_bits != NULL){
+ /* Get bits for 2FSK and 4FSK */
+ if(M==2){
+ rx_bits[i] = sym==1; /* 2FSK. 1 bit per symbol */
+ }else if(M==4){
+ rx_bits[(i*2)+1] = (sym&0x1); /* 4FSK. 2 bits per symbol */
+ rx_bits[(i*2) ] = (sym&0x2)>>1;
+ }
+ }
+
+ /* Optionally output filter magnitudes for soft decision/LLR
+ calculation. Update SNRest always as this is a useful
+ alternative to the earlier EbNo estimator below */
+ float sum = 0.0;
+ for(m=0; m<M; m++) {
+ if (rx_filt != NULL) rx_filt[m*nsym+i] = sqrtf(tmax[m]);
+ sum += tmax[m];
+ }
+ rx_sig_pow += max;
+ rx_nse_pow += (sum-max)/(M-1);
+
+ /* Accumulate resampled int magnitude for EbNodB estimation */
+ /* Standard deviation is calculated by algorithm devised by crafty soviets */
+ #ifdef EST_EBNO
+ /* Accumulate the square of the sampled value */
+ ft1 = max;
+ stdebno += ft1;
+
+ /* Figure the abs value of the max tone */
+ meanebno += sqrtf(ft1);
+ #endif
+ }
+
+ fsk->rx_sig_pow = rx_sig_pow = rx_sig_pow/nsym;
+ fsk->rx_nse_pow = rx_nse_pow = rx_nse_pow/nsym;
+ fsk->v_est = sqrt(rx_sig_pow-rx_nse_pow);
+ fsk->SNRest = rx_sig_pow/rx_nse_pow;
+
+ #ifdef EST_EBNO
+ /* Calculate mean for EbNodB estimation */
+ meanebno = meanebno/(float)nsym;
+
+ /* Calculate the std. dev for EbNodB estimate */
+ stdebno = (stdebno/(float)nsym) - (meanebno*meanebno);
+ /* trap any negative numbers to avoid NANs flowing through */
+ if (stdebno > 0.0) {
+ stdebno = sqrt(stdebno);
+ } else {
+ stdebno = 0.0;
+ }
+
+ fsk->EbNodB = -6+(20*log10f((1e-6+meanebno)/(1e-6+stdebno)));
+ #else
+ fsk->EbNodB = 1;
+ #endif
+
+ /* Write some statistics to the stats struct */
+
+ /* Save clock offset in ppm */
+ fsk->stats->clock_offset = fsk->ppm;
+
+ /* Calculate and save SNR from EbNodB estimate */
+
+ fsk->stats->snr_est = .5*fsk->stats->snr_est + .5*fsk->EbNodB;//+ 10*log10f(((float)Rs)/((float)Rs*M));
+
+ /* Save rx timing */
+ fsk->stats->rx_timing = (float)rx_timing;
+
+ /* Estimate and save frequency offset */
+ fc_avg = fc_tx = 0.0;
+ for(int m=0; m<M; m++) {
+ fc_avg += f_est[m]/M;
+ fc_tx += (fsk->f1_tx + m*fsk->tone_spacing)/M;
+ }
+ fsk->stats->foff = fc_avg-fc_tx;
+
+ /* Take a sample for the eye diagrams ---------------------------------- */
+
+ /* due to oversample rate P, we have too many samples for eye
+ trace. So lets output a decimated version. We use 2P
+ as we want two symbols worth of samples in trace */
+#ifndef __EMBEDDED__
+ int neyesamp_dec = ceil(((float)P*2)/MODEM_STATS_EYE_IND_MAX);
+ neyesamp = (P*2)/neyesamp_dec;
+ assert(neyesamp <= MODEM_STATS_EYE_IND_MAX);
+ fsk->stats->neyesamp = neyesamp;
+
+ neyeoffset = high_sample+1;
+
+ int eye_traces = MODEM_STATS_ET_MAX/M;
+ int ind;
+
+ fsk->stats->neyetr = fsk->mode*eye_traces;
+ for( i=0; i<eye_traces; i++){
+ for ( m=0; m<M; m++){
+ for(j=0; j<neyesamp; j++) {
+ /*
+ 2*P*i...........: advance two symbols for next trace
+ neyeoffset......: centre trace on ideal timing offset, peak eye opening
+ j*neweyesamp_dec: For 2*P>MODEM_STATS_EYE_IND_MAX advance through integrated
+ samples newamp_dec at a time so we dont overflow rx_eye[][]
+ */
+ ind = 2*P*(i+1) + neyeoffset + j*neyesamp_dec;
+ assert((i*M+m) < MODEM_STATS_ET_MAX);
+ assert(ind >= 0);
+ assert(ind < (nsym+1)*P);
+ fsk->stats->rx_eye[i*M+m][j] = cabsolute(f_int[m][ind]);
+ }
+ }
+ }
+
+ if (fsk->normalise_eye) {
+ eye_max = 0;
+ /* Normalize eye to +/- 1 */
+ for(i=0; i<M*eye_traces; i++)
+ for(j=0; j<neyesamp; j++)
+ if(fabsf(fsk->stats->rx_eye[i][j])>eye_max)
+ eye_max = fabsf(fsk->stats->rx_eye[i][j]);
+
+ for(i=0; i<M*eye_traces; i++)
+ for(j=0; j<neyesamp; j++)
+ fsk->stats->rx_eye[i][j] = fsk->stats->rx_eye[i][j]/eye_max;
+ }
+
+ fsk->stats->nr = 0;
+ fsk->stats->Nc = 0;
+
+ for(i=0; i<M; i++)
+ fsk->stats->f_est[i] = f_est[i];
+#endif // !__EMBEDDED__
+
+ /* Dump some internal samples */
+ modem_probe_samp_f("t_EbNodB",&(fsk->EbNodB),1);
+ modem_probe_samp_f("t_ppm",&(fsk->ppm),1);
+ modem_probe_samp_f("t_rx_timing",&(rx_timing),1);
+}
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: fsk_demod
+ AUTHOR......: Brady O'Brien
+ DATE CREATED: 11 February 2016
+
+ FSK demodulator functions:
+
+ fsk_demod...: complex samples in, bits out
+ fsk_demos_sd: complex samples in, soft decision symbols out
+
+\*---------------------------------------------------------------------------*/
+
+void fsk_demod(struct FSK *fsk, uint8_t rx_bits[], COMP fsk_in[]) {
+ fsk_demod_core(fsk,rx_bits,NULL,fsk_in);
+}
+
+void fsk_demod_sd(struct FSK *fsk, float rx_filt[], COMP fsk_in[]){
+ fsk_demod_core(fsk,NULL,rx_filt,fsk_in);
+}
+
+/* make sure stats have known values in case monitoring process reads stats before they are set */
+static void stats_init(struct FSK *fsk) {
+ /* Take a sample for the eye diagrams */
+ int i,j,m;
+ int P = fsk->P;
+ int M = fsk->mode;
+
+ /* due to oversample rate P, we have too many samples for eye
+ trace. So lets output a decimated version */
+
+ /* asserts below as we found some problems over-running eye matrix */
+
+ /* TODO: refactor eye tracing code here and in fsk_demod */
+#ifndef __EMBEDDED__
+ int neyesamp_dec = ceil(((float)P*2)/MODEM_STATS_EYE_IND_MAX);
+ int neyesamp = (P*2)/neyesamp_dec;
+ assert(neyesamp <= MODEM_STATS_EYE_IND_MAX);
+ fsk->stats->neyesamp = neyesamp;
+
+ int eye_traces = MODEM_STATS_ET_MAX/M;
+
+ fsk->stats->neyetr = fsk->mode*eye_traces;
+ for(i=0; i<eye_traces; i++) {
+ for (m=0; m<M; m++){
+ for(j=0; j<neyesamp; j++) {
+ assert((i*M+m) < MODEM_STATS_ET_MAX);
+ fsk->stats->rx_eye[i*M+m][j] = 0;
+ }
+ }
+ }
+#endif // !__EMBEDDED__
+
+ fsk->stats->rx_timing = fsk->stats->snr_est = 0;
+
+}
+
+
+/* Set the FSK modem into burst demod mode */
+
+void fsk_enable_burst_mode(struct FSK *fsk){
+ fsk->nin = fsk->N;
+ fsk->burst_mode = 1;
+}
+
+void fsk_clear_estimators(struct FSK *fsk){
+ int i;
+ /* Clear freq estimator state */
+ for(i=0; i < (fsk->Ndft); i++){
+ fsk->Sf[i] = 0;
+ }
+ /* Reset timing diff correction */
+ fsk->nin = fsk->N;
+}
+
+void fsk_get_demod_stats(struct FSK *fsk, struct MODEM_STATS *stats){
+ /* copy from internal stats, note we can't overwrite stats completely
+ as it has other states rqd by caller, also we want a consistent
+ interface across modem types for the freedv_api.
+ */
+
+ stats->clock_offset = fsk->stats->clock_offset;
+ stats->snr_est = fsk->stats->snr_est; // TODO: make this SNR not Eb/No
+ stats->rx_timing = fsk->stats->rx_timing;
+ stats->foff = fsk->stats->foff;
+#ifndef __EMBEDDED__
+ stats->neyesamp = fsk->stats->neyesamp;
+ stats->neyetr = fsk->stats->neyetr;
+ memcpy(stats->rx_eye, fsk->stats->rx_eye, sizeof(stats->rx_eye));
+ memcpy(stats->f_est, fsk->stats->f_est, fsk->mode*sizeof(float));
+#endif // !__EMBEDDED__
+
+ /* these fields not used for FSK so set to something sensible */
+
+ stats->sync = 0;
+ stats->nr = fsk->stats->nr;
+ stats->Nc = fsk->stats->Nc;
+}
+
+/*
+ * Set the minimum and maximum frequencies at which the freq. estimator can find tones
+ */
+void fsk_set_freq_est_limits(struct FSK *fsk, int est_min, int est_max){
+ assert(fsk != NULL);
+ assert(est_min >= -fsk->Fs/2);
+ assert(est_max <= fsk->Fs/2);
+ assert(est_max > est_min);
+ fsk->est_min = est_min;
+ fsk->est_max = est_max;
+}
+
+void fsk_stats_normalise_eye(struct FSK *fsk, int normalise_enable) {
+ assert(fsk != NULL);
+ fsk->normalise_eye = normalise_enable;
+}
+
+void fsk_set_freq_est_alg(struct FSK *fsk, int est_type) {
+ assert(fsk != NULL);
+ fsk->freq_est_type = est_type;
+}
+
+
+
+
+
+
+