Making a Retro Analog NTP Clock with Unihiker K10 - Arduino IDE Tutorial
This project successfully demonstrates that the Unihiker K10 board, despite being AI-oriented, can effectively utilize standard Arduino libraries for creating traditional electronics projects.
Components and supplies
1
UNIHIKER K10
Tools and machines
1
Soldering Iron Kit, Battery
Apps and platforms
1
arduino IDE
Project description
Code
Code
cpp
....
1#include <WiFi.h> 2#include "time.h" // Built-in for NTP handling 3#include <TFT_eSPI.h> // TFT_eSPI library 4#include <math.h> // For sin/cos calculations 5#include "unihiker_k10.h" 6 7 8UNIHIKER_K10 k10; 9 10uint8_t screen_dir = 2; 11Music music; // For button functionality 12bool showAnalog = true; // To track which display is showing 13bool first_digital = true; // For digital display initialization 14 15// Initialize display 16TFT_eSPI tft = TFT_eSPI(); 17 18// Variables for digital display 19int prev_hour = -1; 20int prev_min = -1; 21int prev_sec = -1; 22 23uint16_t bgColors[] = {TFT_YELLOW, TFT_ORANGE, TFT_MAGENTA, TFT_OLIVE, TFT_WHITE}; 24int currentColorIndex = 0; 25uint16_t currentBgColor = bgColors[0]; 26 27 28#define TFT_GREY 0x5AEB 29#define TFT_LIGHTPINK 0xFDB8 30#define TFT_GOLD 0xFEA0 31#define TFT_LIGHTGREEN 0x9772 32#define TFT_LIGHTSALMON 0xFD0F 33#define TFT_ORANGE 0xFD20 34#define TFT_MAGENTA 0xF81F 35#define TFT_OLIVE 0x7BE0 36 37// WiFi and NTP settings 38const char* ssid = "*******"; 39const char* password = "*******"; 40const char* ntpServer = "pool.ntp.org"; 41long gmtOffset_sec = 3600; // Timezone offset (UTC+1) 42int daylightOffset_sec = 3600; // Daylight Saving Time 43 44// Clock parameters 45const int center_x = 160; // Center of display 46const int center_y = 120; // Center of display 47const int x_radius = 110; // Horizontal radius 48const int y_radius = 90; // Vertical radius 49const int ellipse_offset_x = 3; // Ellipse offset right 50const int ellipse_offset_y = -2; // Ellipse offset up 51const int second_length = 85; 52const int minute_length = 70; 53const int hour_length = 50; 54const int center_size = 5; 55const int tick_size = 3; 56 57// Previous hand positions 58int prev_sx = center_x, prev_sy = center_y; 59int prev_m1x, prev_m1y, prev_m2x, prev_m2y, prev_m3x, prev_m3y; 60int prev_h1x, prev_h1y, prev_h2x, prev_h2y, prev_h3x, prev_h3y; 61 62// Flag for first loop 63bool first_loop = true; 64 65// Function prototypes 66void onButtonAPressed(); 67void onButtonBPressed(); 68void drawDigitalClock(struct tm timeinfo); 69 70// Function to calculate elliptical points with offset 71void getEllipsePoint(float angle, float* x, float* y) { 72 *x = center_x + ellipse_offset_x + x_radius * sin(angle); 73 *y = center_y + ellipse_offset_y - y_radius * cos(angle); 74} 75 76void drawDigitalClock(struct tm timeinfo) { 77 if (first_digital) { 78 tft.fillScreen(currentBgColor); 79 80 // Draw outer frame (5 pixels thick) 81 tft.fillRect(0, 0, 320, 5, TFT_DARKGREY); 82 tft.fillRect(0, 235, 320, 5, TFT_DARKGREY); 83 tft.fillRect(0, 0, 5, 240, TFT_DARKGREY); 84 tft.fillRect(315, 0, 5, 240, TFT_DARKGREY); 85 86 // Draw inner frame (2 pixels thick) 87 tft.drawRoundRect(8, 8, 304, 2, 20, TFT_BLACK); 88 tft.drawRoundRect(8, 230, 304, 2, 20, TFT_BLACK); 89 tft.drawRoundRect(8, 8, 2, 224, 20, TFT_BLACK); 90 tft.drawRoundRect(310, 8, 2, 224, 20, TFT_BLACK); 91 92 first_digital = false; 93 } 94 95 // Set text properties for time 96 tft.setTextSize(4); // Size 4 for time 97 tft.setTextColor(TFT_BLACK, currentBgColor); // Red text on yellow background 98 99 int current_hour = timeinfo.tm_hour; 100 int current_min = timeinfo.tm_min; 101 int current_sec = timeinfo.tm_sec; 102 103 // Position for the time display - centered 104 int x = 40; // Adjusted for better centering 105 int y = 80; // Adjusted vertical position 106 107 // Update hours if changed 108 if (current_hour != prev_hour) { 109 char hourStr[3]; 110 sprintf(hourStr, "%02d", current_hour); 111 tft.fillRect(x, y, 70, 45, currentBgColor); // Adjusted clear area 112 tft.setCursor(x, y); 113 tft.print(hourStr); 114 prev_hour = current_hour; 115 } 116 117 // Always show colon 118 tft.setCursor(x + 60, y); 119 tft.print(":"); 120 121 // Update minutes if changed 122 if (current_min != prev_min) { 123 char minStr[3]; 124 sprintf(minStr, "%02d", current_min); 125 tft.fillRect(x + 95, y, 70, 45, currentBgColor); // Adjusted clear area 126 tft.setCursor(x + 95, y); 127 tft.print(minStr); 128 prev_min = current_min; 129 } 130 131 // Always show colon 132 tft.setCursor(x + 155, y); 133 tft.print(":"); 134 135 // Update seconds if changed 136 if (current_sec != prev_sec) { 137 char secStr[3]; 138 sprintf(secStr, "%02d", current_sec); 139 tft.fillRect(x + 190, y, 70, 45, currentBgColor); // Adjusted clear area 140 tft.setCursor(x + 190, y); 141 tft.print(secStr); 142 prev_sec = current_sec; 143 } 144 145 // Update date 146 tft.setTextSize(3); // Size 3 for date 147 char dateStr[11]; 148 strftime(dateStr, sizeof(dateStr), "%Y-%m-%d", &timeinfo); 149 150 // Center the date 151 tft.fillRect(60, 150, 200, 30, currentBgColor); // Clear previous date 152 tft.setCursor(65, 150); // Adjusted position for better centering 153 tft.print(dateStr); 154} 155void onButtonAPressed() { 156 showAnalog = !showAnalog; 157 if (showAnalog) { 158 // Instead of calling setup(), just redraw the analog clock 159 tft.fillScreen(currentBgColor); // Use current background color 160 161 // Draw frames 162 tft.fillRect(0, 0, 320, 5, TFT_DARKGREY); 163 tft.fillRect(0, 235, 320, 5, TFT_DARKGREY); 164 tft.fillRect(0, 0, 5, 240, TFT_DARKGREY); 165 tft.fillRect(315, 0, 5, 240, TFT_DARKGREY); 166 167 tft.drawRoundRect(8, 8, 304, 2, 20, TFT_BLACK); 168 tft.drawRoundRect(8, 230, 304, 2, 20, TFT_BLACK); 169 tft.drawRoundRect(8, 8, 2, 224, 20, TFT_BLACK); 170 tft.drawRoundRect(310, 8, 2, 224, 20, TFT_BLACK); 171 172 // Draw clock face 173 tft.setTextSize(2); 174 for (int i = 0; i < 60; i++) { 175 float ang_rad = i * 6.0 * PI / 180.0; 176 float tx, ty; 177 getEllipsePoint(ang_rad, &tx, &ty); 178 179 if (i % 5 != 0) { 180 tft.fillCircle(tx, ty, 1, TFT_DARKGREY); 181 } 182 } 183 184 for (int i = 1; i <= 12; i++) { 185 float ang_rad = i * 30.0 * PI / 180.0; 186 float tx, ty; 187 getEllipsePoint(ang_rad, &tx, &ty); 188 189 if (i % 3 == 0) { 190 char num[3]; 191 sprintf(num, "%d", i); 192 int text_offset_x, text_offset_y; 193 194 if (i == 12) { 195 text_offset_x = -12; 196 text_offset_y = -6; 197 } else if (i == 3) { 198 text_offset_x = -4; 199 text_offset_y = -6; 200 } else if (i == 6) { 201 text_offset_x = -6; 202 text_offset_y = -6; 203 } else if (i == 9) { 204 text_offset_x = -6; 205 text_offset_y = -6; 206 } 207 208 tft.setCursor(tx + text_offset_x, ty + text_offset_y); 209 tft.print(num); 210 } else { 211 tft.fillCircle(tx, ty, tick_size, TFT_BLACK); 212 } 213 } 214 tft.fillCircle(center_x, center_y, center_size, TFT_BLACK); 215 first_loop = true; 216 } else { 217 // Reset digital clock variables 218 prev_hour = -1; 219 prev_min = -1; 220 prev_sec = -1; 221 first_digital = true; 222 } 223} 224 225void onButtonBPressed() { 226 // Change background color 227 currentColorIndex = (currentColorIndex + 1) % 5; 228 currentBgColor = bgColors[currentColorIndex]; 229 230 // Play three-tone ascending sequence 231 music.playTone(262, 200); // C4 (middle C) 232 delay(250); // Small delay between tones 233 music.playTone(330, 200); // E4 234 delay(250); 235 music.playTone(392, 200); // G4 236 237 // Redraw screen with new background color 238 if (showAnalog) { 239 onButtonAPressed(); // Redraw analog clock with new color 240 } else { 241 first_digital = true; // Force complete redraw of digital display 242 } 243} 244 245void setup() { 246 Serial.begin(115200); 247 248 k10.begin(); 249 k10.initScreen(screen_dir); 250 251 // Add button callbacks 252 k10.buttonA->setPressedCallback(onButtonAPressed); 253 k10.buttonB->setPressedCallback(onButtonBPressed); 254 255 // Initialize display 256 tft.init(); 257 tft.setRotation(3); // Landscape mode 258 tft.fillScreen(currentBgColor); 259 tft.setTextColor(TFT_BLACK, currentBgColor); 260 261 // Connect to WiFi 262 tft.setCursor(30, 100); 263 tft.setTextSize(3); 264 tft.print("Connecting..."); 265 WiFi.begin(ssid, password); 266 while (WiFi.status() != WL_CONNECTED) { 267 delay(500); 268 tft.fillScreen(currentBgColor); 269 Serial.print("."); 270 } 271 Serial.println("WiFi connected"); 272 tft.setCursor(55, 100); 273 tft.setTextSize(3); 274 tft.print("Connected!"); 275 delay(2000); 276 tft.fillScreen(currentBgColor); 277 278 // Draw outer frame (5 pixels thick) 279 tft.fillRect(0, 0, 320, 5, TFT_DARKGREY); 280 tft.fillRect(0, 235, 320, 5, TFT_DARKGREY); 281 tft.fillRect(0, 0, 5, 240, TFT_DARKGREY); 282 tft.fillRect(315, 0, 5, 240, TFT_DARKGREY); 283 284 // Draw inner frame (2 pixels thick) 285 tft.drawRoundRect(8, 8, 304, 2, 20, TFT_BLACK); 286 tft.drawRoundRect(8, 230, 304, 2, 20, TFT_BLACK); 287 tft.drawRoundRect(8, 8, 2, 224, 20, TFT_BLACK); 288 tft.drawRoundRect(310, 8, 2, 224, 20, TFT_BLACK); 289 290 // Initialize NTP 291 configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); 292 293 // Draw static clock face 294 tft.setTextSize(2); 295 296 // First draw all second markers (60 small dots) 297 for (int i = 0; i < 60; i++) { 298 float ang_rad = i * 6.0 * PI / 180.0; 299 float tx, ty; 300 getEllipsePoint(ang_rad, &tx, &ty); 301 302 if (i % 5 != 0) { 303 tft.fillCircle(tx, ty, 1, TFT_DARKGREY); 304 } 305 } 306 307 // Then draw hour markers over the second markers 308 for (int i = 1; i <= 12; i++) { 309 float ang_rad = i * 30.0 * PI / 180.0; 310 float tx, ty; 311 getEllipsePoint(ang_rad, &tx, &ty); 312 313 if (i % 3 == 0) { 314 char num[3]; 315 sprintf(num, "%d", i); 316 int text_offset_x, text_offset_y; 317 318 if (i == 12) { 319 text_offset_x = -12; 320 text_offset_y = -6; 321 } else if (i == 3) { 322 text_offset_x = -4; 323 text_offset_y = -6; 324 } else if (i == 6) { 325 text_offset_x = -6; 326 text_offset_y = -6; 327 } else if (i == 9) { 328 text_offset_x = -6; 329 text_offset_y = -6; 330 } 331 332 tft.setCursor(tx + text_offset_x, ty + text_offset_y); 333 tft.print(num); 334 } else { 335 tft.fillCircle(tx, ty, tick_size, TFT_BLACK); 336 } 337 } 338 tft.fillCircle(center_x, center_y, center_size, TFT_BLACK); 339} 340 341void loop() { 342 struct tm timeinfo; 343 if (getLocalTime(&timeinfo)) { 344 if (showAnalog) { 345 int sec = timeinfo.tm_sec; 346 int min = timeinfo.tm_min; 347 int hour = timeinfo.tm_hour; 348 349 // Clear previous hands if not first loop 350 if (!first_loop) { 351 tft.drawTriangle(prev_h1x, prev_h1y, prev_h2x, prev_h2y, prev_h3x, prev_h3y, currentBgColor); 352 tft.drawTriangle(prev_m1x, prev_m1y, prev_m2x, prev_m2y, prev_m3x, prev_m3y, currentBgColor); 353 tft.drawLine(center_x, center_y, prev_sx, prev_sy, currentBgColor); 354 } 355 356 // Store current text settings 357 uint8_t current_size = tft.textsize; 358 uint8_t current_font = tft.textfont; 359 360 // Display date with its own settings 361 tft.setTextSize(1); 362 tft.setTextFont(2); 363 char dateStr[11]; 364 strftime(dateStr, sizeof(dateStr), "%Y-%m-%d", &timeinfo); 365 tft.setCursor(130, 150); 366 tft.print(dateStr); 367 368 // Restore original text settings 369 tft.setTextSize(current_size); 370 tft.setTextFont(current_font); 371 372 // Calculate new hour hand position 373 float h_deg = (hour % 12) * 30.0 + min * 0.5 + sec * (0.5 / 60.0); 374 float h_rad = h_deg * PI / 180.0; 375 int hx = center_x + hour_length * sin(h_rad); 376 int hy = center_y - hour_length * cos(h_rad); 377 float hdx = sin(h_rad); 378 float hdy = -cos(h_rad); 379 float hpx = -hdy; 380 float hpy = hdx; 381 float base_half = 5.0; 382 int p1x = center_x + base_half * hpx; 383 int p1y = center_y + base_half * hpy; 384 int p2x = center_x - base_half * hpx; 385 int p2y = center_y - base_half * hpy; 386 int p3x = hx; 387 int p3y = hy; 388 389 // Draw hour hand with cyan fill and black outline 390 tft.fillTriangle(p1x, p1y, p2x, p2y, p3x, p3y, TFT_CYAN); 391 tft.drawTriangle(p1x, p1y, p2x, p2y, p3x, p3y, TFT_BLACK); 392 393 // Calculate new minute hand position 394 float m_deg = min * 6.0 + sec * 0.1; 395 float m_rad = m_deg * PI / 180.0; 396 int mx = center_x + minute_length * sin(m_rad); 397 int my = center_y - minute_length * cos(m_rad); 398 float dx = sin(m_rad); 399 float dy = -cos(m_rad); 400 float px = -dy; 401 float py = dx; 402 float m_base_half = 5.0; 403 int m1x = center_x + m_base_half * px; 404 int m1y = center_y + m_base_half * py; 405 int m2x = center_x - m_base_half * px; 406 int m2y = center_y - m_base_half * py; 407 int m3x = mx; 408 int m3y = my; 409 410 // Draw minute hand (outlined) 411 tft.drawTriangle(m1x, m1y, m2x, m2y, m3x, m3y, TFT_BLACK); 412 413 // Calculate new second hand position 414 float s_deg = sec * 6.0; 415 float s_rad = s_deg * PI / 180.0; 416 int sx = center_x + second_length * sin(s_rad); 417 int sy = center_y - second_length * cos(s_rad); 418 419 // Draw second hand 420 tft.drawLine(center_x, center_y, sx, sy, TFT_RED); 421 422 // Draw center dot 423 tft.fillCircle(center_x, center_y, center_size, TFT_BLACK); 424 425 // Update previous positions 426 prev_h1x = p1x; prev_h1y = p1y; 427 prev_h2x = p2x; prev_h2y = p2y; 428 prev_h3x = p3x; prev_h3y = p3y; 429 prev_m1x = m1x; prev_m1y = m1y; 430 prev_m2x = m2x; prev_m2y = m2y; 431 prev_m3x = m3x; prev_m3y = m3y; 432 prev_sx = sx; prev_sy = sy; 433 434 first_loop = false; 435 } else { 436 drawDigitalClock(timeinfo); 437 } 438 } else { 439 Serial.println("Failed to obtain time"); 440 } 441 delay(1000); 442}
Comments
Only logged in users can leave comments