Contents

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

References