1% fsk_horus.m
2% David Rowe 10 Oct 2015
3%
4% Project Horus High Altitude Balloon (HAB) FSK demodulator
5% See blog write up "All your modems are belong to us"
6%   http://www.rowetel.com/?p=4629
7
8
9fsk_lib;
10
11
12% Basic modem set up for Horus
13
14function states = fsk_horus_init(Fs,Rs,M=2)
15
16  states = fsk_init(Fs,Rs,M);
17
18  % Freq. estimator limits - keep these narrow to stop errors with low SNR 4FSK
19
20  states.fest_fmin = 300;
21  states.fest_fmax = 2800;
22endfunction
23
24
25% init rtty protocol specific states
26
27function rtty = fsk_horus_init_rtty
28  % Generate unque word that correlates against the ASCII "$$$$$" that
29  % is at the start of each frame.
30  % $ -> 36 decimal -> 0 1 0 0 1 0 0 binary
31
32  dollar_bits = fliplr([0 1 0 0 1 0 0]);
33  mapped_db = 2*dollar_bits - 1;
34  sync_bits = [1 1 0];
35  mapped_sb = 2*sync_bits - 1;
36
37  mapped = [mapped_db mapped_sb];
38  npad   = rtty.npad   = 3;     % one start and two stop bits between 7 bit ascii chars
39  nfield = rtty.nfield = 7;     % length of ascii character field
40
41  rtty.uw = [mapped mapped mapped mapped mapped];
42  rtty.uw_thresh = length(rtty.uw) - 2; % allow a few bit errors when looking for UW
43  rtty.max_packet_len = 1000;
44endfunction
45
46
47% I think this is the binary protocol work from Jan 2016
48
49function binary = fsk_horus_init_binary
50  % Generate 16 bit "$$" unique word that is at the front of every horus binary
51  % packet
52
53  dollar_bits = [0 0 1 0 0 1 0 0];
54  mapped_db = 2*dollar_bits - 1;
55
56  binary.uw = [mapped_db mapped_db];
57  binary.uw_thresh = length(binary.uw)-2;   % no bit errors when looking for UW
58
59  binary.max_packet_len = 360;
60endfunction
61
62
63% Look for unique word and return index of first UW bit, or -1 if no
64% UW found Sometimes there may be several matches, returns the
65% position of the best match to UW.
66
67function [uw_start best_corr corr] = find_uw(states, start_bit, rx_bits)
68  uw = states.uw;
69
70  mapped_rx_bits = 2*rx_bits - 1;
71  best_corr = 0;
72  uw_start = -1;
73  found_uw = 0;
74
75  % first first UW in buffer that exceeds threshold
76
77  for i=start_bit:length(rx_bits) - length(uw)
78    corr(i)  = mapped_rx_bits(i:i+length(uw)-1) * uw';
79    if (found_uw == 0) && (corr(i) >= states.uw_thresh)
80      uw_start = i;
81      best_corr = corr;
82      found_uw = 1;
83    end
84  end
85
86endfunction
87
88
89% Extract ASCII string from a Horus frame of bits
90
91function [str crc_ok] = extract_ascii(states, rx_bits_buf, uw_loc)
92  nfield = states.nfield;
93  npad = states.npad;
94
95  str = ""; str_dec = []; nstr = 0; ptx_crc = 1; rx_crc = "";
96  endpacket = 0;
97
98  st = uw_loc + length(states.uw);  % first bit of first char
99  en = uw_loc + states.max_packet_len - nfield;
100  %printf("\nst: %d en: %d len: %d\n", st, en, length(rx_bits_buf));
101
102  for i=st:nfield+npad:en
103    field = rx_bits_buf(i:i+nfield-1);
104    ch_dec = field * (2.^(0:nfield-1))';
105
106    % filter out unlikely characters that bit errors may introduce, and ignore \n
107
108    if (ch_dec > 31) && (ch_dec < 91)
109      str = [str char(ch_dec)];
110    else
111      str = [str char(32)]; % space is "not sure"
112    end
113    nstr++;
114
115    % build up array for CRC16 check
116
117    if !endpacket && (ch_dec == 42)
118      endpacket = 1;
119      rx_crc = crc16(str_dec);      % found a '*' so that's the end of the string for CRC calculations
120      ptx_crc = nstr+1;             % this is where the transmit CRC starts
121    end
122    if !endpacket
123      str_dec = [str_dec ch_dec];
124    end
125  end
126
127  if (ptx_crc+3) <= length(str)
128    tx_crc = str(ptx_crc:ptx_crc+3);
129    crc_ok = strcmp(tx_crc, rx_crc);
130  else
131    crc_ok = 0;
132  end
133
134  str = str(1:ptx_crc-2);
135
136endfunction
137
138
139% Use soft decision information to find bits most likely in error.  I think
140% this is some form of maximum likelihood decoding.
141
142function [str crc_ok rx_bits_log_flipped] = sd_bit_flipping(states, rx_bits_log, rx_bits_sd_log, st, en);
143
144  % force algorithm to ignore rs232 sync bits by marking them as "very likely", they have
145  % no input to crc algorithm
146
147  nfield = states.nfield;
148  npad = states.npad;
149  for i=st:nfield+npad:en
150    rx_bits_sd_log(i+nfield:i+nfield+npad-1) = 1E6;
151  end
152
153  % make a list of bits with smallest soft decn values
154
155  [dodgy_bits_mag dodgy_bits_index] = sort(abs(rx_bits_sd_log(st+length(states.uw):en)));
156  dodgy_bits_index += length(states.uw) + st - 1;
157  nbits = 6;
158  ntries = 2^nbits;
159  str = "";
160  crc_ok = 0;
161
162  % try various combinations of these bits
163
164  for i=1:ntries-1
165    error_mask = zeros(1, length(rx_bits_log));
166    for b=1:nbits
167      x = bitget(i,b);
168      bit_to_flip = dodgy_bits_index(b);
169      error_mask(bit_to_flip) = x;
170      %printf("st: %d i: %d b: %d x: %d index: %d\n", st, i,b,x,bit_to_flip);
171    end
172    rx_bits_log_flipped = xor(rx_bits_log, error_mask);
173    [str_flipped crc_ok_flipped] = extract_ascii(states, rx_bits_log_flipped, st);
174    if crc_ok_flipped
175      %printf("Yayy we fixed a packet by flipping with pattern %d\n", i);
176      str = str_flipped;
177      crc_ok = crc_ok_flipped;
178    end
179  end
180endfunction
181
182
183% Extract as many ASCII packets as we can from a great big buffer of bits
184
185function npackets = extract_and_print_rtty_packets(states, rtty, rx_bits_log, rx_bits_sd_log)
186
187  % use UWs to delimit start and end of data packets
188
189  bit = 1;
190  nbits = length(rx_bits_log);
191  nfield = rtty.nfield;
192  npad = rtty.npad;
193  npackets = 0;
194
195  uw_loc = find_uw(rtty, bit, rx_bits_log, states.verbose);
196
197  while (uw_loc != -1)
198    if bitand(states.verbose,0x8)
199      printf("nbits: %d max_packet_len: %d uw_loc: %d\n", nbits, rtty.max_packet_len, uw_loc);
200    end
201
202    if (uw_loc + rtty.max_packet_len) < nbits
203      % Now start picking out 7 bit ascii chars from frame.  It has some
204      % structure so we can guess where fields are.  I hope we don't get
205      % RS232 idle bits stuck into it anywhere, ie "bit fields" don't
206      % change dynamically.
207
208      % dump msg bits so we can use them as a test signal
209      %msg = rx_bits_log(st:uw_loc-1);
210      %save -ascii horus_msg.txt msg
211
212      % simulate bit error for testing
213      %rx_bits_log(st+200) = xor(rx_bits_log(st+100),1);
214      %rx_bits_sd_log(st+100) = 0;
215
216      [str crc_ok] = extract_ascii(rtty, rx_bits_log, uw_loc);
217
218      if crc_ok == 0
219        [str_flipped crc_flipped_ok rx_bits_log] = sd_bit_flipping(rtty, rx_bits_log, rx_bits_sd_log, uw_loc, uw_loc+rtty.max_packet_len);
220      end
221
222      % update memory of previous packet, we use this to guess where errors may be
223      if crc_ok || crc_flipped_ok
224        states.prev_pkt = rx_bits_log(uw_loc+length(rtty.uw):uw_loc+rtty.max_packet_len);
225      end
226
227      if crc_ok
228        str = sprintf("%s CRC OK", str);
229        npackets++;
230      else
231        if crc_flipped_ok
232          str = sprintf("%s fixed", str_flipped);
233        else
234          str = sprintf("%s CRC BAD", str);
235        end
236      end
237      printf("%s\n", str);
238    end
239
240    % look for next packet
241
242    bit = uw_loc + length(rtty.uw);
243    uw_loc = find_uw(rtty, bit, rx_bits_log, states.verbose);
244
245  endwhile
246endfunction
247
248
249% Extract as many binary packets as we can from a great big buffer of bits,
250% and send them to the C decoder for FEC decoding.
251% horus_l2 can be compiled a bunch of different ways.  You need to
252% compile with:
253%   codec2-dev/src$ gcc horus_l2.c -o horus_l2 -Wall -DDEC_RX_BITS -DHORUS_L2_RX
254
255function corr_log = extract_and_decode_binary_packets(states, binary, rx_bits_log)
256  corr_log = [];
257
258  % use UWs to delimit start and end of data packets
259
260  bit = 1;
261  nbits = length(rx_bits_log);
262
263  [uw_loc best_corr corr] = find_uw(binary, bit, rx_bits_log, states.verbose);
264  corr_log = [corr_log corr];
265
266  while (uw_loc != -1)
267
268    if (uw_loc+binary.max_packet_len) < nbits
269      % printf("uw_loc: %d best_corr: %d\n", uw_loc, best_corr);
270
271      % OK we have a packet delimited by two UWs.  Lets convert the bit
272      % stream into bytes and save for decoding
273
274      pin = uw_loc;
275      for i=1:45
276        rx_bytes(i) = rx_bits_log(pin:pin+7) * (2.^(7:-1:0))';
277        pin += 8;
278        %printf("%d 0x%02x\n", i, rx_bytes(i));
279      end
280
281      f=fopen("horus_rx_bits_binary.bin","wb");
282      fwrite(f, rx_bytes, "uchar");
283      fclose(f);
284
285      % optionally write packet to disk to use as horus_tx_bits_binary.txt
286      f=fopen("horus_rx_bits_binary.txt","wt");
287      for i=uw_loc:uw_loc+45*8-1
288        fprintf(f, "%d ", rx_bits_log(i));
289      end
290      fclose(f);
291
292      system("../src/horus_l2");  % compile instructions above
293    end
294
295    bit = uw_loc + length(binary.uw);
296    [uw_loc best_corr corr] = find_uw(binary, bit, rx_bits_log, states.verbose);
297    corr_log = [corr_log corr];
298
299  endwhile
300
301endfunction
302
303
304% simulation of tx and rx side, add noise, channel impairments ----------------------
305%
306% test_frame_mode     Description
307% 1                   BER testing using known test frames
308% 2                   random bits
309% 3                   repeating sequence of all symbols
310% 4                   Horus RTTY
311% 5                   Horus Binary
312% 6                   Horus High Speed: A 8x oversampled modem, e.g. Fs=9600, Rs=1200
313%                     which is the same as Fs=921600 Rs=115200
314%                     Uses packet based BER counter
315
316function run_sim(test_frame_mode, M=2, frames = 10, EbNodB = 100, filename="fsk_horus.raw")
317  timing_offset = 0.0; % see resample() for clock offset below
318  fading = 0;          % modulates tx power at 2Hz with 20dB fade depth,
319                       % to simulate balloon rotating at end of mission
320  df     = 0;          % tx tone freq drift in Hz/s
321  dA     = 1;          % amplitude imbalance of tones (note this affects Eb so not a gd idea)
322
323  more off
324  rand('state',1);
325  randn('state',1);
326
327  % ----------------------------------------------------------------------
328
329  % sm2000 config ------------------------
330  %states = fsk_horus_init(96000, 1200);
331  %states.f1_tx = 4000;
332  %states.f2_tx = 5200;
333
334  if test_frame_mode < 4
335    % horus rtty config ---------------------
336    states = fsk_horus_init(8000, 50, M);
337  end
338
339  if test_frame_mode == 4
340    % horus rtty config ---------------------
341    states = fsk_horus_init(8000, 100, 2);
342    states.tx_bits_file = "horus_payload_rtty.txt"; % Octave file of bits we FSK modulate
343    rtty = fsk_horus_init_rtty;
344    states.ntestframebits = rtty.max_packet_len;
345  end
346
347  if test_frame_mode == 5
348    % horus binary config ---------------------
349    states = fsk_horus_init(8000, 100, 4);
350    binary = fsk_horus_init_binary;
351    states.tx_bits_file = "horus_tx_bits_binary.txt"; % Octave file of bits we FSK modulate
352    states.ntestframebits = binary.max_packet_len;
353  end
354
355  if test_frame_mode == 6
356    % horus high speed ---------------------
357    states = fsk_horus_init(Fs=9600, Rs=1200, M=2, P=8, nsym=16);
358    states.tx_bits_file = "horus_high_speed.bin";
359  end
360
361  % Tones must be at least Rs apart for ideal non-coherent FSK
362
363  states.ftx = 900 + 2*states.Rs*(1:states.M);
364  states.tx_tone_separation = states.ftx(2) - states.ftx(1);
365
366  % ----------------------------------------------------------------------
367
368  states.verbose = 0x1;
369  M = states.M;
370  N = states.N;
371  P = states.P;
372  Rs = states.Rs;
373  nsym = states.nsym;
374  nbit = states.nbit;
375  Fs = states.Fs;
376  states.df(1:M) = df;
377  states.dA(1:M) = dA;
378
379  % optional noise.  Useful for testing performance of waveforms from real world modulators
380
381  EbNo = 10^(EbNodB/10);
382  variance = states.Fs/(states.Rs*EbNo*states.bitspersymbol);
383
384  % set up tx signal with payload bits based on test mode
385
386  if (test_frame_mode == 1)
387    % test frame of bits, which we repeat for convenience when BER testing
388    states.ntestframebits = states.nbit;
389    test_frame = round(rand(1, states.ntestframebits));
390    tx_bits = [];
391    for i=1:frames+1
392      tx_bits = [tx_bits test_frame];
393    end
394  end
395
396  if test_frame_mode == 2
397    % random bits, just to make sure sync algs work on random data
398    tx_bits = round(rand(1, states.nbit*(frames+1)));
399  end
400
401  if test_frame_mode == 3
402    % repeating sequence of all symbols
403    % great for initial test of demod if nothing else works,
404    % look for this pattern in rx_bits
405    if M == 2
406       % ...10101...
407      tx_bits = zeros(1, states.nbit*(frames+1));
408      tx_bits(1:2:length(tx_bits)) = 1;
409    else
410      % repeat each possible 4fsk symbol
411      pattern = [0 0 0 1 1 0 1 1];
412      %pattern = [0 0 0 1 1 1 1 0];
413      nrepeats = states.nbit*(frames+1)/length(pattern);
414      tx_bits = [];
415      for b=1:nrepeats
416        tx_bits = [tx_bits pattern];
417      end
418      %tx_bits = zeros(1, states.nbit*(frames+1));
419    end
420  end
421
422  if (test_frame_mode == 4) || (test_frame_mode == 5)
423
424    % load up a horus msg from disk and modulate that
425
426    test_frame = load(states.tx_bits_file);
427    ltf = length(test_frame);
428    ntest_frames = ceil((frames+1)*nbit/ltf);
429    printf("Generating %d test packets\n", ntest_frames);
430
431    % 1 second of random bits to let estimators lock on
432
433    preamble = round(rand(1,states.Rs));
434
435    tx_bits = preamble;
436    for i=1:ntest_frames
437      tx_bits = [tx_bits test_frame];
438    end
439
440    % a packet len of random bits at end fill buffers to deocode final packet
441
442    if test_frame_mode == 4
443      postamble = round(rand(1,rtty.max_packet_len));
444    else
445      postamble = round(rand(1,binary.max_packet_len));
446    end
447    tx_bits = [tx_bits postamble];
448  end
449
450  if test_frame_mode == 6
451    states.verbose += 0x4;
452    ftmp = fopen(states.tx_bits_file, "rb"); test_frame = fread(ftmp,Inf,"char")'; fclose(ftmp);
453    states.ntestframebits = length(test_frame);
454    printf("length test frame: %d\n", states.ntestframebits);
455    %test_frame = rand(1,states.ntestframebits) > 0.5;
456
457    tx_bits = [];
458    for i=1:frames+1
459      tx_bits = [tx_bits test_frame];
460    end
461  end
462
463  tx = fsk_mod(states, tx_bits);
464
465  %tx = resample(tx, 1000, 1001); % simulated 1000ppm sample clock offset
466
467  if fading
468     ltx = length(tx);
469     tx = tx .* (1.1 + cos(2*pi*2*(0:ltx-1)/Fs))'; % min amplitude 0.1, -20dB fade, max 3dB
470  end
471
472  noise = sqrt(variance)*randn(length(tx),1);
473  rx    = tx + noise;
474  printf("SNRdB meas: %4.1f\n", 10*log10(var(tx)/var(noise)));
475
476  % dump simulated rx file
477
478  ftx=fopen(filename,"wb"); rxg = rx*1000; fwrite(ftx, rxg, "short"); fclose(ftx);
479
480  timing_offset_samples = round(timing_offset*states.Ts);
481  st = 1 + timing_offset_samples;
482  rx_bits_buf = zeros(1,nbit+states.ntestframebits);
483  x_log = [];
484  timing_nl_log = [];
485  norm_rx_timing_log = [];
486  f_int_resample_log = [];
487  f_log = [];
488  EbNodB_log = [];
489  rx_bits_log = [];
490  rx_bits_sd_log = [];
491
492  % main loop ---------------------------------------------------------------
493
494  run_frames = floor(length(rx)/N)-1;
495  for f=1:run_frames
496
497    % extract nin samples from input stream
498
499    nin = states.nin;
500    en = st + states.nin - 1;
501
502    if en < length(rx) % due to nin variations its possible to overrun buffer
503      sf = rx(st:en);
504      st += nin;
505
506      % demodulate to stream of bits
507
508      states = est_freq(states, sf, states.M);
509      %states.f = 900 + 2*states.Rs*(1:states.M);
510      %states.f = [1200 1400 1600 1800];
511      [rx_bits states] = fsk_demod(states, sf);
512
513      rx_bits_buf(1:states.ntestframebits) = rx_bits_buf(nbit+1:states.ntestframebits+nbit);
514      rx_bits_buf(states.ntestframebits+1:states.ntestframebits+nbit) = rx_bits;
515      %rx_bits_buf(1:nbit) = rx_bits_buf(nbit+1:2*nbit);
516      %rx_bits_buf(nbit+1:2*nbit) = rx_bits;
517
518      rx_bits_log = [rx_bits_log rx_bits];
519      rx_bits_sd_log = [rx_bits_sd_log states.rx_bits_sd];
520
521      norm_rx_timing_log = [norm_rx_timing_log states.norm_rx_timing];
522      x_log = [x_log states.x];
523      timing_nl_log = [timing_nl_log states.timing_nl];
524      f_int_resample_log = [f_int_resample_log abs(states.f_int_resample(:,:))];
525      f_log = [f_log; states.f];
526      EbNodB_log = [EbNodB_log states.EbNodB];
527
528      if test_frame_mode == 1
529        states = ber_counter(states, test_frame, rx_bits_buf);
530      end
531      if test_frame_mode == 6
532        states = ber_counter_packet(states, test_frame, rx_bits_buf);
533      end
534   end
535  end
536
537  % print stats, count errors, decode packets  ------------------------------------------
538
539  if (test_frame_mode == 1) || (test_frame_mode == 6)
540    printf("frames: %d EbNo: %3.2f Tbits: %d Terrs: %d BER %4.3f\n", frames, EbNodB, states.Tbits,states. Terrs, states.Terrs/states.Tbits);
541  end
542
543  if test_frame_mode == 4
544    npackets = extract_and_print_rtty_packets(states, rtty, rx_bits_log, rx_bits_sd_log);
545    printf("Received %d packets\n", npackets);
546  end
547
548  if test_frame_mode == 5
549    extract_and_decode_binary_packets(states, binary, rx_bits_log);
550  end
551
552  figure(1);
553  plot(f_int_resample_log','+')
554  hold off;
555
556  figure(2)
557  clf
558  m = max(abs(x_log));
559  plot(x_log,'+')
560  axis([-m m -m m])
561  title('fine timing metric')
562
563  figure(3)
564  clf
565  subplot(211)
566  plot(norm_rx_timing_log);
567  axis([1 run_frames -1 1])
568  title('norm fine timing')
569  subplot(212)
570  plot(states.nerr_log)
571  title('num bit errors each frame')
572
573  figure(4)
574  clf
575  subplot(211)
576  one_sec_rx = rx(1:min(Fs,length(rx)));
577  plot(one_sec_rx)
578  title('rx signal at demod input')
579  subplot(212)
580  plot(abs(fft(one_sec_rx)))
581
582  figure(5)
583  clf
584  plot(f_log,'+')
585  title('tone frequencies')
586  axis([1 run_frames 0 Fs/2])
587
588  figure(6)
589  clf
590  plot(EbNodB_log);
591  title('Eb/No estimate')
592
593  figure(7)
594  clf
595  subplot(211)
596  X = abs(fft(timing_nl_log));
597  plot(X(1:length(X)/2))
598  subplot(212)
599  plot(abs(timing_nl_log(1:100)))
600
601 endfunction
602
603
604% ---------------------------------------------------------------------
605% demodulate from a user-supplied file
606% ---------------------------------------------------------------------
607
608function rx_bits_log = demod_file(filename, test_frame_mode=4, noplot=0, EbNodB=100, max_frames=1E32)
609  fin = fopen(filename,"rb");
610  more off;
611  read_complex = 0; sample_size = 'int16'; shift_fs_on_4 = 0;
612  max_frames
613
614  if test_frame_mode == 4
615    % horus rtty config ---------------------
616    states = fsk_horus_init(8000, 100, 2);
617    rtty = fsk_horus_init_rtty;
618    states.ntestframebits = rtty.max_packet_len;
619  end
620
621  if test_frame_mode == 5
622    % horus binary config ---------------------
623    states = fsk_horus_init(8000, 100, 4);
624    binary = fsk_horus_init_binary;
625    states.ntestframebits = binary.max_packet_len;
626  end
627
628  states.verbose = 0x1 + 0x8;
629
630  if test_frame_mode == 6
631    % Horus high speed config --------------
632    states = fsk_horus_init(Fs=9600, Rs=1200, M=2,  P=8, nsym=16);
633    states.tx_bits_file = "horus_high_speed.bin";
634    states.verbose += 0x4;
635    ftmp = fopen(states.tx_bits_file, "rb"); test_frame = fread(ftmp,Inf,"char")'; fclose(ftmp);
636    states.ntestframebits = length(test_frame);
637    printf("length test frame: %d\n", states.ntestframebits);
638  end
639
640  if test_frame_mode == 7
641    % 800XA 4FSK modem --------------
642    states = fsk_init(Fs=8000, Rs=400, M=4, P=10, nsym=256);
643    states.tx_bits_file = "horus_high_speed.bin";
644    states.verbose += 0x4;
645    ftmp = fopen(states.tx_bits_file, "rb"); test_frame = fread(ftmp,Inf,"char")'; fclose(ftmp);
646    states.ntestframebits = length(test_frame);
647    printf("length test frame: %d\n", states.ntestframebits);
648  end
649
650  if test_frame_mode == 8
651    % test RS41 type balllon telemetry --------------
652    states = fsk_init(96000, 4800, 2, 10, 16);
653    states.fest_fmin = 1000;
654    states.fest_fmax = 40000;
655    states.fest_min_spacing = 1000;
656    states.tx_bits_file = "../build_linux/src/tx_bit.bin";
657    states.verbose += 0x4;
658    #ftmp = fopen(states.tx_bits_file, "rb"); test_frame = fread(ftmp,Inf,"char")'; fclose(ftmp);
659    #states.ntestframebits = length(test_frame);
660    #printf("length test frame: %d\n", states.ntestframebits);
661    states.ntestframebits = 1000;
662    read_complex = 1;
663    shift_fs_on_4 = 1; % get samples into range of current freq estimator
664  end
665
666  if test_frame_mode == 9
667    % Wenet high speed SSTV, we can just check raw demo here ---------------------
668    % despite the high sample rate the modem sees this as a 8:1 Fs/Rs configuration
669    states = fsk_init(8000, 1000, 2);
670    states.tx_tone_separation = 1000;
671    states.ntestframebits = (256+2+65)*8+40; % from src/drs232_lpc.c
672    states.freq_est_type = 'mask';
673    read_complex=1; sample_size = 'uint8';
674    printf("Wenet mode: ntestframebits: %d freq_est_type: %s\n", states.ntestframebits, states.freq_est_type);
675    %states.verbose = 0x8;
676  end
677
678  N = states.N;
679  P = states.P;
680  Rs = states.Rs;
681  nsym = states.nsym;
682  nbit = states.nbit;
683
684  frames = 0;
685  rx = [];
686  rx_bits_log = [];
687  rx_bits_sd_log = [];
688  norm_rx_timing_log = [];
689  f_int_resample_log = [];
690  EbNodB_log = [];
691  ppm_log = [];
692  f_log = []; Sf_log = [];
693
694  rx_bits_buf = zeros(1,nbit + states.ntestframebits);
695
696  % optional noise.  Useful for testing performance of waveforms from real world modulators
697  % we need to pre-read the file to estimate the signal power
698  ftmp = fopen(filename,"rb"); s = fread(ftmp,Inf,sample_size); fclose(ftmp);
699  if sample_size == "uint8" s = (s - 127)/128; end
700  if read_complex s = s(1:2:end) + j*s(2:2:end); end
701  tx_pwr = var(s);
702  EbNo = 10^(EbNodB/10);
703  variance = (tx_pwr/2)*states.Fs/(states.Rs*EbNo*states.bitspersymbol);
704
705  % First extract raw bits from samples ------------------------------------------------------
706
707  printf("demod of raw bits....\n");
708
709  finished = 0; ph = 1;
710  while (finished == 0)
711
712    % extract nin samples from input stream
713
714    nin = states.nin;
715    if read_complex
716      [sf count] = fread(fin, 2*nin, sample_size);
717      if sample_size == "uint8" sf = (sf - 127)/128; end
718      sf = sf(1:2:end) + j*sf(2:2:end);
719      count /= 2;
720      if shift_fs_on_4
721        % optional shift up in freq by Fs/4 to get into freq est range
722        for i=1:count
723          ph = ph*exp(j*pi/4);
724          sf(i) *= ph;
725        end
726      end
727    else
728      [sf count] = fread(fin, nin, "short");
729    end
730    rx = [rx; sf];
731
732    % add optional noise
733
734    if count
735      noise = sqrt(variance)*randn(count,1);
736      sf += noise;
737    end
738
739    if count == nin
740      frames++;
741
742      % demodulate to stream of bits
743
744      states = est_freq(states, sf, states.M);
745      if states.freq_est_type == 'mask' states.f = states.f2; end
746      [rx_bits states] = fsk_demod(states, sf);
747
748      rx_bits_buf(1:states.ntestframebits) = rx_bits_buf(nbit+1:states.ntestframebits+nbit);
749      rx_bits_buf(states.ntestframebits+1:states.ntestframebits+nbit) = rx_bits;
750
751      rx_bits_log = [rx_bits_log rx_bits];
752      rx_bits_sd_log = [rx_bits_sd_log states.rx_bits_sd];
753      norm_rx_timing_log = [norm_rx_timing_log states.norm_rx_timing];
754      f_int_resample_log = [f_int_resample_log abs(states.f_int_resample)];
755      EbNodB_log = [EbNodB_log states.EbNodB];
756      ppm_log = [ppm_log states.ppm];
757      f_log = [f_log; states.f];
758      Sf_log = [Sf_log; states.Sf'];
759
760      if (test_frame_mode == 1)
761        states = ber_counter(states, test_frame, rx_bits_buf);
762        if states.ber_state == 1
763          states.verbose = 0;
764        end
765      end
766      if (test_frame_mode == 6)  % || (test_frame_mode == 8)
767        states = ber_counter_packet(states, test_frame, rx_bits_buf);
768      end
769     else
770      finished = 1;
771    end
772
773    if frames > max_frames finished=1; end
774
775  end
776  printf("frames: %d\n", frames);
777  fclose(fin);
778
779  if noplot == 0
780    printf("plotting...\n");
781
782    figure(1); clf;
783    plot(f_log);
784    title('Tone Freq Estimates');
785
786    figure(2);
787    plot(f_int_resample_log','+')
788    title('Integrator outputs for each tone');
789
790    figure(3); clf
791    subplot(211)
792    plot(norm_rx_timing_log)
793    axis([1 frames -0.5 0.5])
794    title('norm fine timing')
795    subplot(212)
796    plot(states.nerr_log)
797    title('num bit errors each frame')
798
799    figure(4); clf
800    plot(EbNodB_log);
801    title('Eb/No estimate')
802
803    figure(5); clf
804    rx_nowave = rx(1000:length(rx)); % skip past wav header if it's a wave file
805    subplot(211)
806    plot(real(rx_nowave));
807    title('input signal to demod (1 sec)')
808    xlabel('Time (samples)');
809    %axis([1 states.Fs -35000 35000])
810
811    % normalise spectrum to 0dB full scale with sine wave input
812    subplot(212);
813    if sample_size == "int16" max_value = 32767; end
814    if sample_size == "uint8" max_value = 127; end
815    RxdBFS = 20*log10(abs(fft(rx_nowave(1:states.Fs)))) - 20*log10((states.Fs/2)*max_value);
816    plot(RxdBFS)
817    axis([1 states.Fs/2 -80 0])
818    xlabel('Frequency (Hz)');
819
820    figure(6); clf
821    plot(ppm_log)
822    title('Sample clock (baud rate) offset in PPM');
823
824    figure(7); clf; mesh(Sf_log(1:10,:));
825  end
826
827  if (test_frame_mode == 1) || (test_frame_mode == 6)
828    printf("frames: %d Tbits: %d Terrs: %d BER %4.3f EbNo: %3.2f\n", frames, states.Tbits,states. Terrs, states.Terrs/states.Tbits, mean(EbNodB_log));
829  end
830
831  % we can decode both protocols at the same time
832
833  if (test_frame_mode == 4)
834    npackets = extract_and_print_rtty_packets(states, rtty, rx_bits_log, rx_bits_sd_log)
835    printf("Received %d packets\n", npackets);
836  end
837
838  if (test_frame_mode == 5)
839    corr_log = extract_and_decode_binary_packets(states, binary, rx_bits_log);
840
841    figure(8);
842    clf
843    plot(corr_log);
844    hold on;
845    plot([1 length(corr_log)],[binary.uw_thresh binary.uw_thresh],'g');
846    hold off;
847    title('UW correlation');
848  end
849
850endfunction
851
852
853% Over the years this modem has been used for many different FSK signals ...
854
855if exist("fsk_horus_as_a_lib") == 0
856  run_sim(test_frame_mode=4, M=2, frames=30, EbNodB = 20);
857  %run_sim(5, 4, 30, 100);
858  %rx_bits = demod_file("~/Desktop/115.wav",6,0,90);
859  %rx_bits = demod_file("~/Desktop/fsk_800xa_rx_hackrf.wav",7);
860  %rx_bits = demod_file("~/Desktop/2fsk_100_rx_rpi_rtlsdr_002_ledger.wav",4);
861  %rx_bits = demod_file("~/Desktop/phorus_binary_ascii.wav",4);
862  %rx_bits = demod_file("~/Desktop/binary/horus_160102_binary_rtty_2.wav",4);
863  %rx_bits = demod_file("~/Desktop/horus_160102_vk5ei_capture2.wav",4);
864  %rx_bits = demod_file("~/Desktop/horus_rtty_binary.wav",4);
865  %rx_bits = demod_file("~/Desktop/FSK_4FSK.wav",4);
866  %rx_bits = demod_file("t.raw",5);
867  %rx_bits = demod_file("~/Desktop/fsk_horus_10dB_1000ppm.wav",4);
868  %rx_bits = demod_file("~/Desktop/fsk_horus_6dB_0ppm.wav",4);
869  %rx_bits = demod_file("test.raw",1,1);
870  %rx_bits = .rawdemod_file("/dev/ttyACM0",1);
871  %rx_bits = demod_file("fsk_horus_rx_1200_96k.raw",1);
872  %rx_bits = demod_file("mp.raw",4);
873  %rx_bits = demod_file("~/Desktop/launchbox_v2_landing_8KHz_final.wav",4);
874  %rx_bits = demod_file("~/Desktop/fsk_800xa.wav",7);
875  %rx_bits = demod_file("~/Desktop/rs41_96k_10s.iq16",8);
876end
877