Search This Blog

Monday, 14 July 2025

CARGER AKI CONTROLER INA226-LIBARY WE ROTARI SWITCH DAN PUS LCD 16X 2 TOMBOl OK LCD BERGETAR

 #include <Wire.h> // Untuk komunikasi I2C

#include <LiquidCrystal_I2C.h> // Library untuk LCD I2C - PASTIKAN INI TERINSTAL!

#include <INA226_WE.h> // Library untuk INA226_WE

#include <Preferences.h> // Untuk menyimpan data di NVS (Non-Volatile Storage) ESP32


// --- DEKLARASI PIN ESP32 ---

// Alamat I2C umum untuk LCD 16x2 dengan modul PCF8574 adalah 0x27 atau 0x3F

// Coba 0x27 terlebih dahulu, jika tidak berfungsi, coba 0x3F.

LiquidCrystal_I2C lcd(0x27, 16, 2); // Alamat I2C, Kolom, Baris


// 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_SELECT,      // Mode baru: memilih antara batas bawah/atas

  MODE_SETTING_BAWAH_EDIT,  // Mengedit nilai batas bawah

  MODE_SETTING_ATAS_EDIT    // Mengedit nilai batas 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)

volatile unsigned long pushButtonPressTime = 0; // volatile karena diakses dari ISR

volatile bool buttonPressed = false;           // volatile karena diakses dari ISR

volatile bool longPressTriggered = false;      // volatile karena diakses dari ISR

volatile bool shortPressFlag = false;          // volatile: Flag untuk short press yang dipicu dari ISR


const long LONG_PRESS_DURATION = 3000; // 3 detik

const long DEBOUNCE_DELAY = 50; // Millidetik untuk debounce tombol


// Untuk Kedip-kedip Teks di LCD

bool blinkState = false;

unsigned long lastBlinkMillis = 0;

const long BLINK_INTERVAL = 500; // 500 ms


// Variabel untuk mode pemilihan pengaturan

int selectedSetting = 0; // 0 = Batas Bawah, 1 = Batas Atas


// Variabel untuk tampilan pesan "DISIMPAN"

unsigned long messageDisplayStartTime = 0;

const long MESSAGE_DISPLAY_DURATION = 1000; // 1 detik


// --- PROTOTYPE FUNGSI ---

// Deklarasi fungsi agar bisa dipanggil sebelum didefinisikan

void IRAM_ATTR readEncoder();

void IRAM_ATTR handleButtonInterrupt();

void saveSettings();

void loadSettings();

void updateLCDDisplay();

void checkLongPress();


// --- FUNGSI SETUP ---

void setup() {

  Serial.begin(115200);


  // --- Inisialisasi LCD ---

  lcd.init();      // Inisialisasi LCD

  lcd.backlight(); // Nyalakan backlight

  lcd.clear();

  lcd.setCursor(0, 0);

  lcd.print("Inisialisasi...");

  Serial.println("Inisialisasi LCD...");

  delay(1000);


  // --- Inisialisasi INA226 ---

  Wire.begin(); // PENTING: Inisialisasi komunikasi I2C sebelum menggunakan sensor

  ina226.init(); // Panggil init() untuk memulai inisialisasi sensor


  Serial.println("INA226 ditemukan (asumsi koneksi OK).");


  // KONFIGURASI INA226 (disesuaikan dengan INA226_WE.h yang Anda berikan)

  ina226.setAverage(AVERAGE_4);

  ina226.setConversionTime(CONV_TIME_1100);

  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


  // --- Muat Pengaturan dari NVS ---

  loadSettings();

  Serial.print("Batas Bawah: "); Serial.println(batasTeganganBawah);

  Serial.print("Batas Atas: "); Serial.println(batasTeganganAtas);


  lcd.clear(); // Bersihkan LCD setelah inisialisasi

}


// --- FUNGSI LOOP ---

void loop() {

  // --- BACA SENSOR & INPUT ---

  teganganAki = ina226.getBusVoltage_V();


  // Tangani putaran Rotary Encoder

  if (encoderPos != lastEncoderPos) {

    if (currentMode == MODE_SETTING_BAWAH_EDIT) {

      batasTeganganBawah += (encoderPos > lastEncoderPos) ? 0.1 : -0.1;

      batasTeganganBawah = constrain(batasTeganganBawah, 0.0, 36.0); // Pastikan nilai dalam batas

    } else if (currentMode == MODE_SETTING_ATAS_EDIT) {

      batasTeganganAtas += (encoderPos > lastEncoderPos) ? 0.1 : -0.1;

      batasTeganganAtas = constrain(batasTeganganAtas, 0.0, 36.0); // Pastikan nilai dalam batas

    } else if (currentMode == MODE_SETTING_SELECT) {

      selectedSetting += (encoderPos > lastEncoderPos) ? 1 : -1;

      if (selectedSetting < 0) selectedSetting = 1; // Wrap around

      if (selectedSetting > 1) selectedSetting = 0; // Wrap around

    }

    lastEncoderPos = encoderPos;


    blinkState = true; // Reset blink state untuk memastikan tampilan nilai

    lastBlinkMillis = millis();

  }


  // --- LOGIKA KONTROL RELAY ---

  if (currentMode == MODE_NORMAL) {

    if (digitalRead(RELAY_PIN) == LOW && teganganAki < batasTeganganBawah) {

      digitalWrite(RELAY_PIN, HIGH);

      // Serial.println("Relay ON (Tegangan rendah)"); // Uncomment for debugging

    } else if (digitalRead(RELAY_PIN) == HIGH && teganganAki > batasTeganganAtas) {

      digitalWrite(RELAY_PIN, LOW);

      // Serial.println("Relay OFF (Tegangan tinggi)"); // Uncomment for debugging

    }

  } else {

    digitalWrite(RELAY_PIN, LOW); // Relay OFF saat di mode setting

  }


  // Panggil checkLongPress() secara berkala di sini

  // checkLongPress() akan menangani aksi long press dan mengatur ulang buttonPressed

  checkLongPress();


  // --- TANGANI AKSI SHORT PRESS (DIPINDAHKAN DARI ISR) ---

  if (shortPressFlag) { // Jika ada flag short press dari ISR

    shortPressFlag = false; // Reset flag segera


    if (currentMode == MODE_NORMAL) {

      Serial.println("Short press di Mode Normal. Tidak ada aksi.");

      // Tambahkan aksi lain di sini jika diinginkan, misal: toggle backlight, display info lain

    } else if (currentMode == MODE_SETTING_SELECT) {

      if (selectedSetting == 0) {

        currentMode = MODE_SETTING_BAWAH_EDIT;

        Serial.println("Mode: Edit Batas Bawah");

      } else {

        currentMode = MODE_SETTING_ATAS_EDIT;

        Serial.println("Mode: Edit Batas Atas");

      }

      blinkState = true;

      lastBlinkMillis = millis();

      lcd.clear(); // Clear LCD saat ganti mode

    } else if (currentMode == MODE_SETTING_BAWAH_EDIT || currentMode == MODE_SETTING_ATAS_EDIT) {

      currentMode = MODE_SETTING_SELECT;

      Serial.println("Mode: Kembali ke Pilih Pengaturan");

      blinkState = false; // Berhenti berkedip saat kembali ke pemilihan

      lcd.clear(); // Clear LCD saat ganti mode

    }

  }


  // --- UPDATE TAMPILAN LCD ---

  // Tampilkan pesan "DISIMPAN!" jika sedang aktif

  if (messageDisplayStartTime != 0 && (millis() - messageDisplayStartTime < MESSAGE_DISPLAY_DURATION)) {

    lcd.setCursor(0, 0); lcd.print("PENGATURAN      "); // Tambah spasi untuk hapus sisa

    lcd.setCursor(0, 1); lcd.print("DISIMPAN!       "); // Tambah spasi untuk hapus sisa

  } else {

    // Jika tidak ada pesan, update tampilan normal

    updateLCDDisplay();

    messageDisplayStartTime = 0; // Reset setelah waktu habis

  }


  delay(10);

}


// --- FUNGSI INTERRUPT ROTARY ENCODER ---

void IRAM_ATTR readEncoder() {

  // Teknik ini lebih kuat untuk membaca encoder kuadratur

  // Membaca pin B dan membandingkannya dengan state pin A

  // ketika pin A berubah.

  if (digitalRead(ROTARY_A_PIN) == digitalRead(ROTARY_B_PIN)) {

    encoderPos++; // Putar Searah Jarum Jam (CW)

  } else {

    encoderPos--; // Putar Berlawanan Arah Jarum Jam (CCW)

  }

}


// --- FUNGSI INTERRUPT TOMBOL PUSH ROTARY ---

void IRAM_ATTR handleButtonInterrupt() {

  static unsigned long lastInterruptTime = 0;

  unsigned long currentTime = millis();


  // Debounce logic

  if (currentTime - lastInterruptTime < DEBOUNCE_DELAY) {

    return; // Abaikan jika terlalu cepat setelah interupsi terakhir

  }

  lastInterruptTime = currentTime; // Perbarui waktu interupsi terakhir


  // Hanya set flag di ISR, hindari logika kompleks atau I/O

  if (digitalRead(ROTARY_SW_PIN) == LOW) { // Tombol ditekan

    if (!buttonPressed) { // Hanya jika sebelumnya tidak dalam keadaan ditekan

      buttonPressed = true; // Set flag bahwa tombol sedang ditekan

      pushButtonPressTime = currentTime; // Catat waktu penekanan

      longPressTriggered = false; // Reset flag long press untuk penekanan baru

    }

  } else { // Tombol dilepas

    if (buttonPressed) { // Hanya jika sebelumnya dalam keadaan ditekan

      buttonPressed = false; // Set flag bahwa tombol sudah tidak ditekan


      // Jika ini pelepasan tombol dan BUKAN long press yang sudah terpicu sebelumnya

      if (!longPressTriggered) {

        shortPressFlag = true; // Set flag untuk short press, akan ditangani di loop()

      }

    }

  }

}


// --- FUNGSI UNTUK CEK LONG PRESS (Dipanggil di loop utama) ---

void checkLongPress() {

  // Hanya cek jika tombol sedang ditekan dan long press belum terpicu

  if (buttonPressed && !longPressTriggered) {

    if (millis() - pushButtonPressTime >= LONG_PRESS_DURATION) {

      // Long press terdeteksi!

      longPressTriggered = true; // Set flag agar aksi ini tidak terulang untuk penekanan yang sama

      buttonPressed = false;     // PENTING: Reset buttonPressed di sini setelah long press terpicu

                                 // Ini mencegah handleButtonInterrupt() memproses pelepasan tombol ini sebagai short press


      if (currentMode == MODE_NORMAL) {

        currentMode = MODE_SETTING_SELECT; // Masuk ke mode pemilihan pengaturan

        Serial.println("Mode: Pilih Pengaturan (Long Press)");

        lcd.clear(); // Bersihkan LCD

        blinkState = false; // Pastikan tidak berkedip saat pemilihan

        selectedSetting = 0; // Atur default pilihan ke Batas Bawah

      } else {

        // Jika sudah di mode pengaturan (SELECT, BAWAH_EDIT, atau ATAS_EDIT), long press keluar dan simpan

        currentMode = MODE_NORMAL;

        saveSettings(); // Simpan pengaturan ke NVS

        Serial.println("Mode: Normal (Long Press Keluar, Pengaturan Disimpan)");

        messageDisplayStartTime = millis(); // Mulai timer tampilan pesan "DISIMPAN!"

        // LCD akan diupdate oleh bagian updateLCDDisplay() di loop utama

      }

    }

  }

}


// --- FUNGSI UPDATE TAMPILAN LCD ---

void updateLCDDisplay() {

  // Logika kedip-kedip teks

  // Kedip hanya aktif di mode edit

  if (currentMode == MODE_SETTING_BAWAH_EDIT || currentMode == MODE_SETTING_ATAS_EDIT) {

    if (millis() - lastBlinkMillis > BLINK_INTERVAL) {

      blinkState = !blinkState;

      lastBlinkMillis = millis();

    }

  } else {

    // Pastikan tidak berkedip di mode lain (selalu tampil)

    blinkState = true;

  }


  char line0[17]; // 16 karakter + null terminator

  char line1[17];


  // Selalu bersihkan seluruh baris sebelum menulis untuk menghindari sisa karakter

  // Ini lebih aman daripada menggunakan spasi di snprintf

  lcd.setCursor(0, 0); for(int i=0; i<16; i++) lcd.print(" ");

  lcd.setCursor(0, 1); for(int i=0; i<16; i++) lcd.print(" ");



  if (currentMode == MODE_NORMAL) {

    snprintf(line0, sizeof(line0), "Volt Aki: %.1f V", teganganAki);

    lcd.setCursor(0, 0); lcd.print(line0);


    if (digitalRead(RELAY_PIN) == HIGH) {

      snprintf(line1, sizeof(line1), "RELAY: ON");

    } else {

      snprintf(line1, sizeof(line1), "RELAY: OFF");

    }

    lcd.setCursor(0, 1); lcd.print(line1);


  } else if (currentMode == MODE_SETTING_SELECT) {

    lcd.setCursor(0, 0); lcd.print("PILIH PENGATURAN");

    if (selectedSetting == 0) {

      lcd.setCursor(0, 1); lcd.print("> Batas Bawah");

    } else {

      lcd.setCursor(0, 1); lcd.print("> Batas Atas");

    }

  } else if (currentMode == MODE_SETTING_BAWAH_EDIT) {

    lcd.setCursor(0, 0); lcd.print("EDIT BATAS BAWAH");

    lcd.setCursor(0, 1);

    if (blinkState) {

      snprintf(line1, sizeof(line1), "%.1f V", batasTeganganBawah);

      lcd.print(line1);

    } else {

      lcd.print(""); // Tampilan kosong untuk efek kedip

    }

  } else if (currentMode == MODE_SETTING_ATAS_EDIT) {

    lcd.setCursor(0, 0); lcd.print("EDIT BATAS ATAS ");

    lcd.setCursor(0, 1);

    if (blinkState) {

      snprintf(line1, sizeof(line1), "%.1f V", batasTeganganAtas);

      lcd.print(line1);

    } else {

      lcd.print(""); // Tampilan kosong untuk efek kedip

    }

  }

}


// --- FUNGSI SIMPAN/MUAT PENGATURAN KE NVS (Non-Volatile Storage) ---

void saveSettings() {

  preferences.begin("charger_app", false);

  preferences.putFloat("batasBawah", batasTeganganBawah);

  preferences.putFloat("batasAtas", batasTeganganAtas);

  preferences.end();

  Serial.println("Pengaturan disimpan.");

}


void loadSettings() {

  preferences.begin("charger_app", true);

  batasTeganganBawah = preferences.getFloat("batasBawah", 10.8);

  batasTeganganAtas = preferences.getFloat("batasAtas", 14.8);

  preferences.end();

  Serial.println("Pengaturan dimuat.");

}

No comments:

Post a Comment

CARGER AKI CONTROLER INA226-LIBARY WE ROTARI SW+OLED 0.96 SUKSES TAMPILAN LEBIH BAIK

 #include <Wire.h>  #include <Adafruit_GFX.h>    // Library grafis Adafruit #include <Adafruit_SSD1306.h> // Library untuk...