def VCO(K, dt, phi_0, f, F0):
phi = phi_0+2*np.pi*(F0+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
RC = 1/(f_C*2*np.pi)
y = y0+(x-y0)/RC*dt
return y
def Costas(x, Fc, Error, time):
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 = 0
if PLL==1: # We are implementing PLL
for ind in range(1, len(y)):
phi_VCO[ind] = VCO(10, dt, phi_VCO[ind-1], y[ind-1], Fc-Error)
x_VCO[ind] = np.cos(phi_VCO[ind])
x_lo[ind] = x_VCO[ind]*x[ind]
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, y)
plt.title("y")
plt.xlabel("Time (s)")
plt.show()
else: # We are implementing Costas Loop
for ind in range(1, len(y)):
phi_VCO[ind] = VCO(30, dt, phi_VCO[ind-1], y[ind-1]*ys[ind-1], Fc-Error)
x_VCO[ind] = np.cos(phi_VCO[ind])
x_VCO_sin[ind] = -np.sin(phi_VCO[ind])
x_lo[ind] = x_VCO[ind]*x[ind]
x_lo_sin[ind] = x_VCO_sin[ind]*x[ind]
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)
y1s[ind] = LPF_Costas(x_lo_sin[ind], y1s[ind-1], dt, 6)
y2s[ind] = LPF_Costas(y1s[ind], y2s[ind-1], dt, 9)
ys[ind] = LPF_Costas(y2s[ind], ys[ind-1], dt, 12)
plt.figure(figsize=(14, 5))
plt.plot(time, 4*180/np.pi*y*ys)
plt.title("y")
plt.xlabel("Phase Error (Degrees)")
plt.show()
plt.figure(figsize=(14, 5))
plt.plot(time, x_VCO)
plt.title("x_VCO")
plt.xlabel("Time (s)")
plt.show()
plt.figure(figsize=(14, 5))
plt.plot(time, phi_VCO)
plt.title("Phase")
plt.xlabel("Time (s)")
plt.show()
plt.figure(figsize=(14, 5))
plt.plot(time, x)
plt.title("Input")
plt.xlabel("Time (s)")
plt.show()
return x_VCO
def RX_Demod(Carrier, Fc, t):
# This function multiplies the received signal with a pure carrier wave, for now Costas loop is assumed
Local_Carrier = Costas(Carrier, Fc, 2, t) # Assumed given somehow
Recovered_Signal = Carrier*Local_Carrier
plt.figure(figsize=(14, 5))
plt.plot(t, Recovered_Signal)
plt.xlabel("Time")
plt.title("Signal after local oscillator multiplication")
plt.show()
return Recovered_Signal
def Envelope(x, Fs):
# Forward Euler based envelope detector
RC = 0.1
y = np.zeros(x.shape)
for ind in range(1, x.shape[0]):
if x[ind]>y[ind-1]:
y[ind] = x[ind] # The diode conducts
else:
y[ind] = y[ind-1]-y[ind-1]/RC/Fs # forward Euler Differential equation
#if ind> 206 and ind <210:
#print(str(x[ind]) + " and "+ str(x[ind-1]))
#print(str(y[ind]) + " and "+ str(y[ind-1]))
return y
def LPF(x, Fs):
# Forward Euler based LPF after the envelope detector to remove noise
RC = 0.05
y = np.zeros(x.shape)
for ind in range(1, x.shape[0]):
y[ind] = y[ind-1]+(x[ind]-y[ind-1])/RC/Fs
return y
def Single_Bit_ADC(Data_wave, t, Fsym):
# Compares the signal to a single threshold
count = 1/2
Datum = np.zeros(Data_wave.shape)
while 1/Fsym*count < np.max(t):
time_index = np.where(t>1/Fsym*count)[0][0] #The first sample near the sampling event determined by the count function
sample = Data_wave[time_index]
comparator = sample<0.5
Datum[time_index:] = (comparator)
count = count+1
return Datum
# Local oscillator multiplication
RX_Demodulated_Signal = RX_Demod(Channel_Signal, Fc, t)
# Data envelope generation
Data_Envelope = Envelope(RX_Demodulated_Signal, Fs)
# Further Low pass filtering
Data_LPF = LPF(Data_Envelope, Fs)
# ADC Stage
Data_raw = Single_Bit_ADC(Data_LPF, t, Fsym)
FFT_gen(Channel_Signal, Fs, "Spectrum Before LO", 50)
FFT_gen(RX_Demodulated_Signal, Fs, "Spectrum After LO", 50)