Low ADC readings on sensors compared to multimeter

I'm working with an Arduino Giga, and I am working on a system to measure flow and pressure from a peristaltic pump. When I get the Arduino to measure the signal from the sensors, it often reads low, and I don't know why. This post is a bit long, but I wanted to make sure that I provide enough information. I am getting similar low readings from both the flow and pressure sensor, but I have focused more on the flow sensor in trying to troubleshoot, and it's also a shorter post if I talk about just one of the sensors. This is my first major Arduino project and first electronics project, and it's pretty ambitious, so forgive me if things might be a bit off with some of my setup.

In my system, I have a peristaltic pump that outputs to a bubble trap made from a 100 ml syringe with ~100 ml of air and then about 1.5 ft/45 cm of compliant silicone tubing to help reduce the force of the pulsations from the peristaltic pump. It doesn't completely remove the pulsations, so I implemented an exponential moving average (EMA) and a Kalman filter to try to clean up the signal to get an average that would still be reasonably responsive. After the silicone tubing, I have my flow sensor then my pressure sensor. I am using an Renesas FS1203-DL flow sensor (I am using the voltage rather than the I2C for simplicity) and a generic industrial pressure sensor.

The setup that I am currently testing is part of a larger system, and I am trying to calibrate the sensors. The pressure and flow sensors are both 5V, and I have a 10µF electrolytic and a 0.1µF ceramic capacitor bridging the power leads going to the sensor (and also a 1000µF capacitor coming out of the 5V buck converter that is feeding all of the 5V components of the larger system). For the flow sensor, I use a combination of a 6.2kΩ resistor and a 12kΩ resistor to create a voltage divider to drop the signal to be within the 3.3V of the Arduino Giga (I don't drop the pressure signal with a voltage divider as it's a 5 psi sensor and my system doesn't seem to be able to generate more than about 3 psi). To help clean up the signal, I also added an RC low-pass filter with a 1kΩ resistor from and a 0.1µF ceramic capacitor to ground. I'm using 12-bit resolution (0-4095).

Before I had implemented the RC low-pass filter, this is the output that I was getting if I logged the data every 10 ms (though this really slowed down the pump due to consuming so much processing time, so I can't accurately say what the time intervals actually are, and the pump would occasionally stop and start that would cause spikes in the flow and pressure). The dark blue dots are the raw analogue data, the orange is the raw signal fed through a Kalman filter algorithm, and the green is an EMA (alpha = 0.02) that is also fed through a Kalman filter. The idea is that I can use the EMA to adjust the speed of the pump to target a programmed flow and/or pressure rate.

I am trying to calibrate the sensors to the flow that I am seeing so that I can create an equation to match the readings to a flow output. I was in the process of starting this and was getting flow sensor readings that were a bit high (using a multimeter, I was getting signals of about 0.5V but ADC of 600-700). I implemented the RC low-pass filter and I was getting about 0.45V on a multimeter and ADC readings of ~500-550, which was expected (though it was still a bit high for the expected 0.33V for zero flow from the sensor, which should be about 400 in ADC). I was able to get fairly consistent values, then suddenly sensor readings went back up to 600-700. I played around, I switched analogue pins, and I replaced a trimpot with a voltage divider (I tested the resistors, I and I think they were 6.12kΩ and 11.90kΩ, which would give me 3.302V from 5V, if memory serves) and I started getting signals of ~550 again. Then all of a sudden I started getting readings of 250-400, even though the multimeter was still showing 0.45V. Now, sometimes I will get readings of ~500-550, but more often I am getting 250-400, and I haven't been able to find a pattern.

I tried a few different things. I though that maybe the sensor needed time to warm up, but letting the system run for several minutes seemed to make no difference. I connected the sensor signal wire to either ground or 5V (through the voltage divider and RC low-pass filter), let the system run for 5 min to make sure that I was getting a stable EMA and Kalman filtered signal (though the raw average and range were reset every second), and these were the results:

Connected to sensor (not flow):
•EMA: 297.66
•filtered EMA: 296.99
•filtered raw data: 264.90
•average: 309.42
•range: 0 - 639

Connected to ground:
•EMA = 1.89
•filtered EMA = 1.74
•filtered raw data = 1.74
•average = 1.00
•range = 0–202

Connected to 5V:
•EMA = 3984.29
•filtered EMA = 3986.80
•filtered raw data = 3968.63
•average = 4005.91
•range = 3712–4095

I am more or less getting the readings that I expect from both ground and 5V, but I am getting low readings from the sensors. Does anyone has any idea why I am getting low readings from my sensors?

Here is the code that I am using (though I've left out the part of initializing the USB and data logging code). To explain the code:
-Get an initial sensor reading at the startup and show it on the serial monitor (it reads quite low at this point)
-Cycle the stepper motor and pump for 2000 steps to flush any water that is in the sensor
-Let the system settle for 10 seconds to allow the flow to fully stop
-Establish a baseline EMA and show it on the serial monitor
-Continue to collect and process signal data from the flow and pressure sensors every 10 ms, log the data every second, and display the information every 10 seconds on the serial monitor

#include <AccelStepper.h>

// USB mass storage device libraries
#include <Arduino_USBHostMbed5.h> // v0.3.1
#include <DigitalOut.h>
#include <FATFileSystem.h>

#include <stdio.h>

#include "kalman.h"

// Define stepper interface type and pins
int motorInterfaceType = 1;
int stepPin = 43;
int dirPin = 41;

AccelStepper pumpMotor(motorInterfaceType, stepPin, dirPin);

USBHostMSD msd;
mbed::FATFileSystem usb("usb");
int err;

// Initialize filters with guessed tuning values (tune as needed)
KalmanFilter kalmanFlowZero(0.01, 0.3, 1.0, 0.0);     // For flowRateEMAzero
KalmanFilter kalmanPressureZero(0.01, 0.3, 1.0, 0.0); // For pressureEMAzero
KalmanFilter kalmanFlow(0.01, 0.3, 1.0, 0.0);         // For flowEMA
KalmanFilter kalmanPressure(0.01, 0.3, 1.0, 0.0);     // For pressureEMA
KalmanFilter kalmanMeanFlow(0.01, 0.3, 1.0, 0.0);     // For meanFlowReadings
KalmanFilter kalmanMeanPressure(0.01, 0.3, 1.0, 0.0); // For meanPressureReadings

float alpha = 0.02;

char filename[] = {"/usb/log-15.csv"};
unsigned long readingsTaken = 0;
float testSpeed = 0.0;

const float maxTestSpeed = 1700.0; // Practical upper limit
const unsigned long testDuration = 1800000; // 30 minutes

float flowRateEMAzero = 0.0;
float pressureEMAzero = 0.0;
float filteredFlowRateEMAzero = 0.0;
float filteredPressureEMAzero = 0.0;
float flowEMA = 0.0;
float pressureEMA = 0.0;
float filteredFlowEMA = 0.0;
float filteredPressureEMA = 0.0;

int testStart = 0;
int serialTime = 0;

int analogFlow = 0;
int analogPressure = 0;
float sensorSumFlowReadings = 0.0;
float sensorSumPressureReadings = 0.0;
int minAnalogFlow = 4095;
int maxAnalogFlow = 0;
int minAnalogPressure = 4095;
int maxAnalogPressure = 0;
float meanFlowReadings = 0.0;
float meanPressureReadings = 0.0;
float filteredPressure = 0.0;
float filteredFlow = 0.0;

//Setting pin for flow sensor data
const int flowSensorPin = A2;

// Setting pins for pressure sensor data
const int pressureSensorPin = A1;

// Setting pin for speaker
const int speaker = 8;


void setup() {
  Serial.begin(115200);
  while (!Serial);
  
  //Enable the USB-A port
  pinMode(PA_15, OUTPUT);
  digitalWrite(PA_15, HIGH);
  
  // Create speaker for alarms
  pinMode(speaker, OUTPUT);
  digitalWrite(speaker, LOW);

  analogReadResolution(12);

  initializeUSB();

  tone(speaker, 750, 250);

  testStart = millis();
  unsigned long currentTime = millis();
  while (millis() - currentTime < 1000) {

    readingsTaken++;

    analogFlow = analogRead(flowSensorPin);
    flowRateEMAzero = alpha * (float)analogFlow + (1.0 - alpha) * flowRateEMAzero;
    filteredFlowRateEMAzero = alpha * (float)analogFlow + (1.0 - alpha) * filteredFlowRateEMAzero;
    filteredFlowRateEMAzero = kalmanFlowZero.update(filteredFlowRateEMAzero);
    filteredFlow = kalmanMeanFlow.update(analogFlow);
    minAnalogFlow = min(minAnalogFlow, analogFlow);
    maxAnalogFlow = max(maxAnalogFlow, analogFlow);
    sensorSumFlowReadings += analogFlow;
    
    analogPressure = analogRead(pressureSensorPin);
    pressureEMAzero = alpha * (float)analogPressure + (1.0 - alpha) * pressureEMAzero;
    filteredPressureEMAzero = alpha * (float)analogPressure + (1.0 - alpha) * filteredPressureEMAzero;
    filteredPressureEMAzero = kalmanPressureZero.update(filteredPressureEMAzero);
    filteredPressure = kalmanMeanPressure.update(analogPressure);
    minAnalogPressure = min(minAnalogPressure, analogPressure);
    maxAnalogPressure = max(maxAnalogPressure, analogPressure);
    sensorSumPressureReadings += analogPressure;

    delay(10); // Small delay for stable readings

  }

  write_log_entry();

  Serial.print("\n*****Calibration run @ ");
  Serial.println(millis() / 1000);
  Serial.println(" in 12-bit resolution*****");
  Serial.print("Initial ananlogue flow readings: ");
  Serial.print("\t• flowRateEMAzero: ");
  Serial.println(flowRateEMAzero);
  Serial.print("\t• filteredFlowRateEMAzero: ");
  Serial.println(filteredFlowRateEMAzero);
  Serial.print("\t• filteredFlow: ");
  Serial.println(filteredFlow);
  Serial.print("\t• Average flow: ");
  Serial.println(sensorSumFlowReadings / readingsTaken);
  Serial.print("\t• Analogue flow ADC range: ");
  Serial.print(minAnalogFlow);
  Serial.print(" - ");
  Serial.println(maxAnalogFlow);
  Serial.println();

  Serial.println("Initial ananlogue pressure readings: ");
  Serial.print("\t• pressureEMAzero: ");
  Serial.println(pressureEMAzero);
  Serial.print("\t• filteredPressureEMAzero: ");
  Serial.println(filteredPressureEMAzero);
  Serial.print("\t• filteredPressure: ");
  Serial.println(filteredPressure);
  Serial.print("\t• Average pressure: ");
  Serial.println(sensorSumPressureReadings / readingsTaken);
  Serial.print("\t• Analogue pressure ADC range: ");
  Serial.print(minAnalogPressure);
  Serial.print(" - ");
  Serial.println(maxAnalogPressure);
  Serial.println();

  pumpMotor.setMinPulseWidth(2);
  pumpMotor.setAcceleration(100);  // 
  pumpMotor.setMaxSpeed(maxTestSpeed);
  
  pumpMotor.setCurrentPosition(0);
  pumpMotor.moveTo(2000);
  while (pumpMotor.distanceToGo() != 0) {  
    pumpMotor.run();  // Non-blocking movement
  }

  minAnalogFlow = 4095;
  maxAnalogFlow = 0;
  minAnalogPressure = 4095;
  maxAnalogPressure = 0;
  readingsTaken = 0;
  sensorSumFlowReadings = 0;
  sensorSumPressureReadings = 0;

  currentTime = millis();
  serialTime = millis();
  Serial.print("\nLetting flow readings settle");
  while(millis() - currentTime <= 15000){
    if(millis() - serialTime >= 500){
      Serial.print(".");
      serialTime = millis();
    }
  }

  Serial.print("\nSetting EMA zero");

  currentTime = millis();
  serialTime = millis();
  while (millis() - currentTime < 10000) {

    readingsTaken++;

    analogFlow = analogRead(flowSensorPin);
    flowRateEMAzero = alpha * (float)analogFlow + (1.0 - alpha) * flowRateEMAzero;
    filteredFlowRateEMAzero = alpha * (float)analogFlow + (1.0 - alpha) * filteredFlowRateEMAzero;
    filteredFlowRateEMAzero = kalmanFlowZero.update(filteredFlowRateEMAzero);
    filteredFlow = kalmanMeanFlow.update(analogFlow);
    minAnalogFlow = min(minAnalogFlow, analogFlow);
    maxAnalogFlow = max(maxAnalogFlow, analogFlow);
    sensorSumFlowReadings += analogFlow;
    
    analogPressure = analogRead(pressureSensorPin);
    pressureEMAzero = alpha * (float)analogPressure + (1.0 - alpha) * pressureEMAzero;
    filteredPressureEMAzero = alpha * (float)analogPressure + (1.0 - alpha) * filteredPressureEMAzero;
    filteredPressureEMAzero = kalmanPressureZero.update(filteredPressureEMAzero);
    filteredPressure = kalmanMeanPressure.update(analogPressure);
    minAnalogPressure = min(minAnalogPressure, analogPressure);
    maxAnalogPressure = max(maxAnalogPressure, analogPressure);
    sensorSumPressureReadings += analogPressure;

    delay(10); // Small delay for stable readings

    if(millis() - serialTime >= 500){
      Serial.print(".");
      serialTime = millis();
    }
  }

  write_log_entry();

  Serial.print("\n*****Calibration run @ ");
  Serial.print(millis() / 1000);
  Serial.println(" in 12-bit resolution*****");
  Serial.print("Start ananlogue flow readings: ");
  Serial.print("\t• flowRateEMAzero: ");
  Serial.println(flowRateEMAzero);
  Serial.print("\t• filteredFlowRateEMAzero: ");
  Serial.println(filteredFlowRateEMAzero);
  Serial.print("\t• filteredFlow: ");
  Serial.println(filteredFlow);
  Serial.print("\t• Average flow: ");
  Serial.println(sensorSumFlowReadings / readingsTaken);
  Serial.print("\t• Analogue flow ADC range: ");
  Serial.print(minAnalogFlow);
  Serial.print(" - ");
  Serial.println(maxAnalogFlow);
  Serial.println();

  Serial.print("Start ananlogue pressure readings: ");
  Serial.print("\t• pressureEMAzero: ");
  Serial.println(pressureEMAzero);
  Serial.print("\t• filteredPressureEMAzero: ");
  Serial.println(filteredPressureEMAzero);
  Serial.print("\t• filteredPressure: ");
  Serial.println(filteredPressure);
  Serial.print("\t• Average pressure: ");
  Serial.println(sensorSumPressureReadings / readingsTaken);
  Serial.print("\t• Analogue pressure ADC range: ");
  Serial.print(minAnalogPressure);
  Serial.print(" - ");
  Serial.println(maxAnalogPressure);
  Serial.println();

  minAnalogFlow = 4095;
  maxAnalogFlow = 0;
  minAnalogPressure = 4095;
  maxAnalogPressure = 0;
  readingsTaken = 0;
  sensorSumFlowReadings = 0;
  sensorSumPressureReadings = 0;
}

void loop() {

  flowEMA = 0.0;
  pressureEMA = 0.0;
  filteredFlow = 0.0;
  filteredPressure = 0.0;
  minAnalogFlow = 4095;
  maxAnalogFlow = 0;
  minAnalogPressure = 4095;
  maxAnalogPressure = 0;
  int readingTime = millis();
  bool startUp = true;
  int logTime  = millis();
 
  Serial.print("\nSetting EMA baseline");

  while(millis() - testStart <= testDuration) {

    if(millis() - readingTime >= 10){
      readingsTaken++;
      
      analogFlow = analogRead(flowSensorPin);
      flowEMA = alpha * (float)analogFlow + (1.0 - alpha) * flowEMA;
      filteredFlowEMA =  alpha * (float)analogFlow + (1.0 - alpha) * filteredFlowEMA ;
      filteredFlowEMA  = kalmanFlow.update(filteredFlowEMA );
      filteredFlow = kalmanMeanFlow.update(analogFlow);
      maxAnalogFlow = max(maxAnalogFlow, analogFlow);
      minAnalogFlow = min(minAnalogFlow, analogFlow);

      analogPressure = analogRead(pressureSensorPin);
      pressureEMA = alpha * (float)analogPressure + (1.0 - alpha) * pressureEMA;
      filteredPressureEMA = alpha * (float)analogPressure + (1.0 - alpha) * filteredPressureEMA;
      filteredPressureEMA = kalmanPressure.update(filteredPressureEMA);
      filteredPressure = kalmanMeanPressure.update(analogPressure);
      maxAnalogPressure = max(maxAnalogPressure, analogPressure);
      minAnalogPressure = min(minAnalogPressure, analogPressure);

      sensorSumFlowReadings += analogFlow;
      sensorSumPressureReadings += filteredPressure;

      if (millis() - serialTime >= 10000 && millis() % 10000 >= 10){
      
        Serial.print("\n*****Calibration run @ ");
        Serial.print(millis() / 1000);
        Serial.println(" in 12-bit resolution*****");

        Serial.print("Ananlogue flow readings: ");
        Serial.print("\t• flowEMA: ");
        Serial.println(flowEMA);
        Serial.print("\t• filteredFlowEMA: ");
        Serial.println(filteredFlowEMA);
        Serial.print("\t• filteredFlow: ");
        Serial.println(filteredFlow);
        Serial.print("\t• Average flow: ");
        Serial.println(sensorSumFlowReadings / readingsTaken);
        Serial.print("\t• Analogue flow ADC range: ");
        Serial.print(minAnalogFlow);
        Serial.print(" - ");
        Serial.println(maxAnalogFlow);
        Serial.println();

        Serial.print("Aanlogue pressure readings: ");
        Serial.print("\t• pressureEMA: ");
        Serial.println(pressureEMA);
        Serial.print("\t• filteredPressureEMA: ");
        Serial.println(filteredPressureEMA);
        Serial.print("\t• filteredPressure: ");
        Serial.println(filteredPressure);
        Serial.print("\t• Average pressure: ");
        Serial.println(sensorSumPressureReadings / readingsTaken);
        Serial.print("\t• Analogue pressure ADC range: ");
        Serial.print(minAnalogPressure);
        Serial.print(" - ");
        Serial.println(maxAnalogPressure);
        Serial.println();
        
        serialTime = millis();      

      }

      if (millis() - logTime >= 1000) {
        write_log_entry();
        logTime = millis();

        minAnalogFlow = 4095;
        maxAnalogFlow = 0;
        minAnalogPressure = 4095;
        maxAnalogPressure = 0;
        readingsTaken = 0;
        sensorSumFlowReadings = 0;
        sensorSumPressureReadings = 0;
      }

    }

  }

  tone(speaker, 750, 5000);
  while(1);
}

The code for the Kalman filter is:

#ifndef KALMAN_H
#define KALMAN_H

class KalmanFilter {
  public:
    KalmanFilter(float processNoise, float measurementNoise, float estimatedError, float initialValue) {
      Q = processNoise;
      R = measurementNoise;
      P = estimatedError;
      x = initialValue;
    }

    float update(float measurement) {
      // Prediction update
      P = P + Q;

      // Measurement update
      K = P / (P + R);
      x = x + K * (measurement - x);
      P = (1 - K) * P;

      return x;
    }

  private:
    float Q;  // Process noise covariance
    float R;  // Measurement noise covariance
    float P;  // Estimation error covariance
    float K;  // Kalman gain
    float x;  // Estimated value
};

#endif

***ETA1: There were two solutions.

The first was from camsysca to implement a dummy read of the sensor that gets discarded followed by a second read that it kept and used to process. This cleaned up the signal and brought it higher, but it was still low compared to the voltage. Apparently, doing a dummy read has to do with clearing any residual noise or unstable signals due to there being only one ADC and multiple pins that are read, and doing a dummy read that is discarded helps to get a clean signal.

The second was from jim-p to implement a voltage reference for the Arduino Giga to use. I made a matched voltage divider, created a second matched RC low-pass filter, fed this with the same filtered power signal going to one of the sensors, and put this into the AREF pin on the Giga. I didn't have to implement any additional code (i.e., no analogReference(EXTERNAL) code), and it cleaned up the signal and brought it to the proper range that matches the voltage.

***ETA2: Connecting ground
I had the breadboards and Arduino connected using a star ground, but I decided to connect the 3.3V power rail ground (i.e., where the RC low-pass filter and voltage dividers connect), and connect it to the ground next to the AREF pin on the Arduino to see what would happen, and it significantly cleaned up the noise in the signal. I also connected the ground from the 5V power rail in my setup, and it cleaned the signal up even more (though this isn't shown in the graph below).

Very first thing to try - double read each analog input, discarding the first reading and using the second. If that does NOT change your problem, proceed.
Let us know how that goes.

1 Like

What sensor(s)? Please post links to the data sheets, and a circuit diagram.

Here's the data sheet for the flow sensor. I don't have one for the pressure sensor, as it's a generic pressure sensor.
Renesas FS1023-DL

A circuit diagram is going to take a bit longer to draw up (I can't just draw it out with characters on here, and showing a picture of the breadboard isn't going to be all that helpful because there are other components around it unrelated to this particular calibration procedure). But basically:

-A 5V DC-DC buck converter converter 24VDC to 5VDC
-On the output of the buck converter, there is a 1000µF capacitor bridging the output to help with fluctuations in the power
-For the sensor, there is a 10µF electrolytic capacitor and a 0.1µF ceramic capacitor both bridging the voltage pins going to the sensor
-For the sensor signal wire, that goes through a voltage divider, where R1 is 6120Ω and R2 is 11990Ω
-The output of the voltage divider goes through a 1000Ω resistor to the jumper wire going to the Arduino, and there is also a 0.1µF ceramic capacitor tapping off from the output from the 1000Ω resistor to ground.

Please post a hand drawn circuit diagram.

Why would you even bother to buy a "generic pressure sensor" with no data sheet? No one can help If you are using it incorrectly.

You need to use an external reference that is derived directly from the 5V supply of the sensors, otherwise there is no fixed relationship between the sensor output and the ADC reading.

2 Likes

Because I don't have a lot of money to spend on this project and it's already costing in the range of $2,000. The flow sensor was over $200, a similar pressure sensor to what I have is about $100-$150, and I had to cut costs. It a pressure sensor that people have used with Arduino projects and had decent reviews.

Okay, that makes sense. I am assuming that I would need to set up a similar voltage divider to the one that I am using for the flow sensor and likely another RC low-pass filter too. It looks for the Giga, you connect a the reference signal to the AREF pin but don't have to put anything in the code to tell it to use it as a reference (i.e., no need for analogReference() in the code). I will try to rig that up.

I ran it a few times to be sure, and this seems to have improved the signal, though it's still a bit low, but I am not sure why this would improve the signal. What's going on?

Readings now (after 5 min)
Connected to sensor (no flow):
•EMA: 452.21
•filtered EMA: 445.67
•filtered raw data: 473.76
•average: 428.83
•range: 0 - 923

Previous readings after 5 min
Connected to sensor (not flow):
•EMA: 297.66
•filtered EMA: 296.99
•filtered raw data: 264.90
•average: 309.42
•range: 0 - 639

There is not a lot of information on how the Giga ADC system works, you may need to use an opamp buffer for the reference input. You may also somehow be able to calibrate the internal reference but I'm not that familiar with the Giga

1 Like

That sensor can also do i2c. Fyi.

I know, and I had thought about it, but I am already working with three breadboards while prototyping this and each is intended to represent a separate module, this module with the sensor is going to be housed separate from the one with the Arduino and connected through a multipin connector, etc. I was worried about the signal quality of the I2C (I haven't worked with it, but I was told on another project that where I want to have multiple I2C addressed components that the signal quality can quickly degrade even over a short distance), so I decided to stick with analogue signals, as that is already what I am getting with the pressure sensor, temperature sensors, etc.

This seems to have done the trick. I pulled power from the pin feeding the flow sensor (after the two capacitors), ran it through a matched voltage divider to the one used to process the flow signal, then implemented an RC low-pass filter, then fed into the AREF pin on my Giga. Now I am seeing flow and pressure signals that match the voltage and less variance in the range of signal values. I didn't have to add any extra code for it either. Thanks!

1 Like

What was happening I think is, you have a high-impedance sensor output; when ADCs scan multiple inputs, the voltage at the input may not change to the sensor's output level if it's impedance is too high, instead 'carrying forward' some portion of the previous input's charge. By doing a double read, you effectively charge your input to closer to the actual input voltage. A triple-read is rarely necessary, but would indicate an extremely poor sensor output. It can work the other way, as well - reading a high input level, then a low on the next input, can result in a too-high input signal. Worth remembering when working with sensors.

I suggested it merely as a diagnostic - to see if your signal was impaired - Jim's suggestion cured it, because it places a low-impedance amplifier between your sensor and the ADC input.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.