1% fdmdv.m 2% 3% Functions that implement a Frequency Divison Multiplexed Modem for 4% Digital Voice (FDMDV) over HF channels. 5% 6% Copyright David Rowe 2012 7% This program is distributed under the terms of the GNU General Public License 8% Version 2 9% 10% TODO: 11% [X] refactor with states 12% [X] remove commented out globals 13% [X] tfdmdv works 14% [X] fdmdv_demod works 15% [ ] fdmdv_ut works 16 17% reqd to make sure we get same random bits at mod and demod 18 19fdmdv_common; 20 21rand('state',1); 22randn('state',1); 23 24% Functions ---------------------------------------------------- 25 26 27function f = fdmdv_init(Nc=14) 28 Fs = f.Fs = 8000; % sample rate in Hz 29 T = f.T = 1/Fs; % sample period in seconds 30 Rs = f.Rs = 50; % symbol rate in Hz 31 f.Nc = Nc; 32 Nb = f.Nb = 2; % Bits/symbol for PSK modulation 33 Rb = f.Rb = Nc*Rs*Nb; % bit rate 34 M = f.M = Fs/Rs; % oversampling factor 35 Nsym = f.Nsym = 6; % number of symbols to filter over 36 37 Fsep = f.Fsep = 75; % Separation between carriers (Hz) 38 Fcenter = f.Fcentre = 1500; % Centre frequency, Nc/2 carriers below this, 39 % Nc/2 carriers above (Hz) 40 Nt = f.Nt = 5; % number of symbols we estimate timing over 41 P = f.P = 4; % oversample factor used for rx symbol filtering 42 Nfilter = f.Nfilter = Nsym*M; 43 44 Nfiltertiming = f.Nfiltertiming = M+Nfilter+M; 45 46 Nsync_mem = f.Nsync_mem = 6; 47 f.sync_uw = [1 -1 1 -1 1 -1]; 48 49 alpha = 0.5; 50 f.gt_alpha5_root = gen_rn_coeffs(alpha, T, Rs, Nsym, M); 51 52 f.pilot_bit = 0; % current value of pilot bit 53 54 f.tx_filter_memory = zeros(Nc+1, Nfilter); 55 f.rx_filter_memory = zeros(Nc+1, Nfilter); 56 f.Nrx_fdm_mem = Nfilter+M+M/P; 57 f.rx_fdm_mem = zeros(1,f.Nrx_fdm_mem); 58 59 f.snr_coeff = 0.9; % SNR est averaging filter coeff 60 61 % phasors used for up and down converters 62 63 f.freq = zeros(Nc+1,1); 64 f.freq_pol = zeros(Nc+1,1); 65 66 for c=1:Nc/2 67 carrier_freq = (-Nc/2 - 1 + c)*Fsep; 68 f.freq_pol(c) = 2*pi*carrier_freq/Fs; 69 f.freq(c) = exp(j*f.freq_pol(c)); 70 end 71 72 for c=floor(Nc/2)+1:Nc 73 carrier_freq = (-Nc/2 + c)*Fsep; 74 f.freq_pol(c) = 2*pi*carrier_freq/Fs; 75 f.freq(c) = exp(j*f.freq_pol(c)); 76 end 77 78 f.freq_pol(Nc+1) = 2*pi*0/Fs; 79 f.freq(Nc+1) = exp(j*f.freq_pol(Nc+1)); 80 81 f.fbb_rect = exp(j*2*pi*f.Fcentre/Fs); 82 f.fbb_phase_tx = 1; 83 f.fbb_phase_rx = 1; 84 85 % Spread initial FDM carrier phase out as far as possible. This 86 % helped PAPR for a few dB. We don't need to adjust rx phase as DQPSK 87 % takes care of that. 88 89 f.phase_tx = ones(Nc+1,1); 90 f.phase_tx = exp(j*2*pi*(0:Nc)/(Nc+1)); 91 f.phase_rx = ones(Nc+1,1); 92 93 % decimation filter 94 95 f.Nrxdec = 31; 96 % fir1() output appears to have changed from when coeffs used in C port were used 97 %f.rxdec_coeff = fir1(f.Nrxdec-1, 0.25); 98 f.rxdec_coeff = [-0.00125472 -0.00204605 -0.0019897 0.000163906 0.00490937 0.00986375 ... 99 0.0096718 -0.000480351 -0.019311 -0.0361822 -0.0341251 0.000827866 ... 100 0.0690577 0.152812 0.222115 0.249004 0.222115 0.152812 ... 101 0.0690577 0.000827866 -0.0341251 -0.0361822 -0.019311 -0.000480351 ... 102 0.0096718 0.00986375 0.00490937 0.000163906 -0.0019897 -0.00204605 ... 103 -0.00125472]; 104 105 % we need room for Nrdec + the max nin, as we may need to filter max_min samples 106 107 f.Nrxdecmem = f.Nrxdec+M+M/P; 108 f.rxdec_lpf_mem = zeros(1,f.Nrxdecmem); 109 f.Q=M/4; 110 111 % freq offset estimation 112 113 f.Mpilotfft = 256; 114 f.Npilotcoeff = 30; 115 116 % here's how to make this filter from scratch, however it appeared to change over different 117 % octave versions so have hard coded to version used for C port 118 %f.pilot_coeff = fir1(f.Npilotcoeff-1, 200/(Fs/2))'; % 200Hz LPF 119 f.pilot_coeff = [0.00223001 0.00301037 0.00471258 0.0075934 0.0118145 0.0174153 ... 120 0.0242969 0.0322204 0.0408199 0.0496286 0.0581172 0.0657392 ... 121 0.0719806 0.0764066 0.0787022 0.0787022 0.0764066 0.0719806 ... 122 0.0657392 0.0581172 0.0496286 0.0408199 0.0322204 0.0242969 ... 123 0.0174153 0.0118145 0.0075934 0.00471258 0.00301037 0.00223001]; 124 125 f.Npilotbaseband = f.Npilotcoeff + M + M/P; % number of pilot baseband samples 126 f.Npilotlpf = 4*M; % reqd for pilot LPF 127 % number of symbols we DFT pilot over 128 % pilot est window 129 130 % pilot LUT, used for copy of pilot at rx 131 132 f.pilot_lut = generate_pilot_lut(f); 133 f.pilot_lut_index = 1; 134 f.prev_pilot_lut_index = 3*M+1; 135 136 % Freq offset estimator states 137 138 f.pilot_baseband1 = zeros(1, f.Npilotbaseband); % pilot baseband samples 139 f.pilot_baseband2 = zeros(1, f.Npilotbaseband); % pilot baseband samples 140 f.pilot_lpf1 = zeros(1, f.Npilotlpf); % LPF pilot samples 141 f.pilot_lpf2 = zeros(1, f.Npilotlpf); % LPF pilot samples 142 143 % Timing estimator states 144 145 f.rx_filter_mem_timing = zeros(Nc+1, Nt*P); 146 f.rx_baseband_mem_timing = zeros(Nc+1, f.Nfiltertiming); 147 148 % Test bit stream state variables 149 150 f = init_test_bits(f); 151endfunction 152 153 154% generate Nc+1 PSK symbols from vector of (1,Nc*Nb) input bits. The 155% Nc+1 symbol is the +1 -1 +1 .... BPSK sync carrier 156 157function [tx_symbols f] = bits_to_psk(f, prev_tx_symbols, tx_bits) 158 Nc = f.Nc; Nb = f.Nb; 159 m4_gray_to_binary = [ 160 bin2dec("00") 161 bin2dec("01") 162 bin2dec("11") 163 bin2dec("10") 164 ]; 165 m8_gray_to_binary = [ 166 bin2dec("000") 167 bin2dec("001") 168 bin2dec("011") 169 bin2dec("010") 170 bin2dec("111") 171 bin2dec("110") 172 bin2dec("100") 173 bin2dec("101") 174 ]; 175 176 assert(length(tx_bits) == Nc*Nb, "Incorrect number of bits"); 177 178 m = 2 .^ Nb; 179 assert((m == 4) || (m == 8)); 180 181 for c=1:Nc 182 183 % extract bits for this symbol 184 185 bits_binary = tx_bits((c-1)*Nb+1:c*Nb); 186 bits_decimal = sum(bits_binary .* 2.^(Nb-1:-1:0)); 187 188 % determine phase shift using gray code mapping 189 190 if m == 4 191 phase_shift = (2*pi/m)*m4_gray_to_binary(bits_decimal+1); 192 else 193 phase_shift = (2*pi/m)*m8_gray_to_binary(bits_decimal+1); 194 end 195 196 % apply phase shift from previous symbol 197 198 tx_symbols(c) = exp(j*phase_shift) * prev_tx_symbols(c); 199 end 200 201 % +1 -1 +1 -1 BPSK sync carrier, once filtered becomes two spectral 202 % lines at +/- Rs/2 203 204 if f.pilot_bit 205 tx_symbols(Nc+1) = -prev_tx_symbols(Nc+1); 206 else 207 tx_symbols(Nc+1) = prev_tx_symbols(Nc+1); 208 end 209 if f.pilot_bit 210 f.pilot_bit = 0; 211 else 212 f.pilot_bit = 1; 213 end 214 215endfunction 216 217% LP filter +/- 1000 Hz, allows us to perform rx filtering at a lower rate saving CPU 218 219function [rx_fdm_filter fdmdv] = rxdec_filter(fdmdv, rx_fdm, nin) 220 M = fdmdv.M; 221 Nrxdecmem = fdmdv.Nrxdecmem; 222 Nrxdec = fdmdv.Nrxdec; 223 rxdec_coeff = fdmdv.rxdec_coeff; 224 rxdec_lpf_mem = fdmdv.rxdec_lpf_mem; 225 226 % place latest nin samples at end of buffer 227 228 rxdec_lpf_mem(1:Nrxdecmem-nin) = rxdec_lpf_mem(nin+1:Nrxdecmem); 229 rxdec_lpf_mem(Nrxdecmem-nin+1:Nrxdecmem) = rx_fdm(1:nin); 230 231 % init room for nin output samples 232 233 rx_fdm_filter = zeros(1,nin); 234 235 % Move starting point for filter dot product to filter newest samples 236 % in buffer. This stuff makes my head hurt. 237 238 st = Nrxdecmem - nin - Nrxdec + 1; 239 for i=1:nin 240 a = st+1; b = st+i+Nrxdec-1; 241 %printf("nin: %d i: %d a: %d b: %d\n", nin, i, a, b); 242 rx_fdm_filter(i) = rxdec_lpf_mem(st+i:st+i+Nrxdec-1) * rxdec_coeff'; 243 end 244 245 fdmdv.rxdec_lpf_mem = rxdec_lpf_mem; 246end 247 248 249% Combined down convert and rx filter, more memory efficient but less intuitive design 250% TODO: Decimate mem update and downconversion, this will save some more CPU and memory 251% note phase would have to advance 4 times as fast 252 253function [rx_filt fdmdv] = down_convert_and_rx_filter(fdmdv, rx_fdm, nin, dec_rate) 254 Nc = fdmdv.Nc; 255 M = fdmdv.M; 256 P = fdmdv.P; 257 rx_fdm_mem = fdmdv.rx_fdm_mem; 258 Nrx_fdm_mem = fdmdv.Nrx_fdm_mem; 259 phase_rx = fdmdv.phase_rx; 260 freq = fdmdv.freq; 261 freq_pol = fdmdv.freq_pol; 262 Nfilter = fdmdv.Nfilter; 263 gt_alpha5_root = fdmdv.gt_alpha5_root; 264 Q = fdmdv.Q; 265 266 % update memory of rx_fdm_mem, newest nin sample ast end of buffer 267 268 rx_fdm_mem(1:Nrx_fdm_mem-nin) = rx_fdm_mem(nin+1:Nrx_fdm_mem); 269 rx_fdm_mem(Nrx_fdm_mem-nin+1:Nrx_fdm_mem) = rx_fdm(1:nin); 270 271 for c=1:Nc+1 272 273 #{ 274 So we have rx_fdm_mem, a baseband array of samples at 275 rate Fs Hz, including the last nin samples at the end. To 276 filter each symbol we require the baseband samples for all Nsym 277 symbols that we filter over. So we need to downconvert the 278 entire rx_fdm_mem array. To downconvert these we need the LO 279 phase referenced to the start of the rx_fdm_mem array. 280 281 282 <--------------- Nrx_filt_mem -------> 283 nin 284 |--------------------------|---------| 285 1 | 286 phase_rx(c) 287 288 This means winding phase(c) back from this point 289 to ensure phase continuity. 290 #} 291 292 wind_back_phase = -freq_pol(c)*(Nfilter); 293 phase_rx(c) = phase_rx(c)*exp(j*wind_back_phase); 294 295 % down convert all samples in buffer 296 297 rx_baseband = zeros(1,Nrx_fdm_mem); 298 299 st = Nrx_fdm_mem; % end of buffer 300 st -= nin-1; % first new sample 301 st -= Nfilter; % first sample used in filtering 302 303 %printf("Nfilter: %d Nrx_fdm_mem: %d dec_rate: %d nin: %d st: %d\n", 304 % Nfilter, Nrx_fdm_mem, dec_rate, nin, st); 305 306 f_rect = freq(c) .^ dec_rate; 307 308 for i=st:dec_rate:Nrx_fdm_mem 309 phase_rx(c) = phase_rx(c) * f_rect; 310 rx_baseband(i) = rx_fdm_mem(i)*phase_rx(c)'; 311 end 312 313 % now we can filter this carrier's P (+/-1) symbols. Due to 314 % filtering of rx_fdm we can filter at rate at rate M/Q 315 316 N=M/P; k = 1; 317 for i=1:N:nin 318 rx_filt(c,k) = (M/Q)*rx_baseband(st+i-1:dec_rate:st+i-1+Nfilter-1) * gt_alpha5_root(1:dec_rate:length(gt_alpha5_root))'; 319 k+=1; 320 end 321 end 322 323 fdmdv.phase_rx = phase_rx; 324 fdmdv.rx_fdm_mem = rx_fdm_mem; 325endfunction 326 327 328% LPF and peak pick part of freq est, put in a function as we call it twice 329 330function [foff imax pilot_lpf_out S] = lpf_peak_pick(f, pilot_baseband, pilot_lpf, nin, do_fft) 331 M = f.M; 332 Npilotlpf = f.Npilotlpf; 333 Npilotbaseband = f.Npilotbaseband; 334 Npilotcoeff = f.Npilotcoeff; 335 Fs = f.Fs; 336 Mpilotfft = f.Mpilotfft; 337 pilot_coeff = f.pilot_coeff; 338 339 % LPF cutoff 200Hz, so we can handle max +/- 200 Hz freq offset 340 341 pilot_lpf(1:Npilotlpf-nin) = pilot_lpf(nin+1:Npilotlpf); 342 k = Npilotbaseband-nin+1;; 343 for i = Npilotlpf-nin+1:Npilotlpf 344 pilot_lpf(i) = pilot_baseband(k-Npilotcoeff+1:k) * pilot_coeff'; 345 k++; 346 end 347 348 imax = 0; 349 foff = 0; 350 S = zeros(1, Mpilotfft); 351 352 if do_fft 353 % decimate to improve DFT resolution, window and DFT 354 355 Mpilot = Fs/(2*200); % calc decimation rate given new sample rate is twice LPF freq 356 h = hanning(Npilotlpf); 357 s = pilot_lpf(1:Mpilot:Npilotlpf) .* h(1:Mpilot:Npilotlpf)'; 358 s = [s zeros(1,Mpilotfft-Npilotlpf/Mpilot)]; 359 S = fft(s, Mpilotfft); 360 361 % peak pick and convert to Hz 362 363 [imax ix] = max(abs(S)); 364 r = 2*200/Mpilotfft; % maps FFT bin to frequency in Hz 365 366 if ix > Mpilotfft/2 367 foff = (ix - Mpilotfft - 1)*r; 368 else 369 foff = (ix - 1)*r; 370 endif 371 end 372 373 pilot_lpf_out = pilot_lpf; 374 375endfunction 376 377 378% Estimate frequency offset of FDM signal using BPSK pilot. This is quite 379% sensitive to pilot tone level wrt other carriers 380 381function [foff S1 S2 f] = rx_est_freq_offset(f, rx_fdm, pilot, pilot_prev, nin, do_fft) 382 M = f.M; 383 Npilotbaseband = f.Npilotbaseband; 384 pilot_baseband1 = f.pilot_baseband1; 385 pilot_baseband2 = f.pilot_baseband2; 386 pilot_lpf1 = f.pilot_lpf1; 387 pilot_lpf2 = f.pilot_lpf2; 388 389 % down convert latest nin samples of pilot by multiplying by ideal 390 % BPSK pilot signal we have generated locally. The peak of the DFT 391 % of the resulting signal is sensitive to the time shift between the 392 % received and local version of the pilot, so we do it twice at 393 % different time shifts and choose the maximum. 394 395 pilot_baseband1(1:Npilotbaseband-nin) = pilot_baseband1(nin+1:Npilotbaseband); 396 pilot_baseband2(1:Npilotbaseband-nin) = pilot_baseband2(nin+1:Npilotbaseband); 397 for i=1:nin 398 pilot_baseband1(Npilotbaseband-nin+i) = rx_fdm(i) * conj(pilot(i)); 399 pilot_baseband2(Npilotbaseband-nin+i) = rx_fdm(i) * conj(pilot_prev(i)); 400 end 401 402 [foff1 max1 pilot_lpf1 S1] = lpf_peak_pick(f, pilot_baseband1, pilot_lpf1, nin, do_fft); 403 [foff2 max2 pilot_lpf2 S2] = lpf_peak_pick(f, pilot_baseband2, pilot_lpf2, nin, do_fft); 404 405 if max1 > max2 406 foff = foff1; 407 else 408 foff = foff2; 409 end 410 411 f.pilot_baseband1 = pilot_baseband1; 412 f.pilot_baseband2 = pilot_baseband2; 413 f.pilot_lpf1 = pilot_lpf1; 414 f.pilot_lpf2 = pilot_lpf2; 415endfunction 416 417% Experimental "feed forward" phase estimation function - estimates 418% phase over a windows of Nph (e.g. Nph = 9) symbols. May not work 419% well on HF channels but lets see. Has a phase ambiguity of m(pi/4) 420% where m=0,1,2 which needs to be corrected outside of this function 421 422function [phase_offsets ferr f] = rx_est_phase(f, rx_symbols) 423 global rx_symbols_mem; 424 global prev_phase_offsets; 425 global phase_amb; 426 Nph = f.Nph; 427 Nc = f.Nc; 428 429 % keep record of Nph symbols 430 431 rx_symbols_mem(:,1:Nph-1) = rx_symbols_mem(:,2:Nph); 432 rx_symbols_mem(:,Nph) = rx_symbols; 433 434 % estimate and correct phase offset based of modulation stripped samples 435 436 phase_offsets = zeros(Nc+1,1); 437 for c=1:Nc+1 438 439 % rotate QPSK constellation to a single point 440 mod_stripped = abs(rx_symbols_mem(c,:)) .* exp(j*4*angle(rx_symbols_mem(c,:))); 441 442 % find average phase offset, which will be on -pi/4 .. pi/4 443 sum_real = sum(real(mod_stripped)); 444 sum_imag = sum(imag(mod_stripped)); 445 phase_offsets(c) = atan2(sum_imag, sum_real)/4; 446 447 % determine if phase has jumped from - -> + 448 if (prev_phase_offsets(c) < -pi/8) && (phase_offsets(c) > pi/8) 449 phase_amb(c) -= pi/2; 450 if (phase_amb(c) < -pi) 451 phase_amb(c) += 2*pi; 452 end 453 end 454 455 % determine if phase has jumped from + -> - 456 if (prev_phase_offsets(c) > pi/8) && (phase_offsets(c) < -pi/8) 457 phase_amb(c) += pi/2; 458 if (phase_amb(c) > pi) 459 phase_amb(c) -= 2*pi; 460 end 461 end 462 end 463 464 ferr = mean(phase_offsets - prev_phase_offsets); 465 prev_phase_offsets = phase_offsets; 466 467endfunction 468 469 470% convert symbols back to an array of bits 471 472function [rx_bits sync_bit f_err phase_difference] = psk_to_bits(f, prev_rx_symbols, rx_symbols, modulation) 473 Nc = f.Nc; 474 Nb = f.Nb; 475 476 m4_binary_to_gray = [ 477 bin2dec("00") 478 bin2dec("01") 479 bin2dec("11") 480 bin2dec("10") 481 ]; 482 483 m8_binary_to_gray = [ 484 bin2dec("000") 485 bin2dec("001") 486 bin2dec("011") 487 bin2dec("010") 488 bin2dec("110") 489 bin2dec("111") 490 bin2dec("101") 491 bin2dec("100") 492 ]; 493 494 m = 2 .^ Nb; 495 assert((m == 4) || (m == 8)); 496 497 phase_difference = zeros(Nc+1,1); 498 for c=1:Nc 499 norm = 1/(1E-6+abs(prev_rx_symbols(c))); 500 phase_difference(c) = rx_symbols(c) .* conj(prev_rx_symbols(c)) * norm; 501 end 502 503 for c=1:Nc 504 phase_difference(c) *= exp(j*pi/4); 505 506 if m == 4 507 508 % to get a good match between C and Octave during start up use same as C code 509 510 d = phase_difference(c); 511 if (real(d) >= 0) && (imag(d) >= 0) 512 msb = 0; lsb = 0; 513 end 514 if (real(d) < 0) && (imag(d) >= 0) 515 msb = 0; lsb = 1; 516 end 517 if (real(d) < 0) && (imag(d) < 0) 518 msb = 1; lsb = 1; 519 end 520 if (real(d) >= 0) && (imag(d) < 0) 521 msb = 1; lsb = 0; 522 end 523 524 rx_bits(2*(c-1)+1) = msb; 525 rx_bits(2*c) = lsb; 526 else 527 % determine index of constellation point received 0,1,...,m-1 528 529 index = floor(angle(phase_difference(c))*m/(2*pi) + 0.5); 530 531 if index < 0 532 index += m; 533 end 534 535 % map to decimal version of bits encoded in symbol 536 537 if m == 4 538 bits_decimal = m4_binary_to_gray(index+1); 539 else 540 bits_decimal = m8_binary_to_gray(index+1); 541 end 542 543 % convert back to an array of received bits 544 545 for i=1:Nb 546 if bitand(bits_decimal, 2.^(Nb-i)) 547 rx_bits((c-1)*Nb+i) = 1; 548 else 549 rx_bits((c-1)*Nb+i) = 0; 550 end 551 end 552 end 553 end 554 555 assert(length(rx_bits) == Nc*Nb); 556 557 % Extract DBPSK encoded Sync bit 558 559 norm = 1/(1E-6+abs(prev_rx_symbols(Nc+1))); 560 phase_difference(Nc+1) = rx_symbols(Nc+1) * conj(prev_rx_symbols(Nc+1)) * norm; 561 if (real(phase_difference(Nc+1)) < 0) 562 sync_bit = 1; 563 f_err = imag(phase_difference(Nc+1))*norm; % make f_err magnitude insensitive 564 else 565 sync_bit = 0; 566 f_err = -imag(phase_difference(Nc+1))*norm; 567 end 568 569 % extra pi/4 rotation as we need for snr_update and scatter diagram 570 571 phase_difference(Nc+1) *= exp(j*pi/4); 572 573endfunction 574 575 576% given phase differences update estimates of signal and noise levels 577 578function [sig_est noise_est] = snr_update(f, sig_est, noise_est, phase_difference) 579 snr_coeff = f.snr_coeff; 580 Nc = f.Nc; 581 582 % mag of each symbol is distance from origin, this gives us a 583 % vector of mags, one for each carrier. 584 585 s = abs(phase_difference); 586 587 % signal mag estimate for each carrier is a smoothed version 588 % of instantaneous magntitude, this gives us a vector of smoothed 589 % mag estimates, one for each carrier. 590 591 sig_est = snr_coeff*sig_est + (1 - snr_coeff)*s; 592 593 %printf("s: %f sig_est: %f snr_coeff: %f\n", s(1), sig_est(1), snr_coeff); 594 595 % noise mag estimate is distance of current symbol from average 596 % location of that symbol. We reflect all symbols into the first 597 % quadrant for convenience. 598 599 refl_symbols = abs(real(phase_difference)) + j*abs(imag(phase_difference)); 600 n = abs(exp(j*pi/4)*sig_est - refl_symbols); 601 602 % noise mag estimate for each carrier is a smoothed version of 603 % instantaneous noise mag, this gives us a vector of smoothed 604 % noise power estimates, one for each carrier. 605 606 noise_est = snr_coeff*noise_est + (1 - snr_coeff)*n; 607 608endfunction 609 610 611% calculate current sig estimate for eeach carrier 612 613function snr_dB = calc_snr(f, sig_est, noise_est) 614 Rs = f.Rs; 615 616 % find total signal power by summing power in all carriers 617 618 S = sum(sig_est .^2); 619 SdB = 10*log10(S); 620 621 % Average noise mag across all carriers and square to get an average 622 % noise power. This is an estimate of the noise power in Rs = 50Hz of 623 % BW (note for raised root cosine filters Rs is the noise BW of the 624 % filter) 625 626 N50 = mean(noise_est).^2; 627 N50dB = 10*log10(N50); 628 629 % Now multiply by (3000 Hz)/(50 Hz) to find the total noise power in 630 % 3000 Hz 631 632 N3000dB = N50dB + 10*log10(3000/Rs); 633 634 snr_dB = SdB - N3000dB; 635 636endfunction 637 638 639% sets up test bits system. make sure rand('state', 1) has just beed called 640% so we generate the right test_bits pattern! 641 642function f = init_test_bits(f) 643 f.Ntest_bits = f.Nc*f.Nb*4; % length of test sequence 644 f.test_bits = rand(1,f.Ntest_bits) > 0.5; % test pattern of bits 645 f.current_test_bit = 1; 646 f.rx_test_bits_mem = zeros(1,f.Ntest_bits); 647endfunction 648 649 650% returns nbits from a repeating sequence of random data 651 652function [bits f] = get_test_bits(f, nbits) 653 654 for i=1:nbits 655 bits(i) = f.test_bits(f.current_test_bit++); 656 657 if (f.current_test_bit > f.Ntest_bits) 658 f.current_test_bit = 1; 659 endif 660 end 661 662endfunction 663 664 665% Accepts nbits from rx and attempts to sync with test_bits sequence. 666% if sync OK measures bit errors 667 668function [sync bit_errors error_pattern f] = put_test_bits(f, test_bits, rx_bits) 669 Ntest_bits = f.Ntest_bits; 670 rx_test_bits_mem = f.rx_test_bits_mem; 671 672 % Append to our memory 673 674 [m n] = size(rx_bits); 675 f.rx_test_bits_mem(1:f.Ntest_bits-n) = f.rx_test_bits_mem(n+1:f.Ntest_bits); 676 f.rx_test_bits_mem(f.Ntest_bits-n+1:f.Ntest_bits) = rx_bits; 677 678 % see how many bit errors we get when checked against test sequence 679 680 error_pattern = xor(test_bits, f.rx_test_bits_mem); 681 bit_errors = sum(error_pattern); 682 683 % if less than a thresh we are aligned and in sync with test sequence 684 685 ber = bit_errors/f.Ntest_bits; 686 687 sync = 0; 688 if (ber < 0.2) 689 sync = 1; 690 endif 691endfunction 692 693% Generate M samples of DBPSK pilot signal for Freq offset estimation 694 695function [pilot_fdm bit symbol filter_mem phase] = generate_pilot_fdm(f, bit, symbol, filter_mem, phase, freq) 696 M = f.M; 697 Nfilter = f.Nfilter; 698 gt_alpha5_root = f.gt_alpha5_root; 699 700 % +1 -1 +1 -1 DBPSK sync carrier, once filtered becomes two spectral 701 % lines at +/- Rs/2 702 703 if bit 704 symbol = -symbol; 705 else 706 symbol = symbol; 707 end 708 if bit 709 bit = 0; 710 else 711 bit = 1; 712 end 713 714 % filter DPSK symbol to create M baseband samples 715 716 filter_mem(Nfilter) = (sqrt(2)/2)*symbol; 717 for i=1:M 718 tx_baseband(i) = M*filter_mem(M:M:Nfilter) * gt_alpha5_root(M-i+1:M:Nfilter)'; 719 end 720 filter_mem(1:Nfilter-M) = filter_mem(M+1:Nfilter); 721 filter_mem(Nfilter-M+1:Nfilter) = zeros(1,M); 722 723 % upconvert 724 725 for i=1:M 726 phase = phase * freq; 727 pilot_fdm(i) = sqrt(2)*2*tx_baseband(i)*phase; 728 end 729 730endfunction 731 732 733% Generate a 4M sample vector of DBPSK pilot signal. As the pilot signal 734% is periodic in 4M samples we can then use this vector as a look up table 735% for pilot signal generation in the demod. 736 737function pilot_lut = generate_pilot_lut(f) 738 Nc = f.Nc; 739 Nfilter = f.Nfilter; 740 M = f.M; 741 freq = f.freq; 742 743 % pilot states 744 745 pilot_rx_bit = 0; 746 pilot_symbol = sqrt(2); 747 pilot_freq = freq(Nc+1); 748 pilot_phase = 1; 749 pilot_filter_mem = zeros(1, Nfilter); 750 %prev_pilot = zeros(M,1); 751 752 pilot_lut = []; 753 754 F=8; 755 756 for fr=1:F 757 [pilot pilot_rx_bit pilot_symbol pilot_filter_mem pilot_phase] = generate_pilot_fdm(f, pilot_rx_bit, pilot_symbol, pilot_filter_mem, pilot_phase, pilot_freq); 758 %prev_pilot = pilot; 759 pilot_lut = [pilot_lut pilot]; 760 end 761 762 % discard first 4 symbols as filter memory is filling, just keep last 763 % four symbols 764 765 pilot_lut = pilot_lut(4*M+1:M*F); 766 767endfunction 768 769 770% grab next pilot samples for freq offset estimation at demod 771 772function [pilot prev_pilot pilot_lut_index prev_pilot_lut_index] = get_pilot(f, pilot_lut_index, prev_pilot_lut_index, nin) 773 M = f.M; 774 pilot_lut = f.pilot_lut; 775 776 for i=1:nin 777 pilot(i) = pilot_lut(pilot_lut_index); 778 pilot_lut_index++; 779 if pilot_lut_index > 4*M 780 pilot_lut_index = 1; 781 end 782 prev_pilot(i) = pilot_lut(prev_pilot_lut_index); 783 prev_pilot_lut_index++; 784 if prev_pilot_lut_index > 4*M 785 prev_pilot_lut_index = 1; 786 end 787 end 788endfunction 789 790 791% freq offset state machine. Moves between acquire and track states based 792% on BPSK pilot sequence. Freq offset estimator occasionally makes mistakes 793% when used continuously. So we use it until we have acquired the BPSK pilot, 794% then switch to a more robust tracking algorithm. If we lose sync we switch 795% back to acquire mode for fast-requisition. 796 797function [sync reliable_sync_bit state timer sync_mem] = freq_state(f, sync_bit, state, timer, sync_mem) 798 Nsync_mem = f.Nsync_mem; 799 sync_uw = f.sync_uw; 800 801 % look for 6 symbol (120ms) 010101 of sync sequence 802 803 unique_word = 0; 804 for i=1:Nsync_mem-1 805 sync_mem(i) = sync_mem(i+1); 806 end 807 sync_mem(Nsync_mem) = 1 - 2*sync_bit; 808 corr = 0; 809 for i=1:Nsync_mem 810 corr += sync_mem(i)*sync_uw(i); 811 end 812 if abs(corr) == Nsync_mem 813 unique_word = 1; 814 end 815 reliable_sync_bit = (corr == Nsync_mem); 816 817 % iterate state machine 818 819 next_state = state; 820 if state == 0 821 if unique_word 822 next_state = 1; 823 timer = 0; 824 end 825 end 826 if state == 1 827 if unique_word 828 timer++; 829 if timer == 25 % sync has been good for 500ms 830 next_state = 2; 831 end 832 else 833 next_state = 0; 834 end 835 end 836 if state == 2 % good sync state 837 if unique_word == 0 838 timer = 0; 839 next_state = 3; 840 end 841 end 842 if state == 3 % tenative bad state, but could be a fade 843 if unique_word 844 next_state = 2; 845 else 846 timer++; 847 if timer == 50 % wait for 1000ms in case sync comes back 848 next_state = 0; 849 end 850 end 851 end 852 853 %printf("corr: % -d state: %d next_state: %d uw: %d timer: %d\n", corr, state, next_state, unique_word, timer); 854 state = next_state; 855 856 if state 857 sync = 1; 858 else 859 sync = 0; 860 end 861endfunction 862 863% Save test bits to a text file in the form of a C array 864 865function test_bits_file(filename) 866 global test_bits; 867 global Ntest_bits; 868 869 f=fopen(filename,"wt"); 870 fprintf(f,"/* Generated by test_bits_file() Octave function */\n\n"); 871 fprintf(f,"const int test_bits[]={\n"); 872 for m=1:Ntest_bits-1 873 fprintf(f," %d,\n",test_bits(m)); 874 endfor 875 fprintf(f," %d\n};\n",test_bits(Ntest_bits)); 876 fclose(f); 877endfunction 878 879 880% Saves RN filter coeffs to a text file in the form of a C array 881 882function rn_file(gt_alpha5_root, filename) 883 Nfilter = length(gt_alpha5_root); 884 885 f=fopen(filename,"wt"); 886 fprintf(f,"/* Generated by rn_file() Octave function */\n\n"); 887 fprintf(f,"const float gt_alpha5_root[]={\n"); 888 for m=1:Nfilter-1 889 fprintf(f," %g,\n",gt_alpha5_root(m)); 890 endfor 891 fprintf(f," %g\n};\n",gt_alpha5_root(Nfilter)); 892 fclose(f); 893endfunction 894 895 896% Saves rx decimation filter coeffs to a text file in the form of a C array 897 898function rxdec_file(fdmdv, filename) 899 rxdec_coeff = fdmdv.rxdec_coeff; 900 Nrxdec = fdmdv.Nrxdec; 901 902 f=fopen(filename,"wt"); 903 fprintf(f,"/* Generated by rxdec_file() Octave function */\n\n"); 904 fprintf(f,"const float rxdec_coeff[]={\n"); 905 for m=1:Nrxdec-1 906 fprintf(f," %g,\n",rxdec_coeff(m)); 907 endfor 908 fprintf(f," %g\n};\n",rxdec_coeff(Nrxdec)); 909 fclose(f); 910endfunction 911 912 913function pilot_coeff_file(fdmdv, filename) 914 pilot_coeff = fdmdv.pilot_coeff; 915 Npilotcoeff = fdmdv.Npilotcoeff; 916 917 f=fopen(filename,"wt"); 918 fprintf(f,"/* Generated by pilot_coeff_file() Octave function */\n\n"); 919 fprintf(f,"const float pilot_coeff[]={\n"); 920 for m=1:Npilotcoeff-1 921 fprintf(f," %g,\n",pilot_coeff(m)); 922 endfor 923 fprintf(f," %g\n};\n",pilot_coeff(Npilotcoeff)); 924 fclose(f); 925endfunction 926 927 928% Saves hanning window coeffs to a text file in the form of a C array 929 930function hanning_file(fdmdv, filename) 931 Npilotlpf = fdmdv.Npilotlpf; 932 933 h = hanning(Npilotlpf); 934 935 f=fopen(filename,"wt"); 936 fprintf(f,"/* Generated by hanning_file() Octave function */\n\n"); 937 fprintf(f,"const float hanning[]={\n"); 938 for m=1:Npilotlpf-1 939 fprintf(f," %g,\n", h(m)); 940 endfor 941 fprintf(f," %g\n};\n", h(Npilotlpf)); 942 fclose(f); 943endfunction 944 945 946function png_file(fig, pngfilename) 947 figure(fig); 948 949 pngname = sprintf("%s.png",pngfilename); 950 print(pngname, '-dpng', "-S500,500") 951 pngname = sprintf("%s_large.png",pngfilename); 952 print(pngname, '-dpng', "-S800,600") 953endfunction 954 955 956% dump rx_bits in hex 957 958function dump_bits(rx_bits) 959 960 % pack into bytes, MSB first 961 962 packed = zeros(1,floor(length(rx_bits)+7)/8); 963 bit = 7; byte = 1; 964 for i=1:length(rx_bits) 965 packed(byte) = bitor(packed(byte), bitshift(rx_bits(i),bit)); 966 bit--; 967 if (bit < 0) 968 bit = 7; 969 byte++; 970 end 971 end 972 973 for i=1:length(packed) 974 printf("0x%02x ", packed(i)); 975 end 976 printf("\n"); 977 978endfunction 979 980