Worked Examples

In this section we show several worked examples demonstrating how to use iorodeo-potentiostat library with your Rodeostat shield to make measurements.

Note

The examples in this section make use of the Matplotlib [1] plotting library. You can find Instructions for installing maplotlib here: installing Matplotlib [2].

Cyclic voltammetry

In the following example we demonstate how perform a cyclic voltammetry [3] test with the following parameters:

  • Starts and ends at minimum voltage of -100 mV .
  • Reaches a maximum voltage of 1000 mV at mid-cycle.
  • Transistions at a rate of 50 mV/s (-50 mV/s) from the minimum (maximum) to the maximum (minimum) voltage.
  • Performs a single cycle i.e., transistions from minimum to maximum and back to minimum exactly once.

As the test is run the data are saved to a file named data.txt. The data in the file consist of comma separated values of time, voltage and current. After the test is complete two figures are created to display the data. The first figure shows plots of the voltage (potential) and current vs time. The second figure shows a plot of the current vs the voltage (potential).

from potentiostat import Potentiostat
import matplotlib.pyplot as plt

port = '/dev/ttyACM0'       # Serial port for potentiostat device
datafile = 'data.txt'       # Output file for time, curr, volt data

test_name = 'cyclic'        # The name of the test to run
curr_range = '100uA'        # The name of the current range [-100uA, +100uA]
sample_rate = 100.0         # The number of samples/second to collect

volt_min = -0.1             # The minimum voltage in the waveform (V)
volt_max =  1.0             # The maximum voltage in the waveform (V)
volt_per_sec = 0.050        # The rate at which to transition from volt_min to volt_max (V/s)
num_cycles = 1              # The number of cycle in the waveform

# Convert parameters to amplitude, offset, period, phase shift for triangle waveform
amplitude = (volt_max - volt_min)/2.0            # Waveform peak amplitude (V) 
offset = (volt_max + volt_min)/2.0               # Waveform offset (V) 
period_ms = int(1000*4*amplitude/volt_per_sec)   # Waveform period in (ms)
shift = 0.0                                      # Waveform phase shift - expressed as [0,1] number
                                                 # 0 = no phase shift, 0.5 = 180 deg phase shift, etc.

# Create dictionary of waveform parameters for cyclic voltammetry test
test_param = {
        'quietValue' : 0.0,
        'quietTime'  : 0,
        'amplitude'  : amplitude,
        'offset'     : offset,
        'period'     : period_ms,
        'numCycles'  : num_cycles,
        'shift'      : shift,
        }

# Create potentiostat object and set current range, sample rate and test parameters
dev = Potentiostat(port)     
dev.set_curr_range(curr_range)   
dev.set_sample_rate(sample_rate)
dev.set_param(test_name,test_param)

# Run cyclic voltammetry test
t,volt,curr = dev.run_test(test_name,display='pbar',filename=datafile)

# plot results using matplotlib
plt.figure(1)
plt.subplot(211)
plt.plot(t,volt)
plt.ylabel('potential (V)')
plt.grid('on')

plt.subplot(212)
plt.plot(t,curr)
plt.ylabel('current (uA)')
plt.xlabel('time (sec)')
plt.grid('on')

plt.figure(2)
plt.plot(volt,curr)
plt.xlabel('potential (V)')
plt.ylabel('current (uA)')
plt.grid('on')

plt.show()

An example of the data generated by running this test using a screen printed electrode in a 8mM solution of ascorbate, is shown below.

_images/cyclic_example_values_vs_t.png
_images/cyclic_example_curr_vs_volt.png

Further details on the methods used for the ascorbate experiments can be found here http://potentiostat.iorodeo.com/methods [4].

Constant voltage voltammetry

This example demonstrates how to preform constant voltage voltammetry. The test parameters are as follows:

  • Prior to the start of the test a quiet period is used where the output voltage at 0V for 1s (1000ms).
  • During the test the output voltage is set to 2.5V and kept there for 4s (4000ms).
  • Data is acquired at a rate of 100 samples/second for the duration of the quiet period and the constant voltage test.
from potentiostat import Potentiostat
import matplotlib.pyplot as plt

port = '/dev/ttyACM0'    # Serial port for potentiostat device
datafile = 'data.txt'    # Name of output data file

test_name = 'constant'   # Name of test to run - constant volate voltammetery
curr_range = '100uA'     # Name of current range for test [-10uA, +10uA]
sample_rate = 100.0      # Rate (samples/sec) at which to collect samples 

test_param = { 
        'quietValue' : 0.0,        # Output voltage during quiet peroid
        'quietTime'  : 1000,       # Duration of quiet period (ms)
        'value'      : 2.5,        # Output volatage (V) durring constant voltage test
        'duration'   : 4000,       # Duration of constant voltage test (ms)
        }

# Create Device object and set sample rate, current range and test parameters
dev = Potentiostat(port)             
dev.set_sample_rate(sample_rate)   
dev.set_curr_range(curr_range)     
dev.set_param(test_name,test_param)

# Run cyclic voltammetry test
t,volt,curr = dev.run_test(test_name,display='pbar',filename=datafile)

# plot results using matplotlib
plt.subplot(211)
plt.title('Voltage and current vs time')
plt.plot(t,volt)
plt.ylabel('potential (V)')
plt.ylim(0,test_param['value']*1.1)
plt.grid('on')

plt.subplot(212)
plt.plot(t,curr)
plt.ylabel('current (uA)')
plt.xlabel('time (sec)')
plt.grid('on')

plt.show()

Consider the dummy cell schematic show below. We will use this dummy cell to generate example data using the constant voltage test program (above).

_images/dummy_cell_schem.png

There are three resistors (R1, R2, R3) and one capacitor (C1) on the dummy cell. In the example below, showing voltage and current vs time, the values of the resistors and capacitors are: R1=100k, R2=50k, R3=0 Ohm (wire conductor), C1=4.7 uF.

_images/constant_example_curr_vs_volt.png

During the one the 1 second quiet period the output voltage is 0 V. After 1 second the actual test begins and the output voltage is set to 2.5 V. There an initial spike in current at the start of the test. The current then decays exponentially, as the capacitor C1 charges, gradually approaching a steady state value.

Manual/direct control:

In this section we give several examples which demostrate how to use the Rodeostat in manual/direct control mode. When using this mode, the output voltage is set and the current is measured directly from the host PC using the set_volt() and get_curr() methods. Note, the timing in manual/direct control mode is determined by the software on the host PC and it will not be as precise as when using a pre-programmed voltammetric test running in firmware on the Teensy 3.2. However, it does offer a great deal of flexibility to the user and enables the creation of custom voltammetric test without any firmware programming.

Example 1: linear plus sinewave

This example demonstrates how to use manual/direct control to create an output voltage which is sum of a linear function and a sine wave.

from __future__ import print_function
from potentiostat import Potentiostat
import time
import sched
import math
import scipy
import matplotlib.pyplot as plt


def run_manual_test(pstat, volt_func, dt, t_stop):
    """
    Run a voltammetric test in maunal/direct mode. 

    pstat      = potentiostat
    volt_func  = output voltage function
    dt         = sample time step
    t_stop     = duration of the trial
    """
    t = 0 
    cnt = 0
    t_start = time.time()
    time_list, volt_list, curr_list = [], [], []
    scheduler = sched.scheduler(time.time, time.sleep)

    while t < t_stop:
        # Set potentiostat output voltage and samle current 
        volt = volt_func(t)
        pstat.set_volt(volt)
        curr = pstat.get_curr()
        print('{0:1.2f}, {1:1.2f}, {2:1.2f}'.format(t, volt, curr))
        time_list.append(t)
        volt_list.append(volt)
        curr_list.append(curr)
        # Run scheduler to until time for the next sample (dt seconds)
        t_next = t_start + (cnt+1)*dt
        scheduler.enterabs(t_next, 1, lambda:None, ()) 
        scheduler.run()
        t = time.time() - t_start
        cnt+=1
    return time_list, volt_list, curr_list


def create_sin_linear_func(t0, t1, v0, v1, amp, per):
    """
    Returns a function which in the interval [t0,t1] is a sum of linear
    function and a sine wave.

    t0   = time at which linear transition, from v0 to v1, begins
    t1   = time at which linear transition, from v0 to v1, ends
    v0   = initial value
    v1   = final value
    amp  = amplitude of superimposed sinewave
    per  = period on superimposed sinewave

    """
    def func(t):
        if t < t0:
            return v0 
        elif t < t1:
            dt_trans = t1-t0
            v_lin = (v1-v0)*(t-t0)/dt_trans + v0
            v_sin = amp*math.sin(2*math.pi*(t-t1)/per)
            return v_lin + v_sin
        else:
            return v1
    return func 


if __name__ == '__main__':

    # Run parameters
    t0 = 10.0  # Transition start time (s)
    t1 = 50.0  # Transition stop time (s)

    v0 = -0.9  # Initial voltage (V)
    v1 =  1.2  # Final voltage (V)

    amp = 0.1  # Sinusoid ampliude (V)
    per = 2.0  # Sinusoid period (s)

    dt = 0.05  # Time step for setting voltage and measurements
    t_total = 60.0 # Total experiment duration 

    volt_func = create_sin_linear_func(t0,t1,v0,v1,amp,per)

    # Create device object, set voltage/current ranges and run test
    pstat = Potentiostat('/dev/ttyACM0')
    pstat.set_volt_range('2V')
    pstat.set_curr_range('100uA')
    t, volt, curr = run_manual_test(pstat, volt_func, dt, t_total)

    # Plot results
    plt.subplot(211)
    plt.plot(t,volt)
    plt.ylabel('potential (V)')
    plt.grid('on')

    plt.subplot(212)
    plt.plot(t,curr)
    plt.xlabel('time (s)')
    plt.ylabel('current (uA)')
    plt.grid('on')
    plt.show()

Example data generated using the program above for 50k dummy cell (R1=50k, R2=R3=0 , C1=0) is shown below.

_images/manual_control_v2.png

In a similar manner you can easily create other custom tests by writing your own custom voltage function which gives specifies the desired output voltage as a function of time.

Example 2: long duration constant voltage test

Suppose you want to perform a very long duration (hours to days) constant voltage test with the potentiostat. The sample rate for this test would be fairly low - seconds to minutes between samples. In addition you wanted it such that the logging application for this test could be interrupted and re-started and any time.

In the example below demonstrate how to use manual/direct control to create a such a data logger for long duration constant voltage tests. Once started, the data logger shown below will run forever or until stopped by the user with Ctl-C. The tests can be stopped and re-started and any time. On restart if the data logger finds an existing log file it will open this file and append data to it, otherwise a new log file will be created.

"""
const_volt_logger.py

Example of a constant voltage test data logger which is intended for very long
duration experiments with low sample rates. Uses Python's sched module for
timing. Data is logged to output file specified in the params. The test runs
indefinitely, but can  be interrupted with ctl-c. It can then restarted and
will continue to append data to the same data file. 

Potentiostat Shield is operated manual/direct control mode where output voltage
is set directly from python.

"""
from __future__ import print_function
from potentiostat import Potentiostat
import time
import os.path
import signal
import sched

class ConstVoltLogger(object):

    def __init__(self, param):
        """
        param - dictionary of parameters for the constant voltage test. It
        should contain the following items.

         filename      Data log filename
         port          Potentiostat serial port
         volt_range    Output voltage range
         curr_range    Measurement current range
         sample_dt     Sample period (s)
         volt          # Output voltage (V)

        """
        self.param = param
        self.done = False
        self.sep = ','

        self.pstat = Potentiostat(self.param['port'])
        self.pstat.set_volt_range(self.param['volt_range'])
        self.pstat.set_curr_range(self.param['curr_range'])
        self.pstat.set_volt(self.param['volt'])

        self.scheduler = sched.scheduler(time.time, time.sleep)
        self.scheduler_event = None
        signal.signal(signal.SIGINT,self.sigint_handler)


    def get_file_start_time(self):
        t_start_file = None
        if os.path.exists(self.param['filename']):
            line_list = []
            with open(self.param['filename'],'r') as fid:
                try:
                    line_list = fid.readlines()
                    start_line = line_list[0]
                    start_line = start_line.split(self.sep)
                    t_start_file = float(start_line[0])
                except: 
                    pass
        return t_start_file


    def run(self):
        self.done = False
        t_start = time.time()
        t_start_file = self.get_file_start_time()
        if t_start_file is None:
            t_start_file = t_start

        print()
        print('press ctl-c to exit')
        print()
        print('t (s), current (A)')
        print('------------------')

        with open(self.param['filename'],'a') as fid:
            cnt = 0
            while not self.done:
                curr = self.pstat.get_curr()
                t_current = time.time()
                t_elapsed = t_current - t_start_file

                fid.write('{0}{1} {2}{3}'.format(t_current,self.sep,curr,os.linesep))
                print('{0:1.2f}{1} {2:1.4f}'.format(t_elapsed,self.sep,curr))

                t_next = t_start + (cnt+1)*self.param['sample_dt']
                self.scheduler_event = self.scheduler.enterabs(t_next, 1, lambda:None, ()) 
                self.scheduler.run()
                cnt += 1


    def sigint_handler(self,signum,frame):
        self.scheduler.cancel(self.scheduler_event)
        self.done = True 

if __name__ == '__main__':

    param = { 
            'filename'   : 'logfile.txt',   # Data log filename
            'port'       : '/dev/ttyACM0',  # Potentiostat serial port
            'volt_range' : '1V',            # Output voltage range
            'curr_range' : '1uA',           # Measurement current range
            'sample_dt'  : 5.0,             # Sample period
            'volt'       : 0.5,             # Output voltage
            }

    data_logger = ConstVoltLogger(param)
    data_logger.run()