# QPSK: sampling frequency versus carrier frequency

I was trying to write 4QPSK in Python for my self learning based on the book Digital Modulations Using Python.

When I have my sampling frequency set to 4 times the carrier frequency, I get exactly 4 points in the eye diagram using the inphase and quadrature values. Also I get only 4 unique phases with modulated complex value(s_t)

If I change my sample frequency to multiples of any number (say OF) other than 4, my eye diagram gives me OF number of coordinates.

My code is below. Also demodulation is not providing me with correct value even without introducing any noise. For code given below against given input [1 0 1 1 0 0 0 1], got demodulated output as [1 0 1 1 0 1 0 0]

%matplotlib inline
import numpy as np
from scipy.signal import upfirdn #NRZ encoder
import matplotlib.pyplot as plt
baseband_bit_sequence = np.array([1, 0, 1, 1, 0, 0, 0, 1 ])
# Parameters
Fc = 10  # Carrier frequency Hertz
OF = 16   #Over sampling Factor
Fs = OF*Fc   # Sampling frequency Hertz (i.e. Number of samples per second)
uL = OF*2   #Up Sampling repeat

I = baseband_bit_sequence[0::2]
Q = baseband_bit_sequence[1::2]

#Given Baseband bit sequence is very small. So let we up sample the same to get better visualization
#Let we upsample the given baseband_bit_sequence by value uL
#The same bit values in  baseband_bit_sequence will be repeated uL times

I = np.repeat(I, uL)*2 - 1
Q = np.repeat(Q, uL)*2 - 1
#Now we have len(I) Inphase symbols and len(Q) Quadrature symbols (len (I) = len(Q) )
#For a given Sampling frequency Fs, now we have len(I) samples
#The duration (in seconds) taken for sending Fs symbol is 1 second becuase Fs is samples per second
#The duration (in seconds) taken for sending len(I) symbol is ((len(I)/Fs)*1sec)
duration = (len(I)/Fs)*1
#duration = ((len(I) + len(Q))/Fs)*1

#The time taken for sending one symbol is 1/Fs
one_symbol_duration = 1/Fs
#Time axis for duration D at regular interval of 1/Fs is
t = np.arange(0,duration,one_symbol_duration) #time base

I_t = I*np.cos((2*np.pi*Fc*t));Q_t = Q*np.sin((2*np.pi*Fc*t))
#Let we do frequency mixing to postive frequency
s_t = I_t + Q_t * 1j # QPSK modulated baseband signal
print (np.angle(s_t, deg=True)) # I should get only 4 unique phase angle ???
#For eye diagram do not plot all samples
#Only desired samples to be plot to get perfect 4 quadrant in eye diagram
desired_sample_value_index = int(OF/4)
plt.plot(s_t.real[::desired_sample_value_index], s_t.imag[::desired_sample_value_index], 'ro')
plt.plot(s_t.real, s_t.imag, 'ro')
plt.xlabel('Real Part'), plt.ylabel('Imag part')

#QPSK Demodulation
x=s_t*np.cos(2*np.pi*Fc*t) # I arm
y=s_t*np.sin(2*np.pi*Fc*t) # Q arm

x = np.convolve(x,np.ones(uL)) # integrate for L (Tsym=2*Tb) duration

y = np.convolve(y,np.ones(uL)) #integrate for L (Tsym=2*Tb) duration
x = x[uL-1::uL] # I arm - sample at every symbol instant Tsym
y = y[uL-1::uL] # Q arm - sample at every symbol instant Tsym

a_hat = np.zeros(2*len(x))
a_hat[0::2] = (x>0) # even bits
a_hat[1::2] = (y>0) # odd bits

print(a_hat)
print(baseband_bit_sequence)


• Are you doing timing recovery to align your samples with the correct sampling instants and selecting those at the right decimation rate. It helps me to oversample a lot (64x) and then plot the eye diagram of the real and complex to get full visibility. If that will help you and you think that's the issue I can detail that further in an answer. Feb 15, 2023 at 14:18
• I am not trying any timing recovery. Here I am using 4 Phase QPSK. So after QPSK Modulation of 1 amplitude signal and if I plot Real and Image I should have only 4 points (Since no noise is included). Also if i calculate phase of each QPSK modulated complex value, it should carry only 4 unique phase angle. Is my understanding right ?
– KJG
Feb 15, 2023 at 14:31

I reviewed your code and you are not yet running into timing offset issues as I suspected in my initial comments. You simply interpolated more samples per symbol by increasing the oversampling factor, but you didn't change that in the plotting. If for example you double the number of samples per symbol by using OF=8 and just plot as you did before, you will have twice as many samples shown: the desired samples and the ones in between. You need to plot every other sample in this case by doing:

plt.plot(s_t.real[::2], s_t.imag[::2], 'ro')


In which case you will get your expected result.

• Thanks a lot. By picking alternate samples was able to required result. Can you please let me know the reason for picking every other sample in case of OF=8. I repeated the same in OF=16 and selected every 4th value and got the expected result. Now I modified the code to get adapted based on OF value ' desired_sample_value_index = int(OF/4) plt.plot(s_t.real[::desired_sample_value_index], s_t.imag[::desired_sample_value_index], 'ro')  Also having some issue with my demodulated value. Can I know the reason please ?
– KJG
Feb 16, 2023 at 10:39
• We don’t do detailed code debugging but I suggest continuing your efforts to understand line by line what you are doing here and that will all be clearer. As for the samples, consider as you keep upsampling you are approaching a continuous waveform within a symbol period but only one of those will be the “correct” sample for that symbol. Feb 16, 2023 at 11:37
• Looks like demodulation was failing for Quadrature phase (Q) since I used complex value for manipulating the modulation s_t = I_t + Q_t * 1j # QPSK modulated baseband signal Instead if I do simple addition of Inband and Quadrature value, demodulation worked fine s_t = I_t + Q_t # QPSK modulated baseband signal
– KJG
Feb 17, 2023 at 7:58

A long and tedious harangue of little interest to the cognoscenti who should fast-forward to the last paragraph (after the dotted line separator) for the only relevant stuff.

A standard representation of a QPSK signal over the symbol interval $$[0,T)$$ is $$s(t) = (-1)^{b_I}\cos(2\pi f_c t) - (-1)^{b_Q}\sin(2\pi f_c t)$$ where $$\cos(2\pi f_c t)$$ and $$-\sin(2\pi f_c t)$$ are the inphase and quadrature carrier signals at frequency $$f_c$$ Hz and $$b_I, b_Q \in \{0,1\}$$ are the two data bits (called the inphase and quadrature data bits transmitted during that symbol interval, naturally, since they are transmitted on the inphase and quadrature carriers). Equivalently, looking at the matter from the perspective of phase-shift-keying we see that a $$0$$ bit means that the carrier ($$\cos(2\pi f_c t)$$ or $$-\sin(2\pi f_c t)$$ as the case may be) is transmitted with no change in phase while a $$1$$ data bit creates a change in phase (we will think of it as a phase delay) of $$180$$ degrees or $$\pi$$ radians. Thus, another way of expressing the QPSK signal is as $$s(t) = \cos(2\pi f_c t - b_I\pi) - \sin(2\pi f_c t - b_Q\pi)$$ which makes the phase-shift-keying viewpoint very clear. Note that the QPSK signal is one of the following four signals: $$\sqrt{2}\cos\left(2\pi f_c t + \frac{\pi}{4}\right),\\ \sqrt{2}\cos\left(2\pi f_c t + \frac{3\pi}{4}\right),\\ \sqrt{2}\cos\left(2\pi f_c t + \frac{5\pi}{4}\right),\\ \sqrt{2}\cos\left(2\pi f_c t + \frac{7\pi}{4}\right)$$ corresponding to $$(b_I,b_Q) = (0,0), (1,0), (1,1), (0,1)$$ respectively. As a final note, it is very convenient if $$f_cT$$ is an integer so that the inphase and quadrature carriers complete an integer number of cycles in one symbol interval. Otherwise, the QPSK signals will differ from symbol interval to symbol interval.

"Set phasers on Stun!"
It is convenient to represent sinusoidal signals of frequency $$f_c$$ Hz as phasors -- vectors in the complex plane that rotate about the origin with frequency $$f_c$$ and thus complete one rotation in $$T_c = f_c^{-1}$$ seconds. It is conventional that we don't look at phasors all the time as they rotate but only at multiples of $$T_c$$ seconds; think of a strobe light that flashes on at $$T_c$$ second intervals and illuminates the phasors very briefly making the phasor representation static. The tips (arrowhead ends) of the phasors of the four possible QPSK signals shown above are the four constellation points that the OP seeks to observe.

With that as prologue, consider that the OP has chosen $$f_c = 10$$ and so $$T_c$$ is 100 milliseconds. I don't read Matlab/Python at all well but I think that the OP has chosen $$T$$ to be 1 second. But, he is sampling the signal (looking at the position of the rotating phasor) $$16$$ times in that 100 millisecond period! Thus, he is observing 16 "constellation points" including the 4 mentioned above, whereas over the one-second symbol epoch, the phasor is just one of the constellation points, and he should be getting just one constellation point. Of course, the OP has 4 symbol epochs during which all four of the signals mentioned above occur, so that, if done right, he would get all four actual constellation points, but given the way too much oversampling that he has chosen, he gets 16 alleged constellation points.