Contents

What are Integral and Differential Nonlinearity?

In an ideal ADC, a range of input voltages produces the same digital output value. In ideal ADCs, the size of the range of voltages that produces the same output value is precisely the same for every step. In real ADCs, this is not the case.

Differential nonlinearity is the deviation of each step from the ideal ADC step size. If the DNL for every step is less than one, then there are no missing outputs; if the width of a step is greater than one, that means that can mean that other outputs never appear.

DNL is calculated as:

\[\mathrm{DNL}(\mathrm{i})=\frac{V_{\text {out }}(i+1)-V_{\text {out }}(i)}{\text { ideal LSB step width }}-1\]

DNL

Integral nonlinearity is the deviation of a particular output code from the ideal. For example, if your 12 Bit ADC has an INL of 10 LSBs, you know that the output it reports is always within $10/2^{12} = 0.25\%$ of the “true” output code.

How do we measure it?

INL and DNL can be measured by applying a full-scale sine wave to the input and recording a lot of points. We can calculate the number of points required with the formula below, where $N$ is the number of ADC bits, $Z$ is the two sided Z-score for the confidence level we want (2.576 for 99%), and $\beta$ is the DNL resolution.

\[M=\frac{\pi 2^{N-1} Z^{2}}{\beta^{2}}\]

In the testing, 10 Million points were collected, giving 0.07 LSB accuracy at a 99% confidence level (0.05LSBs at 95%)

DNL Calculation

We can calculate the DNL by comparing the number of times we saw a particular output to the number of times we expected to see it; if we saw more hits than we expected, then that output code is wider than it should be. Or more input voltages correspond to that output than we expect. Vice-versa, if it is smaller than we expect, then it has fewer voltages than we expect to map to that voltage.

The probability of a particular code being hit for a sine wave is below. Where $N$ is the number of bits in the ADC, $n$ is the code, the ADC input is $\pm V_\mathrm{FS}$, and the sine wave has an amplitude of $A$:

\[p(n)=\frac{1}{\pi}\left[\sin ^{-1}\left[\frac{V_{\mathrm{FS}}\left(n-2^{N-1}\right)}{A \times 2^{N}}\right]-\sin ^{-1}\left[\frac{V_{\mathrm{FS}}\left(n-1-2^{N-1}\right)}{A \times 2^{N}}\right]\right]\]

We can calculate the DNL as, with the below equation, where we caculate the expected number of hits for that code based on the total number of samples collected times the probability of that code:

\[\mathrm{DNL}(n)=\frac{\text{# of hits}_\text{Actual}}{\text{Total # of hits} \times p(n)}-1\]

INL calculation

The INL is the integral of the DNL; it can be calculated as:

\[\mathrm{INL}(n)=\sum_{i=0}^{n} \mathrm{DNL}(i)\]

The problem you can run into is the INL measurement error is much higher than the DNL measurement error. The errors in DNL measurement are (approximately) normally distributed, so as you integrate the DNL, the error will grow with the root sum of squares of the accuracy above. The accuracy at the highest code is:

\[\text{INL Accuracy} = \sqrt{2^N \times M^2}\]

We can calculate the INL accuracy for this test as:

\[\text{INL Accuracy} = \sqrt{2^{12} \times {0.07 \text{ LSB}}^2} = 4.48 \text{ LSB 99% confidence}\] \[\text{INL Accuracy} = 1.09 \text{ LSB 50% confidence}\]

Test Methodology

I generated a 1kHz sine wave with a Rigol DG4000 function generator. I used the following code to sample the ADC at its full rate (500ksps) then dump the samples out the USB serial interface:

// 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/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, 0); // 0 for PSM; 1 for PWM

    // 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
import matplotlib.pyplot as plt
from scipy.optimize import minimize
import numpy as np

def sin_pdf(bits, amp, shift):
	vfs = 1
	pdf = list()
	for n in range(2**bits - 1):
		n += shift
		res = (1/np.pi)*(np.arcsin((vfs*(n-2**(bits-1)))/(amp*2**bits))-np.arcsin((vfs*(n-1-2**(bits-1)))/(amp*2**bits)))
		pdf.append(res)
	return pdf

if __name__ == "__main__":
	import json
	dat = np.load("your-datafile.npz") # data in a 1d array called 'data_v'
	
	hist, bin_edges = np.histogram(dat['data_v'], bins = list(range(2**12)), density=True)
	bin_edges = bin_edges[:-1]
	
	# Caculate best fit params
	minf = lambda inp: np.sum(np.nan_to_num((np.asarray(hist) - sin_pdf(12, inp[0], inp[1]))**2))
	res = minimize(minf, (0.5, 0))

	# Plot Histogram
	fig = plt.figure()
	bars = plt.bar(bin_edges, hist/np.sum(hist), width=1, linewidth=1, color='blue', alpha=0.5, label="ADC Data")
	plt.title("Sine Histogram Actual vs. Fitted")
	plt.xlabel("ADC code")
	plt.ylabel("Code probability")
	# Set bar edges so you can see them at low zoom
	for bar in bars:
		bar.set_edgecolor("blue")
		bar.set_linewidth(1)
	bars = plt.bar(range(2**12-1), sin_pdf(12, res.x[0], res.x[1]), width=1, color='red', alpha=0.5, label="Fitted Data")
	for bar in bars:
		bar.set_edgecolor("red")
		bar.set_linewidth(1)
	plt.tight_layout()
	plt.show()
	
	fig = plt.figure()
	plt.plot(range(2**12-1), (np.asarray(hist) / sin_pdf(12, res.x[0], res.x[1]))-1, color='red')
	plt.title("ADC DNL")
	plt.ylabel("DNL (LSBs)")
	plt.xlabel("ADC code")
	
	plt.show()
	fig = plt.figure()
	plt.plot(range(2**12-1), np.cumsum(np.nan_to_num((np.asarray(hist) / sin_pdf(12, res.x[0], res.x[1]))-1, nan=0.0, posinf=0.0, neginf=0.0)), color='red')
	plt.title("ADC INL")
	plt.ylabel("INL (LSBs)")
	plt.xlabel("ADC code")
	plt.show()

Histogram