import numpy as np
import matplotlib.pyplot as plt
def VCO(K, dt, phi_0, f):
# VCO instead of NCO implemented for now, it starts with a frequency of 35 Hz instead of 32Hz
phi = phi_0+2*np.pi*(30+K*f)*dt
return (phi)
def Mixer(f1, f2):
return f1*f2
def LPF_Costas(x, y0, dt, f_C):
# Forward Euler based LPF after the envelope detector to remove noise, 3dB frequency at f_C
RC = 1/(f_C*2*np.pi)
y = y0+(x-y0)/RC*dt
return y
Fc = 32 # Carrier Frequency
Fs = 1024 # Sampling rate
dt = 1/Fs
Fsym = 0.25 # Symbol rate
t_stop = 8/Fsym # Sim time
time = np.arange(0, t_stop, dt) # time grid
Data = np.array([0, 1, 0, 0, 1, 0, 1, 1]) # Data to be transmitted
Phase = np.pi*np.repeat(Data, np.round(1/Fsym*Fs)) # BPSK Phase based on data, either 0 or pi
x = np.sin(2*np.pi*Fc*time+Phase) # Carrier Wave
def Costas_Loop(x, time, PLL_EN=0):
# Costas loop is implemented here
#Initializing internal state of the costas loop, all these variables are local in scope to the Costas Function
x_lo = np.zeros(x.shape)
x_lo_sin = np.zeros(x.shape)
x_VCO = np.zeros(x.shape)
x_VCO_sin = np.zeros(x.shape)
phi_VCO = np.zeros(x.shape)
y = np.zeros(x.shape)
y1 = np.zeros(x.shape)
y2 = np.zeros(x.shape)
ys = np.ones(x.shape)
y1s = np.zeros(x.shape)
y2s = np.zeros(x.shape)
y[0] = 0.4
y1[0] = 0.4
y2[0] = 0.4
ys[0] = 0.4
y1s[0] = 0.4
y1s[0] = 0.4
PLL = PLL_EN
if PLL==1: # We are implementing PLL, ie only one feedback path is implemented
#the result is much greater phase error
for ind in range(1, len(y)):
#VCO output
phi_VCO[ind] = VCO(10, dt, phi_VCO[ind-1], y[ind-1])
x_VCO[ind] = np.cos(phi_VCO[ind])
#Local oscillator output
x_lo[ind] = x_VCO[ind]*x[ind]
#Low pass filter and its internal state
y1[ind] = LPF_Costas(x_lo[ind], y1[ind-1], dt, 6)
y2[ind] = LPF_Costas(y1[ind], y2[ind-1], dt, 9)
y[ind] = LPF_Costas(y2[ind], y[ind-1], dt, 12)
plt.figure(figsize=(14, 5))
plt.plot(time, np.acos(2*y)*180/np.pi)
plt.title("Phase Error (Degrees)")
plt.xlabel("time (s)")
plt.show()
else: # We are implementing Costas Loop, now both feedback paths are implemented and phase error is less
for ind in range(1, len(y)):
# VCO outputs (Both I and Q)
phi_VCO[ind] = VCO(30, dt, phi_VCO[ind-1], y[ind-1]*ys[ind-1])
x_VCO[ind] = np.cos(phi_VCO[ind])
x_VCO_sin[ind] = -np.sin(phi_VCO[ind])
# Local Oscillator outputs
x_lo[ind] = x_VCO[ind]*x[ind]
x_lo_sin[ind] = x_VCO_sin[ind]*x[ind]
# LPFs and their internal states
y1[ind] = LPF(x_lo[ind], y1[ind-1], dt, 6)
y2[ind] = LPF(y1[ind], y2[ind-1], dt, 9)
y[ind] = LPF(y2[ind], y[ind-1], dt, 12)
y1s[ind] = LPF(x_lo_sin[ind], y1s[ind-1], dt, 6)
y2s[ind] = LPF(y1s[ind], y2s[ind-1], dt, 9)
ys[ind] = LPF(y2s[ind], ys[ind-1], dt, 12)
plt.figure(figsize=(14, 5))
plt.plot(time, 4*180/np.pi*y*ys)
plt.title("Phase Error (Degrees)")
plt.xlabel("time (s)")
plt.show()
x11 = -np.sin(2*np.pi*Fc*time-np.pi/180*120)
plt.figure(figsize=(14, 5))
plt.plot(time[0:500], x[0:500])
plt.plot(time[0:500], -x_VCO[0:500])
plt.title("Received Signal and Local Oscillator before lock-in (Notice Frequency drift)")
plt.xlabel("Time (s)")
plt.show()
plt.figure(figsize=(14, 5))
plt.plot(time[-500:-1], x[-500:-1])
plt.plot(time[-500:-1], -x_VCO[-500:-1])
plt.title("Received Signal and Local Oscillator after lock-in")
plt.xlabel("Time (s)")
plt.show()