I am currently writing a script that uses I/Q data to do FSK modulation.

My question is about the FFT plot. I expected to have a peak at 1Hz and 2Hz which are the frequencies that represent binary 0 and 1, however I have peaks at 2 and 4Hz.

I initially suspected that I was using the FFT freq function incorrectly however I set the bin size equal to the number of samples and also defined my sample freq.

Any idea why my FFT frequencies are off?

Thanks in advance

import numpy as np
import matplotlib.pyplot as plt
from scipy.fftpack import fft

binary_in = "10011010" # binary input string
sr = 200  # sampling rate
ts = 1.0/sr # sampling interval
time = (np.arange(0,len(binary_in)*1,ts))-ts  # time for plotting entire FSK signal
step = int((len(time))/(len(binary_in)))  # temporal step size for FSK signal relating to each bit
T = np.arange(0,step/100,0.01) # time for calculating sinusoids

f1 = 1  # frequency for binary 0
f2 = 2 # frequency for binary 1

coeff = 2*np.pi

## I and Q defined to have freq of (f1-f2)/2
I = np.cos(coeff*((f1-f2)/2)*T)
Q = np.sin(coeff*((f1-f2)/2)*T)

## In the case of a binary 1 I and Q would be mixed with the LO freq in the following way:
mixer1 = I*np.cos(coeff*((f1+f2)/2)*T)
mixer2 = Q*np.sin(coeff*((f1+f2)/2)*T)

out1 = mixer1+mixer2   #Output is added to give a FSK signal equal to approx. cos(-f2*t)

## In the case of a binary 1 I and Q would be mixed with the LO freq in the following way:
mixer3 = Q*np.cos(coeff*((f1+f2)/2)*T)
mixer4 = I*np.sin(coeff*((f1+f2)/2)*T)

out2 = mixer3+mixer4  #Approx. sin(f1*t)

final_fsk = []  
bin_plot = []  

ones = [1] * step
zeros = [0] * step

j=0 # index for assigning FSK signal to plotted output list

## Assign correct output freq. based on binary input
for i in range(0, len(binary_in)):
    if binary_in[i] == "1":
        final_fsk[(j*step-(step-1)):(j*step)] = out1
        bin_plot[(j*step-(step-1)):(j*step)] = ones
        final_fsk[(j*step-(step-1)):(j*step)] = out2
        bin_plot[(j*step-(step-1)):(j*step)] = zeros

fourier = np.fft.fft(final_fsk)
# n = np.arange(0, len(fourier))
# period = len(fourier)/sr
# freq = n/period

plt.title("Binary Input")
plt.plot(np.arange(0, (len(binary_in)),ts), bin_plot)

plt.title(f"Output FSK for {binary_in}")
plt.plot(time, final_fsk, 'b')
plt.xlabel('Time (s)')

plt.title("FSK FFT")
plt.plot(freq, np.abs(fourier), 'b')
plt.xlabel('Freq (Hz)')


enter image description here

  • \$\begingroup\$ It would be useful to zoom in the FSK FFT plot to the frequencies of interest. \$\endgroup\$ Commented Feb 16, 2023 at 18:19
  • \$\begingroup\$ You're treating your I and Q oscillator separately when mixing them with your baseband signal. That doesn't work: That's supposed to be a complex multiplication! \$\endgroup\$ Commented Feb 16, 2023 at 19:50
  • \$\begingroup\$ I think this code will get much easier if you just abandon trying to write I and Q separately, but treat them as Real and Imaginary part of a complex signal: then, your tone generation simply becomes \$e^{j 2\pi \pm f\cdot t}\$, and that's it. \$\endgroup\$ Commented Feb 16, 2023 at 19:52
  • \$\begingroup\$ @MarcusMüller: I assume he's trying to simulate physical mixers in the time domain, so there's not going to be complex anything. \$\endgroup\$
    – Eeyn
    Commented Feb 17, 2023 at 3:48
  • 1
    \$\begingroup\$ Mixer1 is multiplying a 1 Hz sinusoid by a 3 Hz sinusoid, that will give you components at the sum and difference frequencies of 4 and 2 Hz, and nothing at 1 Hz. Likewise for Mixers 2, 3, and 4. You add them in various combinations, but adding will not change any frequencies. WIth the right choice of signs when you add them you might cancel the 4 Hz in your final result, but no summing or subtracting will bring back the 1 Hz that never left any of the mixers. \$\endgroup\$
    – Eeyn
    Commented Feb 17, 2023 at 3:51

1 Answer 1


f1-f2 is a 1 Hz sinusoid, and f1+f2 is 3 Hz sinusoid. Regardless of whether they are cosine or sine, ideal multiplication will only have the sum and difference frequencies in the mixer output: 2 Hz and 4 Hz. The phases could be 0, 90, 180, 270 for the various cases but the frequencies will always be 2 Hz or 4 Hz at the mixer outputs.

Then you add mixer outputs. If the signs were chosen correctly that could enable you to cancel various components, which one assumes was your intent. But addition does not shift any frequencies, so no amount of adding and subtracting can get back the 1 Hz that never left any of the mixers. Your output will always be some combination of 2 Hz and/or 4 Hz, with some smearing due to the binary data not being strictly periodic. You can see in your time domain plot that it's 2 Hz and 4 Hz.

Try f1 = 0.5Hz and f2 = 1Hz if you want 1 Hz and 2 Hz in the output.

  • \$\begingroup\$ I chose these frequencies because they would cancel. For example mixing cos((f1-f2)/2) * cos((f1-f2)/2) = 1/2((cos(f1)+cos(-f2)) and mixing sin((f1-f2)/2)*sin((f1+f2)/2) = 1/2((cos(-f2)-cos(f1)) so if we add those two results we get cos(f2) however my fft does not reflect that \$\endgroup\$ Commented Feb 17, 2023 at 13:25

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.