aboutsummaryrefslogtreecommitdiff
path: root/octave/ofdm_acquisition.m
blob: fd56a99c9653f66b41e43e4a411dff8584bea813 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
% ofdm_acquisition.m
% David Rowe Jan 2021
%
% Simulations used for development of HF data modem burst mode acquisition
%
% To run headless on a server:
%
%   DISPLAY=\"\" octave-cli --no-gui -qf ofdm_acquisition.m > 210218.txt &

ofdm_lib;
channel_lib;

% Build a vector of Tx bursts in noise, one burst occurs every padded_burst_len samples

function [rx tx_preamble tx_postamble burst_len padded_burst_len ct_targets states] = generate_bursts(sim_in)
  config = ofdm_init_mode(sim_in.mode);
  states = ofdm_init(config);
  ofdm_load_const;

  tx_preamble = states.tx_preamble; tx_postamble = states.tx_postamble;
   
  Nbursts = sim_in.Nbursts;
  tx_bits = create_ldpc_test_frame(states, coded_frame=0);
  tx_burst = [tx_preamble ofdm_mod(states, tx_bits) tx_postamble];
  burst_len = length(tx_burst);
  tx_burst = ofdm_hilbert_clipper(states, tx_burst, tx_clip_en=0);
  padded_burst_len = Fs+burst_len+Fs;
  
  tx = []; ct_targets = [];
  for f=1:Nbursts
    % 100ms of jitter in the burst start point
    jitter = floor(rand(1,1)*0.1*Fs);
    tx_burst_padded = [zeros(1,Fs+jitter) tx_burst zeros(1,Fs-jitter)];
    ct_targets = [ct_targets Fs+jitter];
    tx = [tx tx_burst_padded];
  end
  
  % adjust channel simulator SNR setpoint given (burst on length)/(sample length) ratio
  mark_space_SNR_offset = 10*log10(burst_len/padded_burst_len);
  SNRdB_setpoint = sim_in.SNR3kdB + mark_space_SNR_offset;
  %printf("SNR3kdB: %f Burst offset: %f\n", sim_in.SNR3kdB, mark_space_SNR_offset)
  rx = channel_simulate(Fs, SNRdB_setpoint, sim_in.foff_Hz, sim_in.channel, tx);

  % optional BPF
  if strcmp(sim_in.mode,"datac4") || strcmp(sim_in.mode,"datac13")
    [rx delay_samples] = ofdm_complex_bandpass_filter(states, sim_in.mode, rx);
    l = length(rx); rx = [rx(delay_samples:l) zeros(1,delay_samples)];
  end
endfunction


function results = evaluate_candidate(states, det, i, Nsamperburstpadded, ct_target, foff_Hz, ttol_samples, ftol_hz)
  results.candidate = 0;
  if det.timing_mx > states.timing_mx_thresh
    % OK we have located a candidate peak
    
    % re-base ct_est to be wrt start of current burst reference frame
    ct_est = det.ct_est - (i-1)*Nsamperburstpadded;
        
    delta_ct = abs(ct_est-ct_target);
    delta_foff = det.foff_est-foff_Hz;
	
    ok = (abs(delta_ct) < ttol_samples) && (abs(delta_foff) < ftol_hz);
    
    results.candidate = 1; results.ct_est = ct_est; results.delta_ct = delta_ct; results.delta_foff = delta_foff; results.ok = ok;
  end    
endfunction


% test frame by frame acquisition algorithm

function Pa = frame_by_frame_acquisition_test(mode="datac1", Ntests=10, channel="awgn", SNR3kdB=100, foff_Hz=0, verbose_top=0) 
  sim_in.SNR3kdB = SNR3kdB;
  sim_in.channel = channel;
  sim_in.foff_Hz = foff_Hz;  
  sim_in.mode = mode;
  sim_in.Nbursts = Ntests;
  [rx tx_preamble tx_postamble Nsamperburst Nsamperburstpadded ct_targets states] = generate_bursts(sim_in);
  states.verbose = bitand(verbose_top,3);
  ofdm_load_const;
  
  timing_mx_log = []; ct_log = []; delta_ct_log = []; delta_foff_log = []; state_log = [];

  % allowable tolerance for acquistion
  ftol_hz = 2;              % we can sync up on this (todo: make mode selectable)
  ttol_samples = 0.006*Fs;  % CP length (todo: make mode selectable)
  target_acq = zeros(1,Ntests);
  
  state = 'acquisition';
  
  for n=1:Nsamperframe:length(rx)-2*Nsamperframe
     pre = burst_acquisition_detector(states, rx, n, tx_preamble);   
     post = burst_acquisition_detector(states, rx, n, tx_postamble);
     
     % adjust time reference for this simulation
     pre.ct_est += n;
     post.ct_est += n;

     timing_mx_log = [timing_mx_log [pre.timing_mx; post.timing_mx]];
     
     % state machine to simulate acquisition/demod processing
     
     next_state = state;
     if strcmp(state,'acquisition')
       state_log = [state_log 0];

       % work out what burst we are evaluating
       i = ceil(n/Nsamperburstpadded); % i-th burst we are evaluating
       w = (i-1)*Nsamperburstpadded;   % offset of burst in s() for plotting purposes
       ct_target_pre = ct_targets(i);
       ct_target_post = ct_targets(i) + Nsamperburst - length(tx_preamble);

       pre_eval = evaluate_candidate(states, pre, i, Nsamperburstpadded, ct_target_pre, foff_Hz, ttol_samples, ftol_hz);
       post_eval = evaluate_candidate(states, post, i, Nsamperburstpadded, ct_target_post, foff_Hz, ttol_samples, ftol_hz);
     
       if pre_eval.candidate
         if pre_eval.ok == 0
           target_acq(i) = -1; % flag bad candidate
         end
         if pre_eval.ok && (target_acq(i) == 0)
           target_acq(i) = 1;  % flag a successful acquisition
           next_state = "demod";
           modem_frame = 0;
         end
         delta_ct_log = [delta_ct_log pre_eval.delta_ct];
         delta_foff_log = [delta_foff_log pre_eval.delta_foff];
         ct_log = [ct_log w+pre_eval.ct_est];
         if states.verbose
           printf("Pre  i: %2d n: %8d ct_est: %6d delta_ct: %6d foff_est: %5.1f timing_mx: %3.2f Acq: %2d\n",
                  i, n, pre_eval.ct_est, pre_eval.delta_ct, pre.foff_est, pre.timing_mx, target_acq(i));
         end
       end
         
       if post_eval.candidate
         if post_eval.ok == 0
           target_acq(i) = -1; % flag bad candidate
         end
         if post_eval.ok && (target_acq(i) == 0)
           target_acq(i) = 1;  % flag a successful acquisition 
           next_state = "demod";
           modem_frame = Np-2;                               
         end
         delta_ct_log = [delta_ct_log post_eval.delta_ct];
         delta_foff_log = [delta_foff_log post_eval.delta_foff];
         ct_log = [ct_log w+post_eval.ct_est];
         if states.verbose
           printf("Post i: %2d n: %8d ct_est: %6d delta_ct: %6d foff_est: %5.1f timing_mx: %3.2f Acq: %2d\n",
                  i, n, post_eval.ct_est, post_eval.delta_ct, post.foff_est, post.timing_mx, target_acq(i));
         end
       end
     end
    
     if strcmp(state, "demod")
        state_log = [state_log 1];
        modem_frame++;
        if modem_frame > states.Np
          next_state = "acquisition";
        end
     end
     
     state = next_state;         
  end
  
  if bitand(verbose_top,8)
    figure(1); clf; 
               plot(timing_mx_log(1,:),'+-;preamble;'); 
               hold on; 
               plot(timing_mx_log(2,:),'o-;postamble;'); 
               plot(0.45+0.1*state_log,'-g;state;'); 
               title('mx log'); axis([0 length(timing_mx_log) 0 1.0]); grid;
               hold off;
    figure(4); clf; plot(real(rx)); axis([0 length(rx) -3E4 3E4]);
               hold on;
               plot(ct_log,zeros(1,length(ct_log)),'r+','markersize', 25, 'linewidth', 2);
               hold off; 
    figure(5); clf; plot_specgram(rx, Fs, 500, 2500);
    all_mx = [ timing_mx_log(1,:) timing_mx_log(2,:)];
    figure(6); clf; [nn xx] = hist(all_mx); semilogy(xx,nn+1); grid;
    figure(7); clf; cdf = empirical_cdf(0:0.1:1,all_mx); plot(0:0.1:1, cdf); grid;

  end
  
  Pacq = length(find(target_acq == 1))/Ntests;
  Pfalse_acq = length(find(target_acq == -1))/Ntests;
  printf("%s %s SNR: %3.1f foff: %3.1f P(acq) = %3.2f P(false_acq) = %3.2f\n", mode, channel, SNR3kdB, foff_Hz,
         Pacq, Pfalse_acq);
endfunction


% test frame by frame across modes, channels, and SNR (don't worry about sweeping freq)

function acquistion_curves_frame_by_frame_modes_channels_snr(Ntests=5, quick_test=0)
  modes={'datac0', 'datac1', 'datac3'};
  if quick_test
    Ntests = 5;
    channels={'awgn','mpp'}; SNR = [0 5];
  else
    channels={'awgn', 'mpm', 'mpp', 'notch'};
    SNR = [ -10 -5 -3.5 -1.5 0 1.5 3.5 5 7.5 10 15];
  end
  
  cc = ['b' 'g' 'k' 'c' 'm' 'r'];
  pt = ['+' '*' 'x' 'o' '+' '*'];
 
  for i=1:length(modes)
    figure(i); clf; hold on; title(sprintf("%s P(acquisition)", modes{i}));
  end
  
  for m=1:length(modes)
    figure(m);
    for c=1:length(channels)
      Pa_log = [];
      for s=1:length(SNR)
        Pa = frame_by_frame_acquisition_test(modes{m}, Ntests, channels{c}, SNR(s), foff_hz=0, verbose=1);
        Pa_log = [Pa_log Pa];
      end
      l = sprintf('%c%c-;%s;', cc(c), pt(c), channels{c}); 
      plot(SNR, Pa_log, l, 'markersize', 10);
    end
  end
  
  for i=1:length(modes)
    figure(i); grid;
    xlabel('SNR3k dB'); legend('location', 'southeast'); 
    xlim([min(SNR)-2 max(SNR)+2]); ylim([0 1.1]);
    print('-dpng', sprintf("%s_ofdm_dev_acq_curves_fbf_%s.png", datestr(clock(),"yyyy-mm-dd"), modes{i}));
  end 
endfunction

% main starts here -----------------------------------------

format;
more off;
pkg load signal;
graphics_toolkit ("gnuplot");
randn('seed',1);

% ---------------------------------------------------------
% choose simulation to run here 
% ---------------------------------------------------------

if exist("ctest","var")
  % simple tests to run as part of ctests
  frame_by_frame_acquisition_test("datac0", Ntests=5, 'mpp', SNR3kdB=5, foff_hz=0, verbose=1+8);
else
  % other development work here
  frame_by_frame_acquisition_test("datac13", Ntests=100, 'mpp', SNR3kdB=-4, foff_hz=0, verbose=1+8);
  %acquistion_curves_frame_by_frame_modes_channels_snr(Ntests=50, quick_test=0)
end