ESP32 & ESP8266

Kalibrasi Sensor pada ESP32: Akurasi Data IoT

TOKEN

Pelajari teknik kalibrasi sensor yang tepat untuk mendapatkan data akurat pada proyek IoT dengan ESP32

1. Mengapa Kalibrasi Penting?

Dalam dunia IoT, akurasi data sensor sangat kritis. Bayangkan sebuah sistem monitoring suhu di gudang obat farmaksi — kesalahan 2°C bisa menyebabkan seluruh batch obat rusak senilai ratusan juta rupiah. Tanpa kalibrasi yang tepat, data dari sensor bisa menyimpang cukup jauh dari nilai sebenarnya.

Semua sensor memiliki toleransi error bawaan dari pabrik. Sensor DHT22 misalnya, memiliki akurasi ¹0.5°C dan ¹2% RH. Namun di kondisi lingkungan tertentu, error bisa lebih besar dari spekifikasi. Kalibrasi adalah proses mengidentifikasi dan mengoreksi error ini.

Sumber Error pada Sensor

Jenis Error Penyebab Dampak
Offset ErrorPergeseran konstan dari nilai sebenarnyaReading selalu lebih tinggi/rendah
Gain ErrorKesalahan slope pada rentang pengukuranError membesar di ujung range
Non-linearityRespon sensor tidak linierError bervariasi di setiap titik
DriftPerubahan karakteristik seiring waktuAkurasi menurun setelah berbulan-bulan
NoiseInterferensi elektrik, EMIFluktuasi acak pada pembacaan
Temperature EffectSuhu lingkungan mempengaruhi pembacaanError berubah sesuai suhu operasi
Pipeline Kalibrasi Sensor
📡
Sensor Raw
ADC / Digital
→
🔧
ADC Calibration
Linearisasi ADC
→
📐
Offset + Gain
Koreksi dasar
→
đŸŒĄī¸
Temp Comp
Kompensasi suhu
→
📊
Filtering
Noise reduction

2. Kalibrasi ADC ESP32

ESP32 memiliki ADC (Analog-to-Digital Converter) 12-bit yang terkenal non-linier. Kurva respon ADC ESP32 bukan garis lurus sempurna, melainkan memiliki "tekukan" di bagian bawah dan atas rentang. Ini masalah serius jika Anda mengandalkan ADC untuk pengukuran presisi.

Permasalahan ADC ESP32

ESP-IDF ADC Calibration

ESP-IDF menyediakan 3 karakter ADC calibration:

Character Deskripsi Akurasi
RawTanpa kalibrasi (nilai ADC mentah)Rendah
Calibrated (Line Fitting)Kalibrasi menggunakan eFuseSedang
Calibrated (Curve Fitting)Kalibrasi kurva dari eFuseTinggi
C++ — ADC dengan Line Fitting Calibration
// ADC Calibration pada ESP32
// BeebaneLabs - https://beebanelabs.pages.dev

#include "esp_adc_cal.h"

#define ADC_PIN       34   // Gunakan pin ADC1
#define ADC_ATTEN     ADC_ATTEN_DB_11  // Attenuation 11dB
#define ADC_WIDTH     ADC_WIDTH_BIT_12 // 12-bit

static esp_adc_cal_characteristics_t adc_chars;

void setupADC() {
  // Konfigurasi ADC
  adc1_config_width(ADC_WIDTH);
  adc1_config_channel_atten(ADC_CHANNEL_6, ADC_ATTEN);

  // Kalibrasi menggunakan line fitting
  esp_adc_cal_characterize(
    ADC_UNIT_1,
    ADC_ATTEN,
    ADC_WIDTH,
    1100,       // VRef default (mV)
    &adc_chars
  );

  Serial.println("ADC calibrated!");
}

// Membaca ADC dengan kalibrasi
uint32_t readCalibratedADC() {
  uint32_t adc_reading = 0;

  // Oversampling untuk mengurangi noise (ambil 64 sample)
  for (int i = 0; i < 64; i++) {
    adc_reading += adc1_get_raw(ADC_CHANNEL_6);
  }
  adc_reading /= 64;

  // Konversi ke tegangan (mV) menggunakan kalibrasi
  uint32_t voltage = esp_adc_cal_raw_to_voltage(
    adc_reading, &adc_chars
  );

  return voltage;
}

void loop() {
  uint32_t voltage_mv = readCalibratedADC();
  Serial.printf("ADC Raw → Voltage: %d mV\n", voltage_mv);
  delay(1000);
}
💡 Tips Akurasi ADC

Selalu gunakan oversampling (ambil rata-rata dari banyak sample) untuk mengurangi noise ADC. Minimal 16 sample, idealnya 64 sample. Ini sangat membantu terutama saat WiFi aktif karena switching power supply ESP32 menambah noise ke ADC.

3. Sensor Offset & Gain

Metode kalibrasi paling dasar adalah two-point calibration menggunakan offset dan gain. Ini mengasumsikan sensor memiliki respon linear dan mengoreksi dengan persamaan garis:

📐 Rumus Kalibrasi

nilai_kalibrasi = (nilai_mentah - offset) × gain

Atau dalam bentuk persamaan garis: y = m × x + b

Di mana m = gain (slope), b = -offset, x = nilai mentah, y = nilai kalibrasi.

Proses Two-Point Calibration

  1. Siapkan referensi standar (termometer akurat, multimeter kalibrasi, timbangan standar)
  2. Ukur pada titik rendah (low point) — catat nilai sensor dan nilai referensi
  3. Ukur pada titik tinggi (high point) — catat nilai sensor dan nilai referensi
  4. Hitung offset dan gain dari kedua titik
C++ — Two-Point Calibration
// Two-Point Calibration untuk Sensor
// BeebaneLabs - https://beebanelabs.pages.dev

struct CalibrationPoint {
  float measured;    // Nilai dari sensor
  float reference;   // Nilai dari alat referensi
};

// Hasil kalibrasi
struct CalibrationData {
  float offset;      // Koreksi offset
  float gain;        // Koreksi gain (slope)
};

// Hitung offset dan gain dari dua titik kalibrasi
CalibrationData calculateCalibration(
    CalibrationPoint low,
    CalibrationPoint high
) {
  CalibrationData cal;

  // Gain = perubahan referensi / perubahan sensor
  cal.gain = (high.reference - low.reference) /
             (high.measured - low.measured);

  // Offset = referensi - (gain × sensor)
  cal.offset = low.reference - (cal.gain * low.measured);

  return cal;
}

// Terapkan kalibrasi pada pembacaan mentah
float applyCalibration(float raw, CalibrationData cal) {
  return (raw * cal.gain) + cal.offset;
}

// === Contoh Penggunaan ===

// Kalibrasi sensor DHT22 suhu
CalibrationData tempCal;

void setupTemperatureCalibration() {
  // Titik 1: Es batu cair (0°C sebenarnya)
  CalibrationPoint low;
  low.measured = 1.2;    // Sensor membaca 1.2°C
  low.reference = 0.0;   // Sebenarnya 0°C

  // Titik 2: Air mendidih (100°C sebenarnya, sesuaikan altitude)
  CalibrationPoint high;
  high.measured = 98.5;   // Sensor membaca 98.5°C
  high.reference = 100.0; // Sebenarnya 100°C

  tempCal = calculateCalibration(low, high);

  Serial.printf("Offset: %.2f, Gain: %.4f\n",
                tempCal.offset, tempCal.gain);
}

void loop() {
  float rawTemp = dht.readTemperature();
  float calTemp = applyCalibration(rawTemp, tempCal);

  Serial.printf("Raw: %.1f°C → Calibrated: %.1f°C\n",
                rawTemp, calTemp);
  delay(2000);
}
Visualisasi: Offset dan Gain Correction
  Nilai
  Referensi
  (°C)    ↑
  100 ────┤                          ●──── ← Kalibrasi (sesuai)
          │                      ╱
   50 ────┤                  ╱        ○──── ← Raw sensor (melenceng)
          │              ╱
          │          ╱
    0 ────┤      ╱
          │  ╱         Offset = nilai di titik 0
          └──â”ŧ────────────────────────→ Nilai Sensor
             0    25    50    75   100

  Offset = selisih di titik rendah
  Gain   = kemiringan garis (slope correction)

4. Kompensasi Suhu

Banyak sensor (terutama sensor tekanan, kelembaban, dan gas) memiliki pembacaan yang terpengaruh oleh suhu lingkungan. Temperature compensation adalah teknik mengoreksi pembacaan sensor berdasarkan suhu aktual di lokasi pengukuran.

Mengapa Perlu Kompensasi Suhu?

C++ — Temperature Compensation
// Temperature Compensation untuk Sensor Tekanan
// BeebaneLabs - https://beebanelabs.pages.dev

struct TempCompensation {
  float refTemp;        // Suhu referensi saat kalibrasi (°C)
  float tempCoeff;      // Koefisien temperatur (% per °C)
  float nominalValue;   // Nilai nominal sensor
};

// Hitung nilai terkompensasi
float applyTempCompensation(
    float rawValue,
    float currentTemp,
    TempCompensation comp
) {
  // Rumus: corrected = raw / (1 + coeff * (T - Tref))
  float tempDiff = currentTemp - comp.refTemp;
  float correctionFactor = 1.0 + (comp.tempCoeff / 100.0) * tempDiff;
  return rawValue / correctionFactor;
}

// Contoh: Kompensasi sensor tekanan BMP280
void setup() {
  Serial.begin(115200);

  // Data dari datasheet BMP280
  TempCompensation pressureComp;
  pressureComp.refTemp = 25.0;      // Suhu referensi 25°C
  pressureComp.tempCoeff = -0.02;   // -0.02% per °C
  pressureComp.nominalValue = 1013.25;

  // Baca sensor
  float rawPressure = readPressureSensor();
  float currentTemp = readTemperature();

  // Terapkan kompensasi
  float compensatedPressure = applyTempCompensation(
    rawPressure, currentTemp, pressureComp
  );

  Serial.printf("Raw: %.2f hPa\n", rawPressure);
  Serial.printf("Compensated: %.2f hPa (pada %.1f°C)\n",
                compensatedPressure, currentTemp);
}
âš ī¸ Peringatan

Koefisien temperatur (tempCoeff) harus diperoleh dari datasheet sensor atau eksperimen kalibrasi pada beberapa suhu berbeda. Jangan pernah menebak nilai ini karena kesalahan bisa berakumulasi signifikan pada suhu ekstrem.

5. Kalibrasi Sensor Kelembaban

Sensor kelembaban (seperti DHT11, DHT22, SHT31) memerlukan perhatian khusus karena kelembaban relatif (RH) sangat dipengaruhi oleh suhu. Selain itu, sensor kelembaban cenderung mengalami drift seiring waktu akibat kontaminasi dari polutan udara.

Metode Kalibrasi Kelembaban

C++ — Humidity Sensor Calibration
// Kalibrasi Sensor Kelembaban
// BeebaneLabs - https://beebanelabs.pages.dev

// Salt Solution Method — Kalibrasi menggunakan larutan garam
// NaCl saturated → 75.3% RH pada 25°C
// LiCl saturated → 11.3% RH pada 25°C
// MgCl2 saturated → 32.8% RH pada 25°C

struct HumidityCalibration {
  float offset;      // Offset correction
  float gain;        // Gain correction
  float tempCoeff;   // Temperature coefficient
  float refTemp;     // Reference temperature
};

HumidityCalibration humCal;

// Setup kalibrasi menggunakan metode garam
void setupHumidityCalibration() {
  // Kalibrasi 2-titik dengan saturated salt solutions

  // Titik 1: NaCl (75.3% RH pada 25°C)
  CalibrationPoint low;
  low.measured = 78.2;    // Sensor membaca 78.2%
  low.reference = 75.3;   // NaCl reference

  // Titik 2: LiCl (11.3% RH pada 25°C)
  CalibrationPoint high;
  high.measured = 14.5;    // Sensor membaca 14.5%
  high.reference = 11.3;   // LiCl reference

  // Hitung offset dan gain
  humCal.gain = (high.reference - low.reference) /
                (high.measured - low.measured);
  humCal.offset = low.reference - (humCal.gain * low.measured);
  humCal.tempCoeff = -0.15;  // -0.15% RH per °C (typical)
  humCal.refTemp = 25.0;

  Serial.printf("Humidity Cal - Offset: %.2f, Gain: %.4f\n",
                humCal.offset, humCal.gain);
}

// Baca dan kalibrasi kelembaban
float readCalibratedHumidity(float rawHum, float currentTemp) {
  // Step 1: Terapkan offset dan gain
  float corrected = (rawHum * humCal.gain) + humCal.offset;

  // Step 2: Terapkan kompensasi suhu
  float tempDiff = currentTemp - humCal.refTemp;
  corrected -= humCal.tempCoeff * tempDiff;

  // Step 3: Clamp ke range valid 0-100%
  if (corrected < 0.0) corrected = 0.0;
  if (corrected > 100.0) corrected = 100.0;

  return corrected;
}
💡 Tips Kalibrasi Kelembaban

Untuk kalibrasi kelembaban di rumah, metode paling praktis adalah saturated salt solution. Masukkan garam dapur (NaCl) ke dalam wadah kedap udara bersama sensor. Setelah 24 jam, RH di dalam wadah akan stabil di ~75.3% pada 25°C. Bandingkan dengan hygrometer referensi.

6. Kalibrasi Sensor Tekanan

Sensor tekanan (BMP280, BME280, MS5611) digunakan dalam proyek IoT untuk weather monitoring dan altitude estimation. Kalibrasi sensor tekanan melibatkan koreksi offset dan kompensasi suhu, karena sensor tekanan sangat sensitif terhadap suhu.

Kalibrasi Altitude dengan Tekanan Referensi

C++ — Pressure & Altitude Calibration
// Kalibrasi Sensor Tekanan (BMP280/BME280)
// BeebaneLabs - https://beebanelabs.pages.dev

#include <Wire.h>
#include <Adafruit_BMP280.h>

Adafruit_BMP280 bmp;

struct PressureCalibration {
  float pressureOffset;      // Offset tekanan (hPa)
  float altitudeOffset;      // Offset altitude (meter)
  float seaLevelPressure;    // Tekanan permukaan laut (hPa)
};

PressureCalibration presCal;

void setupPressureCalibration() {
  // Kalibrasi offset tekanan
  // Bandingkan dengan stasiun cuaca terdekat
  // atau data BMKG (Badan Meteorologi)

  float sensorReading = bmp.readPressure() / 100.0; // hPa
  float referencePressure = 1013.25; // dari BMKG/web

  presCal.pressureOffset = referencePressure - sensorReading;
  presCal.seaLevelPressure = 1013.25;
  presCal.altitudeOffset = 0.0;

  Serial.printf("Pressure offset: %.2f hPa\n",
                presCal.pressureOffset);
}

// Baca tekanan terkalibrasi
float readCalibratedPressure() {
  float rawPressure = bmp.readPressure() / 100.0;
  return rawPressure + presCal.pressureOffset;
}

// Hitung altitude dari tekanan
float calculateAltitude(float calibratedPressure) {
  // Rumus barometric formula
  float altitude = 44330.0 * (
    1.0 - pow(calibratedPressure / presCal.seaLevelPressure, 0.1903)
  );
  return altitude + presCal.altitudeOffset;
}

void setup() {
  Serial.begin(115200);

  if (!bmp.begin(0x76)) {
    Serial.println("BMP280 tidak ditemukan!");
    while(1);
  }

  setupPressureCalibration();
}

void loop() {
  float pressure = readCalibratedPressure();
  float altitude = calculateAltitude(pressure);
  float temperature = bmp.readTemperature();

  Serial.printf("Tekanan: %.2f hPa\n", pressure);
  Serial.printf("Altitude: %.1f m\n", altitude);
  Serial.printf("Suhu: %.1f°C\n", temperature);

  delay(2000);
}

7. Multi-Point Calibration

Untuk sensor yang memiliki non-linearitas signifikan, two-point calibration tidak cukup. Kita perlu multi-point calibration yang menggunakan banyak titik referensi dan interpolasi untuk mengoreksi non-linearitas.

Linear Interpolation

C++ — Multi-Point Calibration
// Multi-Point Calibration dengan Linear Interpolation
// BeebaneLabs - https://beebanelabs.pages.dev

#define NUM_CAL_POINTS 5

struct CalPoint {
  float measured;
  float reference;
};

// Titik kalibrasi (diurutkan dari kecil ke besar)
CalPoint calPoints[NUM_CAL_POINTS] = {
  {50.0,  45.0},    // Titik 1
  {500.0, 480.0},   // Titik 2
  {1000.0, 985.0},  // Titik 3
  {2000.0, 1970.0}, // Titik 4
  {3500.0, 3450.0}  // Titik 5
};

// Linear interpolation antar dua titik
float interpolate(float x, float x0, float y0,
                  float x1, float y1) {
  return y0 + (x - x0) * (y1 - y0) / (x1 - x0);
}

// Multi-point calibration
float multiPointCalibrate(float raw) {
  // Jika di bawah titik pertama → extrapolasi dari 2 titik pertama
  if (raw <= calPoints[0].measured) {
    return interpolate(raw,
      calPoints[0].measured, calPoints[0].reference,
      calPoints[1].measured, calPoints[1].reference);
  }

  // Jika di atas titik terakhir → extrapolasi dari 2 titik terakhir
  if (raw >= calPoints[NUM_CAL_POINTS - 1].measured) {
    return interpolate(raw,
      calPoints[NUM_CAL_POINTS - 2].measured,
      calPoints[NUM_CAL_POINTS - 2].reference,
      calPoints[NUM_CAL_POINTS - 1].measured,
      calPoints[NUM_CAL_POINTS - 1].reference);
  }

  // Cari interval yang tepat dan interpolasi
  for (int i = 0; i < NUM_CAL_POINTS - 1; i++) {
    if (raw >= calPoints[i].measured &&
        raw <= calPoints[i + 1].measured) {
      return interpolate(raw,
        calPoints[i].measured, calPoints[i].reference,
        calPoints[i + 1].measured, calPoints[i + 1].reference);
    }
  }

  return raw; // Fallback
}

void setup() {
  Serial.begin(115200);

  // Test kalibrasi
  float testValues[] = {100, 250, 750, 1500, 2500, 4000};

  for (int i = 0; i < 6; i++) {
    float raw = testValues[i];
    float cal = multiPointCalibrate(raw);
    Serial.printf("Raw: %.0f → Calibrated: %.1f\n", raw, cal);
  }
}
â„šī¸ Kapan Gunakan Multi-Point?

Gunakan multi-point calibration untuk sensor dengan non-linearitas tinggi seperti sensor gas (MQ-series), sensor cahaya (LDR), dan sensor pH. Untuk sensor yang sudah linear (seperti termokopel), two-point calibration biasanya sudah cukup.

8. Data Filtering: Moving Average

Setelah kalibrasi, langkah terakhir untuk mendapatkan data yang akurat adalah filtering untuk menghilangkan noise. Teknik paling umum dan efektif untuk ESP32 adalah Moving Average Filter.

Jenis Filter untuk Data Sensor

Jenis Filter Kompleksitas Kelebihan Kekurangan
Simple Moving AverageRendahMudah diimplementasiMembutuhkan buffer besar
Exponential Moving AverageRendahHemat memoryPerlu tuning alpha
Median FilterSedangTahan terhadap spikeLebih lambat
Kalman FilterTinggiSangat akuratKomputasi berat

Implementasi Moving Average

C++ — Moving Average Filter
// Moving Average Filter untuk Data Sensor
// BeebaneLabs - https://beebanelabs.pages.dev

class MovingAverage {
  private:
    float* buffer;
    int bufferSize;
    int index;
    float sum;
    bool filled;

  public:
    MovingAverage(int size) {
      bufferSize = size;
      buffer = new float[size];
      index = 0;
      sum = 0;
      filled = false;

      // Inisialisasi buffer dengan 0
      for (int i = 0; i < size; i++) {
        buffer[i] = 0;
      }
    }

    ~MovingAverage() {
      delete[] buffer;
    }

    float addValue(float value) {
      // Kurangi nilai lama dari sum
      sum -= buffer[index];

      // Tambah nilai baru
      buffer[index] = value;
      sum += value;

      // Geser index
      index = (index + 1) % bufferSize;
      if (index == 0) filled = true;

      // Hitung rata-rata
      int count = filled ? bufferSize : index;
      return sum / count;
    }

    float getAverage() {
      int count = filled ? bufferSize : index;
      if (count == 0) return 0;
      return sum / count;
    }
};

// === Exponential Moving Average (EMA) ===
class ExponentialMA {
  private:
    float alpha;
    float ema;
    bool initialized;

  public:
    ExponentialMA(float smoothingFactor) {
      alpha = smoothingFactor; // 0.01 - 0.5
      ema = 0;
      initialized = false;
    }

    float addValue(float value) {
      if (!initialized) {
        ema = value;
        initialized = true;
      } else {
        ema = alpha * value + (1.0 - alpha) * ema;
      }
      return ema;
    }
};

// === Contoh Penggunaan ===
MovingAverage tempFilter(10);     // 10-sample moving average
ExponentialMA humidFilter(0.1);   // EMA alpha=0.1

void loop() {
  // Baca sensor mentah
  float rawTemp = dht.readTemperature();
  float rawHumid = dht.readHumidity();

  // Terapkan filter
  float filteredTemp = tempFilter.addValue(rawTemp);
  float filteredHumid = humidFilter.addValue(rawHumid);

  // Terapkan kalibrasi
  float calTemp = applyCalibration(filteredTemp, tempCal);
  float calHumid = readCalibratedHumidity(filteredHumid, calTemp);

  Serial.printf("Raw: %.1f°C → Filtered: %.1f°C → Cal: %.1f°C\n",
                rawTemp, filteredTemp, calTemp);
  Serial.printf("Raw: %.1f%% → Filtered: %.1f%% → Cal: %.1f%%\n",
                rawHumid, filteredHumid, calHumid);

  delay(2000);
}
📊
Simple Moving Average
Window-based
  • ✅ Mudah dipahami
  • ✅ Semua sample bobot sama
  • ❌ Perlu buffer besar (RAM)
  • ❌ Lag lebih besar
📈
Exponential Moving Average
Recursive
  • ✅ Hemat memory (1 variable)
  • ✅ Respons lebih cepat
  • ✅ Data terbaru bobot lebih besar
  • ❌ Perlu tuning alpha
💡 Tips Filter

Untuk sensor suhu (perubahan lambat), gunakan window 10-20 sample. Untuk sensor cahaya atau accelerometer (perubahan cepat), gunakan window 3-5 sample atau EMA dengan alpha 0.3-0.5. Terlalu banyak filtering menyebabkan lag yang tidak responsif.

9. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial kalibrasi sensor di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu:

Pertanyaan 1: Apa yang dimaksud dengan "offset error" pada sensor?

a) Error yang berubah-ubah sesuai waktu
b) Pergeseran konstan dari nilai sebenarnya
c) Error yang hanya terjadi di suhu tinggi
d) Fluktuasi acak pada pembacaan

Pertanyaan 2: Mengapa ADC ESP32 memerlukan kalibrasi khusus?

a) Karena resolusi hanya 8-bit
b) Karena ADC ESP32 non-linier
c) Karena ADC tidak mendukung WiFi
d) Karena ADC hanya bisa membaca 0-1V

Pertanyaan 3: Berapa nilai RH (Relative Humidity) yang dihasilkan oleh larutan garam NaCl jenuh pada 25°C?

a) 50.0%
b) 11.3%
c) 75.3%
d) 32.8%

Pertanyaan 4: Kelebihan utama Exponential Moving Average dibanding Simple Moving Average adalah...

a) Lebih akurat dalam semua kondisi
b) Tidak membutuhkan buffer sama sekali
c) Hemat memory dan respons lebih cepat
d) Dapat memprediksi nilai masa depan

Pertanyaan 5: Untuk sensor dengan non-linearitas tinggi, metode kalibrasi apa yang paling tepat?

a) Single-point calibration
b) Two-point offset/gain
c) Multi-point calibration dengan interpolasi
d) Tanpa kalibrasi, cukup filter
← Sebelumnya FreeRTOS pada ESP32 Selanjutnya → Kembali ke Beranda