// Sketch to draw an analogue clock on the screen // This uses anti-aliased drawing functions that are built into TFT_eSPI // Anti-aliased lines can be drawn with sub-pixel resolution and permit lines to be // drawn with less jaggedness. // Requires MBed OS 2040 RAspberry pi pico board // Based on a sketch by DavyLandman: // https://github.com/Bodmer/TFT_eSPI/issues/905 #include "MBED_RPi_Pico_TimerInterrupt.h" #define WIFI_SSID "Your_SSID" #define WIFI_PASSWORD "Your_Password" #include #include // Master copy here: https://github.com/Bodmer/TFT_eSPI #include #include "NotoSansBold15.h" TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h TFT_eSprite face = TFT_eSprite(&tft); const int BtnFwd = 6; const int BtnMode = 8; const int BtnBack = 7; #define CLOCK_FG TFT_BLACK #define CLOCK_BG TFT_WHITE #define HILITE_FG TFT_RED #define LABEL_FG TFT_NAVY #define CLOCK_R 240.0f / 2.0f // Clock face radius (float type) #define CLOCK_XY CLOCK_R #define H_HAND_LENGTH CLOCK_R/2.0f #define M_HAND_LENGTH CLOCK_R/1.4f #define S_HAND_LENGTH CLOCK_R/1.3f #define FACE_W CLOCK_R * 2 + 1 #define FACE_H CLOCK_R * 2 + 1 // Calculate 1 second increment angles. Hours and minute hand angles // change every second so we see smooth sub-pixel movement #define SECOND_ANGLE 360.0 / 60.0 #define MINUTE_ANGLE SECOND_ANGLE / 60.0 #define HOUR_ANGLE MINUTE_ANGLE / 12.0 // Sprite width and height #define FACE_W CLOCK_R * 2 + 1 #define FACE_H CLOCK_R * 2 + 1 const int ModeNormal = 0; const int ModeMinutes = 1; const int ModeHours = 2; // Time h:m:s uint8_t h = 0, m = 0, s = 0; uint8_t mode = ModeNormal; float time_secs = h * 3600 + m * 60 + s; // Init RPI_PICO_Timer, can use any from 0-15 pseudo-hardware timers MBED_RPI_PICO_Timer ITimer0(0); void TimerHandler(uint alarm_num) { /////////////////////////////////////////////////////////// // Always call this for MBED RP2040 before processing ISR /////////////////////////////////////////////////////////// TIMER_ISR_START(alarm_num); // Increment time by 1000 milliseconds time_secs += 1; // Midnight roll-over if (time_secs >= (60 * 60 * 24)) time_secs = 0; //////////////////////////////////////////////////////////// // Always call this for MBED RP2040 after processing ISR TIMER_ISR_END(alarm_num); //////////////////////////////////////////////////////////// } // ========================================================================= // Setup // ========================================================================= void setup() { Serial.begin(115200); pinMode(BtnFwd,INPUT_PULLUP); pinMode(BtnBack,INPUT_PULLUP); pinMode(BtnMode,INPUT_PULLUP); delay(500); Serial.println("Booting..."); // Interval in microsecs if (ITimer0.attachInterruptInterval(1000*1000, TimerHandler)) { Serial.print(F("Starting ITimer0 OK, millis() = ")); Serial.println(millis()); } else { Serial.println(F("Can't set ITimer0. Select another freq. or timer")); } // Initialise the screen tft.init(); // Ideally set orientation for good viewing angle range because // the anti-aliasing effectiveness varies with screen viewing angle // Usually this is when screen ribbon connector is at the bottom tft.setRotation(0); tft.fillScreen(TFT_RED); // Create the clock face sprite //face.setColorDepth(8); // 8 bit will work, but reduces effectiveness of anti-aliasing face.createSprite(FACE_W, FACE_H); // Only 1 font used in the sprite, so can remain loaded face.loadFont(NotoSansBold15); // Draw the whole clock - NTP time not available yet renderFace(time_secs); } // ========================================================================= // Loop // ========================================================================= void loop() { char time[20]; static unsigned long targetTime = millis() + 1000; static uint8_t lastMode = PinStatus::HIGH; uint8_t val = PinStatus::HIGH; // Update time periodically if (targetTime < millis()) { // Update next tick time in 100 milliseconds for smooth movement targetTime = millis() + 1000; // All graphics are drawn in sprite to stop flicker renderFace(time_secs); } if ( mode > ModeNormal) { if (!digitalRead(BtnFwd)) { if ( mode == ModeHours ) { time_secs += 60 * 60; // Add one minute } else { time_secs += 60; // Add one minute } // Midnight roll-over if (time_secs >= (60 * 60 * 24)) { time_secs -= (60 * 60 * 24); } renderFace(time_secs); } else if (!digitalRead(BtnBack)) { if ( mode == ModeHours ) { time_secs -= 60 * 60; // Add one minute } else { time_secs -= 60; // Add one minute } // Midnight roll-over if (time_secs < 0 ) { time_secs += (60 * 60 * 24); } renderFace(time_secs); } } val = digitalRead(BtnMode); if (!val && val != lastMode) { mode += 1; if ( mode > 3) { mode = 0; } delay(100); } lastMode = val; } // ========================================================================= // Draw the clock face in the sprite // ========================================================================= static void renderFace(float t) { char time[20]; float h_angle = t * HOUR_ANGLE; float m_angle = t * MINUTE_ANGLE; //float s_angle = t * SECOND_ANGLE; long r = (unsigned long) time_secs; int h = (int)(r/3600); r = r%3600; int m = (int)(r/60); int s = (int)(r % 60); int HandColor = CLOCK_FG; // The face is completely redrawn - this can be done quickly face.fillSprite(TFT_BLACK); // Draw the face circle face.fillSmoothCircle( CLOCK_XY, CLOCK_XY, CLOCK_R, CLOCK_BG ); // Set text datum to middle centre and the colour face.setTextDatum(MC_DATUM); // The background colour will be read during the character rendering face.setTextColor(CLOCK_FG, CLOCK_BG); // Text offset adjustment constexpr uint32_t dialOffset = CLOCK_R - 10; float xp = 0.0, yp = 0.0; // Use float pixel position for smooth AA motion // Draw digits around clock perimeter for (uint32_t h = 1; h <= 12; h++) { getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, dialOffset, h * 360.0 / 12); face.drawNumber(h, xp, 2 + yp); } /* // Add text (could be digital time...) face.setTextColor(LABEL_FG, CLOCK_BG); sprintf(time,"%02d:%02d:%02d",h,m,s); face.drawString(time, CLOCK_XY, CLOCK_XY * 0.75); */ if (mode == ModeMinutes) { HandColor = HILITE_FG; } else { HandColor = CLOCK_FG; } // Draw minute hand getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, M_HAND_LENGTH, m_angle); face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 6.0f, HandColor); // face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 2.0f, CLOCK_BG); if (mode == ModeHours) { HandColor = HILITE_FG; } else { HandColor = CLOCK_FG; } // Draw hour hand getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, H_HAND_LENGTH, h_angle); face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 6.0f, HandColor); //face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 2.0f, CLOCK_BG); // Draw the central pivot circle face.fillSmoothCircle( CLOCK_XY, CLOCK_XY, 4, CLOCK_FG); // Draw second hand //getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, S_HAND_LENGTH, s_angle); //face.drawWedgeLine( CLOCK_XY, CLOCK_XY, xp, yp, 2.5, 1.0, SECCOND_FG); face.pushSprite(0,0, TFT_TRANSPARENT); } // ========================================================================= // Get coordinates of end of a line, pivot at x,y, length r, angle a // ========================================================================= // Coordinates are returned to caller via the xp and yp pointers #define DEG2RAD 0.0174532925 void getCoord(int16_t x, int16_t y, float *xp, float *yp, int16_t r, float a) { float sx1 = cos( (a - 90) * DEG2RAD); float sy1 = sin( (a - 90) * DEG2RAD); *xp = sx1 * r + x; *yp = sy1 * r + y; }