#include <LiquidCrystal.h>
#include <Keypad.h>
#include <HX711.h>
#include <Servo.h>
// =====================================
// 1. LCD 16x2 STANDAR (16 PIN)
// =====================================
// Urutan: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 13, A2, A3, A4);
// =====================================
// 2. HX711 + LOAD CELL 5 KG
// =====================================
const byte HX_DOUT = A1;
const byte HX_SCK = A0;
HX711 scale;
// GANTI setelah kalibrasi sensor.
// Nilai ini hanya nilai awal, belum tentu cocok.
float CAL_FACTOR = -7050.0;
// =====================================
// 3. SERVO SG90
// =====================================
const byte SERVO_PIN = 10;
const int SERVO_CLOSED = 0;
const int SERVO_OPEN = 90;
Servo servo;
// =====================================
// 4. KEYPAD 4x4
// =====================================
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};
Keypad keypad = Keypad(
makeKeymap(keys),
rowPins,
colPins,
ROWS,
COLS
);
// =====================================
// 5. PENGATURAN TAKARAN
// =====================================
float targetWeight = 0;
float currentWeight = 0;
bool targetSelected = false;
bool dispensing = false;
// Nilai awal kompensasi aliran
const float STOP_MARGIN_G = 5.0;
// Batas waktu maksimum
const unsigned long MAX_DISPENSE_MS = 30000UL;
// =====================================
// 6. FUNGSI TAMPILAN LCD
// =====================================
void showMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Pilih takaran:");
lcd.setCursor(0, 1);
lcd.print("1:100 2:250 3:500");
}
void showTarget() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Target dipilih:");
lcd.setCursor(0, 1);
lcd.print(targetWeight, 0);
lcd.print(" g #=Mulai");
}
void showWeight() {
lcd.setCursor(0, 0);
lcd.print("Berat:");
lcd.print(currentWeight, 1);
lcd.print("g ");
lcd.setCursor(0, 1);
lcd.print("Target:");
lcd.print(targetWeight, 0);
lcd.print("g ");
}
void closeServo() {
servo.write(SERVO_CLOSED);
}
void resetSystem() {
closeServo();
targetWeight = 0;
targetSelected = false;
dispensing = false;
showMenu();
}
// =====================================
// 7. PEMILIHAN PRESET
// =====================================
void selectPreset(char key) {
switch (key) {
case '1':
targetWeight = 100;
break;
case '2':
targetWeight = 250;
break;
case '3':
targetWeight = 500;
break;
case '4':
targetWeight = 1000;
break;
default:
return;
}
targetSelected = true;
showTarget();
Serial.print("Target dipilih: ");
Serial.print(targetWeight);
Serial.println(" gram");
}
// =====================================
// 8. PROSES DISPENSING
// =====================================
void dispense() {
if (!targetSelected || targetWeight <= 0) {
lcd.clear();
lcd.print("Pilih preset");
delay(1200);
showMenu();
return;
}
if (!scale.is_ready()) {
closeServo();
lcd.clear();
lcd.print("ERROR HX711");
lcd.setCursor(0, 1);
lcd.print("Cek koneksi");
delay(2000);
resetSystem();
return;
}
// Berat harus nol sebelum mulai.
currentWeight = scale.get_units(5);
if (currentWeight > 5.0) {
lcd.clear();
lcd.print("Wadah belum");
lcd.setCursor(0, 1);
lcd.print("kosong / tare");
delay(1800);
resetSystem();
return;
}
dispensing = true;
unsigned long startTime = millis();
lcd.clear();
lcd.print("Mengeluarkan...");
lcd.setCursor(0, 1);
lcd.print("Tekan * = Batal");
// Buka pintu dispenser
servo.write(SERVO_OPEN);
while (dispensing) {
char key = keypad.getKey();
// Pembatalan
if (key == '*') {
closeServo();
lcd.clear();
lcd.print("Proses dibatal");
lcd.setCursor(0, 1);
lcd.print("Servo tertutup");
delay(1500);
resetSystem();
return;
}
// Timeout
if (millis() - startTime >= MAX_DISPENSE_MS) {
closeServo();
lcd.clear();
lcd.print("TIMEOUT!");
lcd.setCursor(0, 1);
lcd.print("Cek mekanisme");
Serial.println("ERROR: Timeout dispensing");
delay(2000);
resetSystem();
return;
}
// Sensor harus tetap merespons
if (!scale.is_ready()) {
closeServo();
lcd.clear();
lcd.print("ERROR HX711");
lcd.setCursor(0, 1);
lcd.print("Servo ditutup");
delay(2000);
resetSystem();
return;
}
currentWeight = scale.get_units(3);
showWeight();
Serial.print("Berat: ");
Serial.print(currentWeight, 1);
Serial.println(" g");
// Target dikurangi margin kompensasi
if (currentWeight >= targetWeight - STOP_MARGIN_G) {
closeServo();
dispensing = false;
}
delay(100);
}
// Tunggu berat stabil
delay(1000);
if (scale.is_ready()) {
currentWeight = scale.get_units(10);
}
lcd.clear();
lcd.print("Selesai!");
lcd.setCursor(0, 1);
lcd.print("Hasil:");
lcd.print(currentWeight, 1);
lcd.print("g");
Serial.print("Target: ");
Serial.print(targetWeight);
Serial.print(" g | Hasil: ");
Serial.print(currentWeight, 1);
Serial.println(" g");
delay(3000);
resetSystem();
}
// =====================================
// 9. SETUP
// =====================================
void setup() {
Serial.begin(9600);
// LCD standar, bukan I2C
lcd.begin(16, 2);
lcd.clear();
// Servo langsung ke posisi tertutup
servo.attach(SERVO_PIN);
closeServo();
lcd.print("Smart Dispenser");
lcd.setCursor(0, 1);
lcd.print("Inisialisasi...");
// Inisialisasi HX711
scale.begin(HX_DOUT, HX_SCK);
scale.set_scale(CAL_FACTOR);
delay(1000);
if (!scale.is_ready()) {
lcd.clear();
lcd.print("HX711 ERROR");
lcd.setCursor(0, 1);
lcd.print("Cek wiring");
Serial.println("HX711 tidak siap.");
// Jangan menjalankan dispensing
while (true) {
closeServo();
delay(500);
}
}
scale.tare(10);
lcd.clear();
lcd.print("Sistem siap!");
delay(1500);
showMenu();
Serial.println("Smart Dispenser siap.");
}
// =====================================
// 10. LOOP UTAMA
// =====================================
void loop() {
char key = keypad.getKey();
if (key == NO_KEY) {
return;
}
Serial.print("Tombol ditekan: ");
Serial.println(key);
if (key == '*') {
closeServo();
resetSystem();
return;
}
if (key >= '1' && key <= '4') {
selectPreset(key);
return;
}
if (key == '#') {
dispense();
}
}