#include <Wire.h> // Untuk komunikasi I2C
#include <Adafruit_GFX.h> // Library grafis dasar
#include <Adafruit_SSD1306.h> // Library untuk OLED
#include <INA226_WE.h> // Library untuk INA226_WE - PASTIKAN INI TERINSTAL!
#include <Preferences.h> // Untuk menyimpan data di NVS (Non-Volatile Storage) ESP32
// --- DEKLARASI PIN ESP32 ---
#define OLED_RESET -1 // OLED terhubung ke pin reset ESP32 (biasanya tidak perlu)
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Pin untuk Rotary Encoder
#define ROTARY_A_PIN 32 // Ganti dengan pin GPIO yang Anda gunakan
#define ROTARY_B_PIN 33 // Ganti dengan pin GPIO yang Anda gunakan
#define ROTARY_SW_PIN 25 // Pin untuk tombol push Rotary Encoder
// Pin untuk Relay
#define RELAY_PIN 26 // Ganti dengan pin GPIO yang terhubung ke modul relay
// --- OBJEK SENSOR ---
INA226_WE ina226; // Membuat objek INA226 menggunakan library INA226_WE
// --- OBJEK NVS ---
Preferences preferences;
// --- PARAMETER AKI & BATAS ---
float batasTeganganBawah = 10.8; // Nilai default, akan dimuat dari NVS
float batasTeganganAtas = 14.8; // Nilai default, akan dimuat dari NVS
// --- VARIABEL PENGUKURAN ---
float teganganAki = 0.0;
// --- VARIABEL KONTROL MODE & UI ---
enum ModeAlat { MODE_NORMAL, MODE_SETTING_BAWAH, MODE_SETTING_ATAS };
ModeAlat currentMode = MODE_NORMAL;
// Untuk Rotary Encoder
volatile int encoderPos = 0; // Posisi encoder saat ini (volatile karena diakses dari ISR)
int lastEncoderPos = 0; // Posisi encoder terakhir yang dibaca di loop
// Untuk Tombol Push (Long Press & Single Press)
unsigned long pushButtonPressTime = 0;
bool buttonPressed = false;
bool longPressTriggered = false; // Flag untuk memastikan long press hanya terpicu sekali
const long LONG_PRESS_DURATION = 3000; // 3 detik
const long DEBOUNCE_DELAY = 50; // Millidetik untuk debounce tombol
// Untuk Kedip-kedip Teks di OLED
bool blinkState = false;
unsigned long lastBlinkMillis = 0;
const long BLINK_INTERVAL = 500; // 500 ms
// --- PROTOTYPE FUNGSI ---
// Deklarasi fungsi agar bisa dipanggil sebelum didefinisikan
void IRAM_ATTR readEncoder();
void IRAM_ATTR handleButtonInterrupt();
void saveSettings();
void loadSettings();
void updateOLEDDisplay();
void checkLongPress(); // Deklarasi untuk fungsi checkLongPress
// --- FUNGSI SETUP ---
void setup() {
Serial.begin(115200);
// --- Inisialisasi OLED ---
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Alamat I2C umum untuk OLED 0.96"
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Jangan lanjutkan jika gagal
}
display.display();
delay(1000);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Inisialisasi...");
display.display();
delay(500);
// --- Inisialisasi INA226 ---
Wire.begin(); // PENTING: Inisialisasi komunikasi I2C sebelum menggunakan sensor
ina226.init(); // Panggil init() untuk memulai inisialisasi sensor
// Karena init() di library Anda tidak mengembalikan boolean dan tidak ada isConnected(),
// kita tidak bisa langsung mengecek di sini. Asumsikan koneksi berhasil.
Serial.println("INA226 ditemukan (asumsi koneksi OK).");
// KONFIGURASI INA226 (disesuaikan dengan INA226_WE.h yang Anda berikan)
// Default shunt resistor untuk INA226_WE adalah 0.1 Ohm.
// Jika shunt Anda berbeda (misal 0.01 Ohm), aktifkan baris ini:
// ina226.setShuntResistor(0.01); // Ganti 0.01 dengan nilai shunt resistor Anda
// Menggunakan setAverage dan konstanta AVERAGE_4 dari INA226_WE.h
ina226.setAverage(AVERAGE_4);
// Menggunakan setConversionTime dan konstanta CONV_TIME_1100 dari INA226_WE.h
ina226.setConversionTime(CONV_TIME_1100);
// Menggunakan setMeasureMode dan konstanta CONTINOUS dari INA226_WE.h
ina226.setMeasureMode(CONTINOUS);
// --- Inisialisasi Rotary Encoder ---
pinMode(ROTARY_A_PIN, INPUT_PULLUP);
pinMode(ROTARY_B_PIN, INPUT_PULLUP);
pinMode(ROTARY_SW_PIN, INPUT_PULLUP); // Tombol push dengan pull-up internal
// Attach Interrupts untuk Rotary Encoder
attachInterrupt(digitalPinToInterrupt(ROTARY_A_PIN), readEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(ROTARY_SW_PIN), handleButtonInterrupt, CHANGE);
// --- Inisialisasi Relay ---
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW); // Pastikan relay OFF saat startup (sesuaikan dengan modul relay Anda, HIGH/LOW)
// --- Muat Pengaturan dari NVS ---
loadSettings();
Serial.print("Batas Bawah: "); Serial.println(batasTeganganBawah);
Serial.print("Batas Atas: "); Serial.println(batasTeganganAtas);
display.clearDisplay();
}
// --- FUNGSI LOOP ---
void loop() {
// --- BACA SENSOR & INPUT ---
// Baca tegangan aki dari INA226
teganganAki = ina226.getBusVoltage_V(); // Fungsi ini umum di INA226_WE
// Anda juga bisa membaca arus jika diperlukan:
// float arusAki = ina226.getCurrent_A();
// Tangani putaran Rotary Encoder
if (encoderPos != lastEncoderPos) {
if (currentMode == MODE_SETTING_BAWAH) {
if (encoderPos > lastEncoderPos) { // Putar Kanan
batasTeganganBawah += 0.1;
} else { // Putar Kiri
batasTeganganBawah -= 0.1;
}
// Batasi range 0.0V hingga 36.0V
if (batasTeganganBawah < 0.0) batasTeganganBawah = 0.0;
if (batasTeganganBawah > 36.0) batasTeganganBawah = 36.0;
} else if (currentMode == MODE_SETTING_ATAS) {
if (encoderPos > lastEncoderPos) { // Putar Kanan
batasTeganganAtas += 0.1;
} else { // Putar Kiri
batasTeganganAtas -= 0.1;
}
// Batasi range 0.0V hingga 36.0V
if (batasTeganganAtas < 0.0) batasTeganganAtas = 0.0;
if (batasTeganganAtas > 36.0) batasTeganganAtas = 36.0;
}
lastEncoderPos = encoderPos; // Update posisi terakhir
// Reset blink state agar tidak terlalu cepat berkedip saat diputar
blinkState = true;
lastBlinkMillis = millis();
}
// --- LOGIKA KONTROL RELAY ---
// Logika histeresis untuk mencegah relay berkedip terlalu cepat
if (currentMode == MODE_NORMAL) { // Hanya aktifkan logika kontrol di mode normal
if (digitalRead(RELAY_PIN) == LOW && teganganAki < batasTeganganBawah) { // Jika relay OFF dan tegangan di bawah batas bawah
digitalWrite(RELAY_PIN, HIGH); // Aktifkan relay
} else if (digitalRead(RELAY_PIN) == HIGH && teganganAki > batasTeganganAtas) { // Jika relay ON dan tegangan di atas batas atas
digitalWrite(RELAY_PIN, LOW); // Nonaktifkan relay
}
} else {
// Saat di mode setting, relay selalu OFF agar aman saat pengaturan
digitalWrite(RELAY_PIN, LOW);
}
// --- UPDATE TAMPILAN OLED ---
updateOLEDDisplay();
// Beri sedikit jeda untuk stabilitas
delay(10);
}
// --- FUNGSI INTERRUPT ROTARY ENCODER ---
// Dipanggil setiap kali pin ROTARY_A_PIN berubah (CHANGE)
void IRAM_ATTR readEncoder() {
static bool lastA = HIGH; // Asumsi PULLUP
static bool lastB = HIGH; // Asumsi PULLUP
bool currentA = digitalRead(ROTARY_A_PIN);
bool currentB = digitalRead(ROTARY_B_PIN);
// Hindari bouncing dengan membaca kedua pin
if (currentA != lastA || currentB != lastB) {
if (currentA == LOW && currentB == HIGH) { // Rotary diputar CW
encoderPos++;
} else if (currentA == HIGH && currentB == LOW) { // Rotary diputar CCW
encoderPos--;
}
}
lastA = currentA;
lastB = currentB;
}
// --- FUNGSI INTERRUPT TOMBOL PUSH ROTARY ---
// Dipanggil setiap kali pin ROTARY_SW_PIN berubah (CHANGE)
void IRAM_ATTR handleButtonInterrupt() {
static unsigned long lastInterruptTime = 0;
unsigned long currentTime = millis();
// Debounce tombol
if (currentTime - lastInterruptTime < DEBOUNCE_DELAY) {
return;
}
lastInterruptTime = currentTime;
// Cek status tombol
if (digitalRead(ROTARY_SW_PIN) == LOW) { // Tombol ditekan
if (!buttonPressed) { // Jika baru ditekan
buttonPressed = true;
pushButtonPressTime = currentTime;
longPressTriggered = false; // Reset flag long press
}
} else { // Tombol dilepas
if (buttonPressed) { // Tombol sebelumnya ditekan dan kini dilepas
if (!longPressTriggered) { // Ini adalah Short Press
if (currentMode == MODE_SETTING_BAWAH) {
currentMode = MODE_SETTING_ATAS; // Pindah ke setting Tegangan Atas
Serial.println("Mode: Setting Atas");
blinkState = true; // Langsung tampilkan berkedip
lastBlinkMillis = millis();
} else if (currentMode == MODE_SETTING_ATAS) {
currentMode = MODE_NORMAL; // Keluar dari setting
saveSettings(); // Simpan pengaturan
Serial.println("Mode: Normal, Pengaturan Disimpan");
}
}
buttonPressed = false;
longPressTriggered = false;
}
}
}
// --- FUNGSI UNTUK CEK LONG PRESS (Dipanggil di loop utama) ---
// Note: Lebih baik deteksi long press di loop utama daripada ISR
void checkLongPress() {
if (buttonPressed && !longPressTriggered) {
if (millis() - pushButtonPressTime >= LONG_PRESS_DURATION) {
// Long press terdeteksi
if (currentMode == MODE_NORMAL) {
currentMode = MODE_SETTING_BAWAH; // Masuk ke menu setting Tegangan Bawah
Serial.println("Mode: Setting Bawah (Long Press)");
} else {
// Jika sudah di mode setting, long press bisa jadi untuk keluar dan simpan
currentMode = MODE_NORMAL;
saveSettings(); // Simpan pengaturan
Serial.println("Mode: Normal (Long Press Keluar)");
}
longPressTriggered = true; // Set flag agar tidak terpicu berulang
blinkState = true; // Langsung tampilkan berkedip
lastBlinkMillis = millis();
}
}
}
// --- FUNGSI UPDATE TAMPILAN OLED ---
void updateOLEDDisplay() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// Panggil checkLongPress() secara berkala di sini
// Agar logika long press bisa terdeteksi dan tidak macet
checkLongPress();
if (currentMode == MODE_NORMAL) {
display.setCursor(0, 0);
display.println("Tegangan Aki:");
display.setTextSize(2);
display.setCursor(0, 16);
display.print(String(teganganAki, 1)); // Tampilkan 1 desimal
display.println(" V");
// Tampilkan status relay
display.setTextSize(1);
display.setCursor(0, 40);
if (digitalRead(RELAY_PIN) == HIGH) { // Sesuaikan HIGH/LOW untuk ON
display.println("RELAY ON");
} else {
display.println("RELAY OFF");
}
} else { // Mode Setting
// Logika kedip-kedip teks
if (millis() - lastBlinkMillis > BLINK_INTERVAL) {
blinkState = !blinkState;
lastBlinkMillis = millis();
}
if (currentMode == MODE_SETTING_BAWAH) {
display.setCursor(0, 0);
display.println("SET TEGANGAN BAWAH:");
display.setTextSize(2);
display.setCursor(0, 16);
if (blinkState) {
display.print(String(batasTeganganBawah, 1));
display.println(" V");
} else {
display.println(" "); // Blank space untuk efek kedip
}
display.setTextSize(1);
display.setCursor(0, 40);
display.print("Batas Atas: ");
display.print(String(batasTeganganAtas, 1));
display.println(" V");
} else if (currentMode == MODE_SETTING_ATAS) {
display.setCursor(0, 0);
display.println("SET TEGANGAN ATAS:");
display.setTextSize(2);
display.setCursor(0, 16);
if (blinkState) {
display.print(String(batasTeganganAtas, 1));
display.println(" V");
} else {
display.println(" "); // Blank space untuk efek kedip
}
display.setTextSize(1);
display.setCursor(0, 40);
display.print("Batas Bawah: ");
display.print(String(batasTeganganBawah, 1));
display.println(" V");
}
}
display.display(); // Update OLED
}
// --- FUNGSI SIMPAN/MUAT PENGATURAN KE NVS (Non-Volatile Storage) ---
void saveSettings() {
preferences.begin("charger_app", false); // "charger_app" adalah nama namespace
preferences.putFloat("batasBawah", batasTeganganBawah);
preferences.putFloat("batasAtas", batasTeganganAtas);
preferences.end();
Serial.println("Pengaturan disimpan.");
}
void loadSettings() {
preferences.begin("charger_app", true); // true untuk mode read-only
// Membaca nilai, jika tidak ada, gunakan nilai default yang diberikan
batasTeganganBawah = preferences.getFloat("batasBawah", 10.8);
batasTeganganAtas = preferences.getFloat("batasAtas", 14.8);
preferences.end();
Serial.println("Pengaturan dimuat.");
}
No comments:
Post a Comment