codec2/octave/ofdm_helper.m

274 lines
8.6 KiB
Matlab

% ofdm_helper.m
%
% Misc functions that are used to support OFDM modem development, that
% aren't required for modem operation
1;
%------------------------------------------------------------------------------
% print_config - utility function to use ascii-art to describe the modem frame
%------------------------------------------------------------------------------
function print_config(states)
ofdm_load_const;
% ASCII-art packet visualisation
s=1; u=1; Nuwsyms=length(uw_ind_sym);
cr = 1:Nc+2;
for f=1:Np
for r=1:Ns
for c=cr
if r == 1
if (c==1) && states.edge_pilots
sym="P";
elseif (c==Nc+1) && states.edge_pilots
sym="P";
elseif c>1 && c <=(Nc+1)
sym="P";
else
sym=" ";
end
elseif c>1 && c <=(Nc+1)
sym=".";
if (u <= Nuwsyms) && (s == uw_ind_sym(u)) sym="U"; u++; end
s++;
else
sym=" ";
end
printf("%s",sym);
end
printf("\n");
end
end
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 Duration (incl post/preamble): %4.2f s\n",
Nbitsperpacket*Fs/(Np*Nsamperframe), (Np+2)*Ns*(Tcp+1/Rs));
end
%-----------------------------------------------------------------------
% create_ldpc_test_frame - generate a test frame of bits
%-----------------------------------------------------------------------
function [tx_bits payload_data_bits codeword] = create_ldpc_test_frame(states, coded_frame=1)
ofdm_load_const;
ldpc;
gp_interleaver;
if coded_frame
% Set up LDPC code
mod_order = 4; bps = 2; modulation = 'QPSK'; mapping = 'gray';
init_cml(); % TODO: make this path sensible and portable
load HRA_112_112.txt
[code_param framesize rate] = ldpc_init_user(HRA_112_112, modulation, mod_order, mapping);
assert(Nbitsperframe == (code_param.coded_bits_per_frame + Nuwbits + Ntxtbits));
payload_data_bits = round(ofdm_rand(code_param.data_bits_per_frame)/32767);
codeword = LdpcEncode(payload_data_bits, code_param.H_rows, code_param.P_matrix);
Nsymbolsperframe = length(codeword)/bps;
% need all these steps to get actual raw codeword bits at demod ..
tx_symbols = [];
for s=1:Nsymbolsperframe
tx_symbols = [tx_symbols qpsk_mod( codeword(2*(s-1)+1:2*s) )];
end
tx_symbols = gp_interleave(tx_symbols);
codeword_raw = [];
for s=1:Nsymbolsperframe
codeword_raw = [codeword_raw qpsk_demod(tx_symbols(s))];
end
else
codeword_raw = round(ofdm_rand(Nbitsperpacket-(Nuwbits+Ntxtbits))/32767);
end
% insert UW and txt bits
tx_bits = assemble_modem_packet(states, codeword_raw, zeros(1,Ntxtbits));
assert(Nbitsperpacket == length(tx_bits));
endfunction
% automated test
function test_assemble_disassemble(states)
ofdm_load_const;
Nsymsperpacket = Nbitsperpacket/bps;
Ndatabitsperpacket = Nbitsperpacket-(Nuwbits+Ntxtbits);
Ndatasymsperpacket = Ndatabitsperpacket/bps;
codeword_bits = round(ofdm_rand(Ndatabitsperpacket)/32767);
tx_bits = assemble_modem_packet(states, codeword_bits, zeros(1,Ntxtbits));
tx_syms = zeros(1,Nsymsperpacket);
for s=1:Nsymsperpacket
if bps == 2
tx_syms(s) = qpsk_mod(tx_bits(bps*(s-1)+1:bps*s));
elseif bps == 4
tx_syms(s) = qam16_mod(states.qam16,tx_bits(bps*(s-1)+1:bps*s));
end
end
codeword_syms = zeros(1,Ndatasymsperpacket);
for s=1:Ndatasymsperpacket
if bps == 2
codeword_syms(s) = qpsk_mod(codeword_bits(bps*(s-1)+1:bps*s));
elseif bps == 4
codeword_syms(s) = qam16_mod(states.qam16,codeword_bits(bps*(s-1)+1:bps*s));
end
end
[rx_uw rx_codeword_syms payload_amps txt_bits] = disassemble_modem_packet(states, tx_syms, ones(1,Nsymsperpacket));
assert(rx_uw == states.tx_uw);
Ndatasymsperframe = (Nbitsperpacket-(Nuwbits+Ntxtbits))/bps;
assert(codeword_syms == rx_codeword_syms);
endfunction
% test function, kind of like a CRC for QPSK symbols, to compare two vectors
function acc = test_acc(v)
sre = 0; sim = 0;
for i=1:length(v)
x = v(i);
re = round(real(x)); im = round(imag(x));
sre += re; sim += im;
%printf("%d %10f %10f %10f %10f\n", i, re, im, sre, sim);
end
acc = sre + j*sim;
end
% Save test bits frame to a text file in the form of a C array
%
% usage:
% ofdm_lib; test_bits_ofdm_file
%
function test_bits_ofdm_file
Ts = 0.018; Tcp = 0.002; Rs = 1/Ts; bps = 2; Nc = 17; Ns = 8;
states = ofdm_init(bps, Rs, Tcp, Ns, Nc);
[test_bits_ofdm payload_data_bits codeword] = create_ldpc_test_frame(states);
printf("%d test bits\n", length(test_bits_ofdm));
f=fopen("../src/test_bits_ofdm.h","wt");
fprintf(f,"/* Generated by test_bits_ofdm_file() Octave function */\n\n");
fprintf(f,"const int test_bits_ofdm[]={\n");
for m=1:length(test_bits_ofdm)-1
fprintf(f," %d,\n",test_bits_ofdm(m));
endfor
fprintf(f," %d\n};\n",test_bits_ofdm(end));
fprintf(f,"\nconst int payload_data_bits[]={\n");
for m=1:length(payload_data_bits)-1
fprintf(f," %d,\n",payload_data_bits(m));
endfor
fprintf(f," %d\n};\n",payload_data_bits(end));
fprintf(f,"\nconst int test_codeword[]={\n");
for m=1:length(codeword)-1
fprintf(f," %d,\n",codeword(m));
endfor
fprintf(f," %d\n};\n",codeword(end));
fclose(f);
endfunction
% Get rid of nasty unfiltered stuff either side of OFDM signal
% This may need to be tweaked, or better yet made a function of Nc, if Nc changes
%
% usage:
% ofdm_lib; make_ofdm_bpf(1);
function bpf_coeff = make_ofdm_bpf(write_c_header_file)
filt_n = 100;
Fs = 8000;
bpf_coeff = fir2(filt_n,[0 900 1000 2000 2100 4000]/(Fs/2),[0.001 0.001 1 1 0.001 0.001]);
if write_c_header_file
figure(1)
clf;
h = freqz(bpf_coeff,1,Fs/2);
plot(20*log10(abs(h)))
grid minor
% save coeffs to a C header file
f=fopen("../src/ofdm_bpf_coeff.h","wt");
fprintf(f,"/* 1000 - 2000 Hz FIR filter coeffs */\n");
fprintf(f,"/* Generated by make_ofdm_bpf() in ofdm_lib.m */\n");
fprintf(f,"\n#define OFDM_BPF_N %d\n\n", filt_n);
fprintf(f,"float ofdm_bpf_coeff[]={\n");
for r=1:filt_n
if r < filt_n
fprintf(f, " %f,\n", bpf_coeff(r));
else
fprintf(f, " %f\n};", bpf_coeff(r));
end
end
fclose(f);
end
endfunction
% Helper function to help design UW error thresholds, in particular for raw
% data modes. See also https://www.rowetel.com/wordpress/?p=7467
function ofdm_determine_bad_uw_errors(Nuw)
figure(1); clf;
% Ideally the 10% and 50% BER curves are a long way apart
plot(0:Nuw, binocdf(0:Nuw,Nuw,0.1),';BER=0.1;'); hold on;
plot(binocdf(0:Nuw,Nuw,0.5),';BER=0.5;');
% Suggested threshold for raw data modes is the 5% probability
% level for the 50% BER curve. The pre/post-amble has a low chance
% of failure. If it does make an error, then we will have random
% bits presented as the UW (50% BER in UW). This threshold means
% there is only a 5% case of random bits being accepted as a valid UW
bad_uw_errors = max(find(binocdf(0:Nuw,Nuw,0.5) <= 0.05))+1;
plot([bad_uw_errors bad_uw_errors],[0 1],';bad uw errors;'); hold off; grid
xlabel('bits');
printf("for Nuw = %d, suggest bad_uw_errors = %d\n", Nuw, bad_uw_errors);
end
% Returns level threshold such that threshold_cdf of the tx magnitudes are
% beneath that level. Helper function that can be used to design
% the clipper level. See also https://www.rowetel.com/?p=7596
function threshold_level = ofdm_determine_clip_threshold(tx, threshold_cdf)
Nsteps = 25;
mx = max(abs(tx));
cdf = empirical_cdf(mx*(1:Nsteps)/Nsteps,abs(tx));
threshold_level = find(cdf >= threshold_cdf)(1)*mx/25;
printf("threshold_cdf: %f threshold_level: %f\n", threshold_cdf, threshold_level);
figure(1); clf; [hh nn] = hist(abs(tx),Nsteps,1);
plotyy(nn,hh,mx*(1:Nsteps)/Nsteps,cdf); title('PDF and CDF Estimates'); grid;
end
% helper function that adds channel simulation and ensures we don't saturate int16 output samples
function [rx_real rx] = ofdm_channel(states, tx, SNR3kdB, channel, freq_offset_Hz)
[rx_real rx sigma] = channel_simulate(states.Fs, SNR3kdB, freq_offset_Hz, channel, tx, states.verbose);
% multipath models can lead to clipping of int16 samples
num_clipped = length(find(abs(rx_real>32767)));
while num_clipped/length(rx_real) > 0.001
rx_real /= 2;
num_clipped = length(find(abs(rx_real>32767)));
printf("WARNING: output samples clipped, reducing level\n")
end
endfunction