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:
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 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.