Reducing Sine Waveform THD by Going Around Rigol Waveform Generator Bug  - Page 1
Forum section: electronics / testgear

author: bb1
author_rank: Regular Contributor
timestamp: August 08, 2024, 10:26:46 pm
content: 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 withtwo 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 outputof the DG1022z, signal of which is shown by blue trace on the attachedBUG_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 waveformand my Arbitrary Sin waveform I connected DG1022z output toLine 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 npimport pyvisarm = pyvisa.ResourceManager()rigGen=rm.open_resource('TCPIP::192.168.1.161::INSTR')amplShift=np.power(2,13)maxAmpl=amplShift-1chunkLen=8192a=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')

author: _Wim_
author_rank: Super Contributor
timestamp: August 11, 2024, 02:10:40 pm
content: 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!

author: bb1
author_rank: Regular Contributor
timestamp: August 12, 2024, 04:25:43 pm
content: Years ago I posted screenshots showing similar bugs in different modes of Siglent SDG2000X.

author: johansen
author_rank: Super Contributor
timestamp: August 12, 2024, 08:18:01 pm
content: Friend of mine says its mishandling two's compliment.

author: Njk
author_rank: Frequent Contributor
timestamp: August 12, 2024, 09:27:44 pm
content: 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.