Skip to main content

Overview

Kinematrix provides a comprehensive calibration system for sensor accuracy. The V2 framework includes an interactive calibration interface with EEPROM persistence, supporting one-point, two-point, and multi-point calibration methods.

Calibration Module Architecture

class SensorCalibrationModuleV2 : public SensorModuleV2 {
public:
    // Calibration discovery
    void discoverCalibrableValues();
    void discoverNestedCalibrableValues();
    
    // Manual calibration
    void calibrateOnePoint(const char *sensorName, const char *valueKey, float knownValue);
    void calibrateTwoPoint(const char *sensorName, const char *valueKey,
                          float knownValue1, float rawValue1,
                          float knownValue2, float rawValue2);
    
    // Interactive calibration
    void startCalibrationMode(Stream *serialPtr, uint32_t timeout = 300000);
    void stopCalibrationMode();
    
    // EEPROM persistence
    bool saveAllCalibrations(int baseEepromAddress = 0);
    bool loadAllCalibrations(int baseEepromAddress = 0);
    CalibrationLoadResult loadAllCalibrationsWithStatus(int baseEepromAddress = 0);
    
    // Value access
    float getRawValue(const char *sensorName, const char *valueKey) const;
    float getCalibratedValue(const char *sensorName, const char *valueKey) const;
};

Calibration Methods

One-Point Calibration

Often called “offset calibration,” this adjusts for a constant offset error.
#define ENABLE_SENSOR_MODULE_V2
#define ENABLE_SENSOR_CALIBRATION_MODULE_V2
#define ENABLE_SENSOR_ANALOG_V2
#include "Kinematrix.h"

SensorCalibrationModuleV2 calibrator;
AnalogSensV2 phSensor(A0, 5.0, 1023, [](BaseSensV2* s, int raw, float volt) {
    float ph = volt * 3.5;  // Initial rough conversion
    s->updateValue("ph", ph);
});

void setup() {
    Serial.begin(115200);
    
    phSensor.addCustomValue("ph", "pH Level", "pH", 2, true);
    calibrator.addSensor("ph", &phSensor);
    calibrator.init();
    
    // Place sensor in pH 7.0 buffer solution
    Serial.println("Place sensor in pH 7.0 solution...");
    delay(5000);
    
    // Calibrate to known value of 7.0
    calibrator.calibrateOnePoint("ph", "ph", 7.0);
    
    Serial.println("Calibration complete!");
    calibrator.saveAllCalibrations(0);
}

void loop() {
    calibrator.update();
    
    float rawPH = calibrator.getRawValue("ph", "ph");
    float calibratedPH = calibrator.getCalibratedValue("ph", "ph");
    
    Serial.print("Raw: ");
    Serial.print(rawPH);
    Serial.print(" | Calibrated: ");
    Serial.println(calibratedPH);
    
    delay(1000);
}

Two-Point Calibration

Corrects both offset and scale errors using two reference points.
AnalogSensV2 tempSensor(A0, 5.0, 1023, [](BaseSensV2* s, int raw, float volt) {
    float temp = (volt - 0.5) * 100.0;  // LM35 basic conversion
    s->updateValue("temperature", temp);
});

void setup() {
    Serial.begin(115200);
    
    tempSensor.addCustomValue("temperature", "Temperature", "°C", 2, true);
    calibrator.addSensor("temp", &tempSensor);
    calibrator.init();
    
    // First calibration point: Ice water (0°C)
    Serial.println("Place sensor in ice water (0°C)...");
    delay(10000);
    calibrator.update();
    float raw1 = calibrator.getRawValue("temp", "temperature");
    
    // Second calibration point: Boiling water (100°C)
    Serial.println("Place sensor in boiling water (100°C)...");
    delay(10000);
    calibrator.update();
    float raw2 = calibrator.getRawValue("temp", "temperature");
    
    // Perform two-point calibration
    calibrator.calibrateTwoPoint("temp", "temperature", 
                                0.0, raw1,    // Known value, raw reading at 0°C
                                100.0, raw2); // Known value, raw reading at 100°C
    
    Serial.println("Two-point calibration complete!");
    calibrator.saveAllCalibrations(0);
}

Multi-Point Calibration

For sensors with non-linear response, use multiple calibration points.
void performMultiPointCalibration() {
    // Start interactive calibration mode
    calibrator.startCalibrationMode(&Serial, 300000);  // 5 minute timeout
    
    // User will be prompted via serial interface to:
    // 1. Select sensor and value to calibrate
    // 2. Choose calibration method (multi-point)
    // 3. Add calibration points at different known values
    // 4. System calculates polynomial fit
    // 5. Save calibration to EEPROM
}

Interactive Calibration Mode

Starting Interactive Calibration

SensorCalibrationModuleV2 calibrator;

void setup() {
    Serial.begin(115200);
    
    // Add sensors
    calibrator.addSensor("temp", &tempSensor);
    calibrator.addSensor("ph", &phSensor);
    calibrator.addSensor("pressure", &pressureSensor);
    calibrator.init();
    
    // Discover which values can be calibrated
    calibrator.discoverCalibrableValues();
    
    // Set EEPROM address and auto-save
    calibrator.setEEPROMStartAddress(100);
    calibrator.setAutoSaveCalibration(true);
    
    // Load previous calibrations
    calibrator.loadAllCalibrations(100);
    
    // Start interactive mode
    calibrator.startCalibrationMode(&Serial, 600000);  // 10 minute timeout
}

void loop() {
    calibrator.update();
    
    if (!calibrator.isInCalibrationMode()) {
        // Normal operation - use calibrated values
        float temp = calibrator.getCalibratedValue("temp", "temperature");
        float ph = calibrator.getCalibratedValue("ph", "ph");
        
        // Process calibrated data...
    }
}

Interactive Calibration Menu

When in calibration mode, users interact via Serial Monitor:
=== Sensor Calibration Menu ===

Available Sensors:
1. temp.temperature (Temperature, °C)
2. ph.ph (pH Level, pH)
3. pressure.pressure (Pressure, kPa)

Commands:
  r - Read current sensor value
  1 - One-point calibration
  2 - Two-point calibration
  m - Multi-point calibration
  s - Save calibration to EEPROM
  l - Load calibration from EEPROM
  d - Display calibration details
  c - Clear calibration
  x - Exit calibration mode

Enter command:

Calibration Engine

The underlying calibration engine supports multiple interpolation methods:
class CalibrationEngine {
public:
    // Calibration methods
    bool calibrateOnePoint(float knownValue, float rawValue);
    bool calibrateTwoPoint(float knownValue1, float rawValue1,
                          float knownValue2, float rawValue2);
    
    // Multi-point calibration
    void setMaxCalibrationPoints(uint8_t maxPoints);
    bool addCalibrationPoint(float knownValue, float rawValue);
    bool calculateCalibration();
    
    // Interpolation methods
    void setInterpolationMethod(uint8_t method);
    void setPolynomialDegree(uint8_t degree);
    
    // Apply calibration
    float calibrate(float rawValue);
    
    // Status
    bool isCalibrated() const;
    uint8_t getCalibrationMethod() const;
};

Interpolation Methods

// Linear interpolation (default for multi-point)
engine.setInterpolationMethod(INTERPOLATION_LINEAR);

// Polynomial fitting
engine.setInterpolationMethod(INTERPOLATION_POLYNOMIAL);
engine.setPolynomialDegree(2);  // Quadratic

// Lookup table with linear interpolation
engine.setInterpolationMethod(INTERPOLATION_LOOKUP);

EEPROM Persistence

Saving Calibrations

SensorCalibrationModuleV2 calibrator;

void saveCalibrations() {
    // Set base EEPROM address
    calibrator.setEEPROMStartAddress(100);
    
    // Save all calibrations
    if (calibrator.saveAllCalibrations(100)) {
        Serial.println("Calibrations saved successfully!");
    } else {
        Serial.println("Failed to save calibrations");
    }
}

Loading Calibrations

void loadCalibrations() {
    // Load with status information
    CalibrationLoadResult result = calibrator.loadAllCalibrationsWithStatus(100);
    
    Serial.print("Total entries: ");
    Serial.println(result.totalEntries);
    Serial.print("Successfully loaded: ");
    Serial.println(result.successCount);
    Serial.print("Not calibrated: ");
    Serial.println(result.notCalibratedCount);
    Serial.print("Errors: ");
    Serial.println(result.errorCount);
}

Auto-Save Configuration

void setup() {
    calibrator.setEEPROMStartAddress(100);
    calibrator.setAutoSaveCalibration(true);  // Auto-save after each calibration
    calibrator.loadAllCalibrations(100);
}

Calibration Discovery

Automatic Value Discovery

void setup() {
    // Add all sensors
    calibrator.addSensor("temp", &tempSensor);
    calibrator.addSensor("humidity", &humiditySensor);
    calibrator.init();
    
    // Discover all calibratable values
    calibrator.discoverCalibrableValues();
    
    // List discovered values
    calibrator.listCalibrableValues();
}
Output:
Calibrable Sensor Values:
1. temp.temperature (Temperature, °C) [ENABLED]
2. temp.fahrenheit (Temperature, °F) [ENABLED]
3. humidity.humidity (Humidity, %) [ENABLED]
4. humidity.dewPoint (Dew Point, °C) [ENABLED]

Nested Value Discovery

For sensors with nested JSON structures:
calibrator.discoverNestedCalibrableValues();

Calibration Control

Enable/Disable Calibration

// Enable calibration for specific value
calibrator.enableValueCalibration("temp", "temperature", true);

// Disable calibration for a value
calibrator.enableValueCalibration("temp", "temperature", false);

// Enable calibration for entire sensor
calibrator.enableSensorCalibration("temp", true);

// Enable/disable all calibrations
calibrator.enableAllCalibration(true);

Check Calibration Status

// Check if value calibration is enabled
if (calibrator.isValueCalibrationEnabled("temp", "temperature")) {
    float calibrated = calibrator.getCalibratedValue("temp", "temperature");
} else {
    float raw = calibrator.getRawValue("temp", "temperature");
}

// Check sensor calibration status
if (calibrator.isSensorCalibrationEnabled("temp")) {
    Serial.println("Temperature sensor calibration is active");
}

Calibration Status Display

// List all sensor calibration status
calibrator.listSensorCalibrationStatus();

// Print detailed calibration status
calibrator.printCalibrationStatus();
Output:
Sensor Calibration Status:
temp:
  temperature: CALIBRATED (2-point) [ENABLED]
  fahrenheit: CALIBRATED (2-point) [ENABLED]
ph:
  ph: CALIBRATED (1-point) [ENABLED]
pressure:
  pressure: NOT CALIBRATED [ENABLED]

Advanced Calibration Features

Calibration Statistics

void showCalibrationStatistics() {
    // Shows statistics over multiple readings
    calibrator.startCalibrationMode(&Serial);
    
    // In calibration menu, use 's' command to:
    // - Display mean, standard deviation
    // - Show min/max values
    // - Calculate measurement stability
}

Calibration Profiles

// Save calibration to profile slot
calibrator.saveCalibrationProfile(1);  // Profile 1
calibrator.saveCalibrationProfile(2);  // Profile 2

// Load calibration from profile
calibrator.loadCalibrationProfile(1);

// List available profiles
calibrator.listCalibrationProfiles();

Calibration Curve Visualization

void visualizeCalibration() {
    // In interactive mode, display ASCII calibration curve
    calibrator.startCalibrationMode(&Serial);
    
    // Use 'v' command to show curve:
    // Input  | Output
    // -------|-------
    //   0.0  | *
    //  10.0  |   *
    //  20.0  |     *
    //  30.0  |       *
}

Complete Calibration Example

#define ENABLE_SENSOR_MODULE_V2
#define ENABLE_SENSOR_CALIBRATION_MODULE_V2
#define ENABLE_SENSOR_ANALOG_V2
#include "Kinematrix.h"

SensorCalibrationModuleV2 calibrator;

// pH sensor with custom processing
AnalogSensV2 phSensor(A0, 5.0, 1023, [](BaseSensV2* s, int raw, float volt) {
    float ph = volt * 3.5;  // Basic conversion
    s->updateValue("ph", ph);
});

// Temperature sensor with custom processing
AnalogSensV2 tempSensor(A1, 5.0, 1023, [](BaseSensV2* s, int raw, float volt) {
    float tempC = (volt - 0.5) * 100.0;
    float tempF = tempC * 9.0/5.0 + 32.0;
    s->updateValue("celsius", tempC);
    s->updateValue("fahrenheit", tempF);
});

void setup() {
    Serial.begin(115200);
    
    // Configure sensors
    phSensor.addCustomValue("ph", "pH Level", "pH", 2, true);
    phSensor.setUpdateInterval(1000);
    
    tempSensor.addCustomValue("celsius", "Temperature", "°C", 2, true);
    tempSensor.addCustomValue("fahrenheit", "Temperature", "°F", 2, true);
    tempSensor.setUpdateInterval(1000);
    
    // Add to calibrator
    calibrator.addSensor("ph", &phSensor);
    calibrator.addSensor("temp", &tempSensor);
    calibrator.init();
    
    // Configure EEPROM
    calibrator.setEEPROMStartAddress(100);
    calibrator.setAutoSaveCalibration(true);
    
    // Load existing calibrations
    CalibrationLoadResult result = calibrator.loadAllCalibrationsWithStatus(100);
    Serial.print("Loaded ");
    Serial.print(result.successCount);
    Serial.println(" calibrations from EEPROM");
    
    // Discover calibratable values
    calibrator.discoverCalibrableValues();
    calibrator.listCalibrableValues();
    
    Serial.println("\n=== Commands ===");
    Serial.println("c - Enter calibration mode");
    Serial.println("s - Show calibration status");
    Serial.println("r - Reset all calibrations");
}

void loop() {
    calibrator.update();
    
    // Handle serial commands
    if (Serial.available()) {
        char cmd = Serial.read();
        
        switch (cmd) {
            case 'c':
                Serial.println("Starting calibration mode...");
                calibrator.startCalibrationMode(&Serial, 600000);
                break;
                
            case 's':
                calibrator.printCalibrationStatus();
                break;
                
            case 'r':
                // Reset would need to be implemented
                Serial.println("Calibration reset not shown in example");
                break;
        }
    }
    
    // Display calibrated values
    static unsigned long lastDisplay = 0;
    if (millis() - lastDisplay >= 2000) {
        float phRaw = calibrator.getRawValue("ph", "ph");
        float phCal = calibrator.getCalibratedValue("ph", "ph");
        
        float tempRaw = calibrator.getRawValue("temp", "celsius");
        float tempCal = calibrator.getCalibratedValue("temp", "celsius");
        
        Serial.println("\n=== Sensor Readings ===");
        Serial.print("pH: ");
        Serial.print(phRaw, 2);
        Serial.print(" -> ");
        Serial.print(phCal, 2);
        Serial.println(" pH");
        
        Serial.print("Temp: ");
        Serial.print(tempRaw, 2);
        Serial.print(" -> ");
        Serial.print(tempCal, 2);
        Serial.println(" °C");
        
        lastDisplay = millis();
    }
}

Best Practices

Calibration Frequency: Recalibrate sensors periodically:
  • pH sensors: Monthly or after 100 hours of use
  • Temperature sensors: Annually or when accuracy degrades
  • Gas sensors: After significant environmental changes
Sensor Stability: Allow sensors to stabilize before calibration:
  • Temperature sensors: 30-60 seconds
  • pH sensors: 2-5 minutes
  • Gas sensors: 5-10 minutes (or longer for some MQ sensors)
Reference Standards: Use high-quality reference standards:
  • pH: Certified pH buffer solutions (pH 4.0, 7.0, 10.0)
  • Temperature: Ice water (0°C), boiling water (100°C at sea level)
  • Pressure: Calibrated pressure gauges

Troubleshooting

Calibration Not Saving

// Ensure EEPROM is properly initialized
#include <EEPROM.h>
void setup() {
    EEPROM.begin(512);  // ESP32/ESP8266
    calibrator.setEEPROMStartAddress(100);
    calibrator.saveAllCalibrations(100);
}

Calibration Not Applied

// Verify calibration is enabled
if (!calibrator.isValueCalibrationEnabled("temp", "temperature")) {
    calibrator.enableValueCalibration("temp", "temperature", true);
}

Unstable Readings

// Use filtering with calibration
FilterParams params;
params.movingAverage.windowSize = 10;
sensors.attachFilter("temp", "temperature", FILTER_MOVING_AVERAGE, params);

float calibrated = calibrator.getCalibratedValue("temp", "temperature");
float filtered = sensors.getFilteredValue("temp", "temperature");

Next Steps

Filtering

Combine calibration with signal filtering

Alerts

Set alerts on calibrated values

Build docs developers (and LLMs) love