mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-30 20:40:20 -04:00 
			
		
		
		
	
		
			
	
	
		
			374 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			374 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | /*---------------------------------------------------------------------------*\
 | ||
|  | 
 | ||
|  |   FILE........: fmfsk.c | ||
|  |   AUTHOR......: Brady O'Brien | ||
|  |   DATE CREATED: 6 February 2016 | ||
|  | 
 | ||
|  |   C Implementation of a FM+ME+FSK modem for FreeDV mode B and other applications | ||
|  |   (better APRS, anyone?) | ||
|  | 
 | ||
|  | \*---------------------------------------------------------------------------*/ | ||
|  | 
 | ||
|  | /*
 | ||
|  |   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/>.
 | ||
|  | */ | ||
|  | 
 | ||
|  | #include <assert.h>
 | ||
|  | #include <stdint.h>
 | ||
|  | #include <stdlib.h>
 | ||
|  | #include <math.h>
 | ||
|  | #include <string.h>
 | ||
|  | #include <stdio.h>
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #include "fmfsk.h"
 | ||
|  | #include "modem_probe.h"
 | ||
|  | #include "comp_prim.h"
 | ||
|  | 
 | ||
|  | #define STD_PROC_BITS 96
 | ||
|  | 
 | ||
|  | namespace FreeDV | ||
|  | { | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * Create a new fmfsk modem instance. | ||
|  |  * | ||
|  |  * int Fs - sample rate | ||
|  |  * int Rb - non-manchester bitrate | ||
|  |  * returns - new struct FMFSK on sucess, NULL on failure | ||
|  |  */ | ||
|  | struct FMFSK * fmfsk_create(int Fs,int Rb){ | ||
|  |     assert( Fs % (Rb*2) == 0 );  /* Sample freq must be divisible by symbol rate */ | ||
|  | 
 | ||
|  |     int nbits = STD_PROC_BITS; | ||
|  | 
 | ||
|  |     /* Allocate the struct */ | ||
|  |     struct FMFSK *fmfsk = (struct FMFSK *) malloc(sizeof(struct FMFSK)); | ||
|  |     if(fmfsk==NULL) return NULL; | ||
|  | 
 | ||
|  |     /* Set up static parameters */ | ||
|  |     fmfsk->Rb = Rb; | ||
|  |     fmfsk->Rs = Rb*2; | ||
|  |     fmfsk->Fs = Fs; | ||
|  |     fmfsk->Ts = Fs/fmfsk->Rs; | ||
|  |     fmfsk->N = nbits*2*fmfsk->Ts; | ||
|  |     fmfsk->nmem = fmfsk->N+(fmfsk->Ts*4); | ||
|  |     fmfsk->nsym = nbits*2; | ||
|  |     fmfsk->nbit = nbits; | ||
|  | 
 | ||
|  |     /* Set up demod state */ | ||
|  |     fmfsk->lodd = 0; | ||
|  |     fmfsk->nin = fmfsk->N; | ||
|  |     fmfsk->snr_mean = 0; | ||
|  | 
 | ||
|  |     float *oldsamps = (float*) malloc(sizeof(float)*fmfsk->nmem); | ||
|  |     if(oldsamps == NULL){ | ||
|  |         free(fmfsk); | ||
|  |         return NULL; | ||
|  |     } | ||
|  | 
 | ||
|  |     fmfsk->oldsamps = oldsamps; | ||
|  | 
 | ||
|  |     fmfsk->stats = (struct MODEM_STATS*)malloc(sizeof(struct MODEM_STATS)); | ||
|  |     if (fmfsk->stats == NULL) { | ||
|  |         free(oldsamps); | ||
|  |         free(fmfsk); | ||
|  |         return NULL; | ||
|  |     } | ||
|  | 
 | ||
|  |     return fmfsk; | ||
|  | } | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * Destroys an fmfsk modem and deallocates memory | ||
|  |  */ | ||
|  | void fmfsk_destroy(struct FMFSK *fmfsk){ | ||
|  |     free(fmfsk->oldsamps); | ||
|  |     free(fmfsk); | ||
|  | } | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * Returns the number of samples that must be fed to fmfsk_demod the next | ||
|  |  * cycle | ||
|  |  */ | ||
|  | uint32_t fmfsk_nin(struct FMFSK *fmfsk){ | ||
|  |     return (uint32_t)fmfsk->nin; | ||
|  | } | ||
|  | 
 | ||
|  | void fmfsk_get_demod_stats(struct FMFSK *fmfsk,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 = fmfsk->stats->clock_offset; | ||
|  |     stats->snr_est = fmfsk->stats->snr_est;           // TODO: make this SNR not Eb/No
 | ||
|  |     stats->rx_timing = fmfsk->stats->rx_timing; | ||
|  |     stats->foff = fmfsk->stats->foff; | ||
|  | 
 | ||
|  |     stats->neyesamp = fmfsk->stats->neyesamp; | ||
|  |     stats->neyetr = fmfsk->stats->neyetr; | ||
|  |     memcpy(stats->rx_eye, fmfsk->stats->rx_eye, sizeof(stats->rx_eye)); | ||
|  | 
 | ||
|  |     /* these fields not used for FSK so set to something sensible */ | ||
|  | 
 | ||
|  |     stats->sync = 0; | ||
|  |     stats->nr = fmfsk->stats->nr; | ||
|  |     stats->Nc = fmfsk->stats->Nc; | ||
|  | } | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * Modulates nbit bits into N samples to be sent through an FM radio | ||
|  |  * | ||
|  |  * struct FSK *fsk - FSK config/state struct, set up by fsk_create | ||
|  |  * float mod_out[] - Buffer for N samples of modulated FMFSK | ||
|  |  * uint8_t tx_bits[] - Buffer containing Nbits unpacked bits | ||
|  |  */ | ||
|  | 
 | ||
|  | void fmfsk_mod(struct FMFSK *fmfsk, float fmfsk_out[],uint8_t bits_in[]){ | ||
|  |     int i,j; | ||
|  |     int nbit = fmfsk->nbit; | ||
|  |     int Ts = fmfsk->Ts; | ||
|  | 
 | ||
|  |     for(i=0; i<nbit; i++){ | ||
|  |         /* Save a manchester-encoded 0 */ | ||
|  |         if(bits_in[i] == 0){ | ||
|  |             for(j=0; j<Ts; j++) | ||
|  |                 fmfsk_out[   j+i*Ts*2] = -1; | ||
|  |             for(j=0; j<Ts; j++) | ||
|  |                 fmfsk_out[Ts+j+i*Ts*2] =  1; | ||
|  |         } else { | ||
|  |         /* Save a manchester-encoded 1 */ | ||
|  |             for(j=0; j<Ts; j++) | ||
|  |                 fmfsk_out[   j+i*Ts*2] =  1; | ||
|  |             for(j=0; j<Ts; j++) | ||
|  |                 fmfsk_out[Ts+j+i*Ts*2] = -1; | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * Demodulate some number of FMFSK samples. The number of samples to be | ||
|  |  *  demodulated can be found by calling fmfsk_nin(). | ||
|  |  * | ||
|  |  * struct FMFSK *fsk - FMFSK config/state struct, set up by fsk_create | ||
|  |  * uint8_t rx_bits[] - Buffer for nbit unpacked bits to be written | ||
|  |  * float fsk_in[] - nin samples of modualted FMFSK from an FM radio | ||
|  |  */ | ||
|  | void fmfsk_demod(struct FMFSK *fmfsk, uint8_t rx_bits[],float fmfsk_in[]){ | ||
|  |     int i,j,k; | ||
|  |     int Ts          = fmfsk->Ts; | ||
|  |     int Fs          = fmfsk->Fs; | ||
|  |     int Rs          = fmfsk->Rs; | ||
|  |     int nin         = fmfsk->nin; | ||
|  |     int N           = fmfsk->N; | ||
|  |     int nsym        = fmfsk->nsym; | ||
|  |     int nbit        = fmfsk->nbit; | ||
|  |     int nmem        = fmfsk->nmem; | ||
|  |     float *oldsamps = fmfsk->oldsamps; | ||
|  |     int nold        =  nmem-nin; | ||
|  |     COMP phi_ft,dphi_ft;    /* Phase and delta-phase for fine timing estimator */ | ||
|  |     float t; | ||
|  |     COMP x;                 /* Magic fine timing angle */ | ||
|  |     float norm_rx_timing,old_norm_rx_timing,d_norm_rx_timing,appm; | ||
|  |     int rx_timing,sample_offset; | ||
|  |     int next_nin; | ||
|  |     float apeven,apodd;     /* Approx. prob of even or odd stream being correct */ | ||
|  |     float currv,mdiff,lastv; | ||
|  |     int neyesamp; | ||
|  |     int neyeoffset; | ||
|  |     float eye_max; | ||
|  |     uint8_t mbit; | ||
|  |     float var_signal = 0, var_noise = 0, lastFabsV; | ||
|  | 
 | ||
|  |     /* Shift in nin samples */ | ||
|  |     memmove(&oldsamps[0]   , &oldsamps[nmem-nold], sizeof(float)*nold); | ||
|  |     memcpy (&oldsamps[nold], &fmfsk_in[0]        , sizeof(float)*nin ); | ||
|  | 
 | ||
|  |     /* Allocate memory for filtering */ | ||
|  |     float *rx_filt = (float*) malloc(sizeof(float)*(nsym+1)*Ts); | ||
|  | 
 | ||
|  |     /* Integrate over Ts input symbols at every offset */ | ||
|  |     for(i=0; i<(nsym+1)*Ts; i++){ | ||
|  |         t=0; | ||
|  |         /* Integrate over some samples */ | ||
|  |         for(j=i;j<i+Ts;j++){ | ||
|  |             t += oldsamps[j]; | ||
|  |         } | ||
|  |         rx_filt[i] = t; | ||
|  |     } | ||
|  | 
 | ||
|  |     /*
 | ||
|  |      *  Fine timing estimation | ||
|  |      * | ||
|  |      * Estimate fine timing using line at Rs/2 that Manchester encoding provides | ||
|  |      * We need this to sync up to Manchester codewords. | ||
|  |      */ | ||
|  | 
 | ||
|  |     /* init fine timing extractor */ | ||
|  |     phi_ft.real = 1; | ||
|  |     phi_ft.imag = 0; | ||
|  | 
 | ||
|  |     /* Set up delta-phase */ | ||
|  |     dphi_ft.real = cosf(2*M_PI*((float)Rs)/((float)Fs)); | ||
|  |     dphi_ft.imag = sinf(2*M_PI*((float)Rs)/((float)Fs)); | ||
|  | 
 | ||
|  |     x.real = 0; | ||
|  |     x.imag = 0; | ||
|  | 
 | ||
|  |     for(i=0; i<(nsym+1)*Ts; i++){ | ||
|  |         /* Apply non-linearity */ | ||
|  |         t = rx_filt[i]*rx_filt[i]; | ||
|  | 
 | ||
|  |         /* Shift Rs/2 down to DC and accumulate */ | ||
|  |         x = cadd(x,fcmult(t,phi_ft)); | ||
|  | 
 | ||
|  |         /* Spin downshift oscillator */ | ||
|  |         phi_ft = cmult(dphi_ft,phi_ft); | ||
|  |         modem_probe_samp_c("t_phi_ft",&phi_ft,1); | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Figure out the normalized RX timing, using David's magic number */ | ||
|  |     norm_rx_timing =  atan2f(x.imag,x.real)/(2*M_PI) - .42; | ||
|  |     rx_timing = (int)lroundf(norm_rx_timing*(float)Ts); | ||
|  | 
 | ||
|  |     old_norm_rx_timing = fmfsk->norm_rx_timing; | ||
|  |     fmfsk->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; | ||
|  |         fmfsk->ppm = .9*fmfsk->ppm + .1*appm; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Figure out how far offset the sample points are */ | ||
|  |     sample_offset = (Ts/2)+Ts+rx_timing-1; | ||
|  | 
 | ||
|  |     /* Request fewer or greater samples next time, if fine timing is far
 | ||
|  |      * enough off. This also makes it possible to tolerate clock offsets */ | ||
|  |     next_nin = N; | ||
|  |     if(norm_rx_timing > -.2) | ||
|  |         next_nin += Ts/2; | ||
|  |     if(norm_rx_timing < -.65) | ||
|  |         next_nin -= Ts/2; | ||
|  |     fmfsk->nin = next_nin; | ||
|  | 
 | ||
|  |     /* Make first diff of this round the last sample of the last round,
 | ||
|  |      * for the odd stream */ | ||
|  |     lastv = fmfsk->lodd; | ||
|  |     lastFabsV = fabs(lastv); | ||
|  |     apeven = 0; | ||
|  |     apodd = 0; | ||
|  |     for(i=0; i<nsym; i++){ | ||
|  |         /* Sample a filtered value */ | ||
|  |         currv = rx_filt[sample_offset+(i*Ts)]; | ||
|  |         modem_probe_samp_f("t_symsamp",&currv,1); | ||
|  |         mdiff = lastv - currv; | ||
|  |         mbit = mdiff>0 ? 1 : 0; | ||
|  |         lastv = currv; | ||
|  | 
 | ||
|  |         // Calculate the signal variance.  Note that the mean is zero
 | ||
|  |         var_signal += currv * currv; | ||
|  | 
 | ||
|  |         /* Calculate the variance of the noise between samples (symbols).  A quick variance estimate
 | ||
|  |          * without calculating mean can be done by differentiating (remove mean) and then | ||
|  |          * dividing by 2.  Fabs the samples as we are looking at how close the samples are to each | ||
|  |          * other as if they were all the same polarity/symbol.  */ | ||
|  |         currv = fabs(currv); | ||
|  |         var_noise += (currv - lastFabsV) * (currv - lastFabsV); | ||
|  |         lastFabsV = currv; | ||
|  | 
 | ||
|  |         mdiff = mdiff>0 ? mdiff : 0-mdiff; | ||
|  | 
 | ||
|  |         /* Put bit in it's stream */ | ||
|  |         if((i%2)==1){ | ||
|  |             apeven += mdiff; | ||
|  |             /* Even stream goes in LSB */ | ||
|  |             rx_bits[i>>1] |= mbit ? 0x1 : 0x0; | ||
|  |         }else{ | ||
|  |             apodd += mdiff; | ||
|  |             /* Odd in second-to-LSB */ | ||
|  |             rx_bits[i>>1]  = mbit ? 0x2 : 0x0; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Div by 2 to correct variance when doing via differentiation.*/ | ||
|  |     var_noise *= 0.5; | ||
|  | 
 | ||
|  |     if(apeven>apodd){ | ||
|  |         /* Zero out odd bits from output bitstream */ | ||
|  |         for(i=0;i<nbit;i++) | ||
|  |             rx_bits[i] &= 0x1; | ||
|  |     }else{ | ||
|  |         /* Shift odd bits into LSB and even bits out of existence */ | ||
|  |         for(i=0;i<nbit;i++) | ||
|  |             rx_bits[i] = (rx_bits[i]&0x2)>>1; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Save last sample of int stream for next demod round */ | ||
|  |     fmfsk->lodd = lastv; | ||
|  | 
 | ||
|  |     /* Save demod statistics */ | ||
|  |     fmfsk->stats->Nc = 0; | ||
|  |     fmfsk->stats->nr = 0; | ||
|  | 
 | ||
|  |     /* Clock offset and RX timing are all we know here */ | ||
|  |     fmfsk->stats->clock_offset = fmfsk->ppm; | ||
|  |     fmfsk->stats->rx_timing = (float)rx_timing; | ||
|  | 
 | ||
|  |     /* Zero out all of the other things */ | ||
|  |     fmfsk->stats->foff = 0; | ||
|  | 
 | ||
|  |     /* Use moving average to smooth SNR display */ | ||
|  |     if(fmfsk->snr_mean < 0.1) | ||
|  |         fmfsk->snr_mean = (10.0 * log10f(var_signal / var_noise)); | ||
|  |     else | ||
|  |         fmfsk->snr_mean = 0.9 * fmfsk->snr_mean + 0.1 * (10.0 * log10f(var_signal / var_noise)); | ||
|  |     fmfsk->stats->snr_est = fmfsk->snr_mean; | ||
|  | 
 | ||
|  |     /* Collect an eye diagram */ | ||
|  |     /* Take a sample for the eye diagrams */ | ||
|  |     neyesamp = fmfsk->stats->neyesamp = Ts*4; | ||
|  |     neyeoffset = sample_offset+(Ts*2*28); | ||
|  | 
 | ||
|  |     fmfsk->stats->neyetr = 8; | ||
|  |     for(k=0; k<fmfsk->stats->neyetr; k++) | ||
|  |         for(j=0; j<neyesamp; j++) | ||
|  |             fmfsk->stats->rx_eye[k][j] = rx_filt[k*neyesamp+neyeoffset+j]; | ||
|  |     //fmfsk->stats->rx_eye[k][j] = fmfsk_in[k*neyesamp+neyeoffset+j];
 | ||
|  |     eye_max = 0; | ||
|  | 
 | ||
|  |     /* Normalize eye to +/- 1 */ | ||
|  |     for(i=0; i<fmfsk->stats->neyetr; i++) | ||
|  |         for(j=0; j<neyesamp; j++) | ||
|  |             if(fabsf(fmfsk->stats->rx_eye[i][j])>eye_max) | ||
|  |                 eye_max = fabsf(fmfsk->stats->rx_eye[i][j]); | ||
|  | 
 | ||
|  |     for(i=0; i<fmfsk->stats->neyetr; i++) | ||
|  |         for(j=0; j<neyesamp; j++) | ||
|  |             fmfsk->stats->rx_eye[i][j] = (fmfsk->stats->rx_eye[i][j]/(2*eye_max))+.5; | ||
|  | 
 | ||
|  |     modem_probe_samp_f("t_norm_rx_timing",&norm_rx_timing,1); | ||
|  |     modem_probe_samp_f("t_rx_filt",rx_filt,(nsym+1)*Ts); | ||
|  |     free(rx_filt); | ||
|  | } | ||
|  | 
 | ||
|  | } // FreeDV
 | ||
|  | 
 |