#include #include #include #include #include #include // ================================================= // OLED 128x32 // ================================================= #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 32 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // ================================================= // PINES // ================================================= #define PIN_BTN_HORAS 2 #define PIN_BTN_MINUTOS 3 #define PIN_BTN_CUENTABOLAS 4 #define PIN_BTN_STOP A0 #define PIN_BTN_DIAG 12 #define PIN_SERVO_FLIPPER 9 #define PIN_SERVO_MOTOR 10 #define PIN_SERVO_CAMPANADAS 11 #define PIN_MOTOR_PWMA 5 #define PIN_MOTOR_AIN1 7 #define PIN_MOTOR_AIN2 8 // ================================================= // CONFIGURACIÓN (NO CAMBIADA) // ================================================= const uint8_t CAMPANADA_DELAY_SEG = 0; const unsigned long DEBOUNCE_MS = 80; const unsigned long LCD_ROTATE_MS = 2000; const unsigned long LCD_UPDATE_MS = 200; const int SERVO_MOTOR_REPOSO = 76; const int SERVO_MOTOR_DELTA = 40; const unsigned long MOTOR_TIMEOUT_MS = 120000UL; const unsigned long DIAG_LONG_PRESS_MS = 3000; const unsigned long BALL_GUARD_MS = 400; // ================================================= // OBJETOS // ================================================= RTC_DS3231 rtc; Servo servoFlipper; Servo servoMotor; Servo servoCampanadas; // ================================================= // VARIABLES (NO CAMBIADAS) // ================================================= enum { BTN_H, BTN_M, BTN_CB, BTN_STOP_I, BTN_TOTAL }; bool lastBtn[BTN_TOTAL]; unsigned long lastBtnTime[BTN_TOTAL]; unsigned long nowMs; unsigned long servoMotorReturn = 0; unsigned long servoCampReturn = 0; unsigned long lcdLastRotate = 0; unsigned long lcdLastUpdate = 0; int lastMinute = -1; int prevMinute = -1; int lastHour = -1; bool flipperAbierto = false; bool flipperHechoHora = false; bool campanadaHechaHora = false; int bolasContadas = 0; int bolasObjetivo = 0; uint8_t lcdPage = 0; unsigned long motorStartMs = 0; bool motorBloqueado = false; unsigned long diagPressStart = 0; bool diagLongHandled = false; unsigned long lastBallMs = 0; bool lastHourBtn = HIGH; bool lastMinuteBtn = HIGH; // ================================================= // FUNCIONES (NO CAMBIADAS) // ================================================= bool botonPulsado(uint8_t idx, uint8_t pin) { bool estado = digitalRead(pin); unsigned long ahora = millis(); if (estado != lastBtn[idx]) { lastBtn[idx] = estado; lastBtnTime[idx] = ahora; return false; } if (estado == LOW && (ahora - lastBtnTime[idx]) > DEBOUNCE_MS) { lastBtnTime[idx] = ahora + 1; return true; } return false; } int bolasPorHora(int hora24) { int h = hora24 % 12; if (h == 0) return 1; return h + 1; } // ================================================= // SETUP // ================================================= void setup() { wdt_enable(WDTO_4S); Wire.begin(); rtc.begin(); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setRotation(2); display.display(); pinMode(PIN_BTN_HORAS, INPUT_PULLUP); pinMode(PIN_BTN_MINUTOS, INPUT_PULLUP); pinMode(PIN_BTN_CUENTABOLAS, INPUT_PULLUP); pinMode(PIN_BTN_STOP, INPUT_PULLUP); pinMode(PIN_BTN_DIAG, INPUT_PULLUP); for (int i = 0; i < BTN_TOTAL; i++) lastBtn[i] = HIGH; servoFlipper.attach(PIN_SERVO_FLIPPER); servoMotor.attach(PIN_SERVO_MOTOR); servoCampanadas.attach(PIN_SERVO_CAMPANADAS); servoFlipper.write(0); servoMotor.write(SERVO_MOTOR_REPOSO); servoCampanadas.write(0); pinMode(PIN_MOTOR_PWMA, OUTPUT); pinMode(PIN_MOTOR_AIN1, OUTPUT); pinMode(PIN_MOTOR_AIN2, OUTPUT); digitalWrite(PIN_MOTOR_AIN1, HIGH); digitalWrite(PIN_MOTOR_AIN2, LOW); DateTime now = rtc.now(); lastMinute = now.minute(); prevMinute = now.minute(); lastHour = now.hour(); } // ================================================= // LOOP // ================================================= void loop() { wdt_reset(); DateTime now = rtc.now(); nowMs = millis(); int currentMinute = now.minute(); // ---- RESET LARGO (IGUAL) if (digitalRead(PIN_BTN_DIAG) == LOW) { if (diagPressStart == 0) { diagPressStart = nowMs; diagLongHandled = false; } if (!diagLongHandled && (nowMs - diagPressStart) > DIAG_LONG_PRESS_MS) { motorBloqueado = false; motorStartMs = 0; flipperAbierto = false; flipperHechoHora = false; bolasContadas = 0; bolasObjetivo = 0; servoFlipper.write(0); servoCampanadas.write(0); diagLongHandled = true; } } else { diagPressStart = 0; diagLongHandled = false; } // ---- AJUSTE RTC (IGUAL) bool hState = digitalRead(PIN_BTN_HORAS); bool mState = digitalRead(PIN_BTN_MINUTOS); if (lastHourBtn == HIGH && hState == LOW) { rtc.adjust(DateTime(now.year(), now.month(), now.day(), (now.hour() + 1) % 24, 0, 0)); } lastHourBtn = hState; if (lastMinuteBtn == HIGH && mState == LOW) { rtc.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), (currentMinute + 1) % 60, 0)); } lastMinuteBtn = mState; // ---- MOTOR (IGUAL) bool motorDebeGirar = (digitalRead(PIN_BTN_STOP) != LOW) && !motorBloqueado; if (motorDebeGirar) { analogWrite(PIN_MOTOR_PWMA, 240); if (motorStartMs == 0) motorStartMs = nowMs; } else { analogWrite(PIN_MOTOR_PWMA, 0); motorStartMs = 0; } if (motorStartMs > 0 && (nowMs - motorStartMs) > MOTOR_TIMEOUT_MS) { motorBloqueado = true; analogWrite(PIN_MOTOR_PWMA, 0); } // ---- SERVO MINUTOS (IGUAL) if (currentMinute != lastMinute) { lastMinute = currentMinute; servoMotor.write(SERVO_MOTOR_REPOSO - SERVO_MOTOR_DELTA); servoMotorReturn = nowMs + 2000; } if (servoMotorReturn && nowMs > servoMotorReturn) { servoMotor.write(SERVO_MOTOR_REPOSO); servoMotorReturn = 0; } // ---- FLIPPER (IGUAL) if (!flipperHechoHora && prevMinute == 29 && currentMinute == 30) { flipperHechoHora = true; flipperAbierto = true; bolasContadas = 0; bolasObjetivo = bolasPorHora(now.hour()); lastBallMs = 0; servoFlipper.write(53); } if (now.hour() != lastHour) { lastHour = now.hour(); flipperHechoHora = false; campanadaHechaHora = false; } // ---- CONTEO BOLAS (IGUAL) if (flipperAbierto && botonPulsado(BTN_CB, PIN_BTN_CUENTABOLAS) && (nowMs - lastBallMs) > BALL_GUARD_MS) { lastBallMs = nowMs; bolasContadas++; if (bolasContadas >= bolasObjetivo) { flipperAbierto = false; servoFlipper.write(0); } } // ---- CAMPANADAS (IGUAL) if (!campanadaHechaHora && currentMinute == 0 && now.second() == CAMPANADA_DELAY_SEG) { servoCampanadas.write(55); servoCampReturn = nowMs + 12000; campanadaHechaHora = true; } if (servoCampReturn && nowMs > servoCampReturn) { servoCampanadas.write(0); servoCampReturn = 0; } // ================================================= // OLED (SUSTITUYE SOLO LCD) // ================================================= if (nowMs - lcdLastUpdate > LCD_UPDATE_MS) { display.clearDisplay(); // Hora con segundos display.setCursor(0, 0); char buf0[20]; snprintf(buf0, sizeof(buf0), "%02d:%02d:%02d", now.hour(), currentMinute, now.second()); display.print(buf0); // Motor display.setCursor(0, 10); if (motorBloqueado) display.print("MOTOR BLOQUEADO"); else display.print(digitalRead(PIN_BTN_STOP) == LOW ? "MOTOR STOP" : "MOTOR RUN"); // BOLAS (LÓGICA CORRECTA DEFINIDA POR TI) display.setCursor(0, 20); int objetivoHora = bolasPorHora(now.hour()); display.print("BOLAS "); if (currentMinute < 30) { // Antes de las :30 → siempre 0/N display.print("0/"); display.print(objetivoHora); } else { // Después de las :30 → conteo real display.print(bolasContadas); display.print("/"); display.print(bolasObjetivo); } display.display(); lcdLastUpdate = nowMs; } prevMinute = currentMinute; }