% fdmdv.m % % Functions that implement a Frequency Divison Multiplexed Modem for % Digital Voice (FDMDV) over HF channels. % % Copyright David Rowe 2012 % This program is distributed under the terms of the GNU General Public License % Version 2 % % reqd to mak sure we get same random bits at mod and demod rand('state',1); randn('state',1); % Constants global Fs = 8000; % sample rate in Hz global T = 1/Fs; % sample period in seconds global Rs = 50; % symbol rate in Hz global Nc = 14; % number of carriers global Nb = 2; % Bits/symbol for QPSK modulation global Rb = Nc*Rs*Nb; % bit rate global M = Fs/Rs; % oversampling factor global Nsym = 6; % number of symbols to filter over global Fsep = 75; % Separation between carriers (Hz) global Fcentre = 1200; % Centre frequency, Nc/2 carriers below this, N/c carriers above (Hz) global Nt = 5; % number of symbols we estimate timing over global P = 4; % oversample factor used for rx symbol filtering global Nfilter = Nsym*M; global Nfiltertiming = M+Nfilter+M; alpha = 0.5; global snr_coeff = 0.9 % SNR est averaging filter coeff % root raised cosine (Root Nyquist) filter global gt_alpha5_root; gt_alpha5_root = gen_rn_coeffs(alpha, T, Rs, Nsym, M); % Functions ---------------------------------------------------- % generate Nc+1 QPSK symbols from vector of (1,Nc*Nb) input bits. The % Nc+1 symbol is the +1 -1 +1 .... BPSK sync carrier function tx_symbols = bits_to_qpsk(prev_tx_symbols, tx_bits, modulation) global Nc; global Nb; global pilot_bit; % re-arrange as (Nc,Nb) matrix tx_bits_matrix = zeros(Nc,Nb); tx_bits_matrix(1:Nc,1) = tx_bits(1:Nb:Nb*Nc); tx_bits_matrix(1:Nc,2) = tx_bits(2:Nb:Nb*Nc); if (strcmp(modulation,'dqpsk')) % map to (Nc,1) DQPSK symbols for c=1:Nc msb = tx_bits_matrix(c,1); lsb = tx_bits_matrix(c,2); if ((msb == 0) && (lsb == 0)) tx_symbols(c) = prev_tx_symbols(c); endif if ((msb == 0) && (lsb == 1)) tx_symbols(c) = j*prev_tx_symbols(c); endif if ((msb == 1) && (lsb == 0)) tx_symbols(c) = -prev_tx_symbols(c); endif if ((msb == 1) && (lsb == 1)) tx_symbols(c) = -j*prev_tx_symbols(c); endif end else % QPSK mapping tx_symbols = -1 + 2*tx_bits_matrix(:,1) - j + 2j*tx_bits_matrix(:,2); endif % +1 -1 +1 -1 BPSK sync carrier, once filtered becomes two spectral % lines at +/- Rs/2 if pilot_bit tx_symbols(Nc+1) = -prev_tx_symbols(Nc+1); else tx_symbols(Nc+1) = prev_tx_symbols(Nc+1); end if pilot_bit pilot_bit = 0; else pilot_bit = 1; end endfunction % Given Nc*Nb bits construct M samples (1 symbol) of Nc filtered % symbols streams function tx_baseband = tx_filter(tx_symbols) global Nc; global M; global tx_filter_memory; global Nfilter; global gt_alpha5_root; tx_baseband = zeros(Nc+1,M); % tx filter each symbol, generate M filtered output samples for each symbol. % Efficient polyphase filter techniques used as tx_filter_memory is sparse tx_filter_memory(:,Nfilter) = sqrt(2)/2*tx_symbols; for i=1:M tx_baseband(:,i) = M*tx_filter_memory(:,M:M:Nfilter) * gt_alpha5_root(M-i+1:M:Nfilter)'; end tx_filter_memory(:,1:Nfilter-M) = tx_filter_memory(:,M+1:Nfilter); tx_filter_memory(:,Nfilter-M+1:Nfilter) = zeros(Nc+1,M); endfunction % Construct FDM signal by frequency shifting each filtered symbol % stream. Returns complex signal so we can apply frequency offsets % easily. function tx_fdm = fdm_upconvert(tx_filt) global Fs; global M; global Nc; global Fsep; global phase_tx; global freq; tx_fdm = zeros(1,M); % Nc/2 tones below centre freq for c=1:Nc/2 for i=1:M phase_tx(c) = phase_tx(c) * freq(c); tx_fdm(i) = tx_fdm(i) + tx_filt(c,i)*phase_tx(c); end end % Nc/2 tones above centre freq for c=Nc/2+1:Nc for i=1:M phase_tx(c) = phase_tx(c) * freq(c); tx_fdm(i) = tx_fdm(i) + tx_filt(c,i)*phase_tx(c); end end % add centre pilot tone c = Nc+1; for i=1:M phase_tx(c) = phase_tx(c) * freq(c); pilot(i) = 2*tx_filt(c,i)*phase_tx(c); tx_fdm(i) = tx_fdm(i) + pilot(i); end % Scale such that total Carrier power C of real(tx_fdm) = Nc. This % excludes the power of the pilot tone. % We return the complex (single sided) signal to make frequency % shifting for the purpose of testing easier tx_fdm = 2*tx_fdm; endfunction % Frequency shift each modem carrier down to Nc+1 baseband signals function rx_baseband = fdm_downconvert(rx_fdm, nin) global Fs; global M; global Nc; global Fsep; global phase_rx; global freq; rx_baseband = zeros(1,nin); % Nc/2 tones below centre freq for c=1:Nc/2 for i=1:nin phase_rx(c) = phase_rx(c) * freq(c); rx_baseband(c,i) = rx_fdm(i)*phase_rx(c)'; end end % Nc/2 tones above centre freq for c=Nc/2+1:Nc for i=1:nin phase_rx(c) = phase_rx(c) * freq(c); rx_baseband(c,i) = rx_fdm(i)*phase_rx(c)'; end end % Pilot c = Nc+1; for i=1:nin phase_rx(c) = phase_rx(c) * freq(c); rx_baseband(c,i) = rx_fdm(i)*phase_rx(c)'; end endfunction % Receive filter each baseband signal at oversample rate P function rx_filt = rx_filter(rx_baseband, nin) global Nc; global M; global P; global rx_filter_memory; global Nfilter; global gt_alpha5_root; global Fsep; rx_filt = zeros(Nc+1,nin*P/M); % rx filter each symbol, generate P filtered output samples for each symbol. % Note we keep memory at rate M, it's just the filter output at rate P N=M/P; j=1; for i=1:N:nin rx_filter_memory(:,Nfilter-N+1:Nfilter) = rx_baseband(:,i:i-1+N); rx_filt(:,j) = rx_filter_memory * gt_alpha5_root'; rx_filter_memory(:,1:Nfilter-N) = rx_filter_memory(:,1+N:Nfilter); j+=1; end endfunction % LPF and peak pick part of freq est, put in a function as we call it twice function [foff imax pilot_lpf S] = lpf_peak_pick(pilot_baseband, pilot_lpf, nin) global M; global Npilotlpf; global Npilotcoeff; global Fs; global Mpilotfft; global pilot_coeff; % LPF cutoff 200Hz, so we can handle max +/- 200 Hz freq offset pilot_lpf(1:Npilotlpf-nin) = pilot_lpf(nin+1:Npilotlpf); j = 1; for i = Npilotlpf-nin+1:Npilotlpf pilot_lpf(i) = pilot_baseband(j:j+Npilotcoeff-1) * pilot_coeff'; j++; end % decimate to improve DFT resolution, window and DFT Mpilot = Fs/(2*200); % calc decimation rate given new sample rate is twice LPF freq h = hanning(Npilotlpf); s = pilot_lpf(1:Mpilot:Npilotlpf) .* h(1:Mpilot:Npilotlpf)'; s = [s zeros(1,Mpilotfft-Npilotlpf/Mpilot)]; S = fft(s, Mpilotfft); % peak pick and convert to Hz [imax ix] = max(S); r = 2*200/Mpilotfft; % maps FFT bin to frequency in Hz if ix > Mpilotfft/2 foff = (ix - Mpilotfft - 1)*r; else foff = (ix - 1)*r; endif endfunction % Estimate frequency offset of FDM signal using BPSK pilot. This is quite % sensitive to pilot tone level wrt other carriers function [foff S1 S2] = rx_est_freq_offset(rx_fdm, pilot, pilot_prev, nin) global M; global Npilotbaseband; global pilot_baseband1; global pilot_baseband2; global pilot_lpf1; global pilot_lpf2; % down convert latest nin samples of pilot by multiplying by % ideal BPSK pilot signal we have generated locally. This % peak of the resulting signal is sensitive to the time shift between % the received and local version of the pilot, so we do it twice at % different time shifts and choose the maximum. pilot_baseband1(1:Npilotbaseband-nin) = pilot_baseband1(nin+1:Npilotbaseband); pilot_baseband2(1:Npilotbaseband-nin) = pilot_baseband2(nin+1:Npilotbaseband); for i=1:nin pilot_baseband1(Npilotbaseband-nin+i) = rx_fdm(i) * conj(pilot(i)); pilot_baseband2(Npilotbaseband-nin+i) = rx_fdm(i) * conj(pilot_prev(i)); end [foff1 max1 pilot_lpf1 S1] = lpf_peak_pick(pilot_baseband1, pilot_lpf1, nin); [foff2 max2 pilot_lpf2 S2] = lpf_peak_pick(pilot_baseband2, pilot_lpf2, nin); if max1 > max2 foff = foff1; else foff = foff2; end endfunction % Estimate optimum timing offset, re-filter receive symbols function [rx_symbols rx_timing env] = rx_est_timing(rx_filt, rx_baseband, nin) global M; global Nt; global Nc; global rx_filter_mem_timing; global rx_baseband_mem_timing; global P; global Nfilter; global Nfiltertiming; global gt_alpha5_root; % nin adjust % -------------------------------- % 120 -1 (one less rate P sample) % 160 0 (nominal) % 200 1 (one more rate P sample) adjust = P - nin*P/M; % update buffer of Nt rate P filtered symbols rx_filter_mem_timing(:,1:(Nt-1)*P+adjust) = rx_filter_mem_timing(:,P+1-adjust:Nt*P); rx_filter_mem_timing(:,(Nt-1)*P+1+adjust:Nt*P) = rx_filt(:,:); % sum envelopes of all carriers env = sum(abs(rx_filter_mem_timing(:,:))); % use all Nc+1 carriers for timing %env = abs(rx_filter_mem_timing(Nc+1,:)); % just use BPSK pilot [n m] = size(env); % The envelope has a frequency component at the symbol rate. The % phase of this frequency component indicates the timing. So work out % single DFT at frequency 2*pi/P x = env * exp(-j*2*pi*(0:m-1)/P)'; % map phase to estimated optimum timing instant at rate M % the M/4 part was adjusted by experiment, I know not why.... rx_timing = angle(x)*M/(2*pi) + M/4; if (rx_timing > M) rx_timing -= M; end if (rx_timing < -M) rx_timing += M; end % rx_baseband_mem_timing contains M + Nfilter + M samples of the % baseband signal at rate M this enables us to resample the filtered % rx symbol with M sample precision once we have rx_timing rx_baseband_mem_timing(:,1:Nfiltertiming-nin) = rx_baseband_mem_timing(:,nin+1:Nfiltertiming); rx_baseband_mem_timing(:,Nfiltertiming-nin+1:Nfiltertiming) = rx_baseband; % sample right in the middle of the timing estimator window, by filtering % at rate M s = round(rx_timing) + M; rx_symbols = rx_baseband_mem_timing(:,s+1:s+Nfilter) * gt_alpha5_root'; endfunction % Phase estimation function - probably won't work over a HF channel % Tries to operate over a single symbol but uses phase information from % all Nc carriers which should increase the SNR of phase estimate. % Maybe phase is coherent over a couple of symbols in HF channel,not % sure but it's worth 3dB so worth experimenting or using coherent as % an option. function rx_phase = rx_est_phase(prev_rx_symbols, rx_symbols) % modulation strip rx_phase = angle(sum(rx_symbols .^ 4))/4; endfunction % convert symbols back to an array of bits function [rx_bits sync_bit f_err phase_difference] = qpsk_to_bits(prev_rx_symbols, rx_symbols, modulation) global Nc; global Nb; global Nb; if (strcmp(modulation,'dqpsk')) % extra 45 degree clockwise lets us use real and imag axis as % decision boundaries phase_difference = zeros(Nc+1,1); phase_difference(1:Nc) = rx_symbols(1:Nc) .* conj(prev_rx_symbols(1:Nc)) * exp(j*pi/4); % map (Nc,1) DQPSK symbols back into an (1,Nc*Nb) array of bits for c=1:Nc d = phase_difference(c); if ((real(d) >= 0) && (imag(d) >= 0)) msb = 0; lsb = 0; endif if ((real(d) < 0) && (imag(d) >= 0)) msb = 0; lsb = 1; endif if ((real(d) < 0) && (imag(d) < 0)) msb = 1; lsb = 0; endif if ((real(d) >= 0) && (imag(d) < 0)) msb = 1; lsb = 1; endif rx_bits(2*(c-1)+1) = msb; rx_bits(2*(c-1)+2) = lsb; end % Extract DBPSK encoded Sync bit phase_difference(Nc+1,1) = rx_symbols(Nc+1) .* conj(prev_rx_symbols(Nc+1)); if (real(phase_difference(Nc+1)) < 0) sync_bit = 1; f_err = imag(phase_difference(Nc+1)); else sync_bit = 0; f_err = -imag(phase_difference(Nc+1)); end % pilot carrier gets an extra pi/4 rotation to make it consistent with % other carriers, as we need it for snr_update and scatter diagram phase_difference(Nc+1) *= exp(j*pi/4); else % map (Nc,1) QPSK symbols back into an (1,Nc*Nb) array of bits rx_bits(1:Nb:Nc*Nb) = real(rx_symbols) > 0; rx_bits(2:Nb:Nc*Nb) = imag(rx_symbols) > 0; endif endfunction % given phase differences update estimates of signal and noise levels function [sig_est noise_est] = snr_update(sig_est, noise_est, phase_difference) global snr_coeff; global Nc; % mag of each symbol is distance from origin, this gives us a % vector of mags, one for each carrier. s = abs(phase_difference); % signal mag estimate for each carrier is a smoothed version % of instantaneous magntitude, this gives us a vector of smoothed % mag estimates, one for each carrier. sig_est = snr_coeff*sig_est + (1 - snr_coeff)*s; % noise mag estimate is distance of current symbol from average % location of that symbol. We reflect all symbols into the first % quadrant for convenience. refl_symbols = abs(real(phase_difference)) + j*abs(imag(phase_difference)); n = abs(exp(j*pi/4)*sig_est - refl_symbols); % noise mag estimate for each carrier is a smoothed version of % instantaneous noise mag, this gives us a vector of smoothed % noise power estimates, one for each carrier. noise_est = snr_coeff*noise_est + (1 - snr_coeff)*n; endfunction % calculate current SNR estimate (3000Hz noise BW) function snr_dB = calc_snr(sig_est, noise_est) global Rs; % find total signal power by summing power in all carriers S = sum(sig_est .^2); SdB = 10*log10(S); % Average noise mag across all carriers and square to get an average % noise power. This is an estimate of the noise power in Rs = 50Hz of % BW (note for raised root cosine filters Rs is the noise BW of the % filter) N50 = mean(noise_est).^2; N50dB = 10*log10(N50); % Now multiply by (3000 Hz)/(50 Hz) to find the total noise power in % 3000 Hz N3000dB = N50dB + 10*log10(3000/Rs); snr_dB = SdB - N3000dB; endfunction % returns nbits from a repeating sequence of random data function bits = get_test_bits(nbits) global Ntest_bits; % length of test sequence global current_test_bit; global test_bits; for i=1:nbits bits(i) = test_bits(current_test_bit++); if (current_test_bit > Ntest_bits) current_test_bit = 1; endif end endfunction % Accepts nbits from rx and attempts to sync with test_bits sequence. % if sync OK measures bit errors function [sync bit_errors] = put_test_bits(rx_bits) global Ntest_bits; % length of test sequence global test_bits; global rx_test_bits_mem; % Append to our memory [m n] = size(rx_bits); rx_test_bits_mem(1:Ntest_bits-n) = rx_test_bits_mem(n+1:Ntest_bits); rx_test_bits_mem(Ntest_bits-n+1:Ntest_bits) = rx_bits; % see how many bit errors we get when checked against test sequence bit_errors = sum(xor(test_bits,rx_test_bits_mem)); % if less than a thresh we are aligned and in sync with test sequence ber = bit_errors/Ntest_bits; sync = 0; if (ber < 0.2) sync = 1; endif endfunction % Generate M samples of DBPSK pilot signal for Freq offset estimation function [pilot_fdm bit symbol filter_mem phase] = generate_pilot_fdm(bit, symbol, filter_mem, phase, freq) global M; global Nfilter; global gt_alpha5_root; % +1 -1 +1 -1 DBPSK sync carrier, once filtered becomes two spectral % lines at +/- Rs/2 if bit symbol = -symbol; else symbol = symbol; end if bit bit = 0; else bit = 1; end % filter DPSK symbol to create M baseband samples filter_mem(Nfilter) = (sqrt(2)/2)*symbol; for i=1:M tx_baseband(i) = M*filter_mem(M:M:Nfilter) * gt_alpha5_root(M-i+1:M:Nfilter)'; end filter_mem(1:Nfilter-M) = filter_mem(M+1:Nfilter); filter_mem(Nfilter-M+1:Nfilter) = zeros(1,M); % upconvert for i=1:M phase = phase * freq; pilot_fdm(i) = sqrt(2)*2*tx_baseband(i)*phase; end endfunction % Generate a 4M sample vector of DBPSK pilot signal. As the pilot signal % is periodic in 4M samples we can then use this vector as a look up table % for pilot signal generation in the demod. function pilot_lut = generate_pilot_lut() global Nc; global Nfilter; global M; global freq; % pilot states pilot_rx_bit = 0; pilot_symbol = sqrt(2); pilot_freq = freq(Nc+1); pilot_phase = 1; pilot_filter_mem = zeros(1, Nfilter); %prev_pilot = zeros(M,1); pilot_lut = []; F=8; for f=1:F [pilot pilot_rx_bit pilot_symbol pilot_filter_mem pilot_phase] = generate_pilot_fdm(pilot_rx_bit, pilot_symbol, pilot_filter_mem, pilot_phase, pilot_freq); %prev_pilot = pilot; pilot_lut = [pilot_lut pilot]; end % discard first 4 symbols as filter memory is filling, just keep last % four symbols pilot_lut = pilot_lut(4*M+1:M*F); endfunction % grab next pilot samples for freq offset estimation at demod function [pilot prev_pilot pilot_lut_index prev_pilot_lut_index] = get_pilot(pilot_lut_index, prev_pilot_lut_index, nin) global M; global pilot_lut; for i=1:nin pilot(i) = pilot_lut(pilot_lut_index); pilot_lut_index++; if pilot_lut_index > 4*M pilot_lut_index = 1; end prev_pilot(i) = pilot_lut(prev_pilot_lut_index); prev_pilot_lut_index++; if prev_pilot_lut_index > 4*M prev_pilot_lut_index = 1; end end endfunction % Change the sample rate by a small amount, for example 1000ppm (ratio % = 1.001). Always returns nout samples in buf_out, but uses a % variable number of input samples nin to accomodate the change in % sample rate. nin is nominally set to nout, but may use nout +/- 2 % samples to accomodate the different sample rates. buf_in should be % of length nout+6 samples to accomodate this, and buf_in should be % updated externally based on the nin returned each time. "ratio" is % Fs_in/Fs_out, for example 48048/48000 = 1.001 (+1000ppm) or % 47952/48000 = 0.999 (-1000ppm). Uses linear interpolation to % perform the resampling. This requires a highly over-sampled signal, % for example 48000Hz sample rate for the modem signal centred on % 1kHz, otherwise linear interpolation will have a low pass filter effect % (for example an 8000Hz sample rate for modem signal centred on 1kHz % would cause problems). function [buf_out t nin] = resample(buf_in, t, ratio, nout) for i=1:nout c = floor(t); a = t - c; b = 1 - a; buf_out(i) = buf_in(c)*b + buf_in(c+1)*a; t += ratio; end t -= nout; % adjust nin and t so that on next call we start with 3 < t < 4, % this gives us +/- 2 samples room to move before we hit start or % end of buf_in delta = floor(t - 3); nin = nout + delta; t -= delta; endfunction % freq offset state machine. Moves between acquire and track states based % on BPSK pilot sequence. Freq offset estimator occasionally makes mistakes % when used continuously. So we use it until we have acquired the BPSK pilot, % then switch to a more robust tracking algorithm. If we lose sync we switch % back to acquire mode for fast-requisition. function [track state] = freq_state(sync_bit, state) % acquire state, look for 6 symbol 010101 sequence from sync bit next_state = state; if state == 0 if sync_bit == 0 next_state = 1; end end if state == 1 if sync_bit == 1 next_state = 2; else next_state = 0; end end if state == 2 if sync_bit == 0 next_state = 3; else next_state = 0; end end if state == 3 if sync_bit == 1 next_state = 4; else next_state = 0; end end if state == 4 if sync_bit == 0 next_state = 5; else next_state = 0; end end if state == 5 if sync_bit == 1 next_state = 6; else next_state = 0; end end % states 6 and above are track mode, make sure we keep getting 0101 sync bit sequence if state == 6 if sync_bit == 0 next_state = 7; else next_state = 0; end end if state == 7 if sync_bit == 1 next_state = 6; else next_state = 0; end end state = next_state; if state >= 6 track = 1; else track = 0; end endfunction % Save test bits to a text file in the form of a C array function test_bits_file(filename) global test_bits; global Ntest_bits; f=fopen(filename,"wt"); fprintf(f,"/* Generated by test_bits_file() Octave function */\n\n"); fprintf(f,"const int test_bits[]={\n"); for m=1:Ntest_bits-1 fprintf(f," %d,\n",test_bits(m)); endfor fprintf(f," %d\n};\n",test_bits(Ntest_bits)); fclose(f); endfunction % Saves RN filter coeffs to a text file in the form of a C array function rn_file(filename) global gt_alpha5_root; global Nfilter; f=fopen(filename,"wt"); fprintf(f,"/* Generated by rn_file() Octave function */\n\n"); fprintf(f,"const float gt_alpha5_root[]={\n"); for m=1:Nfilter-1 fprintf(f," %g,\n",gt_alpha5_root(m)); endfor fprintf(f," %g\n};\n",gt_alpha5_root(Nfilter)); fclose(f); endfunction function pilot_coeff_file(filename) global pilot_coeff; global Npilotcoeff; f=fopen(filename,"wt"); fprintf(f,"/* Generated by pilot_coeff_file() Octave function */\n\n"); fprintf(f,"const float pilot_coeff[]={\n"); for m=1:Npilotcoeff-1 fprintf(f," %g,\n",pilot_coeff(m)); endfor fprintf(f," %g\n};\n",pilot_coeff(Npilotcoeff)); fclose(f); endfunction % Saves hanning window coeffs to a text file in the form of a C array function hanning_file(filename) global Npilotlpf; h = hanning(Npilotlpf); f=fopen(filename,"wt"); fprintf(f,"/* Generated by hanning_file() Octave function */\n\n"); fprintf(f,"const float hanning[]={\n"); for m=1:Npilotlpf-1 fprintf(f," %g,\n", h(m)); endfor fprintf(f," %g\n};\n", h(Npilotlpf)); fclose(f); endfunction function png_file(fig, pngfilename) figure(fig); pngname = sprintf("%s.png",pngfilename); print(pngname, '-dpng', "-S500,500") pngname = sprintf("%s_large.png",pngfilename); print(pngname, '-dpng', "-S800,600") endfunction % Initialise ---------------------------------------------------- global pilot_bit; pilot_bit = 0; % current value of pilot bit global tx_filter_memory; tx_filter_memory = zeros(Nc+1, Nfilter); global rx_filter_memory; rx_filter_memory = zeros(Nc+1, Nfilter); % phasors used for up and down converters global freq; freq = zeros(Nc+1,1); for c=1:Nc/2 carrier_freq = (-Nc/2 - 1 + c)*Fsep + Fcentre; freq(c) = exp(j*2*pi*carrier_freq/Fs); end for c=Nc/2+1:Nc carrier_freq = (-Nc/2 + c)*Fsep + Fcentre; freq(c) = exp(j*2*pi*carrier_freq/Fs); end freq(Nc+1) = exp(j*2*pi*Fcentre/Fs); % Spread initial FDM carrier phase out as far as possible. This % helped PAPR for a few dB. We don't need to adjust rx phase as DQPSK % takes care of that. global phase_tx; %phase_tx = ones(Nc+1,1); phase_tx = exp(j*2*pi*(0:Nc)/(Nc+1)); global phase_rx; phase_rx = ones(Nc+1,1); % Freq offset estimator constants global Mpilotfft = 256; global Npilotcoeff = 30; % number of pilot LPF coeffs global pilot_coeff = fir1(Npilotcoeff-1, 200/(Fs/2))'; % 200Hz LPF global Npilotbaseband = Npilotcoeff + M + M/P; % number of pilot baseband samples reqd for pilot LPF global Npilotlpf = 4*M; % number of samples we DFT pilot over, pilot est window % pilot LUT, used for copy of pilot at rx global pilot_lut; pilot_lut = generate_pilot_lut(); pilot_lut_index = 1; prev_pilot_lut_index = 3*M+1; % Freq offset estimator states global pilot_baseband1; global pilot_baseband2; pilot_baseband1 = zeros(1, Npilotbaseband); % pilot baseband samples pilot_baseband2 = zeros(1, Npilotbaseband); % pilot baseband samples global pilot_lpf1 global pilot_lpf2 pilot_lpf1 = zeros(1, Npilotlpf); % LPF pilot samples pilot_lpf2 = zeros(1, Npilotlpf); % LPF pilot samples % Timing estimator states global rx_filter_mem_timing; rx_filter_mem_timing = zeros(Nc+1, Nt*P); global rx_baseband_mem_timing; rx_baseband_mem_timing = zeros(Nc+1, Nfiltertiming); % Test bit stream constants global Ntest_bits = Nc*Nb*4; % length of test sequence global test_bits = rand(1,Ntest_bits) > 0.5; % Test bit stream state variables global current_test_bit = 1; current_test_bit = 1; global rx_test_bits_mem; rx_test_bits_mem = zeros(1,Ntest_bits);