Jump to content
  • 0

Waveforms Spectrum Analyzer and Zero Padding


Phil_D

Question

Does the Waveforms spectrum analyzer have options for zero padding?

I've seen in other FFT applications that increased zero padding can improve the look of an FFT at low frequencies when monitoring over a wide frequency range, like 10kHz to 20MHz.

Link to comment
Share on other sites

11 answers to this question

Recommended Posts

Hi @Phil_D

There is no zero padding option but it can be done with Script like this:

var rg = Spectrum.Channel1.data // channel 1 time domain data
var c = rg.length
var t = rg[c-1] // last sample
for(var i = 0; i < c; i++) rg.push(t) // 2x padding 

var rghz = Spectrum.Trace1.frequency
var hz = 2.0*rghz[rghz.length-1]
//var hz = 2.0*Spectrum.Frequency.Stop.value // scope sample rate
Spectrum.Trace5.setSamples(rg, hz)

image.thumb.png.cd513ed26655207046f5ec64d7e2e9c4.png

 

Some other suggestions to improve the resolution:

1. For lower frequencies, with 1MHz sampling you can use the Scope to perform a longer recording. This will highly improve the resolution in the FFT view.

2. With AD you can select the second device configuration to have 16k Scope buffer.

3. You can select a higher bandwidth window, like rectangular or cosine.

4. In the latest beta version with CZT algorithm you can select higher number of bins, higher resolution.
https://forum.digilentinc.com/topic/8908-waveforms-beta-download/

Here:
- T1 is CZT BlackmanHarris 10x BINs, 244Hz resolution
- T2 is FFT BlackmanHarris 4k BINs 2.4kHz resolution
- T3 is FFT Cosine 4k BINs 2.4kHz resolution

 

image.png.51c399b9dbcdfe3c347b6279ff91fb55.png

Link to comment
Share on other sites

Hi @Phil_D

It can be done like this:

const NAVG = 10
Spectrum.run()
var sum = []
for(var acq = 1; acq <= NAVG && Spectrum.wait(); acq++){
    var hz = Spectrum.Channel1.dataRate // WF v3.11.2
    { // padding in Trace 5
        var rg = Spectrum.Channel1.data // channel 1 time domain data
        var c = rg.length
        var t = rg[c-1] // last sample
        for(var i = 0; i < c; i++) rg.push(t) // 2x padding 
        Spectrum.Trace5.setSamples(rg, hz)
    }
    { // averaging in Trace 6
        var rg = Spectrum.Trace5.magnitude
        if(acq==1){
            sum = rg;
            Spectrum.Trace6.Clone(Spectrum.Trace5)
        }else{
            rg.forEach(function(v,i){ sum[i] += v;}) // sum
            sum.forEach(function(v,i){ rg[i] = v/acq;}) // average
            Spectrum.Trace6.setMagnitude(rg, 0, hz/2)
        }
    }
}
Spectrum.stop()

 

Link to comment
Share on other sites

This is great! Thanks @attila!

Does this script end up with a rectangular window?   The padded, averaged result from the script looks very different from our previous unpadded result.  Is there a way to set the type of windowing function that is used in your script?  We'd like to use a Hann window.  I tried to look through other objects/functions with cntrl+space, but didn't see any way to set the window type.

Also, if I wanted more than 2x padding, would I do something like

for(var i = 0; i < 2*c; i++) rg.push(t) //4x padding???

Thanks!

 

Link to comment
Share on other sites

Hi @Phil_D

You can configure the window for Trance 5 in the Spectrum interface.
The script feeds the time domain data to T5 and this performs the FFT. The script takes magnitude array from T5, performs averaging and sets this to T6.

I'm not sure if the padding is useful.
To improve or artificially increase the resolution you should use the other options I pointer out in the first reply.

For 4x padding you should append 3x more samples to the end of the buffer:
for(var i = 0; i < 3*c; i++) rg.push(t)

Adding much padding will push the effective signal to the edge of the window, damping and attenuating it further.
Here to demonstrate the effect: M1 generates a Hann window, M2 applies it on the data. The originally +5.3dBV signal is seen as -11dBV.

image.thumb.png.2b23c2a7e61a18cbe158fffcee378f82.png

Link to comment
Share on other sites

Thank  you for the continued support on this, @attila!

However, I think that the FFT window function is not working right when we pad before doing the windowing function.  I'll explain:

I'm using another signal processing program called Sigview, and I can add zero padding there as well. I did a bunch of exporting and graphing in Excel to compare padding between Sigview and Waveforms:

These are the two FFTs I'm getting in Waveforms, no padding in red and 2x padding in green. You can see that the 2x padding trace in green does not look at all like the red trace without padding.
image.png.5248a59808899903267a491e4a205724.png

This graph below is comparing Waveforms and Sigview, both Hann windows, both using no padding. They are basically identical, except for a 3dB offset. I used magnitude in Sigview and RMS (dB) in Waveforms, which I think accounts for that 3dB. But the important thing is that the shape of the FFTs is identical.  Same data in both cases, same window (Hann), both programs produce the same FFT.

image.png.175614447dff12ed2ab62b3d6a09fc6d.png

Now, I add padding in Sigview.  Blue is 2x padding, magenta is no padding. The traces are nearly identical, except the padding in the blue trace helps smooth out the peaks. I think this is how the padding should work.
image.png.8a9d70bd1ad3a759e72a4d47844e44d1.png

We were wondering if it would be possible to do the math to apply the Hann window on the data array in the script *before* adding the padding?  So the order of operations would be:
1. Apply the Hann window to the data

2. Add zero padding

3. Take the FFT

It looks like you added the padding, then did the window, then did the FFT.  As you mentioned, applying the window after the padding really attenuates the actual data and adds more emphasis to the 0's.

I would like to add the padding in Waveforms so we can take advantage of the averaging. With Sigview, I'd have to import many static datasets, process each one separately, and then average in C++, which would be much more laborious and time consuming.

We found a couple of links that talk about this padding and windowing:
https://dsp.stackexchange.com/questions/13736/zero-pad-before-or-after-windowing-for-fft/13740

http://www.mechanicalvibration.com/Zero_Padding_FFTs.html

Thanks!

 

 

Link to comment
Share on other sites

Hi @Phil_D

Sure, it makes more sense to apply the window only on the real data section.

const NAVG = 10
const NPAD = 2
Spectrum.Trace5.Window.text = "Rectangular" // disable windowing
Spectrum.run()
var win = []
var sum = []
for(var acq = 1; acq <= NAVG && Spectrum.wait(); acq++){
    var hz = Spectrum.Channel1.dataRate // WF v3.11.2
    { // padding in Trace 5
        var rg = Spectrum.Channel1.data // channel 1 time domain data
        var c = rg.length
        if(acq==1){ // create window
            var w = 0
            for(var i = 0; i < c; i++) {
                var v = pow(sin(PI*i/(c-1)), 2.0) // Hann
                w += v
                win.push(v)
            }
            w /= c*NPAD
            for(var i = 0; i < c; i++) { // normalize
                win[i] /= w
            }
        }
        for(var i = 0; i < c; i++)  rg[i] = win[i]*rg[i] // apply window
        for(var i = 0; i < c*(NPAD-1); i++) rg.push(0) // padding 
        Spectrum.Trace5.setSamples(rg, hz)
    }
    { // averaging in Trace 6
        var rg = Spectrum.Trace5.magnitude
        if(acq==1){
            sum = rg;
            Spectrum.Trace6.Clone(Spectrum.Trace5)
        }else{
            rg.forEach(function(v,i){ sum[i] += v;}) // sum
            sum.forEach(function(v,i){ rg[i] = v/acq;}) // average
            Spectrum.Trace6.setMagnitude(rg, 0, hz/2)
        }
    }
}
Spectrum.stop()

image.thumb.png.df915ce6b93df8a2c5931046caa1958a.png

 

Here you can see that CZT produces the same result as the padding:
The noise floor difference is due to different captures.

image.thumb.png.c4dc093823687e394d852a3c1cf0870b.png

 

The FFT works on power of two + 1 BINs, with CZT you can specify arbitrary number of BINs, setting fine resolution:

image.thumb.png.85321895586bca280de6a6931aad5516.png

 

Link to comment
Share on other sites

One more hopefully quick question here:

I would like to export the data after all of the windowing/padding/averaging is complete.

I'm using:

Spectrum1.Export("C:/Users/User/pjm_2019/data.txt")

...but this gives me the results for Trace1, Trace5, and Trace 6.

Is there a way to export only Trace 6? 

I tried:

Spectrum1.Trace6.Export("C:/Users/User/pjm_2019/data.txt")

but that gave me an error.

Thanks!

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...