# The timing offset remains after Gardner loop

I would like to use Gardner loop to remove the timing offset in order to demodulate the signal. My signal contains the frequency offset, however I know that the Gardner loop can handle substantial frequency offsets and that is why I apply it.

I have written the following piece of code:

def Gardner_loop(time_support, sig):

sample_rate = 1/time_support[1]
sps = int(sample_rate/f_symb) # samples per symbol
sig_n_1 = int(np.ceil(sps/2)) # x(n-1)
sig_n_2 = int(sps) # x(n-2)
loop_filter = f_symb//20 # BW of the loop filter
loop_num = 10000
error_symbol = np.zeros(loop_num)

error_list = []
sig_wo_time_err = []
step = 0

for i in range(1, loop_num):
error_symbol[i] = sig.real[sig_n_1 + step + int(error_symbol[i-1]*loop_filter)]\
*(sig.real[sig_n_2 + step + int(error_symbol[i-1]*loop_filter)]\
- sig.real[step+int(error_symbol[i-1]*loop_filter)])\

+ sig.imag[sig_n_1 + step + int(error_symbol[i-1]*loop_filter)]\
*(sig.imag[sig_n_2 + step + int(error_symbol[i-1]*loop_filter)]\
- sig.imag[step+int(error_symbol[i-1]*loop_filter)])

step+=sps
error_list.append(error_symbol[i])

# Update

sig_wo_time_err.append(sig.real[sig_n_2+step+int(error_symbol[i-1]*loop_filter)] \
+ 1j*sig.imag[sig_n_2+step+int(error_symbol[i-1]*loop_filter)])

error_to_plot = np.array(error_symbol)
plt.plot(np.arange(1, loop_num+1), error_to_plot)
plt.xlabel('loop number')
plt.ylabel('error')
return np.array(sig_wo_time_err)


to remove timing offset. The BW of the loop filter I have selected from this article

In this case, sps = Number of samples per symbol = sampling frequency / symbol rate = 4.7. However, it seems to me that the loop is not locked since the error fluctuates as in the picture below:

This is not interpolated signal.

My idea was to increase the number of samples per symbol to reduce the error and lock the loop. After interpolation, the number of samples per symbol = interpolated rate/symbol rate = 5. However, the error still remains and is depicted below:

I have spent many hours trying to tackle on this problem, however I still do not understand what am I doing wrong. So my question is: do I make a mistake somewhere in the code, or do I miss something else to remove the timing offset?

Update:

I have updated the code where I save the signal.

My constellation without interpolation for 1000 samples before timing recovery looks like this:

After symbol timing recovery, again for 1000 points (or for 1000 loops in the Gardner loop)

For the same 1000 loops, my error is represented in the picture below

Note that if I plot more than 1000 samples, constellation becomes not readable anymore (which I think is due to the phase offset)

• The timing offset will never be zero. From your plots, it seems to be extremely small. What does the constellation look like? What is your BER? The timing offset error is not an end in itself. – MBaz May 17 at 1:21
• Right just as MBaz has said and to add what you may likely be seeing is the “self-noise” of the detector (which on a per sample basis is very noisy)- it is the long term average (as given by the loop BW that is of interest and the average of the plots show appears to be close to 0 which indicates things are working as expected (need to see for a much longer duration or test with an initial time offset to see that it does converge to zero, on average) – Dan Boschen May 17 at 1:42
• @MBaz thank you for the answer! I have updated the question and plotted the constellations before and after the timing recovery. BER - I do not know how to calculate yet. – Python May 17 at 7:28
• @DanBoschen thank you for the answer! I have updated the question with the constellations before and after the timing recovery. And also I have plotted an error over larger number of loops (1000 in this case) – Python May 17 at 7:34
• How many samples per symbol are you using? – Dan Boschen May 17 at 12:05