Edit: Regarding 80kHz: Yet another point to consider is the frequency deviation between the PLL xtal and the MCU xtal. If the (normalized) deviation between the two oscillators is (say) 20ppm, this leads to a frequency error of 0.3Hz when the IF frequency is 15kHz, but at 80kHz IF, the error is already 1.6Hz. However, the filter bandwidth is independent of the IF frequency. This means that the frequency error to bandwidth ratio is higher at 80kHz than at 15kHz. Still no problem yet and 1.6Hz error is still negligible (at least for magnitude estimation). But when we reduce the filter bandwidth more and more (by vector accumulation of more and more samples), in order to get more and more dynamic range, then at some point the frequency error will begin to matter. I'm also unsure whether the frequency deviation of all LTDZ devices is as low as on yours.
/** Default RX LO frequency offset in Hz in NA mode when using new ADC dB mode */
#define CONFIG_RX_DEFAULT_NA_MODE_FREQ_OFFSET_Hz (10*8000L)
and regenerating the sin/cos-tables for the given LO offset frequency with the windowing applied. For this I have GNU Octave/Matlab script.
In this example, the vector sums for I and Q can continue accumulating
This implies eventually that you implicitly repeat the shorter (975 samples) window function N times, in order to to form the total window function which spans all N*975 samples. And this N-fold repetition leads to the weird frequency response then. But although the frequency response of this filter looks weird, it can still work for the given purpose, if the signal spectrum contains only noise at the position of the high side lobe (which is likely granted here).
For example I do not have strong digital signal processing background
Since DFT assumes that the signal to be analyzed is repetitive, proper windowing needs to be applied (or the buffer length needs to be selected appropriately so that the buffer length is exact multiple of the DFT bin frequency).
On the other hand, if we repeat this same process N times for different signal sample sets (while maintaining the phase, which is true in this case), this process will become as coherent averaging (Lyons, Understanding Digital Signal Processing, 3rd. ed. Chapters 11.1 Coherent Averaging and 11.3 Averaging Multiple Fast Fourier Transforms). As I understand, this process is similar to as if were are performing [synchronous] FFT averaging over multiple FFT transforms with zero time-domain overlap. At the end of the chapter Lyons states that " ... it [coherent FFT integration] only works for periodic time-domain signal sequences that have been obtained through careful synchronous sampling. Coherent integration of multiple FFT results is of no value in reducing spectral measurement noise for non-periodic real-world, information carrying signals". Since we are satisfying this property (we are sampling stationary sinusoidal wave from LO mixer output with synchronous sampling*), it is possible to use coherent DFT integration and obtain increase in SNR. Please correct me if I have understood this wrong.
Edit: OTOH, when I wrote "we are sampling stationary sinusoidal wave from LO mixer output with synchronous sampling", this may not be 100% true because there is a small frequency difference between the ADC sampling clock and LO mixer output frequency, although it will remain the same through out the sampling process in practical terms. As there is a small frequency offset present, the phase will change a little between sample sets. So, I am not quite sure how much this will impact the SNR gain, as we are using qudrature correlator.
About synchronous sampling: Would it make any sense to use an ADC sample buffer of, let's say, 975 samples for 80kHz LO offset, and average N*975 samples into another "working" buffer, and only after N averaged sample sets compute the DFT? If the phase stays the same in practical terms between sample sets, this would reduce the real-time processing requirements, and allow longer sampling times without need for longer sample buffers. If the phase changes too much between sample sets, the SNR gain will be reduced (because the sampling process is no longer synchronous).
sum(sum(x .* repmat(lo,N,1) .* repmat(win,N,1)))
sum(sum(x) .* lo .* win)
[ where x is a Nx975 matrix, and win and lo are 1x975 row vectors ]Let's assume that we are sampling N samples into a buffer. We have selected N in such way that ADC sampling frequency / LO frequency over N will become integer in all practical terms (for Fs=72Mhz/6/14=857142.8571428572_Hz and LO=80kHz: Fs/LO=10.71428571428, we could select N = 93 * 10.71428571428 = 996.42857142857 = 996, for example). Now, we will compute the DFT for the given LO frequency of 80kHz (using quadrature correlator with pre-computed sin/cos tables with Hann-window). The RMS for this DFT = sqrt(sum(Q)^2 + sum(I)^2). No weird spectrum here.
Now, let's extend this a little, and that we are sampling K*N consecutive samples into a buffer. We will then divide this buffer into K blocks, each size of N samples. Then, we will compute DFT and RMS for each individual blocks of N samples. Finally, we will combine the results from all K blocks into one RMS value. There should not be no weird spectrum here, because we are just averaging K * individual DFT results. Right?
for Fs=72Mhz/6/14=857142.8571428572_Hz and LO=80kHz: Fs/LO=10.71428571428, we could select N = 93 * 10.71428571428 = 996.42857142857 = 996, for example
(Edit: The frequency plan is just an initial draft - there is still room for fine-tuning)
Edit: One more thing:Quotefor Fs=72Mhz/6/14=857142.8571428572_Hz and LO=80kHz: Fs/LO=10.71428571428, we could select N = 93 * 10.71428571428 = 996.42857142857 = 996, for example
In order that you can continue a vector sum coherently after accumulating N samples, by wrapping-around the sin/cos tables, an exact integral multiple of LO periods need to fint into the N samples. For fLO = 80kHz, this applies to N=75, or any integral multiple of 75, but not to 996.
Edit: One more thing:Quotefor Fs=72Mhz/6/14=857142.8571428572_Hz and LO=80kHz: Fs/LO=10.71428571428, we could select N = 93 * 10.71428571428 = 996.42857142857 = 996, for example
In order that you can continue a vector sum coherently after accumulating N samples, by wrapping-around the sin/cos tables, an exact integral multiple of LO periods need to fint into the N samples. For fLO = 80kHz, this applies to N=75, or any integral multiple of 75, but not to 996.
And this applies only using 72MHz and 7.5cy ADC sampling time (see my post above). If the system would use 72MHz and 1.5cy ADC sampling time, the 996 samples would not provide exact integer multiple of LO cycles. But it may be possible to nudge the ADC sample buffer pointer for each DFT computation so that the sampling will look practically coherent for each DFT computation.
if you are willing to accept a gap at the center of the lobe, then I've aproposal for SA mode. Actually is is based on the above principle, with K=N=64, and doing RMS averaging of the individual sub-results. I've chosen the frequency plan to exclude the spurs and also the noise near DC. My expectation were a DR improvement of say 15+ dB, compared to simple RMS averaging over all samples. It is still rather wide-band (approx +/- 67kZh @-3dB), in order that the center gap remains small when compared to the total BW.
For NA usage, 12/14 MSa/s and 80kHz IF are fine. Seven 80kHz cyles == 75 samples then.
If a later SA goal were stiching the spectrum from multiple scans, then freqency steps which are in sync with DFT bins can be helpful.
Then 1MSa/s might be indeed be favorable, even if the CPU power is a bit lower then.
Edit: E.g. 4000 samples @1MSa/s would lead to "nice" 250Hz bins, and 8kHz were 32 bins then.
if you are willing to accept a gap at the center of the lobe, then I've aproposal for SA mode. Actually is is based on the above principle, with K=N=64, and doing RMS averaging of the individual sub-results. I've chosen the frequency plan to exclude the spurs and also the noise near DC. My expectation were a DR improvement of say 15+ dB, compared to simple RMS averaging over all samples. It is still rather wide-band (approx +/- 67kZh @-3dB), in order that the center gap remains small when compared to the total BW.
Attached is the expected frequency response of this RBW filter. As said, unfortunately with gap at the center . But there are too much spurs, noise and DC offset error in the center. Excluding it enables a higher dynamic range. IMO the gap needs to be filled eventually by stitching multiple measurements with frequency offset.
Edit: updated Link: https://godbolt.org/z/PG461P1jW
I expect a noise floor of roughly -67dBm (IF level), or a DR of almost 70dB if the max. IF level is 3dBm.
Edit: I played a bit with the gap width vs. DR trade-off.
At the cost of ~0.85dB the gap can be reduced by 40kHz, and reduction by 48 kHz costs ~1.5dB (see attached 2nd diagram).
(In the sin/cos tables this meens 36kHz or 32kHz instead of 56kHz)
Vector averaging of the individual DFT results turns out to be mathematically equivalent to calculating the DFT over the whole K*N sized window, using a window function which is a K-fold repetition of the shorter N-tap window function. Just write-down the equation for sum that is calculated, for both cases, re-arrange them acordingly, and you'll see that both are the same. Consequence is that the equivalent filter frequency response is eventually that of the K-fold repeated N-tap window (i.e. the "weird" one).
Edit: I mean the DFT-term for the single frequency bin that is considered. The complete K*N sized DFT contains of course more frequency bins.
Due to a fact that we are actually performing coherent sampling, we can integrate the real and the imaginary parts separately over all blocks before computing the RMS of the signal, which will increase the SNR. But this will not introduce weird spectral response.
// complex sine wave
lo = exp(-1i*2*pi*freq*[0:74]/fs);
// 4 chunks of samples
samp_1 = rand(1,75);
samp_2 = rand(1,75);
samp_3 = rand(1,75);
samp_4 = rand(1,75);
// regular coherent summation over all samples, note: window function spans all 75*4 samples!
w_all = hanning(75*4,"periodic")';
iq_all = sum([samp_1 samp_2 samp_3 samp_4] .* [lo lo lo lo] .* w_all);
// cohrerent summation in chunks
w = hanning(75,"periodic")';
iq_1 = sum(samp_1 .* lo .* w);
iq_2 = sum(samp_2 .* lo .* w);
iq_3 = sum(samp_3 .* lo .* w);
iq_4 = sum(samp_4 .* lo .* w);
iq_sum = iq_1 + iq_2 + iq_3 + iq_4;
// this is the same as
iq_sum = sum([samp_1 samp_2 samp_3 samp_4] .* [lo lo lo lo] .* [w w w w]);
w_1 = w_all(1:75);
w_2 = w_all(76:150);
w_3 = w_all(151:225);
w_4 = w_all(226:300);
iq_1 = sum(samp_1 .* lo .* w_1);
iq_2 = sum(samp_2 .* lo .* w_2);
iq_3 = sum(samp_3 .* lo .* w_3);
iq_4 = sum(samp_4 .* lo .* w_4);
iq_sum = iq_1 + iq_2 + iq_3 + iq_4;
// this is the same as
iq_sum = sum([samp_1 samp_2 samp_3 samp_4] .* [lo lo lo lo] .* [w w w w]);
I was originally thinking that the system would provide four different sweep rates, for example 0.5K, 1K, 2K and 4K samples, and the system would need to have four set of windowed sin/cos tables: 0.5K, 1K, 2K and 4K in size. Since this is not very flexible and would waste Flash memory because of four set of tables, I thought that the K*N sample approach would be more flexible and more economic.
N=75; K=4;
w_all = hanning(N*K,"periodic")';
iq_all = sum([samp_1 samp_2 samp_3 samp_4] .* [lo lo lo lo] .* w_all);
iq_sum = sum([samp_1 samp_2 samp_3 samp_4] .* [lo lo lo lo] .* [w w w w]);
This is something that I do not understand again: While fft(w) and fft(w_all) look as usual, fft([w w w w]) is the "weird" frequency response, with the extra side lobe. Why this fft([w w w w]? At least my intention is more like fft(w)+fft(w)+fft(w)+fft(w), like averaging four different FFTs/DFTs. Since we are capturing in coherent way, we can integrate over all four FFTs/DFTs in order to get better dynamic range, compared to four individual FFTs/DFTs
sum(SAMPLES .* LO .* WINDOW)
where SAMPLES, LO, WINDOW are row vectors of equal size (-> number of samples).iq_1 = sum(samp_1 .* lo .* w);
iq_2 = sum(samp_2 .* lo .* w);
iq_3 = sum(samp_3 .* lo .* w);
iq_4 = sum(samp_4 .* lo .* w);
iq_sum = iq_1 + iq_2 + iq_3 + iq_4;
which is the same asiq_sum = sum([samp_1 samp_2 samp_3 samp_4] .* [lo lo lo lo] .* [w w w w]);
then you effectively calculate a single DFT term for SAMPLES=[samp_1 samp_2 samp_3 samp_4], LO=[lo lo lo lo] and WINDOW=[w w w w]fs = 12000000/14;
freqs = 30000:100:130000;
lo = exp(-1i*2*pi*80000*[0:74]/fs);
w = hanning(75,"periodic")';
w /= sum(w);
H = freqs .* 0;
for i = 1:length(freqs)
f = freqs(i);
samples = sin(2*pi*f*[0:299]/fs);
for j=0:75:299
H(i) += sum(samples(j+1:j+75) .* lo .* w) / 2;
end
end
clf
plot(freqs,20*log10(abs(H)));
ylim([-100 0])
xlim([freqs(1) freqs(end)])
grid on
fs = 12000000/14;
% approx. 100Hz resolution for the plot
NFREQ=floor(fs / 100 + 0.5); % number of frequency points
f = [0:NFREQ-1] / NFREQ * fs - fs / 2; % frequency scale
% the window functions we want to plot
w = hanning(75, "periodic")';
w_all = hanning(75*4, "periodic")';
w4 = [w w w w];
% normalize to sum==1, in order that we get 0dB for DC
w /= sum(w);
w_all /= sum(w_all);
w4 /= sum(w4);
% zero-fill in order to interpolate and align different lengths
w = [w zeros(1,NFREQ-length(w))];
w_all = [w_all zeros(1,NFREQ-length(w_all))];
w4 = [w4 zeros(1,NFREQ-length(w4))];
clf; hold on;
plot(f,20*log10(abs(fftshift(fft(w)))),";w;")
plot(f,20*log10(abs(fftshift(fft(w_all)))),";w-all;")
plot(f,20*log10(abs(fftshift(fft(w4)))),";w4;")
xlim([-50000 50000])
ylim([-100 0])
grid on
# Reference oscillator frequency
global REF_Hz = 25000000;
# Reference doubler: 0|1
global D = 0;
# Refecence divide by 2: 0|1
global T = 1;
# Preset divide ratio in range [1, 1023]
global R = 100;
# Modulus in range [2, 4095]
global MOD = 4095;
# PFD frequency
global pfd_Hz = REF_Hz * ((1 + D)/(R * (1 + T)));
# Compute ADF4351 INT and FRAC register values for the given frequency.
#
# f Desired frequency 35MHz <= f < 4400MHz.
# INT INT-register value.
# FRAC FRAC-register value.
# rf_Hz Actual output frequency.
# err_Hz Frequency error.
#
function [INT, FRAC, rf_Hz, err_Hz] = adf4351_reg(f)
global REF_Hz;
global MOD;
global D;
global T;
global R;
global pfd_Hz;
if f <= 68750000
RFDIV = 64;
elseif f <= 137500000
RFDIV = 32;
elseif f <= 275000000
RFDIV = 16;
elseif f <= 550000000
RFDIV = 8;
elseif f <= 1100000000
RFDIV = 4;
elseif f <= 2200000000
RFDIV = 2;
else
RFDIV = 1;
endif
VCO = f * RFDIV;
assert(2200000000 <= VCO && VCO <= 4400000000);
INT = fix(VCO / pfd_Hz);
assert(75<= INT && INT <= 65535);
FRAC = round(((VCO / pfd_Hz) - INT) * MOD);
assert(0 <= FRAC && FRAC <= (MOD-1));
rf_Hz = round(REF_Hz * ((1 + D)/(R * (1 + T))) * (INT + FRAC / MOD) / RFDIV);
err_Hz = f - rf_Hz;
endfunction
>> [INT, FRAC, f, err] = adf4351_reg(35000000)
INT = 17920
FRAC = 0
f = 35000000
err = 0
>> [INT, FRAC, f, err] = adf4351_reg(35000001)
INT = 17920
FRAC = 2
f = 35000001
err = 0
>> [INT, FRAC, f, err] = adf4351_reg(35000002)
INT = 17920
FRAC = 4
f = 35000002
err = 0
R=100 is quite much. I wonder what's the phase noise then. My understanding is: Lower fPFD -> larger INT -> more phase noise. To mitigate phase noise then, I also wonder whether the loop filter bandwidth would need to be reduced (significantly), at the expense of a larger lock time.
See also https://www.analog.com/media/en/training-seminars/tutorials/MT-086.pdf