Reading 4 pressure sensors on TCA9548A mux I2C

Hello,

I am having an issue with figuring out how to read four wheatstone bridge pressure sensors as quickly and accurately as possible over one I2C bus to an arduino mega. Essentially, four wheatstone bridge pressure sensors interface with an NAU7802 ADC and communicate with a TC9548A mux over four seperate channels, and the mux communicates directly with arduino mega over one I2C bus. There is only one mux that utilises one I2C line on the Mega (connects to one SDA/SCL line) as I originally planned to use an uno. The goal is to read pressure data from 1 and 2, 2 and 3 as quickly as possible to estimate differential pressure between sensor1 & 2, sensor 3& 4. As the system replicates blood flow by pulsing a small pump. So far I have delayed the mux by 5ms (delay(5)) and a simple moving average (SMA) has been applied to reduce noise but I know this is not accurate and is only delaying the whole system, how could I use timer interrupts instead or is there a better method?

#include <math.h>
#include <Adafruit_NAU7802.h>
#include <Wire.h>

// PWM motor control variables
const int pwmPin = 6; // Main pump PWM pin
const int secondaryPumpPin = 5; // Secondary pump PWM pin
float frequency = 1.5; // Frequency can be adjusted dynamically if needed
unsigned long previousMillis = 0;
const long interval = 10;
float phase = 0.0;

// NAU7802 setup
Adafruit_NAU7802 nau2; // NAU7802 for channel 2
Adafruit_NAU7802 nau3; // NAU7802 for channel 3
Adafruit_NAU7802 nau4; // NAU7802 for channel 4
Adafruit_NAU7802 nau5; // NAU7802 for channel 5
#define TCAADDR 0x70
#define NUM_READINGS 10
int readings2[NUM_READINGS];
int readings3[NUM_READINGS];
int readings4[NUM_READINGS];
int readings5[NUM_READINGS];
int readIndex = 0;
long total2 = 0;
long total3 = 0;
long total4 = 0;
long total5 = 0;
int average2 = 0;
int average3 = 0;
int average4 = 0;
int average5 = 0;
float conversion2; // Converted pressure value for channel 2
float conversion3; // Converted pressure value for channel 3
float conversion4; // Converted pressure value for channel 4
float conversion5; // Converted pressure value for channel 5

void tcaSelect(uint8_t i) {
  if (i > 7) return;
  Wire.beginTransmission(TCAADDR);
  Wire.write(1 << i);
  Wire.endTransmission();
  delay(5);
}

void setup() {
  Serial.begin(115200);
  pinMode(pwmPin, OUTPUT);
  pinMode(secondaryPumpPin, OUTPUT);

  // Initialize NAU7802 for channel 2
  tcaSelect(2);
  if (!nau2.begin()) {
    Serial.println("Failed to find NAU7802 on channel 2");
  } else {
    Serial.println("Found NAU7802 on channel 2");
    nau2.setLDO(NAU7802_4V5);
    nau2.setGain(NAU7802_GAIN_1);
    nau2.setRate(NAU7802_RATE_80SPS);
    if (nau2.calibrate(NAU7802_CALMOD_INTERNAL) && nau2.calibrate(NAU7802_CALMOD_OFFSET)) {
      Serial.println("NAU7802 calibration successful for channel 2");
    } else {
      Serial.println("NAU7802 calibration failed for channel 2");
    }
  }

  // Initialize NAU7802 for channel 3
  tcaSelect(3);
  if (!nau3.begin()) {
    Serial.println("Failed to find NAU7802 on channel 3");
  } else {
    Serial.println("Found NAU7802 on channel 3");
    nau3.setLDO(NAU7802_4V5);
    nau3.setGain(NAU7802_GAIN_1);
    nau3.setRate(NAU7802_RATE_80SPS);
    if (nau3.calibrate(NAU7802_CALMOD_INTERNAL) && nau3.calibrate(NAU7802_CALMOD_OFFSET)) {
      Serial.println("NAU7802 calibration successful for channel 3");
    } else {
      Serial.println("NAU7802 calibration failed for channel 3");
    }
  }

  // Initialize NAU7802 for channel 4
  tcaSelect(4);
  if (!nau4.begin()) {
    Serial.println("Failed to find NAU7802 on channel 4");
  } else {
    Serial.println("Found NAU7802 on channel 4");
    nau4.setLDO(NAU7802_4V5);
    nau4.setGain(NAU7802_GAIN_1);
    nau4.setRate(NAU7802_RATE_80SPS);
    if (nau4.calibrate(NAU7802_CALMOD_INTERNAL) && nau4.calibrate(NAU7802_CALMOD_OFFSET)) {
      Serial.println("NAU7802 calibration successful for channel 4");
    } else {
      Serial.println("NAU7802 calibration failed for channel 4");
    }
  }

  // Initialize NAU7802 for channel 5
  tcaSelect(5);
  if (!nau5.begin()) {
    Serial.println("Failed to find NAU7802 on channel 5");
  } else {
    Serial.println("Found NAU7802 on channel 5");
    nau5.setLDO(NAU7802_4V5);
    nau5.setGain(NAU7802_GAIN_1);
    nau5.setRate(NAU7802_RATE_80SPS);
    if (nau5.calibrate(NAU7802_CALMOD_INTERNAL) && nau5.calibrate(NAU7802_CALMOD_OFFSET)) {
      Serial.println("NAU7802 calibration successful for channel 5");
    } else {
      Serial.println("NAU7802 calibration failed for channel 5");
    }
  }

  // Initialize pressure readings
  for (int i = 0; i < NUM_READINGS; i++) {
    readings2[i] = 0;
    readings3[i] = 0;
    readings4[i] = 0;
    readings5[i] = 0;
  }
}

void loop() {
  unsigned long currentMillis = millis();

  // Motor control for main pump
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    float sinValue = sin(2 * PI * frequency * phase);
    int pwmValue = (int)((sinValue + 1) * 127.5);
    analogWrite(pwmPin, pwmValue);
    phase += frequency * (interval / 1000.0);
    if (phase >= (1.0 / frequency)) {
      phase = 0;
    }
  }

  // Run secondary pump at a constant PWM value
  analogWrite(secondaryPumpPin, 100);

  // Read pressure data for channel 2
  tcaSelect(2);
  if (nau2.available()) {
    total2 -= readings2[readIndex];
    readings2[readIndex] = nau2.read();
    total2 += readings2[readIndex];
    readIndex = (readIndex + 1) % NUM_READINGS;
    if (readIndex >= NUM_READINGS) {
      readIndex = 0;
    }
    average2 = total2 / NUM_READINGS;
    conversion2 = average2 * 1; // Apply conversion factor and offset for channel 2,cal factor to be added
  }

  // Read pressure data for channel 3
  tcaSelect(3);
  if (nau3.available()) {
    total3 -= readings3[readIndex];
    readings3[readIndex] = nau3.read();
    total3 += readings3[readIndex];
    readIndex = (readIndex + 1) % NUM_READINGS;
    if (readIndex >= NUM_READINGS) {
      readIndex = 0;
    }
    average3 = total3 / NUM_READINGS;
    conversion3 = average3 * 1; // Apply conversion factor and offset for channel 3
  }

  // Read pressure data for channel 4
  tcaSelect(4);
  if (nau4.available()) {
    total4 -= readings4[readIndex];
    readings4[readIndex] = nau4.read();
    total4 += readings4[readIndex];
    readIndex = (readIndex + 1) % NUM_READINGS;
    if (readIndex >= NUM_READINGS) {
      readIndex = 0;
    }
    average4 = total4 / NUM_READINGS;
    conversion4 = average4 * 1; // Apply conversion factor and offset for channel 4
  }

  // Read pressure data for channel 5
  tcaSelect(5);
  if (nau5.available()) {
    total5 -= readings5[readIndex];
    readings5[readIndex] = nau5.read();
    total5 += readings5[readIndex];
    readIndex = (readIndex + 1) % NUM_READINGS;
    if (readIndex >= NUM_READINGS) {
      readIndex = 0;
    }
    average5 = total5 / NUM_READINGS;
    conversion5 = average5 * 1; // Apply conversion factor and offset for channel 5
  }

  // Calculate differential pressure and print
  float differentialPressure23 = conversion2 - conversion3;
  float differentialPressure45 = conversion4 - conversion5;
  Serial.print(differentialPressure23); 
  Serial.print(",");
  Serial.println(differentialPressure45);
} 

You could add Wire.setClock(400000); to setup(), that's an easy speed-up. Both the TCA9548 and NAU7802 support that speed.

Mega has only one i2c bus!

5 what?

An SMA connector?

5ms and SMA is simple moving average to filter data, have updated in post, cheers

That's not what your code appears to be doing. It appears to be taking the average of a number of readings from each sensor, then taking the differential between the averages. Is this what you want to change?

Why would you want to use timer interrupts?

I recommend you make more use of arrays. It won't make your code any slower, but it will make it 4 times shorter!

Why have you numbered them starting at 2?

Hi paul, yes so how it works is there is a pressure transducer before and after a synthetic vessel in a closed flow loop, the transducer value at the end (sensor 3) is subtracted from the sensor at the start (sensor2) to estimate differential pressure at the synthetic vessel. They are numbered 2,3,4,5 as this is what channels each ADC connects to on the mux

I was told these may be more accurate for reading each transducer at a specific interval so the differential pressure is less arbitrary as the timing is more specific

Do you have a oscilloscope (I strongly prefer a USB capture device) to do some measurements.
The I2C bus is slow, it could take more time than the 5ms.
For irregular intervals, the millis() or micros() value is often used in a calculation. A specific interval is not needed in most cases.

Yes, I can see that's what the code does, but you originally said

Which is completely different.

Let's say for example the readings from sensor 2 were 1, 2, 3, 4, 5 and the readings from sensor 3 were 2, 3.5, 4.5, 6, 6.5.

Your code is taking the average of sensor 2 which is (1+2+3+4+5)/5 = 4.0, and the average of sensor 3 which is (2+3.5+4.5+6+6.5)/5 = 4.5. then taking the differential which is 0.5.

According to your goal, it should be taking the differential of the values:
(2-1) = 1
(3.5-2) = 1.5
(4.5-3) = 1.5
(6-4) = 2
(6.5-5) = 1.5
and taking the average of those: (1+1.5+1.5+2+1.5)/5 = 1.5

Two very different answers!

Some further ideas to speed up-the process:

This function takes time because the Arduino has to query if the sensor is ready over the i2c bus, which is quite slow, even at 400KHz.

I noticed that the sensor boards have a "Ready" pin which will change when there is a reading available from the sensor. You could connect the "Ready" pins from the 4 sensors to the Mega's external interrupt pins. The interrupt routines could then set 4 bool variables (better still an array of 4 bool variables) according to which pin received the interrupt. The code above could then be changed to check the relevant bool variable instead.

Calculating with float variables on Mega is slow because it's CPU has no hardware floating point unit, so it has to be done in software. Even with hardware floating point, sin() is slow.

Since the frequency and interval don't seem to change while the code is running, the byte values for analogWrite() could be pre-calculated in setup() and stored in an array for use in loop().