aboutsummaryrefslogtreecommitdiff
path: root/octave/oqpsk.m
diff options
context:
space:
mode:
Diffstat (limited to 'octave/oqpsk.m')
-rw-r--r--octave/oqpsk.m521
1 files changed, 0 insertions, 521 deletions
diff --git a/octave/oqpsk.m b/octave/oqpsk.m
deleted file mode 100644
index f308617..0000000
--- a/octave/oqpsk.m
+++ /dev/null
@@ -1,521 +0,0 @@
-% oqpsk.m
-% David Rowe Jan 2017
-%
-% Unfiltered OQPSK modem implementation and simulations to test,
-% derived from GMSK modem in gmsk.m
-%
-% Usage: see "choose one of these to run" at the end of this file.
-
-rand('state',1);
-randn('state',1);
-graphics_toolkit ("gnuplot");
-format
-more off;
-
-% init modem states
-
-function oqpsk_states = oqpsk_init(oqpsk_states, Rs)
-
- % general
-
- verbose = oqpsk_states.verbose;
- oqpsk_states.Fs = 4*Rs;
- oqpsk_states.Rs = Rs;
- oqpsk_states.bps = 2; % two bit/symbol for QPSK
-
- M = oqpsk_states.M = oqpsk_states.Fs/oqpsk_states.Rs;
- assert(floor(M) == M, "oversampling factor M must be an integer");
- assert(floor(M/2) == M/2, "(oversampling factor M)/2 must be an integer to offset QPSK");
-endfunction
-
-
-% Gray coded QPSK modulation function
-
-function symbol = qpsk_mod(two_bits)
- two_bits_decimal = sum(two_bits .* [2 1]);
- switch(two_bits_decimal)
- case (0) symbol = 1;
- case (1) symbol = j;
- case (2) symbol = -j;
- case (3) symbol = -1;
- endswitch
-endfunction
-
-
-% Gray coded QPSK demodulation function
-
-function two_bits = qpsk_demod(symbol)
- if isscalar(symbol) == 0
- printf("only works with scalars\n");
- return;
- end
- bit0 = real(symbol*exp(j*pi/4)) < 0;
- bit1 = imag(symbol*exp(j*pi/4)) < 0;
- two_bits = [bit1 bit0];
-endfunction
-
-
-% Unfiltered OQPSK modulator
-
-function [tx tx_symb] = oqpsk_mod(oqpsk_states, tx_bits)
- M = oqpsk_states.M;
- bps = oqpsk_states.bps;
- nsym = length(tx_bits)/bps;
- nsam = nsym*M;
- verbose = oqpsk_states.verbose;
-
- % Map bits to Gray coded QPSK symbols
-
- tx_symb = zeros(1,nsym);
-
- for i=1:nsym
- tx_symb(i) = qpsk_mod(tx_bits(2*i-1:2*i))*exp(j*pi/4);
- end
-
- % Oversample by M (sample and hold) to create unfiltered QPSK
-
- tx = zeros(1, nsam);
- for i=1:nsym
- tx((i-1)*M+1:(i*M)) = tx_symb(i);
- end
-
- % delay Q arm by half of a symbol to make OQPSK
-
- tx = [real(tx) zeros(1,M/2)] + j*[zeros(1,M/2) imag(tx)];
-endfunction
-
-
-#{
-
- Unfiltered OQPSK demodulator function, with (optional) phase and
- timing estimation. Adapted from Fig 8 of [1]. See also gmsk.m and
- [2].
-
- Note demodulator returns phase corrected symbols sampled at ideal
- timing instant. These symbols may have a m*pi/2 phase ambiguity due
- to properties of phase tracking loop. The caller is responsible for
- determining this ambiguity and recovering the actual bits.
-
- [1] GMSK demodulator in IEEE Trans on Comms, Muroyta et al, 1981,
- "GSM Modulation for Digital Radio Telephony".
-
- [2] GMSK Modem Simulation, http://www.rowetel.com/?p=3824
-
-#}
-
-
-function [rx_symb rx_int filt_log dco_log timing_adj Toff] = oqpsk_demod(oqpsk_states, rx)
- M = oqpsk_states.M;
- Rs = oqpsk_states.Rs;
- Fs = oqpsk_states.Fs;
- nsam = length(rx);
- nsym = floor(nsam/M);
- verbose = oqpsk_states.verbose;
-
- timing_angle_log = zeros(1,length(rx));
- rx_int = zeros(1,length(rx));
- dco_log = filt_log = zeros(1,nsam);
-
- % Unfiltered PSK - integrate energy in symbols M long in re and im arms
-
- rx_int = conv(rx,ones(1,M))/M;
-
- % phase and fine frequency tracking and correction ------------------------
-
- if oqpsk_states.phase_est
-
- % DCO design from "Introduction To Phase-Lock Loop System Modeling", Wen Li
- % http://www.ece.ualberta.ca/~ee401/parts/data/PLLIntro.pdf
-
- eta = 0.707;
- wn = 2*pi*10*(Rs/4800); % (Rs/4800) -> found reducing the BW beneficial with falling Rs
- Ts = 1/Fs;
- g1 = 1 - exp(-2*eta*wn*Ts);
- g2 = 1 + exp(-2*eta*wn*Ts) - 2*exp(-eta*wn*Ts)*cos(wn*Ts*sqrt(1-eta*eta));
- Gpd = 2/pi;
- Gvco = 1;
- G1 = g1/(Gpd*Gvco); G2 = g2/(Gpd*Gvco);
- %printf("g1: %e g2: %e G1: %e G2: %e\n", g1, g2, G1, G2);
-
- filt_prev = dco = lower = ph_err_filt = ph_err = 0;
- end
-
- if oqpsk_states.timing_est
- % w is the ref sine wave at the timing clock frequency
- % tw is the length of the window used to estimate timing
-
- tw = 200*M;
- k = 1;
- xr_log = []; xi_log = [];
- w_log = [];
- timing_clock_phase = 0;
- timing_angle = 0;
- timing_angle_log = zeros(1,nsam);
- end
-
- % Sample by sample processing loop for timing and phase est. Note
- % this operates at sample rate Fs, unlike many PSK modems that
- % operate at the symbol rate Rs
-
- for i=1:nsam
-
- if oqpsk_states.timing_est
-
- % update sample timing estimate every tw samples, free wheel
- % rest of the time
-
- if mod(i,tw) == 0
- l = i - tw+1;
- xr = abs(real(rx_int(l:l+tw-1)));
- xi = abs(imag(rx_int(l:l+tw-1)));
- w = exp(j*(l:l+tw-1)*2*pi*Rs/Fs);
- X = xr * w';
- timing_clock_phase = timing_angle = angle(X);
- k++;
- xr_log = [xr_log xr];
- xi_log = [xi_log xi];
- w_log = [w_log w];
- else
- timing_clock_phase += (2*pi)/M;
- end
- timing_angle_log(i) = timing_angle;
- end
-
- if oqpsk_states.phase_est
-
- % PLL per-sample processing
-
- rx_int(i) *= exp(-j*dco);
- ph_err = sign(real(rx_int(i))*imag(rx_int(i)))*cos(timing_clock_phase);
- lower = ph_err*G2 + lower;
- filt = ph_err*G1 + lower;
- dco_log(i) = dco;
- dco = dco + filt;
- filt_log(i) = filt;
-
- end
-
- end
-
- % final adjustment of timing output to take into account slowly
- % moving estimates due to sample clock offset. Unwrap ensures that
- % when timing angle jumps from -pi to pi we move to the next symbol
- % and frame sync isn't broken
-
- timing_adj = timing_angle_log*M/(2*pi);
- timing_adj_uw = unwrap(timing_angle_log)*M/(2*pi);
- % Toff = floor(2*M+timing_adj);
- Toff = floor(timing_adj_uw+0.5);
-
- % sample integrator output at correct timing instant
-
- k = 1;
- re_syms = im_syms = zeros(1,nsym);
- rx_symb = [];
- for i=M:M:nsam
- if i-Toff(i)+M/2 <= nsam
- re_syms(k) = real(rx_int(i-Toff(i)));
- im_syms(k) = imag(rx_int(i-Toff(i)+M/2));
- %re_syms(k) = real(rx_int(i));
- %im_syms(k) = imag(rx_int(i));
- rx_symb = [rx_symb re_syms(k) + j*im_syms(k)];
- k++;
- end
- end
-
-endfunction
-
-
-% Test modem over a range Eb/No points in an AWGN channel. Can
-% simulate a variety of channel impairments and performs ambiguity
-% resolution.
-
-function sim_out = oqpsk_test(sim_in)
- bitspertestframe = sim_in.bitspertestframe;
- nbits = sim_in.nbits;
- EbNodB = sim_in.EbNodB;
- verbose = sim_in.verbose;
- Rs = 4800;
-
- oqpsk_states.verbose = verbose;
- oqpsk_states.coherent_demod = sim_in.coherent_demod;
- oqpsk_states.phase_est = sim_in.phase_est;
- oqpsk_states.timing_est = sim_in.timing_est;
- oqpsk_states = oqpsk_init(oqpsk_states, Rs);
- M = oqpsk_states.M;
- Fs = oqpsk_states.Fs;
- Rs = oqpsk_states.Rs;
- sample_clock_offset_ppm = sim_in.sample_clock_offset_ppm;
-
- tx_testframe = round(rand(1, bitspertestframe));
- ntestframes = floor(nbits/bitspertestframe);
- tx_bits = [];
- for i=1:ntestframes
- tx_bits = [tx_bits tx_testframe];
- end
-
- for ne = 1:length(EbNodB)
- aEbNodB = EbNodB(ne);
- EbNo = 10^(aEbNodB/10);
- variance = Fs/(Rs*EbNo*oqpsk_states.bps);
-
- [tx tx_symb] = oqpsk_mod(oqpsk_states, tx_bits);
- if sample_clock_offset_ppm
- tx = resample(tx, 1E6, 1E6-sample_clock_offset_ppm);
- end
- nsam = length(tx);
-
- phi = sim_in.phase_offset + 2*pi*sim_in.freq_offset*(1:nsam)/M;
-
- noise = sqrt(variance/2)*(randn(1,nsam) + j*randn(1,nsam));
- st = 1+sim_in.timing_offset; en = length(tx);
- rx = tx(st:en).*exp(j*phi(st:en)) + noise(st:en);
-
- [rx_symb rx_int filt_log dco_log timing_adj Toff] = oqpsk_demod(oqpsk_states, rx);
-
- % OK so the phase and timing estimators get us close (e.g. a good
- % scatter diagram), but no banana just yet. One problem is the
- % PLL can lock up on mulitples of pi/2. Combinations of phase
- % offsets can confuse the timing estimator. One tricky example is a
- % phase offset of pi/2 which swaps I & Q, and with OQPSK (unlike
- % MSK and friends) we can't easily tell which is I and which is Q
- % after a phase rotation, e.g. could be IQIQIQI or QIQIQIQ
-
- % So we need to determine the ambiguities:
- % a) could be m*pi/2 rotations of phase
- % b) could be I and Q swapped by timing est
- % c) time alignment of test frame
-
- nsymb = bitspertestframe/oqpsk_states.bps;
- nrx_symb = length(rx_symb);
- rx_bits = zeros(1, bitspertestframe);
- atx_symb = tx_symb(1:nsymb);
-
- % Treat I and Q as separate sequences, each with their own unique
- % word. In our case the UW is the whole test frame. Correlate rx
- % sequence with tx sequence at each possible offset through the
- % received symbols to find the test frames. Note we also
- % correlate I of tx with Q of rx to trap any IQ swaps.
-
- % The sign of the I and Q correlation lets us sort out the pi/2
- % phase rotation issue.
-
- nerrs_tot = 0; nbits_tot = 0;
-
- max_corr = real(atx_symb) * real(atx_symb)';
- for offset=2:nrx_symb-nsymb+1
- corr_ii(offset) = real(atx_symb) * real(rx_symb(offset:offset+nsymb-1))'/max_corr;
- corr_qq(offset) = imag(atx_symb) * imag(rx_symb(offset:offset+nsymb-1))'/max_corr;
- corr_iq(offset) = real(atx_symb) * imag(rx_symb(offset:offset+nsymb-1))'/max_corr;
- corr_qi(offset) = imag(atx_symb) * real(rx_symb(offset:offset+nsymb-1))'/max_corr;
- %printf("offset: %2d ii: % 5f qq: % 5f iq: % 5f qi: % 5f\n",
- %offset, corr_ii(offset), corr_qq(offset), corr_iq(offset), corr_qi(offset));
-
- if abs(corr_ii(offset)) > 0.8
-
- % no IQ swap, or time offset
-
- i_sign = sign(corr_ii(offset));
- q_sign = sign(corr_qq(offset));
- arx_symb = i_sign*real(rx_symb(offset:offset+nsymb-1)) + j*q_sign*imag(rx_symb(offset:offset+nsymb-1));
-
- for i=1:nsymb
- rx_bits(2*i-1:2*i) = qpsk_demod(arx_symb(i)*exp(-j*pi/4));
- end
- nerrs = sum(xor(tx_testframe, rx_bits));
- if verbose > 2
- printf("offset: %5d swap: %d i_sign: % 2.1f q_sign: % 2.1f nerr: %d\n",
- offset, 0, i_sign, q_sign, nerrs);
- end
- nerrs_tot += nerrs;
- nbits_tot += bitspertestframe;
- end
-
- if abs(corr_qi(offset)) > 0.8
-
- % IQ swap, I part in Q part of symbol before
-
- i_sign = sign(corr_iq(offset-1));
- q_sign = sign(corr_qi(offset));
- arx_symb = i_sign*imag(rx_symb(offset-1:offset+nsymb-2)) + j*q_sign*real(rx_symb(offset:offset+nsymb-1));
-
- for i=1:nsymb
- rx_bits(2*i-1:2*i) = qpsk_demod(arx_symb(i)*exp(-j*pi/4));
- end
- nerrs = sum(xor(tx_testframe, rx_bits));
- if verbose > 1
- printf("offset: %5d swap: %d i_sign: % 2.1f q_sign: % 2.1f nerr: %d\n",
- offset, 1, i_sign, q_sign, nerrs);
- end
- nerrs_tot += nerrs;
- nbits_tot += bitspertestframe;
- end
- end
-
- TERvec(ne) = nerrs_tot;
- BERvec(ne) = nerrs_tot/nbits_tot;
-
- if verbose > 0
- printf("EbNo dB: %3.1f Nbits: %d Nerrs: %d BER: %4.3f BER Theory: %4.3f\n",
- aEbNodB, nbits_tot, nerrs_tot, BERvec(ne), 0.5*erfc(sqrt(EbNo)));
- end
-
- if find(sim_in.plots == 1)
- figure(1); clf;
- subplot(211)
- stem(real(tx))
- title('Tx samples');
- ylabel('Inphase');
- subplot(212)
- stem(imag(tx))
- ylabel('Quadrature');
- end
-
- if find(sim_in.plots == 2)
- figure(2); clf;
- f = fftshift(fft(rx));
- Tx = 20*log10(abs(f));
- plot((1:length(f))*Fs/length(f) - Fs/2, Tx)
- grid;
- title('OQPSK Demodulator Input Spectrum');
- end
-
- if find(sim_in.plots == 3)
- figure(3); clf;
- nplot = min(16, nbits/oqpsk_states.bps);
- title('Rx Integrator');
- subplot(211)
- stem(real(rx_int(1:nplot*M)))
- axis([1 nplot*M -1 1])
- subplot(212)
- stem(imag(rx_int(1:nplot*M)))
- axis([1 nplot*M -1 1])
- end
-
- if find(sim_in.plots == 4)
- figure(4); clf;
- subplot(211);
- plot(filt_log);
- title('PLL filter')
- subplot(212);
- plot(dco_log);
- title('PLL DCO phase');
- end
-
- if find(sim_in.plots == 5)
- figure(5); clf;
- subplot(211)
- plot(timing_adj);
- title('Timing est');
- subplot(212)
- plot(Toff);
- title('Timing est unwrap');
- end
-
- if find(sim_in.plots == 6)
- figure(6); clf;
- st = floor(0.5*nrx_symb);
- plot(rx_symb(st:nrx_symb), '+');
- title('Scatter Diagram');
- axis([-1.5 1.5 -1.5 1.5])
- end
-
- if find(sim_in.plots == 7)
- figure(7); clf;
- subplot(211)
- plot(corr_ii);
- axis([1 length(corr_ii) -1.2 1.2]);
- title('corr ii');
- subplot(212)
- plot(corr_qi);
- axis([1 length(corr_ii) -1.2 1.2]);
- title('corr qi');
- end
-
- if find(sim_in.plots == 8)
- figure(8); clf;
- subplot(211);
- stem(real(arx_symb));
- title('Rx symbols')
- subplot(212);
- stem(imag(arx_symb));
- end
-
- if find(sim_in.plots == 9)
- figure(9); clf;
- subplot(211)
- stem(tx_testframe(1:min(20,length(rx_bits))))
- title('Tx Bits')
- subplot(212)
- stem(rx_bits(1:min(20,length(rx_bits))))
- title('Rx Bits')
- end
- end
-
- sim_out.TERvec = TERvec;
- sim_out.BERvec = BERvec;
- sim_out.Rs = oqpsk_states.Rs;
-endfunction
-
-
-function run_oqpsk_single
- sim_in.coherent_demod = 1;
- sim_in.phase_est = 1;
- sim_in.timing_est = 1;
- sim_in.bitspertestframe = 100;
- sim_in.nbits = 10000;
- sim_in.EbNodB = 4;
- sim_in.verbose = 1;
- sim_in.phase_offset = 3*pi/4; % in radians
- sim_in.timing_offset = 4; % in samples 0..M-1
- sim_in.freq_offset = 0.001; % fraction of Symbol Rate
- sim_in.plots = [1 2 4 5 6 7];
- sim_in.sample_clock_offset_ppm = 100;
-
- sim_out = oqpsk_test(sim_in);
-endfunction
-
-
-% Generate a bunch of BER versus Eb/No curves for various demods
-
-function run_oqpsk_curves
- sim_in.coherent_demod = 1;
- sim_in.EbNodB = 2:8;
- sim_in.verbose = 1;
- sim_in.phase_est = 1;
- sim_in.timing_est = 1;
- sim_in.bitspertestframe = 100;
- sim_in.nbits = 50000;
- sim_in.phase_offset = 3*pi/4; % in radians
- sim_in.timing_offset = 4; % in samples 0..M-1
- sim_in.freq_offset = 0.001; % fraction of Symbol Rate
- sim_in.plots = [];
- sim_in.sample_clock_offset_ppm = 0;
-
- oqpsk_coh = oqpsk_test(sim_in);
-
- Rs = oqpsk_coh.Rs;
- EbNo = 10 .^ (sim_in.EbNodB/10);
- oqpsk_theory.BERvec = 0.5*erfc(sqrt(EbNo));
-
- % BER v Eb/No curves
-
- figure;
- clf;
- semilogy(sim_in.EbNodB, oqpsk_theory.BERvec,'r+-;OQPSK theory;')
- hold on;
- semilogy(sim_in.EbNodB, oqpsk_coh.BERvec,'g+-;OQPSK sim;')
- hold off;
- grid("minor");
- axis([min(sim_in.EbNodB) max(sim_in.EbNodB) 1E-4 1])
- legend("boxoff");
- xlabel("Eb/No (dB)");
- ylabel("Bit Error Rate (BER)")
-endfunction
-
-
-% Choose one of these to run ------------------------------------------
-
-run_oqpsk_single
-%run_oqpsk_curves
-