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 Error | Pergeseran konstan dari nilai sebenarnya | Reading selalu lebih tinggi/rendah |
| Gain Error | Kesalahan slope pada rentang pengukuran | Error membesar di ujung range |
| Non-linearity | Respon sensor tidak linier | Error bervariasi di setiap titik |
| Drift | Perubahan karakteristik seiring waktu | Akurasi menurun setelah berbulan-bulan |
| Noise | Interferensi elektrik, EMI | Fluktuasi acak pada pembacaan |
| Temperature Effect | Suhu lingkungan mempengaruhi pembacaan | Error berubah sesuai suhu operasi |
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
- Non-linearity: ADC ESP32 memiliki error hingga Âą6% di beberapa titik
- Noise: Terutama saat WiFi aktif, noise bisa mencapai Âą100 LSB
- Attenuation default: Range 0-3.3V dengan attenuation 11dB, tapi rentang efektif hanya 0-3.1V
- ADC2 tidak kompatibel WiFi: Pin ADC2 (GPIO 4, 2, 15, dll) tidak bisa dipakai saat WiFi aktif
ESP-IDF ADC Calibration
ESP-IDF menyediakan 3 karakter ADC calibration:
| Character | Deskripsi | Akurasi |
|---|---|---|
| Raw | Tanpa kalibrasi (nilai ADC mentah) | Rendah |
| Calibrated (Line Fitting) | Kalibrasi menggunakan eFuse | Sedang |
| Calibrated (Curve Fitting) | Kalibrasi kurva dari eFuse | Tinggi |
// 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);
}
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:
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
- Siapkan referensi standar (termometer akurat, multimeter kalibrasi, timbangan standar)
- Ukur pada titik rendah (low point) â catat nilai sensor dan nilai referensi
- Ukur pada titik tinggi (high point) â catat nilai sensor dan nilai referensi
- Hitung offset dan gain dari kedua titik
// 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);
}
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?
- Resistansi sensor berubah seiring suhu (koefisien temperatur)
- Karakteristik ADC bergeser pada suhu berbeda
- Elemen sensor (seperti strain gauge) memiliki thermal drift
- IC sensor sudah memiliki kompensasi internal, tapi kadang kurang akurat
// 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);
}
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
// 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;
}
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
// 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
// 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);
}
}
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 Average | Rendah | Mudah diimplementasi | Membutuhkan buffer besar |
| Exponential Moving Average | Rendah | Hemat memory | Perlu tuning alpha |
| Median Filter | Sedang | Tahan terhadap spike | Lebih lambat |
| Kalman Filter | Tinggi | Sangat akurat | Komputasi berat |
Implementasi Moving Average
// 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);
}
- â Mudah dipahami
- â Semua sample bobot sama
- â Perlu buffer besar (RAM)
- â Lag lebih besar
- â Hemat memory (1 variable)
- â Respons lebih cepat
- â Data terbaru bobot lebih besar
- â Perlu tuning alpha
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: