// This project is protected under the Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0) // Attribute this work to https://github.com/pchretien/fibo // Codigo ajustado por Pedro Eisman Cabado para incluir leds individuales de minuto y led de AM/PM // Codigo optimizado por Jose Antonio García Rodriguez para regular correctamente el brillo, dar las "campanadas" cada hora e incluir un modo lámpara // Librerias con las funciones de horas y minutos del reloj RTC DS3231 conectado via I2C y la libreria Wire //#include #include #include // Libreria para la gestion de los botones #include // Libreria para gestionar los diferentes LED: NeoPixel #include // Libreria para almacenar paramentros brillo, colores #define HOUR_PIN 7 // Pin para el botón de Horas #define MINUTE_PIN 8 // Pin para el botón de Minutosx5 #define MINUTEIND_PIN 10 // Pin para el botón de Minutosx1 #define BTN_PIN 5 #define SET_PIN 6 // Pin para el boton MODO #define LED_PIN 4 // Pin que da paso a los LED de Horas y Minutosx5 #define LED1_PIN 2 // Pin que da paso a los LED de Minutosx1, que son 4 (LED1_COUNT) #define AMPM_PIN 13 // Pin que da paso a los LED AM/PM #define LED_COUNT 9 // Numero de LED para las Horas y Minutosx5 #define LED1_COUNT 4 // Numero de LED para los minutosx1 #define AMPM_COUNT 1 // Numero de LED para AM/PM #define DEBOUNCE_DELAY 10 #define MAX_BUTTONS_INPUT 20 #define MAX_MODES 2 #define TOTAL_PALETTES 1 // Solo 1 paleta para cada modo #define CLOCK_PIXELS 5 // Numero de LEDS (bloques) para indicar las Horas y Minutosx5 #define COLORES 0 // Direcciones de la EEPROM para almacenar configuracion Reloj #define BRILLO 1 #define COLORES_L 2 // Direcciones de la EEPROM para almacenar configuracion Lampara #define BRILLO_L 3 // DEFINICION DE VARIABLES GLOBALES Y OBJETOS DEL PROGRAMA ------------------------------------------ RTC_DS3231 RTC; // Reloj que marcará los pulsos RTC, es un RTC DS3231 // Creación de las instancias para los todos los LEDS de la clase Adafruit_NeoPixel. Adafruit_NeoPixel leds = Adafruit_NeoPixel(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); // Horas y Minutosx5 Adafruit_NeoPixel LED1 = Adafruit_NeoPixel(LED1_COUNT, LED1_PIN, NEO_GRB + NEO_KHZ800); // Minutosx1 Adafruit_NeoPixel AMPM = Adafruit_NeoPixel(AMPM_COUNT, AMPM_PIN, NEO_GRB + NEO_KHZ800); // AM/PM byte bits[CLOCK_PIXELS]; // Botones GFButton set_button(SET_PIN); GFButton button(BTN_PIN); GFButton hour_button(HOUR_PIN); GFButton minute_button(MINUTE_PIN); GFButton minuteind_button(MINUTEIND_PIN); byte lightLevelIndex = 0; uint8_t lightLevels[8] = {255, 200, 165, 130, 75, 30, 12, 4}; int ledsValues[5] = {1,1,2,3,5}; // Array con los valores de cada LED para horas y minutos DateTime now; // En lugar de definirla en loop unsigned long tiempoInicio=0; // Para demorar la escritura en la EEPROM const unsigned long duracion = 30000; // 60 segundos unsigned long setButtonPressTime = 0; // Tiempo de pulsación del botón SET const unsigned long longPressDuration = 2000; // 2 segundos para pulsación larga bool longPressExecuted = false; // Estado del botón SET byte mode=0; // Modo de funcionamiento: Reloj, Lampara bool TocarCampana=true; byte palette = 0; // Siempre usará la paleta 0 (única para cada modo) struct Configuracion { byte paleta_Reloj; byte brillo_Reloj; byte paleta_Lampara; byte brillo_Lampara; }; Configuracion miConfig; uint32_t black = leds.Color(0,0,0); // PALETA ÚNICA PARA MODO RELOJ (originalmente paleta #1 "Genérico") uint32_t colors[1][6] = { { leds.Color(255,255,255), // Apagado (Blanco) leds.Color(255,10,10), // Horas (Rojo) leds.Color(10,255,10), // Minutosx5 (Verde) leds.Color(10,10,255), // Horas+Minutos (Azul) leds.Color(128,0,128), // AM/PM (Morado) leds.Color(255,223,0) // Minutosx1 (Amarillo dorado) } }; // PALETA ÚNICA PARA MODO LÁMPARA (originalmente paleta #1 "Genérica") uint32_t colores[1][6] = { { leds.Color(255, 255, 0), // Amarillo leds.Color(0, 255, 0), // Verde leds.Color(0, 255, 255), // Ciano leds.Color(0, 0, 255), // Azul leds.Color(255, 0, 255), // Magenta leds.Color(255, 0, 0) // Rojo } }; byte currentColorIndex = 0; // Índice para transiciones en modo lámpara unsigned long transitionStart = 0; const unsigned long transitionDuration = 20000; // 20 segundos por transición // DEFINICION DE LAS FUNCIONES QUE SE EMPLEAN ------------------------------- void setup () { Serial.begin(57600); set_button.setHoldTime(1000); set_button.setPressHandler(modeChange); // Pulsación corta button.setHoldTime(1000); button.setPressHandler(changeLightLevel); // Pulsación corta hour_button.setPressHandler(Adelanta60); minute_button.setPressHandler(Adelanta5); minuteind_button.setPressHandler(Adelanta1); // Lectura de configuracion almacenada en la EEPROM EEPROM.get(COLORES, miConfig); miConfig.paleta_Reloj=(miConfig.paleta_Reloj==255 ? 0 : miConfig.paleta_Reloj); miConfig.brillo_Reloj=(miConfig.brillo_Reloj==255 ? 0 : miConfig.brillo_Reloj); miConfig.paleta_Lampara=(miConfig.paleta_Lampara==255 ? 0 : miConfig.paleta_Lampara); miConfig.brillo_Lampara=(miConfig.brillo_Lampara==255 ? 0 : miConfig.brillo_Lampara); palette=miConfig.paleta_Reloj; lightLevelIndex=miConfig.brillo_Reloj; // Inicialización de módulo RTC Wire.begin(); if (! RTC.begin()) { Serial.println("El RTC no funciona!"); } if (RTC.lostPower()) { // Si la pila está agotada Serial.println("El RTC perdio la alimentacion. Verificar su pila y ajustar la hora manualmente!"); RTC.adjust(DateTime(F(__DATE__),F(__TIME__))); } leds.begin(); // Inicializacion de los LEDS LED1.begin(); AMPM.begin(); leds.setBrightness(lightLevels[lightLevelIndex]); LED1.setBrightness(lightLevels[lightLevelIndex]); AMPM.setBrightness(lightLevels[lightLevelIndex]); leds.show(); LED1.show(); AMPM.show(); } void GuardaConfiguracion() { Serial.println("Guardando Configuracion"); EEPROM.update(COLORES, miConfig.paleta_Reloj); EEPROM.update(BRILLO, miConfig.brillo_Reloj); EEPROM.update(COLORES_L, miConfig.paleta_Lampara); EEPROM.update(BRILLO_L, miConfig.brillo_Lampara); } void calcInstructionOne(int time, byte instr[]) { const int remain[] = {0, 1, 2, 4, 7 }; for (int i = 4; i >= 0; i--) { if (time > remain[i]) { time -= ledsValues[i]; instr[i] = 1; } else { instr[i] = 0; } } } void dispTime(int hours, int minutes) { byte hoursInstr[CLOCK_PIXELS]; byte minsInstr[CLOCK_PIXELS]; byte i; byte min=minutes%5; if (hours >= 12) { // Activar el led AM/PM sin son más de las 12 horas AMPM.setPixelColor(0, colors[palette][4]); // AM/PM: Morado } else { AMPM.setPixelColor(0, 0, 0, 0); // Negro, apagado } AMPM.show(); calcInstructionOne(hours % 12, hoursInstr); // Decidir que leds se encenderan calcInstructionOne(minutes / 5, minsInstr); // Configura los colores de los LEDs de horas y minutos en funcion de los valores antes obtenidos for (i = 0; i < CLOCK_PIXELS; i++) { bits[i] = (hoursInstr[i] * 0x01) | (minsInstr[i] * 0x02); // Suma de hoursInstr y minsInstr*2 setPixel(i, colors[palette][bits[i]]); } leds.show(); for (i=1; i<=LED1_COUNT; i++) { if (min>=i) { LED1.setPixelColor(LED1_COUNT-i, colors[palette][5]); // Amarillo dorado } else { LED1.setPixelColor(LED1_COUNT-i, 255, 255, 255); // Blanco } } LED1.show(); } void printDate(DateTime now) { char fechaHora[20]; // Buffer para la cadena de fecha y hora usando sprintf para formarla snprintf(fechaHora, sizeof(fechaHora), "%02d/%02d/%04d %02d:%02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second()); Serial.println(fechaHora); // Imprime la cadena completa en una sola llamada } void themeChange() { // Función vacía ya que no hay rotación de paletas // Se mantiene por compatibilidad con el manejo de botones } void changeLightLevel() { lightLevelIndex = (lightLevelIndex + 1) % (sizeof(lightLevels)/sizeof(lightLevels[0])); tiempoInicio=millis(); // Demoramos la escritura en EEPROM por si hay más pulsaciones leds.setBrightness(lightLevels[lightLevelIndex]); LED1.setBrightness(lightLevels[lightLevelIndex]); AMPM.setBrightness(lightLevels[lightLevelIndex]); if (mode==0) { miConfig.brillo_Reloj=lightLevelIndex; } else { miConfig.brillo_Lampara=lightLevelIndex; } } void setPixel(byte pixel, uint32_t color) { switch (pixel) { case 0: leds.setPixelColor(0, color); break; case 1: leds.setPixelColor(1, color); break; case 2: leds.setPixelColor(2, color); break; case 3: leds.setPixelColor(3, color); leds.setPixelColor(4, color); break; case 4: leds.setPixelColor(5, color); leds.setPixelColor(6, color); leds.setPixelColor(7, color); leds.setPixelColor(8, color); leds.setPixelColor(9, color); break; } } void modeChange() { mode = (mode +1) % MAX_MODES; if (mode == 1) { // Al cambiar a modo lámpara currentColorIndex = 0; transitionStart = millis(); TocarCampana=false; } } void Adelanta1() {adjustTime(0,1);} void Adelanta5() {adjustTime(0,5);} void Adelanta60() {adjustTime(1,0);} void adjustTime(int hoursToAdd, int minutesToAdd) { DateTime fixTime = RTC.now(); RTC.adjust(DateTime(fixTime.year(), fixTime.month(), fixTime.day(), (fixTime.hour() + hoursToAdd) % 24, (fixTime.minute() + minutesToAdd) % 60, 0)); TocarCampana=false; } void Campanas(byte horas) { byte Veces=(horas%12==0? 12: horas%12); for (byte i=1; i<=Veces; i++) { delay(250); for (byte j=lightLevels[lightLevelIndex]; j>0; j--) { leds.setBrightness(j); leds.show(); delay(4+lightLevelIndex); } delay(100); leds.setBrightness(lightLevels[lightLevelIndex]); dispTime(horas%12, 0); leds.show(); } TocarCampana=false; } void smoothTransition() { unsigned long now = millis(); float progress = (now - transitionStart) / (float)transitionDuration; byte nextColorIndex = (currentColorIndex + 1) % 6; // 6 colores en la paleta if (progress >= 1.0) { currentColorIndex = nextColorIndex; transitionStart = now; progress = 0.0; } uint32_t currentColor = colores[0][currentColorIndex]; uint32_t nextColor = colores[0][nextColorIndex]; uint8_t r = interpolate(red(currentColor), red(nextColor), progress); uint8_t g = interpolate(green(currentColor), green(nextColor), progress); uint8_t b = interpolate(blue(currentColor), blue(nextColor), progress); for (byte i = 0; i < LED_COUNT; i++) { leds.setPixelColor(i, r, g, b); } AMPM.setPixelColor(0, r, g, b); for (byte i=0; i< LED1_COUNT; i++) { LED1.setPixelColor(i, r, g, b); } leds.show(); AMPM.show(); LED1.show(); } uint8_t red(uint32_t color) { return (color >> 16) & 0xFF; } uint8_t green(uint32_t color) { return (color >> 8) & 0xFF; } uint8_t blue(uint32_t color) { return color & 0xFF; } uint8_t interpolate(uint8_t start, uint8_t end, float progress) { return start + (end - start) * progress; } void loop () { set_button.process(); button.process(); switch (mode) { case 0: hour_button.process(); minute_button.process(); minuteind_button.process(); now = RTC.now(); dispTime(now.hour(), now.minute()); if (now.minute()%60==0) { if (TocarCampana==true) { Campanas(now.hour()); } } else { TocarCampana=true; } break; case 1: smoothTransition(); break; } if (tiempoInicio>0) { if (millis()-tiempoInicio > duracion) { GuardaConfiguracion(); tiempoInicio=0; } } }