featured image

From Voltage to Volume — Calibrating Analog Sensors

Learn how to calibrate an analog pressure sensor to measure volume using numerical integration.

Published

Sun Jul 06 2025

Technologies Used

Arduino
Beginner 8 minutes

Raw sensor data is almost never what you actually need. The MPX2010 pressure sensor I used for my spirometer gives you voltage. But what a spirometer measures is liters — specifically, the volume of air exhaled. That gap between “voltage the sensor produces” and “number the clinician reads” is where calibration lives.

This is the algorithm I wrote to derive the proportionality constant that bridges hardware voltage and physical volume on an Arduino Nano Every.

The Engineering Challenge

The MPX2010 outputs a voltage proportional to air flow rate (Q). To get volume (V), you integrate flow over time:

V=QdtV = \int Q \, dt

The catch: we don’t know the exact relationship between sensor voltage (v) and flow rate yet. We need a calibration factor (k) that maps the two.

By pushing a known volume through the device — a standard 3-liter medical syringe — we can solve for k:

k=3.0 Litersvdtk = \frac{3.0 \text{ Liters}}{\int v \, dt}

Hardware required: Arduino Nano Every (5V logic), MPX2010DP (differential pressure), a 3L calibration syringe, standard Arduino IDE.

Step 1: Handling the DC Offset

Even with a good op-amp circuit, the MPX2010 outputs a non-zero voltage when the air is completely still. If you start integrating this “ghost voltage,” your calculated volume drifts toward infinity within seconds. Establishing a zero baseline at boot is non-negotiable:

float V_amb; // Ambient zero-point voltage

void setup() {
  pinMode(A0, INPUT);
  Serial.begin(9600);
  
  // Snapshot the sensor at rest — this becomes our session zero
  V_amb = bit_to_V(analogRead(A0));
  
  delay(2000);
  Serial.println("Pump 3L when prompted");
}

V_amb gets subtracted from every subsequent reading. It dynamically calibrates against ambient pressure and circuit noise every time you power the device on — no manual adjustment needed between sessions.

Step 2: The Sampling Loop and Hysteresis Threshold

Detecting when the user actually starts pushing the syringe is trickier than it sounds. The sensor has a noise floor of about ±0.03V. If you start integrating the moment voltage moves by 0.01V, you’re integrating noise, not airflow.

I implemented a hysteresis threshold of 0.05V. The code ignores the sensor until it detects a deliberate push:

// Wait for voltage to cross the threshold (V_amb + 0.05V)
while(V_val >= V_amb + 0.05 && i < 500) {
    
    V_val = bit_to_V(analogRead(A0));
    
    // Normalize: remove the ambient offset
    voltages[i] = V_val - V_amb;
    
    // Lock sampling rate to 100Hz
    // DELTA_T is defined as 0.01 seconds
    delay(DELTA_T * 1000); 
    
    i++;
}

The delay(DELTA_T * 1000) locks our sampling rate to 100Hz. In a production RTOS environment I’d use a hardware timer interrupt for this — a blocking delay isn’t ideal. But for a calibration script that runs once under controlled conditions, it’s sufficient. The important thing is that dt stays constant, because time is a fixed variable in the integration math.

Step 3: Trapezoidal Integration

Once the buffer has the waveform of a 3-liter push, we calculate the area under the curve. I chose the trapezoidal rule over a simple Riemann sum because it averages adjacent samples rather than using just the left or right endpoint. For a continuous analog signal this gives better accuracy with no added complexity:

float integrate_vol(float vals[], float dt) {
  int i = 0;
  float sum = 0;
  
  while(vals[i] > 0 && i < 500) {
    // Area = (Height_A + Height_B) / 2 * Width
    sum += (vals[i] + vals[i+1]) * 0.5 * dt;
    i++;
  }
  return sum;
}

Step 4: Deriving the Factor

The script runs this process five times to average out inconsistency in how hard you push the syringe, then outputs the calibration factor:

// avg is the average integrated voltage sum from 5 trials
Serial.println("Calibration factor: " + String(3.0 / avg));

The result — something like 2.94 — means that for every volt-second of integrated pressure, the device moved 2.94 liters of air. This constant gets hardcoded into the production firmware to convert real-time voltage readings into live volume measurements.

That’s the whole calibration pipeline: zero the ambient noise, detect deliberate actuation, integrate the signal numerically, and anchor the result to a known physical volume. The math is straightforward, but getting the noise handling and hysteresis threshold right is what makes the difference between a device that drifts and one that measures accurately.

We respect your privacy.

← View All Tutorials

Related Projects

    Ask me anything!