I am new to signal processing and I am trying to demodulate the IQ data, and I am stuck on the carrier frequency offset compensation. I has 1000 samples and it is 1MHz data.

enter image description here enter image description here For the general idea, I think what makes the constellation of IQ data spinning is because of the non-zero value of delta omega, exp($\Delta wt +\phi$).

phase = np.unwrap(np.angle(data))
cfo_hat = (phase[-1]-phase[0])/2/np.pi/(n*Ts)
data_compensated = data*np.exp(-2j*np.pi*cfo_hat*ts)

By using the following code, I made this plot. enter image description here

cfo_hat = (phase[-1]-phase[0])/2/np.pi/(n*Ts)
data_compensated = data*np.exp(-2j*np.pi*cfo_hat*ts)

The last code, I compensated the CFO.

enter image description here enter image description here

After this these work, will I be able to demodulate the data? What have I done wrong and what will be the next step?

-----------------------------------Edit-------------------------------- After the CFO correction, I plotted the constellation graph and it showed like this. enter image description here However, my goal is to make the constellation like the last plot. What can I do? I am really stucked

enter image description here

  • 1
    $\begingroup$ Looks good! So have you looked at the constellation after removing the offset? Where are you stuck? $\endgroup$ Jul 18, 2023 at 12:19
  • $\begingroup$ Yes I have and I assumed that the constellation plot should show like qpsk plot and the values should be in 4 different area. However, my constellation does not show any difference between the original plot and the process plot. I would like to show the graph but I cannnot send in the comment section $\endgroup$
    – Hoon
    Jul 18, 2023 at 15:35
  • $\begingroup$ Please edit your post and you can show that there. It will also be good to superimpose on the plot the one sample per symbol that you have chosen to be at the correct timing location (since to get the expected constellation there can only be one sample per symbol) $\endgroup$ Jul 18, 2023 at 20:00
  • $\begingroup$ I don't understand what you meant by the last comment! But I edited the post and kinda plotted the correct timing location $\endgroup$
    – Hoon
    Jul 19, 2023 at 3:17

1 Answer 1


The OP is correct that the constellation is spinning due to a frequency offset, and removing the phase versus time is the right direction to take (which is frequency offset given frequency is the time derivative of phase). However the residual trend line that the OP determined is not the frequency offset due to the modulation changing the quadrant from sample to sample combined with the unwrapping led to the trend line shown.

A decision directed approach is commonly used for carrier recovery due to its simplicity and ability to remove the modulation and correctly determine over a longer term average the time rate of phase change. I detail the decision directed phase detector at this post which results in a phase error metric for each symbol given by:

$$\phi_\Delta[n] = \hat{I}[n]Q[n]-I[n]\hat{Q}[n]$$

Where $I[n],Q[n]$ is the complex waveform at sample $n$ (at the correct symbol timing locations given one sample per symbol) and $\hat{I}[n],\hat{Q}[n]$ is the closest QPSK decision for that sample. Below shows a graphic with the crosses at the correct "decision" locations for QPSK, along with an example sample as a complex vector given by $V_2 = I+jQ$, and its closest decision given by $V_1 = \hat{I}+j\hat{Q}$.


To select the correct one sample per symbol, timing recovery can be done prior to carrier recover using the Gardner Timing Error Detector which operates on multiple samples per symbol and converges under relatively large carrier offsets. From these samples the OP's code to remove the trend line would work in a post processing approach to demonstrate its operation. The typical approach is to use this within a carrier recovery loop for continuous tracking of the phase change versus time (frequency offset).

I detail a complete carrier recovery implementation at this post showing the decision directed phase detector combined with a numerically controlled oscillator (NCO) resulting in demodulated QPSK symbols with all carrier offsets removed.

Another simple approach applicable to QPSK and QAM, for frequency offsets that are less than a quarter of the Nyquist rate, is to first raise the waveform to the fourth power. This strips the modulation and creates a stronger spectral component at four times the carrier offset frequency which can then be easily tracked with a clean-up PLL. The carrier is then recovered by dividing this cleaned up reference by four.

These other posts also provide further details of carrier recovery for QPSK and QAM.

How do I know this? I teach courses on DSP and Python related to wireless comm through dsprelated.com and the ieee with new courses running soon!


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.