Author Topic: Reducing Sine Waveform THD by Going Around Rigol Waveform Generator Bug  (Read 423 times)

0 Members and 1 Guest are viewing this topic.

Offline bb1Topic starter

  • Contributor
  • Posts: 46
  • Country: us
I found that Rigol DG1022z output waveform THD can be substantially decreased.

This is based on observation of Sine waveform zero-crossing.
To see separate steps near the zero-crossing the signal amplitude was chosen to be 20Vpp, the highest possible.
For 20 Vpp and 14 bit DG1022z DAC resolution one waveform step should be slightly larger than 1 mV.
However, to avoid overloading of the scope input by 20 Vpp on 2 mV/div range,
I used limiter consisting of 10k resistor connected sequentially with
two anti-parallel germanium diodes. Near zero voltage (<<25mV) the germanium diodes have resistance of several tens of kOms,
which reduced the Sine waveform step amplitude to less than 1 mV.

To see the steps more clearly the Sine signal frequency was set very low, 0.1Hz, and averaging was used.
The averaging was made more reliable by triggering on the Sync output
of the DG1022z, signal of which is shown by blue trace on the attached
BUG_dg1022z_Sine0.1Hz20Vpp_averaged.png.

The sine signal, shown by yellow trace, is obviously non-monotonic.
This increases harmonic distortions.

In an attempt to avoid such obvious bug of Rigol,
I programmed Sinusoudal Arbitrary waveform.

To measure distortions of the original Rigol Sine waveform
and my Arbitrary Sin waveform I connected DG1022z output to
Line Input of a PC sound card.
Free software Arta screenshots for both cases are attached.

Rigol Sine function shows 0.019% THD,
while Arb Sin has almost two times lower 0.011% THD.

The Python code for Arb Sin is very short and is shown here:

import numpy as np
import pyvisa
rm = pyvisa.ResourceManager()
rigGen=rm.open_resource('TCPIP::192.168.1.161::INSTR')

amplShift=np.power(2,13)
maxAmpl=amplShift-1
chunkLen=8192
a=amplShift*np.ones(chunkLen)+maxAmpl*np.sin(2*np.pi*np.arange(chunkLen)/chunkLen)
a16=a.astype('uint16')
chunkData=bytes(a16)
lenchunkData=len(chunkData);
dataSizeLen=len(str(lenchunkData));
headStrBig=':SOUR1:TRAC:DATA:DAC16 VOLATILE,'+'END'+',#'+str(dataSizeLen)+str(lenchunkData)
rigGen.write_raw(bytes(headStrBig,'utf-8')+chunkData)
rigGen.write(':SOUR1:FUNC:ARB:MODE FREQ')
 
The following users thanked this post: Someone, zrq

Offline _Wim_

  • Super Contributor
  • ***
  • Posts: 1553
  • Country: be
This seems like a bug that is worthwhile to fix by Rigol. I wonder if this is also the case with their other signal generators? Thanks for reporting, I also really liked the method used to see the zero crossing level steps!  :-+
 

Offline bb1Topic starter

  • Contributor
  • Posts: 46
  • Country: us
Years ago I posted screenshots showing similar bugs in different modes of Siglent SDG2000X.
 

Offline johansen

  • Super Contributor
  • ***
  • Posts: 1090
Friend of mine says its mishandling two's compliment.
 

Online Njk

  • Frequent Contributor
  • **
  • Posts: 288
  • Country: ru
Perhaps it's not so simple. Every vendor advertises the number of bits in the DAC, the higher the better. But with DDS, the accuracy of waveform rendering depends not only on the clock frequency and DAC resolution, but also on the look-up table size (LUT resolution) that defines how many waveform points are tabulated. Even with a good DAC, it's easy to design a poor generator when the LUT size is not enough. The size does matter, the larger the better. But few vendors advertise the LUT resolution number and there must be a reason for that.

When the waveform math function is known (like with a built-in function waveform), it's not necessary to load all the points of the waveform cycle to LUT at once. A more economical way is to break the total number of points into several parts and load just one part to the LUT at a time. E.g., with sine waveform, it's enough to load the points of 1/4 cycle to LUT and then update the LUT with the next part at the run time. In that way, effective LUT resolution can be four times greater than the native LUT resolution. It's economy. Perhaps the imperfections in the waveform at the part boundaries are related to something like that.

Edit: Now i have to leave for some time, so to clarify some obviously illogical statements above. It was for simplification. The key point is that with a function waveform, only part of the points are loaded to the LUT. But a modern DDS gen can produce a waveform of hundreds of MHz. At that frequencies, no time is available even for thinking about loading a kilobytes of data at run time. So it does not work exactly like this, but much faster. That stuff is very function-specific and many related patents are published for every popular function. For instance, with the sine function, only the points of the first quadrant of the cycle are in the LUT. At the run time, the points are enumerated by the RAM address counter in the FPGA. The counter operates in the up-count mode, so the points are outputted in the ascending order. When the last point of the first quadrant has outputted, the waveform amplitude reaches its culmination. At that moment, the counter switches to the down-count mode and from now on the same points are outputted in the descending order, making the second quadrant. At the end of the quadrant, the waveform amplitude reaches zero. At that moment, the counter switches back to the up-count mode, and one more re-configuration is done such that a NOT gate is inserted in the data path of every data bit of the point. That starts the third quadrant, when the waveform amplitude value is negative. But an inversion is not a negation. Inversion of zero gives 111111..., which is -1, while the negation of zero gives zero. It's easy to correct the problem by adding 1, but that would require one more clock cycle and more complex hardware for the carry bit propagation. It seems they'd decided just to leave it as is.
« Last Edit: August 13, 2024, 06:16:34 am by Njk »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf