freeswitch/libs/libcodec2/octave/fdmdv.m

936 lines
24 KiB
Matlab

% 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);