#include "Arduino.h" // includes for LCD #include #include "Adafruit_GFX.h" #include "Adafruit_HX8357.h" #include "LCD_Graphics.h" // includes for audio #include #include #include "Audio.h" #include "SPIFFS_tasks.h" #define FORMAT_SPIFFS_IF_FAILED true // includes for motors #include "Motors.h" // includes for lighting #include "Lighting.h" // includes for 7 segment #include // Enable this line if using Arduino Uno, Mega, etc. #include "Adafruit_LEDBackpack.h" #include "Adafruit_GFX.h" Adafruit_7segment matrix = Adafruit_7segment(); // Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC, TFT_RST); // SoftSPI - note that on some processors this might be *faster* than hardware SPI! //Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC, MOSI, SCK, TFT_RST, MISO); char initials1[4] = {'T','M','B','\0'}; char initials2[4] = {'X','X','X','\0'}; char initials3[4] = {'L','L','L','\0'}; char tempInitials[4] = {'A','A','A','\0'}; char letters[28] = {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','-','\0'}; enum State {IDLE, EDITING, GAMEPLAY}; State currentState = IDLE; int letterIndex = 0; // Current letter being edited int charPos = 0; // Current position in initials (0-2) bool lastButton1State = HIGH; bool lastButton2State = HIGH; bool button1State = HIGH; bool button2State = HIGH; bool lastIRState = HIGH; volatile bool targetHit = false; unsigned long lastDebounceTime1 = 0; unsigned long lastDebounceTime2 = 0; const unsigned long debounceDelay = 50; // cursors to write highscores int score1_cursorY; int score2_cursorY; int score3_cursorY; int letterUnderlineX; int letterUnderlineY; int motorCounter = 0; // high scores (change to be empty values) int score1 = 500; int score2 = 300; int score3 = 0; int currentScore = 0; // most recent score int scoreToEdit; // housekeeping int graphicsCounter = 0; bool buttonsPressedAndReleased = false; // Add this as a global variable void highScore(){ unsigned long start = micros(); tft.setCursor(0, 0); tft.setTextSize(7); tft.setTextColor(HX8357_GREEN); tft.println("Welcome to Pinball!"); tft.setTextSize(3); tft.println(); tft.setTextColor(HX8357_RED); tft.setTextSize(5); tft.println("High Scores:"); tft.setTextColor(HX8357_WHITE); tft.setTextSize(3); tft.println(); score1_cursorY = tft.getCursorY(); // tft.fillRect(0, score1_cursorY, 200, 30, HX8357_BLACK); // Adjust width/height as needed tft.setCursor(0, score1_cursorY); // Move cursor back tft.print("1: "); tft.print(initials1); tft.print(" - "); tft.println(score1); tft.setTextSize(2); tft.println(); tft.setTextSize(3); score2_cursorY = tft.getCursorY(); // tft.fillRect(0, score2_cursorY, 200, 30, HX8357_BLACK); // Adjust width/height as needed tft.setCursor(0, score2_cursorY); // Move cursor back tft.print("2: "); tft.print(initials2); tft.print(" - "); tft.println(score2); tft.setTextSize(2); tft.println(); tft.setTextSize(3); score3_cursorY = tft.getCursorY(); // tft.fillRect(0, score3_cursorY, 200, 30, HX8357_BLACK); // Adjust width/height as needed tft.setCursor(0, score3_cursorY); // Move cursor back tft.print("3: "); tft.print(initials3); tft.print(" - "); tft.println(score3); } // Button 1 - Cycle through letters void handleButton1Press(int initial) { if (currentState == EDITING) { letterIndex = (letterIndex + 1) % 27; // Wrap around A-Z and '-' if (initial == 1){ initials1[charPos] = letters[letterIndex]; tft.setCursor(0, score1_cursorY); // Move cursor tft.fillRect(0, score1_cursorY, 150, 25, HX8357_BLACK); // Adjust width/height as needed tft.setCursor(0, score1_cursorY); // Move cursor back tft.print("1: "); tft.println(initials1); // tft.fillRect(letterUnderlineX, letterUnderlineY, 20, 3, HX8357_WHITE); // White underline Serial.print("Updated Initials: "); Serial.println(initials1); } else if (initial == 2){ initials2[charPos] = letters[letterIndex]; tft.setCursor(0, score2_cursorY); // Move cursor tft.fillRect(0, score2_cursorY, 150, 25, HX8357_BLACK); // Adjust width/height as needed tft.setCursor(0, score2_cursorY); // Move cursor back tft.print("2: "); tft.println(initials2); Serial.print("Updated Initials: "); Serial.println(initials2); } else if (initial == 3){ initials3[charPos] = letters[letterIndex]; tft.setCursor(0, score3_cursorY); // Move cursor tft.fillRect(0, score3_cursorY, 150, 25, HX8357_BLACK); // Adjust width/height as needed tft.setCursor(0, score3_cursorY); // Move cursor back tft.print("3: "); tft.println(initials3); Serial.print("Updated Initials: "); Serial.println(initials3); } } } // Button 2 - Move to next letter void handleButton2Press(int initial) { if (currentState == EDITING) { // Erase the previous underline if (initial == 1) { tft.fillRect(50, letterUnderlineY, 60, 3, HX8357_BLACK); // Clears all 3 positions } else if (initial == 2) { tft.fillRect(50, letterUnderlineY, 60, 3, HX8357_BLACK); } else if (initial == 3) { tft.fillRect(50, letterUnderlineY, 60, 3, HX8357_BLACK); // Fix incorrect score reference } charPos++; // Move to next letter if (charPos > 2) { Serial.println("Initials Set! Returning to IDLE mode."); currentState = IDLE; // Exit editing mode charPos = 0; } else { letterIndex = 0; // Reset letter choice // Update the underline position for the next character letterUnderlineX = 50 + (charPos * 20); // Draw the new white underline tft.fillRect(letterUnderlineX, letterUnderlineY, 20, 3, HX8357_WHITE); } } } // tasks void IRAM_ATTR targetISR() { targetHit = true; // Set flag when target is hit digitalWrite(LED_BUILTIN,HIGH); // Serial.println("DETECTED"); } void setup() { Serial.begin(115200); // 115200 delay(1000); Serial.println("HX8357D Test!"); pinMode (LED_BUILTIN, OUTPUT); // pin 3 pinMode(buttonPin1, INPUT_PULLUP); pinMode(buttonPin2, INPUT_PULLUP); pinMode(targetPin1, INPUT_PULLUP); pinMode(targetPin2, INPUT_PULLUP); pinMode(IRPin1, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(targetPin1), targetISR, RISING); attachInterrupt(digitalPinToInterrupt(targetPin2), targetISR, RISING); // begin AUDIO i2s_new_channel(&chan_cfg, &tx_handle, NULL); i2s_channel_init_std_mode(tx_handle, &std_cfg); i2s_channel_enable(tx_handle); // SPIFFS if(!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)){ Serial.println("SPIFFS Mount Failed"); return; } if (SPIFFS.begin()) { Serial.println("SPIFFS mounted successfully"); // Read and print the contents of both files readAndPrintFiles(); listFiles("/"); Serial.println("SPIFFS Info:"); Serial.printf("Total Bytes: %u\n", SPIFFS.totalBytes()); Serial.printf("Used Bytes: %u\n", SPIFFS.usedBytes()); Serial.printf("Free Bytes: %u\n", SPIFFS.totalBytes() - SPIFFS.usedBytes()); } else { Serial.println("SPIFFS mount failed"); } // begin lighting SetupLights(); // begin 7 segment Wire.begin(SDA, SCL); // Custom I2C pins matrix.begin(0x70); matrix.println(currentScore); matrix.writeDisplay(); // begin LCD display tft.begin(8000000); // read diagnostics (optional but can help debug problems) uint8_t x = tft.readcommand8(HX8357_RDPOWMODE); Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX); x = tft.readcommand8(HX8357_RDMADCTL); Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX); x = tft.readcommand8(HX8357_RDCOLMOD); Serial.print("Pixel Format: 0x"); Serial.println(x, HEX); x = tft.readcommand8(HX8357_RDDIM); Serial.print("Image Format: 0x"); Serial.println(x, HEX); x = tft.readcommand8(HX8357_RDDSDR); Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX); Serial.println(F("Benchmark Time (microseconds)")); tft.fillScreen(HX8357_BLACK); tft.setRotation(3); highScore(); Serial.println(F("Done!")); Serial.println("Motors check begin"); // begin motors MotorsSetup(); Serial.println("Motors check end"); currentState = GAMEPLAY; } void loop() { unsigned long currentTime = millis(); // ChangePalettePeriodically(); switchLights(); if (targetHit) { // Check if the target was hit targetHit = false; // Reset flag currentScore += 100; Serial.printf("Current Score: %d\n", currentScore); play_MONO_wav_file("/Point.wav"); size_t bytes_written; // updates 7 segment matrix.println(currentScore); matrix.writeDisplay(); } if ((currentState == IDLE) || (currentState == EDITING)) { highScore(); } // Fixing the GAMEPLAY loop if (currentState == GAMEPLAY) { digitalWrite(LED_BUILTIN, HIGH); /* if (motorCounter<2){ turnMotorsForward(); Serial.println("Motor step UP"); motorCounter++; } else{ Serial.println("Motor step DOWN"); turnMotorsBackward(); motorCounter++; } if(motorCounter==4){ motorCounter = 0; } */ // Graphics logic if (graphicsCounter == 1) { testLines(HX8357_CYAN); } else if (graphicsCounter == 2) { testRects(HX8357_GREEN); } else if (graphicsCounter == 3) { tft.fillScreen(HX8357_BLACK); testCircles(10, HX8357_RED); } else if (graphicsCounter == 4) { testTriangles(); } else if (graphicsCounter == 5) { testFilledTriangles(); } else if (graphicsCounter == 6) { testRoundRects(); } else if (graphicsCounter == 7) { // testFilledRoundRects(); graphicsCounter = 0; } graphicsCounter++; } bool sensorState = digitalRead(IRPin1); // Read IR sensor // Detect ball passing through if (sensorState == LOW && lastIRState == HIGH) { Serial.println("🔴 Ball In Holding Area (Beam Broken)"); currentState = IDLE; Serial.println("Entering IDLE Mode"); tft.fillScreen(HX8357_BLACK); digitalWrite(LED_BUILTIN, LOW); play_MONO_wav_file("/YouLose.wav"); size_t bytes_written; } // Detect when the beam is restored else if (sensorState == HIGH && lastIRState == LOW) { Serial.println("✅ Beam Restored (Ball Cleared)"); currentState = GAMEPLAY; currentScore = 0; // updates 7 segment matrix.println(currentScore); matrix.writeDisplay(); Serial.println("Entering GAMEPLAY Mode"); tft.fillScreen(HX8357_BLACK); digitalWrite(LED_BUILTIN, HIGH); graphicsCounter = 1; // Reset graphics counter } lastIRState = sensorState; // Update last state // Read button states bool reading1 = digitalRead(buttonPin1); bool reading2 = digitalRead(buttonPin2); // Debounce Button 1 if (reading1 != lastButton1State) { lastDebounceTime1 = currentTime; } if ((currentTime - lastDebounceTime1) > debounceDelay) { if (reading1 != button1State) { button1State = reading1; if (button1State == LOW) { handleButton1Press(scoreToEdit); Serial.println("Button1_Pressed"); } } } // Debounce Button 2 if (reading2 != lastButton2State) { lastDebounceTime2 = currentTime; } if ((currentTime - lastDebounceTime2) > debounceDelay) { if (reading2 != button2State) { button2State = reading2; if (button2State == LOW) { handleButton2Press(1); Serial.println("Button2_Pressed"); } } } // Cooldown period to prevent multiple state transitions const unsigned long COOLDOWN_DELAY = 1000; // 1 second cooldown static unsigned long lastStateChangeTime = 0; if (currentState == IDLE) { Serial.println("In IDLE mode"); // Check if editing should be triggered if (currentScore > score1) { Serial.println("Entering Initials Editing Mode..."); currentState = EDITING; scoreToEdit = 1; strcpy(initials3, initials2); strcpy(initials2, initials1); strcpy(initials1, "AAA"); score3 = score2; score2 = score1; score1 = currentScore; // Draw the white underline under the first letter of initials1 letterUnderlineY = score1_cursorY + 25; tft.fillRect(50, letterUnderlineY, 20, 3, HX8357_WHITE); } else if (currentScore > score2) { Serial.println("Entering Initials Editing Mode..."); currentState = EDITING; scoreToEdit = 2; strcpy(initials3, initials2); strcpy(initials2, "AAA"); score3 = score2; score2 = currentScore; // Draw the white underline under the first letter of initials1 letterUnderlineY = score2_cursorY + 25; tft.fillRect(50, letterUnderlineY, 20, 3, HX8357_WHITE); } else if (currentScore > score3) { Serial.println("Entering Initials Editing Mode..."); currentState = EDITING; strcpy(initials3, "AAA"); scoreToEdit = 3; score3 = currentScore; // Draw the white underline under the first letter of initials1 letterUnderlineY = score3_cursorY + 25; tft.fillRect(50, letterUnderlineY, 20, 3, HX8357_WHITE); } highScore(); } // Save button states for next loop lastButton1State = reading1; lastButton2State = reading2; }