1% fdmdv_ut_freq_off.m 2% David Rowe 17 June 2014 3% 4% Unit Test program for freq offset estimation in FDMDV modem. 5% 6% Copyright David Rowe 2012 This program is 7% distributed under the terms of the GNU General Public License 8% Version 2 9 10% [ ] sweep of different delays 11% [ ] sweep of Eb/No 12% [ ] sweep of freq offsets 13% [ ] step change in foff 14% + time to respond 15% [ ] plot/print pass fail/relevant stats 16% + variance 17% + histogram of freq ests? 18 19fdmdv; % load modem code 20hf_sim; % load hf sim code 21 22% --------------------------------------------------------------------- 23% Eb/No calculations. We need to work out Eb/No for each FDM carrier. 24% Total power is sum of power in all FDM carriers. These calcs set the 25% Eb/No of the data carriers, Eb/No of pilot will be higher. 26% --------------------------------------------------------------------- 27 28function [Nsd SNR] = calc_Nsd_from_EbNo(EbNo_dB) 29 global Rs; 30 global Nb; 31 global Nc; 32 global Fs; 33 34 C = 1; % power of each FDM carrier (energy/sample). Total Carrier power should = Nc*C = Nc 35 N = 1; % total noise power (energy/sample) of noise source across entire bandwidth 36 37 % Eb = Carrier power * symbol time / (bits/symbol) 38 % = C *(1/Rs) / Nb 39 Eb_dB = 10*log10(C) - 10*log10(Rs) - 10*log10(Nb); 40 41 No_dBHz = Eb_dB - EbNo_dB; 42 43 % Noise power = Noise spectral density * bandwidth 44 % Noise power = Noise spectral density * Fs/2 for real signals 45 N_dB = No_dBHz + 10*log10(Fs/2); 46 Ngain_dB = N_dB - 10*log10(N); 47 Nsd = 10^(Ngain_dB/20); 48 49 % C/No = Carrier Power/noise spectral density 50 % = power per carrier*number of carriers / noise spectral density 51 CNo_dB = 10*log10(C) + 10*log10(Nc) - No_dBHz; 52 53 % SNR in equivalent 3000 Hz SSB channel, adding extra power for pilot to get 54 % true SNR. 55 56 B = 3000; 57 SNR = CNo_dB - 10*log10(B) + 10*log10((Nc+4)/Nc); 58end 59 60% we keep a m sample buffer in sample_memory 61% update sample_memory with n samples each time this function is called 62% outputs one nfft2 slice of spectrogram in dB. Good idea to make nfft2 a power of 2 63 64function [S, states_out] = spectrogram_update(samples, n, states_in) 65 sample_memory = states_in.sample_memory; 66 m = states_in.m; 67 nfft2 = states_in.nfft2; 68 lower_clip_dB = states_in.lower_clip_dB; 69 dec = states_in.dec; 70 71 sample_memory(1:m-n) = sample_memory(n+1:m); 72 sample_memory(m-n+1:m) = samples; 73 74 F = fft(sample_memory .* hanning(m)', 2*nfft2); 75 S = 20*log10(abs(F(1:dec:nfft2))/(nfft2)); 76 S(find(S < lower_clip_dB)) = lower_clip_dB; % clip lower limit 77 78 states_out = states_in; 79 states_out.sample_memory = sample_memory; 80end 81 82% ------------------------------------------------------------ 83 84function sim_out = freq_off_est_test(sim_in) 85 global Nc; 86 global Nb; 87 global M; 88 global Fs; 89 global pilot_lut_index; 90 global prev_pilot_lut_index; 91 global pilot_lpf1; 92 global Npilotlpf; 93 global spread; 94 global spread_2ms; 95 global hf_gain; 96 97 EbNovec = sim_in.EbNovec; 98 Ndelay = sim_in.delay; 99 frames = sim_in.frames; 100 startup_delay = sim_in.startup_delay; 101 allowable_error = sim_in.allowable_error; 102 foff_hz = sim_in.foff_hz; 103 hf_sim = sim_in.hf_sim; 104 hf_delay = floor(sim_in.hf_delay_ms*Fs/1000); 105 plot_type = sim_in.plot_type; 106 107 % work out gain for HF model 108 % e = sum((g*s)^2) = g*g*sum(s^2) = N, g = sqrt(N/sum(s^2)) 109 % compute so e=N 110 111 s1 = spread(1:frames*M); 112 s2 = [zeros(hf_delay,1); spread_2ms(1:frames*M)]; 113 s2 = s2(1:frames*M); 114 115 p = (s1+s2)'*(s1+s2); 116 hf_gain = sqrt(frames*M/p); 117 p2 = (hf_gain*(s1+s2))'*(hf_gain*(s1+s2)); 118 119 if hf_sim 120 channel_model = "HF"; 121 else 122 channel_model = "AWGN"; 123 end 124 125 % spectrogram states 126 127 spec_states.m = 8*M; 128 spec_states.nfft2 = 2 ^ ceil(log2(spec_states.m/2)); 129 spec_states.dec = 4; 130 spec_states.sample_memory = zeros(1, spec_states.m); 131 spec_states.lower_clip_dB = -30; 132 133 printf("\n%s\n", sim_in.test_name); 134 printf(" Channel EbNo SNR(calc) SNR(meas) SD(Hz) Hits Hits(%%) Result\n"); 135 136 % --------------------------------------------------------------------- 137 % Main loop 138 % --------------------------------------------------------------------- 139 140 for ne = 1:length(EbNovec) 141 EbNo_dB = EbNovec(ne); 142 [Nsd SNR] = calc_Nsd_from_EbNo(EbNo_dB); 143 hits = 0; 144 145 tx_filt = zeros(Nc,M); 146 prev_tx_symbols = ones(Nc+1,1); 147 148 tx_fdm_log = []; 149 rx_fdm_log = []; 150 pilot_lpf1_log = []; 151 S1_log = []; 152 rx_fdm_delay = zeros(M+Ndelay,1); 153 154 % freq offset simulation states 155 156 phase_offset = 1; 157 Nmedian = 20; 158 foff_median=zeros(1,Nmedian); 159 160 % hf sim states 161 162 path2 = zeros(1,hf_delay+M); 163 sum_sig = 0; 164 sum_noise = 0; 165 166 % state machine 167 state = 0; 168 fest_current = 0; 169 fdelta = 5; 170 candidate_thresh = 10; 171 foff_est_thresh_prev = 0; 172 173 for f=1:frames 174 175 % ------------------- Modulator ------------------- 176 177 tx_bits = get_test_bits(Nc*Nb); 178 tx_symbols = bits_to_psk(prev_tx_symbols, tx_bits, 'dqpsk'); 179 180 % simulate BPF filtering of +/- 200 Hz 181 % tx_symbols(1:6) = 0; tx_symbols(9:Nc) = 0; 182 183 prev_tx_symbols = tx_symbols; 184 tx_baseband = tx_filter(tx_symbols); 185 tx_fdm = fdm_upconvert(tx_baseband); 186 tx_fdm_log = [tx_fdm_log real(tx_fdm)]; 187 188 % ------------------- Channel simulation ------------------- 189 190 % frequency offset 191 192 for i=1:M 193 freq_offset = exp(j*2*pi*foff_hz(f)/Fs); 194 phase_offset *= freq_offset; 195 tx_fdm(i) = phase_offset*tx_fdm(i); 196 end 197 198 % optional HF channel sim 199 200 if hf_sim 201 path1 = tx_fdm .* conj(spread(f*M+1:f*M+M)'); 202 203 path2(1:hf_delay) = path2(M+1:hf_delay+M); 204 path2(hf_delay+1:hf_delay+M) = tx_fdm .* conj(spread_2ms(f*M+1:f*M+M)'); 205 206 tx_fdm = hf_gain*(path1 + path2(1:M)); 207 end 208 sum_sig += tx_fdm * tx_fdm'; 209 210 rx_fdm = real(tx_fdm); 211 212 % AWGN noise 213 214 noise = Nsd*randn(1,M); 215 sum_noise += noise * noise'; 216 rx_fdm += noise; 217 rx_fdm_log = [rx_fdm_log rx_fdm]; 218 219 % Fixed Delay 220 221 rx_fdm_delay(1:Ndelay) = rx_fdm_delay(M+1:M+Ndelay); 222 rx_fdm_delay(Ndelay+1:M+Ndelay) = rx_fdm; 223 224 % ------------------- Freq Offset Est ------------------- 225 226 % frequency offset estimation and correction, need to call 227 % rx_est_freq_offset even in track mode to keep states updated 228 229 [pilot prev_pilot pilot_lut_index prev_pilot_lut_index] = ... 230 get_pilot(pilot_lut_index, prev_pilot_lut_index, M); 231 [foff_est S1 S2] = rx_est_freq_offset(rx_fdm_delay, pilot, prev_pilot, M); 232 pilot_lpf1_log = [pilot_lpf1_log pilot_lpf1(Npilotlpf-M+1:Npilotlpf)]; 233 S1_log(f,:) = fftshift(S1); 234 S2_log(f,:) = fftshift(S2); 235 236 % raw estimate 237 238 foff_log(ne,f) = foff_est; 239 maxS1_log(ne,f) = max(S1.*conj(S1)/(S1*S1')); 240 maxS2_log(ne,f) = max(S2.*conj(S2)/(S2*S2')); 241 242 % median filter post-processed 243 244 foff_median(1:Nmedian-1) = foff_median(2:Nmedian); 245 foff_median(Nmedian) = foff_est; 246 foff_median_log(ne,f) = foff_coarse = median(foff_median); 247 248 % state machine post-processed 249 250 next_state = state; 251 if state == 0 252 if abs(foff_est - fest_current) > fdelta 253 fest_candidate = foff_est; 254 candidate_count = 0; 255 next_state = 1; 256 end 257 end 258 if state == 1 259 if abs(foff_est - fest_candidate) > fdelta 260 next_state = 0; 261 end 262 candidate_count++; 263 if candidate_count > candidate_thresh 264 fest_current = fest_candidate; 265 next_state = 0; 266 end 267 end 268 state = next_state; 269 foff_statemach_log(ne,f) = fest_current; 270 271 % threshold post processed 272 273 if (maxS1_log(ne,f) > 0.06) || (maxS2_log(ne,f) > 0.06) 274 %if (maxS1_log(ne,f) > 0.08) 275 foff_thresh_log(ne,f) = foff_est; 276 else 277 foff_thresh_log(ne,f) = foff_est_thresh_prev; 278 end 279 foff_est_thresh_prev = foff_thresh_log(ne,f); 280 281 % hit/miss stats 282 fest_current = foff_statemach_log(ne,f); 283 if (f > startup_delay) && (abs(fest_current - foff_hz(f)) < allowable_error) 284 hits++; 285 end 286 287 if length(EbNovec) == 1 288 [spectrogram(f,:) spec_states] = spectrogram_update(rx_fdm, M, spec_states); 289 end 290 end 291 292 % results for this EbNo value 293 294 sim_out.foff_sd(ne) = std(foff_log(ne,startup_delay:frames)); 295 sim_out.hits = hits; 296 sim_out.hits_percent = 100*sim_out.hits/(frames-startup_delay); 297 sim_out.SNRvec(ne) = SNR; 298 sim_out.tx_fdm_log = tx_fdm_log; 299 sim_out.rx_fdm_log = rx_fdm_log; 300 301 % noise we have measures is 4000 Hz wide, we want noise in 3000 Hz BW 302 303 snr_meas = 10*log10(sum_sig/(sum_noise*4000/3000)); 304 305 printf(" %6s %5.2f % -5.2f % -5.2f %3.2f %d %3.2f ", ... 306 channel_model, EbNo_dB, SNR, snr_meas, sim_out.foff_sd(ne), sim_out.hits, sim_out.hits_percent); 307 308 if sim_out.hits_percent == 100 309 printf("PASS\n"); 310 else 311 printf("FAIL\n"); 312 figure(5) 313 clf 314 plot(abs(foff_statemach_log(ne,:) - foff_hz < allowable_error)); 315 end 316 317 % plots if single dimension vector 318 319 if length(EbNovec) == 1 320 fmin = -200; fmax = 200; 321 figure(1) 322 clf; 323 subplot(411) 324 plot(foff_log(ne,:)) 325 axis([1 frames fmin fmax]); 326 ylabel("Foff raw") 327 subplot(412) 328 plot(foff_median_log(ne,:)) 329 axis([1 frames fmin fmax]); 330 ylabel("Foff median") 331 subplot(413) 332 plot(foff_statemach_log(ne,:),'g') 333 ylabel("Foff state") 334 axis([1 frames fmin fmax]); 335 subplot(414) 336 plot(foff_thresh_log(ne,:)) 337 ylabel("Foff thresh") 338 axis([1 frames fmin fmax]); 339 xlabel("Frames") 340 grid; 341 342 figure(2) 343 clf; 344 plot(maxS1_log(ne,:)); 345 axis([1 frames 0 0.2]); 346 xlabel("Frames") 347 ylabel("max(abs(S1/S2))") 348 grid; 349 hold on; 350 plot(maxS2_log(ne,:),'g'); 351 hold off; 352 353 figure(3) 354 [n m] = size(S1_log); 355 if strcmp(plot_type,"mesh") 356 mesh(-200+400*(0:m-1)/256,1:n,abs(S1_log(:,:))); 357 xlabel('Freq (Hz)'); ylabel('Frame num'); zlabel("max(abs(S1))") 358 else 359 imagesc(1:n,-200+400*(0:(m-1))/m,abs(S1_log(:,:))'); 360 set(gca,'YDir','normal') 361 ylabel('Freq (Hz)'); xlabel('Frame num'); 362 axis([1 n -200 200]) 363 end 364 365 figure(4) 366 clf 367 [n m] = size(spectrogram); 368 if strcmp(plot_type,"mesh") 369 mesh((4000/m)*(1:m),1:n,spectrogram); 370 xlabel('Freq (Hz)'); ylabel('Frame num'); zlabel('Amplitude (dB)'); 371 else 372 imagesc(1:n,(4000/m)*(1:m),spectrogram') 373 set(gca,'YDir','normal') 374 ylabel('Freq (Hz)'); xlabel('Frame num'); 375 axis([1 n 500 2500]) 376 end 377 378 sim_out.spec = spectrogram; 379 sim_out.tx_fdm_log = spectrogram; 380 end 381 end 382end 383 384% --------------------------------------------------------------------- 385% Run Automated Tests 386% --------------------------------------------------------------------- 387 388more off; 389 390function test1 391 global M; 392 global Rs; 393 394 sim_in.test_name = "Test 1: range of Eb/No (SNRs) in AWGN channel"; 395 sim_in.EbNovec = [3:10 99]; 396 sim_in.delay = M/2; 397 sim_in.frames = Rs*3; 398 sim_in.foff_hz(1:sim_in.frames) = 50; 399 sim_in.startup_delay = 0.5*Rs; 400 sim_in.allowable_error = 5; 401 sim_in.hf_sim = 0; 402 sim_in.hf_delay_ms = 2; 403 sim_in.delay = M/2; 404 sim_in.plot_type = "waterfall"; 405 406 sim_out = freq_off_est_test(sim_in); 407 408 figure(5) 409 clf 410 subplot(211) 411 plot(sim_in.EbNovec, sim_out.foff_sd) 412 hold on; 413 plot(sim_in.EbNovec, sim_out.foff_sd,'+') 414 hold off; 415 xlabel("Eb/No (dB)") 416 ylabel("Std Dev (Hz)") 417 axis([(min(sim_in.EbNovec)-1) (max(sim_in.EbNovec)+1) -1 10]); 418 419 subplot(212) 420 plot(sim_out.SNRvec,sim_out.foff_sd) 421 hold on; 422 plot(sim_out.SNRvec,sim_out.foff_sd,'+') 423 hold off; 424 xlabel("SNR (dB)") 425 ylabel("Std Dev (Hz)") 426 axis([(min(sim_out.SNRvec)-1) (max(sim_out.SNRvec)+1) -1 10]); 427end 428 429 430function test2 431 sim_in.test_name = "Test 2: range of Eb/No (SNRs) in HF multipath channel" 432 sim_in.EbNovec = 0:10; 433 sim_in.delay = 2; 434 sim_in.hf_sim = 1; 435 sim_in.hf_delay_ms = 2; 436 sim_in.frames = Rs*2; 437 sim_in.foff_hz = 0; 438 sim_in.startup_delay = Rs/2; 439 sim_in.allowable_error = 5; 440 441 sim_out = freq_off_est_test(sim_in); 442 443 figure(5) 444 clf 445 subplot(211) 446 plot(sim_in.EbNovec,sim_out.foff_sd) 447 hold on; 448 plot(sim_in.EbNovec,sim_out.foff_sd,'+') 449 hold off; 450 xlabel("Eb/No (dB)") 451 ylabel("Std Dev") 452 axis([(min(sim_in.EbNovec)-1) (max(sim_in.EbNovec)+1) -1 10]); 453end 454 455function test3 456 global M; 457 global Rs; 458 459 sim_in.test_name = "Test 3: 30 Seconds in HF multipath channel at 0dB-ish SNR"; 460 sim_in.EbNovec = 13; 461 sim_in.hf_sim = 0; 462 sim_in.hf_delay_ms = 2; 463 sim_in.delay = M/2; 464 sim_in.frames = Rs; 465 sim_in.foff_hz(1:sim_in.frames) = -50; 466 sim_in.startup_delay = Rs; % allow 1 second in heavily faded channels 467 sim_in.allowable_error = 5; 468 sim_in.plot_type = "mesh"; 469 sim_out = freq_off_est_test(sim_in); 470endfunction 471 472function animated_gif 473 figure(4) 474 for i=5:5:360 475 view(i,45) 476 filename=sprintf('fdmdv_fig%05d.png',i); 477 print(filename); 478 end 479 if 0 480 for i=90:-5:-270 481 view(45,i) 482 filename=sprintf('fdmdv_fig%05d.png',i); 483 print(filename); 484 end 485 end 486endfunction 487 488test3; 489 490