Signal to Noise and Distortion (SINAD)
Contents
- What is Signal to Noise and Distortion (SINAD)?
- How do we measure it?
- Test Methodology
- Results
- References
What is Signal to Noise and Distortion (SINAD)?
Signal to Noise and Distortion is just that, the ratio of the power of the signal to the power of the noise and distortion (everything that is not signal). We can write it as:
\[SINAD = 10 \log_{10} \left({\frac{P_\text{Signal}}{P_\text{Noise} + P_\text{Distortion}}}\right)\]SINAD is measured with a single tone, a single input frequency. There are more measurements to test the performance with more than one input frequency.
How do we measure it?
SINAD is measured by applying a fixed frequency to the system, computing the power FFT, finding the signal’s power, and the power of everything else.
To eliminate spectral leakage, the ADC sampling needs to be locked to the function generator output. One channel of the function generator goes to the ADC input; the other channel provided a 48MHz clock to the ADC via a GPIO.
To ensure that the quantization noise spectrum is random and eliminate masking of distortion products, the data record should contain a prime number of waveform cycles. In the below equation, $M_C$ is the number of full cycles collected, and $M$ is the record length. In the data collection, I used a 995Hz signal, which captures 199 (a prime number) complete cycles of the waveform.
\[\frac{f_{\text {in }}}{f_{s}}=\frac{M_{C}}{M}\]Test Methodology
A 995Hz sine wave was generated with a Rigol DG4000 function generator. I measured it’s SINAD as around 74dB, exceeding its minimum spec. The following code was used to sample the ADC at it’s full rate then dump the samples out the USB serial interface and lock the ADC clock to the function generator:
// Code modified from https://github.com/raspberrypi/pico-examples/blob/master/adc/adc_console/adc_console.c
#include <stdio.h>
#include <inttypes.h>
#include "pico/stdlib.h"
#include "hardware/clocks.h"
#include "hardware/gpio.h"
#include "hardware/adc.h"
#include "hardware/dma.h"
#define N_SAMPLES 100000
uint16_t sample_buf[N_SAMPLES];
int main() {
stdio_init_all();
// DMA setup:
uint dma_chan = dma_claim_unused_channel(true);
dma_channel_config cfg = dma_channel_get_default_config(dma_chan);
channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16);
channel_config_set_read_increment(&cfg, false);
channel_config_set_write_increment(&cfg, true);
channel_config_set_dreq(&cfg, DREQ_ADC);
adc_gpio_init(26);
adc_init();
adc_select_input(0);
adc_set_temp_sensor_enabled(false);
adc_fifo_setup(true, true, 1, false, false);
// Set SMPS_MODE Pin
gpio_init(23);
gpio_set_dir(23, GPIO_OUT);
gpio_put(23, 1); // 0 for PSM; 1 for PWM
// Configure clock input pin 20
clock_configure_gpin(clk_adc, 20, clock_get_hz(clk_adc), clock_get_hz(clk_adc));
// Output clk_adc / 10 to gpio 26
clock_gpio_init(21, CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_VALUE_CLK_ADC, 10);
// Capture samples then ship them out over USB
while (1) {
adc_fifo_drain();
dma_channel_configure(dma_chan, &cfg, sample_buf, &adc_hw->fifo, N_SAMPLES,true);
adc_run(true);
dma_channel_wait_for_finish_blocking(dma_chan);
adc_run(false);
printf("Done\n");
for (int i = 0; i < N_SAMPLES; i = i + 1) {
printf("%03x\n", sample_buf[i]);
}
}
return 0;
}
Results
Analysis code
def plot_spectrum_sinad(times, vals) -> None:
"""
Plot spectrum, calculate SINAD.
:param wfm: Waveform to plot
:return: None
"""
# processes a prime number of cycles
vals = vals
fft_out = np.fft.rfft(vals, norm="forward")[1:]
fft_freq = np.fft.rfftfreq(len(vals), d=times[1] - times[0])[1:]
fig = plt.figure()
plt.plot(
fft_freq/1000,
20
* np.log10( # Convert from linear scale to dB
np.abs(fft_out)
# dB scale referenced to peak FFT term; assuming this is the carrier
/ np.max(np.abs(fft_out))
),
label=f"Waveform Spectrum",
)
# plt.axhline(20 * np.log10(np.sort(np.abs(fft_out))[-2]/ np.max(np.abs(fft_out))))
print(f"FS size: {np.abs(fft_out).max()}")
print(f"SFDR: {-20 * np.log10(np.sort(np.abs(fft_out))[-2]/ np.max(np.abs(fft_out)))}dBc")
plt.title("ADC Spectrum")
plt.xlabel("Frequency (kHz)")
plt.ylabel("Power dBc")
with open("SINAD_Spectrum.html", "w") as f:
f.write(mpld3.fig_to_html(fig))
plt.show()
power_spectrum = np.abs(fft_out**2) # Convert Vrms to power
max_val = power_spectrum.max()
rem_vals = power_spectrum
rem_vals[np.argmax(power_spectrum)] = 0 # Remove fundamental
plt.plot(rem_vals)
plt.show()
sinad = 10 * np.log10(max_val / np.sum(rem_vals))
print(f"SINAD = {sinad}")
print(f"ENOB = {(sinad-1.78)/6.02}")
plt.xlabel("Frequency (kHz)")
plt.ylabel("Power (dBC)")
plt.title("Spectrum of collected waveform")
plt.legend()
plt.show()
The SINAD is ** 49.26dB**
We can calculate the Spurious free dynamic range as well; it was 50.41dB
SINAD vs ENOB
ENOB and SINAD are equivalent, we can go from one to another with the equation:
\[\text{ENOB} = \frac{\text{SINAD} - 1.76dB}{6.02dB}\] \[\text{ENOB} = 7.89 \text{Bits}\]This is slightly different from the ENOB calculated with the sine curve fit method, 8.64 Bits.
We can calculate the SINAD from the ENOB (calculated with the sine curve fit method) as 53.77dB, a difference of only 4.5dB!
PWM Mode
I repeated the collection for PWM Mode, and the results are:
SINAD: 48.96dB
SINAD (Sine curve fit): 52.82 dB
ENOB (SIND): 7.84 Bits