#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <avr/io.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Total Credit Variable
volatile int creditTotal = 0;
volatile bool creditTotalChange = false;
// Interrupt pins for coin slot and hopper
const int coinSlotInt = 2;
const int coinHopperInt = 3;
const int dispenseButton = 4;
const int hopperRelay = 5;
const int ledRelay = 6;
const int buzzerPin = 7;
const int maxRowChar = 20;
bool dispensingCoin = false;
bool shoewInsertCoin = false;
char lcdRowOne[21] = "pysonet05"; // 20 characters + null terminator
char lcdRowTwo[21] = "";
char lcdRowThree[21] = "";
char lcdRowFour[21] = "";
int buttonState = HIGH; // Current button state
int lastButtonState = HIGH; // Previous button state
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // Debounce time in milliseconds
// Use buzzer type enable if using active buzzer
// #define USE_PASSIVE_BUZZER
#define ACTIVE_HIGH_RELAY
// Count String Length & Add Spaces
void formatText(char *output, const char *text, char func)
{
int stringLength = strlen(text);
if (stringLength > maxRowChar)
{
strcpy(output, "Error: Max Length");
return;
}
int remainSpaceCount = maxRowChar - stringLength;
char makeSpace[maxRowChar + 1] = {0};
if (func == 'c')
{ // Center the text
int leftSpace = remainSpaceCount / 2;
int rightSpace = remainSpaceCount - leftSpace;
memset(makeSpace, ' ', leftSpace);
strcpy(output, makeSpace);
strcat(output, text);
memset(makeSpace, ' ', rightSpace);
strcat(output, makeSpace);
}
else if (func == 'f')
{ // Add spaces in front
memset(makeSpace, ' ', remainSpaceCount);
strcpy(output, makeSpace);
strcat(output, text);
}
else if (func == 'b')
{ // Add spaces in back
strcpy(output, text);
memset(makeSpace, ' ', remainSpaceCount);
strcat(output, makeSpace);
}
}
// Update LCD Display
void updateLcd()
{
char formattedText[21];
lcd.setCursor(0, 0);
formatText(formattedText, lcdRowOne, 'c');
lcd.print(formattedText);
lcd.setCursor(0, 1);
formatText(formattedText, lcdRowTwo, 'b');
lcd.print(formattedText);
lcd.setCursor(0, 2);
formatText(formattedText, lcdRowThree, 'b');
lcd.print(formattedText);
lcd.setCursor(0, 3);
formatText(formattedText, lcdRowFour, 'b');
lcd.print(formattedText);
// strcpy(lcdRowTwo, " ");
strcpy(lcdRowThree, " ");
strcpy(lcdRowFour, " ");
}
// 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");
}
// Get Chip Serial
uint32_t CHIPID()
{
uint32_t chipId = 0;
for (int i = 0; i < 4; i++)
{
chipId |= (uint32_t)(EEPROM.read(0x0E + i)) << (i * 8);
}
Serial.print("Device ID: ");
Serial.println(chipId);
const uint32_t expectedChipID = 4294967295;
if (chipId != expectedChipID)
{
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 details!!!");
while (true)
{
beepBuzzer();
delay(500);
}
// Infinite loop to halt execution
}
else
{
strcpy(lcdRowOne, "PYSONET05");
strcpy(lcdRowTwo, "1, 10, 20 php");
strcpy(lcdRowThree, "to 5 php");
updateLcd();
delay(5000); // Delay before continuing
}
return chipId;
}
// Verify if credit total divisible by 5 before dispense
int verifyDispenseFive(int value)
{
if (value % 5 == 0)
{
// Serial.println(" is a valid multiple of 5.");
return value;
}
// Serial.println(" is not a multiple of 5. Resetting.");
return 0;
}
void debounceDispense()
{
int reading = digitalRead(dispenseButton);
if (reading != lastButtonState)
{
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay)
{
if (reading != buttonState)
{
buttonState = reading;
// Trigger only when button is released (HIGH)
if (buttonState == HIGH)
{
if (verifyDispenseFive(creditTotal))
{
digitalWrite(hopperRelay, HIGH);
dispensingCoin = true;
Serial.println("Dispensing Coin");
}
else
{
Serial.println("Can't dispense due to credit not divisible by 5");
}
}
}
}
lastButtonState = reading;
}
// Add Credit
void addCredit()
{
creditTotal++;
creditTotalChange = true;
}
// Dispense Coin
void dispenseCoin()
{
creditTotal -= 5;
}
// Setup Function
void setup()
{
Serial.begin(9600);
lcd.init();
lcd.begin(20, 4);
lcd.backlight();
CHIPID();
pinMode(coinSlotInt, INPUT_PULLUP);
pinMode(coinHopperInt, INPUT_PULLUP);
pinMode(dispenseButton, INPUT_PULLUP);
pinMode(hopperRelay, OUTPUT);
pinMode(ledRelay, OUTPUT);
attachInterrupt(digitalPinToInterrupt(coinSlotInt), addCredit, FALLING);
attachInterrupt(digitalPinToInterrupt(coinHopperInt), dispenseCoin, FALLING);
}
// Loop Function
void loop()
{
static unsigned long blinkInsert = 0;
if (creditTotal > 0)
{
snprintf(lcdRowTwo, sizeof(lcdRowTwo), "Credit: %d", creditTotal);
}
else
{
if (millis() - blinkInsert >= 1000)
{
if (shoewInsertCoin)
{
strcpy(lcdRowTwo, "INSERT COIN");
shoewInsertCoin = false;
}
else
{
strcpy(lcdRowTwo, " ");
shoewInsertCoin = true;
}
Serial.println("Blinking Insert Coin");
blinkInsert = millis();
}
}
if (!verifyDispenseFive(creditTotal) && creditTotal > 0)
{
snprintf(lcdRowThree, sizeof(lcdRowThree), "Credit %d not / by 5", creditTotal);
}
else if (dispensingCoin)
{
strcpy(lcdRowThree, "Dispensing Coin");
}
updateLcd();
if (creditTotalChange)
{
Serial.print("Current Credit: ");
Serial.println(creditTotal);
beepBuzzer();
creditTotalChange = false;
}
if (creditTotal == 0)
{
digitalWrite(hopperRelay, LOW);
digitalWrite(ledRelay, HIGH);
}
else
{
digitalWrite(ledRelay, LOW);
}
if (!creditTotal && dispensingCoin)
{
for (int i = 0; i < 5; i++)
{
beepBuzzer();
delay(100);
}
dispensingCoin = false;
strcpy(lcdRowTwo, "Thank you!");
strcpy(lcdRowThree, "For using");
strcpy(lcdRowFour, "PYSONET05");
updateLcd();
delay(1000);
}
debounceDispense();
}