19 Lab: Sensor Calibration
Hands-On Wokwi Workshop
sensor calibration, Wokwi, ESP32, two-point calibration, signal conditioning, hands-on lab
19.1 Start With the Measurement Story
A calibration lab should feel like detective work: compare the raw reading to a known condition, identify the error, apply a correction, then test a new point to prove the sensor has become more trustworthy.
19.2 Learning Objectives
By completing this lab, you will be able to:
- Explain calibration fundamentals: Describe why sensors need calibration and how raw readings differ from true values
- Implement two-point calibration: Calculate offset and gain correction using low and high reference points
- Apply signal conditioning: Configure moving average filtering to reduce noise in sensor readings
- Evaluate raw vs calibrated data: Measure the impact of calibration on measurement accuracy
- Integrate calibration persistence: Deploy EEPROM-based coefficient storage for production systems
- Two-Point Calibration: A procedure using two known reference values to calculate gain and offset correction coefficients, correcting sensitivity errors and zero-point errors simultaneously
- Gain Coefficient: The slope m in
calibrated = raw * gain + offset; corrects proportional errors where the sensor reads too high or too low by a percentage of the measured value - Offset Coefficient: The intercept b in the calibration equation; corrects constant errors where the sensor always reads a fixed amount above or below the true value
- Moving Average Filter: A digital filter replacing each reading with the average of the last N samples, reducing noise at the cost of slower response to rapid changes
- EEPROM Persistence: Storing calibration coefficients in non-volatile memory so they survive power cycles — essential for production IoT deployments
- Reference Standards: Known, accurate values used as the basis for calibration; their accuracy sets the ceiling for calibrated sensor accuracy
- Calibration Drift: Gradual change in sensor response over time due to aging, contamination, or thermal stress, requiring periodic recalibration
- Wokwi Simulator: Browser-based ESP32 simulation environment allowing firmware development and testing without physical hardware
19.3 Most Valuable Understanding (MVU)
Two-point calibration corrects sensor errors by calculating a simple linear equation: calibrated = raw * gain + offset, where gain fixes sensitivity errors and offset fixes zero-point errors.
This is the single most important concept in this lab. Every real sensor has manufacturing variations that cause its readings to deviate from the true value. Two-point calibration uses two known reference points to calculate correction coefficients that map inaccurate raw readings to accurate calibrated values. The formula y = mx + b (from basic algebra) is the foundation - gain is the slope (m) and offset is the y-intercept (b).
Remember: Calibration accuracy depends entirely on your reference standards. Use the most accurate references you can obtain, and bracket your expected measurement range (calibrate at 10% and 90%, not both at 50%).
This is part of a series on Sensor Interfacing:
- Sensor Data Processing - Theory behind filtering and calibration
- Sensor Calibration Lab (this chapter) - Hands-on calibration workshop
- Sensor Communication Protocols - I2C, SPI interfaces
- Sensor Applications - Real-world implementation examples
Related Topics:
- ADC Fundamentals - Understanding analog-to-digital conversion
- Signal Processing - Advanced filtering techniques
- Sensor Labs: Implementation and Review - Hardware and browser-lab workflow patterns
19.4 Introduction
In this hands-on lab, you will build a complete sensor calibration system using an ESP32 microcontroller in the Wokwi browser-based simulator. You will wire a potentiometer to simulate a sensor with offset and gain errors, implement two-point calibration to correct those errors, and apply a moving average filter for noise reduction. The lab takes approximately 45-60 minutes to complete and requires no physical hardware – everything runs in your browser.
By the end of this lab, you will have working firmware that interactively captures reference points, calculates calibration coefficients, and applies real-time correction to sensor readings.
This lab has several moving parts, so read it as one calibration workflow:
- First you frame the problem: raw sensors have offset, gain, noise, drift, and non-linearity.
- Then you build the ESP32 simulator circuit and run the low/high reference capture.
- Next you inspect the firmware state machine that turns serial commands into gain and offset coefficients.
- After that you work through the two-point math, ADC resolution, filtering, and validation questions.
- Finally you extend the lab toward multi-point calibration, EEPROM storage, drift compensation, and production evidence.
Checkpoints recap what you have proved so far; “Part” headings are required lab steps, while optional challenges can wait until the core simulator works.
19.5 Prerequisites
- Basic understanding of Arduino/C++ programming
- Familiarity with analog inputs and ADC concepts
- Completion of the Sensor Data Processing chapter (recommended)
19.5.1 Learning Path
1. Before This Lab Review ADC readings, Arduino/ESP32 serial output, and basic sensor data processing.
2. In This Lab Build the circuit, capture low/high references, calculate gain and offset, then smooth noisy readings.
3. After This Lab Apply multi-point calibration, sensor fusion, EEPROM persistence, and production validation.
This lab uses Wokwi, a free online electronics simulator. No physical hardware required! You can experiment with sensor calibration techniques directly in your browser.
Think of calibration like adjusting a musical instrument. Even a brand-new guitar needs to be tuned before it plays the right notes. Sensors are similar - they need to be “tuned” to give accurate readings.
Why do sensors need calibration?
- Manufacturing variations: No two sensors are exactly identical, just like no two guitars are perfectly tuned from the factory
- Environmental factors: Temperature, humidity, and age can cause sensors to drift over time
- Component tolerances: The electronic parts inside sensors have slight variations
Real-world example: Imagine you buy a cheap thermometer that always reads 2 degrees too hot. You could either: - Buy an expensive, perfectly calibrated thermometer ($$$) - Or calibrate your cheap thermometer by noting “always subtract 2 degrees” (FREE!)
The mathematical approach: Instead of just “subtract 2”, calibration gives us a formula: corrected = raw × gain + offset
- Offset fixes constant errors (like always being 2 degrees off)
- Gain fixes scaling errors (like reading 50% when it should be 55%)
This lab teaches you how to find those correction values using two known reference points - just like tuning a guitar by comparing it to a tuner at two different notes!
Hey there, young scientist! Let’s learn about sensor calibration with the Sensor Squad!
Sammy the Sensor has a problem - when the room is actually warm (like 25 degrees), Sammy says “It’s 28 degrees!” And when it’s cold (like 10 degrees), Sammy says “It’s 13 degrees!” Sammy is not lying - Sammy was just built a little differently than other sensors! Sammy always reads too high - that is an offset error (always adding a constant). But notice something: at 10 degrees Sammy is off by 3, and at 25 degrees Sammy is off by 3 too. That constant +3 shift is a pure offset error.
Think of it like a bathroom scale: Imagine your bathroom scale always shows 3 pounds more than your real weight. That is an offset error - it is always off by the same amount! You could fix it by subtracting 3 from every reading.
But what if the scale also stretches the numbers - showing 11 pounds when you are really 10? That is a gain error - the scale reads proportionally too high!
Sammy’s Calibration Adventure:
Step 1 - Find a “known cold” reference: Sammy measures ice water (which we KNOW is 0 degrees). Sammy says “It’s 3 degrees!” Oops - that is Sammy’s offset error! (0 + 3 = 3)
Step 2 - Find a “known hot” reference: Sammy measures boiling water (which we KNOW is 100 degrees). Sammy says “It’s 103 degrees!” Still 3 degrees too high - the offset is consistent! (100 + 3 = 103)
Step 3 - Do the math magic: Using both reference points, we calculate the correction formula: subtract 3 from every reading. Now whenever Sammy gives a reading, we fix it automatically!
Lila the Light explains: “It’s like being a translator! Sammy speaks ‘Sammy language’ and we translate it to ‘real temperature language’ using our special formula!”
Max the Motor adds: “I need calibration too! When someone tells me ‘go 50% speed’, I might actually go 55% without calibration. That could make robots bump into walls!”
Fun Experiment: Ask a grown-up if you can calibrate a kitchen thermometer! Put it in ice water (should read 0C or 32F) and see if it’s accurate. Many thermometers are off by a few degrees!
Remember: Calibration is like teaching your sensor to tell the truth by giving it a “cheat sheet” of corrections!
19.6 Calibration Matters
Real sensors have manufacturing variations that cause:
- Offset errors: Sensor reads non-zero when it should read zero
- Gain errors: Sensor’s sensitivity differs from the ideal specification
- Non-linearity: Response curve deviates from expected linear relationship
Two-point calibration corrects both offset and gain errors by measuring at two known reference points.
19.7 Folded Error And Quality Metrics Notes
Offset and gain errors behave differently in the calibration record. Offset shifts the whole response by a constant amount, so a zero reference can be wrong even when the slope is usable. Gain changes the slope, so error grows as the true value moves away from the reference point. A two-point calibration should therefore record both reference points, the calculated slope and offset, and at least one verification point between or beyond the references.
After calibration, do not report only “it works.” Compare measured values with actual reference values and record metrics that match the claim:
- Maximum absolute error shows the worst observed miss in the checked range.
- RMSE summarizes typical error across several verification points.
- R-squared helps detect whether the calibrated response is close to linear, but it does not replace endpoint error checks.
These metrics are evidence for the tested range only. If the reference points are too close together, small reference mistakes can amplify at the extremes, so span and verification evidence matter as much as the equation.
Two-point calibration fits a straight line through two reference points. This corrects linear errors (offset and gain) but cannot correct non-linearity – cases where the sensor’s response curve bends or deviates from a straight line.
ESP32 ADC non-linearity: The ESP32’s built-in ADC has well-documented non-linearity, with errors of up to 6% at the extremes of its input range (below ~100 mV and above ~3.1 V). Even after a perfect two-point calibration, readings in these regions will still be inaccurate.
Solutions for non-linear sensors:
- Use Espressif’s
esp_adc_calcharacterization library, which applies factory-measured correction curves - Perform multi-point calibration with 3 or more reference points and piecewise linear interpolation (see Challenge 1 in Part 5 below)
- For highly non-linear sensors (thermistors, pH probes), use lookup tables or polynomial fits
Checkpoint: Why Calibration Exists
You now know:
- Offset error is a constant shift, gain error changes the slope, and non-linearity bends the response away from a straight line.
- Two-point calibration uses low and high reference points to correct linear offset and gain errors together.
- The ESP32 ADC warning matters because errors can reach 6% near the range extremes, so validation points are evidence, not decoration.
19.7.1 Calibration Fundamentals
19.8 Part 1: Circuit Setup
19.8.1 Wokwi Simulator
Use Wokwi when you are ready to test the circuit. If the embedded panel stays on “Loading,” open the workspace in a new tab and keep this page beside it.
Open a new ESP32 Wokwi workspace and use the wiring diagram and optional sketch below.
- The potentiometer simulates an “uncalibrated” sensor with offset and gain errors
- Adjust the potentiometer during simulation to test different readings
- Watch the Serial Monitor to compare raw vs calibrated values
- LED brightness indicates calibration mode (blinking) vs normal mode (steady)
19.8.2 Component Connections
| ESP32 Pin | Component | Connection | Purpose |
|---|---|---|---|
| GPIO 34 | Potentiometer | Wiper (middle) | Simulated sensor input |
| 3.3V | Potentiometer | One outer pin | Reference voltage |
| GND | Potentiometer | Other outer pin | Ground reference |
| GPIO 2 | LED | Anode (long leg) | Calibration status indicator |
| GND | LED | Cathode (via 220 ohm resistor) | Complete LED circuit |
19.8.3 Wiring Diagram
The circuit is deliberately small: one analog input, one status output, and a potentiometer you can move to create repeatable low, middle, and high readings.
19.9 Part 2: Calibration Simulator Activity
The purpose of this lab is not to memorize a long sketch. Your goal is to see how two known reference points turn an unreliable raw reading into a corrected measurement.
Run it: Watch two reference points become a correction in the calibration-process animation below before you drive the simulator by hand. Pick the Two-point method on a scenario such as Greenhouse or Cold chain, then Play and Step through capturing the low and high references, and open the Fit and Residuals views to see the slope and offset the two points produce and how well mid-range readings are corrected. This mirrors the low-capture, high-capture, and verify steps in the What to Try First list.
- Run the simulator and open the Serial Monitor.
- Move the potentiometer to a low reference position and capture that point.
- Move it to a high reference position and capture that point.
- Watch the reported gain and offset.
- Move the potentiometer to a middle value and compare raw, filtered, and calibrated readings.
If the calibrated value does not make sense, repeat the capture and check whether the two reference points were far enough apart.
The sketch behaves like a small calibration wizard:
| Stage | Student Action | What the Firmware Learns |
|---|---|---|
| Normal reading | Turn the potentiometer | Shows raw, filtered, and calibrated values |
| Start calibration | Send c |
Switches into reference-capture mode |
| Low reference | Set a known low value and send l |
Stores the raw reading for the low point |
| High reference | Set a known high value and send h |
Stores the raw reading for the high point |
| Calculation | No action | Computes slope and offset |
| Verification | Test middle values | Confirms that correction works away from the endpoints |
The essential algorithm is short:
- Capture
raw_lowat a known low reference value. - Capture
raw_highat a known high reference value. - Calculate the slope from the two reference points.
- Calculate the offset needed to align the low point.
- Apply the correction: calibrated value = slope x raw value + offset.
Checkpoint: Running the Lab
You now know:
- GPIO 34 carries the simulated sensor reading and GPIO 2 reports calibration status with an LED.
- The lab uses a low reference near 10% and a high reference near 90%, then checks middle values instead of trusting the endpoints alone.
- The firmware state machine moves from normal reading to low capture, high capture, coefficient calculation, and verification.
Once that flow is clear, the full sketch below becomes a reference implementation rather than a wall of code to memorize.
19.10 Optional: Full Wokwi Sketch
Use this only when you want to implement the simulator after you understand the calibration behavior.
/*
* Sensor Calibration Workshop
* Interactive Lab: Two-Point Calibration with Signal Conditioning
*
* This code demonstrates:
* - Raw ADC reading and conversion
* - Two-point calibration (offset + gain correction)
* - Simple moving average filter for noise reduction
* - Interactive calibration procedure
*
* Components:
* - ESP32 DevKit
* - Potentiometer on GPIO 34 (simulates uncalibrated sensor)
* - LED on GPIO 2 (calibration status indicator)
*/
// ============ PIN DEFINITIONS ============
const int SENSOR_PIN = 34; // Analog input (ADC1_CH6)
const int LED_PIN = 2; // Built-in LED for status
// ============ ADC CONFIGURATION ============
const float ADC_MAX = 4095.0; // 12-bit ADC resolution (0-4095)
const int FILTER_SIZE = 10; // Moving average window size
// ============ CALIBRATION VARIABLES ============
float calOffset = 0.0; // Offset correction (additive)
float calGain = 1.0; // Gain correction (multiplicative)
// Reference values for two-point calibration
float lowRefActual = 10.0; // Known low reference (e.g., 10%)
float highRefActual = 90.0; // Known high reference (e.g., 90%)
float lowRefRaw = 0.0; // Raw reading at low reference
float highRefRaw = 0.0; // Raw reading at high reference
// ============ FILTER VARIABLES ============
float filterBuffer[FILTER_SIZE];
int filterIndex = 0;
bool filterFilled = false;
// ============ STATE MACHINE ============
enum CalibrationState {
NORMAL_MODE,
WAIT_LOW_POINT,
WAIT_HIGH_POINT
};
CalibrationState currentState = NORMAL_MODE;
unsigned long lastPrintTime = 0;
const unsigned long PRINT_INTERVAL = 500;
// ============ FUNCTION DECLARATIONS ============
float readRawSensor();
float applyFilter(float newValue);
float applyCalibration(float rawValue);
void calculateCalibrationCoefficients();
void printCalibrationStatus();
void handleSerialCommands();
void blinkLED(int times, int delayMs);
void setup() {
Serial.begin(115200);
delay(1000);
// Configure pins
pinMode(LED_PIN, OUTPUT);
analogReadResolution(12); // 12-bit ADC (0-4095)
analogSetAttenuation(ADC_11db); // Set 11dB attenuation for full 0-3.3V input range
// Initialize filter buffer
for (int i = 0; i < FILTER_SIZE; i++) {
filterBuffer[i] = 0.0;
}
// Welcome message
Serial.println("\n========================================");
Serial.println(" SENSOR CALIBRATION WORKSHOP");
Serial.println(" Interactive Two-Point Calibration Lab");
Serial.println("========================================\n");
Serial.println("COMMANDS:");
Serial.println(" 'c' - Start calibration procedure");
Serial.println(" 'l' - Capture LOW reference point");
Serial.println(" 'h' - Capture HIGH reference point");
Serial.println(" 'r' - Reset calibration to defaults");
Serial.println(" 's' - Show current calibration status");
Serial.println("\nTurn the potentiometer to simulate sensor readings.\n");
// Initial LED flash to indicate startup
blinkLED(3, 200);
digitalWrite(LED_PIN, HIGH); // LED on = normal mode
}
void loop() {
// Handle serial commands for calibration
handleSerialCommands();
// Read and process sensor
float rawPercent = readRawSensor();
float filteredRaw = applyFilter(rawPercent);
float calibratedValue = applyCalibration(filteredRaw);
// Print values periodically
if (millis() - lastPrintTime >= PRINT_INTERVAL) {
lastPrintTime = millis();
if (currentState == NORMAL_MODE) {
Serial.print("RAW: ");
Serial.print(rawPercent, 1);
Serial.print("% | FILTERED: ");
Serial.print(filteredRaw, 1);
Serial.print("% | CALIBRATED: ");
Serial.print(calibratedValue, 1);
Serial.print("% | CORRECTION: ");
Serial.print(abs(filteredRaw - calibratedValue), 2);
Serial.println("%");
}
}
// Update LED based on mode
if (currentState != NORMAL_MODE) {
// Blink LED during calibration
digitalWrite(LED_PIN, (millis() / 250) % 2);
} else {
digitalWrite(LED_PIN, HIGH); // Steady on in normal mode
}
delay(50);
}
// ============ SENSOR READING ============
float readRawSensor() {
int rawADC = analogRead(SENSOR_PIN);
float percent = (rawADC / ADC_MAX) * 100.0;
return percent;
}
// ============ MOVING AVERAGE FILTER ============
float applyFilter(float newValue) {
filterBuffer[filterIndex] = newValue;
filterIndex = (filterIndex + 1) % FILTER_SIZE;
if (filterIndex == 0) {
filterFilled = true;
}
int count = filterFilled ? FILTER_SIZE : filterIndex;
if (count == 0) return newValue;
float sum = 0.0;
for (int i = 0; i < count; i++) {
sum += filterBuffer[i];
}
return sum / count;
}
// ============ CALIBRATION APPLICATION ============
float applyCalibration(float rawValue) {
return (rawValue * calGain) + calOffset;
}
// ============ CALIBRATION PROCEDURE ============
void calculateCalibrationCoefficients() {
if (abs(highRefRaw - lowRefRaw) < 0.001) {
Serial.println("ERROR: Reference points too close together!");
return;
}
calGain = (highRefActual - lowRefActual) / (highRefRaw - lowRefRaw);
calOffset = lowRefActual - (lowRefRaw * calGain);
Serial.println("\n========================================");
Serial.println(" CALIBRATION COMPLETE!");
Serial.println("========================================");
Serial.print(" Gain (slope): ");
Serial.println(calGain, 4);
Serial.print(" Offset (intercept): ");
Serial.println(calOffset, 4);
Serial.println("----------------------------------------");
Serial.println(" Calibration Equation:");
Serial.print(" Calibrated = Raw * ");
Serial.print(calGain, 4);
Serial.print(" + ");
Serial.println(calOffset, 4);
Serial.println("========================================\n");
// Verification
float verifyLow = applyCalibration(lowRefRaw);
float verifyHigh = applyCalibration(highRefRaw);
Serial.println("VERIFICATION:");
Serial.print(" Low point: Raw=");
Serial.print(lowRefRaw, 1);
Serial.print("% -> Calibrated=");
Serial.print(verifyLow, 1);
Serial.print("% (Expected: ");
Serial.print(lowRefActual, 1);
Serial.println("%)");
Serial.print(" High point: Raw=");
Serial.print(highRefRaw, 1);
Serial.print("% -> Calibrated=");
Serial.print(verifyHigh, 1);
Serial.print("% (Expected: ");
Serial.print(highRefActual, 1);
Serial.println("%)\n");
currentState = NORMAL_MODE;
blinkLED(5, 100); // Success indication
}
// ============ SERIAL COMMAND HANDLER ============
void handleSerialCommands() {
if (Serial.available() > 0) {
char cmd = Serial.read();
switch (cmd) {
case 'c':
case 'C':
Serial.println("\n========================================");
Serial.println(" CALIBRATION PROCEDURE STARTED");
Serial.println("========================================");
Serial.println("\nStep 1: Set the potentiometer to the LOW reference point");
Serial.print(" (This represents ");
Serial.print(lowRefActual, 0);
Serial.println("% actual value)");
Serial.println(" Press 'l' when ready to capture.\n");
currentState = WAIT_LOW_POINT;
break;
case 'l':
case 'L':
if (currentState == WAIT_LOW_POINT) {
lowRefRaw = applyFilter(readRawSensor());
Serial.print("\nLOW POINT CAPTURED: Raw = ");
Serial.print(lowRefRaw, 1);
Serial.print("% (Actual = ");
Serial.print(lowRefActual, 0);
Serial.println("%)\n");
Serial.println("Step 2: Set the potentiometer to the HIGH reference point");
Serial.print(" (This represents ");
Serial.print(highRefActual, 0);
Serial.println("% actual value)");
Serial.println(" Press 'h' when ready to capture.\n");
currentState = WAIT_HIGH_POINT;
} else {
Serial.println("Press 'c' first to start calibration!");
}
break;
case 'h':
case 'H':
if (currentState == WAIT_HIGH_POINT) {
highRefRaw = applyFilter(readRawSensor());
Serial.print("\nHIGH POINT CAPTURED: Raw = ");
Serial.print(highRefRaw, 1);
Serial.print("% (Actual = ");
Serial.print(highRefActual, 0);
Serial.println("%)\n");
Serial.println("Calculating calibration coefficients...\n");
calculateCalibrationCoefficients();
} else {
Serial.println("Capture low point first with 'l'!");
}
break;
case 'r':
case 'R':
calOffset = 0.0;
calGain = 1.0;
lowRefRaw = 0.0;
highRefRaw = 0.0;
currentState = NORMAL_MODE;
Serial.println("\nCalibration RESET to defaults (gain=1.0, offset=0.0)\n");
break;
case 's':
case 'S':
printCalibrationStatus();
break;
}
}
}
// ============ STATUS DISPLAY ============
void printCalibrationStatus() {
Serial.println("\n========================================");
Serial.println(" CURRENT CALIBRATION STATUS");
Serial.println("========================================");
Serial.print(" Gain: ");
Serial.println(calGain, 4);
Serial.print(" Offset: ");
Serial.println(calOffset, 4);
Serial.println("----------------------------------------");
Serial.print(" Low Ref: Raw=");
Serial.print(lowRefRaw, 1);
Serial.print("% -> Actual=");
Serial.print(lowRefActual, 0);
Serial.println("%");
Serial.print(" High Ref: Raw=");
Serial.print(highRefRaw, 1);
Serial.print("% -> Actual=");
Serial.print(highRefActual, 0);
Serial.println("%");
Serial.println("========================================\n");
}
// ============ LED INDICATOR ============
void blinkLED(int times, int delayMs) {
for (int i = 0; i < times; i++) {
digitalWrite(LED_PIN, HIGH);
delay(delayMs);
digitalWrite(LED_PIN, LOW);
delay(delayMs);
}
}19.11 Calibration Procedure
The calibration code implements a state machine that guides you through the process:
Calibration State Machine Workflow
The firmware stays in normal reading mode until the learner sends serial commands to capture low and high reference points.
c; the firmware explains the low/high reference capture sequence.
l.
h.
Recovery path: send r to reset calibration, or repeat the low/high capture if a reference point was wrong.
Follow these steps to perform two-point calibration:
In this lab, the potentiometer position represents the sensor reading. We are pretending that when the potentiometer is at ~10% position, the “true” value is 10%, and at ~90% position, the “true” value is 90%. The calibration corrects any discrepancies.
19.11.1 Step 1: Run the Simulation
- Click Start in Wokwi to run the simulation
- Open the Serial Monitor (bottom panel)
- Observe the output showing RAW, FILTERED, and CALIBRATED values
19.11.2 Step 2: Observe Raw Readings
- Turn the potentiometer to different positions
- Notice the RAW values in the Serial Monitor
- Before calibration, RAW and CALIBRATED values are identical
19.11.3 Step 3: Start Calibration
- Type
cin the Serial Monitor and press Enter - You will see instructions for the calibration procedure
19.11.4 Step 4: Capture Low Reference Point
- Turn the potentiometer to approximately 10% position
- Let the reading stabilize for 2-3 seconds
- Type
l(lowercase L) and press Enter - The system captures this as the “low reference” point
19.11.5 Step 5: Capture High Reference Point
- Turn the potentiometer to approximately 90% position
- Let the reading stabilize for 2-3 seconds
- Type
hand press Enter - The system captures this as the “high reference” point
19.11.6 Step 6: Verify Calibration
- The system calculates and displays calibration coefficients
- Move the potentiometer through its range
- Observe how CALIBRATED values now differ from RAW values
- The CORRECTION column shows how much calibration adjusts each reading
19.12 Part 4: Understanding the Math
The Two-Point Calibration Formula:
Given two reference points:
- Low point: (raw_low, actual_low) - e.g., sensor reads 15% when true value is 10%
- High point: (raw_high, actual_high) - e.g., sensor reads 85% when true value is 90%
Calculate:
- Gain (slope)
gain = (actual_high - actual_low) / (raw_high - raw_low)
- Offset (intercept)
offset = actual_low - (raw_low * gain)
Apply calibration:
19.12.1 Two-Point Calibration Calculator
Two-Point Calibration Calculation: A load cell for beehive monitoring reads 410 raw ADC counts at 0 kg and 3685 counts at 50 kg (using a 12-bit ADC with 0-4095 range). Calculate gain and offset to convert raw ADC to kilograms.
Given reference points:
- Low: \((410\text{ counts}, 0\text{ kg})\)
- High: \((3685\text{ counts}, 50\text{ kg})\)
Step 1: Calculate gain (slope of the line): \[ \text{Gain} = \frac{y_2 - y_1}{x_2 - x_1} = \frac{50\text{ kg} - 0\text{ kg}}{3685 - 410} = \frac{50}{3275} = 0.01527\text{ kg/count} \]
Step 2: Calculate offset (y-intercept): \[ \text{Offset} = y_1 - (x_1 \times \text{Gain}) = 0 - (410 \times 0.01527) = -6.26\text{ kg} \]
Step 3: Calibration equation: \[ \text{Weight (kg)} = (\text{Raw ADC counts} \times 0.01527) - 6.26 \]
Verification:
- At 410 counts: \((410 \times 0.01527) - 6.26 = 6.26 - 6.26 = 0\text{ kg}\) ✓
- At 3685 counts: \((3685 \times 0.01527) - 6.26 = 56.27 - 6.26 = 50.01\text{ kg} \approx 50\text{ kg}\) ✓
Resolution calculation: \[ \text{Resolution} = \text{Gain} = 0.01527\text{ kg/count} \approx 15\text{ grams/count} \]
With a 12-bit ADC (4096 levels) spanning the full 50 kg range, theoretical resolution would be 50/4096 = 12.2 g. Our actual resolution of 15 g is slightly worse because the load cell does not use the full ADC range (3275 out of 4096 counts, or about 80%). Adjusting amplification to use more of the ADC range would improve resolution.
Checkpoint: Calibration Math
You now know:
- Two reference points define the line: gain equals
(actual_high - actual_low) / (raw_high - raw_low). - The load-cell example maps 410 counts to 0 kg and 3685 counts to 50 kg, giving a gain of 0.01527 kg/count and an offset of -6.26 kg.
- A 12-bit ADC has 4096 levels, but usable resolution depends on how much of that range the sensor actually spans.
19.12.2 Interactive ADC Resolution Calculator
Explore how ADC bit depth and measurement range affect the smallest detectable change (resolution per count).
19.12.3 Knowledge Check: Calibration Math
19.12.4 Moving Average Filter Explorer
A moving average filter smooths noisy data by averaging a window of recent samples. The window size (N) controls the tradeoff between smoothing and responsiveness. Larger windows produce smoother output but react more slowly to real changes.
The math and filter sections answer “what coefficients should I calculate?” The challenges below ask the production question: how do you keep those coefficients trustworthy after the first successful run?
19.13 Part 5: Challenge Exercises
19.14 Challenge 1: Three-Point Calibration
Run it: Before you code the three-point version, compare the methods in the reference animation below. Select 2-point and then Multi-point on the same sensor type and watch how a straight two-point line leaves error in a bending response while multi-point interpolation follows the curve. Use it to predict how much accuracy the added midpoint should buy you before you measure it in the simulator.
Goal: Extend the calibration to use three reference points for improved accuracy across the range.
Tasks:
- Add a third reference point at 50% (midpoint)
- Capture three points: low (10%), mid (50%), high (90%)
- Use piecewise linear interpolation:
- For values < 50%: use low-to-mid segment
- For values >= 50%: use mid-to-high segment
- Compare accuracy against two-point calibration
Hint: Store two sets of gain/offset coefficients and select based on input value.
19.15 Challenge 2: EEPROM Calibration Storage
Goal: Persist calibration coefficients so they survive power cycles.
Tasks:
- Add EEPROM library and save calibration after calculation
- Load calibration automatically at startup
- Add validity check (magic number) to detect uncalibrated state
- Add a ‘w’ command to write calibration and ‘e’ command to erase
#include <EEPROM.h>
// EEPROM addresses
const int EEPROM_MAGIC_ADDR = 0;
const int EEPROM_GAIN_ADDR = 4;
const int EEPROM_OFFSET_ADDR = 8;
const int EEPROM_MAGIC_VALUE = 0xCAFE;
void saveCalibrationToEEPROM() {
EEPROM.begin(64);
EEPROM.put(EEPROM_MAGIC_ADDR, EEPROM_MAGIC_VALUE);
EEPROM.put(EEPROM_GAIN_ADDR, calGain);
EEPROM.put(EEPROM_OFFSET_ADDR, calOffset);
EEPROM.commit();
Serial.println("Calibration saved to EEPROM!");
}
void loadCalibrationFromEEPROM() {
EEPROM.begin(64);
int magic;
EEPROM.get(EEPROM_MAGIC_ADDR, magic);
if (magic == EEPROM_MAGIC_VALUE) {
EEPROM.get(EEPROM_GAIN_ADDR, calGain);
EEPROM.get(EEPROM_OFFSET_ADDR, calOffset);
Serial.println("Calibration loaded from EEPROM");
} else {
Serial.println("No valid calibration found, using defaults");
calGain = 1.0;
calOffset = 0.0;
}
}19.16 Automatic Drift Compensation
Goal: Implement automatic baseline drift correction for long-term deployments.
Background: Sensors drift over time due to aging, temperature changes, and contamination. Automatic Baseline Correction (ABC) can compensate by assuming the sensor occasionally sees a known reference (e.g., CO2 sensors assume 400ppm outdoor air).
Tasks:
- Track the minimum reading over a 24-hour window
- Assume this minimum represents the “baseline” reference value
- Automatically adjust offset to correct drift
- Add drift alarm if correction exceeds threshold
19.17 Key Calibration Concepts Summary
| Concept | Description | When to Use |
|---|---|---|
| Offset Error | Sensor reads non-zero when true value is zero | Always needs correction |
| Gain Error | Sensor’s sensitivity differs from specification | When readings scale incorrectly |
| Two-Point Calibration | Uses two reference points to calculate offset and gain | Linear sensors (most common) |
| Multi-Point Calibration | Uses 3+ reference points with interpolation | Non-linear sensors (thermistors, pH) |
| Moving Average Filter | Averages N recent readings to reduce noise | Noisy environments, slow-changing signals |
| EEPROM Storage | Persists calibration across power cycles | Production deployments |
- Use reference standards that bracket your expected measurement range
- Allow sensor warm-up time before calibration (typically 5-30 minutes)
- Document environmental conditions during calibration (temperature, humidity)
- Recalibrate periodically based on manufacturer recommendations
- Store calibration metadata including date, conditions, and number of points
- Validate calibration by checking known reference values after applying coefficients
Checkpoint: Production Calibration
You now know:
- Multi-point calibration adds 3 or more reference points when a sensor curve is not linear.
- EEPROM storage needs a validity marker such as
0xCAFEso startup code can reject uninitialized memory. - Automatic Baseline Correction tracks a 24-hour minimum only when the sensor is expected to see a known baseline condition.
Two-Point Calibration Formula:
| Step | Formula | Description |
|---|---|---|
| 1 | gain = (actual_high - actual_low) / (raw_high - raw_low) |
Calculate slope |
| 2 | offset = actual_low - (raw_low × gain) |
Calculate y-intercept |
| 3 | calibrated = raw × gain + offset |
Apply correction |
See the Interactive Two-Point Calibration Calculator above for a hands-on tool. The worked load-cell example in Part 4 shows the same calculation with real numbers.
19.17.1 Production Considerations
19.18 Span Validation
The core lab above shows how to build and store a two-point calibration. The companion page focuses on the failure modes that determine whether those coefficients are trustworthy in production.
Continue with Calibration Span Error and Validation to test reference-point spacing, residual checks, range guards, and release evidence for calibrated sensors.
19.19 Summary
This lab provided hands-on experience with essential sensor calibration techniques used in production IoT systems.
19.19.1 Key Takeaways
| Concept | What You Learned | When to Apply |
|---|---|---|
| Two-Point Calibration | Calculate gain and offset from two known reference points | All linear sensors (temperature, pressure, light) |
| Moving Average Filter | Smooth noisy readings by averaging N recent samples | Noisy environments, before capturing calibration references |
| State Machine Design | Guide users through multi-step processes | Any interactive calibration or configuration procedure |
| Calibration Formula | calibrated = raw * gain + offset |
Apply correction to every raw sensor reading |
| EEPROM Persistence | Store calibration across power cycles with magic number validation | Production deployments, field-calibrated devices |
| Automatic Drift Compensation | Track minimum over time window to correct sensor drift | Long-term deployments, sensors prone to aging |
19.19.2 Skills You Practiced
- Circuit building: Connecting potentiometer and LED to ESP32
- Serial communication: Interactive command interface for calibration
- Mathematical modeling: Applying linear algebra to sensor correction
- Firmware architecture: State machine design for multi-step workflows
- Data persistence: Using EEPROM for non-volatile storage
19.19.3 Common Pitfalls to Avoid
| Pitfall | Why It’s Bad | Solution |
|---|---|---|
| Reference points too close together | Small errors in reference measurement cause large errors in calculated gain | Use 10% and 90%, not 45% and 55% |
| Forgetting sensor warm-up time | Sensors drift significantly in first few minutes after power-on | Allow 5-30 minutes before calibration |
| Not filtering before capture | Single noisy sample can corrupt entire calibration | Apply moving average filter before capturing reference |
| Assuming calibration lasts forever | Sensors drift over time due to aging and environment | Recalibrate periodically based on manufacturer guidance |
19.20 Knowledge Check
19.20.1 Quiz: Sensor Calibration
Match each calibration concept with its correct definition:
Arrange the two-point calibration procedure steps in the correct order:
19.21 See Also
For the full list of related chapters, see the “Related Chapters” section at the top of this page. Additional resources:
- Multi-Sensor Data Fusion - Combining calibrated data from multiple sensors
- Sensor Labs: Implementation and Review - Hardware and browser-lab workflow patterns