1. Pengenalan Sensor Interfacing
Sensor interfacing adalah proses menghubungkan sensor fisik ke microcontroller sehingga data dari dunia nyata (suhu, cahaya, kelembaban, tekanan, dll) dapat dibaca, diproses, dan dikirim ke sistem IoT. Memahami berbagai metode interfacing adalah keterampilan fundamental bagi setiap praktisi IoT.
Setiap sensor memiliki karakteristik berbeda: ada yang menghasilkan sinyal analog, ada yang digital; ada yang berkomunikasi via I2C, SPI, atau UART. Pemilihan metode interfacing yang tepat mempengaruhi akurasi, kecepatan, dan kompleksitas sistem Anda.
Mengapa Sensor Interfacing Penting?
| Aspek | Penjelasan |
|---|---|
| Akuisisi Data | Sensor adalah mata dan telinga sistem IoT β tanpa sensor, tidak ada data |
| Akurasi | Interfacing yang benar memastikan pembacaan sensor akurat dan konsisten |
| Efisiensi | Pemilihan protokol yang tepat menghemat pin, power, dan bandwidth |
| Skalabilitas | Protokol seperti I2C dan SPI memungkinkan banyak sensor di satu bus |
| Reliability | Kalibrasi dan filtering menghasilkan data yang dapat diandalkan |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β ARSITEKTUR SENSOR INTERFACING β β β β ββββββββββββ βββββββββββββββββββββββββββββββββββ β β β Sensor β β MICROCONTROLLER β β β β Analog ββββββΊβ ADC βββΊ Processing βββΊ Output β β β ββββββββββββ β β β β β ββββββββββ ββββββββββ β β β ββββββββββββ β β GPIO β β Buffer β β β β β Sensor ββββββΊβ β Input ββββ Filter ββββΊ WiFiβ β β β Digital β β ββββββββββ ββββββββββ β β β ββββββββββββ β β β β β ββββββββββ ββββββββββ β β β ββββββββββββ β β I2C β β Kalibr.β β β β β Sensor ββββββΊβ β Bus ββββ ββββΊ MQTTβ β β β I2C β β ββββββββββ ββββββββββ β β β ββββββββββββ β β β β β ββββββββββ β β β ββββββββββββ β β SPI β β β β β Sensor ββββββΊβ β Bus β β β β β SPI β β ββββββββββ β β β ββββββββββββ βββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Jenis-Jenis Sensor
Sensor dapat diklasifikasikan berdasarkan parameter yang diukur dan jenis sinyal outputnya.
Berdasarkan Parameter
| Kategori | Sensor | Contoh IC/Module | Output |
|---|---|---|---|
| Suhu | NTC Thermistor, DS18B20, DHT22, BME280 | DHT22, BME280, LM35 | Analog / I2C / 1-Wire |
| Kelembaban | Kapasitif, Resistif | DHT22, BME280, SHT31 | Digital / I2C |
| Tekanan | Piezoresistif | BMP280, BME280, MPL3115A2 | I2C / SPI |
| Cahaya | LDR, Photodiode | BH1750, TSL2561 | Analog / I2C |
| Jarak | Ultrasonic, IR, ToF | HC-SR04, VL53L0X | Digital / I2C |
| Gerakan | PIR, Accelerometer, Gyro | MPU6050, ADXL345 | Digital / I2C / SPI |
| Gas | Electrochemical, MOS | MQ-135, SGP30, SCD30 | Analog / I2C |
| pH | Elektroda | SEN0161 | Analog |
Berdasarkan Jenis Output Sinyal
| Jenis Output | Karakteristik | Contoh Sensor | Interfacing |
|---|---|---|---|
| Analog | Sinyal kontinu, perlu ADC | LM35, LDR, MQ-135, pH | ADC pin |
| Digital On/Off | High/Low saja | PIR, Reed switch, Button | GPIO input |
| Digital Protokol | Data terstruktur via bus | BME280, MPU6050 | I2C / SPI |
| PWM/Pulse | Lebar pulsa bervariasi | Some CO2 sensors | Timer capture |
| Serial/UART | Data serial asynchronous | GPS NEO-6M, PM2.5 | UART pins |
3. ADC (Analog to Digital Converter)
ADC adalah konverter yang mengubah sinyal analog (kontinu) menjadi sinyal digital (diskrit) yang bisa diproses oleh microcontroller. Hampir semua sensor analog memerlukan ADC untuk dibaca oleh ESP32, Arduino, atau microcontroller lainnya.
Konsep Dasar ADC
| Parameter | ESP32 | Arduino Uno | STM32 |
|---|---|---|---|
| Resolusi | 12-bit (4096 level) | 10-bit (1024 level) | 12-bit (4096 level) |
| Voltage Reference | 0 - 3.3V | 0 - 5V | 0 - 3.3V |
| Jumlah Channel | 18 channel | 6 channel (A0-A5) | Bervariasi (10-16) |
| Sampling Rate | Hingga 2 MSPS | ~10 KSPS | Hingga 5.33 MSPS |
| ADC Pin | GPIO 32-39 | A0 - A5 | PA0 - PA7 |
Cara Kerja ADC
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β CARA KERJA ADC β β β β Sinyal Analog (kontinu): β β β β Voltage β β 3.3V β€ β±β² β±β² β±β² β β β β± β² β± β² β± β² β β 1.65Vβ€ββββββββ±βββββ²βββββ±βββββ²βββββ±βββββ²βββ β β β β± β² β± β² β± β² β β 0V β€ββββββ±βββββββββ²β±βββββββββ²β±βββββββββ²βββΊ time β β β β Setelah ADC (diskritisasi): β β β β Value β β 4095 β€ ββββ ββββ ββββ β β β ββ ββ ββ ββ ββ ββ β β 2048 β€ββββββββββββββββββββββββββββββββ β β β ββ ββ ββ ββ ββ ββ β β 0 β€ββ ββββ ββββ β β β β β Rumus: Digital Value = (Vin / Vref) Γ (2^resolusi - 1)β β ESP32: Value = (Vin / 3.3) Γ 4095 β β Arduino: Value = (Vin / 5.0) Γ 1023 β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Membaca Sensor Analog dengan ESP32
// Membaca sensor LM35 (suhu) dengan ESP32
// LM35: Output 10mV per Β°C
#define LM35_PIN 34 // ADC1 Channel 6
#define LDR_PIN 35 // ADC1 Channel 7
#define POT_PIN 36 // ADC1 Channel 0 (VP)
// ESP32 ADC non-linear, perlu kalibrasi
// Gunakan analogReadMilliVolts() untuk hasil lebih akurat
void setup() {
Serial.begin(115200);
analogReadResolution(12); // 12-bit (0-4095)
analogSetAttenuation(ADC_11db); // Range 0-3.3V
}
void loop() {
// Baca sensor LM35
int raw_lm35 = analogRead(LM35_PIN);
float voltage_lm35 = analogReadMilliVolts(LM35_PIN) / 1000.0;
float suhu = voltage_lm35 / 0.01; // 10mV per Β°C
// Baca LDR (Light Dependent Resistor)
int raw_ldr = analogRead(LLDR_PIN);
// Konversi ke persentase lux (approximate)
int persen_cahaya = map(raw_ldr, 0, 4095, 0, 100);
// Baca Potensiometer
int raw_pot = analogRead(POT_PIN);
float persen_pot = (raw_pot / 4095.0) * 100.0;
// Tampilkan hasil
Serial.printf("LM35 - Raw: %d, Voltage: %.3fV, Suhu: %.2fΒ°C\n",
raw_lm35, voltage_lm35, suhu);
Serial.printf("LDR - Raw: %d, Cahaya: %d%%\n",
raw_ldr, persen_cahaya);
Serial.printf("Pot - Raw: %d, Posisi: %.1f%%\n",
raw_pot, persen_pot);
Serial.println("---");
delay(1000);
}
Multiple Average untuk Akurasi
// Pembacaan ADC dengan averaging untuk mengurangi noise
// Berguna untuk sensor seperti pH meter dan turbidity
class ADCReader {
private:
int _pin;
int _samples;
public:
ADCReader(int pin, int samples = 10) {
_pin = pin;
_samples = samples;
}
// Baca dengan averaging
float readAverage() {
long sum = 0;
for (int i = 0; i < _samples; i++) {
sum += analogRead(_pin);
delayMicroseconds(100); // Delay kecil antar sample
}
return (float)sum / _samples;
}
// Baca dengan median (lebih tahan terhadap spike)
float readMedian() {
int readings[_samples];
for (int i = 0; i < _samples; i++) {
readings[i] = analogRead(_pin);
delayMicroseconds(100);
}
// Sort readings
for (int i = 0; i < _samples - 1; i++) {
for (int j = i + 1; j < _samples; j++) {
if (readings[i] > readings[j]) {
int temp = readings[i];
readings[i] = readings[j];
readings[j] = temp;
}
}
}
return readings[_samples / 2]; // Median
}
// Baca voltage langsung (ESP32)
float readVoltage() {
float avg = readAverage();
return (avg / 4095.0) * 3.3;
}
};
// Penggunaan
ADCReader sensorPH(34, 20); // 20 samples untuk pH
ADCReader sensorTurbid(35, 15); // 15 samples untuk turbidity
void loop() {
float ph_raw = sensorPH.readMedian();
float ph_voltage = sensorPH.readVoltage();
float ph_value = 3.5 * ph_voltage + 0.0; // Rumus kalibrasi
float turbid_raw = sensorTurbid.readAverage();
Serial.printf("pH: %.2f (V: %.3f)\n", ph_value, ph_voltage);
Serial.printf("Turbidity: %.0f\n", turbid_raw);
delay(2000);
}
ADC pada ESP32 memiliki non-linearity yang cukup signifikan, terutama di ujung bawah (0-100mV) dan atas (3.0-3.3V). Gunakan fungsi analogReadMilliVolts() atau kalibrasi manual untuk akurasi yang lebih baik. Untuk aplikasi presisi tinggi, gunakan ADC eksternal seperti ADS1115 (16-bit, I2C).
4. Komunikasi I2C
I2C (Inter-Integrated Circuit) adalah protokol komunikasi serial dua kabel yang dikembangkan oleh Philips (NXP) pada tahun 1982. I2C menggunakan hanya 2 kabel untuk menghubungkan banyak perangkat β sangat efisien untuk sistem dengan banyak sensor.
Karakteristik I2C
| Parameter | Nilai |
|---|---|
| Jumlah Kabel | 2 (SDA + SCL) + VCC + GND |
| Kecepatan | Standard: 100 KHz, Fast: 400 KHz, High: 3.4 MHz |
| Topology | Multi-master, multi-slave |
| Alamat Perangkat | 7-bit (128 alamat) atau 10-bit (1024 alamat) |
| Jarak | Hingga ~1 meter (board-level) |
| Pull-up Resistor | Diperlukan pada SDA dan SCL (biasanya 4.7KΞ©) |
Cara Kerja I2C
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β BUS I2C β β β β VCC (3.3V / 5V) β β β β β β ββ΄β ββ΄β β β β β4.7KΞ© β β4.7KΞ© β β ββ¬β ββ¬β β β β β β β SDA ββββΌββββββββββΌββββββββββΌββββββββββ β β β β β β β SCL ββββΌββββββββββΌββββββββββΌββββββββββ β β β β β β β βββββ΄ββββ βββββ΄ββββ βββββ΄ββββ β β βMaster β βSlave 1β βSlave 2β β β βESP32 β βBME280 β βOLED β β β β β β0x76 β β0x3C β β β βββββββββ βββββββββ βββββββββ β β β β Komunikasi: Master β Start β Address β R/W β ACK β β β Data β ACK β ... β Stop β β β β β οΈ Setiap perangkat HARUS punya alamat unik di bus β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Scan Alamat I2C
#include <Wire.h>
// I2C Scanner β cari semua device di bus I2C
// Berguna untuk menemukan alamat sensor baru
#define SDA_PIN 21 // ESP32 default SDA
#define SCL_PIN 22 // ESP32 default SCL
void setup() {
Serial.begin(115200);
Wire.begin(SDA_PIN, SCL_PIN);
Serial.println("I2C Scanner - Scanning...");
Serial.println(" 0 1 2 3 4 5 6 7 8 9 A B C D E F");
int deviceCount = 0;
for (byte addr = 1; addr < 127; addr++) {
if (addr % 16 == 0) {
Serial.printf("\n0x%02X:", addr);
}
Wire.beginTransmission(addr);
byte error = Wire.endTransmission();
if (error == 0) {
Serial.printf(" %02X", addr);
deviceCount++;
} else {
Serial.print(" --");
}
}
Serial.printf("\n\nDitemukan %d perangkat I2C\n", deviceCount);
// Tabel alamat umum
Serial.println("\nAlamat Umum:");
Serial.println(" 0x27/0x3F - LCD I2C");
Serial.println(" 0x3C/0x3D - OLED SSD1306");
Serial.println(" 0x48 - ADS1115 (ADC)");
Serial.println(" 0x68 - MPU6050 (IMU)");
Serial.println(" 0x76/0x77 - BME280/BMP280");
Serial.println(" 0x23 - BH1750 (Light)");
Serial.println(" 0x5A - SGP30 (Gas)");
}
void loop() {}
Membaca BME280 via I2C
#include <Wire.h>
#include <Adafruit_BME280.h>
// I2C Pins untuk ESP32
#define SDA_PIN 21
#define SCL_PIN 22
Adafruit_BME280 bme; // I2C (default address 0x76)
void setup() {
Serial.begin(115200);
Wire.begin(SDA_PIN, SCL_PIN);
// Inisialisasi BME280
if (!bme.begin(0x76)) {
Serial.println("BME280 tidak ditemukan!");
Serial.println("Cek wiring dan alamat I2C (0x76 atau 0x77)");
while (1);
}
// Konfigurasi sensor
bme.setSampling(
Adafruit_BME280::MODE_NORMAL, // Mode operasi
Adafruit_BME280::SAMPLING_X2, // Temperature oversampling
Adafruit_BME280::SAMPLING_X16, // Pressure oversampling
Adafruit_BME280::SAMPLING_X1, // Humidity oversampling
Adafruit_BME280::FILTER_X16, // IIR Filter
Adafruit_BME280::STANDBY_MS_500 // Standby time
);
Serial.println("BME280 siap!");
}
void loop() {
float suhu = bme.readTemperature(); // Β°C
float tekanan = bme.readPressure() / 100.0; // hPa
float kelembaban = bme.readHumidity(); // %
float altitude = bme.readAltitude(1013.25); // meter (QNH)
Serial.printf("Suhu : %.2f Β°C\n", suhu);
Serial.printf("Tekanan : %.2f hPa\n", tekanan);
Serial.printf("Kelembaban: %.2f %%\n", kelembaban);
Serial.printf("Altitude : %.2f m\n", altitude);
Serial.println("========================");
delay(2000);
}
5. Komunikasi SPI
SPI (Serial Peripheral Interface) adalah protokol komunikasi serial synchronous yang dikembangkan oleh Motorola. SPI lebih cepat dari I2C dan cocok untuk aplikasi yang membutuhkan throughput tinggi seperti display, ADC berkecepatan tinggi, dan SD card.
Karakteristik SPI
| Parameter | Nilai |
|---|---|
| Jumlah Kabel | 4 (MOSI, MISO, SCK, SS/CS) + VCC + GND |
| Kecepatan | Hingga beberapa MHz (biasanya 1-10 MHz, max ~80 MHz) |
| Topology | Single master, multi-slave (dengan CS per slave) |
| Duplex | Full duplex (bisa kirim dan terima bersamaan) |
| Jarak | Hingga ~30 cm (board-level) |
| Pull-up Resistor | Tidak diperlukan |
SPI Pin dan Fungsi
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β BUS SPI β β β β βββββββββββ βββββββββββ βββββββββββ β β β MASTER β β SLAVE 1 β β SLAVE 2 β β β β ESP32 β β MCP3008 β β SD Card β β β β β β (ADC) β β Module β β β β MOSI βββΌβββββββββΊβ MOSI β β MOSI β β β β MISO βββΌββββββββββ MISO β β MISO β β β β SCK βββΌβββββββββΊβ SCK β β SCK β β β β CS1 βββΌβββββββββΊβ CS β β β β β β CS2 βββΌββββββββββΌββββββββββΌβββββββββΊβ CS β β β βββββββββββ βββββββββββ βββββββββββ β β β β MOSI = Master Out, Slave In (data dari master) β β MISO = Master In, Slave Out (data dari slave) β β SCK = Serial Clock (clock dari master) β β CS = Chip Select (aktifkan slave tertentu) β β β β β Full Duplex: Kirim & terima bersamaan β β β Sangat cepat: cocok untuk high-speed data β β β Setiap slave butuh 1 pin CS di master β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
MCP3008 β ADC Eksternal via SPI
#include <SPI.h>
// MCP3008: 10-bit ADC, 8 channel, SPI interface
// Cocok untuk ESP32 yang ADC-nya non-linear
#define CS_PIN 5 // Chip Select (GPIO 5)
#define SPI_FREQ 1000000 // 1 MHz SPI clock
void setup() {
Serial.begin(115200);
SPI.begin(); // Default: SCK=18, MISO=19, MOSI=23
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH); // Deselect
Serial.println("MCP3008 ADC via SPI siap!");
}
int readMCP3008(int channel) {
// MCP3008 protocol:
// Byte 1: Start bit (1) + Single-ended (1) + Channel (3 bit)
// Byte 2: Don't care (8 clock)
// Byte 3: Don't care (4 clock) + Data (4 bit MSB)
// Byte 4: Data (8 bit LSB) β tapi kita terima di byte 3 & 4
byte command = 0b11000000 | (channel << 3);
SPI.beginTransaction(SPISettings(SPI_FREQ, MSBFIRST, SPI_MODE0));
digitalWrite(CS_PIN, LOW); // Select MCP3008
SPI.transfer(command); // Kirim command
byte highByte = SPI.transfer(0x00); // Terima 5 bit
byte lowByte = SPI.transfer(0x00); // Terima 8 bit
digitalWrite(CS_PIN, HIGH); // Deselect
SPI.endTransaction();
// Gabungkan 10-bit result
int result = ((highByte & 0x1F) << 8) | lowByte;
return result; // 0 - 1023
}
void loop() {
// Baca semua 8 channel
for (int ch = 0; ch < 8; ch++) {
int raw = readMCP3008(ch);
float voltage = (raw / 1023.0) * 3.3; // Vref = 3.3V
Serial.printf("CH%d: %d (%.3fV) ", ch, raw, voltage);
}
Serial.println();
delay(1000);
}
6. Komunikasi UART/Serial
UART (Universal Asynchronous Receiver-Transmitter) adalah protokol komunikasi serial asynchronous yang paling banyak digunakan. UART tidak memerlukan clock line β komunikasi disinkronisasi berdasarkan baud rate yang disepakati kedua belah pihak.
Karakteristik UART
| Parameter | Nilai |
|---|---|
| Jumlah Kabel | 2 minimum (TX + RX) + GND |
| Baud Rate Umum | 9600, 19200, 38400, 57600, 115200 |
| Topology | Point-to-point (2 device) |
| Duplex | Full duplex (TX dan RX terpisah) |
| Synchronization | Asynchronous (tidak perlu clock) |
| Data Frame | Start bit + Data (5-9 bit) + Parity + Stop bit |
UART Data Frame
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β UART DATA FRAME β β β β Idle βββ ββββ ββββββββββββββββββββ ββββ βββ Idle β β HIGH β β β β DATA BITS β β β β β β β βS β β D0 D1 D2 D3 D4 β βP β βS β β ββββ ββββ D5 D6 D7 ββββ ββββ ββ β β ββββββββββββββββββββ β β β β S = Start bit (selalu LOW, 1 bit) β β D0-D7 = Data bits (8 bits, LSB first) β β P = Parity bit (opsional: even/odd) β β S = Stop bit (selalu HIGH, 1 atau 2 bit) β β β β Contoh mengirim karakter 'A' (0x41 = 01000001): β β β β IdleβStartβ 1 β 0 β 0 β 0 β 0 β 0 β 1 β 0 βStopβIdleβ β HIGHβ LOW β β β β β β β β βHIGHβHIGH β β β β β οΈ TX device β RX device (sambung silang!) β β TX1 βββββΊ RX2 β β RX1 βββββ TX2 β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
GPS Module dengan UART
#include <TinyGPS++.h>
#include <HardwareSerial.h>
// GPS Module NEO-6M via UART
// Baud rate default GPS: 9600
#define GPS_RX_PIN 16 // ESP32 RX β GPS TX
#define GPS_TX_PIN 17 // ESP32 TX β GPS RX (opsional)
TinyGPSPlus gps;
HardwareSerial SerialGPS(2); // UART2 pada ESP32
void setup() {
Serial.begin(115200);
SerialGPS.begin(9600, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
Serial.println("GPS Module via UART siap!");
Serial.println("Menunggu sinyal GPS...");
}
void loop() {
// Baca data GPS dari UART
while (SerialGPS.available() > 0) {
char c = SerialGPS.read();
gps.encode(c);
}
// Tampilkan data jika ada update
if (gps.location.isUpdated()) {
double lat = gps.location.lat();
double lng = gps.location.lng();
double alt = gps.altitude.meters();
int sats = gps.satellites.value();
Serial.printf("Lat: %.6f, Lng: %.6f\n", lat, lng);
Serial.printf("Altitude: %.1f m, Satellites: %d\n", alt, sats);
// Cek kualitas fix
if (gps.hdop.isValid()) {
Serial.printf("HDOP: %.1f ", gps.hdop.hdop());
if (gps.hdop.hdop() < 2) Serial.println("(Excellent)");
else if (gps.hdop.hdop() < 5) Serial.println("(Good)");
else Serial.println("(Poor)");
}
}
// Tampilkan raw NMEA (debug)
// while (SerialGPS.available()) {
// Serial.write(SerialGPS.read());
// }
}
7. Perbandingan Protokol Komunikasi
Memilih protokol komunikasi yang tepat sangat penting untuk kesuksesan proyek IoT Anda. Berikut perbandingan lengkap antara ADC, I2C, SPI, dan UART.
Tabel Perbandingan Lengkap
| Aspek | Analog/ADC | I2C | SPI | UART |
|---|---|---|---|---|
| Jumlah Kabel | 1 (+ GND) | 2 (+ VCC + GND) | 4 (+ VCC + GND) | 2 (+ GND) |
| Kecepatan | Tergantung ADC | 100K-3.4M Hz | 1-80 MHz | 9600-115200+ baud |
| Duplex | N/A | Half duplex | Full duplex | Full duplex |
| Multi-device | 1 pin/device | 128+ device | Perlu CS/device | Point-to-point |
| Jarak | ~1 meter | ~1 meter | ~30 cm | ~15 meter |
| Pin Usage | 1 per sensor | 2 shared | 4+N CS | 2 per connection |
| Kompleksitas | π’ Rendah | π‘ Sedang | π‘ Sedang | π’ Rendah |
| Cocok untuk | Sensor analog | Banyak sensor | High speed | Module serial |
Kapan Menggunakan Apa?
- Analog/ADC β Sensor sederhana (suhu LM35, cahaya LDR, pH meter) yang outputnya tegangan analog
- I2C β Banyak sensor digital di satu bus (BME280, MPU6050, OLED). Pin-efficient!
- SPI β Butuh kecepatan tinggi (SD card, display TFT, ADC presisi, radio LoRa)
- UART β Module yang sudah punya serial interface (GPS, Bluetooth, PM2.5 sensor)
8. Kalibrasi Sensor
Kalibrasi adalah proses menyesuaikan pembacaan sensor dengan nilai referensi yang diketahui untuk memastikan akurasi. Tanpa kalibrasi, data sensor bisa menyimpang jauh dari nilai sebenarnya.
Metode Kalibrasi
| Metode | Cara Kerja | Cocok Untuk |
|---|---|---|
| Offset | Tambah/kurangi nilai konstan | Drift yang konsisten |
| Linear | y = mx + b (slope dan intercept) | Hubungan linear |
| Polynomial | y = axΒ² + bx + c | Hubungan non-linear |
| Lookup Table | Tabel mapping input β output | Kurva kompleks |
| Multi-point | Kalibrasi di beberapa titik referensi | Akurasi tinggi |
Kalibrasi pH Meter
// Kalibrasi pH meter dengan 2 titik referensi
// Larutan buffer pH 4.0 dan pH 7.0
class PHSensor {
private:
int _pin;
float _acidVoltage; // Voltage di pH 4.0
float _neutralVoltage; // Voltage di pH 7.0
float _slope;
float _intercept;
public:
PHSensor(int pin) {
_pin = pin;
_acidVoltage = 1.650; // Default, harus dikalibrasi
_neutralVoltage = 1.250; // Default, harus dikalibrasi
_calculateCoefficients();
}
// Kalibrasi dengan 2 titik
void calibrateTwoPoint(float ph1, float voltage1,
float ph2, float voltage2) {
// ph1 = 7.0 (neutral), voltage1 = voltage saat pH 7.0
// ph2 = 4.0 (acid), voltage2 = voltage saat pH 4.0
_neutralVoltage = voltage1;
_acidVoltage = voltage2;
_calculateCoefficients();
Serial.printf("Kalibrasi: slope=%.4f, intercept=%.4f\n",
_slope, _intercept);
}
void _calculateCoefficients() {
// Linear: pH = slope * voltage + intercept
_slope = (7.0 - 4.0) / (_neutralVoltage - _acidVoltage);
_intercept = 7.0 - _slope * _neutralVoltage;
}
float readVoltage() {
long sum = 0;
for (int i = 0; i < 40; i++) {
sum += analogRead(_pin);
delay(10);
}
float avg = sum / 40.0;
return (avg / 4095.0) * 3.3;
}
float readPH() {
float voltage = readVoltage();
float ph = _slope * voltage + _intercept;
return constrain(ph, 0.0, 14.0);
}
};
// Setup kalibrasi
PHSensor phSensor(34);
void setup() {
Serial.begin(115200);
delay(3000);
// Mode kalibrasi
Serial.println("=== KALIBRASI pH METER ===");
Serial.println("Celupkan ke larutan pH 7.0, lalu ketik 'ok'");
while (!Serial.available()) {}
float voltage7 = phSensor.readVoltage();
Serial.printf("Voltage pH 7.0: %.4f V\n", voltage7);
Serial.println("Celupkan ke larutan pH 4.0, lalu ketik 'ok'");
while (!Serial.available()) {}
float voltage4 = phSensor.readVoltage();
Serial.printf("Voltage pH 4.0: %.4f V\n", voltage4);
phSensor.calibrateTwoPoint(7.0, voltage7, 4.0, voltage4);
Serial.println("Kalibrasi selesai!");
}
void loop() {
float ph = phSensor.readPH();
Serial.printf("pH: %.2f\n", ph);
delay(2000);
}
Kalibrasi Sensor Suhu (NTC Thermistor)
// NTC Thermistor dengan Steinhart-Hart equation
// Lebih akurat dari linearisasi sederhana
#include <math.h>
#define THERMISTOR_PIN 34
#define R_FIXED 10000.0 // Resistor tetap 10KΞ©
#define R_NTC_25 10000.0 // Resistansi NTC pada 25Β°C
#define B_COEFFICIENT 3950 // Beta coefficient dari datasheet
float readNTCTemperature() {
// Baca ADC
int raw = analogRead(THERMISTOR_PIN);
// Hitung resistansi NTC
// Voltage divider: Vout = Vcc * R_fixed / (R_ntc + R_fixed)
float voltage = (raw / 4095.0) * 3.3;
float r_ntc = R_FIXED * (3.3 / voltage - 1.0);
// Steinhart-Hart (simplified with B coefficient)
// 1/T = 1/T0 + (1/B) * ln(R/R0)
float tempK = 1.0 / (
(1.0 / 298.15) + // 1/T0 (T0=25Β°C=298.15K)
(1.0 / B_COEFFICIENT) * log(r_ntc / R_NTC_25)
);
float tempC = tempK - 273.15;
// Atau gunakan persamaan Steinhart-Hart penuh (3 titik kalibrasi):
// 1/T = A + B*ln(R) + C*(ln(R))^3
// Perlu mengukur resistansi pada 3 suhu berbeda
return tempC;
}
void setup() {
Serial.begin(115200);
}
void loop() {
float suhu = readNTCTemperature();
Serial.printf("Suhu: %.2f Β°C\n", suhu);
delay(1000);
}
9. Noise Filtering dan Signal Processing
Sinyal dari sensor sering terganggu oleh noise (gangguan listrik) dari berbagai sumber seperti motor, switching power supply, dan interferensi RF. Filtering sangat penting untuk mendapatkan data yang bersih dan akurat.
Jenis Filter
| Filter | Kompleksitas | Kelebihan | Kekurangan |
|---|---|---|---|
| Moving Average | π’ Mudah | Mudah implementasi, halus | Lambat merespon perubahan cepat |
| Exponential Moving Average | π’ Mudah | Responsif, hemat memory | Perlu tuning alpha |
| Median Filter | π‘ Sedang | Tahan spike/outlier | Kurang halus |
| Kalman Filter | π΄ Kompleks | Optimal untuk noisy data | Sulit implementasi dan tuning |
| IIR/Butterworth | π‘ Sedang | Fleksibel (low/high/band pass) | Perlu koefisien filter |
Implementasi Filter
// Implementasi berbagai filter untuk sensor
// 1. Moving Average Filter
class MovingAverage {
private:
float* _buffer;
int _size;
int _index;
float _sum;
bool _filled;
public:
MovingAverage(int size) {
_size = size;
_buffer = new float[size]();
_index = 0;
_sum = 0;
_filled = false;
}
float update(float value) {
_sum -= _buffer[_index];
_buffer[_index] = value;
_sum += value;
_index = (_index + 1) % _size;
if (_index == 0) _filled = true;
return _sum / (_filled ? _size : _index);
}
};
// 2. Exponential Moving Average (EMA)
class EMAFilter {
private:
float _alpha;
float _ema;
bool _initialized;
public:
EMAFilter(float alpha) {
_alpha = constrain(alpha, 0.0, 1.0);
_ema = 0;
_initialized = false;
}
float update(float value) {
if (!_initialized) {
_ema = value;
_initialized = true;
} else {
_ema = _alpha * value + (1.0 - _alpha) * _ema;
}
return _ema;
}
};
// 3. Median Filter
class MedianFilter {
private:
float* _buffer;
int _size;
int _index;
bool _filled;
public:
MedianFilter(int size) {
_size = size;
_buffer = new float[size]();
_index = 0;
_filled = false;
}
float update(float value) {
_buffer[_index] = value;
_index = (_index + 1) % _size;
if (_index == 0) _filled = true;
int count = _filled ? _size : _index;
// Copy and sort
float sorted[count];
for (int i = 0; i < count; i++) sorted[i] = _buffer[i];
for (int i = 0; i < count - 1; i++) {
for (int j = i + 1; j < count; j++) {
if (sorted[i] > sorted[j]) {
float t = sorted[i]; sorted[i] = sorted[j]; sorted[j] = t;
}
}
}
return sorted[count / 2];
}
};
// Penggunaan
MovingAverage filterMA(10); // 10-sample moving average
EMAFilter filterEMA(0.1); // alpha=0.1 (halus)
MedianFilter filterMed(7); // 7-sample median
void loop() {
float raw = analogRead(34);
float filtered_ma = filterMA.update(raw);
float filtered_ema = filterEMA.update(raw);
float filtered_med = filterMed.update(raw);
Serial.printf("Raw: %.0f | MA: %.1f | EMA: %.1f | Med: %.1f\n",
raw, filtered_ma, filtered_ema, filtered_med);
delay(100);
}
10. Proyek Multi-Sensor
Mari gabungkan semua pengetahuan dalam satu proyek: Stasiun Monitoring Lingkungan dengan beberapa sensor yang menggunakan berbagai protokol berbeda.
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <BH1750.h>
#include <DHT.h>
// ========== KONFIGURASI PIN ==========
// I2C Bus (shared)
#define SDA_PIN 21
#define SCL_PIN 22
// Analog Sensors
#define MQ135_PIN 34 // Gas sensor (ADC)
#define LM35_PIN 35 // Suhu reference (ADC)
// Digital Sensors
#define DHT_PIN 4 // DHT22 (One-wire digital)
// ========== SENSOR OBJECTS ==========
// I2C sensors
Adafruit_BME280 bme; // BME280 (0x76) - Suhu, Tekanan, Lembab
BH1750 lightMeter(0x23); // BH1750 - Cahaya (lux)
// Digital sensor
DHT dht(DHT_PIN, DHT22); // DHT22 - Backup suhu & lembab
// Analog filter
float filterAnalog(int pin, int samples = 20) {
long sum = 0;
for (int i = 0; i < samples; i++) {
sum += analogRead(pin);
delayMicroseconds(200);
}
return (float)sum / samples;
}
void setup() {
Serial.begin(115200);
// Init I2C bus
Wire.begin(SDA_PIN, SCL_PIN);
// Init BME280
if (!bme.begin(0x76)) {
Serial.println("β οΈ BME280 tidak ditemukan!");
} else {
Serial.println("β
BME280 OK");
}
// Init BH1750
if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
Serial.println("β
BH1750 OK");
} else {
Serial.println("β οΈ BH1750 tidak ditemukan!");
}
// Init DHT22
dht.begin();
Serial.println("β
DHT22 OK");
// ADC config
analogReadResolution(12);
analogSetAttenuation(ADC_11db);
Serial.println("\n=== Stasiun Monitoring Lingkungan ===\n");
}
void loop() {
// ===== BACA I2C SENSORS =====
float bme_suhu = bme.readTemperature();
float bme_lembab = bme.readHumidity();
float bme_tekanan = bme.readPressure() / 100.0;
float lux = lightMeter.readLightLevel();
// ===== BACA DIGITAL SENSOR =====
float dht_suhu = dht.readTemperature();
float dht_lembab = dht.readHumidity();
// ===== BACA ANALOG SENSORS =====
float mq135_raw = filterAnalog(MQ135_PIN);
float mq135_voltage = (mq135_raw / 4095.0) * 3.3;
float lm35_raw = filterAnalog(LM35_PIN);
float lm35_voltage = (lm35_raw / 4095.0) * 3.3;
float lm35_suhu = lm35_voltage / 0.01;
// ===== KALIBRASI & VALIDASI =====
// Cross-check suhu dari 3 sensor
float suhu_avg = bme_suhu;
int suhu_count = 1;
if (!isnan(dht_suhu)) {
suhu_avg += dht_suhu;
suhu_count++;
}
suhu_avg += lm35_suhu;
suhu_count++;
suhu_avg /= suhu_count;
// ===== TAMPILKAN HASIL =====
Serial.println("ββββββββββββββββββββββββββββββββββββββββ");
Serial.println("β STASIUN MONITORING LINGKUNGAN β");
Serial.println("β βββββββββββββββββββββββββββββββββββββββ£");
Serial.printf("β Suhu : %.2f Β°C (rata-rata 3 sensor)\n", suhu_avg);
Serial.printf("β β BME280: %.2f Β°C\n", bme_suhu);
Serial.printf("β β DHT22 : %.2f Β°C\n", dht_suhu);
Serial.printf("β β LM35 : %.2f Β°C\n", lm35_suhu);
Serial.printf("β Kelembaban: %.1f %% (BME280)\n", bme_lembab);
Serial.printf("β Tekanan : %.1f hPa\n", bme_tekanan);
Serial.printf("β Cahaya : %.1f lux\n", lux);
Serial.printf("β Udara (MQ135): %.3f V\n", mq135_voltage);
Serial.println("ββββββββββββββββββββββββββββββββββββββββ\n");
delay(5000);
}
11. Best Practices
| Aspek | Best Practice | Hindari |
|---|---|---|
| Wiring | Gunakan kabel pendek, twisted pair untuk I2C | Kabel panjang tanpa shielding |
| Power | Decoupling capacitor 100nF dekat sensor | Power langsung dari pin MCU |
| ADC | Multi-sample averaging, kalibrasi | Single read tanpa filtering |
| I2C | Pull-up 4.7KΞ©, scan bus saat startup | Asumsi alamat, skip error handling |
| SPI | SPI.beginTransaction() setiap transaksi | SPI tanpa CS management |
| Error Handling | Cek return value, timeout, retry | Assume sensor selalu bekerja |
| Kalibrasi | Kalibrasi berkala, simpan offset di EEPROM | Pakai default tanpa kalibrasi |
12. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang sensor interfacing: