// Sketch to draw an analogue clock on the screen // This uses anti-aliased drawing functions that are built into TFT_eSPI and this sketch // See AA_functions tab. These functions are the basis for future library updates. // The sub-pixel resolution is achieved using floating point coordinates // Anti-aliased lines can be drawn with sub-pixel resolutuion and permit lines to be // drawn with less obvious pixel resolution imposed jaggedness. // TODO - limit NotoSansBold15 font characters to those needed? // Based on a sketch by DavyLandman: // https://github.com/Bodmer/TFT_eSPI/issues/905 #define WIFI_SSID "Your_SSID" #define WIFI_PASSWORD "Your_password" #include #include // Master copy here: https://github.com/Bodmer/TFT_eSPI #include #define ALPHA_GAIN 255.0f // Should be 255.0 for no gain #include "NotoSansBold15.h" TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h TFT_eSprite face = TFT_eSprite(&tft); #define CLOCK_FG TFT_SKYBLUE #define CLOCK_BG TFT_NAVY #define SECCOND_FG TFT_RED #define LABEL_FG TFT_GOLD #define CLOCK_R 127.0f / 2.0f // Clock face radius (float type), ideally diameter is odd so there is a central pixel #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 // Add 1 so sprite has a central pixel #define FACE_W CLOCK_R * 2 + 1 #define FACE_H CLOCK_R * 2 + 1 // Time h:m:s uint8_t h=0, m=0, s=0; float time_secs = h * 3600 + m *60 + s; // Load header after time_secs global variable has been created so it is in scope #include "NTP_Time.h" // Attached to this sketch, see that tab for library needs // Time for next 1 second tick uint32_t targetTime = 0; // ========================================================================= // Setup // ========================================================================= void setup() { Serial.begin(115200); Serial.println("Booting..."); // 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_BLACK); // Create the clock face sprite // face.setColorDepth(8); face.createSprite(FACE_W, FACE_H); // Only 1 font used in the sprite, so can remain loaded face.loadFont(NotoSansBold15); // Temporary to test drawSpot int colour = 0; uint32_t dt = millis(); for (int32_t i=0; i<1000; i++) { int x = random(tft.width()); int y = random(tft.height()); colour = random(0xFFFF); float w = random(100)/20.0; tft.drawSpot(x, y, w, colour); yield(); } Serial.println(millis()-dt); // Draw the whole clock renderFace(time_secs); targetTime = millis() + 1000; } // ========================================================================= // Loop // ========================================================================= void loop() { // Update time periodically if (targetTime < millis()) { // Update next tick time ever 100 milliseconds targetTime = millis()+100; // Increment time by 100 milliseconds time_secs+= 0.1; if (time_secs >= (60*60*24)) time_secs = 0; // All graphics are drawn in sprite to stop flicker renderFace(time_secs); // Request time from NTP server and synchronise the local clock syncTime(); } } // ========================================================================= // Draw the clock face in the sprite // ========================================================================= static void renderFace(float t) { float h_angle = t * HOUR_ANGLE; float m_angle = t * MINUTE_ANGLE; float s_angle = t * SECOND_ANGLE; // The face is completely redrawn - this can be done quickly face.fillSprite(TFT_BLACK); // Draw the face circle //face.drawSpot(CLOCK_R, CLOCK_R, CLOCK_R, CLOCK_BG); fillCircleAA(CLOCK_R, CLOCK_R, CLOCK_R, CLOCK_BG); // Set text datum to middle centre and the colour face.setTextDatum(MC_DATUM); // In a sprite the background colour for the anti-aliasing does not need to be specified // the sprite pixel colour will be read during the chanracter rendering face.setTextColor(CLOCK_FG); // 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_R, CLOCK_R, &xp, &yp, dialOffset, h * 360.0 / 12); face.drawNumber(h, xp, 2 + yp); } // Add text (could be digital time) face.setTextColor(LABEL_FG); face.drawString("TFT_eSPI", CLOCK_R, CLOCK_R * 0.75); // Draw hour hand getCoord(CLOCK_R, CLOCK_R, &xp, &yp, H_HAND_LENGTH, h_angle); face.drawWideLine(CLOCK_R, CLOCK_R, xp, yp, 6.0f, CLOCK_FG, TFT_BLACK); face.drawWideLine(CLOCK_R, CLOCK_R, xp, yp, 2.0f, CLOCK_BG); // Draw minute hand getCoord(CLOCK_R, CLOCK_R, &xp, &yp, M_HAND_LENGTH, m_angle); face.drawWideLine(CLOCK_R, CLOCK_R, xp, yp, 6.0f, CLOCK_FG, TFT_BLACK); face.drawWideLine(CLOCK_R, CLOCK_R, xp, yp, 2.0f, CLOCK_BG); // Draw the central pivot circle face.drawSpot(CLOCK_R, CLOCK_R, 4, CLOCK_FG); //tft.drawLine(CLOCK_R, CLOCK_R+128.0, xp, yp+128.0, 2.2, CLOCK_FG, TFT_BLACK); // Draw Second hand getCoord(CLOCK_R, CLOCK_R, &xp, &yp, S_HAND_LENGTH, s_angle); face.drawWedgeLine(CLOCK_R, CLOCK_R, 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; }