Electronic – AD5933 takes much longer to perform a frequency sweep than expected

analogi2cimpedancemicrocontroller

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.

Best Answer

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.