Every sensor needs calibration because manufacturing variations cause individual sensors to read slightly differently. Two-point calibration (using a known low and high reference value) corrects both offset and gain errors with a simple linear equation: \(y = m \cdot x + b\). Precision (repeatability) cannot be improved by calibration – only accuracy (closeness to true value) can.
Key Concepts
Wokwi Simulator: Browser-based electronics simulator supporting ESP32, Arduino, and Raspberry Pi Pico with real-time serial monitor and component libraries — no hardware installation required
Potentiometer as Sensor Substitute: In Wokwi calibration labs, a potentiometer simulates a sensor with deliberate offset and gain errors; turning the knob changes ADC input voltage, mimicking real sensor output variation
Serial Plotter: Tool that graphs serial-printed values in real time; essential for visualizing the difference between raw and calibrated sensor readings during calibration exercises
Calibration Workflow: Capture reading at low reference, capture reading at high reference, calculate gain = (high_ref - low_ref) / (high_raw - low_raw), calculate offset = low_ref - gain x low_raw, apply to future readings
EEPROM.put / EEPROM.get: Arduino EEPROM library functions for writing and reading any data type at a given memory address; used to persist calibration coefficients across power cycles
diagram.json: The Wokwi circuit definition file specifying components and wire connections; edit this file to modify the simulated circuit
Two-Point vs. Multi-Point Calibration: Two-point calibration corrects offset and linear gain errors and suffices for most IoT sensors. Multi-point polynomial calibration is needed when the sensor has significant nonlinearity
Calibration Validation: After applying coefficients, test at intermediate values to detect nonlinearity errors that two-point calibration cannot correct
29.1 Learning Objectives
Time: ~45 min | Level: Advanced | Code: P06.C10.LAB01
By the end of this lab, you will be able to:
Implement two-point calibration to correct offset and gain errors in analog sensors
Build multi-point calibration tables with linear interpolation for non-linear sensors
Apply temperature compensation to reduce drift-related measurement errors
Distinguish accuracy from precision through practical measurement exercises
Verify calibration quality using statistical metrics (RMSE, R-squared)
Store calibration coefficients in non-volatile memory for persistence across power cycles
For Beginners: Calibration Lab
This lab teaches you how to make sensor readings more accurate by comparing them against known reference values – like adjusting a kitchen scale by first weighing something you know is exactly one kilogram. You will use an online simulator to practice the technique safely, learning to correct both constant offsets (always reading too high) and scaling errors (reading more wrong as values increase).
29.2 Why Calibration Matters
Every sensor comes from manufacturing with slight variations. A temperature sensor rated at +/-0.5 C accuracy might actually read 2 C too high consistently. Without calibration, your IoT system makes decisions based on incorrect data.
Figure 29.1: Calibration transforms raw sensor readings into accurate measurements by applying mathematical corrections based on known reference values.
Real-World Impact Example:
Scenario
Uncalibrated Result
Calibrated Result
Cost Impact
Smart thermostat
AC runs 20% more
Optimal comfort
$180/year savings
Cold chain monitoring
False vaccine spoilage alarm
Accurate 2-8 C tracking
$500k product saved
Industrial process
Quality defects
In-spec production
$50k/month savings
Try It: Offset and Gain Error Explorer
Adjust the offset and gain sliders to see how each type of error distorts sensor readings. The ideal line (green) shows perfect 1:1 mapping, while the error line (orange) shows how your sensor actually reads. Notice how offset shifts the entire line up or down, while gain tilts it.
Analog-to-digital conversion concepts (ADC resolution, voltage dividers)
Linear equations (y = mx + b) for calibration math
29.4 Wokwi Simulator
About Wokwi
Wokwi is a free online simulator for Arduino, ESP32, and other microcontrollers. This lab uses simulated sensors to demonstrate calibration concepts. The techniques you learn apply directly to real hardware with physical sensors.
Launch the simulator below and copy the calibration code to explore sensor calibration interactively.
Simulator Tips
Click the + button to add components (search for “Potentiometer” to simulate a sensor)
Connect the potentiometer middle pin to GPIO 34 (ADC input)
Use the Serial Monitor to view calibration output
Click “Start Simulation” (green play button) to run the code
Adjust the potentiometer during simulation to test calibration
29.5 Circuit Setup
For this lab, we simulate a sensor using a potentiometer. In real applications, replace this with your actual sensor (thermistor, pressure sensor, etc.).
Figure 29.2: Wiring diagram: Connect potentiometer to ESP32 ADC pin 34 for sensor simulation. The potentiometer simulates varying sensor output from 0V (0% rotation) to 3.3V (100% rotation).
29.5.1 Wokwi diagram.json
Copy this JSON into the diagram.json tab in Wokwi:
This code implements two-point calibration with an interactive Serial menu and an accuracy-vs-precision demonstration. Copy it into the Wokwi editor:
/* * SENSOR CALIBRATION LAB - ESP32 Wokwi Simulation * * This lab demonstrates sensor calibration techniques: * 1. Two-Point Calibration (offset + gain correction) * 2. Accuracy vs Precision Demonstration * 3. Interactive calibration wizard via Serial menu * * Hardware Setup (in Wokwi): * - Potentiometer connected to GPIO 34 (simulates sensor) * - Built-in LED on GPIO 2 (status indicator) * - Serial input for menu commands */#include <Arduino.h>// Pin Definitionsconstint SENSOR_PIN =34;constint LED_PIN =2;constint ADC_MAX_VALUE =4095;// Calibration Configurationconstint SAMPLES_PER_READING =10;// Two-Point Calibration Structurestruct TwoPointCalibration {float offset;float gain;bool isValid;};TwoPointCalibration twoPointCal ={0.0,1.0,false};// Read raw ADC with averagingint readRawSensor(int pin,int samples){long sum =0;for(int i =0; i < samples; i++){ sum += analogRead(pin); delay(10);}return sum / samples;}// Apply two-point calibrationfloat applyTwoPointCalibration(int rawValue){if(!twoPointCal.isValid){return(rawValue /(float)ADC_MAX_VALUE)*100.0;}return(rawValue * twoPointCal.gain)+ twoPointCal.offset;}// Perform two-point calibrationvoid performTwoPointCalibration(int rawLow,float trueLow,int rawHigh,float trueHigh){ Serial.println("\n=== TWO-POINT CALIBRATION ===");if(rawHigh == rawLow){ Serial.println("ERROR: Low and high values cannot be equal!");return;} twoPointCal.gain =(trueHigh - trueLow)/(float)(rawHigh - rawLow); twoPointCal.offset = trueLow -(twoPointCal.gain * rawLow); twoPointCal.isValid =true; Serial.println("\nCalibration Equation:"); Serial.printf(" y = %.6f * x + %.4f\n", twoPointCal.gain, twoPointCal.offset);// Verifyfloat verifyLow = applyTwoPointCalibration(rawLow);float verifyHigh = applyTwoPointCalibration(rawHigh); Serial.printf("\nVerification: Low=%.2f (expected %.2f), High=%.2f (expected %.2f)\n", verifyLow, trueLow, verifyHigh, trueHigh);}// Accuracy vs Precision demonstrationvoid demonstrateAccuracyVsPrecision(){ Serial.println("\n=== ACCURACY vs PRECISION ===");constint NUM_SAMPLES =20;float readings[NUM_SAMPLES];float sum =0.0; Serial.println("Collecting 20 samples...");for(int i =0; i < NUM_SAMPLES; i++){int raw = readRawSensor(SENSOR_PIN, SAMPLES_PER_READING); readings[i]= applyTwoPointCalibration(raw); sum += readings[i]; Serial.printf(" Sample %2d: %.2f\n", i+1, readings[i]); delay(100);}// Calculate statisticsfloat mean = sum / NUM_SAMPLES;float variance =0;for(int i =0; i < NUM_SAMPLES; i++){ variance +=(readings[i]- mean)*(readings[i]- mean);}float stdDev = sqrt(variance / NUM_SAMPLES); Serial.println("\n--- PRECISION (repeatability) ---"); Serial.printf(" Mean: %.4f\n", mean); Serial.printf(" Std Dev: %.4f (lower = more precise)\n", stdDev);if(stdDev <0.5){ Serial.println(" PRECISION: EXCELLENT");}elseif(stdDev <1.0){ Serial.println(" PRECISION: GOOD");}else{ Serial.println(" PRECISION: FAIR - consider filtering");}}// Interactive menuvoid displayMenu(){ Serial.println("\n=== CALIBRATION LAB MENU ==="); Serial.println(" r - Read current sensor value"); Serial.println(" 2 - Two-point calibration wizard"); Serial.println(" a - Accuracy vs Precision demo"); Serial.println(" c - Clear calibration"); Serial.println(" h - Help (this menu)");}void processCommand(){if(Serial.available()>0){char cmd = Serial.read();while(Serial.available()) Serial.read();// Clear bufferswitch(cmd){case'r':case'R':{int raw = readRawSensor(SENSOR_PIN, SAMPLES_PER_READING);float calibrated = applyTwoPointCalibration(raw); Serial.printf("\nRaw: %d, Calibrated: %.2f\n", raw, calibrated);break;}case'2':{ Serial.println("\n=== TWO-POINT CALIBRATION WIZARD ==="); Serial.println("Set potentiometer to LOW position, press any key...");while(!Serial.available()) delay(100);while(Serial.available()) Serial.read();int rawLow = readRawSensor(SENSOR_PIN, SAMPLES_PER_READING); Serial.printf("Low raw: %d\n", rawLow); Serial.println("Set potentiometer to HIGH position, press any key...");while(!Serial.available()) delay(100);while(Serial.available()) Serial.read();int rawHigh = readRawSensor(SENSOR_PIN, SAMPLES_PER_READING); Serial.printf("High raw: %d\n", rawHigh); performTwoPointCalibration(rawLow,0.0, rawHigh,100.0);break;}case'a':case'A': demonstrateAccuracyVsPrecision();break;case'c':case'C': twoPointCal.isValid =false; twoPointCal.gain =1.0; twoPointCal.offset =0.0; Serial.println("\nCalibration cleared.");break;case'h':case'H': displayMenu();break;default:if(cmd !='\n'&& cmd !='\r'){ Serial.printf("Unknown command: '%c'\n", cmd);}}}}void setup(){ Serial.begin(115200); delay(1000); Serial.println("\n========================================"); Serial.println(" SENSOR CALIBRATION LAB"); Serial.println(" ESP32 Wokwi Simulation"); Serial.println("========================================"); analogReadResolution(12); pinMode(LED_PIN, OUTPUT); displayMenu();}void loop(){ processCommand();// Blink LED to show system runningstaticunsignedlong lastBlink =0;if(millis()- lastBlink >1000){ digitalWrite(LED_PIN,!digitalRead(LED_PIN)); lastBlink = millis();} delay(10);}
29.7 Lab Exercises
Complete the following exercises to master sensor calibration:
Exercise 1: Two-Point Calibration (15 min)
Objective: Perform basic two-point calibration and verify results.
Steps:
Run the code in Wokwi simulator
Type r to read the raw sensor value
Set potentiometer to minimum position (turn fully counterclockwise), note the raw value
Type 2 to start two-point calibration wizard
Follow prompts to collect low and high calibration points
Type r again to verify calibrated output
Success Criteria:
Calibration equation displayed correctly
Low position reads ~0, high position reads ~100
Questions to Answer:
What calibration equation did you obtain?
How much did accuracy improve after calibration?
Exercise 2: Accuracy vs Precision Analysis (10 min)
Objective: Understand the critical difference between accuracy and precision.
Steps:
Type a to run the accuracy vs precision demonstration
Observe the statistical metrics (mean, std dev)
Note the difference between precision (std dev) and accuracy (error from true)
Reflection Questions:
Can a sensor be precise but not accurate? Give an example.
Can calibration fix precision problems? Why or why not?
Which is more important for trend detection: accuracy or precision?
Try It: Accuracy vs Precision Target Visualizer
Explore the difference between accuracy and precision using a classic target analogy. Accuracy is how close the average is to the bullseye (true value), while precision is how tightly clustered the measurements are. Adjust the sliders and click “Generate Shots” to see new random measurements.
Where TEMP_COEFFICIENT represents the sensor’s temperature sensitivity (for example, -0.01 units per degree C above the reference temperature).
Check your understanding of the calibration math before continuing:
29.8 Expected Outcomes
After completing this lab, you should observe:
Metric
Before Calibration
After Two-Point
Accuracy Error
2-5%
<0.5%
Calibration Equation
N/A
y = gain*x + offset
Verification
N/A
Both points match expected
29.8.1 Try It: Two-Point Calibration Calculator
Use this interactive calculator to experiment with two-point calibration. Enter your raw ADC readings at two known reference points and see the calibration equation computed instantly.
Show code
viewof cal_raw_low = Inputs.range([0,4095], {value:410,step:1,label:"Raw reading at LOW reference"})viewof cal_true_low = Inputs.range([-50,200], {value:0.0,step:0.1,label:"True value at LOW reference"})viewof cal_raw_high = Inputs.range([0,4095], {value:3685,step:1,label:"Raw reading at HIGH reference"})viewof cal_true_high = Inputs.range([-50,200], {value:100.0,step:0.1,label:"True value at HIGH reference"})viewof cal_test_raw = Inputs.range([0,4095], {value:2048,step:1,label:"Test raw reading to calibrate"})
Sammy the Sensor thought he was measuring temperature perfectly, but when Max the Microcontroller compared Sammy’s reading to an ice bath (which should be exactly 0 degrees), Sammy said “2 degrees!” That is called an offset – Sammy was always reading a little too high.
“No worries,” said Lila the LED. “We just need to calibrate you!” They dunked Sammy in ice water and wrote down what he said (2 degrees instead of 0). Then they dunked him in boiling water and wrote that down too (98.5 degrees instead of 100).
Now Max could do some math: every time Sammy says a number, Max adjusts it using those two reference points, like drawing a straight line between them. “I am calibrated!” Sammy cheered. “Now my readings match reality!”
Bella the Battery reminded everyone: “But calibration only fixes if Sammy is consistently wrong. If he is randomly jumpy (saying different numbers each time), that is a precision problem – we need a filter for that, not calibration!”
Worked Example: Calibrating a Load Cell for Beehive Monitoring
Scenario: You’re building a smart beehive scale to track honey production. The system uses a 50kg load cell with HX711 amplifier. Raw ADC readings don’t directly correspond to weight — calibration is required to convert raw counts to kilograms.
Hardware Setup:
Load cell: 50kg capacity, 2 mV/V sensitivity
HX711: 24-bit ADC, 128× gain
ESP32: Reading HX711 via bit-banging protocol
Calibration weights: 0kg (empty), 5kg, 10kg, 20kg known masses (covering the expected 0-20 kg measurement range for typical hive weight changes)
Step 1: Collect Raw Readings
#include "HX711.h"HX711 scale;void setup(){ Serial.begin(115200); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); scale.set_scale();// No scale factor yet scale.tare();// Reset to zero}void collectCalibrationData(){ Serial.println("=== CALIBRATION DATA COLLECTION ==="); Serial.println("Remove all weight. Press any key..."); waitForKeypress();long raw_0kg = scale.get_units(10);// Average 10 readings Serial.print("0 kg → Raw: "); Serial.println(raw_0kg); Serial.println("Place 5 kg weight. Press any key..."); waitForKeypress();long raw_5kg = scale.get_units(10); Serial.print("5 kg → Raw: "); Serial.println(raw_5kg); Serial.println("Place 10 kg weight. Press any key..."); waitForKeypress();long raw_10kg = scale.get_units(10); Serial.print("10 kg → Raw: "); Serial.println(raw_10kg); Serial.println("Place 20 kg weight. Press any key..."); waitForKeypress();long raw_20kg = scale.get_units(10); Serial.print("20 kg → Raw: "); Serial.println(raw_20kg);}
Example Output:
=== CALIBRATION DATA COLLECTION ===
0 kg → Raw: 150
5 kg → Raw: 42650
10 kg → Raw: 85150
20 kg → Raw: 170150
Step 2: Two-Point Calibration (Simple Method)
Using 0kg and 20kg points:
// Calibration dataconstlong RAW_AT_0KG =150;constlong RAW_AT_20KG =170150;// Calculate calibration factorfloat calculateCalibrationFactor(){float raw_span = RAW_AT_20KG - RAW_AT_0KG;float kg_span =20.0-0.0;float calibration_factor = raw_span / kg_span; Serial.print("Calibration factor: "); Serial.print(calibration_factor); Serial.println(" raw units per kg");return calibration_factor;}void setup(){// ... initialization code ...float cal_factor = calculateCalibrationFactor(); scale.set_scale(cal_factor); scale.tare();// Zero the scale}void loop(){float weight_kg = scale.get_units(5);// Average 5 readings Serial.print("Weight: "); Serial.print(weight_kg,2); Serial.println(" kg"); delay(1000);}
Putting Numbers to It:
The two-point calibration formula converts raw ADC counts to physical weight using a linear equation.
This perfectly matches the 10 kg calibration weight, verifying our calibration is accurate.
Calculation:
Calibration factor = (170150 - 150) / (20 - 0)
= 170000 / 20
= 8500 raw units per kg
Step 3: Verify Two-Point Calibration
Place known weights and compare measured vs actual:
Actual Weight
Raw Reading
Calculated Weight
Error
0 kg (cal point)
150
0.00 kg
0.00 kg
5 kg
42,680
5.04 kg
+0.04 kg
10 kg
85,100
9.99 kg
-0.01 kg
20 kg (cal point)
170,150
20.00 kg
0.00 kg
The calibration points (0 kg and 20 kg) always show zero error because they were used to compute the equation. Intermediate points may show small residual errors due to minor sensor non-linearity.
Actual Weight
Raw Reading
Calculated Weight
Error
7.5 kg
63,920
7.50 kg
+0.00 kg
15 kg
127,700
15.01 kg
+0.01 kg
Excellent – the load cell is highly linear, with errors well below 0.1%. Two-point calibration is sufficient.
Step 4: Multi-Point Calibration (Advanced)
For sensors with non-linearity, use interpolation between multiple points:
See how ambient temperature affects sensor readings and how compensation corrects the error. Adjust the actual weight and ambient temperature to observe drift, then toggle compensation on or off to see the correction in action.
Show code
viewof tc_actual_weight = Inputs.range([0,50], {value:10,step:0.5,label:"Actual weight (kg)"})viewof tc_ambient_temp = Inputs.range([-10,50], {value:25,step:0.5,label:"Ambient temperature (C)"})viewof tc_ref_temp = Inputs.range([15,35], {value:25,step:1,label:"Reference temperature (C)"})viewof tc_coeff = Inputs.range([0.0001,0.001], {value:0.0002,step:0.0001,label:"Temp coefficient (%/C)"})viewof tc_compensate = Inputs.toggle({label:"Apply temperature compensation",value:true})
=== CALIBRATION VERIFICATION ===
Place 5.0 kg. Press key...
Measured: 5.002 kg
Place 10.0 kg. Press key...
Measured: 9.998 kg
Place 15.0 kg. Press key...
Measured: 15.003 kg
Place 20.0 kg. Press key...
Measured: 19.996 kg
RMSE: 0.0032 kg
✓ EXCELLENT calibration (< 50g error)
R² (linearity): 0.9999
✓ EXCELLENT linearity
Try It: Calibration Quality Metrics Calculator
Enter your measured vs actual values at several test points to compute RMSE and R-squared. These metrics tell you how well your calibration is performing. Add up to 6 test points; unused points (actual = 0, measured = 0) are excluded.
After two-point calibration: ±50g accuracy (0.1% error)
With temperature compensation: ±25g accuracy (0.05% error)
Beehive monitoring: Tracks daily honey production to ±10g resolution
Key Lessons:
Always use known reference weights - bathroom scale not accurate enough
Bracket your measurement range - calibrate at 0 and 20kg for 0-20kg application
Verify with independent points - test at 5, 10, 15kg to check linearity
Store calibration persistently - don’t force users to recalibrate after power cycle
Temperature matters - ±15°C swing causes 0.3% error without compensation
Multi-point helps non-linear sensors - but load cells are usually very linear
🏷️ Label the Diagram
Code Challenge
29.10 Summary
This calibration lab covered professional sensor calibration techniques through hands-on Wokwi simulation:
Two-point calibration corrects both offset (y-intercept) and gain (slope) errors using two known reference values – sufficient for linear sensors like load cells
Multi-point calibration with linear interpolation handles non-linear sensors by dividing the range into segments
Calibration equation\(y = \text{gain} \times x + \text{offset}\) transforms raw ADC readings to calibrated physical units
Accuracy vs precision are distinct: precision is repeatability (improved by filtering), accuracy is closeness to truth (improved by calibration)
Calibration verification using RMSE and R-squared confirms that corrections are working correctly
NVS storage (ESP32 Preferences library) preserves calibration coefficients across power cycles, avoiding repeated manual recalibration
Temperature compensation can further reduce drift-related errors in field-deployed sensors
Common Pitfalls
1. Not Waiting for Sensor Stabilization
Capturing a calibration reference point immediately after changing the stimulus gives an unstable reading. Wait for the serial monitor to show stable, consistent values (2-5 seconds) before pressing the calibration capture button.
2. Accidentally Overwriting Calibration in Flash
Using EEPROM with incorrect parameters or writing at the wrong address can corrupt stored coefficients. Always print retrieved coefficients on startup and validate they are within the expected physical range before using them.
3. Wokwi vs. Real Hardware Discrepancy
The Wokwi ADC is ideal with no noise or nonlinearity. Real ESP32 ADCs have known nonlinearity near supply rails. Calibration from Wokwi simulation will not transfer directly to physical hardware without recalibration.
4. Single Window Size for All Filtering Needs
A window of 10-20 samples works well for a slow temperature sensor. For a fast vibration sensor, a large window destroys the signal. Match the moving average window size to the sensor’s actual time constant.
29.11 What’s Next?
Now that you can calibrate sensors for accuracy, explore related topics to build production-quality IoT measurement systems.