For use with: Alpaca Kernel

Voltammetry - Final Project

18A - Cyclic Voltammetry - Data Acquisition#

Notebook 18A contains a set of guidelines for developing a Cyclic Voltammetry procedure for your Potentiostat.

Goals - Overview:

  • Week 17: Understand the design limitations and build a simple model of a potentiostat

  • Week 18: Build the potentiostat and write code for voltammetric measurements

  • Week 19: Run voltammetric measurements with different techniques and samples

Experiments - Week 18:

  • 18A: Cyclic Voltammetry - Part 1 - Data Acquisition

  • 18B: Cyclic Voltammetry - Part 2 - Data Evaluation

  • 18o: Optional Voltammetric Techniques

Background#

Workflow for Voltammetry Experiments#

The workflow for the Voltammetry experiments with your Potentiostat will be divided into two parts:

  1. Part 1 - Data Acquisition - Micropython (Alpaca Kernel)

  2. Part 2 - Data Evaluation - Python (ipykernel)

This approach mimics the typical workflow with commercial devices. It also is required to circumvent some limitations of Pico Pi. Notebooks 18A and 18B address the details of each part separately.

This workflow is universal at the abstract level and will similarly apply to the optional voltammetric techniques, like Squarewave Voltammetry or Differential Pulse Voltammetry. However, the list of parameters for the configuration of the potentiostat will be different in each case, so separate notebooks tailored for those techniques will be required.

Goals#

  • Write a procedure for Cyclic Voltammetry

  • Test your code with a non-linear potentiostat model

  • Test your potentiostat with the buffer solution

Structure#

  1. Background Preparation

  2. Anticipate: Classroom, Recommended as Preparation!

  3. Simulate: Classroom, Recommended as Preparation!

  4. Implement: Classroom

  5. Conclude and Choose: Classroom

Requirements#

  1. ❗❗❗ = Mandatory

  2. 🏆 = Entirely optional - It is interesting and leads to a deeper understanding.

  3. 🏆🏆 = Optional. Recommended - It can be re-visited later.

  4. 🏆🏆🏆 = Recommended - It is relevant now.

  5. 🏆🏆🏆🐐 = Strongly recommended - It might be challenging, but it is surely worth the effort!

Note: The number of trophies (🏆) indicates the level of recomendation for the optional tasks. It does not correlate with the level of difficulty.

Cyclic Voltammetry - Part 1 - Data Acquisition#

The outline of this chapter reflects a proposed order of tasks that lead to a successful measurement with your Alpaca-and-Pico-Pi-based Potentiostat. You may consider this outline as a form of a common tool used in programming, called a block diagram, and you are invited to follow these guidelines in your implementation. Cyclic Voltammetry procedure for your potentiostat should encompass the following tasks in order:

Potentiostat - Data Acquisition

  1. Initialisation

  2. Potentiostat configuration

  3. Waveform preparation

  4. Performing measurement

  5. Saving results

  6. Displaying results

Mentioned tasks are further explained in the sections below.

❗❗❗ Initialisation#

In the beginning, as usual, make sure to import the required modules and initialise the hardware peripherals. You may omit importing the dac module in this project, and use an improved function in picotools instead.

❗❗❗ Picotools#

Therefore, import picotools. It is required for a correct output of the DACs. Instead of the familiar, dac_a.write() use pico.dac_a.write() to set the DACA output. Similarily for DACB. It is a small change, and you can find further instructions in notebook 17C, in the comments of the provided code.

Note that no other function from picotools is strictly required for this project, and you may rely on your experience and the code that you wrote throughout the course. Besides, we can’t guarantee at this point that all functions included in picotools are bug-free. Please, use them with caution and follow your intuition, but feel free to use them as inspiration.

❗❗❗ Relay - Basic Design only#

In the Basic Design, the Alpaca’s relay is a necessary component for connecting the Measurement Cell only for the duration of the measurement. You may similarly find some hints for the use of the relay in notebook 17C.

❗❗❗ Potentiostat Configuration#

The parameters chosen for the list in this section were inspired by the settings of a commercial potentiostat. Have a look at the screenshot of software for Cyclic Voltammetry from PalmSens for reference. Note that you are not required to develop any additional graphical interface for your code.


Cyclic Voltammetry - Voltammogram with a commercial software

The list below contains the standard and advanced parameters that have to be set before each measurement.

  • Parameters marked with ❗❗❗ are a minimum requirement for a successful Cyclic Voltammetry experiment in week 19.

  • The ones marked with the symbol of a link (🔗) are linked together, and their correct implementation might require a creative approach. Choosing fixed optimal values to avoid the conflict between these settings will satisfy the minimum requirement of this assignment.

  • The other parameters, marked with 🏆, are optional but recommended for high quality results and to enable experimenting with some optional sample material.

❗❗❗ List of parameters for Cyclic Voltammetry#

Click on the name to reveal additional information.

1. ❗❗❗ Umax

If the cell potential starts and ends at the ground reference, Ucell = 0, then Umax sets the maximum positive Ucell, and by default, the first turning point during the cycle. For example, Umax = 1.2V.

2. ❗❗❗ Umin

Similarly, Umin sets the maximum negative Ucell, the second turning point during the cycle. For example, Umin = -0.5V.

3. ❗❗❗ rate

defines the rate of change for Ucell over time during the measurement.
It’s typically expressed in \(\frac{\text{mV}}{\text{s}}\) or \(\frac{\text{V}}{\text{s}}\). This parameter could be returned for the measurement as, for example: DELAY_MS.

4. ❗❗❗🔗 Ustep

defines the step, increment or decrement of Ucell between subsequent measurements, typically expressed in \(\text{mV}\) or \(\text{V}\). Recall the minimum step possible (varies per design) and consider the constraints related to NUM_SAMPLES.

5. 🏆🏆🏆🔗 Ncycles

defines the number of full subsequent cycles between Umax and Umin to record during one acquisition.

6. ❗❗❗🔗 NUM_SAMPLES

Constraining the number of samples for the acquisition interferes with Ustep. Furthermore, there is a maximum limit of points that can be stored and handled by the Pico Pi. This limit depends on the level of optimisation of your code, and could be found anywhere between 1000 and 4000+ samples. For example, if one cycle between -0.5V and 1.5V follows Ustep=1mV, then your acquisition has 4000 points. Running two cycles at this setting will certainly lead to a memory error. It is therefore essential to set a constraint. For example, to display a warning and set the minimum Ustep *= Ncycles. In practice, especially during the development and the test phase, opt for measurements with 1000 points or less than that to avoid memory errors.

7. 🏆🏆 Ubegin

sets a non-standard starting cell potential Ucell, for example Ubegin = -0.2V.

8. 🏆🏆 wait

If Ubegin is different from the ground reference, the initial dynamic Ucell sweep to that value usually disturbs the electrochemical equilibrium in the measurement cell. The wait parameter sets a delay between the initial sweep and the actual measurement that is necessary for the equilibration, typically expressed in seconds.

9. 🏆 direction

sets the order for the initial cell potential sweep;
from Ubegin to Umax or from Ubegin to Umin.

10. 🏆🏆🏆 Navg

defines the number of samples recorded at each cell potential step.

11. 🏆🏆🏆 Hardware configuration

Use this optional feature to store the hardware configuration in the same file as the other parameters chosen for the experiment.

  1. Rf for extracting Icell from the measurement

  2. Similarly, the attenuation: gainADC0 and/or gainADC1.

  3. C for computing the cut-off frequency of the analog filter.

  4. Irange - Basic and Advanced Design: returns only the information about the current range based on the given parameters. - Advanced Design only: If a relay-based Rf selector is implemented, choose the range here.

12. 🏆🏆🏆 Advanced Design Only

The optional parameter to programmatically control the Irange using DACA and DACB.


🏆🏆 Saving the configuration#

Saving the list of parameters along with the results is a good practice that allows to confidently review and evaluate the results. Consider automatically exporting pre-defined variables in the form of Python’s dictionary data type, or come up with your own solution to improve the workflow.

### Notes

❗❗❗ Waveform Preparation#

We suggest using the approach in which the Ucell is controlled by iteratively setting the DACs to a pre-defined desired value using a for-loop, which allows synchronising the measurement with the expected Ucell. This approach requires the complete set of instructions for the DACs to be prepared and stored in an array before the measurement starts. Below, you can find some hints on how to achieve that. You are nevertheless allowed to use your custom approach to controlling your potentiostat.

🏆🏆🏆 Triangle#

The functiongenerator class in Pico Pi has an object Triangle that simplifies waveform preparation. It is equipped with a method for extracting the array with the points for a single oscillation of a triangular waveform: Triangle.get_voltages(n=NUM_SAMPLES_per_cycle). You can read more about the use of this class in the Alpaca Kernel reference.

Using Triangle

Note that there are three distinct ways of defining the triangle:

from functiongenerator import Triangle

triangle1 = Triangle(Vmin=-0.5, Vmax=1.5, freq=1, symmetry=50)
triangle2 = Triangle(Vpp=2, offset=-0.2, freq=1, symmetry=50)
triangle3 = Triangle(Vp=2, offset=-0.2, freq=1, symmetry=50)

DACinput = triangleN.get_voltages(n=1000)

Hints:

  • freq is a required but an obsolete parameter in this scenario, so you may set it to 1 and forget it.

  • symmetry=100 could be particularly useful if you only want to extract a ramp instead of a triangle

🏆🏆🏆 Advanced Waveforms#

There are various ways of constructing more advanced waveform depending on the desired final shape, and they have to be chosen based on your requirements.

Hints - Advanced Waveforms

For example, if you want to run multiple cycles in one acquisition, so Ncycles>1, some useful methods could be:

  1. Python methods - for lists, i.e:

    • list.append()

    • list.extend()

  2. numpy methods - for numpy arrays, i.e:

    • numpy.array(list)

    • numpy.append()

    • numpy.concatenate()

🏆 Custom functions#

It is possible to prepare the desired set of instructions for the DACs entirely from scratch, without using Triangle, which might be a preferred approach in some use cases.

The programming approach is then entirely in your hands.

❗❗❗ Performing Measurements#

We recommend collecting the operations for controlling the DAC(s) and reading the ADCs in a single function, for example: runCVmeasurement(), or to dedicate one Jupyter cell in your notebook for that purpose only.

Remember that you can use up to three ADCs, and it is recommended to use them all. Make an informed choice to record additional signals for reference - varies per design.

🏆🏆🏆Measurement procedure template

Following up on the approach suggested in the previous section, the core of the procedure could be built using this template:

U0 = np.zeros(NUM_SAMPLES)

for ii in range(NUM_SAMPLES):
   pico.dac_a.write(DACinput[ii])
   time.sleep_ms(int(DELAY_MS))

   U0[ii] += adc0.read_u16() 
   
pico.dac_a.write(0)
print('Measurement complete')
🏆🏆Optimising the measurement procedure

Any additional operation inside the for-loop will cause the real delay between each measurement to become longer than the desired DELAY_MS and might have to be accounted for high rate values to be accurate. The causes for an additional hidden delay include acquiring and adding multiple samples for averaging, so it is recommended to find an optimal approach.

For example, the subsequent samples at DACinput[ii] recorded with ADC0 could be added to U0[ii] with U0[ii] += adc0.read_u16() and then divided by Navg after the measurement or even in the evaluation phase.

🏆Monitoring the measurement with liveplot
  • It is possible to use the familiar liveplot function to display the signal during the acquisition. Note that this method is useful rather only for slow acquisitions. See example below as reference.

%plot --mode live

U0 = np.zeros(NUM_SAMPLES)

for ii in range(NUM_SAMPLES):
   pico.dac_a.write(DACinput[ii])
   time.sleep_ms(int(DELAY_MS))

   U0[ii] += adc0.read_u16() 

   sample = U0[ii]   
   plt.liveplot(sample*4.577E-5, labels = ['ADC0 [V]'])

pico.dac_a.write(0)
print('Measurement complete')

❗❗❗ Saving Results#

Due to the memory limitation in Pico Pi, we recommend storing the results from your potentiostat on your computer and evaluating them using a separate notebook for with ipykernel - This procedure will be explained in more detail in notebook 18B - Cyclic Voltammetry - Part 2 - Data Evaluation.

This workflow ensures that your data is not lost when Pico Pi errors occur, and allows using Python functions that are not implemented in Pico Pi’s Micropython Alpaca Kernel.

Saving Results is not essential during the development and testing phase, so it could be disabled/skipped to save time.

❗❗❗ Saving numerical data#

This section is adapted from the notebook 17B.
Read sections Implement 1.3 - Saving Data and Implement 1.3 - Handling Errors in notebook 17B for more suggestions.

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 enough storage on Pico for only just one such aggregated array, so every time you run a new 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.

🏆🏆🏆 Template code for saving numerical data
DATA=np.zeros((4,NUM_SAMPLES))
DATA[0,:]=U0 
DATA[1,:]=U1
DATA[2,:]=U3
DATA[3,:]=DACinput #Optionally

np.save('temporary_data.npy', DATA)
del(DATA)
%fetchfile --binary "temporary_data.npy" "Buffer#1.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') # optionally
pico.list_files() # optionally

🏆🏆🏆 Saving the Waveform#

Consider saving the waveform DACinput used for controlling the DACs as a reference along with the signals recorded during the measurement. You might want to skip it when the amount of the measurement data alerady exceeds Pico limits and causes errors. It can be recreated in the Data Evaluation notebook.

❗❗❗ Displaying Results#

Since plotting using Alpaca Kernel often causes trouble with memory management and may force you to reset Pico, we strongly recommend plotting the results for confirmation of the successful measurement in the data acquisition notebook only after the data has been saved and fetched to your computer.

Use the troubleshooting and plotting hints listed in notebook
17B - Introduction 1.4 - Handling Errors.

❗❗❗ Anticipate#

❗❗❗ A1. Develop your Data Acquisition Procedure for Cyclic Voltammetry#

  1. Create a separate Jupyter notebook for running Cyclic Voltammetry experiments with your potentiostat.

  2. Use the sections of 18A: Background - Data Acquisition as a guide and write code to implement the Cyclic Voltammetry - Data Acquisition procedure.

    • 🏆🏆🏆🐐 Consider exporting your finished functions into a file collecting all your functions for running cyclic voltammetry experiments, for example: cyclopico.py. Use the case of picotools for inspiration.

❗❗❗ A2. Predict Signals from the Measurement Cell Model 2#

  1. Recall how Zener diode operates and explain how this model can help you test whether the Cyclic Voltammetry - Data Acquisition code for your potentiostat is correct.

### Notes

🏆🏆🏆 Simulate#

S1. Potentiostat: Non-linear Model#

Simulate your Potentiostat with the Measurement Cell Model 2.

Refer to notebook 17A - Background for details and hints.

Basic Design
  1. Insert a Zener diode 1N750 into your circuit.

    • Consider an alternative Measurement Cell Model 2 proposed in notebook 17A: Background instead.

  2. Simulate the Voltamommogram for the Ucell from -5V to +5V.

  3. Consider changing the axes and displaying other suitable traces that could aid your understanding of the model.

  4. Describe how this model could help you test your code for the potentiostat.

Hint: Displaying time on the x-axis instead of V(we)-V(re) might be more insightul.

Advanced Design

Due to the limitation of the Ucell range spanning over maximum 3V, the use of the default Zener diodes provided in LTSpice might not be as insightul as expected. Importing or defining a custom Zener diode with a breakdown voltage in this range could solve this problem. However, it might be sufficient for gaining more insights into the working of your potentiostat and to have a test reference with one of the alternative Measurement Cell Model 2 implemented instead.

  1. Choose an alternative Measurement Cell Model 2 implementation and add it to the circuit in the LTSpice simulation file for the Advanced Design.

  2. Simulate the Voltamommogram for the Ucell, for example: from -1.5V to +1.5V or any other range available in this design to capture the non-linear behaviour of this model.

  3. Consider changing the axes and displaying other suitable traces that could aid your understanding of the model.

  4. Describe how this model could help you test your code for the potentiostat.

Hint: Displaying time on the x-axis instead of V(we)-V(re) might be more insightul.

### Notes

❗❗❗ Implement and Investigate#

❗❗❗ I1. Non-linear model#

  1. Implement the selected option for the Measurement Cell Model 2.

  2. Test your Cyclic Voltammetry code using this model.

  3. Define what makes you decide whether the test is successful or not, and optionally use the simulation from S1 to support your decision.

### Notes

❗❗❗ I2. Test Sample - Buffer solution (PBS pH 7.4)#

  1. If your code is ready and/or passed the test in the Implement 1, connect the Voltammetry Adapter to your Alpaca and prepare the Italsens sensor.

  2. Place a droplet of the buffer solution on the sensor and make sure that it covers all three electrodes.

  3. Choose a set of suitable parameters and run test measurements with the buffer solution.

    • Measure Icell with Cyclic Voltammetry.

      • Basic Design: Across the range of Ucell between -1.4V to +1.4V.

      • Advanced Design: If the range given for the Basic Design turns out to be problematic, use a narrower negative range,
        for example, with Ucell between -1V to +1.4V.

  4. Plot the raw recorded signal (ADC counts vs time) without any further processing to confirm that the acquisitions were successful.

  5. Ensure that the positive peak from the buffer measurement optimally uses the ADCs range, for example ca. 50% to 80% of the available range (0 to 65355 counts). If that’s not the case, adapt your Rf to set your potentiostat detectable current range such that it allows you to capture a strong signal without clipping and leaves a margin. That way, you can directly start your experiment next week with all correct settings.

  6. Save the successful acquisition(s) and calculate the range of detectable currents for the chosen settings. Optionally, save the settings into a file for a future reference.

  7. Proceed to the Data Evaluation notebook for a full evaluation of the collected data.

🏆🏆🏆🐐 I3. Optimise and Improve your Potentiostat#

  1. 🏆🏆🏆🐐 Improve your results: Optimise the configuration, and increase the number of recorded samples to around or even beyond 2000 points and check whether your code works as expected. In short, test the limits of what’s possible in order to get the highest quality results from your potentiostat.

  2. 🏆🏆🏆🐐 Spot and discuss any significant effects resulting from inaccuracies of your potentiostat discovered in notebook 17B, and discuss techniques that could alleviate those problems.

### Notes

❗❗❗ Compare and Conclude#

To be checked off by a TA:

  1. Plot of the signal recorded during your successfully calibrated measurement of buffer.

  2. Choice of the resistor Rf, capacitor C and the attenuation setting for the AMP(s).

  3. Range of the detectable currents with the chosen settings.

Hints for writing your report#

The report will be submitted only after a few weeks, so to prevent overloading yourselves at the end of the term, gather as much information as you can NOW;

  • Proofs of working setups and simulations.

  • Explanations demonstrating your understanding of your implementation!

For example this week:

  1. Documentation of the Advancements you made

    • Your code,

    • Schematics, LTSpice, etc from this and the last week,

    • Explanation how you setup works; why you choose specific parameters, etc.

  2. The data recorded for the experiment with the buffer.

  3. Proof (and explanation) of the steps taken to optimise the measurements range of Icell for the experiment with Buffer, the selected gain for Uadc, and the Capacitor value.

### Notes