sensor calibration, Wokwi, ESP32, two-point calibration, signal conditioning, hands-on lab
17.1 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
In 60 Seconds
This hands-on Wokwi lab walks through two-point calibration on an ESP32 — calculating gain and offset from two known reference points using calibrated = raw * gain + offset. You configure a moving average filter, persist calibration coefficients in EEPROM, and verify that calibrated readings track true values across the sensor’s full range. No physical hardware needed; everything runs in your browser.
Key Concepts
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
17.2 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%).
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.
1. Before This LabReview ADC readings, Arduino/ESP32 serial output, and basic sensor data processing.
2. In This LabBuild the circuit, capture low/high references, calculate gain and offset, then smooth noisy readings.
3. After This LabApply multi-point calibration, sensor fusion, EEPROM persistence, and production validation.
Interactive Browser-Based Lab
This lab uses Wokwi, a free online electronics simulator. No physical hardware required! You can experiment with sensor calibration techniques directly in your browser.
For Beginners: What Is Calibration and Why Does It Matter?
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!
Sensor Squad: Teaching Your Sensor to Tell the Truth!
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!
17.5 Why Calibration Matters
Figure 17.1: Calibration transforms inaccurate raw sensor readings into precise, reliable measurements
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.
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_cal characterization 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
17.5.1 Knowledge Check: Calibration Fundamentals
Question 1: What causes offset error in sensors?
Question: A temperature sensor consistently reads 3 degrees higher than the actual temperature across its entire range. What type of error is this?
Gain error
Offset error
Non-linearity error
Random noise
Answer
B) Offset error
An offset error is a constant deviation from the true value. If the sensor always reads 3 degrees high (at 0, 25, 50, or 100 degrees), the error is constant - this is offset error. Gain error would cause the deviation to grow proportionally with the reading (e.g., 1 degree error at 10 degrees but 5 degrees error at 50 degrees).
Question 2: Why use two reference points instead of one?
Question: Why does two-point calibration require measuring at TWO known reference points instead of just one?
One point is always inaccurate
Two points are needed to calculate both gain AND offset
It takes longer with one point
Single-point calibration is illegal
Answer
B) Two points are needed to calculate both gain AND offset
A single point can only correct offset error (by subtracting a constant). But to also correct gain error (sensitivity), you need two points to define a line: y = mx + b. The two points let you calculate both the slope (gain/m) and intercept (offset/b) of the correction equation.
17.6 Part 1: Circuit Setup
17.6.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.
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)
17.6.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
17.6.3 Wiring Diagram
Figure 17.2: Circuit schematic showing ESP32 connections to potentiometer (sensor) and calibration status LED
17.7 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.
What to Try First
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.
Firmware Behavior in Plain Language
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:
Calibration Flow
Capture raw_low at a known low reference value.
Capture raw_high at 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.
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 ============constint SENSOR_PIN =34;// Analog input (ADC1_CH6)constint LED_PIN =2;// Built-in LED for status// ============ ADC CONFIGURATION ============constfloat ADC_MAX =4095.0;// 12-bit ADC resolution (0-4095)constint 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 calibrationfloat 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 referencefloat 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;unsignedlong lastPrintTime =0;constunsignedlong 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 bufferfor(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 sensorfloat rawPercent = readRawSensor();float filteredRaw = applyFilter(rawPercent);float calibratedValue = applyCalibration(filteredRaw);// Print values periodicallyif(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 modeif(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");// Verificationfloat 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);}}
17.8 Part 3: Step-by-Step 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.
1. Normal ModeRead the sensor, smooth the signal, and print raw, filtered, and calibrated values.
2. Start CalibrationUser sends c; the firmware explains the low/high reference capture sequence.
3. Capture Low PointUser sets the simulated sensor near 10% and sends l.
4. Capture High PointUser sets the simulated sensor near 90% and sends h.
5. Calculate CoefficientsThe code computes gain and offset from the two reference measurements.
6. Verify and StoreCheck known values, then save coefficients so calibration survives reset.
Recovery path: send r to reset calibration, or repeat the low/high capture if a reference point was wrong.
Calibration state machine workflow for the Wokwi lab
Follow these steps to perform two-point calibration:
Understanding the Simulation
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.
17.8.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
17.8.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
17.8.3 Step 3: Start Calibration
Type c in the Serial Monitor and press Enter
You will see instructions for the calibration procedure
17.8.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
17.8.5 Step 5: Capture High Reference Point
Turn the potentiometer to approximately 90% position
Let the reading stabilize for 2-3 seconds
Type h and press Enter
The system captures this as the “high reference” point
17.8.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
17.9 Part 4: Understanding the Math
Figure 17.3: Two-point calibration calculates gain (slope) and offset (intercept) from two known reference points
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)
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}
\]
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.
17.9.2 Interactive ADC Resolution Calculator
Explore how ADC bit depth and measurement range affect the smallest detectable change (resolution per count).
Question: A sensor reads 20% when the true value is 10%, and reads 80% when the true value is 90%. What is the gain (slope) coefficient?
0.75
1.0
1.33
4.0
Answer
C) 1.33
Using the formula: gain = (actual_high - actual_low) / (raw_high - raw_low)
gain = (90 - 10) / (80 - 20) = 80 / 60 = 1.333...
The sensor’s sensitivity is lower than expected (it only spans 60% when it should span 80%), so we multiply readings by 1.33 to “stretch” them to the correct range.
Question 4: Why Filter Before Calibration?
Question: In the lab code, why is the moving average filter applied BEFORE calibration, not after?
It’s faster to filter first
Filtering removes noise that would cause incorrect calibration captures
Calibration doesn’t work on filtered data
It doesn’t matter - the order is arbitrary
Answer
B) Filtering removes noise that would cause incorrect calibration captures
When capturing reference points (pressing ‘l’ or ‘h’), you want a stable, accurate reading - not a noisy instantaneous sample. The moving average filter smooths out noise, so the captured reference values are more reliable. Calibrating on noisy data would result in incorrect coefficients.
Additionally, during normal operation, filtering before calibration prevents noise from being amplified by the gain coefficient. When the calibration gain is greater than 1 (stretching the reading range), any noise in the raw signal is also multiplied by that gain factor. Filtering first reduces noise before it gets amplified.
17.9.4 Interactive 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.
Show code
viewof filterWindow = Inputs.range([1,50], {value:10,step:1,label:"Filter Window Size (N):"})viewof noiseAmplitude = Inputs.range([0,20], {value:5,step:0.5,label:"Noise Amplitude (%):"})viewof sampleInterval = Inputs.range([10,500], {value:50,step:10,label:"Sample Interval (ms):"})filterLatency = (filterWindow -1) /2noiseReduction =Math.sqrt(filterWindow)html`<div style="background: var(--bs-light, #f8f9fa); padding: 1rem; border-radius: 8px; border-left: 4px solid #16A085; margin-top: 0.5rem;"> <p><strong>Window Size:</strong> ${filterWindow} samples</p> <p><strong>Noise Reduction Factor:</strong> ${noiseReduction.toFixed(2)}x (noise reduced to ${(noiseAmplitude / noiseReduction).toFixed(2)}% from ${noiseAmplitude.toFixed(1)}%)</p> <p><strong>Group Delay:</strong> ${filterLatency.toFixed(1)} samples (${(filterLatency * sampleInterval).toFixed(0)} ms at ${sampleInterval} ms sample interval)</p> <p><strong>Settling Time:</strong> ${filterWindow} samples (${(filterWindow * sampleInterval).toFixed(0)} ms) to fully respond to a step change</p> <hr> <p style="font-size: 0.85em; color: #7F8C8D;"><em>A moving average of N samples reduces uncorrelated (white) noise by a factor of sqrt(N), but introduces a group delay of (N-1)/2 samples. Correlated noise sources (e.g., 50/60 Hz mains interference) may require specialized filters for effective rejection. For calibration capture, the lab code uses a window size of 10 and a 50 ms sample interval, which gives 3.16x noise reduction with 225 ms group delay.</em></p></div>`
17.10 Part 5: Challenge Exercises
Challenge 1: Three-Point Calibration
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.
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
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
17.11 Key Calibration Concepts Summary
Figure 17.4: Mind map of key sensor calibration concepts covered in this lab
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
Best Practices for Sensor Calibration
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
Quick Reference Card: Calibration Formulas
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. For a step-by-step calculation with real numbers, expand the “Putting Numbers to It” callout in Part 4 above.
17.11.1 Knowledge Check: Production Considerations
Question 5: EEPROM Magic Number
Question: In the EEPROM storage challenge, why use a “magic number” (0xCAFE) before the calibration data?
It’s a required EEPROM format
It detects if valid calibration data exists vs uninitialized memory
It encrypts the calibration data
It compresses the data to save space
Answer
B) It detects if valid calibration data exists vs uninitialized memory
When an ESP32 powers up for the first time, EEPROM may contain random garbage data. Without a magic number, the system might try to use garbage as calibration coefficients, causing incorrect readings. By storing a known value (0xCAFE) alongside valid calibration, you can check if calibration was ever saved. If the magic number isn’t present, you know to use default values instead.
Question 6: Drift Compensation Window
Question: Why does automatic baseline correction (ABC) track the MINIMUM reading over 24 hours, rather than the average or maximum?
Minimum values are more accurate
The sensor assumes it occasionally sees the baseline/reference condition
Maximum values consume too much memory
Averages can’t be stored in EEPROM
Answer
B) The sensor assumes it occasionally sees the baseline/reference condition
For sensors like CO2 monitors, ABC assumes the sensor is exposed to “fresh air” (baseline ~400ppm) at least once during the 24-hour period - typically overnight when the room is empty. The minimum reading approximates this baseline. If the sensor has drifted (reading 420ppm for fresh air instead of 400ppm), ABC adjusts the offset to correct for this drift. This technique only works when the assumption of periodic baseline exposure is valid.
Question 7: Selecting Reference Points
Question: You need to calibrate a soil moisture sensor that will measure between 20% and 80% moisture in a greenhouse. Which reference points should you use?
0% and 100% (full range)
50% and 60% (middle of expected range)
15% and 85% (bracketing the expected range)
20% and 80% (exact boundaries of expected range)
Answer
C) 15% and 85% (bracketing the expected range)
You want reference points that bracket (surround) your expected measurement range, but with some margin. Using 15% and 85% provides calibration slightly outside your 20-80% operating range, ensuring good accuracy throughout. Using exact boundaries (20%, 80%) could cause edge-case errors. Using the middle of the range (50%, 60%) provides poor calibration at the extremes. Using 0% and 100% might be impractical to achieve with soil moisture.
Common Mistake: Reference Points Too Close Together
The Mistake: An engineer performs two-point calibration on a soil moisture sensor using reference points at 45% and 55% moisture (only 10% apart). After deployment, the sensor shows large errors at 10% and 90% moisture because small inaccuracies in the closely-spaced reference measurements were amplified across the full range.
Why It Happens: Two-point calibration creates a linear equation (y = mx + b) using two measurements. When those measurements are close together, any small error in the reference measurement or sensor reading gets amplified when extrapolating to values far from the calibration range.
Optional Math: Why Close Reference Points Amplify Error
Calibration points: 45% and 55%.
Correct raw readings: 450 and 550 on a 0-1000 ADC scale.
Correct gain: (55 - 45) / (550 - 450) = 0.1.
If the high point is off by only 10 ADC counts, the gain becomes (55 - 45) / (560 - 450) = 0.0909.
At the low extreme, 0% becomes 4.1%.
At the high extreme, 100% becomes 95.0%.
The tiny reference error becomes a large endpoint error because the calibration span was too narrow.
Real Numbers: A 10 ADC count error (1% of full scale) in your reference point, when calibration range is only 100 counts, produces up to 5% error at the measurement extremes. The narrow calibration range acts as a lever arm amplifying tiny reference errors into significant measurement errors.
The Fix: Use calibration points that bracket your expected measurement range with margin:
Bad practice: Greenhouse needs 20-80% moisture, but calibration points are only 45% and 55%. The 10% calibration span covers only 17% of the operating range.
Good practice: Greenhouse needs 20-80% moisture, and calibration points are 10% and 90%. The 80% span brackets the full operating range with margin.
Industry Rule of Thumb: Calibration span should be at least 1.2× your measurement range, ideally centered on your operating range. For a 20-80% application, calibrate at 10% and 90%.
Why Wide Span Matters:
Error division: 10 ADC count error ÷ 800 ADC span = 1.25% relative error in gain (vs 10% relative error with a 100-count span)
Interpolation not extrapolation: Measuring within calibration range (interpolation) is accurate; measuring outside (extrapolation) magnifies errors
Sensor non-linearity: Most sensors are most linear in their mid-range; calibrating at extremes captures any non-linearity
Verification Test: After calibration, check several points across the full range: - 0% moisture: Read dry sensor in air - 25% moisture: Use calibrated reference soil - 50% moisture: Half-saturated reference - 75% moisture: Nearly saturated reference - 100% moisture: Fully submerged in water
If errors at 0% and 100% exceed ±2%, your calibration points were too close together.
Explore how the width of your calibration span affects error amplification. Adjust the calibration points and reference error to see the impact on measurement accuracy at the extremes.