2
\$\begingroup\$

I've been trying to replicate a 2015 paper that uses electrical impedance tomography to map a cross-section of a hand. They use the AD5933 bio-impedance analyzer.

On page 3 of the paper, they say they could achieve a sample speed of 3ms per sample, but performing a simple, single frequency sweep with the same chip takes me over 90 ms on average.

I'm using modified code from a different project for the AD5933 and Arduino. I've included the parts I think are relevant (although I understand usually the code contained in a post isn't always the offending segment, I'm shortening it to not make the question too long).

//ALL CAPS VARIABLES ARE SET TO THE HEX VALUES SPECIFIED IN THE DATASHEET (AFTER PAGE 23)
//PROGRAMS THE REGISTERS BEFORE A SWEEP. TAKES AROUND 25ms, BUT ONLY NEEDS TO BE PERFORMED ONCE PER FRAME

void programReg() {

  // Set Range 1, PGA gain 1
  writeData(CTRL_REG,0x01);
  // Set settling cycles
  writeData(NUM_SCYCLES_R1, 0x07);
  writeData(NUM_SCYCLES_R2, 0xFF);

  // Start frequency of 1kHz
  writeData(START_FREQ_R1, getFrequency(start_freq,1));
  writeData(START_FREQ_R2, getFrequency(start_freq,2));
  writeData(START_FREQ_R3, getFrequency(start_freq,3));

  // Increment by 1 kHz
  writeData(FREG_INCRE_R1, getFrequency(incre_freq,1));
  writeData(FREG_INCRE_R2, getFrequency(incre_freq,2));
  writeData(FREG_INCRE_R3, getFrequency(incre_freq,3));

  // Points in frequency sweep (100), max 511
  writeData(NUM_INCRE_R1, (incre_num & 0x001F00)>>0x08 );
  writeData(NUM_INCRE_R2, (incre_num & 0x0000FF));
}

//CODE WHERE THE ACTUAL SWEEP IS PERFORMED
void runSweep() {
  Serial.print("0: ");
  Serial.println(micros()-t);
  short re;
  short img;
  double freq;
  double mag;
  double phase;
  double gain;
  double impedance;
  double tot=0;
  double avg;
  double a, b, c;
  int i = 0;
  int j = 0;
  int flag = 0;
  boolean w_f;

  programReg();

  // 1. Standby '10110000' Mask D8-10 of avoid tampering with gains
  writeData(CTRL_REG,(readData(CTRL_REG) & 0x07) | 0xB0);

  // 2. Initialize sweep
  writeData(CTRL_REG,(readData(CTRL_REG) & 0x07) | 0x10);

  // 3. Start sweep
  writeData(CTRL_REG,(readData(CTRL_REG) & 0x07) | 0x20);
  w_f = (readData(STATUS_REG) & 0x07) < 4 ; //Checks if the frequency sweep is not complete
  Serial.print("1: ");
  Serial.println(micros()-t);
  while (w_f) {
    flag = readData(STATUS_REG)& 2; //This is true if there is valid data in the register (DS, pg. 26)
    Serial.print("2: "); 
    Serial.println(micros()-t);
    while (flag != 2) {
      delay(3);
      flag = readData(STATUS_REG)& 2;
      Serial.print("2.1: ");
      Serial.println(micros()-t);
      if (flag == 2) {
        byte R1 = readData(RE_DATA_R1);
        byte R2 = readData(RE_DATA_R2);
        re = (R1 << 8) | R2;

        R1  = readData(IMG_DATA_R1);
        R2  = readData(IMG_DATA_R2);
        img = (R1 << 8) | R2;

        freq = start_freq + i*incre_freq;
        mag = sqrt(pow(double(re),2)+pow(double(img),2));
        Serial.print("3: ");
        Serial.println(micros()-t);

        Serial.print(" Resistance: ");
        Serial.print(re);
        Serial.print(",");

        Serial.print(" Reactance: ");
        Serial.print(img);

        // break;  //TODO: for single run, remove after debugging
      
        //Increment frequency
        w_f = (readData(STATUS_REG) & 0x07) < 4 ;
        if (w_f) {
          writeData(CTRL_REG,(readData(CTRL_REG) & 0x07) | 0x30);
        }
        Serial.print("4: ");
        Serial.println(micros()-t);
      }
    }
    // Power down
    // writeData(CTRL_REG,0xA0);
  }
  writeData(CTRL_REG,(readData(CTRL_REG) & 0x07) | 0xA0);
}

I have timing markers on the runSweep() function that return this (numbers in microseconds from micros()):

1: 25096.00
2: 27900.00
2.1: 33724.00
2.1: 39572.00
2.1: 45412.00
2.1: 51256.00
2.1: 57104.00
2.1: 62944.00
2.1: 68792.00
2.1: 74644.00
2.1: 80488.00
3: 90012.00
 Resistance: -106, Reactance: 301, Impedence: 37267.40
4: 97448.00
2: 100260.00
2.1: 106132.00
2.1: 112020.00
2.1: 117912.00
2.1: 123808.00
2.1: 129712.00
2.1: 135600.00
2.1: 141468.00
2.1: 147340.00
2.1: 153212.00
3: 162772.00
Resistance: -146, Reactance: 418, Impedence: 26873.24
4: 166912.00
Last: 170948.00

My question is: How do I decrease the time it takes for a sweep to be performed?

I've tried: setting the speed of the Wire.h library to 400000Hz (the fastest timing), and skipping the flag check step after a 5 ms delay (led to useless data). On the hardware side, I tried replacing the chip, to no avail. I've run out of ideas, so any help is greatly appreciated.

Edit: In another function which samples the entire frame of electrode pairs, I use the repeat frequency command, but this doesn't cause a noticeable speed boost.

Edit 2: I've added an answer as a community wiki. The problem was in programReg() specifically the Set settling cycles portion.

\$\endgroup\$
10
  • \$\begingroup\$ #Jonah, ah, let me see. The datasheet specifies typical sample rate of 1 Msps, or sample time 1 us. Your result of 90 ms comparing to the 2105 paper's 3 ms of course is unreasonably slow. I would suggest the following troubleshooting methods: (1) set frequency sweep from 100 to 50 points, and see if sample time decreases from your 90 ms to something better. .(2) Your debugging "Serial.print()" statements of course slow things down quite a bit. Perhaps you can uncomment all the print statements and timestamp before and after testing to calculate average sample time lapse. \$\endgroup\$ – tlfong01 Dec 3 '20 at 7:36
  • 1
    \$\begingroup\$ "I have timing markers on the runSweep() function" What do these magic numbers mean? What units and how did you measure them? Obviously, if you run this code on a sh**ty AVR, the program itself will be a massive bottleneck, since the AVR is not a PC and shouldn't be programmed like one. \$\endgroup\$ – Lundin Dec 3 '20 at 9:29
  • \$\begingroup\$ @Lundin, I agree 8-bit Arduino is slow and small. I am using 64-bit Rpi4B. I would recommend STM32 Cortex M3/M4. \$\endgroup\$ – tlfong01 Dec 3 '20 at 14:43
  • \$\begingroup\$ @tlfong01 Thanks for the response! I actually only sweep over 2 points in the function, and I have tested it without the Serial.print() functions (both with just one print at the end, at erasing all the prints and timing it with a hardware pin, with still no speed up. \$\endgroup\$ – Jonah Dec 3 '20 at 16:14
  • \$\begingroup\$ @Lundin these numbers are in microseconds, using the Arduino's inbuilt micros() function. I considered purchasing a Raspberry Pi, but the paper uses an Arduino Pro Mini, which has similar specs to the Arduino Nano I'm using, so although it would probably speed things up, I don't know if that's the issue \$\endgroup\$ – Jonah Dec 3 '20 at 16:16
5
\$\begingroup\$

As I understand the paper, they don't actually use a sweep. They somehow determined that the results at 40kHz were sufficient, so they only read that one frequency.

From page 2:

We used an excitation signal of 40KHz, which we found revealed the most distinguishable features of gestures during piloting.

You set your start (40kHz,) stop (any reasonable value,) and increment (any reasonable value,) and then you just keep reading the real and imaginary values without ever issuing an "Increment frequency" at all.

The AD5933 is fine with this. This method is suggested in the datasheet as a way to average multiple measurements at one frequency. From page 14 of the datasheet:

The measured result is stored in the two register groups that follow: 0x94, 0x95 (real data) and 0x96, 0x97 (imaginary data) that should be read before issuing an increment frequency command to the control register to move to the next sweep point. There is the facility to repeat the current frequency point measurement by issuing a repeat frequency command to the control register.

The paper mentions collecting a full set of measurements (28 pairs,) 10 times per second. At 3 milliseconds per measurement, that works out to 840 milliseconds for the measurements. That leaves 160 milliseconds to do the switching for the pairs and to send the results to the PC. I think that sounds pretty reasonable.


Keep in mind that your "Serial.print" takes some time. Every "print" adds to your measured time.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thanks for your detailed response. I'm sorry but I realize I didn't add all the details to my main question - I do utilize the repeat frequency command in another function (I have two sweep functions, one that samples a single electrode pair at a single frequency, and one that samples at a single frequency for every electrode pair). Using the repeat frequency takes around 5 seconds to complete 64 electrode pairs (or around 80ms per pair), so there seems to be no difference in speed. \$\endgroup\$ – Jonah Dec 3 '20 at 16:26
3
\$\begingroup\$

After reading the datasheet again, I realized the problem was that this chip has a programmable "setting time", which is the number of frequency cycles the chip will wait before proceeding with data sampling. When I inherited the code, I left the sampling time at the max number (511*4 = 2044 cycles), which caused the delay in the 2.1 section. Now, the code is still slow (due mainly to the inferior read/write functions, I assume), but is a little less than 4 times faster now.

TL;DR: The AD5933 has a programmable setting time, so make sure to set to a low number for faster sampling rate.

\$\endgroup\$
3
\$\begingroup\$

This is 'obvious' but still may be useful.
You have some mix of 1 or more of :

    1. The IC is taking longer to do for you what it does for them.
    1. You are trying to get the IC to do things that they are not.
    1. Your program has unneeded delays in it.
    1. Your hardware is less capable than theirs (seems not)
    1. Other :-).

Each of these apart from 'other' can be addressed reasonably orthogonally.
If all else fails then Sherlock Holmes says it's "other".
Break it down into as isolated parts as you can.
You know that, but ... .

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thanks for your reply. The problem for this particular issue was #3 (see my most recent edit), but your method will definitely be useful for all the other projects I do :) \$\endgroup\$ – Jonah Dec 3 '20 at 23:37
1
\$\begingroup\$

Just a few suggestions to speed things up:

  • Serial.print is slow on an Arduino, even when high baudrates are set; better write all output text to a char buffer and don't print anything until after the sweep;

  • Your Arduino doesn't have a FPU for floating point calculations, and it is only 8-bit, so the calculations you are doing here: sqrt(pow(double(re),2)+pow(double(img),2)) will be very slow. You are doing this calculation inside the sweep, but you are not using its result there. Can it be (re)moved, or can the calculation be done with int64s?

  • Avoid using doubles when calculations could be done with big ints.

  • Check your I2C connection; wires not too long, correct pull-up resistors, etc. It helps if you have an oscilloscope to look at the signals.

For raw performance, with FPU, you are probably better served by a Teensy 4 or similar. Even a SAMD21G-based (32-bit) Arduino would be faster (no FPU though).

Not directly relevant for performance, but note that micros() only has a resolution of 4μs on the Arduino you use.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.