Navigation

    IO Rodeo Forum

    • Register
    • Login

    AC Voltammetry and sample rate

    Rodeostat
    2
    3
    1447
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • B
      BlairH last edited by

      I've been attempting to use the rodeostat to perform AC voltammetry as per the example and using the 50k dummy cell which gives a nice output at very low sample rates however at higher sample rates (though still quite low) of 115 Hz I've found the output is heavily affected by the sample rate.
      I also implement the firmware changes as mentioned in this post however noticed no effect.

      Any advice on how to overcome this would be greatly appreciated

      2 Hz
      2 Hz

      115 Hz
      115 Hz

      I've used the following parameters

      Run parameters

          t0 = 0.0  # Transition start time (s)
          t1 = 8.0  # Transition stop time (s)
      
          v0 = -0.1  # Initial voltage (V)
          v1 =  -0.9  # Final voltage (V)
      
          amp = 0.1  # Sinusoid ampliude (V)
          per = 0.5  # Sinusoid period (s)
      
          dt = 0.005  # Time step for setting voltage and measurements
          t_total = t0 + t1 # 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('COM5')
          pstat.set_volt_range('2V')
          pstat.set_curr_range('100uA')
          t, volt, curr = run_manual_test(pstat, volt_func, dt, t_total)
      
      W 1 Reply Last reply Reply Quote 0
      • W
        Will Dickson @BlairH last edited by

        @BlairH

        I don't think the method in post is going to help in the case of example as it is run in manual/direct mode rather than in firmware. In manual/direct mode the every time the output voltage is changed and the current is sampled, as the test is run, requires USB/serial communication with the host PC. In this manner the schedule for the waveform output is running PC side and the voltage is set using set_volt and the current is sample using the get_curr methods. So each sample requires two USB/Serial command/response pairs - one to set_volt and one to get_curr.. Each of these commands take some time to complete. Because of this tests run in manual /direct mode cannot achieve as high a sample rate as those implemented in firmware and run using the 'run_test' method. Unfortunately, at this time, there isn't an AC voltammetry test implement in the firmware. That said even if it was implemented in firmware the maximum sample rate would be about 1000Hz with the stock firmware - and you might be able to achieve up to around 10kHz for very short bursts with the firmware modifications in post.

        Regarding the manual/direct mode AC voltammetry test you are running I'm not quite sure what is happening. I'm not able to replicate this myself - I'm guessing it might be something specific to your system. That said this manual/direct mode test can't achieve very high sample rates as the 'set_volt' and 'get_curr' method calls take some time to complete. So as you lower dt more and more eventually the actually time taken per sample will plateau as it is dominated by the time required for the 'set_volt' and 'get_curr' method calls.

        One thing that is useful to look to help diagnose the timing of manual/direct mode test is the time difference between samples. You can get this by converting the list of samples returned to an array an then looking at the consecutive differences e.g.

        t = scipy.array(t)
        dt = scipy.diff(t)
        plt.plot(t[1:], dt)
        

        This is what I get for dt = 0.05 and dt = 0.005
        dt=0.05
        and
        dt=0.005

        Note, that in when dt parameter is set to 0.005 the measured "actual' dt between time steps much more erratic and has a median value of only 0.0143. this is due to the fact that the sampling time is being dominated by the time required for the 'set_volt' and 'get_curr' calls.

        I've also attached the modified AC voltammetry script I used for generating these plots.

        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 = 0.0  # Transition start time (s)
            t1 = 8.0  # Transition stop time (s)
        
            v0 = -0.1  # Initial voltage (V)
            v1 =  -0.9  # Final voltage (V)
        
            amp = 0.1  # Sinusoid ampliude (V)
            per = 0.5  # Sinusoid period (s)
        
            dt = 0.05  # Time step for setting voltage and measurements
            t_total = t0 + t1 # 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)
        
            t_array = scipy.array(t)
            dt_array = scipy.diff(t)
            dt_median = scipy.median(dt_array)
            print('median dt: {}'.format(dt_median))
        
            # Plot results
            plt.figure()
            plt.subplot(311)
            plt.plot(t,volt)
            plt.ylabel('potential (V)')
            plt.title('param dt = {:0.3},  median actual dt = {:0.4}'.format(dt,dt_median))
            plt.grid(True)
        
            plt.subplot(312)
            plt.plot(t,curr)
            plt.xlabel('time (s)')
            plt.ylabel('current (uA)')
            plt.grid(True)
        
            plt.subplot(313)
            plt.plot(t,curr)
            plt.plot(t_array[1:], dt_array)
            plt.grid(True)
            plt.xlabel('time (s)')
            plt.ylabel(r'$\Delta t$ (s)')
            plt.ylim(0.0, dt_array.max()*(1 + 0.1))
            plt.show()
        
        
        1 Reply Last reply Reply Quote 0
        • B
          BlairH last edited by

          Hi Will,

          Thanks for your feeback. You're right about my system specific differences, even for param dt =0.05 I get a median of 0.058.

          I've attempted to implent the firmware changes outlined as I only need a sample rate of 115Hz however I haven't noticed any difference. I'm not very familar with arduino programming and I suspect I haven't implmented the changes in ps_system_state.cpp correctly. I've attached my changes in hopes that you might notice some sort of obvious error.

          Regards

          Blair

          void SystemState::serviceDataBuffer()
              {
                  // Check for last sample flag to see if done
                  bool run_complete = false;
                  if (lastSampleFlag_)
                  {
                      if ((run_complete = true));
                  }
          
                 		 // Empty data buffer
                  	size_t buffer_size;
                  	ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
                  	{
                      	buffer_size = dataBuffer_.size();
                  	}
          
                  	while (buffer_size > 0)
                  	{
                      	Sample sample;
                      	ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
                      	{
                        	  	sample = dataBuffer_.front();
                          	dataBuffer_.pop_front();
                          	buffer_size = dataBuffer_.size();
                      	}
                      		messageSender_.sendSample(sample);
                  	}
          
                  	// Send indication that the run is complete 
                  	if (run_complete)
                  	{
                      	messageSender_.sendSampleEnd();
                      	lastSampleFlag_ = false;
                  	}
              	}
          
          1 Reply Last reply Reply Quote 0
          • First post
            Last post