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