Search This Blog

Wednesday, 9 July 2025

CARGER AKI CONTROLER INA226-LIBARY WE ROTARI SWITCH DAN PUS

 #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

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