%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#fff'}}}%%
flowchart LR
subgraph Uncalibrated["Without Calibration"]
A1[Sensor reads 25C] --> B1[Actual temp: 23C]
B1 --> C1[Error: +2C]
C1 --> D1[Wrong decisions]
end
subgraph Calibrated["With Calibration"]
A2[Sensor reads 25C] --> B2[Apply correction]
B2 --> C2[Corrected: 23C]
C2 --> D2[Accurate data]
end
style Uncalibrated fill:#FFEBEE,stroke:#E74C3C
style Calibrated fill:#E8F5E9,stroke:#27AE60
528 Sensor Calibration Lab (Wokwi Simulation)
528.1 Learning Objectives
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
- Detect and compensate for sensor drift over time using running baselines
- 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
528.2 Why Calibration Matters
Every sensor comes from manufacturing with slight variations. A temperature sensor rated at +/-0.5C accuracy might actually read 2C too high consistently. Without calibration, your IoT system makes decisions based on incorrect data.
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-8C tracking | $500k product saved |
| Industrial process | Quality defects | In-spec production | $50k/month savings |
528.3 Prerequisites
Before starting this lab, ensure you understand:
- Basic Arduino/ESP32 programming (setup, loop, Serial.print)
- Analog-to-digital conversion concepts (ADC resolution, voltage dividers)
- Linear equations (y = mx + b) for calibration math
528.4 Wokwi Simulator
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.
- 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
528.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.).
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#fff'}}}%%
flowchart TB
subgraph ESP32["ESP32 DevKit"]
ADC["GPIO 34 (ADC1_CH6)"]
V3["3.3V"]
GND["GND"]
LED1["GPIO 2 (Built-in LED)"]
BTN["GPIO 0 (Boot Button)"]
end
subgraph POT["Potentiometer (10kOhm)"]
P1["Pin 1 (VCC)"]
P2["Pin 2 (Wiper/Signal)"]
P3["Pin 3 (GND)"]
end
V3 --> P1
P2 --> ADC
P3 --> GND
style ESP32 fill:#2C3E50,color:#fff
style POT fill:#16A085,color:#fff
528.5.1 Wokwi diagram.json
Copy this JSON into the diagram.json tab in Wokwi:
{
"version": 1,
"author": "IoT Class",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-esp32-devkit-v1", "id": "esp", "top": 0, "left": 0, "attrs": {} },
{
"type": "wokwi-potentiometer",
"id": "pot1",
"top": 150,
"left": 100,
"attrs": { "value": "50" }
}
],
"connections": [
[ "esp:3V3", "pot1:VCC", "red", [ "v:30", "h:50" ] ],
[ "esp:GND.1", "pot1:GND", "black", [ "v:40", "h:80" ] ],
[ "pot1:SIG", "esp:34", "green", [ "h:0" ] ]
]
}528.6 Complete Calibration Code
This comprehensive code demonstrates all calibration techniques. Copy it into the Wokwi editor:
/*
* SENSOR CALIBRATION LAB - ESP32 Wokwi Simulation
*
* This lab demonstrates professional sensor calibration techniques:
* 1. Two-Point Calibration (offset + gain correction)
* 2. Multi-Point Calibration with Linear Interpolation
* 3. Drift Detection and Compensation
* 4. Accuracy vs Precision Demonstration
* 5. Calibration Verification and Validation
*
* Hardware Setup (in Wokwi):
* - Potentiometer connected to GPIO 34 (simulates sensor)
* - Built-in LED on GPIO 2 (status indicator)
* - Boot button on GPIO 0 (calibration trigger)
*/
#include <Arduino.h>
#include <Preferences.h> // For storing calibration in NVS
// Pin Definitions
const int SENSOR_PIN = 34;
const int LED_PIN = 2;
const int ADC_MAX_VALUE = 4095;
const float ADC_MAX_VOLTAGE = 3.3;
// Calibration Configuration
const int NUM_CALIBRATION_POINTS = 5;
const int SAMPLES_PER_READING = 10;
const float REFERENCE_VALUES[] = {0.0, 25.0, 50.0, 75.0, 100.0};
// Two-Point Calibration Structure
struct TwoPointCalibration {
float offset;
float gain;
bool isValid;
};
TwoPointCalibration twoPointCal = {0.0, 1.0, false};
Preferences preferences;
// Read raw ADC with averaging
int 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 calibration
float applyTwoPointCalibration(int rawValue) {
if (!twoPointCal.isValid) {
return (rawValue / (float)ADC_MAX_VALUE) * 100.0;
}
return (rawValue * twoPointCal.gain) + twoPointCal.offset;
}
// Perform two-point calibration
void 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);
// Verify
float 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 demonstration
void demonstrateAccuracyVsPrecision() {
Serial.println("\n=== ACCURACY vs PRECISION ===");
const int 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 statistics
float 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");
} else if (stdDev < 1.0) {
Serial.println(" PRECISION: GOOD");
} else {
Serial.println(" PRECISION: FAIR - consider filtering");
}
}
// Interactive menu
void 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 buffer
switch (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 running
static unsigned long lastBlink = 0;
if (millis() - lastBlink > 1000) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
lastBlink = millis();
}
delay(10);
}528.7 Lab Exercises
Complete the following exercises to master sensor calibration:
528.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 |
- Two-point calibration fixes offset and gain errors - sufficient for linear sensors
- Multi-point calibration handles non-linearity using interpolation tables
- Drift detection monitors sensor health over time
- Precision (repeatability) cannot be improved by calibration - only accuracy can
- Store calibration in NVS to persist across power cycles
- Verify calibration using statistical metrics (RMSE, R-squared)
528.9 Knowledge Check
Question 1: You calibrate a temperature sensor using ice bath (0C reads as 1.5C) and boiling water (100C reads as 98.5C). What is the calibration slope?
Explanation: Using two-point calibration formula: Slope = (True_High - True_Low) / (Raw_High - Raw_Low) = (100 - 0) / (98.5 - 1.5) = 100 / 97 = 1.031
Question 2: What is sensor hysteresis?
Explanation: Hysteresis means the sensor gives different outputs for the same input depending on whether the input is increasing or decreasing. Example: A pressure sensor reads 100 kPa when rising but 99.5 kPa when falling from above.
Question 3: Which is an advantage of digital sensors (I2C, SPI) over analog sensors?
Explanation: Digital sensors are immune to electrical noise because they send discrete digital signals (0s and 1s) rather than analog voltages. A 10m I2C cable works fine, while an analog sensor would have significant voltage drop and noise pickup.
528.10 Summary
This calibration lab covered professional sensor calibration techniques:
- Two-point calibration corrects both offset (y-intercept) and gain (slope) errors using two known reference values
- Calibration equation y = gain * raw + offset transforms raw ADC readings to calibrated units
- Accuracy vs precision distinction: precision is repeatability (improved by filtering), accuracy is correctness (improved by calibration)
- Wokwi simulation provides hands-on practice without physical hardware
- NVS storage preserves calibration coefficients across power cycles
528.11 What’s Next?
Return to the sensor labs overview to review all topics, or continue to actuators to learn about output devices.
528.12 See Also
- Temperature Sensor Labs - Temperature sensing fundamentals
- Best Practices & Labs - Implementation guidelines
- Sensor Fundamentals - Comprehensive sensor characteristics