#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#include <avr/io.h>
// I2C LCD setup
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Keypad setup
const uint8_t ROWS = 4, COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}};
uint8_t colPins[COLS] = {9, 8, 7, 6};
uint8_t rowPins[ROWS] = {13, 12, 11, 10};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// Pin definitions
const int coinAcceptorPin = 2, billAcceptorPin = 3;
const int onePhpHopper = 4, fivePhpHopper = 5;
const int onePhpRelay = A0, fivePhpRelay = A1;
const int buzzerPin = A2;
// Volatile variables for interrupts
volatile int creditTotal = 0, hopper1Count = 0, hopper2Count = 0;
// Dispense values
volatile int onePhpDispense = 0, fivePhpDispense = 0;
volatile bool onePhpDoneDispense = false, fivePhpDoneDispesne = false, creditAdd = false, dispenseCoins = false;
bool setOneValue = true, setFiveValue = false;
// Use buzzer type enable if using active buzzer
#define USE_PASSIVE_BUZZER
// get chip serial
uint32_t CHIPID()
{
uint32_t chipId = 0;
// Read the EEPROM bytes and construct the chip ID
for (int i = 0; i < 4; i++)
{
chipId |= (uint32_t)(eeprom_read_byte((uint8_t *)(0x0E + i))) << (i * 8);
}
Serial.println(chipId); // Print chip ID for debugging
uint32_t ChipID = 4294967295; // Define the expected Chip ID for comparison
if (chipId != ChipID)
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("NOT VALID DEVICE ID");
lcd.setCursor(0, 1);
lcd.print("CODE has a copyright");
lcd.setCursor(0, 2);
lcd.print("contact manufacturer");
lcd.setCursor(0, 3);
lcd.print("for code detail !!!");
while (true)
; // Infinite loop to halt execution
}
else
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WELCOME");
delay(5000); // Delay before continuing
}
return chipId; // Ensure a return value is always provided
}
// Buzzer function
void beepBuzzer()
{
#ifdef USE_PASSIVE_BUZZER
tone(buzzerPin, 2000);
delay(50);
noTone(buzzerPin);
#else
digitalWrite(buzzerPin, HIGH);
delay(50);
digitalWrite(buzzerPin, LOW);
#endif
Serial.println("Buzzer triggered");
}
// Update LCD efficiently
void updateLCD()
{
lcd.setCursor(0, 0);
lcd.print("Credit: ");
lcd.print(creditTotal);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("Dispense 1php: ");
lcd.print(onePhpDispense);
lcd.print(" ");
lcd.setCursor(0, 2);
lcd.print("Dispense 5php: ");
lcd.print(fivePhpDispense);
lcd.print(" ");
}
// Update LCD efficiently
void processFunction()
{
lcd.setCursor(0, 0);
lcd.print("Min Credit: 5php");
lcd.setCursor(0, 1);
lcd.print("A: Set 5php Val");
lcd.setCursor(0, 2);
lcd.print("B: Set 1php Val");
lcd.setCursor(0, 3);
lcd.print("C: Backspace");
delay(5000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("A: Set 5php Val");
lcd.setCursor(0, 1);
lcd.print("B: Set 1php Val");
lcd.setCursor(0, 2);
lcd.print("C: Backspace");
lcd.setCursor(0, 3);
lcd.print("D: Dispense");
delay(5000);
lcd.clear();
}
// Interrupt Service Routine for coin and bill acceptors
void addCredit()
{
creditTotal++;
creditAdd = true;
}
// Ensure A0 and A1 HIGH before setup() runs
void preSetup() __attribute__((constructor));
void preSetup()
{
PORTC |= (1 << PC0) | (1 << PC1); // Set A0 (PC0) and A1 (PC1) HIGH
}
// Pin Change Interrupt for hoppers
ISR(PCINT2_vect)
{
if (!digitalRead(fivePhpHopper))
{
fivePhpDispense -= 5;
creditTotal -= 5;
}
if (!digitalRead(onePhpHopper))
{
onePhpDispense -= 1;
creditTotal -= 1;
}
}
// Ensure fivePhpDispense is a multiple of 5
int verifyDispenseFive(int value)
{
if (value % 5 == 0)
{
Serial.print(value);
Serial.println(" is a valid multiple of 5.");
return value;
}
Serial.print(value);
Serial.println(" is not a multiple of 5. Resetting.");
return 0;
}
void adjustRemainingCredit()
{
int usedCredit = fivePhpDispense + onePhpDispense;
if (usedCredit > creditTotal)
{
onePhpDispense = 0;
fivePhpDispense = 0;
}
else
{
onePhpDispense = creditTotal - fivePhpDispense;
}
}
void setup()
{
Serial.begin(9600);
lcd.init();
lcd.backlight();
CHIPID();
pinMode(coinAcceptorPin, INPUT_PULLUP);
pinMode(billAcceptorPin, INPUT_PULLUP);
pinMode(onePhpHopper, INPUT_PULLUP);
pinMode(fivePhpHopper, INPUT_PULLUP);
pinMode(onePhpRelay, OUTPUT);
pinMode(fivePhpRelay, OUTPUT);
pinMode(buzzerPin, OUTPUT);
// Make sure hopper is off as relay use is active low
digitalWrite(onePhpHopper, HIGH);
digitalWrite(fivePhpHopper, HIGH);
attachInterrupt(digitalPinToInterrupt(coinAcceptorPin), addCredit, FALLING);
attachInterrupt(digitalPinToInterrupt(billAcceptorPin), addCredit, FALLING);
// attachInterrupt(digitalPinToInterrupt(fivePhpHopper), fivePhpDispenseCounter, FALLING);
// attachInterrupt(digitalPinToInterrupt(onePhpHopper), fivePhpDispenseCounter, FALLING);
PCICR |= 0b00000100;
PCMSK2 |= 0b00110000;
Serial.println("System ready. Waiting for inputs...");
lcd.clear();
}
void loop()
{
static unsigned long lastUpdate = 0;
char key = keypad.getKey();
////////////////////////Trigger by hopper/////////////////////////////////////////
if (dispenseCoins) // Dispense key "D" is triggered
{
if (onePhpDispense)
{
digitalWrite(onePhpRelay, LOW);
digitalWrite(fivePhpRelay, HIGH);
}
else if (fivePhpDispense)
{
digitalWrite(onePhpRelay, HIGH);
digitalWrite(fivePhpRelay, LOW);
}
}
////////////////////////OFF ALL HOPPER TOTALY//////////////////////////////////////////
if (!onePhpDispense)
{
digitalWrite(onePhpRelay, HIGH);
}
if (!fivePhpDispense)
{
digitalWrite(fivePhpRelay, HIGH);
}
///////////////////////////Keypad functions/////////////////////////////////////
if (key && creditTotal >= 5)
{
Serial.print("Key Pressed: ");
Serial.println(key);
if (key == 'B')
{
setOneValue = true;
setFiveValue = false;
}
else if (key == 'A')
{
setOneValue = false;
setFiveValue = true;
}
else if (key >= '0' && key <= '9')
{
int num = key - '0';
if (setOneValue)
{
int newOnePhpDispense = onePhpDispense * 10 + num;
if (newOnePhpDispense + fivePhpDispense <= creditTotal)
{
onePhpDispense = newOnePhpDispense % 1000;
}
else
{
// Reject input if total exceeds credit
Serial.println("Input exceeds credit. Resetting.");
onePhpDispense = 0;
}
}
else if (setFiveValue)
{
int newFivePhpDispense = fivePhpDispense * 10 + num;
if (newFivePhpDispense + onePhpDispense <= creditTotal)
{
fivePhpDispense = newFivePhpDispense % 1000;
if (String(fivePhpDispense).length() > 1)
{
fivePhpDispense = verifyDispenseFive(fivePhpDispense);
}
}
else
{
// Reject input if total exceeds credit
Serial.println("Input exceeds credit. Resetting.");
fivePhpDispense = 0;
}
}
}
else if (key == 'C')
{
// Backspace functionality
if (setOneValue)
{
onePhpDispense = onePhpDispense / 10;
}
else if (setFiveValue)
{
fivePhpDispense = fivePhpDispense / 10;
}
}
else if (key == 'D' && (!dispenseCoins && (onePhpDispense || fivePhpDispense))) // make sure any of dispense coin has a value and currently not dispensing yet
{
dispenseCoins = true;
}
}
else if (key and creditTotal < 5)
{
Serial.println("Not enought credit");
}
//////////////////////////////////////////Procedure /////////////////////////////////////////////////
if (key == '*')
{
processFunction();
}
//////////////////////Periodic LCD update every 10ms //////////////////////////////////
if (millis() - lastUpdate >= 10)
{
lastUpdate = millis();
updateLCD();
}
///////////////////////////RESET EVERYTHING to ZERO no Negative number//////////////////////////////////////
if (creditTotal <= 0)
{
creditTotal = 0;
}
if (onePhpDispense <= 0)
{
onePhpDispense = 0;
}
if (fivePhpDispense <= 0)
{
fivePhpDispense = 0;
}
////////////////////////////Beep When inserted and end////////////////////////////////////////////
if (creditAdd)
{
beepBuzzer();
creditAdd = false;
}
if (!onePhpDispense && !fivePhpDispense && dispenseCoins)
{
digitalWrite(fivePhpRelay, HIGH);
digitalWrite(onePhpRelay, HIGH);
for (int i = 0; i < 5; i++)
{
beepBuzzer();
delay(100);
}
dispenseCoins = false;
}
}