1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-23 01:55:48 -05:00
sdrangel/libfreedv/fsk.cpp

1252 lines
37 KiB
C++

/*---------------------------------------------------------------------------*\
FILE........: fsk.c
AUTHOR......: Brady O'Brien
DATE CREATED: 7 January 2016
C Implementation of 2/4FSK modulator/demodulator, based on octave/fsk_horus.m
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2016 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
\*---------------------------------------------------------------------------*/
/* P oversampling rate constant -- should probably be init-time configurable */
#define horus_P 8
/* 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"
namespace FreeDV
{
/*---------------------------------------------------------------------------*\
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;
int i;
/* Set up complex oscilator to calculate hann function */
COMP dphi = comp_exp_j((2*M_PI)/((float)Ndft-1));
COMP rphi = {.5,0};
rphi = cmult(cconj(dphi),rphi);
for (i = 0; i < Ndft; i++)
{
rphi = cmult(dphi,rphi);
float hannc = .5-rphi.real;
//float hann = .5-(.5*cosf((2*M_PI*(float)(i))/((float)Ndft-1)));
fsk->hann_table[i] = hannc;
}
}
#endif
/*---------------------------------------------------------------------------*\
FUNCTION....: fsk_create_hbr
AUTHOR......: Brady O'Brien
DATE CREATED: 11 February 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. returns NULL on failure.
\*---------------------------------------------------------------------------*/
struct FSK * fsk_create_hbr(int Fs, int Rs,int P,int M, int tx_f1, int tx_fs)
{
struct FSK *fsk;
int i;
int memold;
int Ndft = 0;
/* Number of symbols in a processing frame */
int nsyms = 48;
/* Check configuration validity */
assert(Fs > 0 );
assert(Rs > 0 );
assert(tx_f1 > 0);
assert(tx_fs > 0);
assert(P > 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 );
assert( M==2 || M==4);
fsk = (struct FSK*) malloc(sizeof(struct FSK));
if(fsk == NULL) return NULL;
/* Set constant config parameters */
fsk->Fs = Fs;
fsk->Rs = Rs;
fsk->Ts = Fs/Rs;
fsk->burst_mode = 0;
fsk->N = fsk->Ts*nsyms;
fsk->P = P;
fsk->Nsym = nsyms;
fsk->Nmem = fsk->N+(2*fsk->Ts);
fsk->f1_tx = tx_f1;
fsk->fs_tx = tx_fs;
fsk->nin = fsk->N;
fsk->mode = M==2 ? MODE_2FSK : MODE_4FSK;
fsk->Nbits = M==2 ? fsk->Nsym : fsk->Nsym*2;
/* Find smallest 2^N value that fits Fs for efficient FFT */
/* It would probably be better to use KISS-FFt's routine here */
for(i=1; i; i<<=1)
if((fsk->N)&i)
Ndft = i;
fsk->Ndft = Ndft;
fsk->est_min = Rs/4;
if(fsk->est_min<0) fsk->est_min = 0;
fsk->est_max = (Fs/2)-Rs/4;
fsk->est_space = Rs-(Rs/5);
/* Set up rx state */
for( i=0; i<M; i++)
fsk->phi_c[i] = comp_exp_j(0);
memold = (4*fsk->Ts);
fsk->nstash = memold;
fsk->samp_old = (COMP*) malloc(sizeof(COMP)*memold);
if(fsk->samp_old == NULL){
free(fsk);
return NULL;
}
for(i=0;i<memold;i++) {
fsk->samp_old[i].real = 0;
fsk->samp_old[i].imag = 0;
}
fsk->fft_cfg = kiss_fft_alloc(fsk->Ndft,0,NULL,NULL);
if(fsk->fft_cfg == NULL){
free(fsk->samp_old);
free(fsk);
return NULL;
}
fsk->fft_est = (float*)malloc(sizeof(float)*fsk->Ndft/2);
if(fsk->fft_est == NULL){
free(fsk->samp_old);
free(fsk->fft_cfg);
free(fsk);
return NULL;
}
#ifdef USE_HANN_TABLE
#ifdef GENERATE_HANN_TABLE_RUNTIME
fsk->hann_table = (float*)malloc(sizeof(float)*fsk->Ndft);
if(fsk->hann_table == NULL){
free(fsk->fft_est);
free(fsk->samp_old);
free(fsk->fft_cfg);
free(fsk);
return NULL;
}
fsk_generate_hann_table(fsk);
#else
fsk->hann_table = NULL;
#endif
#endif
for(i=0;i<fsk->Ndft/2;i++)fsk->fft_est[i] = 0;
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));
if(fsk->stats == NULL){
free(fsk->fft_est);
free(fsk->samp_old);
free(fsk->fft_cfg);
free(fsk);
return NULL;
}
stats_init(fsk);
fsk->normalise_eye = 1;
return fsk;
}
#define HORUS_MIN 800
#define HORUS_MAX 2500
#define HORUS_MIN_SPACING 100
/*---------------------------------------------------------------------------*\
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. returns NULL on failure.
\*---------------------------------------------------------------------------*/
struct FSK * fsk_create(int Fs, int Rs,int M, int tx_f1, int tx_fs)
{
struct FSK *fsk;
int i;
int Ndft = 0;
int memold;
/* Check configuration validity */
assert(Fs > 0 );
assert(Rs > 0 );
assert(tx_f1 > 0);
assert(tx_fs > 0);
assert(horus_P > 0);
/* Ts (Fs/Rs) must be an integer */
assert( (Fs%Rs) == 0 );
/* Ts/P (Fs/Rs/P) must be an integer */
assert( ((Fs/Rs)%horus_P) == 0 );
assert( M==2 || M==4);
fsk = (struct FSK*) malloc(sizeof(struct FSK));
if(fsk == NULL) return NULL;
Ndft = 1024;
/* Set constant config parameters */
fsk->Fs = Fs;
fsk->Rs = Rs;
fsk->Ts = Fs/Rs;
fsk->N = Fs;
fsk->burst_mode = 0;
fsk->P = horus_P;
fsk->Nsym = fsk->N/fsk->Ts;
fsk->Ndft = Ndft;
fsk->Nmem = fsk->N+(2*fsk->Ts);
fsk->f1_tx = tx_f1;
fsk->fs_tx = tx_fs;
fsk->nin = fsk->N;
fsk->mode = M==2 ? MODE_2FSK : MODE_4FSK;
fsk->Nbits = M==2 ? fsk->Nsym : fsk->Nsym*2;
fsk->est_min = HORUS_MIN;
fsk->est_max = HORUS_MAX;
fsk->est_space = HORUS_MIN_SPACING;
/* Set up rx state */
for( i=0; i<M; i++)
fsk->phi_c[i] = comp_exp_j(0);
memold = (4*fsk->Ts);
fsk->nstash = memold;
fsk->samp_old = (COMP*) malloc(sizeof(COMP)*memold);
if(fsk->samp_old == NULL){
free(fsk);
return NULL;
}
for(i=0;i<memold;i++) {
fsk->samp_old[i].real = 0.0;
fsk->samp_old[i].imag = 0.0;
}
fsk->fft_cfg = kiss_fft_alloc(Ndft,0,NULL,NULL);
if(fsk->fft_cfg == NULL){
free(fsk->samp_old);
free(fsk);
return NULL;
}
fsk->fft_est = (float*)malloc(sizeof(float)*fsk->Ndft/2);
if(fsk->fft_est == NULL){
free(fsk->samp_old);
free(fsk->fft_cfg);
free(fsk);
return NULL;
}
#ifdef USE_HANN_TABLE
#ifdef GENERATE_HANN_TABLE_RUNTIME
fsk->hann_table = (float*)malloc(sizeof(float)*fsk->Ndft);
if(fsk->hann_table == NULL){
free(fsk->fft_est);
free(fsk->samp_old);
free(fsk->fft_cfg);
free(fsk);
return NULL;
}
fsk_generate_hann_table(fsk);
#else
fsk->hann_table = NULL;
#endif
#endif
for(i=0;i<Ndft/2;i++)fsk->fft_est[i] = 0;
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));
if(fsk->stats == NULL){
free(fsk->fft_est);
free(fsk->samp_old);
free(fsk->fft_cfg);
free(fsk);
return NULL;
}
stats_init(fsk);
fsk->normalise_eye = 1;
return fsk;
}
/* 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 */
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;
}
}
}
fsk->stats->rx_timing = fsk->stats->snr_est = 0;
}
void fsk_set_nsym(struct FSK *fsk,int nsyms){
assert(nsyms>0);
int Ndft,i;
Ndft = 0;
/* Set constant config parameters */
fsk->N = fsk->Ts*nsyms;
fsk->Nsym = nsyms;
fsk->Nmem = fsk->N+(2*fsk->Ts);
fsk->nin = fsk->N;
fsk->Nbits = fsk->mode==2 ? fsk->Nsym : fsk->Nsym*2;
/* Find smallest 2^N value that fits Fs for efficient FFT */
/* It would probably be better to use KISS-FFt's routine here */
for(i=1; i; i<<=1)
if((fsk->N)&i)
Ndft = i;
fsk->Ndft = Ndft;
free(fsk->fft_cfg);
free(fsk->fft_est);
fsk->fft_cfg = kiss_fft_alloc(Ndft,0,NULL,NULL);
fsk->fft_est = (float*)malloc(sizeof(float)*fsk->Ndft/2);
for(i=0;i<Ndft/2;i++)fsk->fft_est[i] = 0;
}
/* Set the FSK modem into burst demod mode */
void fsk_enable_burst_mode(struct FSK *fsk,int nsyms){
fsk_set_nsym(fsk,nsyms);
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/2); i++){
fsk->fft_est[i] = 0;
}
/* Reset timing diff correction */
fsk->nin = fsk->N;
}
uint32_t fsk_nin(struct FSK *fsk){
return (uint32_t)fsk->nin;
}
void fsk_destroy(struct FSK *fsk){
free(fsk->fft_cfg);
free(fsk->samp_old);
free(fsk->stats);
free(fsk);
}
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;
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));
/* 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_est_limits(struct FSK *fsk,int est_min, int est_max){
fsk->est_min = est_min;
if(fsk->est_min<0) fsk->est_min = 0;
fsk->est_max = est_max;
}
/*
* Internal function to estimate the frequencies of the two tones within a block of samples.
* 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;
int i,j;
float hann;
float max;
float tc;
int imax;
kiss_fft_cfg fft_cfg = fsk->fft_cfg;
int *freqi = new int[M];
int f_min,f_max,f_zero;
/* Array to do complex FFT from using kiss_fft */
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);
#ifndef USE_HANN_TABLE
COMP dphi = comp_exp_j((2*M_PI)/((float)Ndft-1));
COMP rphi = {.5,0};
rphi = cmult(cconj(dphi),rphi);
#endif
f_min = (fsk->est_min*Ndft)/Fs;
f_max = (fsk->est_max*Ndft)/Fs;
f_zero = (fsk->est_space*Ndft)/Fs;
/* scale averaging time constant based on number of samples */
tc = 0.95*Ndft/Fs;
int samps;
int fft_samps;
int fft_loops = nin / Ndft;
for (j = 0; j < fft_loops; j++)
{
/* 48000 sample rate (for example) will have a spare */
/* 896 samples besides the 46 "Ndft" samples, so adjust */
samps = (nin - ((j + 1) * Ndft));
fft_samps = (samps >= Ndft) ? Ndft : samps;
/* Copy FSK buffer into reals of FFT buffer and apply a hann window */
for(i=0; i<fft_samps; i++){
#ifdef USE_HANN_TABLE
hann = fsk->hann_table[i];
#else
//hann = 1-cosf((2*M_PI*(float)(i))/((float)fft_samps-1));
rphi = cmult(dphi,rphi);
hann = .5-rphi.real;
#endif
fftin[i].r = hann*fsk_in[i+Ndft*j].real;
fftin[i].i = hann*fsk_in[i+Ndft*j].imag;
}
/* Zero out the remaining slots on spare samples */
for(; i<Ndft;i++){
fftin[i].r = 0;
fftin[i].i = 0;
}
/* Do the FFT */
kiss_fft(fft_cfg,fftin,fftout);
/* Find the magnitude^2 of each freq slot and stash away in the real
* value, so this only has to be done once. Since we're only comparing
* these values and only need the mag of 2 points, we don't need to do
* a sqrt to each value */
for(i=0; i<Ndft/2; i++){
fftout[i].r = (fftout[i].r*fftout[i].r) + (fftout[i].i*fftout[i].i) ;
}
/* Zero out the minimum and maximum ends */
for(i=0; i<f_min; i++){
fftout[i].r = 0;
}
for(i=f_max-1; i<Ndft/2; i++){
fftout[i].r = 0;
}
/* Mix back in with the previous fft block */
/* Copy new fft est into imag of fftout for frequency divination below */
for(i=0; i<Ndft/2; i++){
fsk->fft_est[i] = (fsk->fft_est[i]*(1-tc)) + (sqrtf(fftout[i].r)*tc);
fftout[i].i = fsk->fft_est[i];
}
}
modem_probe_samp_f("t_fft_est", fsk->fft_est, Ndft/2);
max = 0;
/* Find the M frequency peaks here */
for(i=0; i<M; i++){
imax = 0;
max = 0;
for(j=0;j<Ndft/2;j++){
if(fftout[j].i > max){
max = fftout[j].i;
imax = j;
}
}
/* Blank out FMax +/-Fspace/2 */
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;
}
/* 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);
}
delete[] freqi;
free(fftin);
free(fftout);
}
void fsk2_demod(struct FSK *fsk, uint8_t rx_bits[], float rx_sd[], 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;
int i, j, m, dc_i, cbuf_i;
float ft1;
int nstash = fsk->nstash;
COMP* *f_int = new COMP*[M]; /* Filtered and downsampled symbol tones */
COMP *t = new COMP[M]; /* complex number temps */
COMP t_c; /* another complex temp */
COMP *phi_c = new COMP[M];
COMP phi_ft;
int nold = Nmem-nin;
COMP *dphi = new COMP[M];
COMP dphift;
float rx_timing,norm_rx_timing,old_norm_rx_timing,d_norm_rx_timing,appm;
int using_old_samps;
COMP* sample_src;
COMP* f_intbuf_m;
float *f_est = new float[M];
float fc_avg, fc_tx;
float meanebno,stdebno,eye_max;
int neyesamp,neyeoffset;
#ifdef MODEMPROBE_ENABLE
char mp_name_tmp[20]; /* Temporary string for modem probe trace names */
#endif
//for(size_t jj = 0; jj<nin; jj++){
// fprintf(stderr,"%f,j%f,",fsk_in[jj].real,fsk_in[jj].imag);
//}
/* Load up demod phases from struct */
for(m = 0; m < M; m++) {
phi_c[m] = fsk->phi_c[m];
}
/* Estimate tone frequencies */
fsk_demod_freq_est(fsk,fsk_in,f_est,M);
modem_probe_samp_f("t_f_est",f_est,M);
/* Allocate circular buffer for integration */
f_intbuf_m = (COMP*) malloc(sizeof(COMP)*Ts);
/* allocate memory for the integrated samples */
for( m=0; m<M; m++){
/* allocate memory for the integrated samples */
/* Note: This must be kept after fsk_demod_freq_est for memory usage reasons */
f_int[m] = (COMP*) malloc(sizeof(COMP)*(nsym+1)*P);
}
/* If this is the first run, we won't have any valid f_est */
/* TODO: add first_run flag to FSK to make negative freqs possible */
if(fsk->f_est[0]<1){
for( m=0; m<M; m++)
fsk->f_est[m] = f_est[m];
}
/* Initalize downmixers for each symbol tone */
for( m=0; m<M; m++){
/* Back the stored phase off to account for re-integraton of old samples */
dphi[m] = comp_exp_j(-2*(Nmem-nin-(Ts/P))*M_PI*((fsk->f_est[m])/(float)(Fs)));
phi_c[m] = cmult(dphi[m],phi_c[m]);
//fprintf(stderr,"F%d = %f",m,fsk->f_est[m]);
/* Figure out how much to nudge each sample downmixer for every sample */
dphi[m] = comp_exp_j(2*M_PI*((fsk->f_est[m])/(float)(Fs)));
}
/* Integrate and downsample for symbol tones */
for(m=0; m<M; m++){
/* Copy buffer pointers in to avoid second buffer indirection */
float f_est_m = f_est[m];
COMP* f_int_m = &(f_int[m][0]);
COMP dphi_m = dphi[m];
dc_i = 0;
cbuf_i = 0;
sample_src = &(fsk->samp_old[nstash-nold]);
using_old_samps = 1;
/* Pre-fill integration buffer */
for(dc_i=0; dc_i<Ts-(Ts/P); dc_i++){
/* Switch sample source to new samples when we run out of old ones */
if(dc_i>=nold && using_old_samps){
sample_src = &fsk_in[0];
dc_i = 0;
using_old_samps = 0;
/* Recalculate delta-phi after switching to new sample source */
phi_c[m] = comp_normalize(phi_c[m]);
dphi_m = comp_exp_j(2*M_PI*((f_est_m)/(float)(Fs)));
}
/* Downconvert and place into integration buffer */
f_intbuf_m[dc_i]=cmult(sample_src[dc_i],cconj(phi_c[m]));
#ifdef MODEMPROBE_ENABLE
snprintf(mp_name_tmp,19,"t_f%zd_dc",m+1);
modem_probe_samp_c(mp_name_tmp,&f_intbuf_m[dc_i],1);
#endif
/* Spin downconversion phases */
phi_c[m] = cmult(phi_c[m],dphi_m);
}
cbuf_i = dc_i;
/* Integrate over Ts at offsets of Ts/P */
for(i=0; i<(nsym+1)*P; i++){
/* Downconvert and Place Ts/P samples in the integration buffers */
for(j=0; j<(Ts/P); j++,dc_i++){
/* Switch sample source to new samples when we run out of old ones */
if(dc_i>=nold && using_old_samps){
sample_src = &fsk_in[0];
dc_i = 0;
using_old_samps = 0;
/* Recalculate delta-phi after switching to new sample source */
phi_c[m] = comp_normalize(phi_c[m]);
dphi_m = comp_exp_j(2*M_PI*((f_est_m)/(float)(Fs)));
}
/* Downconvert and place into integration buffer */
f_intbuf_m[cbuf_i+j]=cmult(sample_src[dc_i],cconj(phi_c[m]));
#ifdef MODEMPROBE_ENABLE
snprintf(mp_name_tmp,19,"t_f%zd_dc",m+1);
modem_probe_samp_c(mp_name_tmp,&f_intbuf_m[cbuf_i+j],1);
#endif
/* Spin downconversion phases */
phi_c[m] = cmult(phi_c[m],dphi_m);
}
/* Dump internal samples */
cbuf_i += Ts/P;
if(cbuf_i>=Ts) cbuf_i = 0;
/* Integrate over the integration buffers, save samples */
float it_r = 0;
float it_i = 0;
for(j=0; j<Ts; j++){
it_r += f_intbuf_m[j].real;
it_i += f_intbuf_m[j].imag;
}
f_int_m[i].real = it_r;
f_int_m[i].imag = it_i;
}
}
/* Save phases back into FSK struct */
for(m=0; m<M; m++){
fsk->phi_c[m] = phi_c[m];
fsk->f_est[m] = f_est[m];
}
/* Stash samples away in the old sample buffer for the next round of bit getting */
memcpy((void*)&(fsk->samp_old[0]),(void*)&(fsk_in[nin-nstash]),sizeof(COMP)*nstash);
/* 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 */
if(!fsk->burst_mode){
if(norm_rx_timing > 0.25)
fsk->nin = N+Ts/2;
else if(norm_rx_timing < -0.25)
fsk->nin = N-Ts/2;
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 = new float[M];
#ifdef EST_EBNO
meanebno = 0;
stdebno = 0;
#endif
/* FINALLY, THE BITS */
/* also, resample fx_int */
for(i = 0; i < nsym; i++)
{
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);
}
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];
}
}
/* Get the actual bit */
if(rx_bits != NULL){
/* Get bits for 2FSK and 4FSK */
/* TODO: Replace this with something more generic maybe */
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;
}
}
/* Produce soft decision symbols */
if(rx_sd != NULL){
/* Convert symbols from max^2 into max */
for( m=0; m<M; m++)
tmax[m] = sqrtf(tmax[m]);
if(M==2){
rx_sd[i] = tmax[0] - tmax[1];
}else if(M==4){
/* TODO: Find a soft-decision mode that works for 4FSK */
min = sqrtf(min);
rx_sd[(i*2)+1] = - tmax[0] ; /* Bits=00 */
rx_sd[(i*2) ] = - tmax[0] ;
rx_sd[(i*2)+1]+= tmax[1] ; /* Bits=01 */
rx_sd[(i*2) ]+= - tmax[1] ;
rx_sd[(i*2)+1]+= - tmax[2] ; /* Bits=10 */
rx_sd[(i*2) ]+= tmax[2] ;
rx_sd[(i*2)+1]+= tmax[3] ; /* Bits=11 */
rx_sd[(i*2) ]+= tmax[3] ;
}
}
/* 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
/* Soft output goes here */
}
delete[] tmax;
#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 = (f_est[0]+f_est[1])/2;
fc_tx = (fsk->f1_tx+fsk->f1_tx+fsk->fs_tx)/2;
fsk->stats->foff = fc_tx-fc_avg;
/* 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 */
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;
#ifdef I_DONT_UNDERSTAND
neyeoffset = high_sample+1+(P*28); /* WTF this line? Where does "28" come from ? */
#endif /* ifdef-ed out as I am afraid it will index out of memory as P changes */
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 + neyeoffset + j*neyesamp_dec;
assert((i*M+m) < MODEM_STATS_ET_MAX);
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];
}
/* 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);
#ifdef MODEMPROBE_ENABLE
for( m=0; m<M; m++){
snprintf(mp_name_tmp,19,"t_f%zd_int",m+1);
modem_probe_samp_c(mp_name_tmp,f_int[m],(nsym+1)*P);
snprintf(mp_name_tmp,19,"t_f%zd",m+1);
modem_probe_samp_f(mp_name_tmp,&f_est[m],1);
}
#endif
for( m=0; m<M; m++){
free(f_int[m]);
}
free(f_intbuf_m);
delete[] f_est;
delete[] dphi;
delete[] phi_c;
delete[] t;
delete[] f_int;
}
void fsk_demod(struct FSK *fsk, uint8_t rx_bits[], COMP fsk_in[]){
fsk2_demod(fsk,rx_bits,NULL,fsk_in);
}
void fsk_demod_sd(struct FSK *fsk, float rx_sd[], COMP fsk_in[]){
fsk2_demod(fsk,NULL,rx_sd,fsk_in);
}
void fsk_mod(struct FSK *fsk,float fsk_out[],uint8_t tx_bits[]){
COMP tx_phase_c = fsk->tx_phase_c; /* Current complex TX phase */
int f1_tx = fsk->f1_tx; /* '0' frequency */
int fs_tx = fsk->fs_tx; /* space between frequencies */
int Ts = fsk->Ts; /* samples-per-symbol */
int Fs = fsk->Fs; /* sample freq */
int M = fsk->mode;
COMP *dosc_f = new COMP[M]; /* phase shift per sample */
COMP dph; /* phase shift of current bit */
int i, j, m, bit_i, sym;
/* 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+(fs_tx*m))/(float)(Fs)));
}
bit_i = 0;
for( i=0; i<fsk->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;
delete[] dosc_f;
}
void fsk_mod_c(struct FSK *fsk,COMP fsk_out[],uint8_t tx_bits[]){
COMP tx_phase_c = fsk->tx_phase_c; /* Current complex TX phase */
int f1_tx = fsk->f1_tx; /* '0' frequency */
int fs_tx = fsk->fs_tx; /* space between frequencies */
int Ts = fsk->Ts; /* samples-per-symbol */
int Fs = fsk->Fs; /* sample freq */
int M = fsk->mode;
COMP *dosc_f = new COMP[M]; /* phase shift per sample */
COMP dph; /* phase shift of current bit */
int i, j, bit_i, sym;
int m;
/* 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+(fs_tx*m))/(float)(Fs)));
}
bit_i = 0;
for( i=0; i<fsk->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] = fcmult(2,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;
delete[] dosc_f;
}
/* 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 f1_tx = fsk->f1_tx; /* '0' frequency */
int fs_tx = fsk->fs_tx; /* space between frequencies */
int Ts = fsk->Ts; /* samples-per-symbol */
int M = fsk->mode;
int i, j, m, sym, bit_i;
bit_i = 0;
for(i=0; i<fsk->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 + fs_tx*(float)sym);
for(j=0; j<Ts; j++) {
vco_out[i*Ts+j] = f1_tx + fs_tx*(float)sym;
}
}
}
void fsk_stats_normalise_eye(struct FSK *fsk, int normalise_enable) {
fsk->normalise_eye = normalise_enable;
}
} // freeDV