Impedance Spectroscopy and Manual Control Sampling Rate



  • I was hoping to use the Rodeostat to get a measure of electrochemical impedance in a cell at difference signal frequencies. Is this something any of you have tried before? If so, how did you go about it using the libraries?

    Additionally: when using manual control, I have been unable to achieve a sampling rate higher than 40 sps. Whether using the Manual/Direct example here or using my own script, no matter how much the intervale between samples is decreased I only get roughly 40 samples per second of testing. When using the Python library the software seems to have a sampling rate limit of 1000 sps, but even when I set the sampling rate to the maximum I still see 40. Is this a hardware limitation, or a limitation in the Python library?

    In order to run these Impedance Spectroscopy tests, I'd ideally need a sampling frequency of at least 2000 samples per second. Is this achievable with the Rodeostat?



  • @DavidL

    Manual/Direct control mode will not be able to achieve as high sample rate as the firmware based tests which are paced by the microcontroller on the teensy 3.2. So your best bet for higher frequency sampling is to use one of the firmware based tests. Manual/Direct mode is paced via software on the PC and at each step it requires setting the voltage and then requesting the current. Each of these requires sending a command to the teensy followed by a wait for the response. This limits the sample rate quite a bit. In contrast, with the firmware based tests, the teensy just streams the data to the host PC - so we can achieve higher sample rates. The highest sample rate I can acheive with Manual/Direct mode on my PC here is about 60Hz (the results will probably vary with your hardware). Whereas with the firmware based test I can get 1000Hz.

    If you are trying to measure the impedance of the cell - then I'm guessing you might be testing with sinusoids in order to put together something like a Bode plot (https://en.wikipedia.org/wiki/Bode_plot) ? If that is the case then maybe the firmware based 'sinusoid' test will work for you. This test outputs a sine wave and you can set the amplitude, period, phase (shift), offset, etc. I've attached an example below showing how to run the firmware based 'sinusoid' test at 1000Hz sample rate.

    from __future__ import print_function
    from potentiostat import Potentiostat
    import matplotlib.pyplot as plt
    
    port = '/dev/ttyACM0'
    
    dev = Potentiostat(port)
    dev.set_curr_range('100uA')
    dev.set_sample_rate(1000)
    
    name = 'sinusoid'
    param = {
            'quietValue' : 0.0,
            'quietTime'  : 0,
            'amplitude'  : 2.0,
            'offset'     : 0.0,
            'period'     : 100,
            'numCycles'  : 5,
            'shift'      : 0.0,
            }
    
    dev.set_param(name,param)
    t,volt,curr = dev.run_test(name,display='pbar')
    
    plt.figure(1)
    plt.subplot(2,1,1)
    plt.plot(t,volt,'b')
    plt.ylabel('(V)')
    plt.grid('on')
    
    plt.subplot(2,1,2)
    plt.plot(t,curr,'b')
    plt.ylabel('uA)')
    plt.xlabel('(s)')
    plt.grid('on')
    plt.show()
    

    The above test outputs a sine wave with 100ms period, 2V amplitude (goes from -2V to 2V) and no DC offset. The data are sampled at 1000Hz.

    Note, depending on your system you might start to drop samples above 500Hz - so you might want to examine the time points. This will usually happen with longer tests and high sample rates.

    With respect to achieving even higher sample rates e.g. 2000Hz, the teensy 3.2 is definitely capable of it. However, achieving this would require some modifications to the firmware.



  • Most of the tasty stuff for impedance spectroscopy occurs above 500Hz sin waves. so you're pretty much not gonna have much luck with the sampling limited to 2kHz. Especially determining phase shift. I do wonder if we can do saw tooth or square wave excitation, to get at least an impedance. 1kHz impedance measurement (with out phase determination) is quite useful for battery testing.



  • @Will-Dickson Thank you for your response. The software limitation makes sense. Ideally we're looking to measure the impedance of the cell in response to a 1000Hz sine wave, so I think you're right: our best bet will be to modify the firmware to achieve a higher sampling rate via the Teensy board. Where do you think would be the best place in the firmware to start? I'll take a look now and see what I can do.



  • I have changed the MinimumSamplePeriod value in the ps_constants.cpp file in the firmware. However, I'm running into problems trying to upload the modified firmware to the Teensy.

    I have Arduino and Teensy installed. I put all of the header files and .cpp files from the firmware/libraries/potentiostat folder into the /firmware/potentiostat folder with the potentiostat.ino file. The Arduino IDE compiles this file, and successfully uploads it via Teensyduino. However, when I attempt to run the example CV script from python, I get an error stating that “No JSON Object Could Be Decoded”.

    I went back and made sure to zip the Array and ArduinoJson libraries and include them via the Arduino IDE in the potentiostat.ino file. However, when these libraries are included in that file the Arduino IDE will no longer compile the potentiostat.ino file, returning multiple errors about redefining various structs related to JSON.

    This may seem dumb, but what is the correct procedure on how to upload new firmware for the Rodeostat?



  • @DavidL

    When re-programming the firmware make sure the "tools -> optimize" is set to FAST. Right now if it is set to FASTER, which is the default, there will be an issue with the ArduinoJson library.

    This is something I'm going to fix soon - just need to upgrade to the latest version of the ArduionJson library and the problem goes away. Then the optimize setting won't matter. There is a little bit of an API change between the new and old version of the library - so I want to test a bit first.



  • My apologies for the late update.

    Changing the optimization to FAST from FASTER allowed for the firmware to correctly update.

    After some more testing, changing the MinimumSamplePeriod value allows me to manually set the sampling period/rate to be much faster than before without the command being rejected for being too small. However, when I run the sinusoid test, I still cannot apply a waveform of a frequency higher than 500Hz.

    I will continue looking through the firmware to see where I can change this, as well as do further sampling frequency tests with different firmware tests. Any recommendations on where else in the firmware to increase sampling frequency? I’ve begun looking into the firmware files for the periodic test and the sinusoid test.



  • @DavidL

    I think that to get to higher frequencies you made need to do a bit more than just change the MinimumSamplePeriod. The MinimumSamplePeriod just sets how fast data is streamed back to the PC.

    First, the voltage is set via the DAC in the testTimer callback. This timer runs are a frequency set by TestTimerPeriod (set in ps_constants.cpp). The value for this in the stock firmware is 200us - so 5000Hz. So with a 500Hz sine wave you will only have about 10 points per cycle. So you will probably want to decrease the TestTimerPeriod in order to get more points per sinewave.

    Second, currently the output voltages for the sinewave test are set in a lookup table (see ps_sinusoid_test.h and .cpp) which is interpolated at intermediate time points. This isn't really optimized for speed - it is meant for lower frequencies where frequency of the sinewave is much less than the TestTimerPeriod. In the higher frequency case you may need to do this more efficiently - maybe just have the lookup table store the output values directly - so you don't have to do any computation and can just set the output voltage from the lookup table value.

    Third, at higher frequencies streaming back the values during the test might not be feasible. The way we are doing it now - as JSON - definitely isn't the most efficient method. It is nice and clear, but not really meant for speed. So you might need to modify things so either the data is packaged more efficiently. Or maybe buffer the data and send it back after the test is finished or stream it in chunks consisting of data for multiple samples. Also, note, USB frames are 1ms so when sending back samples one at a time the best you could probably do would be 1kHz - and you might be able to achieve that.



  • This post is deleted!


  • @Will-Dickson
    Another update:
    I’ve followed your first point and changed the testTimerPeriod to be much lower. In addition, I noticed that for periodic tests the period was limited to an integer in units of milliseconds, which would limit the period of the applied wave to 1 ms, for a maximum applied frequency of 1000 Hz. So in the ps_periodic_test.cpp file I removed the call to convertMstoUs() in order to fix this.

    In the further testing I’ve done, I’ve seen strange results. The Start and End time are correct for my plots, and the correct number of points are plotted based on my sampling rate, even if the sampling period is set to be quite low. However, in the returned t vector, there are multiple time points. e.g., we’ll see two 0.0s, two 0.001s, and so on until the end of the test.

    This appears to be a sort of delay of 1ms, where the program attempts to sample but can’t move forward. I believe you are correct: and this has to do with the data streaming over USB.

    For our purposes, we do not need to send data back to the computer until the test is complete. Thus, we would be happy to modify the data transmission to send the time, voltage, and current arrays over USB after the test is over. So essentially, we want to do like you said an buffer the data, sending it after the test is over. What would we need to change to do this?

    Where is the current data streaming controlled? Is this within the Python library, JSON, or the Arduino code? What specific files or scripts would I need to modify?

    EDIT: If we do need to modify the JSON library, how would we incorporate these changes to the library? I’m not all that familiar with JSON to be honest.



  • Update:

    We ended up encapsulating the everything in the SystemState::serviceDataBuffer() in the ps_system_state.cpp file after the comment "//Empty data buffer" in an if statement. The data buffer is only emptied once the test is finished running.

    After making this change, the t vectors returned by the test were still delayed, each point was a minimum of 1ms apart, and thus we had repeated points. However, after creating our own t vectore with the know t step, the plots came out quite nicely, and we were able to run waveforms up to 10kHz.

    Due to the limitation on data buffer size, slower waveforms must be analyzed with a sample rate such that the number of points doesn’t exceed the data buffer. But this is not big deal, as for our purposes we only need the high sampling rate for the fast waveforms.. I may do some further modifications to change the buffer size to allow for high sampling rates on slow waves, but for now the above modifications have sufficed to allow us to run waveforms of up to 10kHz.

    Next we’ll be working on calculating impedance and phase from these output data arrays.



  • FINAL UPDATE:

    After buffering the data, the limitation in sampling frequency appears to come from the testTimer function. This function has a specific runtime, and runs on a timer interrupt. If the timer interrupt's period (testTimerPeriod in ps_constants.cpp) is smaller than the actual time it takes for the function to run, the function does not run correctly, yielding incorrect and strange results.

    For those looking to increase their sampling frequency, this seems to be the next bottleneck after the data streaming. Perhaps some alteration/optimization of this function could increase the sampling rate slightly, perhaps not.

    Ultimately, to increase the sampling frequency of the Rodeostat, you can:

    Decrease MinimumSamplingPeriod within ps_constants.cpp in order to remove the software limitation on sampling frequency
    
    Remove calls to convertMstoUs in ps_periodic test and in ps_system_state.cpp, allowing for periods to be passed directly in units of us, rather than being limited to a sampling period of an integer in ms
    
    In serviceDataBuffer (in ps_system_state.cpp) , modify the function so that it does not transmit data until test done flag has been set (by putting an if statement around most of the function).
    

    With this we were able to achieve higher than the stock sampling frequency for a small number of periods (this was limited by the memory, since the data buffer would get filled with too many points since we did not clear it until the end). Unfortunately, high frequencies like 10kHz or 100kHz did not seem to be reachable.

    In addition, thank you @Will-Dickson for your help figuring this out.


Log in to reply