Search This Blog

Monday, 14 July 2025

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

 #include <Wire.h> 

#include <Adafruit_GFX.h>    // Library grafis Adafruit

#include <Adafruit_SSD1306.h> // Library untuk OLED SSD1306

#include <INA226_WE.h> 

#include <Preferences.h> 


// --- DEKLARASI PIN ESP32 ---


// Deklarasi untuk OLED 0.96 inci (SSD1306)

// Ukuran layar OLED dalam piksel (biasanya 128x64 atau 128x32)

#define SCREEN_WIDTH 128 // OLED display width, in pixels

#define SCREEN_HEIGHT 64 // OLED display height, in pixels (atau 32 jika Anda menggunakan versi 128x32)


// Deklarasi pin reset OLED (biasanya terhubung ke GPIO, atau -1 jika terhubung ke VCC)

#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)


// Inisialisasi objek Adafruit_SSD1306 dengan lebar, tinggi, dan objek Wire

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);


// Pin untuk Rotary Encoder

#define ROTARY_A_PIN 32

#define ROTARY_B_PIN 33

#define ROTARY_SW_PIN 25


// Pin untuk Relay

#define RELAY_PIN 26


// --- OBJEK SENSOR ---

INA226_WE ina226;


// --- OBJEK NVS ---

Preferences preferences;


// --- PARAMETER AKI & BATAS ---

float batasTeganganBawah = 10.80; 

float batasTeganganAtas = 14.80; 


// --- VARIABEL PENGUKURAN ---

float teganganAki = 0.0;


// --- VARIABEL KONTROL MODE & UI ---

enum ModeAlat {

  MODE_NORMAL,

  MODE_SETTING_SELECT,      

  MODE_SETTING_BAWAH_EDIT,  

  MODE_SETTING_ATAS_EDIT    

};

ModeAlat currentMode = MODE_NORMAL;


bool manualRelayOn = false; 

unsigned long manualControlStartTime = 0; 

const long MANUAL_CONTROL_TIMEOUT = 300000;


volatile int encoderPos = 0;

int lastEncoderPos = 0;      


volatile int lastEncoded = 0;

const int ENCODER_DEBOUNCE_DELAY = 1; 


const int ENCODER_STEPS_PER_CLICK = 4;

int encoderClickCounter = 0;


volatile unsigned long pushButtonPressTime = 0;

volatile bool buttonIsCurrentlyPressed = false;


unsigned long lastButtonStateChangeTime = 0;

const long BUTTON_DEBOUNCE_MS = 150;


volatile bool shortPressDetected = false;

volatile bool longPressDetected = false;


const long LONG_PRESS_DURATION = 3000;


bool blinkState = true; // Selalu true agar teks selalu terlihat (kedipan dihilangkan)

unsigned long lastBlinkMillis = 0;

const long BLINK_INTERVAL = 500; // Tidak lagi relevan untuk kedipan di mode pengaturan


int selectedSetting = 0;


unsigned long messageDisplayStartTime = 0;

const long MESSAGE_DISPLAY_DURATION = 1000;


// --- VARIABEL UNTUK DELAY RELAY OFF ---

unsigned long relayOffDelayStartTime = 0;

bool relayOffDelayActive = false;

const long RELAY_OFF_DELAY_MS = 1000; // Tetap 1 detik


// --- VARIABEL UNTUK OPTIMALISASI UPDATE OLED ---

// Untuk OLED, kita masih bisa menggunakan optimasi string

char lastLine0[25] = ""; // Lebih panjang karena OLED bisa menampilkan lebih banyak karakter per baris dengan font kecil

char lastLine1[25] = ""; 

char lastLine2[25] = ""; // Baris tambahan untuk OLED 128x64

char lastLine3[25] = ""; // Baris tambahan untuk OLED 128x64

ModeAlat lastMode = MODE_NORMAL;

int lastSelectedSetting = -1;

float lastTeganganAki = -1.0;

bool displayNeedsUpdate = true;


bool lastRelayStateDisplayed = false; 


// Variabel untuk mengontrol frekuensi update OLED

unsigned long lastOLEDUpdateTime = 0;

const long OLED_UPDATE_INTERVAL_MS = 200; // Update OLED setiap 200ms


// --- PROTOTYPE FUNGSI ---

void IRAM_ATTR readEncoder();

void IRAM_ATTR handleButtonInterrupt();

void saveSettings();

void loadSettings();

void updateOLEDDisplay(); // Ganti nama fungsi update


// --- FUNGSI SETUP ---

void setup() {

  Serial.begin(115200);


  // --- Inisialisasi OLED ---

  Wire.begin(); // Pastikan Wire (I2C) dimulai sebelum inisialisasi OLED

  Wire.setClock(400000); // OLED bisa bekerja lebih cepat, coba 400kHz untuk I2C


  // Inisialisasi display dengan alamat I2C 0x3C (bisa 0x3D juga, tergantung modul)

  // Periksa alamat I2C OLED Anda. Jika tidak berhasil dengan 0x3C, coba 0x3D

  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 

    Serial.println(F("SSD1306 alokasi gagal"));

    for(;;); // Jangan lanjutkan jika gagal

  }


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

  display.display(); // Tampilkan splash screen Adafruit

  delay(2000); 

  display.clearDisplay(); // Hapus buffer


  display.setTextSize(1);      // Ukuran teks 1 (6x8 piksel per karakter)

  display.setTextColor(SSD1306_WHITE); // Warna teks putih

  display.setCursor(0, 0);

  display.print("Inisialisasi...");

  display.display(); // Tampilkan teks ke layar

  Serial.println("Inisialisasi OLED selesai.");

  delay(1000);


  // --- Inisialisasi INA226 ---

  ina226.init(); // Sudah menggunakan Wire.begin() di atas


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


  ina226.setAverage(AVERAGE_4);

  ina226.setConversionTime(CONV_TIME_1100);

  ina226.setMeasureMode(CONTINOUS); // Perbaikan: ina26 -> ina226


  // --- Inisialisasi Rotary Encoder ---

  pinMode(ROTARY_A_PIN, INPUT_PULLUP);

  pinMode(ROTARY_B_PIN, INPUT_PULLUP);

  pinMode(ROTARY_SW_PIN, INPUT_PULLUP);


  attachInterrupt(digitalPinToInterrupt(ROTARY_A_PIN), readEncoder, CHANGE);

  attachInterrupt(digitalPinToInterrupt(ROTARY_B_PIN), readEncoder, CHANGE);

  attachInterrupt(digitalPinToInterrupt(ROTARY_SW_PIN), handleButtonInterrupt, CHANGE);


  // --- Inisialisasi Relay ---

  pinMode(RELAY_PIN, OUTPUT);

  digitalWrite(RELAY_PIN, LOW); 

  lastRelayStateDisplayed = (digitalRead(RELAY_PIN) == HIGH); 


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

  loadSettings();

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

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


  int initialEncoderAPinState = digitalRead(ROTARY_A_PIN);

  int initialEncoderBPinState = digitalRead(ROTARY_B_PIN);

  lastEncoded = (initialEncoderAPinState << 1) | initialEncoderBPinState;


  display.clearDisplay(); // Bersihkan layar OLED

  displayNeedsUpdate = true; // Paksa update pertama setelah setup

}


// --- FUNGSI LOOP ---

void loop() {

  unsigned long currentTime = millis();


  // --- BACA SENSOR ---

  teganganAki = ina226.getBusVoltage_V();

  Serial.print("Volt Aki Terbaca: ");

  Serial.print(teganganAki, 3);

  Serial.print(" V | Batas Atas: ");

  Serial.print(batasTeganganAtas, 3);

  Serial.print(" V | Batas Bawah: ");

  Serial.print(batasTeganganBawah, 3);

  Serial.print(" V | Status Pin RELAY: ");

  Serial.println(digitalRead(RELAY_PIN) == HIGH ? "HIGH (ON)" : "LOW (OFF)"); 


  // Periksa apakah tegangan aki berubah signifikan untuk update OLED

  if (abs(teganganAki - lastTeganganAki) > 0.01) { 

    displayNeedsUpdate = true;

  }


  // Tangani putaran Rotary Encoder

  if (encoderPos != lastEncoderPos) {

    int encoderDelta = encoderPos - lastEncoderPos;

    encoderClickCounter += encoderDelta;


    if (abs(encoderClickCounter) >= ENCODER_STEPS_PER_CLICK) {

      int numClicks = encoderClickCounter / ENCODER_STEPS_PER_CLICK;

      encoderClickCounter %= ENCODER_STEPS_PER_CLICK;


      if (currentMode == MODE_SETTING_BAWAH_EDIT) {

        batasTeganganBawah += (float)numClicks * 0.01;

        batasTeganganBawah = round(batasTeganganBawah * 100.0) / 100.0;

        batasTeganganBawah = constrain(batasTeganganBawah, 0.0, 36.0);

      } else if (currentMode == MODE_SETTING_ATAS_EDIT) {

        batasTeganganAtas += (float)numClicks * 0.01;

        batasTeganganAtas = round(batasTeganganAtas * 100.0) / 100.0;

        batasTeganganAtas = constrain(batasTeganganAtas, 0.0, 36.0);

      } else if (currentMode == MODE_SETTING_SELECT) {

        selectedSetting += numClicks;

        if (selectedSetting < 0) selectedSetting = 1;

        if (selectedSetting > 1) selectedSetting = 0;

      }

      displayNeedsUpdate = true;

    }

    lastEncoderPos = encoderPos;

  }


  // --- LOGIKA KONTROL RELAY ---

  if (currentMode == MODE_NORMAL) {

    if (manualRelayOn && (currentTime - manualControlStartTime > MANUAL_CONTROL_TIMEOUT)) {

      manualRelayOn = false;

      Serial.println("Kontrol manual relay nonaktif (timeout).");

      digitalWrite(RELAY_PIN, LOW); 

      displayNeedsUpdate = true;

      relayOffDelayActive = false;

    }


    if (manualRelayOn) {

      if (teganganAki >= batasTeganganBawah && teganganAki <= batasTeganganAtas) {

        if (digitalRead(RELAY_PIN) == LOW) { 

          digitalWrite(RELAY_PIN, HIGH); 

          Serial.println("Relay MANUAL ON.");

        }

        relayOffDelayActive = false;

      } else {

        if (digitalRead(RELAY_PIN) == HIGH) { 

          digitalWrite(RELAY_PIN, LOW); 

          Serial.println("Relay MANUAL OFF karena di luar batas aman.");

        }

        manualRelayOn = false;

        displayNeedsUpdate = true;

        relayOffDelayActive = false;

        display.clearDisplay(); // Bersihkan OLED

        display.setCursor(0,0); display.print("Tegangan Di Luar");

        display.setCursor(0,16); display.print("Batas Aman!"); // Baris kedua di OLED (16 piksel ke bawah)

        display.display(); // Tampilkan perubahan

        messageDisplayStartTime = currentTime;

      }

    } else {

      bool currentRelayPhysicalState = (digitalRead(RELAY_PIN) == HIGH); 


      if (teganganAki < batasTeganganBawah) { 

        if (digitalRead(RELAY_PIN) == LOW) { 

          digitalWrite(RELAY_PIN, HIGH); 

          Serial.println("Relay OTOMATIS ON (Tegangan rendah).");

          displayNeedsUpdate = true; 

        }

        relayOffDelayActive = false;

      } else if (teganganAki >= batasTeganganAtas) { 

        if (!relayOffDelayActive) {

          relayOffDelayStartTime = currentTime;

          relayOffDelayActive = true;

          Serial.println("Tegangan tinggi terdeteksi. Memulai hitung mundur untuk OFF relay...");

          displayNeedsUpdate = true; 

        }


        if (relayOffDelayActive && (currentTime - relayOffDelayStartTime >= RELAY_OFF_DELAY_MS)) {

          if (digitalRead(RELAY_PIN) == HIGH) { 

            digitalWrite(RELAY_PIN, LOW); 

            Serial.println("Relay OTOMATIS OFF (Tegangan tinggi setelah delay).");

            displayNeedsUpdate = true; 

          }

          relayOffDelayActive = false;

        }

      } else {

        if (relayOffDelayActive) { 

            relayOffDelayActive = false;

            Serial.println("Delay OFF relay dibatalkan, tegangan kembali normal.");

            displayNeedsUpdate = true; 

        }

      }


      bool newRelayState = (digitalRead(RELAY_PIN) == HIGH); 

      if (newRelayState != lastRelayStateDisplayed) {

          displayNeedsUpdate = true;

          lastRelayStateDisplayed = newRelayState;

          Serial.print("Relay state changed! New state: "); Serial.println(newRelayState ? "ON" : "OFF");

      }


    } 

  } else { 

    if (digitalRead(RELAY_PIN) == HIGH) { 

      digitalWrite(RELAY_PIN, LOW); 

      Serial.println("Relay OFF (Di mode pengaturan).");

      displayNeedsUpdate = true;

    }

    manualRelayOn = false;

    displayNeedsUpdate = true;

    relayOffDelayActive = false;

  }


  // --- LOGIKA PENANGANAN TOMBOL PUSH ROTARY ---

  if (buttonIsCurrentlyPressed) {

    if (!longPressDetected && (currentTime - pushButtonPressTime >= LONG_PRESS_DURATION)) {

      longPressDetected = true;

      shortPressDetected = false;


      if (currentMode == MODE_NORMAL) {

        currentMode = MODE_SETTING_SELECT;

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

        selectedSetting = 0;

        displayNeedsUpdate = true;

      } else {

        currentMode = MODE_NORMAL;

        saveSettings();

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

        messageDisplayStartTime = currentTime;

        displayNeedsUpdate = true;

      }

      lastButtonStateChangeTime = currentTime;

    }

  } else {

    if (pushButtonPressTime != 0 && (currentTime - pushButtonPressTime < LONG_PRESS_DURATION) && !longPressDetected) {

      if (currentTime - lastButtonStateChangeTime > BUTTON_DEBOUNCE_MS) {

        shortPressDetected = true;

        lastButtonStateChangeTime = currentTime;

      }

    }

    pushButtonPressTime = 0;

    longPressDetected = false;

  }


  // --- TANGANI AKSI SHORT PRESS (SETELAH DEBOUNCE) ---

  if (shortPressDetected) {

    shortPressDetected = false;


    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");

      }

      displayNeedsUpdate = true;

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

      currentMode = MODE_SETTING_SELECT;

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

      displayNeedsUpdate = true;

    }

  }


  // --- UPDATE TAMPILAN OLED ---

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

    // Pesan sementara sedang ditampilkan, tidak perlu panggil updateOLEDDisplay

  } else {

    if (currentTime - lastOLEDUpdateTime >= OLED_UPDATE_INTERVAL_MS) {

      updateOLEDDisplay();

      lastOLEDUpdateTime = currentTime; 

      messageDisplayStartTime = 0;     

    }

  }


  delay(10); 

}


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

void IRAM_ATTR readEncoder() {

  int encoderAPinState = digitalRead(ROTARY_A_PIN);

  int encoderBPinState = digitalRead(ROTARY_B_PIN);


  int encoded = (encoderAPinState << 1) | encoderBPinState;


  if (encoded == 0b00) {

    if (lastEncoded == 0b01) encoderPos--;

    else if (lastEncoded == 0b10) encoderPos++;

  } else if (encoded == 0b01) {

    if (lastEncoded == 0b00) encoderPos++;

    else if (lastEncoded == 0b11) encoderPos--;

  } else if (encoded == 0b11) {

    if (lastEncoded == 0b01) encoderPos++;

    else if (lastEncoded == 0b10) encoderPos--;

  } else if (encoded == 0b10) {

    if (lastEncoded == 0b11) encoderPos++;

    else if (lastEncoded == 0b00) encoderPos--;

  }

  

  lastEncoded = encoded;

}


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

void IRAM_ATTR handleButtonInterrupt() {

  unsigned long currentTime = millis();


  if (digitalRead(ROTARY_SW_PIN) == LOW) {

    if (!buttonIsCurrentlyPressed) {

      buttonIsCurrentlyPressed = true;

      pushButtonPressTime = currentTime;

    }

  } else {

    buttonIsCurrentlyPressed = false;

  }

}


// --- FUNGSI UPDATE TAMPILAN OLED ---

void updateOLEDDisplay() {

  Serial.println("updateOLEDDisplay() called."); 


  char currentLine0[25]; 

  char currentLine1[25];

  char currentLine2[25]; 

  char currentLine3[25]; 


  blinkState = true; // Selalu true agar teks selalu terlihat


  display.setTextSize(1); // Ukuran teks 1 (6x8 piksel per karakter)

  display.setTextColor(SSD1306_WHITE); // Warna teks putih


  if (currentMode == MODE_NORMAL) {

    snprintf(currentLine0, sizeof(currentLine0), "Volt Aki: %.2f V", teganganAki); 

    

    const char* relayStatus = (digitalRead(RELAY_PIN) == HIGH) ? "ON" : "OFF";

    snprintf(currentLine1, sizeof(currentLine1), "RELAY: %s", relayStatus);


    snprintf(currentLine2, sizeof(currentLine2), ""); // Kosongkan baris

    snprintf(currentLine3, sizeof(currentLine3), ""); // Kosongkan baris


  } else if (currentMode == MODE_SETTING_SELECT) {

    snprintf(currentLine0, sizeof(currentLine0), "PILIH PENGATURAN");

    snprintf(currentLine1, sizeof(currentLine1), ""); 


    if (selectedSetting == 0) {

      snprintf(currentLine2, sizeof(currentLine2), "> Batas Bawah"); 

      snprintf(currentLine3, sizeof(currentLine3), "  Batas Atas");

    } else {

      snprintf(currentLine2, sizeof(currentLine2), "  Batas Bawah");

      snprintf(currentLine3, sizeof(currentLine3), "> Batas Atas");

    }

  } else if (currentMode == MODE_SETTING_BAWAH_EDIT) {

    snprintf(currentLine0, sizeof(currentLine0), "EDIT BATAS BAWAH");

    snprintf(currentLine1, sizeof(currentLine1), ""); 

    snprintf(currentLine2, sizeof(currentLine2), "%.2f V", batasTeganganBawah); 

    snprintf(currentLine3, sizeof(currentLine3), ""); 

  } else if (currentMode == MODE_SETTING_ATAS_EDIT) {

    snprintf(currentLine0, sizeof(currentLine0), "EDIT BATAS ATAS ");

    snprintf(currentLine1, sizeof(currentLine1), ""); 

    snprintf(currentLine2, sizeof(currentLine2), "%.2f V", batasTeganganAtas); 

    snprintf(currentLine3, sizeof(currentLine3), ""); 

  }


  // --- HANYA TULIS KE OLED JIKA ADA PERUBAHAN ATAU DIPAKSA UPDATE ---

  if (currentMode != lastMode || displayNeedsUpdate || 

      (strcmp(currentLine0, lastLine0) != 0) || 

      (strcmp(currentLine1, lastLine1) != 0) ||

      (strcmp(currentLine2, lastLine2) != 0) ||

      (strcmp(currentLine3, lastLine3) != 0)) 

  {

    Serial.println("  OLED actually updated due to change."); 

    display.clearDisplay(); 


    display.setCursor(0, 0); 

    display.print(currentLine0);


    display.setCursor(0, 16); 

    display.print(currentLine1);


    if (SCREEN_HEIGHT >= 64) { 

      display.setCursor(0, 32); 

      display.print(currentLine2);


      display.setCursor(0, 48); 

      display.print(currentLine3);

    }


    display.display(); // Kirim buffer ke layar OLED


    strcpy(lastLine0, currentLine0);

    strcpy(lastLine1, currentLine1);

    strcpy(lastLine2, currentLine2);

    strcpy(lastLine3, currentLine3);


    displayNeedsUpdate = false;

  } else {

    Serial.println("  OLED NOT updated (no significant change detected)."); 

  }


  lastMode = currentMode;

  lastSelectedSetting = selectedSetting;

  lastTeganganAki = teganganAki;

}


// --- FUNGSI SIMPAN/MUAT PENGATURAN KE NVS ---

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.80);

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

  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...