/******************************************************************* Tetris clock that fetches its time Using the EzTimeLibrary For use with the ESP32 or TinyPICO * * Written by Brian Lough YouTube: https://www.youtube.com/brianlough Tindie: https://www.tindie.com/stores/brianlough/ Twitter: https://twitter.com/witnessmenow *******************************************************************/ // ---------------------------- // Standard Libraries - Already Installed if you have ESP32 set up // ---------------------------- #include // ---------------------------- // Additional Libraries - each one of these will need to be installed. // ---------------------------- // Enabling this is meant to have a performance // improvement but its worse for me. // https://github.com/2dom/PxMatrix/pull/103 //#define double_buffer #include // The library for controlling the LED Matrix // At time of writing this my changes for the TinyPICO // Have been merged into the main PxMatrix library, // but have not been released, so you will need to install // from Github // // If you are using a regular ESP32 you may be able to use // the library manager version // https://github.com/2dom/PxMatrix // Adafruit GFX library is a dependancy for the PxMatrix Library // Can be installed from the library manager // https://github.com/adafruit/Adafruit-GFX-Library #include // This library draws out characters using a tetris block // amimation // Can be installed from the library manager // https://github.com/toblum/TetrisAnimation #include // Library used for getting the time and adjusting for DST // Search for "ezTime" in the Arduino Library manager // https://github.com/ropg/ezTime // ---- Stuff to configure ---- // Initialize Wifi connection to the router char ssid[] = "SSID"; // your network SSID (name) char password[] = "PASSWORD"; // your network key // Set a timezone using the following list // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones #define MYTIMEZONE "Europe/Dublin" // Sets whether the clock should be 12 hour format or not. bool twelveHourFormat = false; // If this is set to false, the number will only change if the value behind it changes // e.g. the digit representing the least significant minute will be replaced every minute, // but the most significant number will only be replaced every 10 minutes. // When true, all digits will be replaced every minute. bool forceRefresh = true; // ----------------------------- // ----- Wiring ------- #define P_LAT 22 #define P_A 19 #define P_B 23 #define P_C 18 #define P_D 5 #define P_E 15 //#define P_OE 26 //TinyPICO //#define P_OE 21 //Huzzah32 #define P_OE 2 // Generic ESP32 // --------------------- portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; hw_timer_t * timer = NULL; hw_timer_t * animationTimer = NULL; // PxMATRIX display(32,16,P_LAT, P_OE,P_A,P_B,P_C); // PxMATRIX display(64,32,P_LAT, P_OE,P_A,P_B,P_C,P_D); PxMATRIX display(64, 32, P_LAT, P_OE, P_A, P_B, P_C, P_D, P_E); TetrisMatrixDraw tetris(display); // Main clock TetrisMatrixDraw tetris2(display); // The "M" of AM/PM TetrisMatrixDraw tetris3(display); // The "P" or "A" of AM/PM Timezone myTZ; unsigned long oneSecondLoopDue = 0; bool showColon = true; volatile bool finishedAnimating = false; bool displayIntro = true; String lastDisplayedTime = ""; String lastDisplayedAmPm = ""; // This method is needed for driving the display void IRAM_ATTR display_updater() { portENTER_CRITICAL_ISR(&timerMux); display.display(10); portEXIT_CRITICAL_ISR(&timerMux); } // This method is for controlling the tetris library draw calls void animationHandler() { #ifndef double_buffer portENTER_CRITICAL_ISR(&timerMux); #endif // Not clearing the display and redrawing it when you // dont need to improves how the refresh rate appears if (!finishedAnimating) { #ifdef double_buffer display.fillScreen(tetris.tetrisBLACK); #else display.clearDisplay(); #endif //display.fillScreen(tetris.tetrisBLACK); if (displayIntro) { finishedAnimating = tetris.drawText(1, 21); } else { if (twelveHourFormat) { // Place holders for checking are any of the tetris objects // currently still animating. bool tetris1Done = false; bool tetris2Done = false; bool tetris3Done = false; tetris1Done = tetris.drawNumbers(-6, 26, showColon); tetris2Done = tetris2.drawText(56, 25); // Only draw the top letter once the bottom letter is finished. if (tetris2Done) { tetris3Done = tetris3.drawText(56, 15); } finishedAnimating = tetris1Done && tetris2Done && tetris3Done; } else { finishedAnimating = tetris.drawNumbers(2, 26, showColon); } } #ifdef double_buffer display.showBuffer(); #endif } #ifndef double_buffer portEXIT_CRITICAL_ISR(&timerMux); #endif } void drawIntro(int x = 0, int y = 0) { tetris.drawChar("P", x, y, tetris.tetrisCYAN); tetris.drawChar("o", x + 5, y, tetris.tetrisMAGENTA); tetris.drawChar("w", x + 11, y, tetris.tetrisYELLOW); tetris.drawChar("e", x + 17, y, tetris.tetrisGREEN); tetris.drawChar("r", x + 22, y, tetris.tetrisBLUE); tetris.drawChar("e", x + 27, y, tetris.tetrisRED); tetris.drawChar("d", x + 32, y, tetris.tetrisWHITE); tetris.drawChar(" ", x + 37, y, tetris.tetrisMAGENTA); tetris.drawChar("b", x + 42, y, tetris.tetrisYELLOW); tetris.drawChar("y", x + 47, y, tetris.tetrisGREEN); } void drawConnecting(int x = 0, int y = 0) { tetris.drawChar("C", x, y, tetris.tetrisCYAN); tetris.drawChar("o", x + 5, y, tetris.tetrisMAGENTA); tetris.drawChar("n", x + 11, y, tetris.tetrisYELLOW); tetris.drawChar("n", x + 17, y, tetris.tetrisGREEN); tetris.drawChar("e", x + 22, y, tetris.tetrisBLUE); tetris.drawChar("c", x + 27, y, tetris.tetrisRED); tetris.drawChar("t", x + 32, y, tetris.tetrisWHITE); tetris.drawChar("i", x + 37, y, tetris.tetrisMAGENTA); tetris.drawChar("n", x + 42, y, tetris.tetrisYELLOW); tetris.drawChar("g", x + 47, y, tetris.tetrisGREEN); } void setup() { Serial.begin(115200); // Attempt to connect to Wifi network: Serial.print("Connecting Wifi: "); Serial.println(ssid); // Set WiFi to station mode and disconnect from an AP if it was Previously // connected WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(""); Serial.println("WiFi connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); // Do not set up display before WiFi connection // as it will crash! // Intialise display library //display.begin(16, SPI_BUS_CLK, 27, SPI_BUS_MISO, SPI_BUS_SS); // TinyPICO display.begin(16); // Generic ESP32 including Huzzah display.flushDisplay(); // Setup timer for driving display timer = timerBegin(0, 80, true); timerAttachInterrupt(timer, &display_updater, true); timerAlarmWrite(timer, 2000, true); timerAlarmEnable(timer); yield(); #ifdef double_buffer display.fillScreen(tetris.tetrisBLACK); #else display.clearDisplay(); #endif // "connecting" drawConnecting(5, 10); #ifdef double_buffer display.showBuffer(); #endif // Setup EZ Time setDebug(INFO); waitForSync(); Serial.println(); Serial.println("UTC: " + UTC.dateTime()); myTZ.setLocation(F(MYTIMEZONE)); Serial.print(F("Time in your set timezone: ")); Serial.println(myTZ.dateTime()); #ifdef double_buffer display.fillScreen(tetris.tetrisBLACK); #else display.clearDisplay(); #endif // "Powered By" drawIntro(6, 12); #ifdef double_buffer display.showBuffer(); #endif delay(2000); // Start the Animation Timer tetris.setText("TINY PICO"); animationTimer = timerBegin(1, 80, true); timerAttachInterrupt(animationTimer, &animationHandler, true); timerAlarmWrite(animationTimer, 100000, true); timerAlarmEnable(animationTimer); // Wait for the animation to finish while (!finishedAnimating) { delay(10); //waiting for intro to finish } delay(2000); finishedAnimating = false; displayIntro = false; tetris.scale = 2; } void setMatrixTime() { String timeString = ""; String AmPmString = ""; if (twelveHourFormat) { // Get the time in format "1:15" or 11:15 (12 hour, no leading 0) // Check the EZTime Github page for info on // time formatting timeString = myTZ.dateTime("g:i"); //If the length is only 4, pad it with // a space at the beginning if (timeString.length() == 4) { timeString = " " + timeString; } //Get if its "AM" or "PM" AmPmString = myTZ.dateTime("A"); if (lastDisplayedAmPm != AmPmString) { Serial.println(AmPmString); lastDisplayedAmPm = AmPmString; // Second character is always "M" // so need to parse it out tetris2.setText("M", forceRefresh); // Parse out first letter of String tetris3.setText(AmPmString.substring(0, 1), forceRefresh); } } else { // Get time in format "01:15" or "22:15"(24 hour with leading 0) timeString = myTZ.dateTime("H:i"); } // Only update Time if its different if (lastDisplayedTime != timeString) { Serial.println(timeString); lastDisplayedTime = timeString; tetris.setTime(timeString, forceRefresh); // Must set this to false so animation knows // to start again finishedAnimating = false; } } void handleColonAfterAnimation() { // It will draw the colon every time, but when the colour is black it // should look like its clearing it. uint16_t colour = showColon ? tetris.tetrisWHITE : tetris.tetrisBLACK; // The x position that you draw the tetris animation object int x = twelveHourFormat ? -6 : 2; // The y position adjusted for where the blocks will fall from // (this could be better!) int y = 26 - (TETRIS_Y_DROP_DEFAULT * tetris.scale); tetris.drawColon(x, y, colour); } void loop() { unsigned long now = millis(); if (now > oneSecondLoopDue) { // We can call this often, but it will only // update when it needs to setMatrixTime(); showColon = !showColon; // To reduce flicker on the screen we stop clearing the screen // when the animation is finished, but we still need the colon to // to blink if (finishedAnimating) { handleColonAfterAnimation(); } oneSecondLoopDue = now + 1000; } }