For use with: Alpaca Kernel

17B - Testing and Calibration#

Overview#

Before you begin assembling your potentiostat, in this assignment, you will learn about the limitations of Alpaca and Pico by measuring noise, resolution, and differences between requested and generated signal.

We expect you to eventually make a voltammogram with positive and negative currents and voltages. The location of the voltammogram peak depends on the substance you measure, but can be influenced by limitations of your system. In this 17B assignment you will explore those limitations.

If you are still unsure which design to choose, feel free to reach out to the TAs and discuss which option would be the most suitable for you and your partner.

Goals#

  1. Learn:

    • how to save and read files with Pico, and make a habit of saving data regularly

    • how to handle errors effectively

    • more about the noise in Alpaca

  2. Calibrate your DAC Assistant

  3. Understand the limits of Alpaca+Pico measurements

Requirements#

  1. ❗❗❗ = Mandatory

  2. πŸ† = Entirely optional. Interesting and useful.

  3. πŸ†πŸ† = Optional. Recommended.

  4. πŸ†πŸ†πŸ† = Recommended.

  5. πŸ†πŸ†πŸ†πŸ = Challenging. Surely, it’s worth it!

Outline#

  1. Implement 1 - Introduction

    • I1.1 ❗❗❗ - Jumpers

    • I1.2 πŸ†πŸ†πŸ† - Picotools

    • I1.3 πŸ†πŸ†πŸ† - Saving files

    • I1.4 πŸ†πŸ†πŸ† - Handling errors

  2. Implement 2 - ADCs

    • I2.1 πŸ† - Offset

    • I2.2 πŸ†πŸ† - Noise

    • I2.3 πŸ†πŸ†πŸ† - -12Vin to Cria

  3. Implement 3 - DAC

    • I3.1 ❗❗❗ - Accuracy

    • I3.2 πŸ†πŸ† - Noise

    • I3.3 πŸ†πŸ†πŸ† - Resolution

  4. Implement 4 πŸ†πŸ† - Timing accuracy

  5. Implement 5B - Basic design only - DAC Assistant

    • I5B.1 ❗❗❗ - Calibration

    • I5B.2 πŸ†πŸ†πŸ† - Resolution

    • I5B.3 πŸ†πŸ† - Noise

  6. Implement 6B - Basic design only - GAIN 1:3

    • I6B.1 ❗❗❗ - Configure GAIN 1:3

    • I6B.2 πŸ† - Noise

  7. Implement 7 πŸ†πŸ†πŸ†πŸ - Helper functions

  8. Compare and Conclude ❗❗❗

Implement 1: Introduction#

❗❗❗ I1.1: Jumpers#

Check your Alpaca - are all jumpers present?

The Fritzing just below shows how your Alpaca should look like at the beginning of this project.
Make sure that your Alpaca matches this layout.

Double check the four jumpers at SPI DIRECT TO DAQ and the two jumpers (or wires) at AMPLIFIER DIRECT TO NANO


Default ALPACA configuration for the Final Project

πŸ†πŸ†πŸ† I1.2: Picotools - Module for testing the potentiostat#

In the previous assignments, we defined all our custom functions in the cell of the notebook, which makes Jupyter notebooks so handy for prototyping, but for a larger project, this approach creates notebooks that are cumbersome to navigate. So, when your custom function is tested and ready for use later, or simply to have a better overview in your notebook, you may store its definition in an external Python file - a library, which is a simple, and a good practice to improve your workflow.

As an example, we prepared a small library with some familiar and some new functions for automating the testing of the potentiostat, the picotools.py.

Download this file from Brightspace and save it in the folder with the notebook 17B.

This file has to be sent to Pico using the Alpaca kernel, and imported like any other module that you have been using so far. This procedure will be explained throughout this notebook.

Feel free to take a peek into this file to learn more about the function that we prepared for you to speed up and simplify some of the procedures.

At some points throughout the Final Project, you might need to tailor some functions for your application. If you want to write your own, improved functions, you may simply add them to picotools.py, when they’re ready for β€œdeployment”. You could also create your own library(-ies) to store some of the advanced Voltammetry procedures of your Final Project. So, consider the use case of picotools as a basic example and an inspiration towards advanced programming practices with Python.

Please, bear in mind that Picotools is currently in version 0.2, so it is not a production-level module. It wasn’t thoroughly tested by a large community, like for example numpy, so prepare to encounter many constraints, minimal documentation, and potentially some serious bugs. If that happens, please report them to us or take the matter in your hands and improve them right away.

Import picotools#

Follow the procedure in the cells below to import picotools.

This procedure assumes that you have already downloaded picotools.py from Brightspace and placed it in the same folder with this notebook.

%serialconnect to --port="COM5"
%sendtofile /picotools.py --source picotools.py
import picotools as pico
import numpy as np
import matplotlib.pyplot as plt

import os
import machine

import time

Now, all functions and variables specified in the picotools.py are available under as methods of the pico. object, so for example:

# Example of a function in picotools
pico.test_ADC()

Note that we also have to additionally import all the usual modules.

πŸ†πŸ†πŸ† I1.3: Saving files#

Because of the very limited storage in Pico’s memory, you will have to develop a habit to regularly save your measurements.

It is crucial in this project!

Because of the amount of data that will be generated during your measurements, you will very often overload Pico’s memory, and most likely have to reset it regularly. In this notebook, we will show you how to do it safely and to avoid frustration of lost data.

Follow the recipe below to learn how to shuttle files between Pico and your computer. It is a little cumbersome, so we prepared some examples for you.

Saving textfiles#

In the example below, you can also learn how to make a timestamp for your measurement.

# Initialize the RTC (Real-time Clock)
rtc = machine.RTC()

# Get the current date and time
year, month, day, weekday, hours, minutes, seconds, subseconds = rtc.datetime()

# Define the file path
file_path = 'Greeting.txt'

# Create the greeting message
file_content = (
    "Hello from Pico Pi and Alpaca!\n"
    f"File saved on: {year:04d}-{month:02d}-{day:02d} {hours:02d}:{minutes:02d}:{seconds:02d}.\n"
)

# Writing to the file
with open(file_path, 'w') as file:
    file.write(file_content)

print("Greeting saved to Greeting.txt")
# This is the essential command to fetch your file from Pico to your computer
%fetchfile --binary "Greeting.txt" "Greeting_from_Pico.txt"
# This is a helper function to list all files currently in Pico's storage
pico.list_files()
# This is how you can remove a file
pico.remove_file('Greeting.txt')
pico.list_files()

Saving numerical data#

Once you record the signal from multiple ADCs during a single measurement, always save it in a temporary array on Pico, i.e. temporary_data.npy and then fetch it from Pico to your computer.

There is usually only enough storage on Pico for just one such aggregated array, so every time you run a new, long measurement, you must overwrite the already existing temporary_data.npy file. You could also, always remove it after use and create a new one after the next measurement.

How long is a long measurement?

  • Anything with more than NUM_SAMPLES > 500 might cause some trouble for storage or plotting. Remember that Pico will most likely have to handle multiple arrays of that size internally.

    For the regular measurements, we recommend NUM_SAMPLES=1000

    and only if you are confident that your code works, go for longer arrays in your final measurement.
    In our experience, and with the most optimised code, we managed to succesfully record and handle three arrays of NUM_SAMPLES=3600.

This procedure is again a little cumbersome, but we will push Pico to its limits in this project to run high quality measurements.

You may use the code below as a template for later use.

DATA=np.zeros((3,3))
DATA[0,:]=1 
DATA[1,:]=2
DATA[2,:]=3

np.save('temporary_data.npy', DATA)
del(DATA)
%fetchfile --binary "temporary_data.npy" "Greeting_DATA_from_Pico.npy"

πŸ’‘ Always check if the Fetched XXX=XXX bytes. There are known issues with large files. Retrying often works, but for very large files, different solutions must be found.

pico.remove_file('temporary_data.npy')

πŸ†πŸ†πŸ† I1.4:Handling Errors#

Saving data and optimising storage is one thing, but Pico’s internal memory is another, and managing that is a very difficult task, so in order to prevent trouble here, we propose that you regularly reset your Pico.

What is the machine.soft_reset()?

  • In short, Soft reset mimics disconnecting and connecting the USB cable between Pico and your computer.

  • It restarts Pico, which means that all your variables, i.e. you unsaved data, will be lost. We perform this reset to easily clear Pico’s internal memory (not the storage, not your files). This means that all typical errors with plotting and handling data will be resolved.

  • It also means that you will have to re-import all usual modules.

Use the two cells below to restart your Pico and start anew.

Resetting Pico#

# Note that you can execute machine.soft_reset() only when Pico is already connected. 
machine.soft_reset()
# Default cell for re-connecting Pico, re-importing picotools and the usual modules.
%serialconnect to --port="COM5"

%sendtofile /picotools.py --source picotools.py

import picotools as pico

import numpy as np
import matplotlib.pyplot as plt
# If you want to disconnect Pico from this notebook, use this command:
# %disconnect

Implement 2: Exploring the limits of the ADCs#

Did you know that measuring very low voltages with Pico might be problematic? This is especially relevant for the Basic Design. Can you argue why it is so?

Goal 1: Measure the baseline noise level, without any signal on the ADC’s.
Goal 2: Observe ADC ground error and the improvement with -12V connected to Cria

πŸ’‘Fritzing: ADC Test

ADC Baseline Test - Do not connect anything to ADCs


πŸ† I2.1: Baseline offset#

First, find out what’s the baseline offset of the ADCs, so without any inputs

Behind the scenes, the ADC0 and ADC1 will be measured via the Alpaca’s amplifiers. Let’s see if they’re really 0V! In detail:

  1. Ain0 via ADC0 (Jumpers on AMPLIFIER DIRECT TO NANO, directly in the cut on the right side of the Cria)

  2. Ain1 via ADC1

  3. Ain2 directly

Let’s start with the reset to develop a habit:

machine.soft_reset()
%serialconnect to --port="COM5"

%sendtofile /picotools.py --source picotools.py

import picotools as pico

import numpy as np
import matplotlib.pyplot as plt
# Run the test:
AMP0, AMP1, Ain2 = pico.test_ADC()
# Remember to convert ADC samples to Volts!
AMP0v = pico.convert_samples_to_volts(AMP0, gain=1)
AMP1v = pico.convert_samples_to_volts(AMP1, gain=1)
Ain2v = pico.convert_samples_to_volts(Ain2, gain=1)

# Plot the baseline offset
plt.plot(AMP0v, label='AMP0')
plt.plot(AMP1v, label='AMP1')
plt.plot(Ain2v, label='Ain2')
plt.xlabel('Sample')
plt.ylabel('Signal [V]')
plt.legend()

It’s most likely not 0V!

Let’s run some statistics and investigate this further.

πŸ†πŸ† I2.2: ADC - Noise#

Run the cells below to compute the errors and find out more about the nature of this noise.

avg_signal_Ain0, std_dev_Ain0 = pico.compute_noise_statistics(AMP0v)
avg_signal_Ain1, std_dev_Ain1 = pico.compute_noise_statistics(AMP1v)
avg_signal_Ain2, std_dev_Ain2 = pico.compute_noise_statistics(Ain2v)
  1. Make a note of the magnitude of the error. Will it affect your Voltammetry measurements?

  2. What is the magnitude of the noise? How does it compare to the resolution of ADCs?

### Notes
pico.plot_noise_spectrum(AMP0v, label="AMP0")
pico.plot_noise_spectrum(AMP1v, label="AMP1")
pico.plot_noise_spectrum(Ain2v, label="Ain2")
plt.legend()
  1. Are there any significantly dominanting frequencies?

Let’s save the original data for practice, and for reference!

DATA=np.zeros((3,pico.NUM_SAMPLES))
DATA[0,:]=AMP0 
DATA[1,:]=AMP1
DATA[2,:]=Ain2

np.save('temporary_data.npy', DATA)
del(DATA)
%fetchfile --binary "temporary_data.npy" "ADC_Baseline_Test_DATA.npy" # for I2.2
#%fetchfile --binary "temporary_data.npy" "ADC_Baseline_Test_DATA-12V.npy" #for I2.3 (the next section)

πŸ†πŸ†πŸ† I2.3: Test ADCs with -12V connected to Cria#

⚠️ Warning: Use the Fritzing below to carefully connect -12V to Cria: Orange LED will light up!

πŸ’‘ Fritzing: -12V to Cria

Connecting -12V source in Alpaca to Cria's J5:-12V in

  1. Re-run the test from I2.2 with -12V connected to the β€˜-12V in’ pin on the multifunction connector.

  2. Use a different name for your data file on your laptop (ADC_Baseline_Test_DATA), otherwise you overwrite previous data. Use our hint in the comments.

  3. In the cell below, compare the average noise signal without any input signal, in the two cases: with and without -12V in

  4. Argue whether it is better to work with -12V in or without?

### Notes

Implement 3: Exploring the limits of the DAC#

In this section, you will have a closer look at the accuracy, noise and the resolution of the DAC. Use the provided Fritzing for this test.

Goal: Measure the noise in the signal applied from DACA to the ADCs.

You will be using amplifiers (attenuations 1:1 and 1:3) and measuring directly to Ain2.

πŸ’‘ Fritzing: DAC Test

DAC Test


Let’s again start with a reset to clear Pico’s memory.

machine.soft_reset()
%serialconnect to --port="COM5"

%sendtofile /picotools.py --source picotools.py

import picotools as pico

import numpy as np
import matplotlib.pyplot as plt

import machine

❗❗❗ I3.1 DAC - Accuracy#

The function in the cell below performs a sweep over all values of DACA and measures it with all three ADCs to check the accuracy of the measurement.

ref, Ain0, Ain1, Ain2, err_out_0, err_out_1, err_out_2 = pico.test_DAC_A(NUM_SAMPLES = 100, gain0=1, gain1=0.3333)
# Plot the measured DACA output vs Expected DACA output
plt.plot(ref, Ain0, label='AMP0')
plt.plot(ref, Ain1, label='AMP1')
plt.plot(ref, Ain2, label='Ain2')
plt.xlabel("Expected value from the DACA output [V]")
plt.ylabel("DACA measured output [V]")
plt.legend()
# Plot the DACA Error vs DACA expected output
plt.plot(ref, err_out_0*1e3, label='AMP0')
plt.plot(ref, err_out_1*1e3, label='AMP1')
plt.plot(ref, err_out_2*1e3, label='Ain2')
plt.xlabel("Expected value from the DACA output [V]")
plt.ylabel("DACA measured error [mV]")
plt.legend()
  1. Make a note of the magnitude of the error. Will it affect your Voltammetry measurements?

  2. Does it change when you disconnect -12V from Cria? Run the measurements in the section with and without this connection.

# Notes

πŸ†πŸ† I3.2 DAC - Noise#

Run the cells below to measure the noise of the DAC output.

DCsetpoint = 1 # Set the DACA output value to test its accuracy
AMP0,AMP1,Ain2 = pico.SetDAC_and_MeasureADC0andADC1andADC2(DCsetpoint)
AMP0v = pico.convert_samples_to_volts(AMP0)
AMP1v = pico.convert_samples_to_volts(AMP1, gain=0.333)
Ain2v = pico.convert_samples_to_volts(Ain2)
plt.plot(AMP0v[:500], label='AMP0')
plt.plot(AMP1v[:500], label='AMP1')
plt.plot(Ain2v[:500], label='Ain2')
plt.xlabel('Sample')
plt.ylabel('Signal [V]')
plt.legend()

It’s probably not as stable as you would expect it!

Let’s run some familiar statistics in the next section.

avg_signal_AMP0v, std_dev_AMP0v = pico.compute_noise_statistics(AMP0v, DC=DCsetpoint)
avg_signal_AMP1v, std_dev_AMP1v = pico.compute_noise_statistics(AMP1v, DC=DCsetpoint)
avg_signal_Ain2v, std_dev_Ain2v = pico.compute_noise_statistics(Ain2v, DC=DCsetpoint)
pico.plot_noise_spectrum(AMP0v, label="AMP0")
pico.plot_noise_spectrum(AMP1v, label="AMP1")
pico.plot_noise_spectrum(Ain2v, label="Ain2")
plt.legend()

Conclude#

Just like in the ADC Test:

  1. Make a note of the magnitude of the error. Will it affect your Voltammetry measurements?

  2. Is there any significantly dominant frequency? Does it change when you disconnect -12V from Cria?

  3. What is the magnitude of the noise? How does it compare to the resolution of DAC?

The last answer is especially important for finding out the limits for the Voltammetry measurements.

### Notes

Let’s save the acquired data - for practice, and for future reference!

DATA=np.zeros((3,pico.NUM_SAMPLES))
DATA[0,:]=Ain0v 
DATA[1,:]=Ain1v
DATA[2,:]=Ain2v

np.save('temporary_data.npy', DATA)
del(DATA)
%fetchfile --binary "temporary_data.npy" "DAC_Setpoint_Test_DATA_-12V.npy"
# %fetchfile --binary "temporary_data.npy" "DAC_Setpoint_Test_DATA.npy"

πŸ†πŸ†πŸ† I3.3 Resolution#

In this section, you will experimentally find the resolution of the DAC. Use the same setup as before.

The provided test function: pico.test_dac_resolution measures a voltage ramp from DACA over very fine steps of step_mV=0.1mV over a range of span_mV=10mV using the ADC0 via amplifier AMP0.

In the first attempt, DACA is set to a desired value, and it is measured only once at each step.

  1. Run the code below

machine.soft_reset()
%serialconnect to --port="COM5"

%sendtofile /picotools.py --source picotools.py

import picotools as pico

import numpy as np
import matplotlib.pyplot as plt
import machine
voltages, ADC0, ADC0_errors, mean_error = pico.test_dac_resolution(adc=pico.adc0,gain=1,step_mV=0.1,set_initial_voltage_mV=2500,span_mV=10, N_iter=1)

plt.plot(voltages, ADC0, label="ADC0 Measurement")
plt.xlabel("DAC Voltage (mV)")
plt.ylabel("ADC Reading (mV)")
plt.title("DAC Voltage vs ADC Reading from a single measurement")
plt.legend()
plt.grid(True)
  1. Have a look at the value of the Absolute mean error printed above the plot. Does it look familiar?

Most likely it’s similar to the ADC noise and DAC accuracy that you measured before. Confirm with the statistics plot below.

plt.plot(voltages, ADC0_errors, label="ADC0 Measurement")
plt.xlabel("DAC Voltage (mV)")
plt.ylabel("ADC Reading Error (mV)")
plt.title("DAC Voltage vs ADC Reading Error from a single measurement")
plt.legend()
plt.grid(True)

We must therefore average over several measurements to resolve the finer steps. It turns out, that we must average a lot!

  1. In the next cell, each step is measured 4000 times! So, it will take a few seconds to complete the measurement, but be patient - It’s worth it!

    If by any chance, this large number of iterations causes a problem with Pico, you just learned why we need all those cumbersome save-and-reset procedures.
    Try lowering the N_iter to 1000 or so and reset Pico to resolve this issue. Then, re-run this entire section.

voltages, ADC0, ADC0_errors, noise = pico.test_dac_resolution(adc=pico.adc0,gain=1,step_mV=0.1,set_initial_voltage_mV=2500,span_mV=10, N_iter=4000)

plt.plot(voltages, ADC0, label="ADC0 Measurement")
plt.xlabel("DAC Voltage (mV)")
plt.ylabel("ADC Reading (mV)")
plt.title("DAC Voltage vs ADC Reading from averaged measurement")
plt.legend()
plt.grid(True)

If everything went well, and you see a staircase, you should be able to deduce the DAC’s resolution from the plot!

And you can also confirm it from the amplitude of the error plotted below.

plt.plot(voltages, ADC0_errors, label="ADC0 Measurement")
plt.xlabel("DAC Voltage (mV)")
plt.ylabel("ADC Reading Error (mV)")
plt.title("DAC Voltage vs ADC Reading Error from averaged measurement")
plt.legend()
plt.grid(True)

Conclusions#

Think about the following questions and make some notes:

  1. What can you conclude about:

    • the accuracy of the DAC?

    • the noise when measuring no input and the noise when measuring the DAC signal?

  2. What happens when you try to take smaller steps than the smallest detectable increment?

In the actual measurement, you won’t be able to measure 4000 times at each step to average over the noise. You just learned that it takes a relatively long time, but you can afford to measure a few times at each step.

In the next section, you’ll explore the effect of multiple measurements on timing of the experiment.

πŸ†πŸ† Implement 4: Timing accuracy#

The function provided below pico.test_pico_timing_with_for_loop() measures the time between each step of a measurement with averaging. You can adjust two parameters:

  1. N_iter sets the number of measurements for all three ADCs that are then averaged at each step of your experiment

  2. delay_ms sets the delay between each step of your experiment

By default, NUM_SAMPLES=512 for demonstration.

Run the code below for different values, for example:

  • N_iter=1,3,10

  • delay_ms=1,2,5,10

Make notes from your observations, and feel free to reach out to the TAs for a discussion.

machine.soft_reset()
%serialconnect to --port="COM5"

%sendtofile /picotools.py --source picotools.py

import picotools as pico

import numpy as np
import matplotlib.pyplot as plt
import machine
times = pico.test_pico_timing_with_for_loop(N_iter=1,delay_ms=1)
plt.plot(times*1e-3)
plt.xlabel("Sample")
plt.ylabel("Duration of the measurement [ms]")
plt.grid(True)
### Notes
πŸ’‘ Hints

Note that pico.test_pico_timing_with_for_loop() uses a for loop for averaging over multiple measurements. It has an if-statement, and the averaging happens between the steps. Also, note that time-keeping takes some time too, so there are many operations additional to setting the DAC value and reading the ADCs.
The results above demonstrate how essential it is to optimise your code for the final measurements, especially if you are using averaging and DELAY_MS < 10ms”

Implement 5-Basic: DAC Assistant + Dual ADC#

Implement 5 is required only for the Basic Design, and optional for the Advanced Design.

Goal 1: Calibrate your DAC Assistant and save the setting to a file
Goal 2: Measure the the accuracy, and the effective resolution of the controls used in the Basic Design
Goal 3: Practice setting attenuation 1:3 for positive and negative signals

In the Alpaca Manual, you can find a formula for the output DAC Assistant:

\[ U^{\text{DAC Assistant}}_{OUT} = 5 \cdot (U^{\text{DAC Assistant}}_{+IN} - 2.048\text{V}) \]

In reality, the expected offset 2.048V might have a significant error making your potentiostat unusable. Follow the procedure below to calibrate your DAC Assistant.

❗❗❗ I5Basic.1: Calibration#

  1. Build the calibration setup as shown in the Fritzing below.

πŸ’‘ Fritzing: DAC Assistant Calibration

DAC Assistant Calibration Setup

  1. Run the code in the cells below to measure the accuracy of your DAC Assistant.

This calibration procedure performs a voltage sweep from -3V to +3V and checks for the accuracy of the generated signal.

machine.soft_reset()
%serialconnect to --port="COM5"

%sendtofile /picotools.py --source picotools.py

import picotools as pico

import numpy as np
import matplotlib.pyplot as plt
import machine
ref, Ain0, Ain1, Ain2, err_in, err_out = pico.test_DAC_Assistant()
# Plot the DAC Assistant measured DAC Assistant output vs DAC A output
plt.plot((5 * (ref - 2.048)), Ain0+Ain1)
plt.xlabel("Expected value converted from the DAC_A output [V]")
plt.ylabel("DAC Assistant measured output [V]")
  1. Do you observe a large offset error in the figure above?

### Notes: 
  1. Also, check the accuracy of DACA. Could it be causing such a large deviation?

# Plot Error on the DAC_A (SET vs GET)
plt.plot(ref, 1e3*err_in)
plt.xlabel("DAC_A voltage [V]")
plt.ylabel("DAC_A output error [mV]")
# Plot Error of the DAC_Assistant OUT (SET vs GET)
plt.plot((5 * (ref - 2.048)), err_out*1e3)
plt.xlabel("Expected DAC Assistant output [V]")
plt.ylabel("DAC Assistant output error [mV]")

It is rather unlikely that DACA inaccuracy leads to such a large error in the DAC Assistant OUT.

We measured it before already, but keep in mind that DACA output error is magnified five times in the DAC Assistant OUT, so its effect here could be very significant.

This is one of the main causes of the Basic Design potentiostat’s low performance in some experiments.

At this point, we can only calibrate the offset, but the error that you see in the first from the two plots above is not likely to go away.

  1. Run the calibration procedure in the cell below.

calibrated_offset = pico.Calibrate_DAC_Assistant(1.5)

If the calibration was successful, go ahead and SAVE THIS VALUE! … and if it is not working, please report this problem to the TAs!

Optionally, come up with a way to save this value in a file!

You can also run this calibration, on demand, without any modifications - as long as your setup matches the one required for this section. This won’t be the case for long in this project, so it is recommended that you find a practical way to include this offset in your code.

Let’s confirm that it worked by testing the accuracy again.

ref, Ain0, Ain1, Ain2, err_in, err_out = pico.test_DAC_Assistant(offset=calibrated_offset)
plt.plot((5 * (ref - calibrated_offset)), Ain0+Ain1)
plt.xlabel("DAC_A output [V]")
plt.ylabel("DAC Assistant measured output [V]")

You should see a straight line here!

  1. Let’s check the DACA error again. It should stay mostly the same, but shifted a bit.

# plot Error on the DAC_A (SET vs GET)
plt.plot(ref, 1e3*err_in)
plt.xlabel("DAC_A voltage [V]")
plt.ylabel("DAC_A output error [mV]")
  1. It’s the moment of truth! πŸ₯ The accuracy of the calibrated DAC Assistant is….

plt.plot((5 * (ref - calibrated_offset)), err_out*1e3)
plt.xlabel("DAC Assistant output [V]")
plt.ylabel("DAC Assistant output error [mV]")
  1. How does it compare to the DACA output error?

#Notes

πŸ†πŸ†πŸ† I5Basic.2: Resolution - DAC Assistant#

Here, once again, you will test the resolution - This time, the one of the DAC Assistant.

The insights of this section are essential for finding the limits for parameters to run Cyclic and Squarewave Voltammetry with the Basic Design

Use the same setup as in the section above and follow the steps of the familiar procedure.

machine.soft_reset()
%serialconnect to --port="COM5"

%sendtofile /picotools.py --source picotools.py

import picotools as pico

import numpy as np
import matplotlib.pyplot as plt
import machine
calibrated_offset = pico.Calibrate_DAC_Assistant(1.5)

Let’s first try with just one measurement at each step.

N_iter = 1
voltages, ADC0, ADC0_errors, mean_error = pico.test_dac_assistant_resolution(adc=pico.adc0,gain=1,step_mV=0.1,set_initial_voltage_mV=2000,span_mV=10, N_iter=N_iter, calibrated_offset=calibrated_offset)

plt.plot(voltages, ADC0, label="ADC0 Measurement")
plt.xlabel("DAC Assistant Voltage (mV)")
plt.ylabel("ADC Reading (mV)")
plt.title(f'DAC Assistant Voltage vs ADC Reading avg from {N_iter} measurement(s)')
plt.legend()
plt.grid(True)
  1. Is the Absolute mean error printed above the plot similar as for DAC in Implement3.3?

Let’s get a better idea of it with the statistics plot:

plt.plot(voltages, ADC0_errors, label="ADC0 Measurement")
plt.xlabel("DAC Assistant Voltage (mV)")
plt.ylabel("ADC Reading Error (mV)")
plt.title(f'DAC Assistant Voltage vs ADC Reading avg from {N_iter} measurement(s)')
plt.legend()
plt.grid(True)

Most likely, it is much bigger, which is expected.

Think about the gain factor in the DAC Assistant OUT formula. Could it be related to the magnitude of this error?

  1. So, let’s again average over many samples to remove the noise.

N_iter = 5000
voltages, ADC0, ADC0_errors, mean_error = pico.test_dac_assistant_resolution(adc=pico.adc0,gain=1,step_mV=0.1,set_initial_voltage_mV=2000,span_mV=10, N_iter=N_iter, calibrated_offset=calibrated_offset)

plt.plot(voltages, ADC0, label="ADC0 Measurement")
plt.xlabel("DAC Assistant Voltage (mV)")
plt.ylabel("ADC Reading (mV)")
plt.title("DAC Assistant Voltage vs ADC Reading from averaged measurement")
plt.legend()
plt.grid(True)

Do you still see a staircase?

In this case, the steps are most likely much bigger, and therefore, the resolution lower.

  1. You can also confirm with the amplitude of the error plotted below.

plt.plot(voltages, ADC0_errors, label="ADC0 Measurement")
plt.xlabel("DAC Voltage (mV)")
plt.ylabel("ADC Reading Error (mV)")
plt.title("DAC Voltage vs ADC Reading Error from averaged measurement")
plt.legend()
plt.grid(True)

Conclude#

  1. What is therefore the smallest step for the Ucell increment in your Voltammetry measurements with the Basic Design?

  2. And taking into considerations that in your measurements, you won’t be able to average over thoursands of samples, but only a few. What is the realistic resolution?

### Notes:

πŸ†πŸ† I5Basic.3: Noise - Optional#

This section is optional, but it is recommended! You can learn more about the magnitude of noise in the Basic Design to be able to identify problems later in your design

Use the same test setup as for Implement 5B.1: Calibration, follow the steps, and write down your conclusions.

This test takes a few seconds

NUM_SAMPLES sets the number of points across the test range , which is -3V to 3V by default
interations sets the number of samples at each step to computer the noise statistics

For large values, you might expect problems with Pico’s memory and plotting.

machine.soft_reset()
%serialconnect to --port="COM5"

%sendtofile /picotools.py --source picotools.py

import picotools as pico

import numpy as np
import matplotlib.pyplot as plt
import machine
calibrated_offset = pico.Calibrate_DAC_Assistant(1.5)
ref, Ain0, Ain1, Ain2, Ain0std, Ain1std, Ain2std, err_in, err_out = pico.test_DAC_Assistant_noise(offset=calibrated_offset, NUM_SAMPLES=600, iterations = 100, gain0=1, gain1=-1)
plt.plot((5 * (ref - calibrated_offset)), Ain0std*1e3, label='Noise via ADC0')
plt.plot((5 * (ref - calibrated_offset)), -Ain1std*1e3, label='Noise via ADC1')
plt.xlabel("DAC Assistant output [V]")
plt.ylabel("ADC average readout error [mV]")
plt.legend()
### Notes

Implement 6-Basic: ADC Gain 1:3#

The section I6B.1 is mandatory for Basic Design: Learn how to implement attenuation 1:3 with Basic Design.

❗❗❗ I6Basic.1: Configure and test ADC Gain 1:3#

Implement the setup presented in the Fritzing below and run the test below to find out if you got it right!

πŸ’‘ Fritzing: Basic Design - Gain 1:3

DAC Test



machine.soft_reset()
%serialconnect to --port="COM5"

%sendtofile /picotools.py --source picotools.py

import picotools as pico

import numpy as np
import matplotlib.pyplot as plt
import machine
calibrated_offset = 2.2826
# GAIN (1:3)
ref, Ain0v, Ain1v, Ain2v, err_in, err_out = pico.test_DAC_Assistant(offset=calibrated_offset, gain0=0.333, gain1=-0.333)
# Plot the DAC Assistant measured DAC Assistant output vs DAC A output
plt.plot((5 * (ref - calibrated_offset)), Ain0v+Ain1v)
plt.xlabel("Expected value converted from the DAC_A output [V]")
plt.ylabel("DAC Assistant measured output [V]")
### Notes:

πŸ† I6Basic.2: Noise - (GAIN 1:3) - Optional#

This section is entirely optional. Use it for debugging and to understand the cumulative effect of noise due to amplification and attenuation.

There is an interesting difference between the results of GAIN 1:1 (in section I5B.3) in and GAIN 1:3 (here). Can you argue what’s behind that?

# GAIN (1:3)
ref, Ain0, Ain1, Ain2, Ain0std, Ain1std, Ain2std, err_in, err_out = pico.test_DAC_Assistant_noise(offset=calibrated_offset, NUM_SAMPLES=240, iterations = 50, gain0=0.3333, gain1=-0.3333)
plt.plot((5 * (ref - calibrated_offset)), Ain0std*1e3, label='Noise via ADC0')
plt.plot((5 * (ref - calibrated_offset)), -Ain1std*1e3, label='Noise via ADC1')
plt.xlabel("DAC Assistant output [V]")
plt.ylabel("ADC readout error [mV]")
plt.legend()

πŸ†πŸ†πŸ†πŸ Optional - Implement 7: Write Helper Functions#

Write some helper functions to make your further steps easier. Here are some ideas:

  1. πŸ†πŸ†πŸ†πŸ Function to calculate the detectable Icell range based on the Ucell, Rf and the values of attenuation. Its form will vary per design.

  2. πŸ†πŸ†πŸ†πŸ Function to calculate the cutoff frequency of the tamed integrator.

  3. πŸ†πŸ† Advanced Design only - Function for using the Alpaca’s Relay for switching between two different Rf resistors.

  4. πŸ†πŸ† Advanced Design only - Function for including the use of DAC-B

  5. πŸ†πŸ†πŸ†πŸπŸπŸ Your own ideas!

# Task for the students!

❗❗❗ Compare and Conclude#

To check-off with the TA: From DAC-accuracy:

  1. Make a note of the magnitude of the error. Will it affect your Voltammetry measurements?

  2. Does it change when you disconnect -12V from Cria? Run the measurements in the section with and without this connection.

  3. (for the Basic Model): discuss the outcome and necessity of the DAC assistant calibration

  4. (for the Advanced Model): discuss which additional picotools functions you will/did write for use of DAC-B

Mandatory to think about (for the report):

  1. Argue how significant; - the ADC noise - the DAC noise - the DAC Assitant noise are for your measurements.

  2. How can you reduce the influence of each noise?

  3. What can you conclude about the smallest detectable increment of the:

  • DAC Assistant?

  • DAC A measured directly?

  1. What happens when you try to take smaller steps than the smallest detectable increment?

  2. How does it relate to the resolution of the ADCs?

  3. Can you already predict what will be the lowest effective Ucell increment for Voltammetry with your design?

Challenging:

  1. What is the estimated effective resolution in each design when you can take only a few measurements at once for averaging

  2. How does it affect the electrochemical reaction in the measurement cell?

Very challenging:

  1. How does the Icell resolution depend on the ADC gain?