#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_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 LCD
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 updateLCDDisplay(); // Nama fungsi diganti
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) {
if (encoderPos > lastEncoderPos) { // Putar Kanan
batasTeganganBawah += 0.1;
} else { // Putar Kiri
batasTeganganBawah -= 0.1;
}
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;
}
if (batasTeganganAtas < 0.0) batasTeganganAtas = 0.0;
if (batasTeganganAtas > 36.0) batasTeganganAtas = 36.0;
}
lastEncoderPos = encoderPos;
blinkState = true; // Reset blink state
lastBlinkMillis = millis();
}
// --- LOGIKA KONTROL RELAY ---
if (currentMode == MODE_NORMAL) {
if (digitalRead(RELAY_PIN) == LOW && teganganAki < batasTeganganBawah) {
digitalWrite(RELAY_PIN, HIGH);
} else if (digitalRead(RELAY_PIN) == HIGH && teganganAki > batasTeganganAtas) {
digitalWrite(RELAY_PIN, LOW);
}
} else {
digitalWrite(RELAY_PIN, LOW); // Relay OFF saat di mode setting
}
// Panggil checkLongPress() secara berkala di sini
checkLongPress();
// --- UPDATE TAMPILAN LCD ---
updateLCDDisplay();
delay(10);
}
// --- FUNGSI INTERRUPT ROTARY ENCODER ---
void IRAM_ATTR readEncoder() {
static bool lastA = HIGH;
static bool lastB = HIGH;
bool currentA = digitalRead(ROTARY_A_PIN);
bool currentB = digitalRead(ROTARY_B_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 ---
void IRAM_ATTR handleButtonInterrupt() {
static unsigned long lastInterruptTime = 0;
unsigned long currentTime = millis();
if (currentTime - lastInterruptTime < DEBOUNCE_DELAY) {
return;
}
lastInterruptTime = currentTime;
if (digitalRead(ROTARY_SW_PIN) == LOW) { // Tombol ditekan
if (!buttonPressed) {
buttonPressed = true;
pushButtonPressTime = currentTime;
longPressTriggered = false;
}
} else { // Tombol dilepas
if (buttonPressed) {
if (!longPressTriggered) { // Ini adalah Short Press
if (currentMode == MODE_SETTING_BAWAH) {
currentMode = MODE_SETTING_ATAS;
Serial.println("Mode: Setting Atas");
// Langsung tampilkan di LCD
lcd.clear();
lcd.setCursor(0,0);
lcd.print("SET TEGANGAN ATAS:");
blinkState = true;
lastBlinkMillis = millis();
} else if (currentMode == MODE_SETTING_ATAS) {
currentMode = MODE_NORMAL;
saveSettings();
Serial.println("Mode: Normal, Pengaturan Disimpan");
// Tampilkan pesan simpan sebentar
lcd.clear();
lcd.setCursor(0,0);
lcd.print("PENGATURAN");
lcd.setCursor(0,1);
lcd.print("DISIMPAN!");
delay(1000); // Tampilkan selama 1 detik
}
}
buttonPressed = false;
longPressTriggered = false;
}
}
}
// --- FUNGSI UNTUK CEK LONG PRESS (Dipanggil di loop utama) ---
void checkLongPress() {
if (buttonPressed && !longPressTriggered) {
if (millis() - pushButtonPressTime >= LONG_PRESS_DURATION) {
if (currentMode == MODE_NORMAL) {
currentMode = MODE_SETTING_BAWAH;
Serial.println("Mode: Setting Bawah (Long Press)");
lcd.clear();
lcd.setCursor(0,0);
lcd.print("SET TEGANGAN BAWAH:");
} else {
// Jika sudah di mode setting, long press bisa jadi untuk keluar dan simpan
currentMode = MODE_NORMAL;
saveSettings();
Serial.println("Mode: Normal (Long Press Keluar)");
// Tampilkan pesan simpan sebentar
lcd.clear();
lcd.setCursor(0,0);
lcd.print("PENGATURAN");
lcd.setCursor(0,1);
lcd.print("DISIMPAN!");
delay(1000); // Tampilkan selama 1 detik
}
longPressTriggered = true;
blinkState = true;
lastBlinkMillis = millis();
}
}
}
// --- FUNGSI UPDATE TAMPILAN LCD ---
void updateLCDDisplay() {
// Logika kedip-kedip teks
if (millis() - lastBlinkMillis > BLINK_INTERVAL) {
blinkState = !blinkState;
lastBlinkMillis = millis();
}
// Menggunakan char array untuk format teks agar lebih efisien di mikrokontroler
char line0[17]; // 16 karakter + null terminator
char line1[17];
if (currentMode == MODE_NORMAL) {
lcd.setCursor(0, 0);
snprintf(line0, sizeof(line0), "Volt Aki: %.1f V", teganganAki);
lcd.print(line0);
lcd.setCursor(0, 1);
if (digitalRead(RELAY_PIN) == HIGH) {
snprintf(line1, sizeof(line1), "RELAY: ON");
} else {
snprintf(line1, sizeof(line1), "RELAY: OFF");
}
lcd.print(line1);
} else { // Mode Setting
if (currentMode == MODE_SETTING_BAWAH) {
lcd.setCursor(0, 0);
lcd.print("SET BATAS BAWAH:"); // Baris atas tetap
lcd.setCursor(0, 1);
if (blinkState) {
snprintf(line1, sizeof(line1), "%.1f V ", batasTeganganBawah); // Tambah spasi untuk hapus sisa
lcd.print(line1);
} else {
lcd.print(" "); // Blank space untuk efek kedip
}
} else if (currentMode == MODE_SETTING_ATAS) {
lcd.setCursor(0, 0);
lcd.print("SET BATAS ATAS: "); // Baris atas tetap
lcd.setCursor(0, 1);
if (blinkState) {
snprintf(line1, sizeof(line1), "%.1f V ", batasTeganganAtas); // Tambah spasi untuk hapus sisa
lcd.print(line1);
} else {
lcd.print(" "); // Blank space 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