# Understanding Phase-offset in 4-QAM/QPSK

#### Hekiem

Joined Sep 28, 2022
1
I stambled on a Matlab solution to calculate the phase by which symbols in the Rx of a 4-QAM have been shifted and therefore do the phase correction. The solution is only custome to Tx'ng and Rx'ng 432 bits, which therefore takes 2bits per symbol. The thing is, i don't understand well how they calculated the error and did the correction after they Match-filtered. Is there anyone that understands how from the sample code and how the correction is done? And in anyway, is there a better solution/effective way of doing it?

Why do we use mode and then variance. How is the correction done like in line 143. I know its kind of ambiguous because its another persons solution. But am still scratching my head why they did error = 5.

Rx side:
function [audio_recorder] = receiver(fc)
fs = 22050;
% https://se.mathworks.com/help/matlab/ref/audiorecorder.html
% audiorecorder creates and returns an audiorecorder object with these example properties:
% SampleRate: 8000
% BitsPerSample: 8 | can either be 8, 16 or 24
% NumChannels: 1 | To obtain the ID of a device, use audiodevinfo -> https://se.mathworks.com/help/matlab/ref/audiodevinfo.html
audio_recorder = audiorecorder(fs, 24, 1);

audio_recorder.UserData.fs            = fs;
audio_recorder.UserData.fc            = fc;
audio_recorder.UserData.stopBit       = 0;

% Attach callback function
% How often the function should be called in seconds
time_value = 0.5;

% Attach a function that should be called every timer_value seconds.
set(audio_recorder, 'TimerPeriod', time_value, 'TimerFcn', @audioTimerFcn)

% Add user data for callback function
audio_recorder.UserData.receive_complete    = 0;    % this is a flag that the while loop in the GUI will check
audio_recorder.UserData.pack                = [];   % allocate for data package
audio_recorder.UserData.pwr_spect           = [];   % allocate for PSD
audio_recorder.UserData.const               = [];   % allocate for constellation
audio_recorder.UserData.eyed                = [];   % allocate for eye diagram

% start recording
record(audio_recorder);

end

% CALLBACK FUNCTION
% This function will be called every [time_value] seconds, where time_value
% is specified above. Note that, as stated in the project MEMO, all the
% fields: pwr_spect, eyed, const and pack need to be assigned if you want
% to get outputs in the GUI.
function audioTimerFcn(recObj, event, handles)

[~, tsamp, const, bps, fsfd, Rs, Ts, preamble, rrc, stopBit, NoBits, ~] = common();
fc = recObj.UserData.fc;
fs = recObj.UserData.fs;

disp('Callback triggered')
disp(' ')

% implement an RRC pulse shape
pulse_train = rtrcpuls(rrc.roll_off_factor, Ts, fs, rrc.span);

% disp(length(pulse_train));
% captured_signal = getaudiodata(recObj)';
% Generate a random delay between 10 and 500
% Check Ex8.m file on Canvas
% delay = zeros(1, randi([10, 20], 1, 1));
% delay_data = awgn(delay, 10, 'measured');

cs = getaudiodata(recObj)';

% cs = [delay_data, info];

% Remove the stop bit
cs = cs(recObj.UserData.stopBit + 1 : end);

% Normalize Rx signal
cs = cs./max(abs(cs));

% Demodulation, Put the baseband signal on carrier signal
demod_rx = sqrt(2) * cs.*exp(1j * 2 * pi * fc * (0:length(cs) - 1) * tsamp);

% Timer Synchronization
upsampled_preamble = upsample(preamble, fsfd);

% shape the known preamble like the rx preamble
preamb_train = conv(pulse_train, upsampled_preamble);

% correlate the processed preamble and demodulated signal to find the similarity
corr = conv(demod_rx, fliplr(preamb_train));

% noise threshold
noiseThreshold = 10;

% find location of max correlation
[maxCorr, Tmax] = max(corr);

% find delay (it will take length preamble)
Tx_hat = Tmax - length(preamb_train);

% pass the signal only if
%  1. the correlation is not with noise
%  2. the delay is positive
%  3. the whole signal is captured
a = noiseThreshold > abs(maxCorr);
b = Tmax < length(preamb_train);
d = length(demod_rx) - Tmax;
e = fsfd * (NoBits / 2);
c = d < e;

% disp([a, b, c]);

if(a || b || c)
fprintf('No signal found \n')
return;
end

fprintf("Delay occured is %s samples\n", num2str(Tx_hat));

% Take note of the stop bit
recObj.UserData.stopBit = length(cs);

% ===================================================================
%       Matched filter calculations
% ===================================================================

% Create a matched filter impulse response from the pulse_train
matched_filter = fliplr(conj(pulse_train));

% Now we run collected signal through the filter
matched_filter_output = filter(matched_filter, 1, demod_rx);

% Remove the transient response in the FIR bandpass filter
% Transient response is the time the system takes from
% an equilibrium position to another steady state
matched_filter_output = matched_filter_output(length(matched_filter) : end);

% ===================================================================
%       Phase Synchronization
% ===================================================================
% Match the received preamble symbols with what we have so far
rx_preamble = matched_filter_output(Tx_hat + 1 : fsfd : Tx_hat + length(upsampled_preamble));

% Normalize the preamble symbols to 1 unit
rx_preamble = rx_preamble./mean(abs(rx_preamble));

% At this stage, we can already extract the phase difference
% between the known preambles and the received ones.
phase = angle(rx_preamble) - angle(preamble);
phase = mod(phase, 2 * pi);

if(var(phase) > 5)
error = 5;
phase(phase < (2 * pi + error) & phase > (2 * pi - error)) = phase(phase < (2 * pi + error) & phase > (2 * pi - error)) - 2 * pi;
end

avgPhase = mean(phase);

% Get the real signal received
matched_filter_output = matched_filter_output(Tx_hat+  length(upsampled_preamble) + 1 : Tx_hat + length(upsampled_preamble) + (NoBits / 2) * fsfd);

% Get the correct phase used at the Tx side
matched_filter_output = matched_filter_output * exp(-1j * avgPhase);

fprintf("The signal is phase shifted by %s deg CCW\n", num2str(avgPhase * 180/pi));

% ===================================================================
%      Extract symbols from the signal
% ===================================================================
% Sample the matched filter signal at fs
rx = matched_filter_output(1:fsfd:end);

% Normalize the vector
rx = rx./mean(abs(rx));

% ===================================================================
%      Turn the symbols to bits
% ===================================================================
% Minimum Eucledian distance detector
% Relate the detection to Detection region

% Compute the distance to each received symbol from where it should be
metric = abs(repmat(rx.', 1, 4) - repmat(const, length(rx), 1)).^2;

% find the closest for each received symbol
[tmp, msg_hat] = min(metric, [], 2);

% Get the index of the symbol in the constellation
msg_hat = msg_hat' - 1;

% Convert symbols to bits
msg_hat = de2bi(msg_hat, 2, 'left-msb')';
data_hat = msg_hat(:)';

bits = data_hat(1 : NoBits);

x = rx(1 : (NoBits / 2));
pulse_train = matched_filter_output;

end

function [const, fs, Tsamp, bitperSymb, alpha, fsfd, Rs, Ts, preamb, span] = common()

const = [(1 + 1i), (1 - 1i), (-1 -1i), (-1 + 1i)]/sqrt(2);
fs = 22050;
Tsamp = 1 / fs;
M = length(const);
bitperSymb = log2(M);
alpha = 0.4;
fsfd = 120;
Rs = fs/fsfd;
Ts = 1/Rs;
preamb = [1 1 1 1 1 -1 -1 1 1 -1 1 -1 1 1 1 1 1 1 -1 -1 1 1 -1 1 -1 1];
span = 6;

end