<jupyter_start><jupyter_text>First of all, let's compute the measured input voltage using the reference voltage values.In the most exact case, we can calculate each value separately when we know all the reference voltages during all the measurements.However, it might also be reasonable to perform the analysis after averaging this column, since the actual end user might not monitor the reference voltage constantly during measurements.<jupyter_code>res = 2 ** 10
data["vin"] = data.code * data.vref / res
data[5000:6000]

vref = np.mean(data.vref)
print(vref)<jupyter_output>4.6127748530337636<jupyter_text>Let's try to find the last transition<jupyter_code>last_transition_v = data.vref / res * (res - 1)
print(last_transition_v)
last_transition = data[data.vout >= last_transition_v].iloc[0].name

# Keep only the data up until the last transition.
data_original = data.copy()
data = data[:last_transition]
print(len(data))<jupyter_output>0       4.604021
1       4.605171
2       4.609278
3       4.609606
4       4.605006
          ...   
8187    4.605335
8188    4.600078
8189    4.611742
8190    4.613056
8191    4.605664
Name: vref, Length: 8192, dtype: float64
8166<jupyter_text>Calculate the statistics.<jupyter_code>from scipy.stats import linregress

def v_to_code(voltage):
    global res, vref
    return voltage * res / vref

def perfect_adc_code(voltage):
    global res, vref
    return np.round(voltage * res / vref) / (res / vref)  # "/ (res / vref)" just because it will be scaled later on.

code = True
if code:
    coef = res / vref
else:
    coef = 1
print(f"Coefficient = {coef}.")


# Perform linear regression using least squares.
lr = linregress(x=data.vout, y=data.vin)
print(lr)

fig, ax = plt.subplots(figsize=(15, 10))

# print(perfect_adc_code(data.vout))
# print(v_to_code(data.vout))

ax.axis("equal")
# Plot the voltages.
limits = [0 * coef, 0.025 * coef]
# limits = [1000, 1023]
# limits = [0, 1024]
ax.plot(data.vout * coef, data.vin * coef, label="Measured transfer function")
ax.plot(data.vout * coef, (lr.slope * data.vout + lr.intercept) * coef, label="Fitted transfer function")
ax.plot(data.vout * coef, data.vout * coef, label="Ideal transfer function")
ax.plot(data.vout * coef, perfect_adc_code(data.vout) * coef, label="Perfect transfer function")
ax.plot(data.vout * coef, data.vout_meas * coef, label="Measured output voltage")
ax.plot(data.vout * coef, perfect_adc_code(lr.slope * data.vout + lr.intercept) * coef, label="Perfect fitted transfer function")

# Set the limits to display only a part of the plot.
ax.set_xlim(limits)
ax.set_ylim(limits)

# Set the legend.
ax.legend()

# Calculate offset and gain errors.
lsb_v = vref / res
print(lsb_v)

def calculate_fit_error(lsbs, lsb_v, slope, intercept, verbose=False):
    offset_y = lsbs * lsb_v
    ideal_x = offset_y
    offset_x = (offset_y - intercept) / slope
    if verbose:
        print(f"Offset_x: {offset_x * coef}, offset_y (==ideal_x): {offset_y}.")
    return offset_x - ideal_x

# Offset error calculated at 0.5 LSB.
offset_error_v = calculate_fit_error(0.5, lsb_v, lr.slope, lr.intercept)
# Full-scale error calculated at MAX - 1.5 LSB.
fs_error_v = calculate_fit_error(res - 1.5, lsb_v, lr.slope, lr.intercept)
# Gain error
gain_error_v = -(fs_error_v - offset_error_v)

offset_error = offset_error_v * coef
fs_error = fs_error_v * coef
gain_error = gain_error_v * coef
print(f"Offset error = {offset_error} LSB, full-scale error: {fs_error} LSB, gain error = {gain_error} LSB.")

# plt.show()
# plt.savefig("errors.pdf")
# Missing codes?
print(data.code.astype("category").unique())

# Since finding the DNL is done using the best-fit line, calculate the perfect codes based on that.
# Also take into account the actual output values (vout_meas).
# Also, do not output negative codes!
data["perfect_code"] = (np.maximum(np.round(perfect_adc_code(lr.slope * data.vout_meas + lr.intercept) * (res / vref)), 0)).astype(int)
data.perfect_code<jupyter_output>[0.0, 1.0, 2.0, 3.0, 4.0, ..., 1018.0, 1020.0, 1019.0, 1021.0, 1022.0]
Length: 1023
Categories (1023, float64): [0.0, 1.0, 2.0, 3.0, ..., 1019.0, 1020.0, 1021.0, 1022.0]<jupyter_text>Histogram testingUse histogram testing to find the DNL and INL.The DNL is the ratio between the actual and theoretical counts of bins of codes minus one. A subtraction by one is done because the ideal code width is 1 and correspondingly a 1:1 ratio would be considered ideal. So for example, if the code 512 was supposed to be present 4 times and it was actually present 2 times, then the DNL would be 2 / 4 - 1 = -0.5 LSB.To calculate the INL, we need to sum up all the DNLs leading up to a particular index (and the current DNL). So if we want the DNL of the third code, we need to sum the DNLs of the first, second, and third code. As an example, if the DNLs were -1, 0, 1, then the INL of the third code would be -1 + 0 + 1 = 0. An ideal value is 0.<jupyter_code>np.max(data_original.vout - data_original.vout_meas)
codes_ = np.round(perfect_adc_code(lr.slope * data_original.vout_meas + lr.intercept) * (res / vref)).astype(int)
codes = np.round(perfect_adc_code(lr.slope * data_original.vout + lr.intercept) * (res / vref)).astype(int)
codes_[codes_ < 0] = 0
codes_[codes_ > 1023] = 1023
codes[codes < 0] = 0
codes[codes > 1023] = 1023
(codes != codes_).argsort()

perfect_codes = (np.maximum(np.round(perfect_adc_code(lr.slope * data_original.vout + lr.intercept) * (res / vref)), 0)).astype(int)

plt.figure(figsize=(15, 5))
perfect_hist = plt.hist(codes, bins=1024, range=(0, 1024))
plt.xlabel("ADC code")
plt.ylabel("Count")

print(perfect_hist)
plt.figure(figsize=(15, 5))
real_hist = plt.hist(data_original.code.astype(int), bins=1024, range=(0, 1024))
plt.xlabel("ADC code")
plt.ylabel("Count")

print(real_hist)

dnls = real_hist[0] / perfect_hist[0] - 1

print(real_hist[0][1023], perfect_hist[0][1023])

print(dnls)
dnl = np.nanmax(np.abs(dnls[np.isfinite(dnls)]))
print(np.nanmin(dnls), np.nanmax(dnls))
print(np.nanargmin(dnls), np.nanargmax(dnls))

print(f"Differential non-linearity: {round(dnl, 2)} LSB.")
# Calculate integral non-linearities.
print(len(dnls))
inls = np.zeros_like(dnls)
for i in range(len(dnls)):
    inls[i] = np.sum(dnls[:i + 1])

print(inls)
inl = np.nanmax(np.abs(inls[np.isfinite(inls)]))
print(np.nanmin(inls), np.nanmax(inls))
print(np.nanargmin(inls), np.nanargmax(inls))
print(f"Integral non-linearity: {round(inl, 2)} LSB.")
# Print out all the errors found so far.
print(
f"""Offset error = {round(offset_error, 2)} LSB, 
Full-scale error: {round(fs_error, 2)} LSB, 
Gain error = {round(gain_error, 2)} LSB,
Differential non-linearity = {round(dnl, 2)} LSB,
Integral non-linearity = {round(inl, 2)} LSB."""
)<jupyter_output>Offset error = 1.05 LSB, 
Full-scale error: -0.19 LSB, 
Gain error = 1.25 LSB,
Differential non-linearity = 0.88 LSB,
Integral non-linearity = 0.91 LSB.<jupyter_text>Now, also create plots of DNL and INL.<jupyter_code>def plot(x=[], y=[], xlabel="", ylabel="", legend="", figsize=(15, 5)):
    fig, ax = plt.subplots(figsize=figsize)
    if len(x) > 0:
        ax.plot(x, y, label=legend)
    else:
        ax.plot(y, label=legend)
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.legend()
plt.rc("font", size=16)

plot(y=dnls, xlabel="ADC code", ylabel="LSB", legend="DNL")
plot(y=inls, xlabel="ADC code", ylabel="LSB", legend="INL")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13606911447084233, 'fraction_numerical': 0.04697624190064795, 'mean_word_length': 4.516753536857781, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '366723', 'license': 'MIT', 'n_tokens_mistral': 3055, 'n_tokens_neox': 2773, 'n_words': 764, 'path': 'data/processing_static.ipynb', 'repo_name': 'Jyrijoul/arduino-adc-tester', 'size': '7408'}