What a view. . .

What a view. . .

Sunday, November 2, 2014

EngNote - ADCs & Dithering: When adding noise is a good thing

The concept of dithering seems counter-intuitive. In short, you add noise to improve performance. Why does this work, what performance are you getting by adding noise? An example that I commonly deal with is combating quantization noise and its resulting impacts to an ADC's spurious free dynamic range (SFDR).

Quantization Overview

Let's assume we have a 3bit ADC. This ADC only has 8 unique values it can produce. An input signal gets mapped to a uniform grid (in an ideal ADC) of 8 distinct values, which is a non-linear operation. The result of this operation is quantization noise. Below is an illustration.

Quantization Noise Resulting from Sinusoidal Inputs

When an ADC is operating with sinusoidal inputs, the quantization error it produces ends up manifesting itself as spurious content in the spectrum of the digital output.  The spurious content is bad because the spurs themselves cannot be averaged out and can limit dynamic range by masking other weaker signals that may be present.

Quantization noise impacts depend on the input frequency.  There are some interesting things that result from this dependence.  As an example, consider an input frequency of fs/4 (the ADC's sampling rate divided by four).  The quantization noise creates spurious content at a single frequency which happens to be at exactly that of the input signal, fs/4.  Note the amplitude of the spur is also dependent on the phase of the signal relative to the ADC sampling points.  To see this behavior and phase dependence, try an example with a signal that is in-phase with the ADC sampling points and then again with a signal that has a phase offset.


# Fs/4 Example - 0deg phase
fs = 100;
ts = 1/fs;
endTime = 100;
time = np.linspace(0,endTime-ts,endTime*fs);
freq = fs/4;
numBits = 8;
# Generate signals
sig = np.sin(2*np.pi*freq*time);
quantSig = np.round(sig*2**(numBits-1))/2**(numBits-1);
# Fs/4 Example - 45deg phase
fs = 100;
ts = 1/fs;
endTime = 100;
time = np.linspace(0,endTime-ts,endTime*fs);
freq = fs/4;
numBits = 8;
# Generate signals
sig = np.sin(2*np.pi*freq*time+np.pi/4);
quantSig = np.round(sig*2**(numBits-1))/2**(numBits-1);

A more common situation is when the sinusoidal input is not an integer division of the sampling frequency.  In this case, spurs are spread across the spectrum.  There is a pattern to this whole madness but I don't have the specifics worked out yet.

The commonly used Signal to Quantization Noise Ratio Equation makes an assumption about the spectrum of the quantization noise.  The assumption is that the quantization error can be approximated by an uncorrelated saw-tooth waveform.  When dealing with sinusoidal inputs at specific frequency/phase combinations such as the example above, those assumptions do not hold.

$ SQNR_{dB}=6.02*numBits+1.76 $
Commonly Used Signal to Quantization Noise Ratio Equation

How Dithering Helps

So how does dithering, the act of adding random noise, help?  Often times to find signals below the noise floor of an ADC, "to dig really low", we integrate over many ADC samples to obtain processing gain.  However, if there are quantization spurs generated by another higher power signal going into the ADC, the low power signal being looked for may be corrupted or overlooked because of the quantization noise spurs that cannot be averaged out.  The act of dithering helps this process by decorrelating the quantization noise.

Consider a simple example where a constant (not sinusoidal) signal is sent into a 1bit ADC.  If the signal is at 0.75 (ignore units) and the ADC can, depending on its input, either output a 0.0 or 1.0, the signal will be reported as a 1.0 till the cows come him.  If multiple measurements of the ADC are integrated together, the answer will always be the same, 1.0.

ADC Equation
$ \begin{matrix} ADC\left ( x \right ) = 0.0, & x < 0.5\\ ADC\left ( x \right ) = 1.0, & x \geq 0.5 \end{matrix} $ 
ADC Integration
$ \frac{1}{N}\sum_{n=0}^{N-1}ADC\left (x_{n} \right ) = \frac{1}{N}\sum_{n=0}^{N-1}ADC\left (0.75 \right ) = \frac{1}{N}\sum_{n=0}^{N-1}1.0 = 1.0 $

If Guassian random noise is added to the input, a form of dithering, the input to the ADC will vary.  With the right amount of noise, the signal will occasionally be below the 0.5 threshold and other times above.  If averaging is employed on this signal, the result of the averaging can be much closer to the true value of the signal attempting to be measured.

ADC Equation
$ \begin{matrix} ADC\left ( x \right ) = 0.0, & x < 0.5\\ ADC\left ( x \right ) = 1.0, & x \geq 0.5 \end{matrix} $ 
ADC Integration
$ \frac{1}{N}\sum_{n=0}^{N-1}ADC\left (0.75 + N\left ( 0,1 \right ) \right ) = \frac{1}{N}\sum_{n=0}^{N-1}ADC\left (N\left ( 0.75,1 \right ) \right ) $
$ \lim\limits_{N\rightarrow\infty} \frac{1}{N}\sum_{n=0}^{N-1}ADC\left (N\left ( 0.75,1 \right ) \right ) \approx 0.75 $

The reason for the approximation above is because the amount of decorrelation (reduction in spurs) depends on the amplitude of the noise added.  If the Guassian noise added is much smaller than the quantization error, there will be little benefit.  In the extreme case, if the added noise is zero then the results are back to where they started where averaging has no benefit.  In the other extreme case, if the Guassian noise is extremely large, a large amount of averaging is required to obtain an accurate measurement due to the error in the noise itself.  A rule of thumb used to obtain a balance between these two extremes is to try to ensure that the Guassian noise is ~6dB larger than that of the quantization noise.  Depending on application, this should be treated on a case-by-case basis. 

Wrap Up

Putting everything together, below is an illustration of how dithering can help.  Note that the spurs are reduced (below the noise floor introduced by the Guassian noise) and averaging is accomplished via the size of the FFT employed to create the spectrum.
# --------------------------------------------------------------------------------------
# Complex Dither Example
# --------------------------------------------------------------------------------------
fs = 100;
ts = 1/fs;
endTime = 1000;
time = np.linspace(0,endTime-ts,endTime*fs);
freq = fs/8.1;
freqVect = np.linspace(-1,1,len(time))*fs/2;
numBits = 8;
win = np.hamming(len(time));
signal = np.sin(2*np.pi*freq*time+np.pi/4);

sigPlusNoise = signal + np.random.randn(len(signal)) / 2**numBits;
quantSig = np.round(signal*2**(numBits-1))/2**(numBits-1);
quantSigPlusNoise = np.round(sigPlusNoise*2**(numBits-1))/2**(numBits-1);

sigSpect = np.fft.fft(win*signal);
sigSpect = np.fft.fftshift(sigSpect);
spurSpect = np.fft.fft(win*(signal-quantSig));
spurSpect = np.fft.fftshift(spurSpect);
noiseSpect = np.fft.fft(win*(quantSigPlusNoise-signal));
noiseSpect = np.fft.fftshift(noiseSpect);
plot.figure();
plot.plot(freqVect,20*np.log10(abs(sigSpect)),linewidth=2.0);
plot.plot(freqVect,20*np.log10(abs(spurSpect)),linewidth=2.0);
plot.plot(freqVect,20*np.log10(abs(noiseSpect)),linewidth=2.0);
plot.xlim(-fs/2,fs/2);
plot.ylim(0,100);
plot.title('Quantization Noise Dither Example');
plot.xlabel('Frequency (Hz)');
plot.ylabel('Amplitude (dBx)');
plot.legend(['True Signal','Quantization Noise','Dithered Quantization Noise']);
plot.grid();
plot.show();


Good References

http://www.utdallas.edu/~cpb021000/EE%204361/Great%20DSP%20Papers/Harris%20on%20Windows.pdf
http://www.analog.com/static/imported-files/tutorials/MT-001.pdf
Analog Devices Application Note [local mirror]
http://www.analog.com/static/imported-files/tutorials/MT-003.pdf