Sensor calibration corrects manufacturing variations so readings match reality. One-point calibration fixes constant offset errors (sensor always reads 2.3°C high). Two-point calibration fixes both offset and scale errors using two reference values (ice water at 0°C, boiling water at 100°C). Store calibration coefficients in non-volatile memory and recalibrate periodically since sensors drift over time.
Key Concepts
Calibration: the process of comparing a sensor’s output against a known reference standard and applying correction factors to make readings accurate
Offset Error: a constant additive error where the sensor always reads too high or too low by the same amount regardless of the measured value; corrected by one-point calibration
Gain (Scale) Error: a proportional error where the sensor’s reading diverges from truth at a rate proportional to the measured value; requires at least two reference points to correct
One-Point Calibration: a calibration technique using a single reference value to correct offset error; appropriate when the sensor’s gain is known to be accurate
Two-Point Calibration: a calibration technique using two reference values to simultaneously correct offset and gain errors across a defined measurement range
Reference Standard: a known, traceable measurement source (ice-water bath at 0°C, boiling water at 100°C, certified weights) used as ground truth during calibration
Drift: the gradual change in a sensor’s calibration over time due to ageing, mechanical stress, or environmental exposure; requires periodic recalibration to maintain accuracy
Non-Volatile Memory (NVM): storage (EEPROM, flash) that retains calibration coefficients after power-off so the sensor does not need to be recalibrated every time it starts up
Learning Objectives
After completing this chapter, you will be able to:
Justify why sensor calibration is necessary for production IoT systems and identify which error type each technique corrects
Execute one-point and two-point calibration procedures using reference standards
Implement calibration routines in MicroPython on ESP32 and store coefficients in non-volatile memory
Design a calibration maintenance schedule for deployed sensors based on sensor type and environment
For Beginners: Sensor Calibration
Calibration is like setting your bathroom scale to zero before weighing yourself. Every sensor has small manufacturing differences, so two identical temperature sensors might give slightly different readings in the same room. Calibration corrects these differences by comparing the sensor’s reading against a known reference value and applying a simple correction formula to get accurate results.
Even the best sensors have manufacturing variations. Two DHT22 sensors from the same batch might read 0.5°C apart when measuring the same temperature. Calibration corrects these individual differences.
Types of Sensor Errors:
Error Type
Description
Fixable with Calibration?
Offset (Bias)
Constant error at all values
Yes - one-point calibration
Gain (Scale)
Error proportional to reading
Yes - two-point calibration
Nonlinearity
Curved error across range
Partially - multi-point calibration
Noise
Random variation
No - requires filtering
Drift
Error changes over time
Requires periodic recalibration
18.3 One-Point Calibration (Offset Correction)
When to use: Sensor reads consistently high or low by a fixed amount.
Method:
Measure a known reference value
Calculate the offset: offset = sensor_reading - true_value
Subtract offset from all future readings
# One-Point Calibration Example# Step 1: Measure reference (ice water = 0C)reference_temp =0.0sensor_reading =2.3# Sensor reads 2.3C in ice water# Step 2: Calculate offsetoffset = sensor_reading - reference_temp # offset = 2.3# Step 3: Apply correctiondef read_calibrated_temp(): raw = sensor.read()return raw - offset # Subtract 2.3 from all readings
18.3.1 Interactive: One-Point Offset Calculator
Show code
viewof ref_1pt = Inputs.range([-50,150], {value:0,step:0.1,label:"Known Reference Value (°C)"})viewof raw_1pt = Inputs.range([-50,150], {value:2.3,step:0.1,label:"Sensor Reading at Reference (°C)"})viewof test_1pt = Inputs.range([-50,150], {value:25.0,step:0.1,label:"Test Raw Reading (°C)"})
Show code
offset_1pt = raw_1pt - ref_1ptcorrected_1pt = test_1pt - offset_1pthtml`<div style="background: var(--bs-light, #f8f9fa); padding: 1rem; border-radius: 8px; border-left: 4px solid #16A085; margin-top: 0.5rem;"><p><strong>Offset:</strong> ${offset_1pt.toFixed(2)}°C ${Math.abs(offset_1pt) <0.01?"(sensor is perfectly calibrated at this point)":`(sensor reads ${offset_1pt >0?"high":"low"} by ${Math.abs(offset_1pt).toFixed(2)}°C)`}</p><p><strong>Correction:</strong> corrected = raw − ${offset_1pt.toFixed(2)}</p><hr style="margin: 0.5rem 0;"><p><strong>Test result:</strong> Raw ${test_1pt.toFixed(1)}°C → Corrected <strong>${corrected_1pt.toFixed(2)}°C</strong></p></div>`
18.4 Two-Point Calibration (Offset + Gain Correction)
When to use: Sensor has both offset error AND scale error.
Method:
Measure two known reference values (low and high)
Calculate scale and offset
Apply linear correction: corrected = raw * scale + offset
Two-point calibration corrects both gain and offset errors. Let \(R\) = reference (true) value and \(M\) = measured (raw sensor) value. Given ice water (0°C) reads 1.8°C and boiling water (100°C) reads 98.5°C:
Corrected reading: \(T_{corrected} = T_{raw} \times 1.034 - 1.86\). At room temperature (sensor reads 24.5°C): \(T_{corrected} = 24.5 \times 1.034 - 1.86 = 23.5°C\). Without calibration, the 1.0°C error would violate many HVAC control requirements.
Enter your sensor’s raw readings at two known reference points to compute the calibration coefficients and test a correction.
Show code
viewof ref_low_val = Inputs.range([-50,50], {value:0,step:0.1,label:"Reference Low (°C)"})viewof ref_high_val = Inputs.range([0,200], {value:100,step:0.1,label:"Reference High (°C)"})viewof raw_low_val = Inputs.range([-50,50], {value:1.8,step:0.1,label:"Raw Reading at Low Ref (°C)"})viewof raw_high_val = Inputs.range([0,200], {value:98.5,step:0.1,label:"Raw Reading at High Ref (°C)"})viewof test_raw = Inputs.range([-50,200], {value:24.5,step:0.1,label:"Test Raw Reading (°C)"})
Show code
cal_denom = raw_high_val - raw_low_valcal_valid =Math.abs(cal_denom) >0.001cal_scale = cal_valid ? (ref_high_val - ref_low_val) / cal_denom :1cal_offset = cal_valid ? ref_low_val - (raw_low_val * cal_scale) :0corrected_test = test_raw * cal_scale + cal_offsethtml`<div style="background: var(--bs-light, #f8f9fa); padding: 1rem; border-radius: 8px; border-left: 4px solid #3498DB; margin-top: 0.5rem;">${cal_valid ?html`<p><strong>Scale factor:</strong> ${cal_scale.toFixed(4)}</p><p><strong>Offset:</strong> ${cal_offset.toFixed(2)}°C</p><p><strong>Correction formula:</strong> corrected = raw × ${cal_scale.toFixed(4)} + (${cal_offset.toFixed(2)})</p><hr style="margin: 0.5rem 0;"><p><strong>Test result:</strong> Raw ${test_raw.toFixed(1)}°C → Corrected <strong>${corrected_test.toFixed(2)}°C</strong></p>`:html`<p style="color: #E74C3C;"><strong>Error:</strong> Raw low and raw high readings must differ. Two-point calibration requires two distinct measurement points.</p>`}</div>`
18.5 Practical Calibration References
Measurement
Reference
Accuracy
Notes
Temperature
Ice water (0°C)
+/-0.1°C
Use crushed ice, stir
Temperature
Boiling water (100°C*)
+/-0.5°C
*Altitude dependent
Temperature
Room thermometer
+/-0.5°C
Use NIST-traceable reference
Humidity
Saturated salt (75.3% RH)
+/-0.5%
Sodium chloride (NaCl) solution
Humidity
Saturated salt (32.8% RH at 25°C)
+/-0.5%
Magnesium chloride (MgCl₂)
Pressure
Weather station data
+/-1 hPa
Compare with local airport
Distance
Tape measure
+/-1mm
Fixed distance reference
18.6 Multi-Point Calibration
When to use: Sensor has nonlinear response that two-point calibration cannot correct (as demonstrated in the worked example below).
The following example uses NumPy for polynomial fitting. On resource-constrained microcontrollers, compute the coefficients on a PC and hard-code them, or use a simple lookup table with linear interpolation between points.
# Multi-Point Calibration with Polynomial Fitting (run on PC)import numpy as np# Calibration points: (true_reference, raw_sensor_reading)calibration_points = [ (0.0, 1.8), (25.0, 26.5), (50.0, 52.1), (75.0, 77.8), (100.0, 98.5)]# Separate reference and raw valuesrefs = [p[0] for p in calibration_points]raws = [p[1] for p in calibration_points]# Fit quadratic polynomial: true = a*raw^2 + b*raw + ccoefficients = np.polyfit(raws, refs, deg=2)print(f"Coefficients: {coefficients}")# Use these coefficients in your microcontroller codedef calibrate(raw):"""Apply polynomial correction."""return np.polyval(coefficients, raw)# Verify at calibration pointsfor ref, raw in calibration_points: corrected = calibrate(raw)print(f"Raw: {raw:.1f} -> Corrected: {corrected:.1f} (Expected: {ref:.1f})")
Try It: Multi-Point Polynomial Calibration Explorer
Enter 3–5 calibration points (reference vs raw) and see how a quadratic polynomial fits the data. Adjust individual points to observe how nonlinearity affects the correction curve.
Use stable conditions: Allow sensor to warm up (30 min for gas sensors), avoid drafts
Average multiple readings: Take 10–20 readings at each calibration point to reduce noise
Use accurate references: Your reference must be more accurate than your sensor
Verify after calibration: Validate against a third reference NOT used in calibration
Recalibrate periodically: Follow the schedule guidelines below for your sensor type
Store coefficients safely: Non-volatile memory (EEPROM/flash) with backup
Avoid overfitting: Too many polynomial terms can amplify noise between calibration points
18.9 Calibration Schedule Guidelines
Sensor Type
Recommended Interval
Trigger Events
Temperature
Every 6 months
After shipping, extreme temps
Humidity
Every 3 months
After high humidity exposure
Pressure
Every 12 months
After altitude changes
Gas sensors
Every 3-6 months
After contamination
pH sensors
Before each use
After storage
Load cells
Every 12 months
After overload events
Try It: Calibration Drift Estimator
Sensors drift over time due to aging, contamination, and environmental stress. Estimate when your sensor will exceed its accuracy tolerance and needs recalibration.
18.10 Worked Example: Complete Two-Point Calibration on ESP32
This example walks through a real calibration procedure for a DHT22 temperature sensor deployed in a greenhouse monitoring system. We cover the physical setup, the math, the code, and how to validate the result.
Scenario: You are deploying 20 DHT22 sensors in a commercial greenhouse. Each sensor will trigger HVAC adjustments, so accuracy matters – a 2°C error could stress plants or waste energy. Your reference instrument is a NIST-traceable digital thermometer accurate to +/-0.1°C.
Step 1: Prepare Reference Points
Reference
Setup
Expected Reading
Notes
Low point
Crushed ice + water slurry in insulated container
0.0°C +/- 0.1°C
Stir continuously, wait 5 min for equilibrium
High point
Warm water bath at greenhouse max temp
45.0°C (from reference thermometer)
Use 45°C, not 100°C – closer to operating range gives better results
Why 45°C instead of 100°C? The DHT22 operates between 0–50°C in a greenhouse. Calibrating at 100°C (outside operating range) introduces extrapolation error. Always calibrate within or near the expected operating range.
Step 2: Collect Raw Readings
Place the DHT22 and reference thermometer side by side in each bath. Wait 3 minutes for the sensor to stabilize, then take 20 readings over 2 minutes:
# MicroPython on ESP32 -- collect calibration dataimport dhtimport machineimport timesensor = dht.DHT22(machine.Pin(4))def collect_readings(n=20, interval_ms=6000):"""Collect n readings with interval between each. DHT22 minimum sampling period is 2 seconds; we use 6 seconds for better stability.""" readings = []for i inrange(n): sensor.measure() temp = sensor.temperature() readings.append(temp)print(f"Reading {i+1}: {temp:.1f}C") time.sleep_ms(interval_ms)return readings# Collect at low reference (ice bath, reference = 0.0C)print("Place sensor in ice bath, wait 3 min, then press Enter")low_readings = collect_readings()# Result: [2.1, 2.3, 2.2, 2.3, 2.1, 2.2, 2.3, 2.2, 2.1, 2.3,# 2.2, 2.2, 2.3, 2.1, 2.2, 2.3, 2.2, 2.1, 2.2, 2.1]# Collect at high reference (warm bath, reference = 45.0C)print("Place sensor in warm bath, wait 3 min, then press Enter")high_readings = collect_readings()# Result: [43.8, 43.9, 43.7, 43.8, 43.9, 43.8, 43.7, 43.8, 43.9, 43.8,# 43.7, 43.8, 43.9, 43.8, 43.7, 43.9, 43.8, 43.8, 43.7, 43.8]
def read_calibrated():"""Read temperature with two-point calibration applied.""" sensor.measure() raw = sensor.temperature() corrected = raw * scale + offsetreturnround(corrected, 1)# Validation: measure a third reference point NOT used in calibration# Room temperature, reference thermometer reads 22.5Cvalidation_readings = collect_readings(n=10)# Raw readings: [21.4, 21.5, 21.4, 21.5, 21.4, 21.5, 21.4, 21.5, 21.4, 21.5]# Raw average: 21.45C# Calibrated: 21.45 * 1.0817 + (-2.38) = 23.20 - 2.38 = 20.82C...# Wait -- that's off by 1.7C! Let's check the math:# calibrated = 21.45 * 1.0817 - 2.38 = 23.20 - 2.38 = 20.82C# Expected: 22.5C, Got: 20.82C -- error of 1.68C# This reveals NONLINEARITY in the sensor. The DHT22 error# is not purely linear across its range. For better accuracy# at room temperature, add a third calibration point.
The Validation Catch: This is why Step 4 matters. Two-point calibration assumes the sensor error is linear. When validation at a third point reveals significant residual error (>0.5°C for DHT22), you need multi-point calibration instead.
Try It: Calibration Validation Checker
After calibrating a sensor, test it against a third reference point to verify accuracy. Enter your calibration coefficients and a validation measurement to determine if your calibration is adequate or if you need a different approach.
Show code
viewof val_scale = Inputs.range([0.5,1.5], {value:1.0817,step:0.0001,label:"Calibration scale factor"})viewof val_offset = Inputs.range([-10,10], {value:-2.38,step:0.01,label:"Calibration offset"})viewof val_raw_reading = Inputs.range([-20,120], {value:21.45,step:0.05,label:"Raw sensor reading at validation point (°C)"})viewof val_true_value = Inputs.range([-20,120], {value:22.5,step:0.1,label:"True reference value at validation point (°C)"})viewof val_app_tolerance = Inputs.select( ["+/-0.2°C (Laboratory)","+/-0.5°C (HVAC/Greenhouse)","+/-1.0°C (General monitoring)","+/-2.0°C (Rough estimate)"], {value:"+/-0.5°C (HVAC/Greenhouse)",label:"Application tolerance"})
import jsoncal_data = {"sensor_id": "DHT22_GH_014","date": "2026-02-07","scale": round(scale, 4),"offset": round(offset, 2),"ref_instrument": "Fluke_1524_SN4892","ref_low": ref_low,"ref_high": ref_high,"raw_low_avg": round(raw_low, 2),"raw_high_avg": round(raw_high, 2),"validation_error": 1.68,"next_cal_date": "2026-08-07"}# Save to ESP32 flash filesystemwithopen("cal.json", "w") as f: json.dump(cal_data, f)# Load at bootwithopen("cal.json", "r") as f: cal = json.load(f) scale = cal["scale"] offset = cal["offset"]
Lesson from This Example
The validation step (Step 4) caught a problem that two-point calibration alone would have missed. In production, always validate against a reference point that was NOT used for calibration. If the validation error exceeds your application’s tolerance (here, 0.5°C for greenhouse HVAC), switch to multi-point calibration or select a more linear sensor (e.g., the SHT31 has better linearity than the DHT22).
Common Calibration Pitfall
Temperature and humidity affect many sensor types beyond the one you are calibrating. A pressure sensor calibrated in a heated lab may behave differently at outdoor deployment temperatures. Always note the ambient conditions during calibration and recalibrate if the deployment environment differs significantly from the calibration environment.
For Kids: Meet the Sensor Squad!
Sammy the Sensor was feeling embarrassed. “I keep saying it is 2.3 degrees when it should be zero!” he told the Squad while sitting in a bowl of ice water.
“Do not worry, Sammy!” said Max the Microcontroller. “That is totally normal. Every sensor is a little bit different from the factory. We just need to calibrate you!”
“Cali-what?” asked Lila the LED.
Max explained: “We dip Sammy in ice water – we KNOW that is 0 degrees. If Sammy says 2.3, we learn his offset. Then we dip him in boiling water – we KNOW that is 100 degrees. If Sammy says 98.5, we learn his scale. Now I can do math to fix every future reading!”
Bella the Battery pulled out a notebook. “We write down today’s date, what references we used, and the calibration numbers. In six months, we do it again because sensors can drift over time – like a clock that slowly gets behind.”
“And ALWAYS use a reference that is MORE accurate than the sensor you are calibrating,” Max added. “Otherwise, it is like asking someone who is MORE lost for directions!”
Try It: Calibration Method Advisor
Describe your sensor scenario and get a recommended calibration approach.
Show code
viewof adv_error_type = Inputs.checkbox( ["Constant offset (reads high/low by same amount everywhere)","Scale error (error grows with reading)","Nonlinear (error varies unpredictably across range)"], {label:"Error characteristics observed"})viewof adv_accuracy_needed = Inputs.radio( ["Low (<2°C / 5%)","Medium (<0.5°C / 1%)","High (<0.1°C / 0.2%)"], {value:"Medium (<0.5°C / 1%)",label:"Required accuracy"})viewof adv_num_sensors = Inputs.range([1,100], {value:5,step:1,label:"Number of sensors to calibrate"})viewof adv_has_references = Inputs.radio( ["One reference point available","Two reference points available","Three or more reference points"], {value:"Two reference points available",label:"Reference standards available"})
Show code
adv_has_offset = adv_error_type.includes("Constant offset (reads high/low by same amount everywhere)")adv_has_scale = adv_error_type.includes("Scale error (error grows with reading)")adv_has_nonlinear = adv_error_type.includes("Nonlinear (error varies unpredictably across range)")adv_high_acc = adv_accuracy_needed ==="High (<0.1°C / 0.2%)"adv_med_acc = adv_accuracy_needed ==="Medium (<0.5°C / 1%)"adv_refs = adv_has_references ==="Three or more reference points"?3: adv_has_references ==="Two reference points available"?2:1adv_method = adv_has_nonlinear || (adv_high_acc && adv_refs >=3)?"multi": (adv_has_scale || (adv_has_offset && adv_med_acc && adv_refs >=2))?"two":"one"adv_methods = ({one: {name:"One-Point Calibration",color:"#16A085",desc:"Corrects constant offset error using a single reference measurement.",time:"2--5 min per sensor",math:"corrected = raw - offset",limitations:"Cannot correct scale or nonlinear errors." },two: {name:"Two-Point Calibration",color:"#3498DB",desc:"Corrects both offset and scale (gain) errors using two reference measurements.",time:"10--15 min per sensor",math:"corrected = raw × scale + offset",limitations:"Assumes linear sensor response. May miss nonlinear behavior between calibration points." },multi: {name:"Multi-Point Calibration",color:"#9B59B6",desc:"Fits a polynomial curve through 3+ reference points to correct nonlinear sensor response.",time:"20--30 min per sensor",math:"corrected = a×raw² + b×raw + c (or higher order)",limitations:"Requires more reference standards and computation. Risk of overfitting with too many terms." }})adv_chosen = adv_methods[adv_method]adv_total_time_min = adv_method ==="one"?2: adv_method ==="two"?10:20adv_total_time_max = adv_method ==="one"?5: adv_method ==="two"?15:30html`<div style="background: var(--bs-light, #f8f9fa); padding: 1rem; border-radius: 8px; border-left: 4px solid ${adv_chosen.color}; margin-top: 0.5rem;"><h4 style="color: ${adv_chosen.color}; margin-top: 0;">Recommended: ${adv_chosen.name}</h4><p>${adv_chosen.desc}</p><table style="width: 100%; border-collapse: collapse; margin: 0.5rem 0; font-size: 0.9em;"><tr><td style="padding: 4px 8px; font-weight: bold; color: #2C3E50; width: 35%;">Formula</td><td style="padding: 4px 8px; font-family: monospace;">${adv_chosen.math}</td></tr><tr style="background: rgba(0,0,0,0.03);"><td style="padding: 4px 8px; font-weight: bold; color: #2C3E50;">Time per sensor</td><td style="padding: 4px 8px;">${adv_chosen.time}</td></tr><tr><td style="padding: 4px 8px; font-weight: bold; color: #2C3E50;">Fleet estimate (${adv_num_sensors} sensors)</td><td style="padding: 4px 8px;">${adv_total_time_min * adv_num_sensors}--${adv_total_time_max * adv_num_sensors} min (${((adv_total_time_min * adv_num_sensors) /60).toFixed(1)}--${((adv_total_time_max * adv_num_sensors) /60).toFixed(1)} hours)</td></tr><tr style="background: rgba(0,0,0,0.03);"><td style="padding: 4px 8px; font-weight: bold; color: #2C3E50;">Limitations</td><td style="padding: 4px 8px;">${adv_chosen.limitations}</td></tr></table>${adv_refs <2&& adv_method !=="one"?html`<p style="color: #E67E22; margin-top: 0.5rem;"><strong>Note:</strong> ${adv_chosen.name} requires ${adv_method ==="two"?"2":"3+"} reference points, but you indicated only ${adv_refs}. Consider obtaining additional reference standards.</p>`:""}${adv_error_type.length===0?html`<p style="color: #7F8C8D; margin-top: 0.5rem;"><em>Tip: Select at least one error characteristic above for a more specific recommendation.</em></p>`:""}</div>`
🏷️ Label the Diagram
Code Challenge
18.11 Summary
Key calibration takeaways:
All sensors benefit from calibration - Even factory-calibrated ones
One-point for offset - Simple constant correction
Two-point for offset + gain - Linear correction
Multi-point for nonlinearity - Polynomial or lookup table
Recalibrate regularly - Sensors drift over time
Try It Yourself
Beginner: You have a DHT22 that reads 23.8°C while your reference thermometer reads 22.0°C. Calculate the offset and write one line of code to apply it. Then check: if the DHT22 reads 30.5°C, what is the corrected temperature?
Intermediate: Using the MicroPython code from the worked example above, calibrate a sensor with ice water (0°C) and a warm bath (40°C). Your raw readings average 1.5°C at the low point and 38.9°C at the high point. Calculate the scale and offset, then predict the corrected reading if the sensor outputs 25.0°C.
Advanced: An MQ-135 gas sensor gives these readings at known CO₂ concentrations: (400 ppm, raw=120), (800 ppm, raw=280), (1200 ppm, raw=390), (1600 ppm, raw=460), (2000 ppm, raw=505). Plot these points – is the relationship linear? Fit a quadratic polynomial and calculate the predicted concentration when the sensor outputs raw=350.
18.12 Concept Relationships
Core Concept
Related Concepts
Why It Matters
Offset Error
Bias, Zero Point
Constant shift at all values
Gain Error
Scale Factor, Span
Proportional error increasing with reading
Nonlinearity
Polynomial Fit, Lookup Tables
Cannot be fixed with two-point calibration
Drift
Aging, Temperature, Recalibration Schedule
Calibration degrades over time
Common Pitfalls
1. Using Unstable Reference Standards
Calibrating a sensor against a reference that has not been verified against a primary standard propagates the reference’s error into the calibrated sensor. Always trace reference standards to national measurement standards (NIST/NPL) or use certified reference materials.
2. Calibrating Only at One Temperature
A one-point offset calibration at 25 C assumes the offset is constant across the sensor’s range. Many sensors have temperature-dependent offset. Perform calibration at the actual expected operating temperature, or characterize and compensate for the temperature coefficient in firmware.
3. Not Allowing Warm-Up Before Calibration
Many sensors require a warm-up period (2-30 minutes) for internal electronics to stabilize. Calibrating during warm-up captures an unstable reference reading and produces incorrect coefficients. Always wait for the manufacturer’s recommended stabilization time before taking calibration reference readings.
4. Validating Calibration Only at Reference Points
Verifying calibration only at the two reference points used to derive the coefficients provides false confidence. Linearity errors are not visible at calibration endpoints. Always verify the calibrated output at 3-5 intermediate values spanning the measurement range.
18.13 What’s Next
If you want to…
Read this
Practice calibration hands-on with an ESP32 simulator