diff --git a/CMakeLists.txt b/CMakeLists.txt index ff1b2959..dbd2eca7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -472,7 +472,7 @@ endif() add_test(NAME test_OFDM_modem_octave_qam16_uncoded COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; - echo \"ofdm_tx('test_qam16.raw','qam16c1',3,12,'awgn','bursts',3); ofdm_rx('test_qam16.raw','qam16c1', 'passber', 0.05, 'packetsperburst', 1); quit\" | + echo \"ofdm_tx('test_qam16.raw','qam16c2',1,12,'awgn','bursts',3); ofdm_rx('test_qam16.raw','qam16c2', 'passber', 0.05, 'packetsperburst', 1); quit\" | DISPLAY=\"\" octave-cli") set_tests_properties(test_OFDM_modem_octave_qam16_uncoded PROPERTIES PASS_REGULAR_EXPRESSION "Pass") @@ -662,6 +662,20 @@ endif() cd ${CMAKE_CURRENT_BINARY_DIR}/src; cat test.raw | ./ofdm_demod --mode datac13 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + # DATAC14 Octave Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_datac14_octave + COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; + DISPLAY=\"\" octave-cli -qf --eval 'ofdm_ldpc_tx(\"${CMAKE_CURRENT_BINARY_DIR}/src/test.raw\",\"datac14\",1,3,\"awgn\",\"bursts\",5)'; + cd ${CMAKE_CURRENT_BINARY_DIR}/src; + cat test.raw | ./ofdm_demod --mode datac14 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + + # QAM16C2 Octave Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_qam16c2_octave + COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; + DISPLAY=\"\" octave-cli -qf --eval 'ofdm_ldpc_tx(\"${CMAKE_CURRENT_BINARY_DIR}/src/test.raw\",\"qam16c2\",1,10,\"awgn\",\"bursts\",2)'; + cd ${CMAKE_CURRENT_BINARY_DIR}/src; + cat test.raw | ./ofdm_demod --mode qam16c2 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + # DATAC4 C Tx, C Rx, burst mode add_test(NAME test_OFDM_modem_datac4_ldpc_burst COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; @@ -676,7 +690,21 @@ endif() ./ch - - --No -17 | ./ofdm_demod --mode datac13 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") - # ------------------------------------------------------------------------- + # DATAC14 C Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_datac14_ldpc_burst + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + ./ofdm_mod --mode datac14 --in /dev/zero --testframes 1 --verbose 1 --ldpc --bursts 10 | + ./ch - - --No -17 | + ./ofdm_demod --mode datac14 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + + # QAM16C2 C Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_qam16c2_ldpc_burst + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + ./ofdm_mod --mode qam16c2 --in /dev/zero --testframes 1 --verbose 1 --ldpc --bursts 10 | + ./ch - - --No -30 | + ./ofdm_demod --mode qam16c2 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + + # ------------------------------------------------------------------------- # LDPC # ------------------------------------------------------------------------- @@ -1064,6 +1092,12 @@ if (NOT APPLE) ./freedv_data_raw_tx --testframes 10 DATAC13 /dev/zero /dev/null") set_tests_properties(test_memory_leak_FreeDV_DATAC13_tx PROPERTIES PASS_REGULAR_EXPRESSION "ERROR SUMMARY: 0 errors") + add_test(NAME test_memory_leak_FreeDV_DATAC14_tx + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes \ + ./freedv_data_raw_tx --testframes 10 DATAC14 /dev/zero /dev/null") + set_tests_properties(test_memory_leak_FreeDV_DATAC14_tx PROPERTIES PASS_REGULAR_EXPRESSION "ERROR SUMMARY: 0 errors") + add_test(NAME test_memory_leak_FreeDV_700E_tx COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes \ @@ -1271,6 +1305,12 @@ endif(NOT APPLE) ./freedv_data_raw_rx --framesperburst 2 --testframes DATAC0 - /dev/null --vv") set_tests_properties(test_freedv_data_raw_ofdm_datac0_burst PROPERTIES PASS_REGULAR_EXPRESSION "Coded FER: 0.0000 Tfrms: 6 Tfers: 0") + add_test(NAME test_freedv_data_raw_ofdm_data_custom + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + ./freedv_data_raw_tx --bursts 3 --testframes 3 custom /dev/zero - | + ./freedv_data_raw_rx --testframes custom - /dev/null --vv") + set_tests_properties(test_freedv_data_raw_ofdm_data_custom PROPERTIES PASS_REGULAR_EXPRESSION "Coded FER: 0.0000 Tfrms: 3 Tfers: 0") + # Burst mode with data file I/O: add_test(NAME test_freedv_data_raw_ofdm_datac0_burst_file COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; @@ -1307,6 +1347,20 @@ endif(NOT APPLE) ./freedv_data_raw_rx DATAC13 - binaryOut.bin -v; diff binaryIn.bin binaryOut.bin") + add_test(NAME test_freedv_data_raw_ofdm_datac14_burst_file + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + head -c $((3*10)) binaryIn.bin; + ./freedv_data_raw_tx DATAC14 binaryIn.bin - --bursts 10 | + ./freedv_data_raw_rx DATAC14 - binaryOut.bin -v; + diff binaryIn.bin binaryOut.bin") + + add_test(NAME test_freedv_data_raw_ofdm_qam16c2_burst_file + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + head -c $((1213*10)) binaryIn.bin; + ./freedv_data_raw_tx qam16c2 binaryIn.bin - --bursts 10 | + ./freedv_data_raw_rx qam16c2 - binaryOut.bin -v; + diff binaryIn.bin binaryOut.bin") + # FSK LDPC default 100 bit/s 2FSK, enough noise for several % raw BER to give # FEC/acquisition a work out, bursts of 1 frame as that stresses acquisition add_test(NAME test_freedv_data_raw_fsk_ldpc_100 @@ -1392,6 +1446,8 @@ endif(NOT APPLE) test_OFDM_modem_datac3_octave test_OFDM_modem_datac4_octave test_OFDM_modem_datac13_octave + test_OFDM_modem_datac14_octave + test_OFDM_modem_qam16c2_octave test_fsk_lib_4fsk_ldpc test_OFDM_modem_datac0_compression PROPERTIES diff --git a/README.md b/README.md index 726152cc..2247afb3 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Feature Requests can be submitted via GitHub Issues. Before writing any code or submitting a PR - **please discuss** the PR with developers by raising a GitHub Issue. We have many years of experience and a carefully considered plan for Codec 2 development, and can guide you on work that will most benefit this project. Some key guidelines about the code in the `codec2` repo: -1. Code that is required to build libcodec2, or to test libcodec2 goes in codec2. +1. Only code that is required to build, test, or document libcodec2 goes in codec2. 2. Experimental work, code used for algorithm development, should probably go into some other repo. 3. Only widely used “production” code goes in codec2. If it has an user base of < 2 (e.g. personal projects, early R&D) - it should probably be application code or a fork. diff --git a/README_data.md b/README_data.md index 17481f39..b53b5fa0 100644 --- a/README_data.md +++ b/README_data.md @@ -146,6 +146,8 @@ These modes use an OFDM modem with powerful LDPC codes and are designed for send | DATAC3 | 500 | 321 | 126 | (2048,1024) | 3.19 | 74/100 at 0dB | Forward link data (low SNR) | | DATAC4 | 250 | 87 | 56 | (1472,448) | 5.17 | 90/100 at -4dB | Forward link data (low SNR) | | DATAC13 | 200 | 64 | 14 | (384,128) | 2.0 | 90/100 at -4dB | Reverse link ACK packets (low SNR) | +| DATAC14 | 250 | 58 | 3 | (112,56) | 0.69 | 90/100 at -2dB | Reverse link ACK packets (low SNR) | +| QAM16C2 | 2100 | 3100 | 1213 | (16200,9720) | 3.2 | 90/100 at 15dB | Forward link data (high SNR) | Notes: 1. 16 bits (2 bytes) per frame are reserved for a 16 bit CRC, e.g. for `datac3` we have 128 byte frames, and 128-2=126 bytes/frame of payload data. @@ -245,7 +247,7 @@ This command line demonstrates the effect: ``` Try adjusting `--clip` and `No` argument of `ch` (noise level) for different modes. Note the SNR estimates returned from `freedv_data_raw_rx` compared to the SNR from the channel simulator `ch`. You will notice clipping also increases the RMS power and reduces the PER for a given channel noise power. CPAPR will also reduce with clipping enabled. -The following plots illustrate the SNR estimates versus actual channel SNR with and without compression (clipping). Not that even with the uncompressed waveform there is a small offset of around 1dB, possibly due to modem implementation loss or noise in the frequency, phase, or timing estimators. +The following plots illustrate the SNR estimates versus actual channel SNR with and without compression (clipping). Note that even with the uncompressed waveform there is a small offset of around 1dB, possibly due to modem implementation loss or noise in the frequency, phase, or timing estimators. ![](doc/snrest_snr_ctx.png) ![](doc/snrest_snr_ctxc.png) diff --git a/doc/c_tx_comp.png b/doc/c_tx_comp.png index 5d781c05..a9504796 100644 Binary files a/doc/c_tx_comp.png and b/doc/c_tx_comp.png differ diff --git a/doc/c_tx_comp_thruput.png b/doc/c_tx_comp_thruput.png index 0288f7aa..f4010ca9 100644 Binary files a/doc/c_tx_comp_thruput.png and b/doc/c_tx_comp_thruput.png differ diff --git a/doc/modem_codec_frame_design.ods b/doc/modem_codec_frame_design.ods index e6cf1c74..040ba016 100644 Binary files a/doc/modem_codec_frame_design.ods and b/doc/modem_codec_frame_design.ods differ diff --git a/octave/gp_interleaver.m b/octave/gp_interleaver.m index 1ee0ee38..c97af0f1 100644 --- a/octave/gp_interleaver.m +++ b/octave/gp_interleaver.m @@ -7,18 +7,31 @@ 1; +% return 1 if prime +function ret = is_prime(x) + for i=2:x-1 + if mod(x,i) == 0 + ret = 0; + return; + end + end + ret = 1; +end + +function x = next_prime(x) + x++; + while is_prime(x) == 0 + x++; + end +end + % Choose b for Golden Prime Interleaver. b is chosen to be the % closest integer, which is relatively prime to N, to the Golden % section of N. function b = choose_interleaver_b(Nbits) - - p = primes(Nbits); - i = 1; - while(p(i) < Nbits/1.62) - i++; - end - b = p(i); + b = floor(Nbits/1.62); + b = next_prime(b); assert(gcd(b,Nbits) == 1, "b and Nbits must be co-prime"); end @@ -36,7 +49,7 @@ function frame = gp_deinterleave(interleaved_frame) Nbits = length(interleaved_frame); - b = choose_interleaver_b(Nbits); + b = choose_interleaver_b(Nbits); frame = zeros(1,Nbits); for i=1:Nbits j = mod((b*(i-1)), Nbits); diff --git a/octave/ofdm_helper.m b/octave/ofdm_helper.m index b34ec239..38c02486 100644 --- a/octave/ofdm_helper.m +++ b/octave/ofdm_helper.m @@ -44,7 +44,8 @@ function print_config(states) printf("Nc=%d Ts=%4.3f Tcp=%4.3f Ns: %d Np: %d\n", Nc, 1/Rs, Tcp, Ns, Np); printf("Nsymperframe: %d Nbitsperpacket: %d Nsamperframe: %d Ntxtbits: %d Nuwbits: %d Nuwframes: %d\n", Ns*Nc, Nbitsperpacket, Nsamperframe, Ntxtbits, Nuwbits, Nuwframes); - printf("uncoded bits/s: %4.1f\n", Nbitsperpacket*Fs/(Np*Nsamperframe)); + printf("uncoded bits/s: %4.1f Duration (incl post/preamble): %4.2f s\n", + Nbitsperpacket*Fs/(Np*Nsamperframe), (Np+2)*Ns*(Tcp+1/Rs)); end %----------------------------------------------------------------------- diff --git a/octave/ofdm_lib.m b/octave/ofdm_lib.m index 2d3a16e7..93a2da7a 100644 --- a/octave/ofdm_lib.m +++ b/octave/ofdm_lib.m @@ -52,7 +52,6 @@ bad_uw_errors = config.bad_uw_errors; amp_scale = config.amp_scale; amp_est_mode = config.amp_est_mode; - EsNo_est_all_symbols = config.EsNo_est_all_symbols; EsNodB = config.EsNodB; state_machine = config.state_machine; edge_pilots = config.edge_pilots; @@ -74,10 +73,10 @@ states.Nsampersymbol = states.M+states.Ncp; % number of samples in a single symbol states.Nsamperframe = Ns*states.Nsampersymbol; % number of samples in a modem frame states.qam16 = [ - 1 + j, 1 + j*3, 3 + j, 3 + j*3; - 1 - j, 1 - j*3, 3 - j, 3 - j*3; - -1 + j, -1 + j*3, -3 + j, -3 + j*3; - -1 - j, -1 - j*3, -3 - j, -3 - j*3]/3; + 1 + j, 1 + j*3, 3 + j, 3 + j*3, ... + 1 - j, 1 - j*3, 3 - j, 3 - j*3, ... + -1 + j, -1 + j*3, -3 + j, -3 + j*3, ... + -1 - j, -1 - j*3, -3 - j, -3 - j*3]; rms = sqrt(states.qam16(:)'*states.qam16(:)/16);% set average Es to 1 states.qam16 /= rms; states.qam16 *= exp(-j*pi/4); % same rotation as QPSK constellation @@ -128,7 +127,7 @@ states.uw_ind = [states.uw_ind bps*ind_sym-b]; % bit index end end - + % how many of the first few frames have UW symbols in them Nsymsperframe = states.Nbitsperframe/states.bps; states.Nuwframes = ceil(states.uw_ind_sym(end)/Nsymsperframe); @@ -275,7 +274,6 @@ % Es/No (SNR) est states - states.EsNo_est_all_symbols = EsNo_est_all_symbols; states.clock_offset_est = 0; % pre-amble for data modes @@ -1254,7 +1252,7 @@ Can be used for acquisition (coarse timing), and fine timing. Tends % with acquisition function [rx delay_samples] = ofdm_rx_filter(states, mode, rx) delay_samples = 0; - if strcmp(mode,"datac4") || strcmp(mode,"datac13") + if strcmp(mode,"datac4") || strcmp(mode,"datac13") || strcmp(mode,"datac14") w_centre = mean(states.w); centre_norm = w_centre/(2*pi); n_coeffs = 100; cutoff_Hz = 400; cutoff_norm = cutoff_Hz/states.Fs; diff --git a/octave/ofdm_mode.m b/octave/ofdm_mode.m index 074fd84f..87009e72 100644 --- a/octave/ofdm_mode.m +++ b/octave/ofdm_mode.m @@ -25,7 +25,6 @@ config.bad_uw_errors = 3; config.amp_scale = 245E3; config.amp_est_mode = 0; - config.EsNo_est_all_symbols = 1; config.EsNodB = 3; config.state_machine = "voice1"; config.edge_pilots = 1; @@ -59,12 +58,12 @@ config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 15*4; config.bad_uw_errors = 5; config.state_machine = "data"; config.ftwindow_width = 32; config.amp_scale = 132E3; - config.EsNo_est_all_symbols = 0; config.amp_est_mode = 1; config.EsNodB = 10; + config.amp_est_mode = 1; config.EsNodB = 10; elseif strcmp(mode,"qam16c2") Ns=5; config.Np=31; Tcp = 0.004; Ts = 0.016; Nc = 33; config.data_mode = "streaming"; - config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 42*4; config.bad_uw_errors = 15; + config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 42*4; config.bad_uw_errors = 50; config.ftwindow_width = 80; config.amp_scale = 135E3; config.state_machine = "data"; - config.EsNo_est_all_symbols = 0; config.amp_est_mode = 1; config.EsNodB = 10; + config.amp_est_mode = 1; config.EsNodB = 10; config.tx_uw = zeros(1,config.Nuwbits = 42*4); config.tx_uw(1:24) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; config.tx_uw(end-24+1:end) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; @@ -134,6 +133,18 @@ config.state_machine = "data"; config.amp_scale = 2.5*300E3; config.clip_gain1 = 1.2; config.clip_gain2 = 1.0; config.txbpf_width_Hz = 400; + elseif strcmp(mode,"datac14") + Ns=5; config.Np=4; Tcp = 0.005; Ts = 0.018; Nc = 4; config.data_mode = "streaming"; + config.edge_pilots = 0; + config.Ntxtbits = 0; config.Nuwbits = 32; config.bad_uw_errors = 12; + config.ftwindow_width = 80; config.timing_mx_thresh = 0.45; + config.tx_uw = zeros(1,config.Nuwbits); + config.tx_uw(1:24) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; + config.tx_uw(end-24+1:end) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; + config.amp_est_mode = 1; config.EsNodB = 3; + config.state_machine = "data"; + config.amp_scale = 2*300E3; config.clip_gain1 = 2; config.clip_gain2 = 1; + config.txbpf_width_Hz = 400; elseif strcmp(mode,"1") Ns=5; config.Np=10; Tcp=0; Tframe = 0.1; Ts = Tframe/Ns; Nc = 1; else @@ -231,9 +242,17 @@ code_param.coded_bits_per_frame = code_param.data_bits_per_frame + code_param.ldpc_parity_bits_per_frame; code_param.coded_syms_per_frame = code_param.coded_bits_per_frame/code_param.bits_per_symbol; end + if strcmp(mode, "datac14") + load HRA_56_56.txt + code_param = ldpc_init_user(HRA_56_56, modulation, mod_order, mapping); + code_param.data_bits_per_frame = 40; + code_param.coded_bits_per_frame = code_param.data_bits_per_frame + code_param.ldpc_parity_bits_per_frame; + code_param.coded_syms_per_frame = code_param.coded_bits_per_frame/code_param.bits_per_symbol; + end if strcmp(mode, "datac0") || strcmp(mode, "datac1") || strcmp(mode, "datac3") ... || strcmp(mode, "datac4") || strcmp(mode, "qam16c1") ... - || strcmp(mode, "qam16c2") || strcmp(mode, "datac5") || strcmp(mode, "datac13") + || strcmp(mode, "qam16c2") || strcmp(mode, "datac5") || strcmp(mode, "datac13") ... + || strcmp(mode, "datac14") printf("ldpc_data_bits_per_frame = %d\n", code_param.ldpc_data_bits_per_frame); printf("ldpc_coded_bits_per_frame = %d\n", code_param.ldpc_coded_bits_per_frame); printf("ldpc_parity_bits_per_frame = %d\n", code_param.ldpc_parity_bits_per_frame); diff --git a/octave/ofdm_rx.m b/octave/ofdm_rx.m index fea4b339..6dbe8f65 100644 --- a/octave/ofdm_rx.m +++ b/octave/ofdm_rx.m @@ -83,7 +83,9 @@ function ofdm_rx(filename, mode="700D", varargin) Nerrs = 0; rx_uw = zeros(1,states.Nuwbits); % main loop ---------------------------------------------------------------- - + + rx = ofdm_rx_filter(states, mode, rx); + f = 1; while(prx < Nsam) diff --git a/octave/qam16.m b/octave/qam16.m index 5b0f047c..7d2dff4d 100644 --- a/octave/qam16.m +++ b/octave/qam16.m @@ -25,7 +25,7 @@ function test_qam16_mod_demod(constellation) for decimal=0:15 tx_bits = zeros(1,4); for i=1:4 - tx_bits(1,5-i) = bitand(bitshift(decimal-1,1-i),1); + tx_bits(1,5-i) = bitand(bitshift(decimal,1-i),1); end symbol = qam16_mod(constellation, tx_bits); rx_bits = qam16_demod(constellation,symbol); diff --git a/octave/snr_curves_plot.m b/octave/snr_curves_plot.m new file mode 100644 index 00000000..8baafe0d --- /dev/null +++ b/octave/snr_curves_plot.m @@ -0,0 +1,267 @@ +% snr_curves_plot.m +% +% Companion script for unittest/raw_data_curves + +1; + +function state_vec = set_graphics_state_print() + textfontsize = get(0,"defaulttextfontsize"); + linewidth = get(0,"defaultlinelinewidth"); + markersize = get(0, "defaultlinemarkersize"); + set(0, "defaulttextfontsize", 16); + set(0, "defaultaxesfontsize", 16); + set(0, "defaultlinelinewidth", 1); + state_vec = [textfontsize linewidth markersize]; +endfunction + +function set_graphics_state_screen(state_vec) + textfontsize = state_vec(1); + linewidth = state_vec(2); + markersize = state_vec(3); + set(0, "defaulttextfontsize", textfontsize); + set(0, "defaultaxesfontsize", textfontsize); + set(0, "defaultlinelinewidth", linewidth); + set(0, "defaultlinemarkersize", markersize); +endfunction + +function [snr_ch per] = snr_scatter(source, mode, channel, colour) + suffix = sprintf("_%s_%s_%s",source, mode, channel); + snr = load(sprintf("snr%s.txt",suffix)); + offset = load(sprintf("offset%s.txt",suffix)); + snr -= offset; + snr_x = []; snrest_y = []; + for i=1:length(snr) + fn = sprintf('snrest%s_%d.txt',suffix,i); + if exist(fn,'file') == 2 + snrest=load(fn); + if i == length(snr) + plot(snr(i)*ones(1,length(snrest)), snrest, sprintf('%s;%s %s;',colour,source,mode)); + else + plot(snr(i)*ones(1,length(snrest)), snrest, sprintf('%s',colour)); + end + snr_x = [snr_x snr(i)]; snrest_y = [snrest_y mean(snrest)]; + end + end + plot(snr_x, snrest_y, sprintf('%s', colour)); +endfunction + +function [snr_ch per] = per_snr(mode, colour) + snrch = load(sprintf("snrch_%s.txt",mode)); + snroffset = load(sprintf("snroffset_%s.txt",mode)); + snrch -= snroffset; + per = load(sprintf("per_%s.txt",mode)); + plot(snrch, per, sprintf('%so-;%s;', colour, mode)); +endfunction + +function snrest_snr_screen(source, channel) + clf; hold on; + snr_scatter(source, 'datac0', channel,'b+-') + snr_scatter(source, 'datac1', channel,'g+-') + snr_scatter(source, 'datac3', channel,'r+-') + snr_scatter(source, 'datac4', channel,'c+-') + snr_scatter(source, 'datac13', channel,'m+-') + xlabel('SNR (dB)'); ylabel('SNRest (dB)'); grid('minor'); + axis([-12 12 -12 12]); + a = axis; + plot([a(1) a(2)],[a(1) a(2)],'bk-'); + hold off; grid; + if strcmp(source,'ctx') + title(sprintf('SNR estimate versus SNR (%s) (no compression)', channel)); + else + title(sprintf('SNR estimate versus SNR (%s) (with compression)', channel)); + end + legend('location','northwest'); +endfunction + +function snrest_snr_print(source, channel) + state_vec = set_graphics_state_print(); + snrest_snr_screen(source, channel); + print(sprintf("snrest_snr_%s.png", source), "-dpng", "-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +function ber_per_v_snr(source, mode, channel, colour) + suffix = sprintf("_%s_%s_%s.txt",source, mode, channel); + snr = load(sprintf("snr%s",suffix)); + offset = load(sprintf("offset%s",suffix)); + snr -= offset; + ber = load(sprintf("ber%s",suffix)) + 1E-6; + per = load(sprintf("per%s",suffix)) + 1E-6; + semilogy(snr, ber, sprintf('%s;%s %s ber;', colour, source, mode)); + semilogy(snr, per, sprintf('%s;%s %s per;', colour, source, mode),'linewidth',3,'markersize',10); +endfunction + +function per_v_snr(source, mode, channel, colour) + suffix = sprintf("_%s_%s_%s.txt",source, mode, channel); + snr = load(sprintf("snr%s",suffix)); + offset = load(sprintf("offset%s",suffix)); + snr -= offset; + per = load(sprintf("per%s",suffix)) + 1E-6; + if strcmp(channel,"awgn") + semilogy(snr, per, sprintf('%s;%s %s;', colour, mode, channel)); + else + semilogy(snr, per, sprintf('%s;%s %s;', colour, mode, channel),'linewidth',3,'markersize',10); + end +endfunction + +function thruput_v_snr(source, mode, channel, colour) + suffix = sprintf("_%s_%s_%s.txt",source, mode, channel); + snr = load(sprintf("snr%s",suffix)); + offset = load(sprintf("offset%s",suffix)); + snr -= offset; + per = load(sprintf("per%s",suffix)) + 1E-6; + if strcmp(mode,"datac0") Rb=291; end; + if strcmp(mode,"datac1") Rb=980; end; + if strcmp(mode,"datac3") Rb=321; end; + if strcmp(mode,"datac4") Rb=87; end; + if strcmp(mode,"datac13") Rb=65; end; + if strcmp(mode,"datac14") Rb=58; end; + if strcmp(channel,"awgn") + plot(snr, Rb*(1-per), sprintf('%s;%s %s;', colour, mode, channel)); + else + plot(snr, Rb*(1-per), sprintf('%s;%s %s;', colour, mode, channel),'linewidth',3,'markersize',10); + end +endfunction + +function octave_ch_noise_screen(channel) + clf; hold on; + ber_per_v_snr('oct','datac0',channel,'bo-') + ber_per_v_snr('ch' ,'datac0',channel,'bx-') + ber_per_v_snr('oct','datac1',channel,'go-') + ber_per_v_snr('ch' ,'datac1',channel,'gx-') + ber_per_v_snr('oct','datac3',channel,'ro-') + ber_per_v_snr('ch' ,'datac3',channel,'rx-') + xlabel('SNR (dB)'); grid; + hold off; + if strcmp(channel,"awgn") + axis([-6 8 1E-3 1]); + else + axis([-2 12 1E-3 1]); + end + title(sprintf('Comparsion of Measuring SNR from Octave and ch tool (%s)', channel)); +endfunction + +function octave_ch_noise_print(channel) + state_vec = set_graphics_state_print(); + octave_ch_noise_screen(channel); + print(sprintf("octave_ch_noise_%s.png", channel), "-dpng","-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +function octave_c_tx_screen(channel) + clf; hold on; + ber_per_v_snr('oct','datac0',channel,'bo-') + ber_per_v_snr('ctx','datac0',channel,'bx-') + ber_per_v_snr('oct','datac1',channel,'go-') + ber_per_v_snr('ctx','datac1',channel,'gx-') + ber_per_v_snr('oct','datac3',channel,'ro-') + ber_per_v_snr('ctx','datac3',channel,'rx-') + xlabel('SNR (dB)'); grid; + hold off; + if strcmp(channel,"awgn") + axis([-6 8 1E-3 1]); + else + axis([-2 12 1E-3 1]); + end + title(sprintf('Comparsion of Octave Tx and C Tx (no compression) (%s)', channel)); +endfunction + +function octave_c_tx_print(channel) + state_vec = set_graphics_state_print(); + octave_c_tx_screen(channel); + print(sprintf("octave_c_tx_%s.png", channel), "-dpng","-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +function octave_c_tx_comp_screen(channel) + clf; hold on; + ber_per_v_snr('oct','datac0',channel,'bo-') + ber_per_v_snr('ctxc','datac0',channel,'bx-') + ber_per_v_snr('oct','datac1',channel,'go-') + ber_per_v_snr('ctxc','datac1',channel,'gx-') + ber_per_v_snr('oct','datac3',channel,'ro-') + ber_per_v_snr('ctxc','datac3',channel,'rx-') + xlabel('SNR (dB)'); grid; + hold off; + if strcmp(channel,"awgn") + axis([-6 8 1E-3 1]); + else + axis([-2 12 1E-3 1]); + end + title(sprintf('Comparsion of Octave Tx and C Tx (with compression) (%s)', channel)); +endfunction + +function octave_c_tx_comp_print(channel) + state_vec = set_graphics_state_print(); + octave_c_tx_comp_screen(channel); + print(sprintf("octave_c_tx_comp_%s.png", channel), "-dpng","-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +% composite AWGN and MPP for compressed +function c_tx_comp_screen + clf; hold on; + per_v_snr('ctxc','datac0','awgn','bo-') + per_v_snr('ctxc','datac1','awgn','go-') + per_v_snr('ctxc','datac3','awgn','ro-') + per_v_snr('ctxc','datac4','awgn','co-') + per_v_snr('ctxc','datac13','awgn','mo-') + per_v_snr('ctxc','datac14','awgn','ko-') + per_v_snr('ctxc','datac0','mpp','bx-') + per_v_snr('ctxc','datac1','mpp','gx-') + per_v_snr('ctxc','datac3','mpp','rx-') + per_v_snr('ctxc','datac4','mpp','cx-') + per_v_snr('ctxc','datac13','mpp','mx-') + per_v_snr('ctxc','datac14','mpp','kx-') + xlabel('SNR (dB)'); ylabel('PER'); grid; + hold off; + axis([-10 14 1E-3 1]); + title('PER of C Raw Data Modes (with compression)'); +endfunction + +function c_tx_comp_print; + state_vec = set_graphics_state_print(); + c_tx_comp_screen; + print("c_tx_comp.png", "-dpng","-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +function c_tx_comp_thruput_screen + clf; hold on; + thruput_v_snr('ctxc','datac0','awgn','bo-') + thruput_v_snr('ctxc','datac1','awgn','go-') + thruput_v_snr('ctxc','datac3','awgn','ro-') + thruput_v_snr('ctxc','datac4','awgn','co-') + thruput_v_snr('ctxc','datac13','awgn','mo-') + thruput_v_snr('ctxc','datac14','awgn','ko-') + thruput_v_snr('ctxc','datac0','mpp','bx-') + thruput_v_snr('ctxc','datac1','mpp','gx-') + thruput_v_snr('ctxc','datac3','mpp','rx-') + thruput_v_snr('ctxc','datac4','mpp','cx-') + thruput_v_snr('ctxc','datac13','mpp','mx-') + thruput_v_snr('ctxc','datac14','mpp','kx-') + xlabel('SNR (dB)'); ylabel('bits/s'); grid; + hold off; + axis([-10 10 0 1000]); + title(' Throughput for C Tx (with compression)'); + legend('location','west'); +endfunction + +function c_tx_comp_thruput_print; + state_vec = set_graphics_state_print; + c_tx_comp_thruput_screen; + print("c_tx_comp_thruput.png", "-dpng","-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +#{ +figure(1); octave_ch_noise_screen; +figure(2); octave_c_tx_screen; +figure(3); octave_c_tx_comp_screen +figure(4); snrest_snr_screen; + +figure(5); octave_ch_noise_print; +figure(6); octave_c_tx_print; +figure(7); octave_c_tx_comp_print; +figure(8); snrest_snr_print; +#} diff --git a/src/ch.c b/src/ch.c index 9d8d8957..45e1b496 100644 --- a/src/ch.c +++ b/src/ch.c @@ -256,7 +256,7 @@ int main(int argc, char *argv[]) { stderr, "\nAdjust path --fading_dir or use GNU Octave to generate:\n\n"); gen_fading_file: - fprintf(stderr, "$ octave --no-gui\n"); + fprintf(stderr, "$ octave-cli\n"); fprintf(stderr, "octave:24> pkg load signal\n"); fprintf(stderr, "octave:24> time_secs=60\n"); fprintf(stderr, diff --git a/src/freedv_2020.c b/src/freedv_2020.c index f1dee284..a1a3d318 100644 --- a/src/freedv_2020.c +++ b/src/freedv_2020.c @@ -229,8 +229,7 @@ int freedv_comprx_2020(struct freedv *f, COMP demod_in[]) { f->sync = 0; - // TODO: should be higher for 2020? - float EsNo = 3.0; + float EsNo = pow(10.0, ofdm->EsNodB / 10); /* looking for modem sync */ @@ -249,7 +248,7 @@ int freedv_comprx_2020(struct freedv *f, COMP demod_in[]) { ofdm_demod(ofdm, rx_bits, demod_in); ofdm_extract_uw(ofdm, ofdm->rx_np, ofdm->rx_amp, rx_uw); - ofdm_disassemble_qpsk_modem_packet_with_text_amps( + ofdm_disassemble_psk_modem_packet_with_text_amps( ofdm, ofdm->rx_np, ofdm->rx_amp, payload_syms, payload_amps, txt_bits, &txt_sym_index); @@ -280,14 +279,14 @@ int freedv_comprx_2020(struct freedv *f, COMP demod_in[]) { uint8_t out_char[coded_bits_per_frame]; if (f->test_frames) { - Nerrs_raw = - count_uncoded_errors(ldpc, &f->ofdm->config, codeword_symbols_de, 0); + Nerrs_raw = count_uncoded_errors( + ldpc, &f->ofdm->config, codeword_symbols_de, codeword_amps_de, 0); f->total_bit_errors += Nerrs_raw; f->total_bits += f->ofdm_bitsperframe; } symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, EsNo, - ofdm->mean_amp, coded_syms_per_frame); + ofdm->mean_amp, ofdm->bps, coded_syms_per_frame); ldpc_decode_frame(ldpc, &parityCheckCount, &iter, out_char, llr); if (parityCheckCount != ldpc->NumberParityBits) rx_status |= FREEDV_RX_BIT_ERRORS; diff --git a/src/freedv_700.c b/src/freedv_700.c index 3dc4329f..72e05632 100644 --- a/src/freedv_700.c +++ b/src/freedv_700.c @@ -189,7 +189,7 @@ void freedv_ofdm_voice_open(struct freedv *f, char *mode) { // open function for OFDM data modes, TODO consider moving to a new // (freedv_ofdm_data.c) file -void freedv_ofdm_data_open(struct freedv *f) { +void freedv_ofdm_data_open(struct freedv *f, struct freedv_advanced *adv) { struct OFDM_CONFIG ofdm_config; char mode[32]; if (f->mode == FREEDV_MODE_DATAC0) strcpy(mode, "datac0"); @@ -197,8 +197,16 @@ void freedv_ofdm_data_open(struct freedv *f) { if (f->mode == FREEDV_MODE_DATAC3) strcpy(mode, "datac3"); if (f->mode == FREEDV_MODE_DATAC4) strcpy(mode, "datac4"); if (f->mode == FREEDV_MODE_DATAC13) strcpy(mode, "datac13"); - - ofdm_init_mode(mode, &ofdm_config); + if (f->mode == FREEDV_MODE_DATAC14) strcpy(mode, "datac14"); + if (f->mode == FREEDV_MODE_QAM16C2) strcpy(mode, "qam16c2"); + if (f->mode == FREEDV_MODE_DATA_CUSTOM) { + assert(adv != NULL); + assert(adv->config != NULL); + memcpy(&ofdm_config, (struct OFDM_CONFIG *)adv->config, + sizeof(struct OFDM_CONFIG)); + } else { + ofdm_init_mode(mode, &ofdm_config); + } f->ofdm = ofdm_create(&ofdm_config); assert(f->ofdm != NULL); @@ -218,8 +226,8 @@ void freedv_ofdm_data_open(struct freedv *f) { f->ofdm_ntxtbits = ofdm_config.txtbits; /* payload bits per FreeDV API "frame". In OFDM modem nomenclature this is - the number of payload data bits per packet, or the number of data bits in a - LDPC codeword */ + the number of payload data bits per packet, or the number of data bits in + a LDPC codeword */ f->bits_per_modem_frame = f->ldpc->data_bits_per_frame; // buffers for received symbols for one packet/LDPC codeword - may span many @@ -298,8 +306,8 @@ int freedv_comprx_700c(struct freedv *f, COMP demod_in_8kHz[]) { int rx_status = 0; - // quisk_cfInterpDecim() modifies input data so lets make a copy just in case - // there is no sync and we need to echo input to output + // quisk_cfInterpDecim() modifies input data so lets make a copy just in + // case there is no sync and we need to echo input to output // freedv_nin(f): input samples at Fs=8000 Hz // f->nin: input samples at Fs=7500 Hz @@ -331,7 +339,8 @@ int freedv_comprx_700c(struct freedv *f, COMP demod_in_8kHz[]) { rx_status |= FREEDV_RX_BITS; } else { if (f->test_frames_diversity) { - /* normal operation - error pattern on frame after diveristy combination + /* normal operation - error pattern on frame after diveristy + * combination */ short error_pattern[COHPSK_BITS_PER_FRAME]; int bit_errors; @@ -439,7 +448,7 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, assert((demod_in_is_short == 0) || (demod_in_is_short == 1)); int rx_status = 0; - float EsNo = 3.0; /* further work: estimate this properly from signal */ + float EsNo = pow(10.0, ofdm->EsNodB / 10); f->sync = 0; /* looking for OFDM modem sync */ @@ -470,8 +479,8 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, memcpy(&rx_amps[Nsymsperpacket - Nsymsperframe], ofdm->rx_amp, sizeof(float) * Nsymsperframe); - /* look for UW as frames enter packet buffer, note UW may span several modem - * frames */ + /* look for UW as frames enter packet buffer, note UW may span several + * modem frames */ int st_uw = Nsymsperpacket - ofdm->nuwframes * Nsymsperframe; ofdm_extract_uw(ofdm, &rx_syms[st_uw], &rx_amps[st_uw], rx_uw); @@ -482,7 +491,7 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, /* we have received enough modem frames to complete packet and run LDPC * decoder */ int txt_sym_index = 0; - ofdm_disassemble_qpsk_modem_packet_with_text_amps( + ofdm_disassemble_psk_modem_packet_with_text_amps( ofdm, rx_syms, rx_amps, payload_syms, payload_amps, txt_bits, &txt_sym_index); @@ -496,10 +505,8 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, float llr[Npayloadbitsperpacket]; uint8_t decoded_codeword[Npayloadbitsperpacket]; symbols_to_llrs(llr, payload_syms_de, payload_amps_de, EsNo, - ofdm->mean_amp, Npayloadsymsperpacket); + ofdm->mean_amp, ofdm->bps, Npayloadsymsperpacket); ldpc_decode_frame(ldpc, &parityCheckCount, &iter, decoded_codeword, llr); - // iter = run_ldpc_decoder(ldpc, decoded_codeword, llr, - // &parityCheckCount); memcpy(f->rx_payload_bits, decoded_codeword, Ndatabitsperpacket); if (strlen(ofdm->data_mode)) { @@ -509,8 +516,8 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, else rx_status |= FREEDV_RX_BIT_ERRORS; } else { - // voice modes aren't as strict - pass everything through to the speech - // decoder, but flag frame with possible errors + // voice modes aren't as strict - pass everything through to the + // speech decoder, but flag frame with possible errors rx_status |= FREEDV_RX_BITS; if (parityCheckCount != ldpc->NumberParityBits) rx_status |= FREEDV_RX_BIT_ERRORS; @@ -518,8 +525,9 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, if (f->test_frames) { /* est uncoded BER from payload bits */ - Nerrs_raw = count_uncoded_errors( - ldpc, &f->ofdm->config, payload_syms_de, strlen(ofdm->data_mode)); + Nerrs_raw = + count_uncoded_errors(ldpc, &f->ofdm->config, payload_syms_de, + payload_amps_de, strlen(ofdm->data_mode)); f->total_bit_errors += Nerrs_raw; f->total_bits += Npayloadbitsperpacket; diff --git a/src/freedv_api.c b/src/freedv_api.c index 5172959d..cb9e7064 100644 --- a/src/freedv_api.c +++ b/src/freedv_api.c @@ -60,7 +60,7 @@ /* The API version number. The first version is 10. Increment if the API changes in a way that would require changes by the API user. */ -#define VERSION 15 +#define VERSION 16 /* Version 10 Initial version August 2, 2015. @@ -79,6 +79,9 @@ Version 15 December 2022 Removing rarely used DPSK support which is not needed given fast fading modes + + Version 16 April 2024, added field to struct freedv_advanced to support + FREEDV_MODE_DATA_CUSTOM */ char *ofdm_statemode[] = {"search", "trial", "synced"}; @@ -101,7 +104,8 @@ char *rx_sync_flags_to_text[] = {"----", "---T", "--S-", "--ST", "-B--", "-B-T", struct freedv *freedv_open(int mode) { // defaults for those modes that support the use of adv - struct freedv_advanced adv = {0, 2, 100, 8000, 1000, 200, "H_256_512_4"}; + struct freedv_advanced adv = {0, 2, 100, 8000, + 1000, 200, "H_256_512_4", NULL}; return freedv_open_advanced(mode, &adv); } @@ -125,7 +129,10 @@ struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode)) == false) + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, mode)) == false) return NULL; /* set everything to zero just in case */ @@ -149,11 +156,18 @@ struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) { if (FDV_MODE_ACTIVE(FREEDV_MODE_2400B, mode)) freedv_2400b_open(f); if (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, mode)) freedv_800xa_open(f); if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, mode)) freedv_fsk_ldpc_open(f, adv); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, mode)) freedv_ofdm_data_open(f); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, mode)) freedv_ofdm_data_open(f); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, mode)) freedv_ofdm_data_open(f); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode)) freedv_ofdm_data_open(f); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode)) freedv_ofdm_data_open(f); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, mode)) freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, mode)) freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, mode)) freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode)) freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode)) + freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, mode)) + freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, mode)) + freedv_ofdm_data_open(f, adv); + if (FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, mode)) + freedv_ofdm_data_open(f, NULL); varicode_decode_init(&f->varicode_dec_states, 1); @@ -235,7 +249,10 @@ void freedv_close(struct freedv *freedv) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, freedv->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, freedv->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, freedv->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, freedv->mode)) { + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, freedv->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, freedv->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, freedv->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, freedv->mode)) { FREE(freedv->rx_syms); FREE(freedv->rx_amps); FREE(freedv->ldpc); @@ -266,7 +283,10 @@ static int is_ofdm_mode(struct freedv *f) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode); + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode); } static int is_ofdm_data_mode(struct freedv *f) { @@ -274,7 +294,10 @@ static int is_ofdm_data_mode(struct freedv *f) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode); + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode); } /*---------------------------------------------------------------------------*\ @@ -464,7 +487,10 @@ void freedv_rawdatacomptx(struct freedv *f, COMP mod_out[], FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode)) + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode)) freedv_comptx_ofdm(f, mod_out); if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, f->mode)) { @@ -1063,7 +1089,10 @@ int freedv_rawdatacomprx(struct freedv *f, unsigned char *packed_payload_bits, FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode)) + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode)) rx_status = freedv_comp_short_rx_ofdm(f, (void *)demod_in, 0, 1.0f); if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, f->mode)) { rx_status = freedv_rx_fsk_ldpc_data(f, demod_in); diff --git a/src/freedv_api.h b/src/freedv_api.h index ce95a559..4acf40ca 100644 --- a/src/freedv_api.h +++ b/src/freedv_api.h @@ -61,6 +61,9 @@ extern "C" { #define FREEDV_MODE_DATAC0 14 #define FREEDV_MODE_DATAC4 18 #define FREEDV_MODE_DATAC13 19 +#define FREEDV_MODE_DATAC14 20 +#define FREEDV_MODE_DATA_CUSTOM 21 +#define FREEDV_MODE_QAM16C2 22 // Sample rates used #define FREEDV_FS_8000 8000 @@ -140,6 +143,15 @@ extern "C" { #if !defined(FREEDV_MODE_DATAC13_EN) #define FREEDV_MODE_DATAC13_EN FREEDV_MODE_EN_DEFAULT #endif +#if !defined(FREEDV_MODE_DATAC14_EN) +#define FREEDV_MODE_DATAC14_EN FREEDV_MODE_EN_DEFAULT +#endif +#if !defined(FREEDV_MODE_DATA_CUSTOM_EN) +#define FREEDV_MODE_DATA_CUSTOM_EN FREEDV_MODE_EN_DEFAULT +#endif +#if !defined(FREEDV_MODE_QAM16C2_EN) +#define FREEDV_MODE_QAM16C2_EN FREEDV_MODE_EN_DEFAULT +#endif #define FDV_MODE_ACTIVE(mode_name, var) \ ((mode_name##_EN) == 0 ? 0 : (var) == mode_name) @@ -159,6 +171,9 @@ struct freedv_advanced { int first_tone; // Freq of first tone Hz int tone_spacing; // Spacing between tones Hz char *codename; // LDPC codename, from codes listed in ldpc_codes.c + + // parameters for FREEDV_MODE_DATA_CUSTOM + void *config; // ptr to struct OFDM_CONFIG }; // Called when text message char is decoded diff --git a/src/freedv_api_internal.h b/src/freedv_api_internal.h index ef6426f6..9559a2d9 100644 --- a/src/freedv_api_internal.h +++ b/src/freedv_api_internal.h @@ -221,7 +221,7 @@ void freedv_2400a_open(struct freedv *f); void freedv_2400b_open(struct freedv *f); void freedv_800xa_open(struct freedv *f); void freedv_fsk_ldpc_open(struct freedv *f, struct freedv_advanced *adv); -void freedv_ofdm_data_open(struct freedv *f); +void freedv_ofdm_data_open(struct freedv *f, struct freedv_advanced *adv); // each mode has tx and rx functions in various flavours for real and complex // valued samples diff --git a/src/freedv_data_raw_rx.c b/src/freedv_data_raw_rx.c index 1957e103..50fcb32f 100644 --- a/src/freedv_data_raw_rx.c +++ b/src/freedv_data_raw_rx.c @@ -39,6 +39,7 @@ #include "ldpc_codes.h" #include "modem_stats.h" #include "octave.h" +#include "ofdm_internal.h" /* other processes can end this program using signals */ @@ -214,6 +215,12 @@ int main(int argc, char *argv[]) { mode = FREEDV_MODE_DATAC4; if (!strcmp(argv[dx], "DATAC13") || !strcmp(argv[dx], "datac13")) mode = FREEDV_MODE_DATAC13; + if (!strcmp(argv[dx], "DATAC14") || !strcmp(argv[dx], "datac14")) + mode = FREEDV_MODE_DATAC14; + if (!strcmp(argv[dx], "QAM16C2") || !strcmp(argv[dx], "qam16c2")) + mode = FREEDV_MODE_QAM16C2; + if (!strcmp(argv[dx], "CUSTOM") || !strcmp(argv[dx], "custom")) + mode = FREEDV_MODE_DATA_CUSTOM; if (mode == -1) { fprintf(stderr, "Error in mode: %s\n", argv[dx]); exit(1); @@ -246,6 +253,23 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Setting estimator limits to %d to %d Hz.\n", fsk_lower, fsk_upper); fsk_set_freq_est_limits(fsk, fsk_lower, fsk_upper); + } else if (mode == FREEDV_MODE_DATA_CUSTOM) { + // demonstrate custom OFDM raw data modes + struct OFDM_CONFIG ofdm_config; + ofdm_init_mode("datac14", &ofdm_config); + // modify datac14 to have 3 carriers instead of 4, which means + // we have to tweak Np, and the number of unique word bits + ofdm_config.nc = 3; + ofdm_config.np = 6; + ofdm_config.nuwbits = 48; + ofdm_config.bad_uw_errors = 18; + uint8_t uw[] = {1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0}; + memcpy(ofdm_config.tx_uw, uw, sizeof(uw)); + memcpy(&ofdm_config.tx_uw[ofdm_config.nuwbits - sizeof(uw)], uw, + sizeof(uw)); + adv.config = (void *)&ofdm_config; + freedv = freedv_open_advanced(mode, &adv); } else { freedv = freedv_open(mode); } @@ -262,8 +286,8 @@ int main(int argc, char *argv[]) { fsk->Ndft); } - /* for streaming bytes it's much easier use the modes that have a multiple of - * 8 payload bits/frame */ + /* for streaming bytes it's much easier use the modes that have a multiple + * of 8 payload bits/frame */ assert((freedv_get_bits_per_modem_frame(freedv) % 8) == 0); int bytes_per_modem_frame = freedv_get_bits_per_modem_frame(freedv) / 8; // last two bytes used for CRC diff --git a/src/freedv_data_raw_tx.c b/src/freedv_data_raw_tx.c index 44d53e9f..70fefe74 100644 --- a/src/freedv_data_raw_tx.c +++ b/src/freedv_data_raw_tx.c @@ -236,6 +236,12 @@ int main(int argc, char *argv[]) { mode = FREEDV_MODE_DATAC4; if (!strcmp(argv[dx], "DATAC13") || !strcmp(argv[dx], "datac13")) mode = FREEDV_MODE_DATAC13; + if (!strcmp(argv[dx], "DATAC14") || !strcmp(argv[dx], "datac14")) + mode = FREEDV_MODE_DATAC14; + if (!strcmp(argv[dx], "QAM16C2") || !strcmp(argv[dx], "qam16c2")) + mode = FREEDV_MODE_QAM16C2; + if (!strcmp(argv[dx], "CUSTOM") || !strcmp(argv[dx], "custom")) + mode = FREEDV_MODE_DATA_CUSTOM; if (mode == -1) { fprintf(stderr, "Error: in mode: %s", argv[dx]); exit(1); @@ -257,10 +263,28 @@ int main(int argc, char *argv[]) { exit(1); } - if (mode != FREEDV_MODE_FSK_LDPC) - freedv = freedv_open(mode); - else + if (mode == FREEDV_MODE_FSK_LDPC) + freedv = freedv_open_advanced(mode, &adv); + else if (mode == FREEDV_MODE_DATA_CUSTOM) { + // demonstrate custom OFDM raw data modes + struct OFDM_CONFIG ofdm_config; + ofdm_init_mode("datac14", &ofdm_config); + // modify datac14 to have 3 carriers instead of 4, which means + // we have to tweak Np, and the number of unique word bits + ofdm_config.nc = 3; + ofdm_config.np = 6; + ofdm_config.nuwbits = 48; + ofdm_config.bad_uw_errors = 18; + uint8_t uw[] = {1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0}; + memcpy(ofdm_config.tx_uw, uw, sizeof(uw)); + memcpy(&ofdm_config.tx_uw[ofdm_config.nuwbits - sizeof(uw)], uw, + sizeof(uw)); + adv.config = (void *)&ofdm_config; freedv = freedv_open_advanced(mode, &adv); + } else { + freedv = freedv_open(mode); + } assert(freedv != NULL); diff --git a/src/gp_interleaver.c b/src/gp_interleaver.c index 61678f8c..70b8ad07 100644 --- a/src/gp_interleaver.c +++ b/src/gp_interleaver.c @@ -31,44 +31,32 @@ #include "gp_interleaver.h" #include +#include #include /* Choose b for Golden Prime Interleaver. b is chosen to be the closest integer, which is relatively prime to N, to the Golden section of N. - - Implemented with a LUT in C for convenience, Octave version - has a more complete implementation. If you find you need some more - numbers head back to the Octave choose_interleaver_b() function. */ -static const int b_table[] = { - 56, 37, /* 700E: HRA_56_56 */ - 106, 67, /* 2020B: (112,56) partial protection */ - 112, 71, /* 700D: HRA_112_112 */ - 128, 83, /* datac0: H_128_256_5 */ - 192, 127, /* datac13: H_256_512_4, 128 data bits used */ - 210, 131, /* 2020: HRAb_396_504 with 312 data bits used */ - 736, 457, /* datac4: H_1024_2048_4f, 448 data bits used */ - 1024, 641, /* datac3: H_1024_2048_4f */ - 1290, 797, /* datac2: H2064_516_sparse */ - 4096, 2531 /* datac1: H_4096_8192_3d */ -}; - -int choose_interleaver_b(int Nbits) { - int i; - for (i = 0; i < sizeof(b_table) / sizeof(int); i += 2) { - if (b_table[i] == Nbits) { - return b_table[i + 1]; - } +int is_prime(int x) { + for (int i = 2; i < x; i++) { + if ((x % i) == 0) return 0; } + return 1; +} - /* if we get to here it means a Nbits we don't have in our table so choke */ +int next_prime(int x) { + x++; + while (is_prime(x) == 0) x++; + return x; +} - fprintf(stderr, "gp_interleaver: Nbits: %d, b not found!\n", Nbits); - assert(0); - return -1; +int choose_interleaver_b(int Nbits) { + int b = floor(Nbits / 1.62); + b = next_prime(b); + return b; } void gp_interleave_comp(COMP interleaved_frame[], COMP frame[], int Nbits) { diff --git a/src/interldpc.c b/src/interldpc.c index 24f4b2ab..cb6f964a 100644 --- a/src/interldpc.c +++ b/src/interldpc.c @@ -78,6 +78,7 @@ void ldpc_mode_specific_setup(struct OFDM *ofdm, struct LDPC *ldpc) { } if (!strcmp(ofdm->mode, "datac4")) set_data_bits_per_frame(ldpc, 448); if (!strcmp(ofdm->mode, "datac13")) set_data_bits_per_frame(ldpc, 128); + if (!strcmp(ofdm->mode, "datac14")) set_data_bits_per_frame(ldpc, 40); } /* LDPC encode frame - generate parity bits and a codeword, applying the @@ -133,17 +134,18 @@ void ldpc_encode_frame(struct LDPC *ldpc, int codeword[], for (j = 0; j < ldpc->NumberParityBits; i++, j++) codeword[i] = pbits[j]; } -void qpsk_modulate_frame(COMP tx_symbols[], int codeword[], int n) { +void psk_modulate_frame(int bps, COMP tx_symbols[], int codeword[], int n) { int s, i; - int dibit[2]; - complex float qpsk_symb; - - for (s = 0, i = 0; i < n; s += 2, i++) { - dibit[0] = codeword[s + 1] & 0x1; - dibit[1] = codeword[s] & 0x1; - qpsk_symb = qpsk_mod(dibit); - tx_symbols[i].real = crealf(qpsk_symb); - tx_symbols[i].imag = cimagf(qpsk_symb); + int bits[bps]; + complex float symb; + + assert((bps == 2) || (bps == 4)); + for (s = 0, i = 0; i < n; s += bps, i++) { + for (int b = 0; b < bps; b++) bits[b] = codeword[s + bps - 1 - b] & 0x1; + if (bps == 2) symb = qpsk_mod(bits); + if (bps == 4) symb = qam16_mod(bits); + tx_symbols[i].real = crealf(symb); + tx_symbols[i].imag = cimagf(symb); } } @@ -219,7 +221,8 @@ void ldpc_decode_frame(struct LDPC *ldpc, int *parityCheckCount, int *iter, of txt bits as this is done after we dissassemmble the frame */ int count_uncoded_errors(struct LDPC *ldpc, struct OFDM_CONFIG *config, - COMP codeword_symbols_de[], int crc16) { + COMP codeword_symbols_de[], float codeword_amps_de[], + int crc16) { int i, Nerrs; int coded_syms_per_frame = ldpc->coded_bits_per_frame / config->bps; @@ -247,12 +250,13 @@ int count_uncoded_errors(struct LDPC *ldpc, struct OFDM_CONFIG *config, ldpc_encode_frame(ldpc, test_codeword, tx_bits); for (i = 0; i < coded_syms_per_frame; i++) { - int bits[2]; + int bits[config->bps]; complex float s = codeword_symbols_de[i].real + I * codeword_symbols_de[i].imag; - qpsk_demod(s, bits); - rx_bits_raw[config->bps * i] = bits[1]; - rx_bits_raw[config->bps * i + 1] = bits[0]; + if (config->bps == 2) qpsk_demod(s, bits); + if (config->bps == 4) qam16_demod(s, bits, codeword_amps_de[i]); + for (int b = 0; b < config->bps; b++) + rx_bits_raw[config->bps * i + b] = bits[config->bps - 1 - b]; } Nerrs = 0; @@ -328,10 +332,11 @@ void ofdm_ldpc_interleave_tx(struct OFDM *ofdm, struct LDPC *ldpc, complex float tx_symbols[Nbitsperpacket / ofdm->bps]; ldpc_encode_frame(ldpc, codeword, tx_bits); - qpsk_modulate_frame(payload_symbols, codeword, Npayloadsymsperpacket); + psk_modulate_frame(ofdm->bps, payload_symbols, codeword, + Npayloadsymsperpacket); gp_interleave_comp(payload_symbols_inter, payload_symbols, Npayloadsymsperpacket); - ofdm_assemble_qpsk_modem_packet_symbols(ofdm, tx_symbols, - payload_symbols_inter, txt_bits); + ofdm_assemble_psk_modem_packet_symbols(ofdm, tx_symbols, + payload_symbols_inter, txt_bits); ofdm_txframe(ofdm, tx_sams, tx_symbols); } diff --git a/src/interldpc.h b/src/interldpc.h index 3b1d1caf..1c72558f 100644 --- a/src/interldpc.h +++ b/src/interldpc.h @@ -49,7 +49,8 @@ void qpsk_modulate_frame(COMP tx_symbols[], int codeword[], int n); void ldpc_decode_frame(struct LDPC *ldpc, int *parityCheckCount, int *iter, uint8_t out_char[], float llr[]); int count_uncoded_errors(struct LDPC *ldpc, struct OFDM_CONFIG *config, - COMP codeword_symbols_de[], int crc16); + COMP codeword_symbols_de[], float codeword_amps_de[], + int crc16); int count_errors(uint8_t tx_bits[], uint8_t rx_bits[], int n); void count_errors_protection_mode(int protection_mode, int *pNerrs, int *pNcoded, uint8_t tx_bits[], diff --git a/src/mpdecode_core.c b/src/mpdecode_core.c index 53742551..9cf44f72 100644 --- a/src/mpdecode_core.c +++ b/src/mpdecode_core.c @@ -27,11 +27,21 @@ #define QPSK_CONSTELLATION_SIZE 4 #define QPSK_BITS_PER_SYMBOL 2 -/* QPSK constellation for symbol likelihood calculations */ +/* Constellations for symbol likelihood calculations */ -static COMP S_matrix[] = { +static COMP S_matrix_qpsk[] = { {1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, -1.0f}, {-1.0f, 0.0f}}; +static COMP S_matrix_qam16[] = { + {4.4721e-01, 2.7756e-17}, {8.9443e-01, 4.4721e-01}, + {8.9443e-01, -4.4721e-01}, {1.3416e+00, 1.1102e-16}, + {2.7756e-17, -4.4721e-01}, {-4.4721e-01, -8.9443e-01}, + {4.4721e-01, -8.9443e-01}, {1.1102e-16, -1.3416e+00}, + {-2.7756e-17, 4.4721e-01}, {4.4721e-01, 8.9443e-01}, + {-4.4721e-01, 8.9443e-01}, {-1.1102e-16, 1.3416e+00}, + {-4.4721e-01, -2.7756e-17}, {-8.9443e-01, -4.4721e-01}, + {-8.9443e-01, 4.4721e-01}, {-1.3416e+00, -1.1102e-16}}; + // c_nodes will be an array of NumberParityBits of struct c_node // Each c_node contains an array of c_sub_node elements // This structure reduces the indexing calclations in SumProduct() @@ -565,17 +575,14 @@ void sd_to_llr(float llr[], float sd[], int n) { */ void Demod2D(float symbol_likelihood[], /* output, M*number_symbols */ - COMP r[], /* received QPSK symbols, number_symbols */ - COMP S_matrix[], /* constellation of size M */ - float EsNo, - float fading[], /* real fading values, number_symbols */ + COMP r[], /* received PSK symbols, number_symbols */ + COMP S_matrix[], /* constellation of size M */ + int M, float EsNo, + float fading[], /* real fading values, number_symbols */ float mean_amp, int number_symbols) { - int M = QPSK_CONSTELLATION_SIZE; int i, j; float tempsr, tempsi, Er, Ei; - /* determine output */ - for (i = 0; i < number_symbols; i++) { /* go through each received symbol */ for (j = 0; j < M; j++) { /* each postulated symbol */ tempsr = fading[i] * S_matrix[j].real / mean_amp; @@ -583,10 +590,7 @@ void Demod2D(float symbol_likelihood[], /* output, M*number_symbols */ Er = r[i].real / mean_amp - tempsr; Ei = r[i].imag / mean_amp - tempsi; symbol_likelihood[i * M + j] = -EsNo * (Er * Er + Ei * Ei); - // printf("symbol_likelihood[%d][%d] = %f\n", - // i,j,symbol_likelihood[i*M+j]); } - // exit(0); } } @@ -633,18 +637,22 @@ void Somap(float bit_likelihood[], /* number_bits, bps*number_symbols */ } } -void symbols_to_llrs(float llr[], COMP rx_qpsk_symbols[], float rx_amps[], - float EsNo, float mean_amp, int nsyms) { +void symbols_to_llrs(float llr[], COMP rx_psk_symbols[], float rx_amps[], + float EsNo, float mean_amp, int bps, int nsyms) { int i; - - float symbol_likelihood[nsyms * QPSK_CONSTELLATION_SIZE]; - float bit_likelihood[nsyms * QPSK_BITS_PER_SYMBOL]; - - Demod2D(symbol_likelihood, rx_qpsk_symbols, S_matrix, EsNo, rx_amps, mean_amp, - nsyms); - Somap(bit_likelihood, symbol_likelihood, QPSK_CONSTELLATION_SIZE, - QPSK_BITS_PER_SYMBOL, nsyms); - for (i = 0; i < nsyms * QPSK_BITS_PER_SYMBOL; i++) { + int constellation_points = 1 << bps; + float symbol_likelihood[nsyms * constellation_points]; + float bit_likelihood[nsyms * bps]; + + COMP *S_matrix; + assert((bps == 2) || (bps == 4)); + if (bps == 2) S_matrix = S_matrix_qpsk; + if (bps == 4) S_matrix = S_matrix_qam16; + + Demod2D(symbol_likelihood, rx_psk_symbols, S_matrix, constellation_points, + EsNo, rx_amps, mean_amp, nsyms); + Somap(bit_likelihood, symbol_likelihood, constellation_points, bps, nsyms); + for (i = 0; i < nsyms * bps; i++) { llr[i] = -bit_likelihood[i]; } } diff --git a/src/mpdecode_core.h b/src/mpdecode_core.h index 95ead460..d36a1f00 100644 --- a/src/mpdecode_core.h +++ b/src/mpdecode_core.h @@ -47,12 +47,12 @@ int run_ldpc_decoder(struct LDPC *ldpc, uint8_t out_char[], float input[], int *parityCheckCount); void sd_to_llr(float llr[], float sd[], int n); -void Demod2D(float symbol_likelihood[], COMP r[], COMP S_matrix[], float EsNo, - float fading[], float mean_amp, int number_symbols); +void Demod2D(float symbol_likelihood[], COMP r[], COMP S_matrix[], int M, + float EsNo, float fading[], float mean_amp, int number_symbols); void Somap(float bit_likelihood[], float symbol_likelihood[], int M, int bps, int number_symbols); void symbols_to_llrs(float llr[], COMP rx_qpsk_symbols[], float rx_amps[], - float EsNo, float mean_amp, int nsyms); + float EsNo, float mean_amp, int bps, int nsyms); void fsk_rx_filt_to_llrs(float llr[], float rx_filt[], float v_est, float SNRest, int M, int nsyms); diff --git a/src/ofdm.c b/src/ofdm.c index 28d6dba4..bb9a10f2 100644 --- a/src/ofdm.c +++ b/src/ofdm.c @@ -77,10 +77,14 @@ static const complex float qpsk[] = {1.0f + 0.0f * I, 0.0f + 1.0f * I, 0.0f - 1.0f * I, -1.0f + 0.0f * I}; static const complex float qam16[] = { - 1.0f + 1.0f * I, 1.0f + 3.0f * I, 3.0f + 1.0f * I, 3.0f + 3.0f * I, - 1.0f - 1.0f * I, 1.0f - 3.0f * I, 3.0f - 1.0f * I, 3.0f - 3.0f * I, - -1.0f + 1.0f * I, -1.0f + 3.0f * I, -3.0f + 1.0f * I, -3.0f + 3.0f * I, - -1.0f - 1.0f * I, -1.0f - 3.0f * I, -3.0f - 1.0f * I, -3.0f - 3.0f * I}; + 4.4721e-01 + 2.7756e-17 * I, 8.9443e-01 + 4.4721e-01 * I, + 8.9443e-01 - 4.4721e-01 * I, 1.3416e+00 + 1.1102e-16 * I, + 2.7756e-17 - 4.4721e-01 * I, -4.4721e-01 - 8.9443e-01 * I, + 4.4721e-01 - 8.9443e-01 * I, 1.1102e-16 - 1.3416e+00 * I, + -2.7756e-17 + 4.4721e-01 * I, 4.4721e-01 + 8.9443e-01 * I, + -4.4721e-01 + 8.9443e-01 * I, -1.1102e-16 + 1.3416e+00 * I, + -4.4721e-01 - 2.7756e-17 * I, -8.9443e-01 - 4.4721e-01 * I, + -8.9443e-01 + 4.4721e-01 * I, -1.3416e+00 - 1.1102e-16 * I}; /* * These pilots are compatible with Octave version @@ -123,10 +127,13 @@ complex float qam16_mod(int *bits) { return qam16[(bits[3] << 3) | (bits[2] << 2) | (bits[1] << 1) | bits[0]]; } -void qam16_demod(complex float symbol, int *bits) { +void qam16_demod(complex float symbol, int *bits, float amp_est) { float dist[16]; int i; + amp_est += 1E-12; // prevent /0 errors + symbol /= amp_est; + for (i = 0; i < 16; i++) { dist[i] = cnormf(symbol - qam16[i]); } @@ -190,6 +197,7 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->state_machine = "voice1"; ofdm->edge_pilots = 1; ofdm->codename = "HRA_112_112"; + ofdm->EsNodB = 3.0; ofdm->amp_est_mode = 0; ofdm->tx_bpf_en = true; ofdm->rx_bpf_en = false; @@ -224,6 +232,7 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->state_machine = config->state_machine; ofdm->edge_pilots = config->edge_pilots; ofdm->codename = config->codename; + ofdm->EsNodB = config->EsNodB; ofdm->amp_est_mode = config->amp_est_mode; ofdm->tx_bpf_en = config->tx_bpf_en; ofdm->rx_bpf_en = config->rx_bpf_en; @@ -273,6 +282,7 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->config.state_machine = ofdm->state_machine; ofdm->config.edge_pilots = ofdm->edge_pilots; ofdm->config.codename = ofdm->codename; + ofdm->config.EsNodB = ofdm->EsNodB; ofdm->config.amp_est_mode = ofdm->amp_est_mode; ofdm->config.tx_bpf_en = ofdm->tx_bpf_en; ofdm->config.rx_bpf_en = ofdm->rx_bpf_en; @@ -464,13 +474,12 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->tx_uw_syms = MALLOC(sizeof(complex float) * (ofdm->nuwbits / ofdm->bps)); assert(ofdm->tx_uw_syms != NULL); - - assert(ofdm->bps == 2); // TODO generalise - for (int s = 0; s < (ofdm->nuwbits / ofdm->bps); s++) { - int dibit[2]; - dibit[1] = ofdm->tx_uw[2 * s]; - dibit[0] = ofdm->tx_uw[2 * s + 1]; - ofdm->tx_uw_syms[s] = qpsk_mod(dibit); + for (int b = 0, s = 0; b < ofdm->nuwbits; b += ofdm->bps, s++) { + int bits[ofdm->bps]; + for (int i = 0; i < ofdm->bps; i++) + bits[ofdm->bps - 1 - i] = ofdm->tx_uw[b + i]; + if (ofdm->bps == 2) ofdm->tx_uw_syms[s] = qpsk_mod(bits); + if (ofdm->bps == 4) ofdm->tx_uw_syms[s] = qam16_mod(bits); } /* sync state machine */ @@ -559,7 +568,8 @@ static void allocate_tx_bpf(struct OFDM *ofdm) { quisk_filt_cfInit(ofdm->tx_bpf, filtP400S600, sizeof(filtP400S600) / sizeof(float)); quisk_cfTune(ofdm->tx_bpf, ofdm->tx_centre / ofdm->fs); - } else if (!strcmp(ofdm->mode, "datac4") || !strcmp(ofdm->mode, "datac13")) { + } else if (!strcmp(ofdm->mode, "datac4") || !strcmp(ofdm->mode, "datac13") || + !strcmp(ofdm->mode, "datac14")) { quisk_filt_cfInit(ofdm->tx_bpf, filtP200S400, sizeof(filtP200S400) / sizeof(float)); // centre the filter on the mean carrier freq, allows a narrower filter to @@ -590,7 +600,8 @@ static void allocate_rx_bpf(struct OFDM *ofdm) { /* Receive bandpass filter; complex coefficients, center frequency */ - if (!strcmp(ofdm->mode, "datac4") || !strcmp(ofdm->mode, "datac13")) { + if (!strcmp(ofdm->mode, "datac4") || !strcmp(ofdm->mode, "datac13") || + !strcmp(ofdm->mode, "datac14")) { quisk_filt_cfInit(ofdm->rx_bpf, filtP200S400, sizeof(filtP200S400) / sizeof(float)); // centre the filter on the mean carrier freq, allows a narrower filter to @@ -1185,25 +1196,15 @@ void ofdm_mod(struct OFDM *ofdm, COMP *result, const int *tx_bits) { complex float *tx = (complex float *)result; // complex has same memory layout complex float tx_sym_lin[length]; - int dibit[2]; - int s, i; - if (ofdm->bps == 1) { - /* Here we will have Nbitsperpacket / 1 */ - - for (s = 0; s < length; s++) { - tx_sym_lin[s] = (float)(2 * tx_bits[s] - 1); - } - } else if (ofdm->bps == 2) { - /* Here we will have Nbitsperpacket / 2 */ - - for (s = 0, i = 0; i < length; s += 2, i++) { - dibit[0] = tx_bits[s + 1] & 0x1; - dibit[1] = tx_bits[s] & 0x1; - - tx_sym_lin[i] = qpsk_mod(dibit); - } - } /* else if (ofdm->bps == 3) { } TODO */ + assert((ofdm->bps == 2) || (ofdm->bps == 4)); + for (int b = 0, s = 0; b < ofdm->bitsperpacket; b += ofdm->bps, s++) { + int bits[ofdm->bps]; + for (int i = 0; i < ofdm->bps; i++) + bits[ofdm->bps - 1 - i] = tx_bits[b + i] & 0x1; + if (ofdm->bps == 2) tx_sym_lin[s] = qpsk_mod(bits); + if (ofdm->bps == 4) tx_sym_lin[s] = qam16_mod(bits); + } ofdm_txframe(ofdm, tx, tx_sym_lin); } @@ -1873,7 +1874,7 @@ static void ofdm_demod_core(struct OFDM *ofdm, int *rx_bits) { * frame bit ordering correct */ complex float rx_corr; - int abit[2]; + int abit[ofdm->bps]; int bit_index = 0; float sum_amp = 0.0f; @@ -1920,17 +1921,10 @@ static void ofdm_demod_core(struct OFDM *ofdm, int *rx_bits) { ofdm->aphase_est_pilot_log[(rr * ofdm->nc) + (i - 1)] = aphase_est_pilot[i]; - if (ofdm->bps == 1) { - rx_bits[bit_index++] = crealf(rx_corr) > 0.0f; - } else if (ofdm->bps == 2) { - /* - * Only one final task, decode what quadrant the phase - * is in, and return the dibits - */ - qpsk_demod(rx_corr, abit); - rx_bits[bit_index++] = abit[1]; - rx_bits[bit_index++] = abit[0]; - } + if (ofdm->bps == 2) qpsk_demod(rx_corr, abit); + if (ofdm->bps == 4) qam16_demod(rx_corr, abit, aamp_est_pilot[i]); + for (int i = 0; i < ofdm->bps; i++) + rx_bits[bit_index++] = abit[ofdm->bps - 1 - i]; } } @@ -2387,9 +2381,9 @@ void ofdm_get_demod_stats(struct OFDM *ofdm, struct MODEM_STATS *stats, /* * Assemble packet of bits from UW, payload bits, and txt bits */ -void ofdm_assemble_qpsk_modem_packet(struct OFDM *ofdm, uint8_t modem_frame[], - uint8_t payload_bits[], - uint8_t txt_bits[]) { +void ofdm_assemble_psk_modem_packet(struct OFDM *ofdm, uint8_t modem_frame[], + uint8_t payload_bits[], + uint8_t txt_bits[]) { int s, t; int p = 0; @@ -2416,10 +2410,10 @@ void ofdm_assemble_qpsk_modem_packet(struct OFDM *ofdm, uint8_t modem_frame[], /* * Assemble packet of symbols from UW, payload symbols, and txt bits */ -void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *ofdm, - complex float modem_packet[], - COMP payload_syms[], - uint8_t txt_bits[]) { +void ofdm_assemble_psk_modem_packet_symbols(struct OFDM *ofdm, + complex float modem_packet[], + COMP payload_syms[], + uint8_t txt_bits[]) { complex float *payload = (complex float *)&payload_syms[0]; // complex has same memory layout int Nsymsperpacket = ofdm->bitsperpacket / ofdm->bps; @@ -2431,10 +2425,6 @@ void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *ofdm, int p = 0; int u = 0; - assert( - ofdm->bps == - 2); /* this only works for QPSK at this stage (e.g. modem packet mod) */ - for (s = 0; s < (Nsymsperpacket - Ntxtsyms); s++) { if ((u < Nuwsyms) && (s == ofdm->uw_ind_sym[u])) { modem_packet[s] = ofdm->tx_uw_syms[u++]; @@ -2446,6 +2436,9 @@ void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *ofdm, assert(u == Nuwsyms); assert(p == (Nsymsperpacket - Nuwsyms - Ntxtsyms)); + /* txt bit insertion only works for QPSK at this stage, however QAM modes + * generally has no txt bits */ + assert((Ntxtsyms == 0) || (ofdm->bps == 2)); for (t = 0; s < Nsymsperpacket; s++, t += 2) { dibit[1] = txt_bits[t] & 0x1; dibit[0] = txt_bits[t + 1] & 0x1; @@ -2459,24 +2452,22 @@ void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *ofdm, * Disassemble a received packet of symbols into UW bits and payload data * symbols */ -void ofdm_disassemble_qpsk_modem_packet(struct OFDM *ofdm, - complex float rx_syms[], - float rx_amps[], COMP codeword_syms[], - float codeword_amps[], - short txt_bits[]) { +void ofdm_disassemble_psk_modem_packet(struct OFDM *ofdm, + complex float rx_syms[], float rx_amps[], + COMP codeword_syms[], + float codeword_amps[], + short txt_bits[]) { complex float *codeword = (complex float *)&codeword_syms[0]; // complex has same memory layout int Nsymsperpacket = ofdm->bitsperpacket / ofdm->bps; int Nuwsyms = ofdm->nuwbits / ofdm->bps; int Ntxtsyms = ofdm->ntxtbits / ofdm->bps; - int dibit[2]; + int bits[ofdm->bps]; int s, t; int p = 0; int u = 0; - assert(ofdm->bps == 2); /* this only works for QPSK at this stage */ - for (s = 0; s < (Nsymsperpacket - Ntxtsyms); s++) { if ((u < Nuwsyms) && (s == ofdm->uw_ind_sym[u])) { u++; @@ -2490,11 +2481,12 @@ void ofdm_disassemble_qpsk_modem_packet(struct OFDM *ofdm, assert(u == Nuwsyms); assert(p == (Nsymsperpacket - Nuwsyms - Ntxtsyms)); - for (t = 0; s < Nsymsperpacket; s++, t += 2) { - qpsk_demod(rx_syms[s], dibit); + for (t = 0; s < Nsymsperpacket; s++, t += ofdm->bps) { + if (ofdm->bps == 2) qpsk_demod(rx_syms[s], bits); + if (ofdm->bps == 4) qam16_demod(rx_syms[s], bits, rx_amps[s]); - txt_bits[t] = dibit[1]; - txt_bits[t + 1] = dibit[0]; + for (int i = 0; i < ofdm->bps; i++) + txt_bits[t + i] = bits[ofdm->bps - 1 - i]; } assert(t == ofdm->ntxtbits); @@ -2504,7 +2496,7 @@ void ofdm_disassemble_qpsk_modem_packet(struct OFDM *ofdm, * Disassemble a received packet of symbols into UW bits and payload data * symbols */ -void ofdm_disassemble_qpsk_modem_packet_with_text_amps( +void ofdm_disassemble_psk_modem_packet_with_text_amps( struct OFDM *ofdm, complex float rx_syms[], float rx_amps[], COMP codeword_syms[], float codeword_amps[], short txt_bits[], int *textIndex) { @@ -2513,13 +2505,12 @@ void ofdm_disassemble_qpsk_modem_packet_with_text_amps( int Nsymsperpacket = ofdm->bitsperpacket / ofdm->bps; int Nuwsyms = ofdm->nuwbits / ofdm->bps; int Ntxtsyms = ofdm->ntxtbits / ofdm->bps; - int dibit[2]; + int bits[ofdm->bps]; int s, t; int p = 0; int u = 0; - assert(ofdm->bps == 2); /* this only works for QPSK at this stage */ assert(textIndex != NULL); for (s = 0; s < (Nsymsperpacket - Ntxtsyms); s++) { @@ -2536,11 +2527,12 @@ void ofdm_disassemble_qpsk_modem_packet_with_text_amps( assert(p == (Nsymsperpacket - Nuwsyms - Ntxtsyms)); *textIndex = s; - for (t = 0; s < Nsymsperpacket; s++, t += 2) { - qpsk_demod(rx_syms[s], dibit); + for (t = 0; s < Nsymsperpacket; s++, t += ofdm->bps) { + if (ofdm->bps == 2) qpsk_demod(rx_syms[s], bits); + if (ofdm->bps == 4) qam16_demod(rx_syms[s], bits, rx_amps[s]); - txt_bits[t] = dibit[1]; - txt_bits[t + 1] = dibit[0]; + for (int i = 0; i < ofdm->bps; i++) + txt_bits[t + i] = bits[ofdm->bps - 1 - i]; } assert(t == ofdm->ntxtbits); @@ -2553,17 +2545,15 @@ void ofdm_extract_uw(struct OFDM *ofdm, complex float rx_syms[], float rx_amps[], uint8_t rx_uw[]) { int Nsymsperframe = ofdm->bitsperframe / ofdm->bps; int Nuwsyms = ofdm->nuwbits / ofdm->bps; - int dibit[2]; int s, u; - assert(ofdm->bps == - 2); /* this only works for QPSK at this stage (e.g. UW demod) */ - for (s = 0, u = 0; s < Nsymsperframe * ofdm->nuwframes; s++) { if ((u < Nuwsyms) && (s == ofdm->uw_ind_sym[u])) { - qpsk_demod(rx_syms[s], dibit); - rx_uw[2 * u] = dibit[1]; - rx_uw[2 * u + 1] = dibit[0]; + int bits[ofdm->bps]; + if (ofdm->bps == 2) qpsk_demod(rx_syms[s], bits); + if (ofdm->bps == 4) qam16_demod(rx_syms[s], bits, rx_amps[s]); + for (int i = 0; i < ofdm->bps; i++) + rx_uw[ofdm->bps * u + i] = bits[ofdm->bps - 1 - i]; u++; } } diff --git a/src/ofdm_demod.c b/src/ofdm_demod.c index 80f01bd6..84439d99 100644 --- a/src/ofdm_demod.c +++ b/src/ofdm_demod.c @@ -408,9 +408,7 @@ int main(int argc, char *argv[]) { else Ndiscard = 1; /* much longer packets, so discard thresh smaller */ - float EsNo = 3.0f; - - if (verbose == 2) fprintf(stderr, "Warning EsNo: %f hard coded\n", EsNo); + float EsNo = pow(10.0, ofdm->EsNodB / 10.0); /* More logging */ COMP payload_syms_log[NFRAMES][Npayloadsymsperpacket]; @@ -473,8 +471,8 @@ int main(int argc, char *argv[]) { /* we have received enough frames to make a complete packet .... */ /* extract payload symbols from packet */ - ofdm_disassemble_qpsk_modem_packet(ofdm, rx_syms, rx_amps, payload_syms, - payload_amps, txt_bits); + ofdm_disassemble_psk_modem_packet(ofdm, rx_syms, rx_amps, payload_syms, + payload_amps, txt_bits); if (ldpc_en) { assert((ofdm_nuwbits + ofdm_ntxtbits + Npayloadbitsperpacket) <= @@ -492,15 +490,15 @@ int main(int argc, char *argv[]) { uint8_t out_char[Npayloadbitsperpacket]; if (testframes == true) { - Nerrs_raw = - count_uncoded_errors(&ldpc, ofdm_config, payload_syms_de, 0); + Nerrs_raw = count_uncoded_errors( + &ldpc, ofdm_config, payload_syms_de, payload_amps_de, 0); Terrs += Nerrs_raw; Tbits += Npayloadbitsperpacket; /* not counting errors in txt bits */ } symbols_to_llrs(llr, payload_syms_de, payload_amps_de, EsNo, - ofdm->mean_amp, Npayloadsymsperpacket); + ofdm->mean_amp, ofdm->bps, Npayloadsymsperpacket); assert(Ndatabitsperpacket == ldpc.data_bits_per_frame); ldpc_decode_frame(&ldpc, &parityCheckCount, &iter, out_char, llr); @@ -524,7 +522,7 @@ int main(int argc, char *argv[]) { assert(Npayloadsymsperpacket * ofdm_config->bps == Npayloadbitsperpacket); for (i = 0; i < Npayloadsymsperpacket; i++) { - int bits[2]; + int bits[ofdm->bps]; complex float s = payload_syms[i].real + I * payload_syms[i].imag; qpsk_demod(s, bits); rx_bits_char[ofdm_config->bps * i] = bits[1]; @@ -547,17 +545,16 @@ int main(int argc, char *argv[]) { memset(txt_bits, 0, ofdm_ntxtbits); uint8_t tx_bits[Nbitsperpacket]; ofdm_generate_payload_data_bits(payload_bits, Npayloadbitsperpacket); - ofdm_assemble_qpsk_modem_packet(ofdm, tx_bits, payload_bits, - txt_bits); + ofdm_assemble_psk_modem_packet(ofdm, tx_bits, payload_bits, txt_bits); /* count errors across UW, payload, txt bits */ int rx_bits[Nbitsperpacket]; - int dibit[2]; - assert(ofdm->bps == 2); /* this only works for QPSK at this stage */ + int bits[ofdm->bps]; for (int s = 0; s < Nsymsperpacket; s++) { - qpsk_demod(rx_syms[s], dibit); - rx_bits[2 * s] = dibit[1]; - rx_bits[2 * s + 1] = dibit[0]; + if (ofdm->bps == 2) qpsk_demod(rx_syms[s], bits); + if (ofdm->bps == 4) qam16_demod(rx_syms[s], bits, rx_amps[s]); + for (int i = 0; i < ofdm->bps; i++) + rx_bits[ofdm->bps * s + i] = bits[ofdm->bps - 1 - i]; } for (Nerrs_raw = 0, i = 0; i < Nbitsperpacket; i++) if (tx_bits[i] != rx_bits[i]) Nerrs_raw++; diff --git a/src/ofdm_internal.h b/src/ofdm_internal.h index 8007934f..af449d33 100644 --- a/src/ofdm_internal.h +++ b/src/ofdm_internal.h @@ -46,7 +46,7 @@ extern "C" { #define TAU (2.0f * M_PI) #define ROT45 (M_PI / 4.0f) -#define MAX_UW_BITS 64 +#define MAX_UW_BITS 192 #define cmplx(value) (cosf(value) + sinf(value) * I) #define cmplxconj(value) (cosf(value) + sinf(value) * -I) @@ -108,6 +108,7 @@ struct OFDM_CONFIG { char *data_mode; float fmin; float fmax; + float EsNodB; /* EsNo est used for LDPC decoder */ }; struct OFDM { @@ -248,6 +249,7 @@ struct OFDM { detector */ char *codename; + float EsNodB; /* EsNo est used for LDPC decoder */ char *state_machine; }; @@ -256,19 +258,19 @@ struct OFDM { complex float qpsk_mod(int *); complex float qam16_mod(int *); void qpsk_demod(complex float, int *); -void qam16_demod(complex float, int *); +void qam16_demod(complex float, int *, float); void ofdm_txframe(struct OFDM *, complex float *, complex float[]); -void ofdm_assemble_qpsk_modem_packet(struct OFDM *, uint8_t[], uint8_t[], - uint8_t[]); -void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *, complex float[], - COMP[], uint8_t[]); -void ofdm_disassemble_qpsk_modem_packet(struct OFDM *, complex float rx_syms[], - float rx_amps[], COMP[], float[], - short[]); -void ofdm_disassemble_qpsk_modem_packet_with_text_amps(struct OFDM *, - complex float rx_syms[], - float rx_amps[], COMP[], - float[], short[], int *); +void ofdm_assemble_psk_modem_packet(struct OFDM *, uint8_t[], uint8_t[], + uint8_t[]); +void ofdm_assemble_psk_modem_packet_symbols(struct OFDM *, complex float[], + COMP[], uint8_t[]); +void ofdm_disassemble_psk_modem_packet(struct OFDM *, complex float rx_syms[], + float rx_amps[], COMP[], float[], + short[]); +void ofdm_disassemble_psk_modem_packet_with_text_amps(struct OFDM *, + complex float rx_syms[], + float rx_amps[], COMP[], + float[], short[], int *); void ofdm_extract_uw(struct OFDM *ofdm, complex float rx_syms[], float rx_amps[], uint8_t rx_uw[]); void ofdm_rand(uint16_t[], int); diff --git a/src/ofdm_mod.c b/src/ofdm_mod.c index d4c495e8..23444f57 100644 --- a/src/ofdm_mod.c +++ b/src/ofdm_mod.c @@ -398,8 +398,7 @@ int main(int argc, char *argv[]) { /* assemble packet of bits then modulate */ uint8_t tx_bits_char[Nbitsperpacket]; - ofdm_assemble_qpsk_modem_packet(ofdm, tx_bits_char, data_bits, - txt_bits); + ofdm_assemble_psk_modem_packet(ofdm, tx_bits_char, data_bits, txt_bits); int tx_bits[Nbitsperpacket]; for (i = 0; i < Nbitsperpacket; i++) tx_bits[i] = tx_bits_char[i]; COMP tx_sams[Nsamperpacket]; diff --git a/src/ofdm_mode.c b/src/ofdm_mode.c index 1aabb1af..f8cf3228 100644 --- a/src/ofdm_mode.c +++ b/src/ofdm_mode.c @@ -42,6 +42,7 @@ void ofdm_init_mode(char mode[], struct OFDM_CONFIG *config) { config->state_machine = "voice1"; config->data_mode = ""; config->codename = "HRA_112_112"; + config->EsNodB = 3.0; config->clip_gain1 = 2.5; config->clip_gain2 = 0.8; config->clip_en = true; @@ -92,23 +93,36 @@ void ofdm_init_mode(char mode[], struct OFDM_CONFIG *config) { config->state_machine = "voice2"; config->ftwindowwidth = 64; config->foff_limiter = true; - } else if (strcmp(mode, "qam16") == 0) { - /* not in use yet */ + } else if (strcmp(mode, "qam16c2") == 0) { config->ns = 5; - config->np = 5; + config->np = 31; config->tcp = 0.004; config->ts = 0.016; config->nc = 33; config->bps = 4; config->txtbits = 0; - config->nuwbits = 15 * 4; - config->bad_uw_errors = 5; - config->ftwindowwidth = 32; + config->nuwbits = 42 * 4; + assert(config->nuwbits <= MAX_UW_BITS); + config->bad_uw_errors = 50; + config->ftwindowwidth = 80; config->state_machine = "data"; config->amp_est_mode = 1; config->tx_bpf_en = false; config->clip_en = false; config->data_mode = "streaming"; + config->amp_scale = 135E3; + config->clip_en = false; + config->tx_bpf_en = false; + config->rx_bpf_en = false; + + uint8_t uw[] = {1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0}; + memset(config->tx_uw, 0, config->nuwbits); + memcpy(config->tx_uw, uw, sizeof(uw)); + memcpy(&config->tx_uw[config->nuwbits - sizeof(uw)], uw, sizeof(uw)); + + config->EsNodB = 10; + config->codename = "H_16200_9720"; } else if (strcmp(mode, "datac0") == 0) { config->ns = 5; config->np = 4; @@ -226,6 +240,31 @@ void ofdm_init_mode(char mode[], struct OFDM_CONFIG *config) { config->clip_gain1 = 1.2; config->clip_gain2 = 1.0; config->rx_bpf_en = true; + } else if (strcmp(mode, "datac14") == 0) { + config->ns = 5; + config->np = 4; + config->tcp = 0.005; + config->ts = 0.018; + config->nc = 4; + config->edge_pilots = 0; + config->txtbits = 0; + config->state_machine = "data"; + config->ftwindowwidth = 80; + config->timing_mx_thresh = 0.45; + config->codename = "HRA_56_56"; + config->amp_est_mode = 1; + config->nuwbits = 32; + config->bad_uw_errors = 12; + uint8_t uw[] = {1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0}; + assert(sizeof(uw) <= MAX_UW_BITS); + memcpy(config->tx_uw, uw, sizeof(uw)); + memcpy(&config->tx_uw[config->nuwbits - sizeof(uw)], uw, sizeof(uw)); + config->data_mode = "streaming"; + config->amp_scale = 2.0 * 300E3; + config->clip_gain1 = 2.0; + config->clip_gain2 = 1.0; + config->rx_bpf_en = true; } else { assert(0); } diff --git a/src/reliable_text.c b/src/reliable_text.c index 8a5ba219..ff7c5c0e 100644 --- a/src/reliable_text.c +++ b/src/reliable_text.c @@ -182,7 +182,8 @@ static int reliable_text_ldpc_decode(reliable_text_impl_t* obj, char* dest) { float EsNo = 3.0; // note: constant from freedv_700.c symbols_to_llrs(llr, (COMP*)deinterleavedSyms, deinterleavedAmps, EsNo, - obj->fdv->ofdm->mean_amp, Npayloadsymsperpacket); + obj->fdv->ofdm->mean_amp, obj->fdv->ofdm->bps, + Npayloadsymsperpacket); } else { // Deinterlace the received bits. gp_deinterleave_bits(deinterleavedBits, src, LDPC_TOTAL_SIZE_BITS / 2); diff --git a/stm32/unittest/src/tst_ofdm_demod.c b/stm32/unittest/src/tst_ofdm_demod.c index 54cc027b..980c2368 100644 --- a/stm32/unittest/src/tst_ofdm_demod.c +++ b/stm32/unittest/src/tst_ofdm_demod.c @@ -25,7 +25,6 @@ along with this program; if not, see . */ - /* This is a unit test implementation of the OFDM Demod function. * It is used for several tests: * @@ -45,40 +44,39 @@ * ofdm_get_test_bits - 10 | * ofdm_mod - - | \ * cohpsk_ch - stm_in.raw -20 -Fs 8000 -f -5 * - * Reference data can be created by running the same input through the x86 + * Reference data can be created by running the same input through the x86 * ofdm_demod tool. * - * ofdm_demod stm_in.raw ref_demod_out.raw -o ofdm_demod_ref_log.txt --testframes + * ofdm_demod stm_in.raw ref_demod_out.raw -o ofdm_demod_ref_log.txt + * --testframes * - * Comparison of the results to the reference will depend on the test conditions. - * Some small differences are expected due to differences in implementation. + * Comparison of the results to the reference will depend on the test + * conditions. Some small differences are expected due to differences in + * implementation. * */ - #include +#include +#include +#include #include #include #include -#include -#include -#include #include -#include "semihosting.h" #include "codec2_ofdm.h" -#include "ofdm_internal.h" -#include "mpdecode_core.h" -#include "ldpc_codes.h" -#include "interldpc.h" -#include "gp_interleaver.h" -#include "test_bits_ofdm.h" - #include "debug_alloc.h" - -#include "stm32f4xx_conf.h" -#include "stm32f4xx.h" +#include "gp_interleaver.h" +#include "interldpc.h" +#include "ldpc_codes.h" #include "machdep.h" +#include "mpdecode_core.h" +#include "ofdm_internal.h" +#include "semihosting.h" +#include "stm32f4xx.h" +#include "stm32f4xx_conf.h" +#include "test_bits_ofdm.h" #define NDISCARD 20 @@ -92,347 +90,361 @@ static int ofdm_rowsperframe; static int ofdm_nuwbits; static int ofdm_ntxtbits; static int ofdm_nin; -static char fout_buffer[4*4096]; -static __attribute__ ((section (".ccm"))) char fdiag_buffer[4*8192]; -static __attribute__ ((section (".ccm"))) char fin_buffer[4096*8]; +static char fout_buffer[4 * 4096]; +static __attribute__((section(".ccm"))) char fdiag_buffer[4 * 8192]; +static __attribute__((section(".ccm"))) char fin_buffer[4096 * 8]; -static char *statemode[] = { - "search", - "trial", - "synced" -}; +static char *statemode[] = {"search", "trial", "synced"}; static FILE *fout, *fdiag; void flush_all(void) { - fflush(fout); - fflush(fdiag); - fflush(stdout); - fflush(stderr); - } + fflush(fout); + fflush(fdiag); + fflush(stdout); + fflush(stderr); +} int main(int argc, char *argv[]) { - struct OFDM *ofdm; - FILE *fcfg; - int nin_frame; - struct LDPC ldpc; - - // Test configuration, read from stm_cfg.txt - int config_verbose; - int config_testframes; - int config_ldpc_en; - int config_log_payload_syms; - int config_profile; - - int i; - int Nerrs, Terrs, Tbits, Terrs2, Tbits2, frame_count; - int Tbits_coded, Terrs_coded; - - semihosting_init(); - - fprintf(stdout, "OFDM Demod test\n"); - - // Read configuration - a file of '0' or '1' characters - char config[8]; - fcfg = fopen("stm_cfg.txt", "r"); - if (fcfg == NULL) { - fprintf(stderr, "Error opening config file\n"); - exit(1); - } - if (fread(&config[0], 1, 8, fcfg) != 8) { - fprintf(stderr, "Error reading config file\n"); - exit(1); + struct OFDM *ofdm; + FILE *fcfg; + int nin_frame; + struct LDPC ldpc; + + // Test configuration, read from stm_cfg.txt + int config_verbose; + int config_testframes; + int config_ldpc_en; + int config_log_payload_syms; + int config_profile; + + int i; + int Nerrs, Terrs, Tbits, Terrs2, Tbits2, frame_count; + int Tbits_coded, Terrs_coded; + + semihosting_init(); + + fprintf(stdout, "OFDM Demod test\n"); + + // Read configuration - a file of '0' or '1' characters + char config[8]; + fcfg = fopen("stm_cfg.txt", "r"); + if (fcfg == NULL) { + fprintf(stderr, "Error opening config file\n"); + exit(1); + } + if (fread(&config[0], 1, 8, fcfg) != 8) { + fprintf(stderr, "Error reading config file\n"); + exit(1); + } + config_verbose = config[0] - '0'; + config_testframes = config[1] - '0'; + config_ldpc_en = config[2] - '0'; + config_log_payload_syms = config[3] - '0'; + config_profile = config[4] - '0'; + fclose(fcfg); + + int Nerrs_raw = 0; + int Nerrs_coded = 0; + int iter = 0; + int parityCheckCount = 0; + + PROFILE_VAR(ofdm_demod_start, ofdm_demod_sync_search, ofdm_demod_demod, + ofdm_demod_diss, ofdm_demod_snr); + ofdm_demod_start = 0; + ofdm_demod_sync_search = 0; + ofdm_demod_demod = 0; + ofdm_demod_diss = 0; + ofdm_demod_snr = 0; + if (config_profile) machdep_profile_init(); + + ofdm = ofdm_create(NULL); + assert(ofdm != NULL); + + /* Get a copy of the actual modem config */ + ofdm_config = ofdm_get_config_param(ofdm); + + ldpc_codes_setup(&ldpc, "HRA_112_112"); + + ofdm_bitsperframe = ofdm_get_bits_per_frame(ofdm); + ofdm_rowsperframe = ofdm_bitsperframe / (ofdm_config->nc * ofdm_config->bps); + ofdm_nuwbits = + (ofdm_config->ns - 1) * ofdm_config->bps - ofdm_config->txtbits; + ofdm_ntxtbits = ofdm_config->txtbits; + ofdm_nin = ofdm_get_nin(ofdm); + + ofdm_set_verbose(ofdm, config_verbose); + + int Nmaxsamperframe = ofdm_get_max_samples_per_frame(ofdm); + + int data_bits_per_frame = ldpc.data_bits_per_frame; + int coded_bits_per_frame = ldpc.coded_bits_per_frame; + int coded_syms_per_frame = ldpc.coded_bits_per_frame / ofdm->bps; + + short rx_scaled[Nmaxsamperframe]; + int rx_bits[ofdm_bitsperframe]; + char rx_bits_char[ofdm_bitsperframe]; + uint8_t rx_uw[ofdm_nuwbits]; + short txt_bits[ofdm_ntxtbits]; + int f = 0; + Nerrs = Terrs = Tbits = Terrs2 = Tbits2 = Terrs_coded = Tbits_coded = + frame_count = 0; + + float snr_est_smoothed_dB = 0.0; + + float EsNo = 3.0f; // Constant from ofdm_demod.c + + COMP payload_syms[coded_syms_per_frame]; + float payload_amps[coded_syms_per_frame]; + COMP codeword_symbols[coded_syms_per_frame]; + float codeword_amps[coded_syms_per_frame]; + + FILE *fin = fopen("stm_in.raw", "rb"); + if (fin == NULL) { + fprintf(stderr, "Error opening input file\n"); + exit(1); + } + setvbuf(fin, fin_buffer, _IOFBF, sizeof(fin_buffer)); + + fout = fopen("stm_out.raw", "wb"); + if (fout == NULL) { + fprintf(stderr, "Error opening output file\n"); + exit(1); + } + setvbuf(fout, fout_buffer, _IOFBF, sizeof(fout_buffer)); + + fdiag = fopen("stm_diag.raw", "wb"); + if (fdiag == NULL) { + fprintf(stderr, "Error opening diag file\n"); + exit(1); + } + setvbuf(fdiag, fdiag_buffer, _IOFBF, sizeof(fdiag_buffer)); + + nin_frame = ofdm_get_nin(ofdm); + int num_read; + + while ((num_read = fread(rx_scaled, sizeof(short), nin_frame, fin)) == + nin_frame) { + int log_payload_syms_flag = 0; + + if (config_profile) PROFILE_SAMPLE(ofdm_demod_start); + + /* demod */ + + if (config_profile) + PROFILE_SAMPLE_AND_LOG2(ofdm_demod_start, " ofdm_demod_start"); + + if (ofdm->sync_state == search) { + if (config_profile) PROFILE_SAMPLE(ofdm_demod_sync_search); + ofdm_sync_search_shorts(ofdm, rx_scaled, (OFDM_PEAK / 2)); + if (config_profile) + PROFILE_SAMPLE_AND_LOG2(ofdm_demod_sync_search, + " ofdm_demod_sync_search"); } - config_verbose = config[0] - '0'; - config_testframes = config[1] - '0'; - config_ldpc_en = config[2] - '0'; - config_log_payload_syms = config[3] - '0'; - config_profile = config[4] - '0'; - fclose(fcfg); - - int Nerrs_raw = 0; - int Nerrs_coded = 0; - int iter = 0; - int parityCheckCount = 0; - - PROFILE_VAR(ofdm_demod_start, ofdm_demod_sync_search, - ofdm_demod_demod, ofdm_demod_diss, ofdm_demod_snr); - ofdm_demod_start = 0; - ofdm_demod_sync_search = 0; - ofdm_demod_demod = 0; - ofdm_demod_diss = 0; - ofdm_demod_snr = 0; - if (config_profile) machdep_profile_init(); - - ofdm = ofdm_create(NULL); - assert(ofdm != NULL); - - /* Get a copy of the actual modem config */ - ofdm_config = ofdm_get_config_param(ofdm); - - ldpc_codes_setup(&ldpc, "HRA_112_112"); - - ofdm_bitsperframe = ofdm_get_bits_per_frame(ofdm); - ofdm_rowsperframe = ofdm_bitsperframe / (ofdm_config->nc * ofdm_config->bps); - ofdm_nuwbits = (ofdm_config->ns - 1) * ofdm_config->bps - ofdm_config->txtbits; - ofdm_ntxtbits = ofdm_config->txtbits; - ofdm_nin = ofdm_get_nin(ofdm); - - ofdm_set_verbose(ofdm, config_verbose); - - int Nmaxsamperframe = ofdm_get_max_samples_per_frame(ofdm); - - int data_bits_per_frame = ldpc.data_bits_per_frame; - int coded_bits_per_frame = ldpc.coded_bits_per_frame; - int coded_syms_per_frame = ldpc.coded_bits_per_frame/ofdm->bps; - - short rx_scaled[Nmaxsamperframe]; - int rx_bits[ofdm_bitsperframe]; - char rx_bits_char[ofdm_bitsperframe]; - uint8_t rx_uw[ofdm_nuwbits]; - short txt_bits[ofdm_ntxtbits]; - int f = 0; - Nerrs = Terrs = Tbits = Terrs2 = Tbits2 = Terrs_coded = Tbits_coded = frame_count = 0; - - float snr_est_smoothed_dB = 0.0; - - float EsNo = 3.0f; // Constant from ofdm_demod.c - - COMP payload_syms[coded_syms_per_frame]; - float payload_amps[coded_syms_per_frame]; - COMP codeword_symbols[coded_syms_per_frame]; - float codeword_amps[coded_syms_per_frame]; - - FILE* fin = fopen("stm_in.raw", "rb"); - if (fin == NULL) { - fprintf(stderr, "Error opening input file\n"); - exit(1); - } - setvbuf(fin, fin_buffer,_IOFBF,sizeof(fin_buffer)); + if ((ofdm->sync_state == synced) || (ofdm->sync_state == trial)) { + if (config_profile) PROFILE_SAMPLE(ofdm_demod_demod); + ofdm_demod_shorts(ofdm, rx_bits, rx_scaled, (OFDM_PEAK / 2)); + if (config_profile) + PROFILE_SAMPLE_AND_LOG2(ofdm_demod_demod, " ofdm_demod_demod"); + if (config_profile) PROFILE_SAMPLE(ofdm_demod_diss); + ofdm_extract_uw(ofdm, ofdm->rx_np, ofdm->rx_amp, rx_uw); + ofdm_disassemble_psk_modem_packet(ofdm, ofdm->rx_np, ofdm->rx_amp, + payload_syms, payload_amps, txt_bits); + if (config_profile) + PROFILE_SAMPLE_AND_LOG2(ofdm_demod_diss, " ofdm_demod_diss"); + log_payload_syms_flag = 1; + + /* SNR estimation and smoothing */ + if (config_profile) PROFILE_SAMPLE(ofdm_demod_snr); + float EsNodB = ofdm_esno_est_calc((complex float *)payload_syms, + coded_syms_per_frame); + float snr_est_dB = ofdm_snr_from_esno(ofdm, EsNodB); + snr_est_smoothed_dB = 0.9f * snr_est_smoothed_dB + 0.1f * snr_est_dB; + if (config_profile) { + PROFILE_SAMPLE_AND_LOG2(ofdm_demod_snr, " ofdm_demod_snr"); + } + + // LDPC + if (config_ldpc_en) { // was llr_en in orig + + /* first few symbols are used for UW and txt bits, find + start of (224,112) LDPC codeword and extract QPSK + symbols and amplitude estimates */ + assert((ofdm_nuwbits + ofdm_ntxtbits + coded_bits_per_frame) == + ofdm_bitsperframe); + + /* newest symbols at end of buffer (uses final i from last loop) */ + for (i = 0; i < coded_syms_per_frame; i++) { + codeword_symbols[i] = payload_syms[i]; + codeword_amps[i] = payload_amps[i]; + } - fout = fopen("stm_out.raw", "wb"); - if (fout == NULL) { - fprintf(stderr, "Error opening output file\n"); - exit(1); - } - setvbuf(fout, fout_buffer,_IOFBF,sizeof(fout_buffer)); + /* run de-interleaver */ + COMP codeword_symbols_de[coded_syms_per_frame]; + float codeword_amps_de[coded_syms_per_frame]; - fdiag = fopen("stm_diag.raw", "wb"); - if (fdiag == NULL) { - fprintf(stderr, "Error opening diag file\n"); - exit(1); - } - setvbuf(fdiag, fdiag_buffer,_IOFBF,sizeof(fdiag_buffer)); + gp_deinterleave_comp(codeword_symbols_de, codeword_symbols, + coded_syms_per_frame); + gp_deinterleave_float(codeword_amps_de, codeword_amps, + coded_syms_per_frame); - nin_frame = ofdm_get_nin(ofdm); - int num_read; - - while((num_read = fread(rx_scaled, sizeof(short) , nin_frame, fin)) == nin_frame) { + float llr[coded_bits_per_frame]; - int log_payload_syms_flag = 0; + if (config_ldpc_en) { + uint8_t out_char[coded_bits_per_frame]; + + if (config_testframes) { + Terrs += count_uncoded_errors( + &ldpc, ofdm_config, codeword_symbols_de, codeword_amps_de, 0); + Tbits += coded_bits_per_frame; + } + + symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, EsNo, + ofdm->mean_amp, ofdm->bps, coded_syms_per_frame); + iter = run_ldpc_decoder(&ldpc, out_char, llr, &parityCheckCount); + + // fprintf(stderr,"iter: %d pcc: %d\n", iter, parityCheckCount); + + if (config_testframes) { + /* construct payload data bits */ + uint8_t payload_data_bits[data_bits_per_frame]; + ofdm_generate_payload_data_bits(payload_data_bits, + data_bits_per_frame); + + Nerrs_coded = + count_errors(payload_data_bits, out_char, data_bits_per_frame); + Terrs_coded += Nerrs_coded; + Tbits_coded += data_bits_per_frame; + } + + fwrite(out_char, sizeof(char), data_bits_per_frame, fout); + } else { + /* lpdc_en == 0, external LDPC decoder, so output LLRs */ + symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, EsNo, + ofdm->mean_amp, ofdm->bps, coded_syms_per_frame); + fwrite(llr, sizeof(double), coded_bits_per_frame, fout); + } + } else { // !llrs_en (or ldpc_en) + + /* simple hard decision output for uncoded testing, excluding UW and txt + */ + assert(coded_syms_per_frame * ofdm_config->bps == coded_bits_per_frame); + for (i = 0; i < coded_syms_per_frame; i++) { + int bits[2]; + complex float s = payload_syms[i].real + I * payload_syms[i].imag; + qpsk_demod(s, bits); + rx_bits_char[ofdm_config->bps * i] = bits[1]; + rx_bits_char[ofdm_config->bps * i + 1] = bits[0]; + } - if (config_profile) PROFILE_SAMPLE(ofdm_demod_start); + fwrite(rx_bits_char, sizeof(uint8_t), coded_bits_per_frame, fout); + } - /* demod */ + /* optional error counting on uncoded data in non-LDPC testframe mode */ - if (config_profile) PROFILE_SAMPLE_AND_LOG2(ofdm_demod_start, " ofdm_demod_start"); + if (config_testframes && (config_ldpc_en == 0)) { + /* build up a test frame consisting of unique word, txt bits, and + psuedo-random uncoded payload bits. The psuedo-random generator is + the same as Octave so it can interoperate with ofdm_tx.m/ofdm_rx.m */ - if (ofdm->sync_state == search) { - if (config_profile) PROFILE_SAMPLE(ofdm_demod_sync_search); - ofdm_sync_search_shorts(ofdm, rx_scaled, (OFDM_PEAK/2)); - if (config_profile) PROFILE_SAMPLE_AND_LOG2(ofdm_demod_sync_search, " ofdm_demod_sync_search"); - } + int Npayloadbits = ofdm_bitsperframe - (ofdm_nuwbits + ofdm_ntxtbits); + uint16_t r[Npayloadbits]; + uint8_t payload_bits[Npayloadbits]; + uint8_t tx_bits[Npayloadbits]; + + ofdm_rand(r, Npayloadbits); - if ((ofdm->sync_state == synced) || (ofdm->sync_state == trial) ) { - if (config_profile) PROFILE_SAMPLE(ofdm_demod_demod); - ofdm_demod_shorts(ofdm, rx_bits, rx_scaled, (OFDM_PEAK/2)); - if (config_profile) PROFILE_SAMPLE_AND_LOG2(ofdm_demod_demod, " ofdm_demod_demod"); - if (config_profile) PROFILE_SAMPLE(ofdm_demod_diss); - ofdm_extract_uw(ofdm, ofdm->rx_np, ofdm->rx_amp, rx_uw); - ofdm_disassemble_qpsk_modem_packet(ofdm, ofdm->rx_np, ofdm->rx_amp, payload_syms, payload_amps, txt_bits); - if (config_profile) PROFILE_SAMPLE_AND_LOG2(ofdm_demod_diss, " ofdm_demod_diss"); - log_payload_syms_flag = 1; - - /* SNR estimation and smoothing */ - if (config_profile) PROFILE_SAMPLE(ofdm_demod_snr); - float EsNodB = ofdm_esno_est_calc((complex float*)payload_syms, coded_syms_per_frame); - float snr_est_dB = ofdm_snr_from_esno(ofdm, EsNodB); - snr_est_smoothed_dB = 0.9f * snr_est_smoothed_dB + 0.1f *snr_est_dB; - if (config_profile) { - PROFILE_SAMPLE_AND_LOG2(ofdm_demod_snr, " ofdm_demod_snr"); - } - - // LDPC - if (config_ldpc_en) { // was llr_en in orig - - /* first few symbols are used for UW and txt bits, find - start of (224,112) LDPC codeword and extract QPSK - symbols and amplitude estimates */ - assert((ofdm_nuwbits + ofdm_ntxtbits + coded_bits_per_frame) - == ofdm_bitsperframe); - - /* newest symbols at end of buffer (uses final i from last loop) */ - for(i=0; i < coded_syms_per_frame; i++) { - codeword_symbols[i] = payload_syms[i]; - codeword_amps[i] = payload_amps[i]; - } - - /* run de-interleaver */ - COMP codeword_symbols_de[coded_syms_per_frame]; - float codeword_amps_de[coded_syms_per_frame]; - - gp_deinterleave_comp (codeword_symbols_de, codeword_symbols, coded_syms_per_frame); - gp_deinterleave_float(codeword_amps_de, codeword_amps, coded_syms_per_frame); - - float llr[coded_bits_per_frame]; - - if (config_ldpc_en) { - uint8_t out_char[coded_bits_per_frame]; - - if (config_testframes) { - Terrs += count_uncoded_errors(&ldpc, ofdm_config, codeword_symbols_de, 0); - Tbits += coded_bits_per_frame; - } - - symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, - EsNo, ofdm->mean_amp, coded_syms_per_frame); - iter = run_ldpc_decoder(&ldpc, out_char, llr, &parityCheckCount); - - //fprintf(stderr,"iter: %d pcc: %d\n", iter, parityCheckCount); - - if (config_testframes) { - /* construct payload data bits */ - uint8_t payload_data_bits[data_bits_per_frame]; - ofdm_generate_payload_data_bits(payload_data_bits, data_bits_per_frame); - - Nerrs_coded = count_errors(payload_data_bits, out_char, data_bits_per_frame); - Terrs_coded += Nerrs_coded; - Tbits_coded += data_bits_per_frame; - } - - fwrite(out_char, sizeof(char), data_bits_per_frame, fout); - } else { - /* lpdc_en == 0, external LDPC decoder, so output LLRs */ - symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, EsNo, ofdm->mean_amp, coded_syms_per_frame); - fwrite(llr, sizeof(double), coded_bits_per_frame, fout); - } - } else { // !llrs_en (or ldpc_en) - - /* simple hard decision output for uncoded testing, excluding UW and txt */ - assert(coded_syms_per_frame*ofdm_config->bps == coded_bits_per_frame); - for (i = 0; i < coded_syms_per_frame; i++) { - int bits[2]; - complex float s = payload_syms[i].real + I * payload_syms[i].imag; - qpsk_demod(s, bits); - rx_bits_char[ofdm_config->bps * i] = bits[1]; - rx_bits_char[ofdm_config->bps * i + 1] = bits[0]; - } - - fwrite(rx_bits_char, sizeof (uint8_t), coded_bits_per_frame, fout); - } - - /* optional error counting on uncoded data in non-LDPC testframe mode */ - - if (config_testframes && (config_ldpc_en == 0)) { - /* build up a test frame consisting of unique word, txt bits, and psuedo-random - uncoded payload bits. The psuedo-random generator is the same as Octave so - it can interoperate with ofdm_tx.m/ofdm_rx.m */ - - int Npayloadbits = ofdm_bitsperframe-(ofdm_nuwbits+ofdm_ntxtbits); - uint16_t r[Npayloadbits]; - uint8_t payload_bits[Npayloadbits]; - uint8_t tx_bits[Npayloadbits]; - - ofdm_rand(r, Npayloadbits); - - for(i=0; i 16384; - //fprintf(stderr,"%d %d ", r[i], tx_bits_char[i]); - } - - uint8_t txt_bits[ofdm_ntxtbits]; - - for(i=0; i= NDISCARD) { - Terrs2 += Nerrs; - Tbits2 += ofdm_bitsperframe; - } - } // config_testframes ... - - frame_count++; - } // state "synced" or "trial" - - nin_frame = ofdm_get_nin(ofdm); - ofdm_sync_state_machine(ofdm, rx_uw); - - /* act on any events returned by state machine */ - - if (ofdm->sync_start) { - Terrs = Tbits = Terrs2 = Tbits2 = Terrs_coded = Tbits_coded = frame_count = Nerrs_raw = Nerrs_coded = 0; + for (i = 0; i < Npayloadbits; i++) { + payload_bits[i] = r[i] > 16384; + // fprintf(stderr,"%d %d ", r[i], tx_bits_char[i]); } - if (config_testframes && config_verbose) { - fprintf(stderr, "%3d st: %-6s", f, statemode[ofdm->last_sync_state]); - fprintf(stderr, " euw: %2d %1d f: %5.1f eraw: %3d ecdd: %3d iter: %3d pcc: %3d\n", - ofdm->uw_errors, ofdm->sync_counter, - (double)ofdm->foff_est_hz, - Nerrs, Nerrs_coded, iter, parityCheckCount); + uint8_t txt_bits[ofdm_ntxtbits]; + + for (i = 0; i < ofdm_ntxtbits; i++) { + txt_bits[i] = 0; } - if (config_log_payload_syms) { - if (! log_payload_syms_flag) { - memset(payload_syms, 0, (sizeof(COMP)*coded_syms_per_frame)); - memset(payload_amps, 0, (sizeof(float)*coded_syms_per_frame)); - } - fwrite(payload_syms, sizeof(COMP), coded_syms_per_frame, fdiag); - fwrite(payload_amps, sizeof(float), coded_syms_per_frame, fdiag); - } - - f++; - } // while(fread(.., fin)) - - flush_all(); // To make sure this function is included in binary. - fclose(fin); - fclose(fout); - fclose(fdiag); - - if (config_testframes) { - printf("BER......: %5.4f Tbits: %5d Terrs: %5d\n", (double)Terrs/Tbits, Tbits, Terrs); - if (!config_ldpc_en) { - printf("BER2.....: %5.4f Tbits: %5d Terrs: %5d\n", (double)Terrs2/Tbits2, Tbits2, Terrs2); + ofdm_assemble_psk_modem_packet(ofdm, tx_bits, payload_bits, txt_bits); + + Nerrs = 0; + for (i = 0; i < ofdm_bitsperframe; i++) { + if (tx_bits[i] != rx_bits[i]) { + Nerrs++; + } } - if (config_ldpc_en) { - printf("Coded BER: %5.4f Tbits: %5d Terrs: %5d\n", - (double)Terrs_coded/Tbits_coded, Tbits_coded, Terrs_coded); + + Terrs += Nerrs; + Tbits += ofdm_bitsperframe; + + if (frame_count >= NDISCARD) { + Terrs2 += Nerrs; + Tbits2 += ofdm_bitsperframe; } + } // config_testframes ... + + frame_count++; + } // state "synced" or "trial" + + nin_frame = ofdm_get_nin(ofdm); + ofdm_sync_state_machine(ofdm, rx_uw); + + /* act on any events returned by state machine */ + + if (ofdm->sync_start) { + Terrs = Tbits = Terrs2 = Tbits2 = Terrs_coded = Tbits_coded = + frame_count = Nerrs_raw = Nerrs_coded = 0; } - if (config_profile) { - printf("\nStart Profile Data\n"); - machdep_profile_print_logged_samples(); - printf("End Profile Data\n"); - } + if (config_testframes && config_verbose) { + fprintf(stderr, "%3d st: %-6s", f, statemode[ofdm->last_sync_state]); + fprintf(stderr, + " euw: %2d %1d f: %5.1f eraw: %3d ecdd: %3d iter: %3d pcc: %3d\n", + ofdm->uw_errors, ofdm->sync_counter, (double)ofdm->foff_est_hz, + Nerrs, Nerrs_coded, iter, parityCheckCount); + } + + if (config_log_payload_syms) { + if (!log_payload_syms_flag) { + memset(payload_syms, 0, (sizeof(COMP) * coded_syms_per_frame)); + memset(payload_amps, 0, (sizeof(float) * coded_syms_per_frame)); + } + fwrite(payload_syms, sizeof(COMP), coded_syms_per_frame, fdiag); + fwrite(payload_amps, sizeof(float), coded_syms_per_frame, fdiag); + } + + f++; + } // while(fread(.., fin)) + + flush_all(); // To make sure this function is included in binary. + fclose(fin); + fclose(fout); + fclose(fdiag); + + if (config_testframes) { + printf("BER......: %5.4f Tbits: %5d Terrs: %5d\n", (double)Terrs / Tbits, + Tbits, Terrs); + if (!config_ldpc_en) { + printf("BER2.....: %5.4f Tbits: %5d Terrs: %5d\n", + (double)Terrs2 / Tbits2, Tbits2, Terrs2); + } + if (config_ldpc_en) { + printf("Coded BER: %5.4f Tbits: %5d Terrs: %5d\n", + (double)Terrs_coded / Tbits_coded, Tbits_coded, Terrs_coded); + } + } + + if (config_profile) { + printf("\nStart Profile Data\n"); + machdep_profile_print_logged_samples(); + printf("End Profile Data\n"); + } - printf("\nEnd of Test\n"); - fclose(stdout); - fclose(stderr); + printf("\nEnd of Test\n"); + fclose(stdout); + fclose(stderr); - return 0; + return 0; } /* vi:set ts=4 et sts=4: */ diff --git a/stm32/unittest/src/tst_ofdm_mod.c b/stm32/unittest/src/tst_ofdm_mod.c index 77b2e370..33a35a0d 100644 --- a/stm32/unittest/src/tst_ofdm_mod.c +++ b/stm32/unittest/src/tst_ofdm_mod.c @@ -29,7 +29,7 @@ * * Typical run: - ofdm_gen_test_bits stm_in.raw 6 --rand + ofdm_gen_test_bits stm_in.raw 6 --rand ofdm_mod stm_in.raw ref_mod_out.raw @@ -58,191 +58,195 @@ */ #include +#include +#include +#include #include #include #include -#include -#include -#include #include -#include "semihosting.h" #include "codec2_ofdm.h" -#include "ofdm_internal.h" -#include "ldpc_codes.h" -#include "interldpc.h" +#include "debug_alloc.h" #include "gp_interleaver.h" - -#include "stm32f4xx_conf.h" -#include "stm32f4xx.h" +#include "interldpc.h" +#include "ldpc_codes.h" #include "machdep.h" - -#include "debug_alloc.h" +#include "ofdm_internal.h" +#include "semihosting.h" +#include "stm32f4xx.h" +#include "stm32f4xx_conf.h" int main(int argc, char *argv[]) { - struct OFDM *ofdm; - FILE *fcfg; - struct LDPC ldpc; - - // Test configuration, read from stm_cfg.txt - int config_verbose; -// int config_testframes; - int config_ldpc_en; -// int config_log_payload_syms; - int config_profile; - - int Nbitsperframe, Nsamperframe; - int frame = 0; - int i; - - semihosting_init(); - - printf("OFDM_mod test and profile\n"); - - // Read configuration - a file of '0' or '1' characters - char config[8]; - fcfg = fopen("stm_cfg.txt", "r"); - if (fcfg == NULL) { - fprintf(stderr, "Error opening config file\n"); - exit(1); - } - if (fread(&config[0], 1, 8, fcfg) != 8) { - fprintf(stderr, "Error reading config file\n"); - exit(1); - } - config_verbose = config[0] - '0'; -// config_testframes = config[1] - '0'; - config_ldpc_en = config[2] - '0'; -// config_log_payload_syms = config[3] - '0'; - config_profile = config[4] - '0'; - fclose(fcfg); - - PROFILE_VAR(ofdm_mod_start); - if (config_profile) machdep_profile_init(); - - struct OFDM_CONFIG *ofdm_config; + struct OFDM *ofdm; + FILE *fcfg; + struct LDPC ldpc; + + // Test configuration, read from stm_cfg.txt + int config_verbose; + // int config_testframes; + int config_ldpc_en; + // int config_log_payload_syms; + int config_profile; + + int Nbitsperframe, Nsamperframe; + int frame = 0; + int i; + + semihosting_init(); + + printf("OFDM_mod test and profile\n"); + + // Read configuration - a file of '0' or '1' characters + char config[8]; + fcfg = fopen("stm_cfg.txt", "r"); + if (fcfg == NULL) { + fprintf(stderr, "Error opening config file\n"); + exit(1); + } + if (fread(&config[0], 1, 8, fcfg) != 8) { + fprintf(stderr, "Error reading config file\n"); + exit(1); + } + config_verbose = config[0] - '0'; + // config_testframes = config[1] - '0'; + config_ldpc_en = config[2] - '0'; + // config_log_payload_syms = config[3] - '0'; + config_profile = config[4] - '0'; + fclose(fcfg); + + PROFILE_VAR(ofdm_mod_start); + if (config_profile) machdep_profile_init(); + + struct OFDM_CONFIG *ofdm_config; + + ofdm = ofdm_create(NULL); + assert(ofdm != NULL); + + /* Get a copy of the actual modem config */ + ofdm_config = ofdm_get_config_param(ofdm); + + ldpc_codes_setup(&ldpc, "HRA_112_112"); + + Nbitsperframe = ofdm_get_bits_per_frame(ofdm); + int Ndatabitsperframe; + if (config_ldpc_en) { + Ndatabitsperframe = ldpc.data_bits_per_frame; + } else { + Ndatabitsperframe = + ofdm_get_bits_per_frame(ofdm) - ofdm->nuwbits - ofdm->ntxtbits; + } + + Nsamperframe = ofdm_get_samples_per_frame(ofdm); + // int ofdm_nuwbits = (ofdm_config->ns - 1) * ofdm_config->bps - + // ofdm_config->txtbits; + + if (config_verbose) { + ofdm_set_verbose(ofdm, config_verbose); + fprintf(stderr, "Nsamperframe: %d, Nbitsperframe: %d \n", Nsamperframe, + Nbitsperframe); + } + + int ofdm_ntxtbits = ofdm_config->txtbits; + + uint8_t tx_bits_char[Ndatabitsperframe]; + int16_t tx_scaled[Nsamperframe]; + uint8_t txt_bits_char[ofdm_ntxtbits]; + + for (i = 0; i < ofdm_ntxtbits; i++) { + txt_bits_char[i] = 0; + } + + if (config_verbose) { + ofdm_print_info(ofdm); + } + + int sin = open("stm_in.raw", O_RDONLY); + if (sin < 0) { + printf("Error opening input file\n"); + exit(1); + } + + int sout = open("mod.raw", O_WRONLY | O_TRUNC | O_CREAT, 0666); + if (sout < 0) { + printf("Error opening output file\n"); + exit(1); + } + + while (read(sin, tx_bits_char, sizeof(char) * Ndatabitsperframe) == + Ndatabitsperframe) { + fprintf(stderr, "Frame %d\n", frame); - ofdm = ofdm_create(NULL); - assert(ofdm != NULL); - - /* Get a copy of the actual modem config */ - ofdm_config = ofdm_get_config_param(ofdm); - - ldpc_codes_setup(&ldpc, "HRA_112_112"); - - Nbitsperframe = ofdm_get_bits_per_frame(ofdm); - int Ndatabitsperframe; - if (config_ldpc_en) { - Ndatabitsperframe = ldpc.data_bits_per_frame; - } else { - Ndatabitsperframe = ofdm_get_bits_per_frame(ofdm) - ofdm->nuwbits - ofdm->ntxtbits; - } - - Nsamperframe = ofdm_get_samples_per_frame(ofdm); -// int ofdm_nuwbits = (ofdm_config->ns - 1) * ofdm_config->bps - ofdm_config->txtbits; - - if (config_verbose) { - ofdm_set_verbose(ofdm, config_verbose); - fprintf(stderr, "Nsamperframe: %d, Nbitsperframe: %d \n", Nsamperframe, Nbitsperframe); - } - - int ofdm_ntxtbits = ofdm_config->txtbits; - - uint8_t tx_bits_char[Ndatabitsperframe]; - int16_t tx_scaled[Nsamperframe]; - uint8_t txt_bits_char[ofdm_ntxtbits]; - - for(i=0; i< ofdm_ntxtbits; i++) { - txt_bits_char[i] = 0; - } - - if (config_verbose) { - ofdm_print_info(ofdm); - } - - int sin = open("stm_in.raw", O_RDONLY); - if (sin < 0) { - printf("Error opening input file\n"); - exit(1); - } - - int sout = open("mod.raw", O_WRONLY|O_TRUNC|O_CREAT, 0666); - if (sout < 0) { - printf("Error opening output file\n"); - exit(1); + if (config_profile) { + PROFILE_SAMPLE(ofdm_mod_start); } - while (read(sin, tx_bits_char, sizeof(char) * Ndatabitsperframe) == Ndatabitsperframe) { - fprintf(stderr, "Frame %d\n", frame); - - if (config_profile) { PROFILE_SAMPLE(ofdm_mod_start); } - - if (config_ldpc_en) { - - complex float tx_sams[Nsamperframe]; - ofdm_ldpc_interleave_tx(ofdm, &ldpc, tx_sams, tx_bits_char, txt_bits_char); + if (config_ldpc_en) { + complex float tx_sams[Nsamperframe]; + ofdm_ldpc_interleave_tx(ofdm, &ldpc, tx_sams, tx_bits_char, + txt_bits_char); - for(i=0; i=3) { - fprintf(stderr, "\ntx_bits:\n"); - for (i = 0; i < Nbitsperframe; i++) { - fprintf(stderr, " %3d %8d\n", i, tx_bits[i]); - } - } + if (config_verbose >= 3) { + fprintf(stderr, "\ntx_bits:\n"); + for (i = 0; i < Nbitsperframe; i++) { + fprintf(stderr, " %3d %8d\n", i, tx_bits[i]); + } + } - COMP tx_sams[Nsamperframe]; - ofdm_mod(ofdm, tx_sams, tx_bits); + COMP tx_sams[Nsamperframe]; + ofdm_mod(ofdm, tx_sams, tx_bits); - if (config_verbose >=3) { - fprintf(stderr, "\ntx_sams:\n"); - for (i = 0; i < Nsamperframe; i++) { - fprintf(stderr, " %3d % f\n", i, (double)tx_sams[i].real); - } - } + if (config_verbose >= 3) { + fprintf(stderr, "\ntx_sams:\n"); + for (i = 0; i < Nsamperframe; i++) { + fprintf(stderr, " %3d % f\n", i, (double)tx_sams[i].real); + } + } - for(i=0; i pkg load signal +# octave:24> time_secs=60*20 +# octave:26> ch_fading("~/codec2/build_linux/unittest/fast_fading_samples.float", 8000, 1.0, 8000*time_secs) +# +# 2. Run scripts: +# +# $ make + +SHELL := /bin/bash +CODEC2 := $(HOME)/codec2 + +all: test \ + octave_ch_noise_awgn.png octave_c_tx_awgn.png octave_c_tx_comp_awgn.png \ + octave_ch_noise_mpp.png octave_c_tx_mpp.png octave_c_tx_comp_mpp.png \ + snrest_snr_ctx.png snrest_snr_ctxc.png \ + c_tx_comp.png c_tx_comp_thruput.png + +clean: + rm -f *.txt *.png *.raw + +# run this first, traps common CML setup error +test: + source snr_curves.sh; test_ldpc + +# subset of files generated, but enough to set up Makefile dependencies +snr_oct = snr_oct_datac0_awgn.txt snr_oct_datac1_awgn.txt snr_oct_datac3_awgn.txt +snr_ch = snr_ch_datac0_awgn.txt snr_ch_datac1_awgn.txt snr_ch_datac3_awgn.txt +snr_ctx = snr_ctx_datac0_awgn.txt snr_ctx_datac1_awgn.txt snr_ctx_datac3_awgn.txt +snr_ctxc = snr_ctxc_datac0_awgn.txt snr_ctxc_datac3_awgn.txt + +snr_oct_mpp = snr_oct_datac0_mpp.txt snr_oct_datac1_mpp.txt snr_oct_datac3_mpp.txt +snr_ch_mpp = snr_ch_datac0_mpp.txt snr_ch_datac1_mpp.txt snr_ch_datac3_mpp.txt +snr_ctx_mpp = snr_ctx_datac0_mpp.txt snr_ctx_datac1_mpp.txt snr_ctx_datac3_mpp.txt +snr_ctxc_mpp = snr_ctxc_datac0_mpp.txt snr_ctxc_datac3_mpp.txt + +$(snr_oct): + source snr_curves.sh; generate_octave_tx_data datac0 awgn + source snr_curves.sh; generate_octave_tx_data datac1 awgn + source snr_curves.sh; generate_octave_tx_data datac3 awgn + +$(snr_oct_mpp): + source snr_curves.sh; generate_octave_tx_data datac0 mpp + source snr_curves.sh; generate_octave_tx_data datac1 mpp + source snr_curves.sh; generate_octave_tx_data datac3 mpp + +$(snr_ch): + source snr_curves.sh; generate_ch_data datac0 awgn + source snr_curves.sh; generate_ch_data datac1 awgn + source snr_curves.sh; generate_ch_data datac3 awgn + +$(snr_ch_mpp): + source snr_curves.sh; generate_ch_data datac0 mpp + source snr_curves.sh; generate_ch_data datac1 mpp + source snr_curves.sh; generate_ch_data datac3 mpp + +# C without compression + +$(snr_ctx): + source snr_curves.sh; generate_snrest_v_snr_data datac0 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac1 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac3 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac4 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac13 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac14 awgn + +$(snr_ctx_mpp): + source snr_curves.sh; generate_snrest_v_snr_data datac0 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac1 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac3 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac4 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac13 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac14 mpp + +# C with compression + +$(snr_ctxc): + source snr_curves.sh; generate_snrest_v_snr_data datac0 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac1 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac3 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac4 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac13 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac14 awgn 1 + +$(snr_ctxc_mpp): + source snr_curves.sh; generate_snrest_v_snr_data datac0 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac1 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac3 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac4 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac13 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac14 mpp 1 + +# Octave and C curves should be on top of each other, indicating Octave +# and ch noise injection/SNR measurement are equivalent +octave_ch_noise_awgn.png: $(snr_oct) $(snr_ch) + echo "snr_curves_plot; octave_ch_noise_print('awgn'); quit" | \ + octave-cli -p $(CODEC2)/octave +octave_ch_noise_mpp.png: $(snr_oct_mpp) $(snr_ch_mpp) + echo "snr_curves_plot; octave_ch_noise_print('mpp'); quit" | \ + octave-cli -p $(CODEC2)/octave + +# Octave Tx and C Tx curves should be on top of each other +octave_c_tx_awgn.png: $(snr_oct) $(snr_ctx) + echo "snr_curves_plot; octave_c_tx_print('awgn'); quit" | \ + octave-cli -p $(CODEC2)/octave +octave_c_tx_mpp.png: $(snr_oct_mpp) $(snr_ctx_mpp) + echo "snr_curves_plot; octave_c_tx_print('mpp'); quit" | \ + octave-cli -p $(CODEC2)/octave + +# Octave Tx and C Tx (compressed) curves should be close, but C may be 1dB +# poorer +octave_c_tx_comp_awgn.png: $(snr_oc) $(snr_ctxc) + echo "snr_curves_plot; octave_c_tx_comp_print('awgn'); quit" | \ + octave-cli -p $(CODEC2)/octave +octave_c_tx_comp_mpp.png: $(snr_oct_mpp) $(snr_ctxc_mpp) + echo "snr_curves_plot; octave_c_tx_comp_print('mpp'); quit" | \ + octave-cli -p $(CODEC2)/octave + +# combined AWGN and MPP from C Tx (compressed) - what end users would run +c_tx_comp.png: $(snr_ctxc) $(snr_ctxc_mpp) + echo "snr_curves_plot; c_tx_comp_print; quit" | \ + octave-cli -p $(CODEC2)/octave + +# Curves of SNR estimates from C Rx compared to actual SNR, useful for "gear shifting" +snrest_snr_ctx.png: $(snr_ctx) + echo "snr_curves_plot; snrest_snr_print('ctx', 'awgn'); quit" | \ + octave-cli -p $(CODEC2)/octave +snrest_snr_ctxc.png: $(snr_ctxc) + echo "snr_curves_plot; snrest_snr_print('ctxc', 'awgn'); quit" | \ + octave-cli -p $(CODEC2)/octave + +# Throughput of payload data in bits/s of modes against SNR +c_tx_comp_thruput.png: $(snr_ctxc) $(snr_ctxc_mpp) + echo "snr_curves_plot; c_tx_comp_thruput_print; quit" | \ + octave-cli -p $(CODEC2)/octave diff --git a/unittest/raw_data_curves/snr_curves.sh b/unittest/raw_data_curves/snr_curves.sh new file mode 100755 index 00000000..62f0a52f --- /dev/null +++ b/unittest/raw_data_curves/snr_curves.sh @@ -0,0 +1,191 @@ +# snr_curves.sh +# +# Library of bash functions to generate data for SNR curves. +# +# testing a function example: +# $ bash -c "source ./snr_curves.sh; generate_octave_tx_data datac0 awgn" + +set -x + +PATH=${PATH}:${HOME}/codec2/build_linux/src +CODEC2=${HOME}/codec2 +FADING_DIR=${CODEC2}/build_linux/unittest + +snr_list='-5 -4 -3 -2 0 1 2 4' +No_list='-13 -14 -15 -16 -18 -20 -22 -24 -26' +Nbursts_awgn=20 +Nbursts_mpp=100 + +# Octave Tx injects noise and is source of truth for SNR, measure BER/PER v SNR +function generate_octave_tx_data { + mode=$1 + channel=$2 + + Nbursts=$Nbursts_awgn + snr_nudge=0 + if [ "$channel" == "mpp" ]; then + Nbursts=$Nbursts_mpp + snr_nudge=4 + fi + + rx_log=$(mktemp) + + i=1 + rm -f snr_oct_${mode}_${channel}*.txt + rm -f ber_oct_${mode}_${channel}*.txt + rm -f per_oct_${mode}_${channel}*.txt + for snr in $snr_list + do + snr_adj=$((${snr}+${snr_nudge})) + echo "warning ('off', 'Octave:data-file-in-path'); + ofdm_ldpc_tx('test_${mode}.raw','${mode}',1,${snr_adj},'${channel}','bursts',${Nbursts},'crc'); + quit" | DISPLAY="" octave-cli -p ${CODEC2}/octave + freedv_data_raw_rx --testframes $mode test_${mode}.raw /dev/null 2>${rx_log} -v + BERmeas=$(cat ${rx_log} | grep 'BER......:' | cut -d' ' -f2) + PERmeas=$(cat ${rx_log} | grep 'Coded FER' | cut -d' ' -f3) + + echo ${snr_adj} >> snr_oct_${mode}_${channel}.txt + echo ${BERmeas} >> ber_oct_${mode}_${channel}.txt + echo ${PERmeas} >> per_oct_${mode}_${channel}.txt + i=$((i+1)) + done + echo 0 > offset_oct_${mode}_${channel}.txt +} + +# ch injects noise and is source of truth for SNR, measure BER/PER v SNR +# Octave Tx +function generate_ch_data { + mode=$1 + channel=$2 + + ch_multipath='' + Nbursts=$Nbursts_awgn + snr_nudge=0 + if [ "$channel" == "mpp" ]; then + ch_multipath='--mpp' + Nbursts=$Nbursts_mpp + snr_nudge=4 + fi + + octave_log=$(mktemp) + ch_log=$(mktemp) + rx_log=$(mktemp) + + i=1 + rm -f snr_ch_${mode}_${channel}*.txt + rm -f ber_ch_${mode}_${channel}*.txt + rm -f per_ch_${mode}_${channel}*.txt + for No in $No_list + do + No_adj=$((${No}-${snr_nudge})) + echo "warning ('off', 'Octave:data-file-in-path'); + ofdm_ldpc_tx('test_${mode}.raw','${mode}',1,100,'awgn','bursts',${Nbursts},'crc'); + quit" | DISPLAY="" octave-cli -p ${CODEC2}/octave 1>${octave_log} + SNRoffset=$(cat ${octave_log} | grep 'Burst offset:' | cut -d' ' -f5) + + ch test_${mode}.raw - --No $No_adj ${ch_multipath} --fading_dir ${FADING_DIR} 2>>${ch_log} | \ + freedv_data_raw_rx --testframes $mode - /dev/null -v 2>${rx_log} + BERmeas=$(cat ${rx_log} | grep 'BER......:' | cut -d' ' -f2) + PERmeas=$(cat ${rx_log} | grep 'Coded FER' | cut -d' ' -f3) + + echo ${BERmeas} >> ber_ch_${mode}_${channel}.txt + echo ${PERmeas} >> per_ch_${mode}_${channel}.txt + i=$((i+1)) + + # trap not enough fading file samples (with mpp) + grep "Fading file finished" ${ch_log} + if [ $? -eq 0 ]; then + cat ${ch_log} + exit 1 + fi + done + + echo ${SNRoffset} > offset_ch_${mode}_${channel}.txt + SNRch=$(cat ${ch_log} | grep SNR3k | tr -s ' ' | cut -d' ' -f3) + echo ${SNRch} > snr_ch_${mode}_${channel}.txt +} + +# ch injects noise and is source of truth for SNR, measure BER/PER v SNR and +# SNR estimates v SNR from rx, C Tx +function generate_snrest_v_snr_data { + mode=$1 + channel=$2 + + snr_nudge=0 + aNo_list=$No_list + + # nudge SNR test range to get meaningful results for these tests + if [ "$mode" == "datac1" ]; then + snr_nudge=4 + fi + if [[ "$mode" == "datac4" || "$mode" == "datac13" || "$mode" == "datac14" ]]; then + snr_nudge=-6 + fi + + ch_multipath='' + Nbursts=$Nbursts_awgn + if [ "$channel" == "mpp" ]; then + ch_multipath='--mpp' + Nbursts=$Nbursts_mpp + snr_nudge=$((${snr_nudge}+4)) + fi + + clip=0 + id='ctx' + if [ "$#" -eq 3 ]; then + clip=$3 + id='ctxc' + snr_nudge=$((${snr_nudge}-4)) + fi + + tx_log=$(mktemp) + ch_log=$(mktemp) + rx_log=$(mktemp) + + i=1 + rm -f snrest_${id}_${mode}_${channel}*.txt + rm -f ber_${id}_${mode}_${channel}*.txt + rm -f per_${id}_${mode}_${channel}*.txt + for No in $aNo_list + do + No_adj=$((${No}-${snr_nudge})) + freedv_data_raw_tx --clip ${clip} --delay 1000 --txbpf ${clip} --bursts $Nbursts --testframes $Nbursts $mode /dev/zero - 2>${tx_log} | \ + ch - - --No $No_adj ${ch_multipath} --fading_dir ${FADING_DIR} 2>>${ch_log} | \ + freedv_data_raw_rx --testframes $mode - /dev/null 2>${rx_log} -v + SNRoffset=$(cat ${tx_log} | grep "mark:space" | tr -s ' ' | cut -d' ' -f 5) + + SNRest=$(cat ${rx_log} | grep '\-BS\-' | tr -s ' ' | cut -d' ' -f17) + if [ ! -z "$SNRest" ]; then + echo ${SNRest} > snrest_${id}_${mode}_${channel}_${i}.txt + fi + BERmeas=$(cat ${rx_log} | grep 'BER......:' | cut -d' ' -f2) + PERmeas=$(cat ${rx_log} | grep 'Coded FER' | cut -d' ' -f3) + echo ${BERmeas} >> ber_${id}_${mode}_${channel}.txt + echo ${PERmeas} >> per_${id}_${mode}_${channel}.txt + i=$((i+1)) + done + + echo ${SNRoffset} > offset_${id}_${mode}_${channel}.txt + + # trap not enough fading file samples (with mpp) + grep "Fading file finished" ${ch_log} + if [ $? -eq 0 ]; then + cat ${ch_log} + exit 1 + fi + SNRch=$(cat ${ch_log} | grep SNR3k | tr -s ' ' | cut -d' ' -f3) + echo ${SNRch} > snr_${id}_${mode}_${channel}.txt +} + +# Sanity check to make sure Octave/CML is set up OK +function test_ldpc { + echo "ldpcut; quit" | DISPLAY="" octave-cli -p ${CODEC2}/octave + if [ "$?" -ne 0 ]; then + echo "basic octave test failed, you may need to" + echo "(a) run ctests to create build_xxx/cml" + echo "(b) set up ~/.octaverc as per octave/ldpc.m" + exit 1 + else + echo "OK" + fi +} diff --git a/unittest/tofdm.c b/unittest/tofdm.c index b4d60b75..a6b139f8 100644 --- a/unittest/tofdm.c +++ b/unittest/tofdm.c @@ -324,8 +324,8 @@ int main(int argc, char *argv[]) { for (i = 0; i < ofdm_ntxtbits; i++) txt_bits[i] = 0; uint8_t tx_bits_char[ofdm_bitsperframe]; - ofdm_assemble_qpsk_modem_packet(ofdm, tx_bits_char, payload_bits, - txt_bits); + ofdm_assemble_psk_modem_packet(ofdm, tx_bits_char, payload_bits, + txt_bits); for (i = 0; i < ofdm_bitsperframe; i++) tx_bits[i] = tx_bits_char[i]; } @@ -475,8 +475,8 @@ int main(int argc, char *argv[]) { float *ldpc_codeword_symbol_amps = &ofdm->rx_amp[(ofdm_nuwbits + ofdm_ntxtbits) / ofdm_bps]; - Demod2D(symbol_likelihood, ldpc_codeword_symbols, S_matrix, EsNo, - ldpc_codeword_symbol_amps, ofdm->mean_amp, + Demod2D(symbol_likelihood, ldpc_codeword_symbols, S_matrix, 1 << ofdm_bps, + EsNo, ldpc_codeword_symbol_amps, ofdm->mean_amp, CODED_BITSPERFRAME / ofdm_bps); Somap(bit_likelihood, symbol_likelihood, 1 << ofdm_bps, ofdm_bps, CODED_BITSPERFRAME / ofdm_bps); diff --git a/unittest/tqam16.c b/unittest/tqam16.c index 9e4c8d02..04971d3d 100644 --- a/unittest/tqam16.c +++ b/unittest/tqam16.c @@ -19,7 +19,7 @@ int main(void) { int tx_bits[4], rx_bits[4]; for (int i = 0; i < 4; i++) tx_bits[i] = (c >> (3 - i)) & 0x1; complex float symbol = qam16_mod(tx_bits); - qam16_demod(symbol, rx_bits); + qam16_demod(symbol, rx_bits, 1.0); if (memcmp(tx_bits, rx_bits, 4)) { fprintf(stderr, "FAIL on %d!\ntx_bits: ", c); for (int i = 0; i < 4; i++) fprintf(stderr, "%d ", tx_bits[i]);