Merge branch 'main' into gr-droneid

gr-droneid-3.8
David Protzman 2022-04-22 11:47:15 -04:00
commit 4cb3ed52d3
8 zmienionych plików z 1044 dodań i 443 usunięć

5
.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,5 @@
**/*.asv
**/*.fc32
**/octave-workspace
collects/
**/images/*.png

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -1,267 +0,0 @@
clear all
%% Signal parameters
file_path = '/opt/dji/collects/2437MHz_30.72MSPS.fc32';
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
rough_frequency_offset = 7.5e6; % The collected signal is 7.5 MHz off center
%% Read in the IQ samples for a single burst
% The burst is found using baudline and its cursor/delta time measurements
file_start_time = 0.802225;
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;
figure(1000);
plot(10 * log10(abs(fftshift(fft(samples)))));
title('Original Samples - Freq Shifted')
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
[score, index] = max(abs(scores).^2);
if (score < 0.7)
warning("Correlation score for the first ZC sequence was bad. Might have errors later");
end
% 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);
% Calculate where the burst starts based on the correlation index
start_offset = round(index - zc_seq_offset);
angle(scores(index))
% samples = samples .* exp(1j * -angle(scores(index)) * (0:(length(samples)-1)));
samples = samples .* exp(1j * -angle(scores(index)));
% 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);
%% 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)
plot_symbol_boundaries(samples, critial_sample_rate, 4);
%% Coarse frequency adjustment
% My CFO detection isn't working, so rotate the constellation by hand (just eyeballing the constellation plots)
% offset_adj = -0.00005;
% samples = samples .* exp(1j * 2 * pi * offset_adj * (0:length(samples)-1));
%% Phase adjustment
% 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);
%% Symbol extraction
symbols_time_domain = zeros(9, fft_size);
symbols_freq_domain = zeros(9, fft_size);
sample_offset = 1;
for symbol_idx=1:9
% OFDM symbols 1 and 9 have a long cyclic prefix
if (symbol_idx == 1 || symbol_idx == 9)
cyclic_prefix_len = long_cp_len;
else
cyclic_prefix_len = short_cp_len;
end
% Skip the cyclic prefix
sample_offset = sample_offset + cyclic_prefix_len;
% Extract out just the OFDM symbol ignoring the cyclic prefix
symbol = samples(sample_offset:sample_offset + fft_size - 1);
% Save off the original time domain and the FFT'd samples
symbols_time_domain(symbol_idx,:) = symbol;
symbols_freq_domain(symbol_idx,:) = fftshift(fft(symbol));
% Skip the OFDM symbol just extracted
sample_offset = sample_offset + fft_size;
end
% Plot the constellation diagrams for all extracted OFDM symbols
figure(5)
for idx=1:9
subplot(3, 3, idx);
plot(symbols_freq_domain(idx,:), 'o');
end
title('Constellations (Pre Freq Correction)')
%% 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;
% 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);
% Place to store the data carrier samples
data_carriers = zeros(7, occupied_carriers);
% Plot and extract the data carriers from the 7 data carrying OFDM symbols
figure(6);
output_idx = 1;
% Symbols 4 and 6 are ZC sequences and do not contain data
for idx=[1,2,3,5,7,8,9]
subplot(3, 3, idx);
% Get all of the freq domain samples for the current symbol, extract
% out just the data carriers, and plot
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
% Plot the data carriers as one constellation (first one with lines, second
% with dots)
figure(7);
subplot(1, 2, 1);
plot(squeeze(data_carriers));
subplot(1, 2, 2);
plot(squeeze(data_carriers), 'o');
%% 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
xor_window = 128;
% TODO(10April2022): REMOVE THIS!!! IT'S ONLY HERE TO TROUBLESHOOT THE DESCRAMBLER!!!
data_carriers = data_carriers(1,:);
figure(88);
% 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);
subplot(2, 2, idx+1);
plot(data_carriers, 'o');
% 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
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
% 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

Wyświetl plik

@ -1,55 +0,0 @@
clear all;
symbol_number = 6;
file_path = ['/opt/dji/collects/zc_symbol_', mat2str(symbol_number), '_15360KSPS_time_domain.fc32'];
file_handle = fopen(file_path, 'r');
reals = fread(file_handle, inf, 'single');
fclose(file_handle);
received_samples = reals(1:2:end) + 1j * reals(2:2:end);
figure(1); plot(10 * log10(abs(received_samples).^2));
root_sequnce_attempts = 1000;
scores = zeros(root_sequnce_attempts, 1);
for root=1:length(scores)
try
% Not all ZC roots are valid and the function will throw an error if the root is invalid
zc = zadoffChuSeq(root, 601);
% Remove the center sample as this is what's done on the transmitter.
zc = [zc(1:300); zc(302:end)];
catch
continue
end
% Correlate the
scores(root) = abs(xcorr(received_samples, zc(1:end), 0, 'normalized')).^2;
end
figure(2);
plot(scores);
title('ZC Normalized XCorr Results')
[value, index] = max(scores);
if (value < 0.7)
warning('Unable to find matching sequence');
end
zc = zadoffChuSeq(index, 601);
figure(3);
subplot(2, 1, 1);
spectrogram(zc);
title('Calculated ZC')
subplot(2, 1, 2);
spectrogram(received_samples);
title('Received ZC')
figure(4)
subplot(2, 1, 1);
plot(abs(fftshift(fft(zc))).^2);
title('Calculated ZC')
subplot(2, 1, 2);
plot(abs(fftshift(fft(received_samples))).^2);
title('Received ZC')

Wyświetl plik

@ -0,0 +1,13 @@
function [] = create_gnuradio_zc(fft_size, symbol_number)
zc = conj(create_zc(fft_size, symbol_number));
fprintf('[');
for idx=1:length(zc)
fprintf('(%0.4f+%0.4fj)', real(zc(idx)), imag(zc(idx)));
if (idx ~= length(zc))
fprintf(',')
end
end
fprintf(']\n');
end

Wyświetl plik

@ -1,28 +0,0 @@
function [freq_offset_est] = estimate_cp_freq_offset(samples, fft_size, short_cp_len, long_cp_len)
figure(44);
scores = zeros(9, 1);
skip = 8;
sample_offset = 1;
figure(10);
for idx=1:9
if (idx == 1 || idx == 9)
cp_len = long_cp_len;
else
cp_len = short_cp_len;
end
cyclic_prefix = samples(sample_offset+skip:sample_offset + cp_len - 1 - skip);
end_of_symbol = samples(sample_offset+fft_size+skip:sample_offset+fft_size+cp_len - 1 - skip);
subplot(3, 3, idx);
plot(abs(cyclic_prefix).^2);
hold on;
plot(abs(end_of_symbol).^2);
hold off;
scores(idx) = angle(sum(cyclic_prefix .* conj(end_of_symbol))) / length(cyclic_prefix);
sample_offset = sample_offset + fft_size + cp_len;
end
freq_offset_est = sum(scores) / length(scores) / fft_size;
end

Wyświetl plik

@ -1,93 +0,0 @@
%% Signal parameters
file_path = '/opt/dji/collects/2437MHz_30.72MSPS.fc32';
file_sample_rate = 30.72e6; % Collected 2x oversampled
rough_frequency_offset = 7.5e6; % The collected signal is 7.5 MHz off center
correlation_threshold = 0.7; % Minimum correlation score to accept (0.0 - 1.0)
%% LTE parameters
carrier_spacing = 15e3;
signal_bandwidth = 10e6;
% How many complex samples to read in at a time
chunk_size = 1e6;
% Pre-calculate the frequency offset rotation
freq_offset_constant = 1j * pi * 2 * (rough_frequency_offset / file_sample_rate);
% The output of the ZC sequence generator needs to be conjugated to be used in a filter. Also create a variable that
% can hold the filter state between chunks to prevent discontinuities
correlator_taps = conj(create_zc(file_sample_rate / carrier_spacing, 4));
correlator_state = [];
% Open the IQ recording
file_handle = fopen(file_path, 'r');
% Figure out how many samples there are in the file
fseek(file_handle, 0, 'eof');
total_samples = ftell(file_handle) / 4 / 2; % 4 bytes per float, 2 floats per complex sample
fseek(file_handle, 0, 'bof');
fprintf('There are %d samples in "%s"\n', total_samples, file_path);
% Really large array to store the cross correlation results from *all* samples
zc_scores = zeros(total_samples - length(correlator_taps), 0);
sample_offset = 0;
while (~ feof(file_handle))
%% Read in the next buffer
% The `fread` command will return interleaved real, imag values, so pack those into complex samples
floats = fread(file_handle, chunk_size * 2, 'float');
samples = floats(1:2:end) + 1j * floats(2:2:end);
%% Frequency shift the input
% This is somewhat optional, but the correlation scores will go down fast if the offset is > 1 MHz
rotation_vector = exp(freq_offset_constant * (sample_offset:(sample_offset+length(samples)-1)));
samples = samples .* reshape(rotation_vector, [], 1);
%% Correlate for the ZC sequence
% Use a FIR filter to search for the ZC sequence. The resulting values will be normalized to between 0 and 1.0 if
% the abs^2 is taken
% TODO(9April2022): Would be nice to use the fftfilt function, but I don't know if it has issues with keeping state
% as it doesn't have a state input like the filter command. In testing the FFT filter is twice
% as fast
[correlation_values, correlator_state] = filter(correlator_taps, 1, samples, correlator_state);
zc_scores(sample_offset+1:sample_offset+length(correlation_values)) = correlation_values;
sample_offset = sample_offset + length(samples);
end
% Get the floating normalized correlation results
abs_scores = abs(zc_scores).^2;
figure(1);
plot(abs_scores);
title('Correlation Scores (normalized)')
% Find all places where the correlation result meets the specified threshold
% This is going to find duplicates because there are very likely going to be two points right next to each other that
% meet the required threshold. This will be dealt with later
passing_scores = find(abs_scores > correlation_threshold);
% Look through each element of the `passing_scores` vector (which is just indicies where the correlation threshold was
% met) and pick just the highest value `search_window` elements around (`search_window/2` to the left and right) of each
% value. The goal here is to only end up with the best score for the starting point of each burst instead of having
% multiple starting points for each burst.
true_peaks = zeros(length(passing_scores), 1);
search_window = 100;
for idx = 1:length(passing_scores)
% Calculate how far to the left and right to look for the highest peak
left_idx = passing_scores(idx) - (search_window / 2);
right_idx = left_idx + search_window - 1;
% Get the correlation scores for the samples around the current point
window = abs_scores(left_idx:right_idx);
% Find the peak in the window and use that value as the actual peak
[value, index] = max(window);
true_peaks(idx) = left_idx + index;
end
% There are going to be duplicates in the vector, so just take the unique elements. What's left should just be the
% actual starting indices for each ZC sequence
true_peaks = unique(true_peaks);