2022-04-07 15:16:24 +00:00
|
|
|
clear all
|
|
|
|
|
|
|
|
%% Signal parameters
|
2022-04-08 21:49:17 +00:00
|
|
|
file_path = '/opt/dji/collects/2437MHz_30.72MSPS.fc32';
|
2022-04-07 15:16:24 +00:00
|
|
|
sample_rate = 30.72e6; % Collected 2x oversampled
|
|
|
|
critial_sample_rate = 15.36e6; % The actual sample rate for this signal
|
|
|
|
carrier_spacing = 15e3; % Per the LTE spec
|
|
|
|
signal_bandwidth = 10e6; % Per the LTE spec
|
2022-04-09 03:44:21 +00:00
|
|
|
rough_frequency_offset = 7.5e6; % The collected signal is 7.5 MHz off center
|
2022-04-07 15:16:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
%% Read in the IQ samples for a single burst
|
|
|
|
% The burst is found using baudline and its cursor/delta time measurements
|
2022-04-08 21:49:17 +00:00
|
|
|
file_start_time = 0.802225;
|
2022-04-07 15:16:24 +00:00
|
|
|
file_start_sample_idx = uint64(file_start_time * sample_rate);
|
|
|
|
|
|
|
|
burst_duration_time = 0.000721;
|
|
|
|
burst_duration_samples = uint64(burst_duration_time * sample_rate);
|
|
|
|
|
|
|
|
samples = read_complex_floats(file_path, file_start_sample_idx, burst_duration_samples);
|
|
|
|
|
|
|
|
figure(1);
|
|
|
|
plot(10 * log10(abs(fftshift(fft(samples)))));
|
|
|
|
title('Original Samples')
|
|
|
|
|
|
|
|
%% Roughly center the signal of interest
|
|
|
|
% The signal might have been collected at an offset, so remove that offset
|
|
|
|
rotation_vector = exp(1j * 2* pi / sample_rate * (0:length(samples)-1) * rough_frequency_offset);
|
|
|
|
samples = samples .* rotation_vector;
|
|
|
|
|
2022-04-08 20:00:30 +00:00
|
|
|
figure(1000);
|
|
|
|
plot(10 * log10(abs(fftshift(fft(samples)))));
|
|
|
|
title('Original Samples - Freq Shifted')
|
|
|
|
|
2022-04-09 03:45:03 +00:00
|
|
|
orig_fft_size = sample_rate / carrier_spacing;
|
|
|
|
orig_long_cp_len = round(1/192000 * sample_rate);
|
|
|
|
orig_short_cp_len = round(0.0000046875 * sample_rate);
|
|
|
|
|
|
|
|
%% Low pass filter the original samples
|
|
|
|
filter_taps = fir1(200, signal_bandwidth/sample_rate);
|
|
|
|
samples = filter(filter_taps, 1, samples);
|
|
|
|
|
|
|
|
%% Search for the ZC sequence in symbol 4
|
|
|
|
|
|
|
|
% Create the ZC seqeunce
|
|
|
|
zc = create_zc(orig_fft_size, 4);
|
|
|
|
zc = reshape(zc, 1, []); % Reshape to match the samples vector
|
|
|
|
|
|
|
|
% Run a normalized cross correlation searching for the ZC sequence
|
|
|
|
% TODO(8Apr2022): Search through a smaller space (no need to look through everything)
|
|
|
|
scores = zeros(1, length(samples)-length(zc));
|
|
|
|
for idx=1:length(scores)
|
|
|
|
scores(idx) = normalized_xcorr(zc, samples(idx:idx + length(zc) - 1));
|
|
|
|
end
|
|
|
|
|
|
|
|
% Find the highest score value and index
|
2022-04-07 15:16:24 +00:00
|
|
|
[score, index] = max(abs(scores).^2);
|
2022-04-09 03:45:03 +00:00
|
|
|
if (score < 0.7)
|
|
|
|
warning("Correlation score for the first ZC sequence was bad. Might have errors later");
|
2022-04-07 15:16:24 +00:00
|
|
|
end
|
|
|
|
|
2022-04-09 03:45:03 +00:00
|
|
|
% The ZC sequence is the fourth symbol which means there are three FFT windows, one
|
|
|
|
% long cyclic prefix and two short cyclic prefixes before the ZC. But, to keep the
|
|
|
|
% correlation window as small as possible, the cyclic prefix of the ZC is ignored,
|
|
|
|
% so that adds an additional short cyclic prefix of offset
|
|
|
|
zc_seq_offset = (orig_fft_size * 3) + orig_long_cp_len + (orig_short_cp_len * 3);
|
2022-04-07 15:16:24 +00:00
|
|
|
|
2022-04-09 03:45:03 +00:00
|
|
|
% Calculate where the burst starts based on the correlation index
|
|
|
|
start_offset = round(index - zc_seq_offset);
|
2022-04-07 15:16:24 +00:00
|
|
|
|
2022-04-10 15:37:13 +00:00
|
|
|
angle(scores(index))
|
|
|
|
% samples = samples .* exp(1j * -angle(scores(index)) * (0:(length(samples)-1)));
|
|
|
|
samples = samples .* exp(1j * -angle(scores(index)));
|
|
|
|
|
2022-04-09 03:45:03 +00:00
|
|
|
% Trim all samples before the starting offset, and decimate at the same time.
|
|
|
|
% The decimation operation here is to get the signal back down to critical rate.
|
|
|
|
% Can get away with dropping every other sample because the signal is already filtered
|
|
|
|
samples = samples(start_offset:2:end);
|
2022-04-07 15:16:24 +00:00
|
|
|
|
|
|
|
%% Calculate critical rate parameters
|
|
|
|
fft_size = critial_sample_rate / carrier_spacing;
|
|
|
|
long_cp_len = round(0.0000052 * critial_sample_rate);
|
|
|
|
short_cp_len = round(0.00000469 * critial_sample_rate);
|
|
|
|
|
|
|
|
%% Plot symbol overlays
|
|
|
|
% The logic below is for debugging only. It draws OFDM symbol boundaries
|
|
|
|
% over a time domain view of the burst. It alternates between red and
|
|
|
|
% green transparent rectangles over the time domain as a sanity check that
|
|
|
|
% the time alignment is correct (starting at the right sample)
|
2022-04-07 19:32:00 +00:00
|
|
|
plot_symbol_boundaries(samples, critial_sample_rate, 4);
|
2022-04-07 16:15:45 +00:00
|
|
|
|
|
|
|
%% Coarse frequency adjustment
|
|
|
|
|
2022-04-08 20:02:24 +00:00
|
|
|
% My CFO detection isn't working, so rotate the constellation by hand (just eyeballing the constellation plots)
|
2022-04-10 15:38:00 +00:00
|
|
|
% offset_adj = -0.00005;
|
|
|
|
% samples = samples .* exp(1j * 2 * pi * offset_adj * (0:length(samples)-1));
|
2022-04-08 20:02:24 +00:00
|
|
|
|
2022-04-07 16:15:45 +00:00
|
|
|
|
2022-04-08 21:49:17 +00:00
|
|
|
%% Phase adjustment
|
2022-04-10 15:38:00 +00:00
|
|
|
|
|
|
|
% The phase angle adjustment made in the ZC sequence detection step got the QPSK to be lined up with points in
|
|
|
|
% [1,0], [0,1], [-1,0], and [0,-1] but we want it at [1,1], [-1,1], [-1,-1], and [1,-1] to make the demodulation
|
|
|
|
% decision simpler. That means that the constellation needs to be rotated by pi/2 (45 degrees).
|
|
|
|
% TODO(10Apr2022): Because of timing offsets there is a lot of "smearing" as the constellation rotates. Instead of
|
|
|
|
% fixing that right now, the constellation is rotated a little more to keep the points in distinct
|
|
|
|
% quadrants.
|
|
|
|
phase_offset = pi/2 - 0.5;
|
|
|
|
samples = samples * exp(1j * phase_offset);
|
2022-04-08 21:49:17 +00:00
|
|
|
|
2022-04-07 16:15:45 +00:00
|
|
|
%% Symbol extraction
|
|
|
|
|
|
|
|
symbols_time_domain = zeros(9, fft_size);
|
|
|
|
symbols_freq_domain = zeros(9, fft_size);
|
|
|
|
|
|
|
|
sample_offset = 1;
|
|
|
|
for symbol_idx=1:9
|
2022-04-07 19:42:03 +00:00
|
|
|
% OFDM symbols 1 and 9 have a long cyclic prefix
|
2022-04-07 16:15:45 +00:00
|
|
|
if (symbol_idx == 1 || symbol_idx == 9)
|
|
|
|
cyclic_prefix_len = long_cp_len;
|
|
|
|
else
|
|
|
|
cyclic_prefix_len = short_cp_len;
|
|
|
|
end
|
|
|
|
|
2022-04-07 19:42:03 +00:00
|
|
|
% Skip the cyclic prefix
|
2022-04-07 16:15:45 +00:00
|
|
|
sample_offset = sample_offset + cyclic_prefix_len;
|
2022-04-07 19:42:03 +00:00
|
|
|
|
|
|
|
% Extract out just the OFDM symbol ignoring the cyclic prefix
|
2022-04-07 16:15:45 +00:00
|
|
|
symbol = samples(sample_offset:sample_offset + fft_size - 1);
|
2022-04-07 19:42:03 +00:00
|
|
|
|
|
|
|
% Save off the original time domain and the FFT'd samples
|
2022-04-07 16:15:45 +00:00
|
|
|
symbols_time_domain(symbol_idx,:) = symbol;
|
|
|
|
symbols_freq_domain(symbol_idx,:) = fftshift(fft(symbol));
|
|
|
|
|
2022-04-07 19:42:03 +00:00
|
|
|
% Skip the OFDM symbol just extracted
|
2022-04-07 16:15:45 +00:00
|
|
|
sample_offset = sample_offset + fft_size;
|
|
|
|
end
|
|
|
|
|
2022-04-07 19:42:03 +00:00
|
|
|
% Plot the constellation diagrams for all extracted OFDM symbols
|
2022-04-07 16:15:45 +00:00
|
|
|
figure(5)
|
|
|
|
for idx=1:9
|
|
|
|
subplot(3, 3, idx);
|
|
|
|
plot(symbols_freq_domain(idx,:), 'o');
|
|
|
|
end
|
|
|
|
title('Constellations (Pre Freq Correction)')
|
2022-04-07 19:59:02 +00:00
|
|
|
|
|
|
|
%% Demodulation
|
|
|
|
|
|
|
|
% The spec says that the 15.36 MHz signal has 601 occupied carriers, but
|
|
|
|
% one of those is DC which doesn't appear to be used
|
|
|
|
% NOTE: I have seen someone saying that there is a specific phase present
|
|
|
|
% in some of the DC. Not sure if this is correct, so for now assuming that
|
|
|
|
% the DC carrier is not used
|
|
|
|
occupied_carriers = 600;
|
|
|
|
|
|
|
|
% Create a list of data carrier indices
|
|
|
|
data_carriers_left = (fft_size/2)-(occupied_carriers/2)+1:fft_size/2;
|
|
|
|
data_carriers_right = (fft_size/2)+2:fft_size/2+(occupied_carriers/2)+1;
|
|
|
|
|
2022-04-08 23:09:37 +00:00
|
|
|
% Below is logic to write the received ZC sequence out to a file. It is meant to be used with the
|
|
|
|
% brute_force_zc.m script
|
|
|
|
%
|
|
|
|
% % figure(1000); plot(abs(symbols_time_domain(4,:)).^2);
|
|
|
|
% file_handle = fopen('/opt/dji/collects/zc_symbol_4_15360KSPS_time_domain.fc32', 'w');
|
|
|
|
% zc_samples = symbols_freq_domain(4, [data_carriers_left, data_carriers_right]);
|
|
|
|
% reals = reshape([real(zc_samples); imag(zc_samples)], [], 1);
|
|
|
|
% fwrite(file_handle, reals, 'single');
|
|
|
|
% fclose(file_handle);
|
|
|
|
%
|
|
|
|
% % figure(1000); plot(abs(symbols_time_domain(6,:)).^2);
|
|
|
|
% file_handle = fopen('/opt/dji/collects/zc_symbol_6_15360KSPS_time_domain.fc32', 'w');
|
|
|
|
% zc_samples = symbols_freq_domain(6, [data_carriers_left, data_carriers_right]);
|
|
|
|
% reals = reshape([real(zc_samples); imag(zc_samples)], [], 1);
|
|
|
|
% fwrite(file_handle, reals, 'single');
|
|
|
|
% fclose(file_handle);
|
|
|
|
|
2022-04-07 20:27:06 +00:00
|
|
|
% Place to store the data carrier samples
|
2022-04-07 19:59:02 +00:00
|
|
|
data_carriers = zeros(7, occupied_carriers);
|
|
|
|
|
2022-04-07 20:27:06 +00:00
|
|
|
% Plot and extract the data carriers from the 7 data carrying OFDM symbols
|
2022-04-07 19:59:02 +00:00
|
|
|
figure(6);
|
|
|
|
output_idx = 1;
|
2022-04-07 20:27:06 +00:00
|
|
|
|
|
|
|
% Symbols 4 and 6 are ZC sequences and do not contain data
|
2022-04-07 19:59:02 +00:00
|
|
|
for idx=[1,2,3,5,7,8,9]
|
|
|
|
subplot(3, 3, idx);
|
2022-04-07 20:27:06 +00:00
|
|
|
|
|
|
|
% Get all of the freq domain samples for the current symbol, extract
|
|
|
|
% out just the data carriers, and plot
|
2022-04-07 19:59:02 +00:00
|
|
|
carriers = symbols_freq_domain(idx,:);
|
|
|
|
data_carriers(output_idx,:) = carriers([data_carriers_left, data_carriers_right]);
|
|
|
|
plot(squeeze(data_carriers(output_idx,:)), 'o');
|
|
|
|
output_idx = output_idx + 1;
|
|
|
|
end
|
|
|
|
|
2022-04-07 20:27:06 +00:00
|
|
|
% Plot the data carriers as one constellation (first one with lines, second
|
|
|
|
% with dots)
|
2022-04-07 19:59:02 +00:00
|
|
|
figure(7);
|
|
|
|
subplot(1, 2, 1);
|
|
|
|
plot(squeeze(data_carriers));
|
|
|
|
subplot(1, 2, 2);
|
|
|
|
plot(squeeze(data_carriers), 'o');
|
|
|
|
|
2022-04-08 20:03:03 +00:00
|
|
|
%% Below is a brute force attempt at trying to get the first data symbol to drop out as all 0's or 1's
|
|
|
|
|
|
|
|
% Initializing to 0x12345678
|
|
|
|
scrambler_x2_init = [0 0 1, 0 0 1 0, 0 0 1 1, 0 1 0 0, 0 1 0 1, 0 1 1 0, 0 1 1 1, 1 0 0 0];
|
|
|
|
|
|
|
|
% Generate enough random for one OFDM symbol (need two bits per data carrier since there are 2 bits per sample)
|
|
|
|
scrambler_bit_count = length(data_carriers) * 2;
|
|
|
|
|
|
|
|
% Try out 4 possibilities for the scrambler. It's unclear if the order of the initialization vector
|
|
|
|
% is correct, nor if the output of the scrambler needs to be reversed before getting applied. So, try
|
|
|
|
% all 4 combos
|
|
|
|
scrambler_perms = [...
|
|
|
|
generate_scrambler_seq(scrambler_bit_count, scrambler_x2_init);
|
|
|
|
generate_scrambler_seq(scrambler_bit_count, fliplr(scrambler_x2_init));
|
|
|
|
fliplr(generate_scrambler_seq(scrambler_bit_count, scrambler_x2_init));
|
|
|
|
fliplr(generate_scrambler_seq(scrambler_bit_count, fliplr(scrambler_x2_init)));
|
|
|
|
];
|
|
|
|
|
|
|
|
% Number of bits to XOR and show in the terminal
|
2022-04-10 15:39:26 +00:00
|
|
|
xor_window = 128;
|
2022-04-08 20:03:03 +00:00
|
|
|
|
2022-04-10 15:40:14 +00:00
|
|
|
% TODO(10April2022): REMOVE THIS!!! IT'S ONLY HERE TO TROUBLESHOOT THE DESCRAMBLER!!!
|
|
|
|
data_carriers = data_carriers(1,:);
|
|
|
|
figure(88);
|
2022-04-08 20:03:03 +00:00
|
|
|
% Loop over the data carriers 4 times, each time rotating the constellation by 90 degrees
|
|
|
|
% This is a brute force approach to finding the correct phase offset since it's unknown due
|
|
|
|
% to not having a known training sequence
|
|
|
|
for idx=0:3
|
|
|
|
% Rotate all samples by 90 degrees
|
|
|
|
offset = pi / 2;
|
|
|
|
data_carriers = data_carriers .* exp(1j * offset);
|
|
|
|
|
2022-04-10 15:40:14 +00:00
|
|
|
subplot(2, 2, idx+1);
|
|
|
|
plot(data_carriers, 'o');
|
|
|
|
|
2022-04-14 01:22:50 +00:00
|
|
|
% Demapping the constellation points by hand. The mapping was taken from
|
|
|
|
% https://github.com/ttsou/openphy/blob/master/src/lte/qam.c#L35
|
|
|
|
|
|
|
|
% Using concatenation like a chump
|
2022-04-10 15:40:14 +00:00
|
|
|
demodulated_bits = [];
|
|
|
|
for sample_idx = 1:length(data_carriers)
|
|
|
|
sample = data_carriers(sample_idx);
|
|
|
|
if (real(sample) > 0 && imag(sample) > 0)
|
|
|
|
bits = [0, 0];
|
|
|
|
elseif (real(sample) > 0 && imag(sample) < 0)
|
|
|
|
bits = [0, 1];
|
|
|
|
elseif (real(sample) < 0 && imag(sample) > 0)
|
|
|
|
bits = [1, 0];
|
|
|
|
elseif (real(sample) < 0 && imag(sample) < 0)
|
|
|
|
bits = [1, 1];
|
|
|
|
else
|
|
|
|
error("Invalid coordinate!");
|
|
|
|
end
|
|
|
|
|
|
|
|
demodulated_bits = [demodulated_bits, bits];
|
|
|
|
end
|
2022-04-08 20:03:03 +00:00
|
|
|
|
|
|
|
% Try all 4 scrambler possibilities
|
|
|
|
xor_out = [
|
|
|
|
char('0'+bitxor(demodulated_bits(1,1:xor_window), scrambler_perms(1,1:xor_window)));
|
|
|
|
char('0'+bitxor(demodulated_bits(1,1:xor_window), scrambler_perms(2,1:xor_window)));
|
|
|
|
char('0'+bitxor(demodulated_bits(1,1:xor_window), scrambler_perms(3,1:xor_window)));
|
|
|
|
char('0'+bitxor(demodulated_bits(1,1:xor_window), scrambler_perms(4,1:xor_window)));
|
|
|
|
]
|
|
|
|
end
|