Added:
- Class folder 'Timing Recovery' with different timing recoveries - Minimal example for the timing recovery on the FSO data - New evaluation scripts in the FSO project folder
This commit is contained in:
112
Classes/04_DSP/Timing Recovery/Godard_Timing_Recovery.m
Normal file
112
Classes/04_DSP/Timing Recovery/Godard_Timing_Recovery.m
Normal file
@@ -0,0 +1,112 @@
|
||||
classdef Godard_Timing_Recovery < handle
|
||||
|
||||
properties(Access=public)
|
||||
mode
|
||||
num_blocks
|
||||
fft_length
|
||||
sps
|
||||
rolloff
|
||||
mu
|
||||
Ki
|
||||
end
|
||||
|
||||
methods(Access=public)
|
||||
function obj = Godard_Timing_Recovery(options)
|
||||
arguments(Input)
|
||||
|
||||
options.mode = 0;
|
||||
options.num_blocks = 1;
|
||||
options.fft_length = 1024;
|
||||
options.sps = 2;
|
||||
options.rolloff = 0;
|
||||
options.mu = 0;
|
||||
options.Ki = 1e-3;
|
||||
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
%obj-Initialization here%
|
||||
|
||||
end
|
||||
|
||||
function [data_out, tau_hat] = process(obj, data_in)
|
||||
|
||||
data_out = data_in;
|
||||
output_vector = zeros(length(data_in),1);
|
||||
block_length = length(data_in)/obj.num_blocks;
|
||||
for i = 1:obj.num_blocks
|
||||
i_start = ((i-1)*block_length)+1;
|
||||
i_end = i*block_length;
|
||||
x = data_in.signal(i_start:i_end);
|
||||
beta = obj.rolloff;
|
||||
eta = obj.sps;
|
||||
R = fft(x,obj.fft_length);
|
||||
R_full = fft(x);
|
||||
|
||||
if obj.mode == 0 % Classic Godard
|
||||
% Calculate time shift
|
||||
k0 = (1:obj.fft_length/2).';
|
||||
idx1 = k0;
|
||||
idx2 = k0+(obj.fft_length/2);
|
||||
tau_hat = sum(imag(R(idx1) .* conj(R(idx2))));
|
||||
elseif obj.mode == 1 || obj.mode == 2 % Modified Godard 1
|
||||
% Calculate Shift for the received signal
|
||||
shiftBins = (1 - 1/eta) * obj.fft_length;
|
||||
if abs(shiftBins - round(shiftBins)) > 1e-12
|
||||
disp('Warning: shiftBins=(1-1/eta)*fft_length is non-integer. Choose compatible values for fft_length and eta.');
|
||||
end
|
||||
shiftBins = round(shiftBins);
|
||||
|
||||
% Calculate upper and lower bounds
|
||||
kStart = ((1-beta)/(2*eta)) * obj.fft_length;
|
||||
kEnd = ((1+beta)/(2*eta)) * obj.fft_length - 1;
|
||||
if abs(kStart - round(kStart)) > 1e-12 || abs(kEnd - round(kEnd)) > 1e-12
|
||||
disp('Warning: kStart/kEnd are non-integer. Choose compatible values for fft_length, eta, and beta.');
|
||||
end
|
||||
kStart = round(kStart);
|
||||
kEnd = round(kEnd);
|
||||
|
||||
k0 = (kStart:kEnd).';
|
||||
idx1 = k0 + 1;
|
||||
idx2 = mod(k0 + shiftBins, obj.fft_length) + 1;
|
||||
|
||||
% Calculate time shift
|
||||
if obj.mode == 1
|
||||
tau_hat = sum(imag(R(idx1) .* conj(R(idx2))));
|
||||
elseif obj.mode == 2
|
||||
tau_hat = sum(angle(R(idx1))-angle(R(idx2)));
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
% % Normalization
|
||||
% denom = floor(log10(abs(tau_hat)));
|
||||
% tau_hat = tau_hat / 10^denom;
|
||||
|
||||
% Calculate mu using a first-order loop filter
|
||||
mu_block = obj.mu + obj.Ki * tau_hat;
|
||||
|
||||
% % Shifting the signal in time domain using interpolation
|
||||
% x_original = linspace(0,length(x)-1,length(x)).';
|
||||
% x_new = x_original + mu_block;
|
||||
% output_vector(i_start:i_end) = interp1(x_original,x,x_new,'linear','extrap');
|
||||
|
||||
% Shifting the signal in frequency domain
|
||||
k = (0:block_length-1).';
|
||||
phaseRamp = exp(-1j * 2*pi * (k/block_length) * mu_block);
|
||||
R_shifted = R_full .* phaseRamp;
|
||||
|
||||
output_vector(i_start:i_end) = real(ifft(R_shifted));
|
||||
|
||||
end
|
||||
data_out.signal = output_vector;
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
111
Classes/04_DSP/Timing Recovery/MaxVar_Timing_Recovery.m
Normal file
111
Classes/04_DSP/Timing Recovery/MaxVar_Timing_Recovery.m
Normal file
@@ -0,0 +1,111 @@
|
||||
classdef MaxVar_Timing_Recovery < handle
|
||||
|
||||
properties(Access=public)
|
||||
mode
|
||||
sps
|
||||
fsym
|
||||
fadc
|
||||
num_tau
|
||||
comp_signal
|
||||
comp_mode
|
||||
end
|
||||
|
||||
methods(Access=public)
|
||||
function obj = MaxVar_Timing_Recovery(options)
|
||||
arguments(Input)
|
||||
|
||||
options.mode = 0;
|
||||
options.sps = 2;
|
||||
options.fsym = 32e9;
|
||||
options.fadc = 80e9;
|
||||
options.num_tau = 64;
|
||||
options.comp_signal = 0;
|
||||
options.comp_mode = 0;
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
%obj-Initialization here%
|
||||
|
||||
end
|
||||
|
||||
function [data_out] = process(obj, data_in)
|
||||
|
||||
data_out = data_in;
|
||||
x = data_in.signal;
|
||||
|
||||
if obj.comp_mode
|
||||
our_signal = data_in;
|
||||
their_signal = obj.comp_signal;
|
||||
end
|
||||
|
||||
if obj.mode == 0
|
||||
vars = zeros(obj.sps,1);
|
||||
for phi = 1:obj.sps
|
||||
|
||||
% Test variance for different start samples
|
||||
y_phi = x(phi:obj.sps:end);
|
||||
vars(phi) = var(y_phi);
|
||||
|
||||
% Comparison to reference signal
|
||||
if obj.comp_mode
|
||||
our_signal.signal = x(phi:obj.sps:end);
|
||||
our_signal.fs = 6e9;
|
||||
our_signal.normalize("mode","rms").plot("displayname",['Our signal, ' num2str(vars(phi))],'fignum',1231+phi);
|
||||
their_signal.normalize("mode","rms").plot("displayname",'Their signal','fignum',1231+phi);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
% Choose signal configuration with the maximum variance
|
||||
[~,phi_opt] = max(vars);
|
||||
|
||||
y = x(phi_opt:obj.sps:end).';
|
||||
|
||||
elseif obj.mode == 1
|
||||
% Create a grid with different (sub)sample starting points
|
||||
T = 1/obj.fsym;
|
||||
t = (0:length(x)-1)/obj.fadc;
|
||||
tauGrid = linspace(0, T, obj.num_tau);
|
||||
|
||||
% Interpolate the signal starting from every defined point
|
||||
% and calculate the MMSE/variance
|
||||
vars = zeros(size(tauGrid));
|
||||
for i = 1:numel(tauGrid)
|
||||
tau = tauGrid(i);
|
||||
% tk = linspace(tau, t(end), length(x)/obj.sps);
|
||||
tk = tau : T : t(end)+tau;
|
||||
xk = interp1(t, x, tk, 'linear', 'extrap');
|
||||
if obj.comp_mode % MMSE
|
||||
e = xk.' - obj.comp_signal.signal;
|
||||
vars(i) = mean(abs(e).^2);
|
||||
else % Variance
|
||||
vars(i) = var(xk, 1);
|
||||
end
|
||||
end
|
||||
|
||||
if obj.comp_mode % MMSE
|
||||
[~,idx] = min(vars);
|
||||
else % Variance
|
||||
[~,idx] = max(vars);
|
||||
end
|
||||
tau_opt = tauGrid(idx);
|
||||
|
||||
% Choosing the signal at optimum
|
||||
% tk = linspace(tau_opt, t(end), length(x)/obj.sps);
|
||||
tk = tau_opt : T : t(end)+tau_opt;
|
||||
y = interp1(t, x, tk, 'linear', 'extrap');
|
||||
end
|
||||
% if length(y) ~= length(x)/obj.sps
|
||||
% y = [y 0];
|
||||
% end
|
||||
data_out.signal = y.';
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
39
Classes/04_DSP/Timing Recovery/Time_Shifter.m
Normal file
39
Classes/04_DSP/Timing Recovery/Time_Shifter.m
Normal file
@@ -0,0 +1,39 @@
|
||||
classdef Time_Shifter < handle
|
||||
|
||||
properties(Access=public)
|
||||
value
|
||||
end
|
||||
|
||||
methods(Access=public)
|
||||
function obj = Time_Shifter(options)
|
||||
arguments(Input)
|
||||
|
||||
options.value = 0;
|
||||
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
%obj-Initialization here%
|
||||
|
||||
end
|
||||
|
||||
function [data_out, tau_hat] = process(obj, data_in)
|
||||
|
||||
data_out = data_in;
|
||||
x = data_in.signal;
|
||||
R = fft(x);
|
||||
k = (0:length(R)-1).';
|
||||
phaseRamp = exp(-1j * 2*pi * (k/length(R)) * obj.value);
|
||||
R_shifted = R .* phaseRamp;
|
||||
data_out.signal = real(ifft(R_shifted));
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
49
Classes/04_DSP/Timing Recovery/Timing_Recovery.m
Normal file
49
Classes/04_DSP/Timing Recovery/Timing_Recovery.m
Normal file
@@ -0,0 +1,49 @@
|
||||
classdef Timing_Recovery < handle
|
||||
|
||||
properties(Access=public)
|
||||
modulation
|
||||
timing_error_detector
|
||||
sps
|
||||
damping_factor
|
||||
normalized_loop_bandwidth
|
||||
detector_gain
|
||||
end
|
||||
|
||||
methods(Access=public)
|
||||
function obj = Timing_Recovery(options)
|
||||
arguments(Input)
|
||||
|
||||
options.modulation = 'PAM/PSK/QAM';
|
||||
options.timing_error_detector = 'Gardner (non-data-aided)';
|
||||
options.sps = 2;
|
||||
options.damping_factor = 1.0;
|
||||
options.normalized_loop_bandwidth = 0.01;
|
||||
options.detector_gain = 2.7;
|
||||
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
%obj-Initialization here%
|
||||
|
||||
end
|
||||
|
||||
function [data_out, timing_error] = process(obj, data_in)
|
||||
|
||||
timing_synchronization = comm.SymbolSynchronizer( ...
|
||||
"Modulation", obj.modulation,...
|
||||
"TimingErrorDetector", obj.timing_error_detector, ...
|
||||
"SamplesPerSymbol", obj.sps, ...
|
||||
"DampingFactor", obj.damping_factor, ...
|
||||
"NormalizedLoopBandwidth", obj.normalized_loop_bandwidth, ...
|
||||
"DetectorGain", obj.detector_gain);
|
||||
data_out = data_in;
|
||||
[data_out.signal, timing_error] = timing_synchronization(data_in.signal);
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
77
Classes/04_DSP/Timing Recovery/Timing_Recovery_GPT.m
Normal file
77
Classes/04_DSP/Timing Recovery/Timing_Recovery_GPT.m
Normal file
@@ -0,0 +1,77 @@
|
||||
classdef Timing_Recovery_GPT < handle
|
||||
|
||||
properties(Access=public)
|
||||
sps
|
||||
muGrid
|
||||
end
|
||||
|
||||
methods(Access=public)
|
||||
function obj = Timing_Recovery_GPT(options)
|
||||
arguments(Input)
|
||||
|
||||
options.sps = 2;
|
||||
options.muGrid = 0;
|
||||
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
%obj-Initialization here%
|
||||
|
||||
end
|
||||
|
||||
function [data_out, mu_best, score] = process(obj, data_in)
|
||||
%MAXVARTIMINGSYNC Choose sampling phase mu that maximizes variance of downsampled symbols.
|
||||
%
|
||||
% data_in : matched-filtered samples (complex or real), length N
|
||||
% sps : samples per symbol (here typically 2)
|
||||
% muGrid : candidate fractional offsets in [0,1)
|
||||
%
|
||||
% data_out : symbol-rate samples (length floor(N/sps))
|
||||
% mu_best: chosen fractional offset
|
||||
% score : variance score for each mu in muGrid
|
||||
|
||||
data_out = data_in;
|
||||
|
||||
x = data_in.signal(:);
|
||||
N = length(x);
|
||||
Ns = floor(N/obj.sps);
|
||||
|
||||
if nargin < 3 || isempty(obj.muGrid)
|
||||
obj.muGrid = linspace(0, 0.99, 101); % 0..0.99 in ~0.01 steps
|
||||
end
|
||||
|
||||
% Symbol indices (1-based sample positions)
|
||||
n0 = 1; % start sample index
|
||||
k = (0:Ns-1).';
|
||||
tBase = n0 + k*obj.sps; % integer times (1, 1+sps, ...)
|
||||
|
||||
score = zeros(numel(obj.muGrid),1);
|
||||
|
||||
for m = 1:numel(obj.muGrid)
|
||||
mu = obj.muGrid(m);
|
||||
t = tBase + mu;
|
||||
|
||||
% Linear fractional sampling
|
||||
y = interp1(1:N, x, t, 'linear', 'extrap');
|
||||
|
||||
% For PAM, maximize variance of real part (or abs if you prefer)
|
||||
yr = real(y);
|
||||
score(m) = var(yr, 1); % use population variance (normalization doesn't matter for argmax)
|
||||
end
|
||||
|
||||
% Pick best mu
|
||||
[~, idx] = max(score);
|
||||
mu_best = obj.muGrid(idx);
|
||||
|
||||
% Resample with best mu
|
||||
t = tBase + mu_best;
|
||||
data_out.signal = interp1(1:N, x, t, 'linear', 'extrap');
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
77
Classes/04_DSP/Timing Recovery/Timing_Recovery_Move_It.m
Normal file
77
Classes/04_DSP/Timing Recovery/Timing_Recovery_Move_It.m
Normal file
@@ -0,0 +1,77 @@
|
||||
classdef Timing_Recovery_Move_It < handle
|
||||
|
||||
properties(Access=public)
|
||||
f_sim
|
||||
gamma
|
||||
end
|
||||
|
||||
methods(Access=public)
|
||||
function obj = Timing_Recovery_Move_It(options)
|
||||
arguments(Input)
|
||||
|
||||
options.f_sim = 14e9;
|
||||
options.gamma = 0.1;
|
||||
|
||||
end
|
||||
|
||||
fn = fieldnames(options);
|
||||
for n = 1:numel(fn)
|
||||
obj.(fn{n}) = options.(fn{n});
|
||||
end
|
||||
|
||||
%obj-Initialization here%
|
||||
|
||||
end
|
||||
|
||||
function data_out = process(obj, data_in)
|
||||
|
||||
e = NaN(size(data_in.signal));
|
||||
T = 1/obj.f_sim;
|
||||
t = zeros(size(data_in.signal));
|
||||
t(2) = T/2;
|
||||
mu = zeros(size(data_in.signal));
|
||||
|
||||
% n = 2;
|
||||
for k = 3:2:length(data_in.signal)
|
||||
data_in.signal(k-1) = data_in.signal(k-1)*(1-mu(k-2)) + data_in.signal(k)*mu(k-2);
|
||||
data_in.signal(k) = data_in.signal(k)*(1-mu(k-1)) + data_in.signal(k+1)*mu(k-1);
|
||||
|
||||
% TED
|
||||
e(k) = (data_in.signal(k-2)-data_in.signal(k))*data_in.signal(k-1);
|
||||
e(k+1) = e(k);
|
||||
|
||||
% TED Mueller Mueller
|
||||
% e(k) = ref_in(n-1)*data_in.signal(k) - ref_in(n)*data_in.signal(k-2);
|
||||
% e(k+1) = e(k);
|
||||
% n = n+1;
|
||||
%
|
||||
% interpolator control
|
||||
% t(k) = t(k-1) + T/2 + obj.gamma/2*e(k);
|
||||
% t(k+1) = t(k) + T/2 + obj.gamma/2*e(k+1);
|
||||
t(k) = t(k-1) + T/2 + obj.gamma*e(k)*T/2;
|
||||
t(k+1) = t(k) + T/2 + obj.gamma*e(k+1)*T/2;
|
||||
% t(k) = k*T/2 + obj.gamma*e(k)*T/2;
|
||||
% t(k+1) = k*T/2 + obj.gamma*e(k+1)*T/2;
|
||||
|
||||
% interpolator
|
||||
mu(k) = t(k)/(T/2) - round(t(k)/(T/2));
|
||||
mu(k+1) = t(k+1)/(T/2) - round(t(k+1)/(T/2));
|
||||
|
||||
thres = 0.7;
|
||||
if mu(k)-mu(k-1) > thres
|
||||
mu(k) = mu(k) - 1;
|
||||
elseif mu(k) - mu(k-1) < -thres
|
||||
mu(k) = mu(k) + 1;
|
||||
end
|
||||
|
||||
if mu(k+1)-mu(k) > thres
|
||||
mu(k+1) = mu(k+1) - 1;
|
||||
elseif mu(k+1) - mu(k) < -thres
|
||||
mu(k+1) = mu(k+1) + 1;
|
||||
end
|
||||
end
|
||||
data_out = data_in;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user