Square wave FFT results show frequency that seems too low? I have created an open source plotting package for a low cost oscilloscope - see link here: GitHub Plotting Software

But during my testing I have one result that may be wrong: a noisy 7 kHz square wave shows an FFT peak at 6.25 kHz. Can this be correct, or am I missing something?

Here is a python code extract for my FFT




# stack FFT plot in this figure vertically
plt.subplot(grid_stack[grid_row, :])

y_heading = self.y_heading

# Reduce signal_data array size by a factor of n (used to reduce the frequency range)
n = self.n
updated_df_data = self.reduce_sample_array(self.df_data, n)

# update sample rate and array size
sample_rate = float(self.info_dict['rate'][1])/n
array_size = updated_df_data[y_heading].size

# Calculate y-axis magnitude scaled to same units as y-axis in heading2Use data (ie volts)
yf = 2/array_size * fft(updated_df_data[y_heading].values)

# Calculate x axis as frequency in Hz
x = fftfreq(array_size, 1 / float(sample_rate))
x_half = x[:x.size//2]

freq_units, freq_multiplier = rescale_frequency(x_half)
x_half = rescale_data(x_half, 10 ** freq_multiplier)

y_half = abs(yf)[:array_size//2]
engr_power_v = calculate_scale(y_half)
y_half = rescale_data(y_half, 10**engr_power_v)

plt.plot(x_half, y_half, color=self.iplot_colors[-1])


Sample plot with clean square wave that seems to be ok



Sample plot with noisy square wave that seems to have wrong peak value (6.25 instead of 7 kHz)



Any comments or suggestions are welcome as this is my first python signal analysis software project.
The frequency resolution of your FFT is determined by its length and the sampling rate.

In your second plot you show Fs = 10e6 and 3200 samples. Assuming you don't zero-pad these samples when you pass them to the FFT this gives you a frequency resolution of 3125Hz:

$$ 3215 = \frac{10 \times 10^6}{3200} $$

So at lower frequencies you have frequency bins as 0Hz, 3125Hz, 6250Hz and 9375Hz. The largest peak in the FFT will be in the bin closest to the signal fundamental frequency.

If you want better resolution you either need to capture more samples, or append a load of zeros to the end of your time series (zero-padding).

I would recommend reading about windowing for FFT analysis also, especially if you go down the zero-padding route.

As an aside: the reduce_sample_array function seems to decimate the time series by simply discarding samples. You really should be doing proper down-sampling here, to avoid aliasing.