746 lines
24 KiB
Matlab
746 lines
24 KiB
Matlab
% tcohpsk.m
|
|
% David Rowe Oct 2014
|
|
%
|
|
% Octave coherent PSK modem script that has two modes:
|
|
%
|
|
% i) tests the C port of the coherent PSK modem. This script loads
|
|
% the output of unittest/tcohpsk.c and compares it to the output of
|
|
% the reference versions of the same modem written in Octave.
|
|
%
|
|
|
|
% (ii) Runs the Octave version of the cohpsk modem to tune and develop
|
|
% it, including extensive channel simulations such as AWGN noise,
|
|
% fading/HF, frequency offset, frequency drift, and tx/rx sample
|
|
% rate differences.
|
|
|
|
% TODO:
|
|
%
|
|
% [X] Test
|
|
% [X] AWGN channel
|
|
% [X] freq offset
|
|
% [X] fading channel
|
|
% [X] freq drift
|
|
% [X] timing drift
|
|
% [X] tune perf/impl loss to get closer to ideal
|
|
% [X] linear interp of phase for better fading perf
|
|
% [X] freq offset/drift feedback loop
|
|
% [X] PAPR measurement and reduction
|
|
% [X] false sync
|
|
% [X] doesn't sync up on noise (used EsNo = -12)
|
|
% [X] similar but invalid signal like huge f off
|
|
% [X] ability to "unsync" when signal disappears
|
|
% [ ] some calibrated tests against FreeDV 1600
|
|
% + compare sound quality at various Es/Nos
|
|
% [ ] sync
|
|
% + set some req & implement
|
|
% [ ] way to handle eom w/o nasties
|
|
% + like mute output when signal has gone or v low snr
|
|
% + instantaneous snr
|
|
% [X] ssb tx filter with 3dB passband ripple
|
|
% + diverisity helped for AWGN BER 0.024 down to 0.016
|
|
% + Only a small change in fading perf with filter on/off
|
|
% + however other filters may have other effects, should test this,
|
|
% e.g. scatter plots, some sort of BER metric?
|
|
% [X] EsNo estimation
|
|
% [ ] filter reqd with compression?
|
|
% + make sure not too much noise passed into noise floor
|
|
% [X] different diversity combination
|
|
% + taking largest symbol didn't help
|
|
% [X] histogram of bit errors
|
|
% + lot of data
|
|
% + ssb filter
|
|
% + compression
|
|
% + make sure it's flat with many errors
|
|
|
|
pkg load signal;
|
|
more off;
|
|
|
|
global passes = 0;
|
|
global fails = 0;
|
|
|
|
cohpsk_dev;
|
|
fdmdv_common;
|
|
autotest;
|
|
|
|
rand('state',1);
|
|
randn('state',1);
|
|
|
|
% select which test ----------------------------------------------------------
|
|
|
|
test = 'compare to c';
|
|
%test = 'awgn';
|
|
%test = 'fading';
|
|
|
|
% some parameters that can be over ridden, e.g. to disable parts of modem
|
|
|
|
initial_sync = 0; % setting this to 1 put us straight into sync w/o freq offset est
|
|
ftrack_en = 1; % set to 1 to enable freq tracking
|
|
ssb_tx_filt = 0; % set to 1 to to simulate SSB tx filter with passband ripple
|
|
Fs = 7500;
|
|
|
|
% predefined tests ....
|
|
|
|
if strcmp(test, 'compare to c')
|
|
frames = 30;
|
|
foff = 58.7;
|
|
dfoff = -0.5/Fs;
|
|
EsNodB = 8;
|
|
fading_en = 0;
|
|
hf_delay_ms = 2;
|
|
compare_with_c = 1;
|
|
sample_rate_ppm = -1500;
|
|
ssb_tx_filt = 0;
|
|
end
|
|
|
|
% should be BER around 0.015 to 0.02
|
|
|
|
if strcmp(test, 'awgn')
|
|
frames = 100;
|
|
foff = 58.7;
|
|
dfoff = -0.5/Fs;
|
|
EsNodB = 8;
|
|
fading_en = 0;
|
|
hf_delay_ms = 2;
|
|
compare_with_c = 0;
|
|
sample_rate_ppm = 0;
|
|
end
|
|
|
|
% Similar to AWGN - should be BER around 0.015 to 0.02
|
|
|
|
if strcmp(test, 'fading');
|
|
frames = 100;
|
|
foff = -25;
|
|
dfoff = 0.5/Fs;
|
|
EsNodB = 12;
|
|
fading_en = 1;
|
|
hf_delay_ms = 2;
|
|
compare_with_c = 0;
|
|
sample_rate_ppm = 0;
|
|
end
|
|
|
|
EsNo = 10^(EsNodB/10);
|
|
|
|
% modem constants ----------------------------------------------------------
|
|
|
|
Rs = 75; % symbol rate in Hz
|
|
Nc = 7; % number of carriers
|
|
Nd = 2; % diveristy factor
|
|
framesize = 56; % number of payload data bits in the frame
|
|
|
|
Nsw = 4; % frames we demod for initial sync window
|
|
afdmdv.Nsym = 6; % size of tx/tx root nyquist filter in symbols
|
|
afdmdv.Nt = 5; % number of symbols we estimate timing over
|
|
|
|
clip = 6.5; % Clipping of tx signal to reduce PAPR. Adjust by
|
|
% experiment as Nc and Nd change. Check out no noise
|
|
% scatter diagram and AWGN/fading BER perf
|
|
% at operating points
|
|
|
|
% FDMDV init ---------------------------------------------------------------
|
|
|
|
afdmdv.Fs = Fs;
|
|
afdmdv.Nc = Nd*Nc-1;
|
|
afdmdv.Rs = Rs;
|
|
|
|
if Fs/afdmdv.Rs != floor(Fs/afdmdv.Rs)
|
|
printf("\n Oops, Fs/Rs must be an integer!\n\n");
|
|
return
|
|
end
|
|
|
|
M = afdmdv.M = afdmdv.Fs/afdmdv.Rs;
|
|
afdmdv.Nfilter = afdmdv.Nsym*M;
|
|
afdmdv.tx_filter_memory = zeros(afdmdv.Nc+1, afdmdv.Nfilter);
|
|
excess_bw = 0.5;
|
|
afdmdv.gt_alpha5_root = gen_rn_coeffs(excess_bw, 1/Fs, Rs, afdmdv.Nsym, afdmdv.M);
|
|
|
|
Fcentre = afdmdv.Fcentre = 1500;
|
|
afdmdv.Fsep = afdmdv.Rs*(1+excess_bw);
|
|
afdmdv.phase_tx = ones(afdmdv.Nc+1,1);
|
|
|
|
% non linear carrier spacing, combined with clip, helps PAPR a lot!
|
|
|
|
freq_hz = afdmdv.Fsep*( -Nc*Nd/2 - 0.5 + (1:Nc*Nd).^0.98 );
|
|
afdmdv.freq_pol = 2*pi*freq_hz/Fs;
|
|
afdmdv.freq = exp(j*afdmdv.freq_pol);
|
|
afdmdv.Fcentre = 1500;
|
|
|
|
afdmdv.fbb_rect = exp(j*2*pi*Fcentre/Fs);
|
|
afdmdv.fbb_phase_tx = 1;
|
|
afdmdv.fbb_phase_rx = 1;
|
|
|
|
afdmdv.Nrxdec = 31;
|
|
afdmdv.rxdec_coeff = fir1(afdmdv.Nrxdec-1, 0.25)';
|
|
afdmdv.rxdec_lpf_mem = zeros(1,afdmdv.Nrxdec-1+afdmdv.M);
|
|
|
|
P = afdmdv.P = 4;
|
|
afdmdv.phase_rx = ones(afdmdv.Nc+1,1);
|
|
afdmdv.Nfilter = afdmdv.Nsym*afdmdv.M;
|
|
afdmdv.rx_fdm_mem = zeros(1,afdmdv.Nfilter + afdmdv.M);
|
|
Q = afdmdv.Q = afdmdv.M/4;
|
|
if Q != floor(Q)
|
|
printf("\n Yeah .... if (Fs/Rs)/4 = M/4 isn't an integer we will just go and break things.\n\n");
|
|
end
|
|
|
|
afdmdv.rx_filter_mem_timing = zeros(afdmdv.Nc+1, afdmdv.Nt*afdmdv.P);
|
|
afdmdv.Nfiltertiming = afdmdv.M + afdmdv.Nfilter + afdmdv.M;
|
|
|
|
afdmdv.rx_filter_memory = zeros(afdmdv.Nc+1, afdmdv.Nfilter);
|
|
|
|
afdmdv.filt = 0;
|
|
afdmdv.prev_rx_symb = ones(1,afdmdv.Nc+1);
|
|
|
|
% COHPSK Init --------------------------------------------------------
|
|
|
|
acohpsk = standard_init();
|
|
acohpsk.framesize = framesize;
|
|
acohpsk.ldpc_code = 0;
|
|
acohpsk.ldpc_code_rate = 1;
|
|
acohpsk.Nc = Nc;
|
|
acohpsk.Rs = Rs;
|
|
acohpsk.Ns = 4;
|
|
acohpsk.coh_en = 1;
|
|
acohpsk.Nd = Nd;
|
|
acohpsk.modulation = 'qpsk';
|
|
acohpsk.do_write_pilot_file = 0; % enable this to dump pilot symbols to C .h file, e.g. if frame params change
|
|
acohpsk = symbol_rate_init(acohpsk);
|
|
acohpsk.Ndft = 1024;
|
|
acohpsk.f_est = afdmdv.Fcentre;
|
|
|
|
ch_fdm_frame_buf = zeros(1, Nsw*acohpsk.Nsymbrowpilot*afdmdv.M);
|
|
|
|
% -----------------------------------------------------------
|
|
|
|
tx_bits_log = [];
|
|
tx_symb_log = [];
|
|
rx_amp_log = [];
|
|
rx_phi_log = [];
|
|
ch_symb_log = [];
|
|
rx_symb_log = [];
|
|
rx_bits_log = [];
|
|
tx_bits_prev_log = [];
|
|
uvnoise_log = [];
|
|
nerr_log = [];
|
|
tx_baseband_log = [];
|
|
tx_fdm_frame_log = [];
|
|
ch_fdm_frame_log = [];
|
|
rx_fdm_frame_bb_log = [];
|
|
rx_filt_log = [];
|
|
rx_fdm_filter_log = [];
|
|
rx_baseband_log = [];
|
|
rx_fdm_frame_log = [];
|
|
ct_symb_ff_log = [];
|
|
rx_timing_log = [];
|
|
ratio_log = [];
|
|
foff_log = [];
|
|
f_est_log = [];
|
|
sig_rms_log = [];
|
|
noise_rms_log = [];
|
|
noise_rms_filt_log = [];
|
|
|
|
% Channel modeling and BER measurement ----------------------------------------
|
|
|
|
rand('state',1);
|
|
tx_bits_coh = round(rand(1,framesize*10));
|
|
ptx_bits_coh = 1;
|
|
|
|
Nerrs = Tbits = 0;
|
|
prev_tx_bits = prev_tx_bits2 = [];
|
|
error_positions_hist = zeros(1,framesize);
|
|
|
|
phase_ch = 1;
|
|
sync = initial_sync;
|
|
acohpsk.f_est = Fcentre;
|
|
acohpsk.f_fine_est = 0;
|
|
acohpsk.ct = 4;
|
|
acohpsk.ftrack_en = ftrack_en;
|
|
|
|
if fading_en
|
|
[spread spread_2ms hf_gain] = init_hf_model(Fs, frames*acohpsk.Nsymbrowpilot*afdmdv.M);
|
|
hf_n = 1;
|
|
nhfdelay = floor(hf_delay_ms*Fs/1000);
|
|
ch_fdm_delay = zeros(1, acohpsk.Nsymbrowpilot*M + nhfdelay);
|
|
end
|
|
|
|
% simulated SSB tx filter
|
|
|
|
[b, a] = cheby1(4, 3, [600, 2600]/(Fs/2));
|
|
[y filt_states] = filter(b,a,0);
|
|
h = freqz(b,a,(600:2600)/(Fs/(2*pi)));
|
|
filt_gain = (2600-600)/sum(abs(h) .^ 2); % ensures power after filter == before filter
|
|
|
|
noise_rms_filt = 0;
|
|
|
|
% main loop --------------------------------------------------------------------
|
|
|
|
% run mod and channel as aseparate loop so we can resample to simulate sample rate differences
|
|
|
|
for f=1:frames
|
|
tx_bits = tx_bits_coh(ptx_bits_coh:ptx_bits_coh+framesize-1);
|
|
ptx_bits_coh += framesize;
|
|
if ptx_bits_coh > length(tx_bits_coh)
|
|
ptx_bits_coh = 1;
|
|
end
|
|
|
|
tx_bits_log = [tx_bits_log tx_bits];
|
|
|
|
[tx_symb tx_bits] = bits_to_qpsk_symbols(acohpsk, tx_bits, []);
|
|
tx_symb_log = [tx_symb_log; tx_symb];
|
|
|
|
tx_fdm_frame = [];
|
|
for r=1:acohpsk.Nsymbrowpilot
|
|
tx_onesymb = tx_symb(r,:);
|
|
[tx_baseband afdmdv] = tx_filter(afdmdv, tx_onesymb);
|
|
tx_baseband_log = [tx_baseband_log tx_baseband];
|
|
[tx_fdm afdmdv] = fdm_upconvert(afdmdv, tx_baseband);
|
|
tx_fdm_frame = [tx_fdm_frame tx_fdm];
|
|
end
|
|
|
|
% clipping, which along with non-linear carrier spacing, improves PAPR
|
|
% The value of clip is a function of Nc and is adjusted experimentally
|
|
% such that the BER hit over no clipping at Es/No=8dB is small.
|
|
|
|
ind = find(abs(tx_fdm_frame) > clip);
|
|
tx_fdm_frame(ind) = clip*exp(j*angle(tx_fdm_frame(ind)));
|
|
|
|
tx_fdm_frame_log = [tx_fdm_frame_log tx_fdm_frame];
|
|
|
|
%
|
|
% Channel --------------------------------------------------------------------
|
|
%
|
|
|
|
% simulate tx SSB filter with ripple
|
|
|
|
if ssb_tx_filt
|
|
[tx_fdm_frame filt_states] = filter(b,a,sqrt(filt_gain)*tx_fdm_frame, filt_states);
|
|
end
|
|
|
|
% frequency offset and frequency drift
|
|
|
|
ch_fdm_frame = zeros(1,acohpsk.Nsymbrowpilot*M);
|
|
for i=1:acohpsk.Nsymbrowpilot*M
|
|
foff_rect = exp(j*2*pi*foff/Fs);
|
|
foff += dfoff;
|
|
phase_ch *= foff_rect;
|
|
ch_fdm_frame(i) = tx_fdm_frame(i) * phase_ch;
|
|
end
|
|
foff_log = [foff_log foff];
|
|
phase_ch /= abs(phase_ch);
|
|
|
|
% optional fading
|
|
|
|
if fading_en
|
|
ch_fdm_delay(1:nhfdelay) = ch_fdm_delay(acohpsk.Nsymbrowpilot*M+1:nhfdelay+acohpsk.Nsymbrowpilot*M);
|
|
ch_fdm_delay(nhfdelay+1:nhfdelay+acohpsk.Nsymbrowpilot*M) = ch_fdm_frame;
|
|
|
|
for i=1:acohpsk.Nsymbrowpilot*M
|
|
ahf_model = hf_gain*(spread(hf_n)*ch_fdm_frame(i) + spread_2ms(hf_n)*ch_fdm_delay(i));
|
|
ch_fdm_frame(i) = ahf_model;
|
|
hf_n++;
|
|
end
|
|
end
|
|
|
|
% each carrier has power = 2, total power 2Nc, total symbol rate NcRs, noise BW B=Fs
|
|
% Es/No = (C/Rs)/(N/B), N = var = 2NcFs/NcRs(Es/No) = 2Fs/Rs(Es/No)
|
|
|
|
variance = 2*Fs/(acohpsk.Rs*EsNo);
|
|
uvnoise = sqrt(0.5)*(randn(1,acohpsk.Nsymbrowpilot*M) + j*randn(1,acohpsk.Nsymbrowpilot*M));
|
|
uvnoise_log = [uvnoise_log uvnoise];
|
|
noise = sqrt(variance)*uvnoise;
|
|
|
|
ch_fdm_frame += noise;
|
|
|
|
ch_fdm_frame_log = [ch_fdm_frame_log ch_fdm_frame];
|
|
end
|
|
|
|
% simulate difference in sample clocks
|
|
|
|
tin=1;
|
|
tout=1;
|
|
ch_fdm_frame_log_out = zeros(1,length(ch_fdm_frame_log));
|
|
while tin < length(ch_fdm_frame_log)
|
|
t1 = floor(tin);
|
|
t2 = ceil(tin);
|
|
f = tin - t1;
|
|
ch_fdm_frame_log_out(tout) = (1-f)*ch_fdm_frame_log(t1) + f*ch_fdm_frame_log(t2);
|
|
tout += 1;
|
|
tin += 1+sample_rate_ppm/1E6;
|
|
end
|
|
ch_fdm_frame_log = ch_fdm_frame_log_out(1:tout-1);
|
|
|
|
% Now run demod ----------------------------------------------------------------
|
|
|
|
ch_fdm_frame_log_index = 1;
|
|
nin = M;
|
|
f = 0;
|
|
nin_frame = acohpsk.Nsymbrowpilot*M;
|
|
|
|
%while (ch_fdm_frame_log_index + acohpsk.Nsymbrowpilot*M+M/P) < length(ch_fdm_frame_log)
|
|
for f=1:frames;
|
|
acohpsk.frame = f;
|
|
|
|
ch_fdm_frame = ch_fdm_frame_log(ch_fdm_frame_log_index:ch_fdm_frame_log_index + nin_frame - 1);
|
|
ch_fdm_frame_log_index += nin_frame;
|
|
|
|
%
|
|
% Demod ----------------------------------------------------------------------
|
|
%
|
|
|
|
% store two frames of received samples so we can rewind if we get a good candidate
|
|
|
|
ch_fdm_frame_buf(1:Nsw*acohpsk.Nsymbrowpilot*M-nin_frame) = ch_fdm_frame_buf(nin_frame+1:Nsw*acohpsk.Nsymbrowpilot*M);
|
|
ch_fdm_frame_buf(Nsw*acohpsk.Nsymbrowpilot*M-nin_frame+1:Nsw*acohpsk.Nsymbrowpilot*M) = ch_fdm_frame;
|
|
|
|
next_sync = sync;
|
|
|
|
% if out of sync do Initial Freq offset estimation over NSW frames to flush out memories
|
|
|
|
if (sync == 0)
|
|
|
|
% we can test +/- 20Hz, so we break this up into 3 tests to cover +/- 60Hz
|
|
|
|
max_ratio = 0;
|
|
for acohpsk.f_est = Fcentre-40:40:Fcentre+40
|
|
|
|
printf(" [%d] acohpsk.f_est: %f +/- 20\n", f, acohpsk.f_est);
|
|
|
|
% we are out of sync so reset f_est and process two frames to clean out memories
|
|
|
|
[ch_symb rx_timing rx_filt rx_baseband afdmdv acohpsk.f_est] = rate_Fs_rx_processing(afdmdv, ch_fdm_frame_buf, acohpsk.f_est, Nsw*acohpsk.Nsymbrowpilot, nin, 0);
|
|
rx_baseband_log = [rx_baseband_log rx_baseband];
|
|
|
|
rx_filt_log = [rx_filt_log rx_filt];
|
|
ch_symb_log = [ch_symb_log; ch_symb];
|
|
rx_timing_log = [rx_timing_log rx_timing];
|
|
|
|
for i=1:Nsw-1
|
|
acohpsk.ct_symb_buf = update_ct_symb_buf(acohpsk.ct_symb_buf, ch_symb((i-1)*acohpsk.Nsymbrowpilot+1:i*acohpsk.Nsymbrowpilot,:), acohpsk.Nct_sym_buf, acohpsk.Nsymbrowpilot);
|
|
end
|
|
[anext_sync acohpsk] = frame_sync_fine_freq_est(acohpsk, ch_symb((Nsw-1)*acohpsk.Nsymbrowpilot+1:Nsw*acohpsk.Nsymbrowpilot,:), sync, next_sync);
|
|
|
|
if anext_sync == 1
|
|
%printf(" [%d] acohpsk.ratio: %f\n", f, acohpsk.ratio);
|
|
if acohpsk.ratio > max_ratio
|
|
max_ratio = acohpsk.ratio;
|
|
f_est = acohpsk.f_est - acohpsk.f_fine_est;
|
|
next_sync = anext_sync;
|
|
end
|
|
end
|
|
end
|
|
|
|
if next_sync == 1
|
|
|
|
% we've found a sync candidate!
|
|
% re-process last two frames with adjusted f_est then check again
|
|
|
|
acohpsk.f_est = f_est;
|
|
|
|
printf(" [%d] trying sync and f_est: %f\n", f, acohpsk.f_est);
|
|
|
|
[ch_symb rx_timing rx_filt rx_baseband afdmdv f_est] = rate_Fs_rx_processing(afdmdv, ch_fdm_frame_buf, acohpsk.f_est, Nsw*acohpsk.Nsymbrowpilot, nin, 0);
|
|
rx_baseband_log = [rx_baseband_log rx_baseband];
|
|
rx_filt_log = [rx_filt_log rx_filt];
|
|
ch_symb_log = [ch_symb_log; ch_symb];
|
|
rx_timing_log = [rx_timing_log rx_timing];
|
|
|
|
for i=1:Nsw-1
|
|
acohpsk.ct_symb_buf = update_ct_symb_buf(acohpsk.ct_symb_buf, ch_symb((i-1)*acohpsk.Nsymbrowpilot+1:i*acohpsk.Nsymbrowpilot,:), acohpsk.Nct_sym_buf, acohpsk.Nsymbrowpilot);
|
|
end
|
|
[next_sync acohpsk] = frame_sync_fine_freq_est(acohpsk, ch_symb((Nsw-1)*acohpsk.Nsymbrowpilot+1:Nsw*acohpsk.Nsymbrowpilot,:), sync, next_sync);
|
|
if abs(acohpsk.f_fine_est) > 2
|
|
printf(" [%d] Hmm %f is a bit big so back to coarse est ...\n", f, acohpsk.f_fine_est);
|
|
next_sync = 0;
|
|
end
|
|
|
|
if acohpsk.ratio < 0.9
|
|
next_sync = 0;
|
|
end
|
|
if next_sync == 1
|
|
% OK we are in sync!
|
|
% demodulate first frame (demod completed below)
|
|
|
|
printf(" [%d] in sync! f_est: %f ratio: %f \n", f, f_est, acohpsk.ratio);
|
|
acohpsk.ct_symb_ff_buf(1:acohpsk.Nsymbrowpilot+2,:) = acohpsk.ct_symb_buf(acohpsk.ct+1:acohpsk.ct+acohpsk.Nsymbrowpilot+2,:);
|
|
end
|
|
end
|
|
end
|
|
|
|
% If in sync just do sample rate processing on latest frame
|
|
|
|
if sync == 1
|
|
[ch_symb rx_timing rx_filt rx_baseband afdmdv acohpsk.f_est] = rate_Fs_rx_processing(afdmdv, ch_fdm_frame, acohpsk.f_est, acohpsk.Nsymbrowpilot, nin, acohpsk.ftrack_en);
|
|
[next_sync acohpsk] = frame_sync_fine_freq_est(acohpsk, ch_symb, sync, next_sync);
|
|
|
|
acohpsk.ct_symb_ff_buf(1:2,:) = acohpsk.ct_symb_ff_buf(acohpsk.Nsymbrowpilot+1:acohpsk.Nsymbrowpilot+2,:);
|
|
acohpsk.ct_symb_ff_buf(3:acohpsk.Nsymbrowpilot+2,:) = acohpsk.ct_symb_buf(acohpsk.ct+3:acohpsk.ct+acohpsk.Nsymbrowpilot+2,:);
|
|
|
|
rx_baseband_log = [rx_baseband_log rx_baseband];
|
|
rx_filt_log = [rx_filt_log rx_filt];
|
|
ch_symb_log = [ch_symb_log; ch_symb];
|
|
rx_timing_log = [rx_timing_log rx_timing];
|
|
f_est_log = [f_est_log acohpsk.f_est];
|
|
end
|
|
|
|
% if we are in sync complete demodulation with symbol rate processing
|
|
|
|
if (next_sync == 1) || (sync == 1)
|
|
[rx_symb rx_bits rx_symb_linear amp_ phi_ sig_rms noise_rms] = qpsk_symbols_to_bits(acohpsk, acohpsk.ct_symb_ff_buf);
|
|
rx_symb_log = [rx_symb_log; rx_symb];
|
|
rx_amp_log = [rx_amp_log; amp_];
|
|
rx_phi_log = [rx_phi_log; phi_];
|
|
rx_bits_log = [rx_bits_log rx_bits];
|
|
tx_bits_prev_log = [tx_bits_prev_log prev_tx_bits2];
|
|
ratio_log = [ratio_log acohpsk.ratio];
|
|
ct_symb_ff_log = [ct_symb_ff_log; acohpsk.ct_symb_ff_buf(1:acohpsk.Nsymbrowpilot,:)];
|
|
sig_rms_log = [sig_rms_log sig_rms];
|
|
noise_rms_log = [noise_rms_log noise_rms];
|
|
noise_rms_filt = 0.9*noise_rms_filt + 0.1*noise_rms;
|
|
noise_rms_filt_log = [noise_rms_filt_log noise_rms_filt];
|
|
|
|
% BER stats
|
|
|
|
if f > 2
|
|
error_positions = xor(tx_bits_log((f-3)*framesize+1:(f-2)*framesize), rx_bits);
|
|
Nerrs += sum(error_positions);
|
|
nerr_log = [nerr_log sum(error_positions)];
|
|
Tbits += length(error_positions);
|
|
error_positions_hist += error_positions;
|
|
end
|
|
printf("\r [%d]", f);
|
|
end
|
|
|
|
% reset BER stats if we lose sync
|
|
|
|
if sync == 1
|
|
%Nerrs = 0;
|
|
%Tbits = 0;
|
|
%nerr_log = [];
|
|
end
|
|
|
|
[sync acohpsk] = sync_state_machine(acohpsk, sync, next_sync);
|
|
|
|
% work out how many samples we need for next time
|
|
|
|
nin = M;
|
|
if sync == 1
|
|
if rx_timing(length(rx_timing)) > M/P
|
|
nin = M + M/P;
|
|
end
|
|
if rx_timing(length(rx_timing)) < -M/P
|
|
nin = M - M/P;
|
|
end
|
|
end
|
|
nin_frame = (acohpsk.Nsymbrowpilot-1)*M + nin;
|
|
|
|
prev_tx_bits2 = prev_tx_bits;
|
|
prev_tx_bits = tx_bits;
|
|
|
|
end
|
|
|
|
ber = Nerrs/Tbits;
|
|
printf("\nOctave EsNodB: %4.1f ber..: %4.3f Nerrs..: %d Tbits..: %d\n", EsNodB, ber, Nerrs, Tbits);
|
|
|
|
if compare_with_c
|
|
|
|
% Output vectors from C port ---------------------------------------------------
|
|
|
|
load tcohpsk_out.txt
|
|
|
|
% Determine bit error rate
|
|
|
|
|
|
sz = length(rx_bits_log_c);
|
|
Nerrs_c = sum(xor(tx_bits_log(1:sz-framesize), rx_bits_log_c(framesize+1:sz)));
|
|
Tbits_c = length(tx_bits_prev_log);
|
|
ber_c = Nerrs_c/Tbits_c;
|
|
printf("C EsNodB.....: %4.1f ber_c: %4.3f Nerrs_c: %d Tbits_c: %d\n", EsNodB, ber_c, Nerrs_c, Tbits_c);
|
|
|
|
stem_sig_and_error(1, 111, tx_bits_log_c, tx_bits_log - tx_bits_log_c, 'tx bits', [1 length(tx_bits_log) -1.5 1.5])
|
|
|
|
stem_sig_and_error(2, 211, real(tx_symb_log_c), real(tx_symb_log - tx_symb_log_c), 'tx symb re', [1 length(tx_symb_log_c) -1.5 1.5])
|
|
stem_sig_and_error(2, 212, imag(tx_symb_log_c), imag(tx_symb_log - tx_symb_log_c), 'tx symb im', [1 length(tx_symb_log_c) -1.5 1.5])
|
|
|
|
stem_sig_and_error(3, 211, real(tx_fdm_frame_log_c), real(tx_fdm_frame_log - tx_fdm_frame_log_c), 'tx fdm frame re', [1 length(tx_fdm_frame_log) -10 10])
|
|
stem_sig_and_error(3, 212, imag(tx_fdm_frame_log_c), imag(tx_fdm_frame_log - tx_fdm_frame_log_c), 'tx fdm frame im', [1 length(tx_fdm_frame_log) -10 10])
|
|
stem_sig_and_error(4, 211, real(ch_fdm_frame_log_c), real(ch_fdm_frame_log - ch_fdm_frame_log_c), 'ch fdm frame re', [1 length(ch_fdm_frame_log) -10 10])
|
|
stem_sig_and_error(4, 212, imag(ch_fdm_frame_log_c), imag(ch_fdm_frame_log - ch_fdm_frame_log_c), 'ch fdm frame im', [1 length(ch_fdm_frame_log) -10 10])
|
|
|
|
c = 1;
|
|
stem_sig_and_error(5, 211, real(rx_baseband_log_c(c,:)), real(rx_baseband_log(c,:) - rx_baseband_log_c(c,:)), 'rx baseband re', [1 length(rx_baseband_log) -10 10])
|
|
stem_sig_and_error(5, 212, imag(rx_baseband_log_c(c,:)), imag(rx_baseband_log(c,:) - rx_baseband_log_c(c,:)), 'rx baseband im', [1 length(rx_baseband_log) -10 10])
|
|
stem_sig_and_error(6, 211, real(rx_filt_log_c(c,:)), real(rx_filt_log(c,:) - rx_filt_log_c(c,:)), 'rx filt re', [1 length(rx_filt_log) -1 1])
|
|
stem_sig_and_error(6, 212, imag(rx_filt_log_c(c,:)), imag(rx_filt_log(c,:) - rx_filt_log_c(c,:)), 'rx filt im', [1 length(rx_filt_log) -1 1])
|
|
|
|
[n m] = size(ch_symb_log);
|
|
stem_sig_and_error(7, 211, real(ch_symb_log_c), real(ch_symb_log - ch_symb_log_c), 'ch symb re', [1 n -1.5 1.5])
|
|
stem_sig_and_error(7, 212, imag(ch_symb_log_c), imag(ch_symb_log - ch_symb_log_c), 'ch symb im', [1 n -1.5 1.5])
|
|
|
|
[n m] = size(rx_symb_log);
|
|
stem_sig_and_error(8, 211, rx_amp_log_c, rx_amp_log - rx_amp_log_c, 'Amp Est', [1 n -1.5 1.5])
|
|
phi_log_diff = rx_phi_log - rx_phi_log_c;
|
|
phi_log_diff(find(phi_log_diff > pi)) -= 2*pi;
|
|
phi_log_diff(find(phi_log_diff < -pi)) += 2*pi;
|
|
stem_sig_and_error(8, 212, rx_phi_log_c, phi_log_diff, 'Phase Est', [1 n -4 4])
|
|
stem_sig_and_error(9, 211, real(rx_symb_log_c), real(rx_symb_log - rx_symb_log_c), 'rx symb re', [1 n -1.5 1.5])
|
|
stem_sig_and_error(9, 212, imag(rx_symb_log_c), imag(rx_symb_log - rx_symb_log_c), 'rx symb im', [1 n -1.5 1.5])
|
|
|
|
stem_sig_and_error(10, 111, rx_bits_log_c, rx_bits_log - rx_bits_log_c, 'rx bits', [1 length(rx_bits_log) -1.5 1.5])
|
|
stem_sig_and_error(11, 111, f_est_log_c - Fcentre - foff, f_est_log - f_est_log_c, 'f est', [1 length(f_est_log) -5 5])
|
|
stem_sig_and_error(12, 111, rx_timing_log_c, rx_timing_log_c - rx_timing_log, 'rx timing', [1 length(rx_timing_log) -M M])
|
|
|
|
check(tx_bits_log, tx_bits_log_c, 'tx_bits');
|
|
check(tx_symb_log, tx_symb_log_c, 'tx_symb');
|
|
check(tx_fdm_frame_log, tx_fdm_frame_log_c, 'tx_fdm_frame',0.01);
|
|
check(ch_fdm_frame_log, ch_fdm_frame_log_c, 'ch_fdm_frame',0.01);
|
|
check(ch_symb_log, ch_symb_log_c, 'ch_symb',0.05);
|
|
check(rx_amp_log, rx_amp_log_c, 'rx_amp_log',0.01);
|
|
check(phi_log_diff, zeros(length(phi_log_diff), Nc*Nd), 'rx_phi_log',0.1);
|
|
check(rx_symb_log, rx_symb_log_c, 'rx_symb',0.01);
|
|
check(rx_timing_log, rx_timing_log_c, 'rx_timing',0.005);
|
|
check(rx_bits_log, rx_bits_log_c, 'rx_bits');
|
|
check(f_est_log, f_est_log_c, 'f_est');
|
|
check(sig_rms_log, sig_rms_log_c, 'sig_rms');
|
|
check(noise_rms_log, noise_rms_log_c, 'noise_rms');
|
|
|
|
printf("\npasses: %d fails: %d\n", passes, fails);
|
|
|
|
else
|
|
|
|
papr = max(tx_fdm_frame_log.*conj(tx_fdm_frame_log)) / mean(tx_fdm_frame_log.*conj(tx_fdm_frame_log));
|
|
papr_dB = 10*log10(papr);
|
|
printf("av tx pwr: %4.2f PAPR: %4.2f av rx pwr: %4.2f\n", var(tx_fdm_frame_log), papr_dB, var(ch_fdm_frame_log));
|
|
|
|
% some other useful plots
|
|
|
|
f = figure(1)
|
|
clf
|
|
subplot(211)
|
|
plot(real(tx_fdm_frame_log))
|
|
title('tx fdm real');
|
|
subplot(212)
|
|
plot(imag(tx_fdm_frame_log))
|
|
title('tx fdm imag');
|
|
|
|
f = figure(2)
|
|
clf
|
|
spec = 20*log10(abs(fft(tx_fdm_frame_log)));
|
|
l = length(spec);
|
|
plot((Fs/l)*(1:l), spec)
|
|
axis([1 Fs/2 0 max(spec)]);
|
|
title('tx spectrum');
|
|
ylabel('Amplitude (dB)')
|
|
xlabel('Frequency (Hz)')
|
|
grid;
|
|
|
|
f = figure(3)
|
|
clf;
|
|
% plot combined signals to show diversity gains
|
|
combined = rx_symb_log(:,1:Nc);
|
|
for d=2:Nd
|
|
combined += rx_symb_log(:, (d-1)*Nc+1:d*Nc);
|
|
end
|
|
plot(combined*exp(j*pi/4)/sqrt(Nd),'+')
|
|
title('Scatter');
|
|
ymax = abs(max(max(combined)));
|
|
axis([-ymax ymax -ymax ymax])
|
|
|
|
f = figure(4)
|
|
clf;
|
|
subplot(211)
|
|
plot(rx_phi_log)
|
|
subplot(212)
|
|
plot(rx_amp_log)
|
|
|
|
f = figure(5)
|
|
clf;
|
|
subplot(211)
|
|
plot(rx_timing_log)
|
|
title('rx timing');
|
|
subplot(212)
|
|
stem(ratio_log)
|
|
title('Sync ratio');
|
|
|
|
f = figure(6)
|
|
clf;
|
|
subplot(211)
|
|
stem(nerr_log)
|
|
title('Bit Errors');
|
|
subplot(212)
|
|
plot(noise_rms_filt_log,'r', sig_rms_log,'g');
|
|
title('Est rms signal and noise')
|
|
|
|
f = figure(7);
|
|
clf;
|
|
subplot(211)
|
|
plot(foff_log,';freq offset;');
|
|
hold on;
|
|
plot(f_est_log - Fcentre,'g;freq offset est;');
|
|
hold off;
|
|
title('freq offset');
|
|
legend("boxoff");
|
|
subplot(212)
|
|
plot(foff_log(1:length(f_est_log)) - f_est_log + Fcentre)
|
|
title('freq offset estimation error');
|
|
|
|
f = figure(8)
|
|
clf
|
|
h = freqz(b,a,Fs/2);
|
|
plot(20*log10(abs(h)))
|
|
axis([1 Fs/2 -20 0])
|
|
grid
|
|
title('SSB tx filter')
|
|
|
|
f = figure(9)
|
|
clf
|
|
plot(error_positions_hist)
|
|
title('histogram of bit errors')
|
|
|
|
|
|
end
|
|
|
|
|
|
% function to write C header file of noise samples so C version gives
|
|
% exactly the same results
|
|
|
|
function write_noise_file(uvnoise_log)
|
|
|
|
m = length(uvnoise_log);
|
|
|
|
filename = sprintf("../unittest/noise_samples.h");
|
|
f=fopen(filename,"wt");
|
|
fprintf(f,"/* unit variance complex noise samples */\n\n");
|
|
fprintf(f,"/* Generated by write_noise_file() Octave function */\n\n");
|
|
fprintf(f,"COMP noise[]={\n");
|
|
for r=1:m
|
|
if r < m
|
|
fprintf(f, " {%f,%f},\n", real(uvnoise_log(r)), imag(uvnoise_log(r)));
|
|
else
|
|
fprintf(f, " {%f,%f}\n};", real(uvnoise_log(r)), imag(uvnoise_log(r)));
|
|
end
|
|
end
|
|
|
|
fclose(f);
|
|
endfunction
|
|
|
|
|
|
% function to write float fading samples for use by C programs
|
|
|
|
%function write_noise_file(raw_file_name, Fs, dopplerSpreadHz, len_samples)
|
|
% spread = doppler_spread(dopplerSpreadHz, Fs, len_samples);
|
|
% spread_2ms = doppler_spread(dopplerSpreadHz, Fs, len_samples);
|
|
% hf_gain = 1.0/sqrt(var(spread)+var(spread_2ms));
|
|
%
|
|
% % interleave real imag samples
|
|
%
|
|
% inter = zeros(1,len_samples*4);
|
|
% inter(1:4) = hf_gain;
|
|
% for i=1:len_samples
|
|
% inter(i*4+1) = real(spread(i));
|
|
% inter(i*4+2) = imag(spread(i));
|
|
% inter(i*4+3) = real(spread_2ms(i));
|
|
% inter(i*4+4) = imag(spread_2ms(i));
|
|
% end
|
|
% f = fopen(raw_file_name,"wb");
|
|
% fwrite(f, inter, "float32");
|
|
% fclose(f);
|
|
%endfunction
|