From Unboxing to Coding - Radar Clock on Elecrow’s 2.1 HMI Display
This module from Elecrow has endless possibilities for making DIY projects in a relatively simple way, without need for soldering or other hardware work.
Devices & Components
1
Elecrow CrowPanel 2.1inch-HMI ESP32 Rotary Display 480*480
Hardware & Tools
1
Soldering Iron Kit, Battery
Software & Tools
Arduino IDE
Project description
Code
Code
cpp
...
1/* Arduino Radar Clock on CrowPanel 2.1inch-HMI ESP32 Rotary Display 480*480 by mircemk, October 2025*/ 2 3#include <Arduino.h> 4#include <Arduino_GFX_Library.h> 5#include <time.h> 6#include <WiFi.h> 7 8// WiFi credentials 9const char* ssid = "*****"; // Replace with your WiFi SSID 10const char* password = "*****"; // Replace with your WiFi password 11 12// NTP Server settings 13const char* ntpServer = "pool.ntp.org"; 14const long gmtOffset_sec = 3600; // Change according to your timezone (3600 = GMT+1) 15const int daylightOffset_sec = 3600; 16 17/* ======== DISPLAY CONFIGURATION ======== */ 18#define TYPE_SEL 7 // ST7701 init table 19#define PCLK_NEG 1 // 1 = falling edge 20#define TIMING_SET 1 // 1 = wider safe porches 21 22/* --- Backlight PIN --- */ 23#define BL_PIN 6 24 25/* --- SPI for ST7701 init --- */ 26#define PANEL_CS 16 27#define PANEL_SCK 2 28#define PANEL_SDA 1 29 30/* --- Rotary Encoder Pins --- */ 31#define ENCODER_A_PIN 42 32#define ENCODER_B_PIN 44 33 34/* --- Display Timing --- */ 35#if TIMING_SET == 0 36 static const int HFP=20, HPW=10, HBP=10; 37 static const int VFP=8, VPW=10, VBP=10; 38#else 39 static const int HFP=40, HPW=8, HBP=40; 40 static const int VFP=20, VPW=8, VBP=20; 41#endif 42 43/* ======== RADAR CONFIGURATION ======== */ 44#define DISPLAY_WIDTH 480 45#define DISPLAY_HEIGHT 480 46#define CENTER_X 240 47#define CENTER_Y 240 48#define RADAR_RADIUS 200 49#define FRAME_START 200 50#define FRAME_WIDTH 5 51#define BORDER_WIDTH 5 52#define SWEEP_SPEED 3 53 54/* ======== COLOR SCHEMES ======== */ 55// Color definitions for different schemes (RGB565) 56typedef struct { 57 uint16_t green_color; 58 uint16_t dim_green_color; 59 uint16_t sweep_color; 60 uint16_t grid_color; 61 uint16_t frame_color; 62 uint16_t text_color; 63 uint16_t trail_color; 64 uint16_t black; 65} ColorScheme; 66 67// Scheme 1: Classic Green (original) 68const ColorScheme SCHEME_GREEN = { 69 0x07E0, // green_color 70 0x03A0, // dim_green_color 71 0x07FF, // sweep_color (cyan) 72 0x03E0, // grid_color 73 0x07E0, // frame_color 74 0x07E0, // text_color 75 0x0320, // trail_color 76 0x0000 // black 77}; 78 79// Scheme 2: Red 80const ColorScheme SCHEME_RED = { 81 0xF800, // green_color -> red 82 0x7800, // dim_green_color -> dark red 83 0xF810, // sweep_color -> bright red 84 0xF800, // grid_color -> red 85 0xF800, // frame_color -> red 86 0xF800, // text_color -> red 87 0x7800, // trail_color -> dark red 88 0x0000 // black 89}; 90 91// Scheme 3: Blue (like second vector link - digital radar) 92const ColorScheme SCHEME_BLUE = { 93 0x001F, // green_color -> blue 94 0x0010, // dim_green_color -> dark blue 95 0x041F, // sweep_color -> bright blue 96 0x001F, // grid_color -> blue 97 0x001F, // frame_color -> blue 98 0x001F, // text_color -> blue 99 0x0010, // trail_color -> dark blue 100 0x0000 // black 101}; 102 103// Scheme 4: Yellow-Orange 104const ColorScheme SCHEME_YELLOW_ORANGE = { 105 0xFDA0, // green_color -> yellow-orange 106 0xFB00, // dim_green_color -> dark yellow-orange 107 0xFEA0, // sweep_color -> bright yellow-orange 108 0xFDA0, // grid_color -> yellow-orange 109 0xFDA0, // frame_color -> yellow-orange 110 0xFDA0, // text_color -> yellow-orange 111 0xFB00, // trail_color -> dark yellow-orange 112 0x0000 // black 113}; 114 115// Scheme 5: White 116const ColorScheme SCHEME_WHITE = { 117 0xFFFF, // green_color -> white 118 0xDEFB, // dim_green_color -> light gray 119 0xFFFF, // sweep_color -> white 120 0xFFFF, // grid_color -> white 121 0xFFFF, // frame_color -> white 122 0xFFFF, // text_color -> white 123 0xBDF7, // trail_color -> medium gray 124 0x0000 // black 125}; 126 127const ColorScheme* colorSchemes[] = { 128 &SCHEME_GREEN, 129 &SCHEME_RED, 130 &SCHEME_BLUE, 131 &SCHEME_YELLOW_ORANGE, 132 &SCHEME_WHITE 133}; 134 135#define NUM_COLOR_SCHEMES 5 136 137/* ======== GLOBAL VARIABLES ======== */ 138Arduino_DataBus *panelBus = nullptr; 139Arduino_ESP32RGBPanel *rgbpanel = nullptr; 140Arduino_RGB_Display *gfx = nullptr; 141 142static float current_angle = 0; 143static uint16_t *frameBuffer = nullptr; 144static uint16_t *staticFrameBuffer = nullptr; 145 146// Color scheme management 147int currentColorScheme = 0; 148bool colorSchemeChanged = true; 149 150// Rotary encoder variables 151volatile int encoderPos = 0; 152int lastEncoderPos = 0; 153portMUX_TYPE encoderMux = portMUX_INITIALIZER_UNLOCKED; 154 155// Startup sequence state 156enum StartupState { 157 SHOW_TITLE, 158 SHOW_CONNECTING, 159 SHOW_CLOCK 160}; 161StartupState currentStartupState = SHOW_TITLE; 162unsigned long startupStartTime = 0; 163 164// WiFi connection state 165bool wifiConnecting = false; 166bool wifiConnected = false; 167unsigned long lastWiFiCheck = 0; 168const unsigned long WIFI_CHECK_INTERVAL = 1000; 169 170// Display stability 171bool displayStable = false; 172 173// Encoder interrupt service routine 174void IRAM_ATTR encoderISR() { 175 portENTER_CRITICAL_ISR(&encoderMux); 176 177 static uint8_t old_AB = 0; 178 // Grey code 179 // 0b00: 0 180 // 0b01: 1 181 // 0b11: 2 182 // 0b10: 3 183 const int8_t enc_states[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}; 184 185 old_AB <<= 2; // Remember previous state 186 old_AB |= (digitalRead(ENCODER_A_PIN) ? (1 << 1) : 0) | (digitalRead(ENCODER_B_PIN) ? (1 << 0) : 0); 187 188 encoderPos += enc_states[(old_AB & 0x0f)]; 189 190 portEXIT_CRITICAL_ISR(&encoderMux); 191} 192 193void handleEncoder() { 194 portENTER_CRITICAL(&encoderMux); 195 int currentPos = encoderPos; 196 portEXIT_CRITICAL(&encoderMux); 197 198 if (currentPos != lastEncoderPos && currentStartupState == SHOW_CLOCK) { 199 if (currentPos > lastEncoderPos) { 200 // Clockwise rotation - next color scheme 201 currentColorScheme = (currentColorScheme + 1) % NUM_COLOR_SCHEMES; 202 } else { 203 // Counter-clockwise rotation - previous color scheme 204 currentColorScheme = (currentColorScheme - 1 + NUM_COLOR_SCHEMES) % NUM_COLOR_SCHEMES; 205 } 206 207 colorSchemeChanged = true; 208 Serial.printf("Color scheme changed to: %d\n", currentColorScheme + 1); 209 210 lastEncoderPos = currentPos; 211 } 212} 213 214/* ======== STARTUP SCREEN FUNCTIONS ======== */ 215void showTitleScreen() { 216 // Use direct display drawing for maximum stability 217 gfx->fillScreen(SCHEME_GREEN.black); 218 219 // Draw "RADAR CLOCK" - larger text 220 gfx->setTextColor(SCHEME_GREEN.text_color); 221 gfx->setTextSize(4); 222 223 // Calculate position for "RADAR CLOCK" 224 String radarText = "RADAR CLOCK"; 225 int16_t x1, y1; 226 uint16_t w, h; 227 gfx->getTextBounds(radarText, 0, 0, &x1, &y1, &w, &h); 228 int radarX = (DISPLAY_WIDTH - w) / 2; 229 int radarY = CENTER_Y - 60; 230 231 gfx->setCursor(radarX, radarY); 232 gfx->print(radarText); 233 234 // Draw "by" - smaller text 235 gfx->setTextSize(2); 236 String byText = "by"; 237 gfx->getTextBounds(byText, 0, 0, &x1, &y1, &w, &h); 238 int byX = (DISPLAY_WIDTH - w) / 2; 239 int byY = CENTER_Y; 240 241 gfx->setCursor(byX, byY); 242 gfx->print(byText); 243 244 // Draw "Mircemk" - medium text 245 gfx->setTextSize(3); 246 String nameText = "Mircemk"; 247 gfx->getTextBounds(nameText, 0, 0, &x1, &y1, &w, &h); 248 int nameX = (DISPLAY_WIDTH - w) / 2; 249 int nameY = CENTER_Y + 40; 250 251 gfx->setCursor(nameX, nameY); 252 gfx->print(nameText); 253} 254 255void showConnectingScreen() { 256 // Use direct display drawing for stability during WiFi connection 257 gfx->fillScreen(SCHEME_GREEN.black); 258 259 // Draw "connecting..." text 260 gfx->setTextColor(SCHEME_GREEN.text_color); 261 gfx->setTextSize(3); 262 263 String connectingText = "connecting..."; 264 int16_t x1, y1; 265 uint16_t w, h; 266 gfx->getTextBounds(connectingText, 0, 0, &x1, &y1, &w, &h); 267 int textX = (DISPLAY_WIDTH - w) / 2; 268 int textY = CENTER_Y; 269 270 gfx->setCursor(textX, textY); 271 gfx->print(connectingText); 272 273 // Draw the display immediately using the frame buffer method for stability 274 if (frameBuffer) { 275 gfx->draw16bitRGBBitmap(0, 0, frameBuffer, DISPLAY_WIDTH, DISPLAY_HEIGHT); 276 } 277} 278 279void updateConnectingAnimation() { 280 static unsigned long lastDotUpdate = 0; 281 static bool dotVisible = false; 282 283 unsigned long currentTime = millis(); 284 if (currentTime - lastDotUpdate >= 500) { // Blink every 500ms 285 lastDotUpdate = currentTime; 286 dotVisible = !dotVisible; 287 288 // Update dot without redrawing entire screen 289 if (dotVisible) { 290 gfx->fillRect(DISPLAY_WIDTH - 30, CENTER_Y + 40, 10, 10, SCHEME_GREEN.text_color); 291 } else { 292 gfx->fillRect(DISPLAY_WIDTH - 30, CENTER_Y + 40, 10, 10, SCHEME_GREEN.black); 293 } 294 295 // Update only the changed area 296 gfx->draw16bitRGBBitmap(DISPLAY_WIDTH - 30, CENTER_Y + 40, 297 &frameBuffer[CENTER_Y * DISPLAY_WIDTH + (DISPLAY_WIDTH - 30)], 298 10, 10); 299 } 300} 301 302bool connectToWiFi() { 303 Serial.print("Connecting to WiFi"); 304 WiFi.begin(ssid, password); 305 306 unsigned long startTime = millis(); 307 const unsigned long timeout = 30000; // 30 seconds timeout 308 309 while (WiFi.status() != WL_CONNECTED && millis() - startTime < timeout) { 310 delay(500); 311 Serial.print("."); 312 313 // Update connecting animation 314 updateConnectingAnimation(); 315 } 316 317 Serial.println(); 318 319 if (WiFi.status() == WL_CONNECTED) { 320 Serial.println("WiFi connected!"); 321 Serial.print("IP address: "); 322 Serial.println(WiFi.localIP()); 323 return true; 324 } else { 325 Serial.println("WiFi connection failed!"); 326 return false; 327 } 328} 329 330void updateStartupSequence() { 331 unsigned long currentTime = millis(); 332 unsigned long elapsedTime = currentTime - startupStartTime; 333 334 switch(currentStartupState) { 335 case SHOW_TITLE: 336 if (elapsedTime >= 2000) { // Show title for 2 seconds 337 currentStartupState = SHOW_CONNECTING; 338 startupStartTime = currentTime; 339 showConnectingScreen(); 340 Serial.println("Showing connecting screen..."); 341 342 // Start WiFi connection 343 wifiConnecting = true; 344 WiFi.begin(ssid, password); 345 } 346 break; 347 348 case SHOW_CONNECTING: 349 // Update connecting animation 350 updateConnectingAnimation(); 351 352 // Check WiFi status periodically 353 if (currentTime - lastWiFiCheck >= WIFI_CHECK_INTERVAL) { 354 lastWiFiCheck = currentTime; 355 356 if (WiFi.status() == WL_CONNECTED) { 357 wifiConnecting = false; 358 wifiConnected = true; 359 currentStartupState = SHOW_CLOCK; 360 startupStartTime = currentTime; 361 Serial.println("WiFi connected! Showing clock..."); 362 363 // Initialize the clock display 364 initClockDisplay(); 365 displayStable = true; 366 } 367 } 368 break; 369 370 case SHOW_CLOCK: 371 // Clock is now running in main loop 372 break; 373 } 374} 375 376/* ======== DRAWING FUNCTIONS ======== */ 377void draw_line_to_buffer(uint16_t *buffer, int x0, int y0, int x1, int y1, uint16_t color) { 378 int dx = abs(x1 - x0); 379 int dy = abs(y1 - y0); 380 int sx = (x0 < x1) ? 1 : -1; 381 int sy = (y0 < y1) ? 1 : -1; 382 int err = dx - dy; 383 384 while (true) { 385 if (x0 >= 0 && x0 < DISPLAY_WIDTH && y0 >= 0 && y0 < DISPLAY_HEIGHT) { 386 buffer[y0 * DISPLAY_WIDTH + x0] = color; 387 } 388 389 if (x0 == x1 && y0 == y1) break; 390 391 int e2 = 2 * err; 392 if (e2 > -dy) { 393 err -= dy; 394 x0 += sx; 395 } 396 if (e2 < dx) { 397 err += dx; 398 y0 += sy; 399 } 400 } 401} 402 403void draw_rectangle_to_buffer(uint16_t *buffer, int x1, int y1, int x2, int y2, uint16_t color) { 404 for (int y = y1; y <= y2; y++) { 405 for (int x = x1; x <= x2; x++) { 406 if (x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT) { 407 buffer[y * DISPLAY_WIDTH + x] = color; 408 } 409 } 410 } 411} 412 413void draw_digit_to_buffer(uint16_t *buffer, int x, int y, int digit, uint16_t color) { 414 // Increased sizes (3x original) 415 int width = 18; // Was 6 416 int height = 24; // Was 8 417 418 // Clear background for larger digit 419 draw_rectangle_to_buffer(buffer, x-9, y-12, x+9, y+12, colorSchemes[currentColorScheme]->black); 420 421 switch(digit) { 422 case 0: 423 draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top 424 draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom 425 draw_line_to_buffer(buffer, x-6, y-9, x-6, y+9, color); // left 426 draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right 427 break; 428 case 1: 429 draw_line_to_buffer(buffer, x, y-9, x, y+9, color); // vertical 430 draw_line_to_buffer(buffer, x-3, y-9, x, y-9, color); // top 431 break; 432 case 2: 433 draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top 434 draw_line_to_buffer(buffer, x+6, y-9, x+6, y, color); // right top 435 draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle 436 draw_line_to_buffer(buffer, x-6, y, x-6, y+9, color); // left bottom 437 draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom 438 break; 439 case 3: 440 draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top 441 draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right 442 draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle 443 draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom 444 break; 445 case 4: 446 draw_line_to_buffer(buffer, x-6, y-9, x-6, y, color); // left top 447 draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle 448 draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right 449 break; 450 case 5: 451 draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top 452 draw_line_to_buffer(buffer, x-6, y-9, x-6, y, color); // left top 453 draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle 454 draw_line_to_buffer(buffer, x+6, y, x+6, y+9, color); // right bottom 455 draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom 456 break; 457 case 6: 458 draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top 459 draw_line_to_buffer(buffer, x-6, y-9, x-6, y+9, color); // left 460 draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle 461 draw_line_to_buffer(buffer, x+6, y, x+6, y+9, color); // right bottom 462 draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom 463 break; 464 case 7: 465 draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top 466 draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right 467 break; 468 case 8: 469 draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top 470 draw_line_to_buffer(buffer, x-6, y-9, x-6, y+9, color); // left 471 draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right 472 draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle 473 draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom 474 break; 475 case 9: 476 draw_line_to_buffer(buffer, x-6, y-9, x+6, y-9, color); // top 477 draw_line_to_buffer(buffer, x-6, y-9, x-6, y, color); // left top 478 draw_line_to_buffer(buffer, x+6, y-9, x+6, y+9, color); // right 479 draw_line_to_buffer(buffer, x-6, y, x+6, y, color); // middle 480 draw_line_to_buffer(buffer, x-6, y+9, x+6, y+9, color); // bottom 481 break; 482 } 483} 484 485void draw_text_to_buffer(uint16_t *buffer, int x, int y, const char* text, uint16_t color) { 486 int char_width = 24; // Increased from 8 to 24 for larger spacing 487 int pos_x = x; 488 489 while (*text) { 490 char c = *text++; 491 492 if (c >= '0' && c <= '9') { 493 draw_digit_to_buffer(buffer, pos_x, y, c - '0', color); 494 } 495 else if (c == ':') { 496 // Draw larger colon 497 buffer[(y - 6) * DISPLAY_WIDTH + pos_x] = color; 498 buffer[(y - 5) * DISPLAY_WIDTH + pos_x] = color; 499 buffer[(y + 5) * DISPLAY_WIDTH + pos_x] = color; 500 buffer[(y + 6) * DISPLAY_WIDTH + pos_x] = color; 501 } 502 else if (c == '/') { 503 // Draw larger slash 504 for (int i = -9; i <= 9; i++) { 505 int px = pos_x + i; 506 int py = y - i; 507 if (px >= 0 && px < DISPLAY_WIDTH && py >= 0 && py < DISPLAY_HEIGHT) { 508 buffer[py * DISPLAY_WIDTH + px] = color; 509 } 510 } 511 } 512 pos_x += char_width; 513 } 514} 515 516void update_time_display() { 517 struct tm timeinfo; 518 if(!getLocalTime(&timeinfo)) { 519 Serial.println("Failed to obtain time"); 520 return; 521 } 522 523 // Calculate positions 524 // Font height is 24, so three heights = 72 pixels 525 int time_y = CENTER_Y - 72; // Move up by three font heights 526 int date_y = CENTER_Y + 72; // Move down by three font heights 527 int time_x = CENTER_X - 90; // Keep the same horizontal position 528 int date_x = CENTER_X - 105; // Keep the same horizontal position 529 530 // Clear previous time area - precise clearing 531 // Height of clearing = font height (24) + 2 pixels margin 532 // Width of clearing = 8 digits * 24 pixels width + 4 pixels margin 533 for (int y = time_y - 13; y < time_y + 13; y++) { 534 for (int x = time_x - 2; x < time_x + (8 * 24) + 2; x++) { 535 if (x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT) { 536 frameBuffer[y * DISPLAY_WIDTH + x] = colorSchemes[currentColorScheme]->black; 537 } 538 } 539 } 540 541 // Format time string 542 char timeStr[9]; 543 sprintf(timeStr, "%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); 544 545 // Draw time 546 draw_text_to_buffer(frameBuffer, time_x, time_y, timeStr, colorSchemes[currentColorScheme]->text_color); 547 548 // Clear previous date area - precise clearing 549 // Height of clearing = font height (24) + 2 pixels margin 550 // Width of clearing = 10 digits * 24 pixels width + 4 pixels margin 551 for (int y = date_y - 13; y < date_y + 13; y++) { 552 for (int x = date_x - 2; x < date_x + (10 * 24) + 2; x++) { 553 if (x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT) { 554 frameBuffer[y * DISPLAY_WIDTH + x] = colorSchemes[currentColorScheme]->black; 555 } 556 } 557 } 558 559 // Format date string 560 char dateStr[11]; 561 sprintf(dateStr, "%02d/%02d/%04d", timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year + 1900); 562 563 // Draw date 564 draw_text_to_buffer(frameBuffer, date_x, date_y, dateStr, colorSchemes[currentColorScheme]->text_color); 565} 566 567void redrawStaticElements() { 568 // Clear static frame buffer 569 for (int i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) { 570 staticFrameBuffer[i] = colorSchemes[currentColorScheme]->black; 571 } 572 573 // Redraw all static elements with new colors 574 draw_frame_border(); 575 draw_radar_grid(); 576 577 // Copy to main frame buffer 578 for (int i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) { 579 frameBuffer[i] = staticFrameBuffer[i]; 580 } 581 582 colorSchemeChanged = false; 583 Serial.println("Static elements redrawn with new color scheme"); 584} 585 586/* ======== DISPLAY INITIALIZATION ======== */ 587void init_display() { 588 Serial.println("Initializing display..."); 589 590 pinMode(BL_PIN, OUTPUT); 591 digitalWrite(BL_PIN, HIGH); 592 delay(100); 593 594 // Initialize rotary encoder pins 595 pinMode(ENCODER_A_PIN, INPUT_PULLUP); 596 pinMode(ENCODER_B_PIN, INPUT_PULLUP); 597 598 // Attach interrupts for rotary encoder 599 attachInterrupt(digitalPinToInterrupt(ENCODER_A_PIN), encoderISR, CHANGE); 600 attachInterrupt(digitalPinToInterrupt(ENCODER_B_PIN), encoderISR, CHANGE); 601 602 panelBus = new Arduino_SWSPI( 603 GFX_NOT_DEFINED, PANEL_CS, PANEL_SCK, PANEL_SDA, GFX_NOT_DEFINED 604 ); 605 606 rgbpanel = new Arduino_ESP32RGBPanel( 607 40, 7, 15, 41, 608 46, 3, 8, 18, 17, 609 14, 13, 12, 11, 10, 9, 610 5, 45, 48, 47, 21, 611 1, 50, 10, 50, 612 1, 30, 10, 30, 613 PCLK_NEG, 8000000UL 614 ); 615 616#if TYPE_SEL == 7 617 gfx = new Arduino_RGB_Display( 618 DISPLAY_WIDTH, DISPLAY_HEIGHT, rgbpanel, 0, true, 619 panelBus, GFX_NOT_DEFINED, 620 st7701_type7_init_operations, sizeof(st7701_type7_init_operations) 621 ); 622#else 623 gfx = new Arduino_RGB_Display( 624 DISPLAY_WIDTH, DISPLAY_HEIGHT, rgbpanel, 0, true, 625 panelBus, GFX_NOT_DEFINED, 626 st7701_type5_init_operations, sizeof(st7701_type5_init_operations) 627 ); 628#endif 629 630 Serial.println("Starting display begin..."); 631 bool ok = gfx->begin(16000000); 632 Serial.printf("Display begin: %s\n", ok ? "OK" : "FAILED"); 633 634 if (!ok) { 635 Serial.println("Display initialization failed!"); 636 while(1) delay(1000); 637 } 638 639 // Allocate main frame buffer with extra margin for safety 640 frameBuffer = (uint16_t*)ps_malloc(DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t) + 32); 641 if (!frameBuffer) { 642 Serial.println("Frame buffer allocation failed!"); 643 while(1) delay(1000); 644 } 645 646 // Allocate static frame buffer for unchanging elements with extra margin 647 staticFrameBuffer = (uint16_t*)ps_malloc(DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t) + 32); 648 if (!staticFrameBuffer) { 649 Serial.println("Static frame buffer allocation failed!"); 650 while(1) delay(1000); 651 } 652 653 // Clear both buffers to black 654 for (int i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) { 655 frameBuffer[i] = colorSchemes[currentColorScheme]->black; 656 staticFrameBuffer[i] = colorSchemes[currentColorScheme]->black; 657 } 658 659 Serial.println("Display initialized successfully"); 660} 661 662void initClockDisplay() { 663 // Draw static elements to static buffer 664 draw_frame_border(); 665 draw_radar_grid(); 666 667 // Copy static elements to main frame buffer 668 for (int i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) { 669 frameBuffer[i] = staticFrameBuffer[i]; 670 } 671 672 // Init and get the time 673 configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); 674 675 Serial.println("Clock display initialized"); 676} 677 678void draw_circle_to_buffer(uint16_t *buffer, int center_x, int center_y, int radius, uint16_t color) { 679 int x = radius; 680 int y = 0; 681 int err = 0; 682 683 while (x >= y) { 684 buffer[(center_y + y) * DISPLAY_WIDTH + (center_x + x)] = color; 685 buffer[(center_y + x) * DISPLAY_WIDTH + (center_x + y)] = color; 686 buffer[(center_y + x) * DISPLAY_WIDTH + (center_x - y)] = color; 687 buffer[(center_y + y) * DISPLAY_WIDTH + (center_x - x)] = color; 688 buffer[(center_y - y) * DISPLAY_WIDTH + (center_x + x)] = color; 689 buffer[(center_y - x) * DISPLAY_WIDTH + (center_x + y)] = color; 690 buffer[(center_y - x) * DISPLAY_WIDTH + (center_x - y)] = color; 691 buffer[(center_y - y) * DISPLAY_WIDTH + (center_x - x)] = color; 692 693 y++; 694 err += 1 + 2 * y; 695 if (2 * (err - x) + 1 > 0) { 696 x--; 697 err += 1 - 2 * x; 698 } 699 } 700} 701 702void draw_arc_to_buffer(uint16_t *buffer, int center_x, int center_y, int radius, int start_angle, int end_angle, uint16_t color) { 703 // Convert angles to radians 704 float start_rad = start_angle * PI / 180.0; 705 float end_rad = end_angle * PI / 180.0; 706 707 // Draw arc by stepping through angles 708 for (float angle = start_rad; angle <= end_rad; angle += 0.01) { 709 int x = center_x + radius * cos(angle); 710 int y = center_y + radius * sin(angle); 711 if (x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT) { 712 buffer[y * DISPLAY_WIDTH + x] = color; 713 } 714 } 715} 716 717void draw_radial_scale_marks() { 718 for (int angle = 0; angle < 360; angle += 10) { 719 if ((angle >= 355 || angle <= 5) || 720 (angle >= 85 && angle <= 95) || 721 (angle >= 175 && angle <= 185) || 722 (angle >= 265 && angle <= 275)) { 723 continue; 724 } 725 726 float rad = angle * PI / 180.0; 727 int inner_x = CENTER_X + (FRAME_START + FRAME_WIDTH) * sin(rad); 728 int inner_y = CENTER_Y - (FRAME_START + FRAME_WIDTH) * cos(rad); 729 int outer_x = CENTER_X + (FRAME_START + FRAME_WIDTH + 12) * sin(rad); 730 int outer_y = CENTER_Y - (FRAME_START + FRAME_WIDTH + 12) * cos(rad); 731 732 draw_line_to_buffer(staticFrameBuffer, inner_x, inner_y, outer_x, outer_y, colorSchemes[currentColorScheme]->frame_color); 733 } 734 735 for (int angle = 0; angle < 360; angle += 30) { 736 if (angle % 90 == 0) continue; 737 if ((angle >= 355 || angle <= 5) || 738 (angle >= 85 && angle <= 95) || 739 (angle >= 175 && angle <= 185) || 740 (angle >= 265 && angle <= 275)) { 741 continue; 742 } 743 744 float rad = angle * PI / 180.0; 745 int inner_x = CENTER_X + (FRAME_START + FRAME_WIDTH) * sin(rad); 746 int inner_y = CENTER_Y - (FRAME_START + FRAME_WIDTH) * cos(rad); 747 int outer_x = CENTER_X + (FRAME_START + FRAME_WIDTH + 20) * sin(rad); 748 int outer_y = CENTER_Y - (FRAME_START + FRAME_WIDTH + 20) * cos(rad); 749 750 draw_line_to_buffer(staticFrameBuffer, inner_x, inner_y, outer_x, outer_y, colorSchemes[currentColorScheme]->frame_color); 751 } 752} 753 754void draw_cardinal_directions() { 755 const int gap_size = 10; 756 757 // North 758 int north_x = CENTER_X; 759 int north_y = CENTER_Y - FRAME_START - FRAME_WIDTH - 20; 760 for (int dx = -12; dx <= 12; dx++) { 761 for (int dy = -12; dy <= 12; dy++) { 762 int px = north_x + dx; 763 int py = north_y + dy; 764 if (px >= 0 && px < DISPLAY_WIDTH && py >= 0 && py < DISPLAY_HEIGHT) { 765 staticFrameBuffer[py * DISPLAY_WIDTH + px] = colorSchemes[currentColorScheme]->black; 766 } 767 } 768 } 769 draw_line_to_buffer(staticFrameBuffer, north_x - 8, north_y + 8, north_x - 8, north_y - 8, colorSchemes[currentColorScheme]->text_color); 770 draw_line_to_buffer(staticFrameBuffer, north_x + 8, north_y + 8, north_x + 8, north_y - 8, colorSchemes[currentColorScheme]->text_color); 771 draw_line_to_buffer(staticFrameBuffer, north_x - 8, north_y - 8, north_x + 8, north_y + 8, colorSchemes[currentColorScheme]->text_color); 772 773 // South 774 int south_x = CENTER_X; 775 int south_y = CENTER_Y + FRAME_START + FRAME_WIDTH + 20; 776 for (int dx = -12; dx <= 12; dx++) { 777 for (int dy = -12; dy <= 12; dy++) { 778 int px = south_x + dx; 779 int py = south_y + dy; 780 if (px >= 0 && px < DISPLAY_WIDTH && py >= 0 && py < DISPLAY_HEIGHT) { 781 staticFrameBuffer[py * DISPLAY_WIDTH + px] = colorSchemes[currentColorScheme]->black; 782 } 783 } 784 } 785 draw_line_to_buffer(staticFrameBuffer, south_x - 8, south_y - 8, south_x + 8, south_y - 8, colorSchemes[currentColorScheme]->text_color); 786 draw_line_to_buffer(staticFrameBuffer, south_x - 8, south_y - 8, south_x - 8, south_y, colorSchemes[currentColorScheme]->text_color); 787 draw_line_to_buffer(staticFrameBuffer, south_x - 8, south_y, south_x + 8, south_y, colorSchemes[currentColorScheme]->text_color); 788 draw_line_to_buffer(staticFrameBuffer, south_x + 8, south_y, south_x + 8, south_y + 8, colorSchemes[currentColorScheme]->text_color); 789 draw_line_to_buffer(staticFrameBuffer, south_x - 8, south_y + 8, south_x + 8, south_y + 8, colorSchemes[currentColorScheme]->text_color); 790 791 // East 792 int east_x = CENTER_X + FRAME_START + FRAME_WIDTH + 20; 793 int east_y = CENTER_Y; 794 for (int dx = -12; dx <= 12; dx++) { 795 for (int dy = -12; dy <= 12; dy++) { 796 int px = east_x + dx; 797 int py = east_y + dy; 798 if (px >= 0 && px < DISPLAY_WIDTH && py >= 0 && py < DISPLAY_HEIGHT) { 799 staticFrameBuffer[py * DISPLAY_WIDTH + px] = colorSchemes[currentColorScheme]->black; 800 } 801 } 802 } 803 draw_line_to_buffer(staticFrameBuffer, east_x - 8, east_y - 8, east_x + 8, east_y - 8, colorSchemes[currentColorScheme]->text_color); 804 draw_line_to_buffer(staticFrameBuffer, east_x - 8, east_y, east_x + 8, east_y, colorSchemes[currentColorScheme]->text_color); 805 draw_line_to_buffer(staticFrameBuffer, east_x - 8, east_y + 8, east_x + 8, east_y + 8, colorSchemes[currentColorScheme]->text_color); 806 draw_line_to_buffer(staticFrameBuffer, east_x - 8, east_y - 8, east_x - 8, east_y + 8, colorSchemes[currentColorScheme]->text_color); 807 808 // West 809 int west_x = CENTER_X - FRAME_START - FRAME_WIDTH - 20; 810 int west_y = CENTER_Y; 811 for (int dx = -12; dx <= 12; dx++) { 812 for (int dy = -12; dy <= 12; dy++) { 813 int px = west_x + dx; 814 int py = west_y + dy; 815 if (px >= 0 && px < DISPLAY_WIDTH && py >= 0 && py < DISPLAY_HEIGHT) { 816 staticFrameBuffer[py * DISPLAY_WIDTH + px] = colorSchemes[currentColorScheme]->black; 817 } 818 } 819 } 820 draw_line_to_buffer(staticFrameBuffer, west_x - 8, west_y - 8, west_x - 4, west_y + 8, colorSchemes[currentColorScheme]->text_color); 821 draw_line_to_buffer(staticFrameBuffer, west_x - 4, west_y + 8, west_x, west_y - 4, colorSchemes[currentColorScheme]->text_color); 822 draw_line_to_buffer(staticFrameBuffer, west_x, west_y - 4, west_x + 4, west_y + 8, colorSchemes[currentColorScheme]->text_color); 823 draw_line_to_buffer(staticFrameBuffer, west_x + 4, west_y + 8, west_x + 8, west_y - 8, colorSchemes[currentColorScheme]->text_color); 824} 825 826void draw_frame_border() { 827 const int gap_size = 10; 828 829 for (int r = FRAME_START; r < FRAME_START + FRAME_WIDTH; r++) { 830 draw_arc_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y, r, 831 gap_size, 90 - gap_size, colorSchemes[currentColorScheme]->frame_color); 832 draw_arc_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y, r, 833 90 + gap_size, 180 - gap_size, colorSchemes[currentColorScheme]->frame_color); 834 draw_arc_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y, r, 835 180 + gap_size, 270 - gap_size, colorSchemes[currentColorScheme]->frame_color); 836 draw_arc_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y, r, 837 270 + gap_size, 360 - gap_size, colorSchemes[currentColorScheme]->frame_color); 838 } 839 840 draw_radial_scale_marks(); 841 draw_cardinal_directions(); 842} 843 844void draw_radar_grid() { 845 for (int r = 50; r <= RADAR_RADIUS; r += 50) { 846 draw_circle_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y, r, colorSchemes[currentColorScheme]->grid_color); 847 } 848 849 draw_line_to_buffer(staticFrameBuffer, CENTER_X - RADAR_RADIUS, CENTER_Y, CENTER_X + RADAR_RADIUS, CENTER_Y, colorSchemes[currentColorScheme]->grid_color); 850 draw_line_to_buffer(staticFrameBuffer, CENTER_X, CENTER_Y - RADAR_RADIUS, CENTER_X, CENTER_Y + RADAR_RADIUS, colorSchemes[currentColorScheme]->grid_color); 851} 852 853void draw_radar_sweep() { 854 float rad = current_angle * PI / 180.0; 855 int end_x = CENTER_X + RADAR_RADIUS * sin(rad); 856 int end_y = CENTER_Y - RADAR_RADIUS * cos(rad); 857 858 for (int r = 0; r <= RADAR_RADIUS; r++) { 859 int trail_x = CENTER_X + r * sin(rad); 860 int trail_y = CENTER_Y - r * cos(rad); 861 862 int dx = trail_x - CENTER_X; 863 int dy = trail_y - CENTER_Y; 864 if (dx * dx + dy * dy <= RADAR_RADIUS * RADAR_RADIUS) { 865 uint16_t trail_color = colorSchemes[currentColorScheme]->trail_color; 866 867 if (r == RADAR_RADIUS) { 868 frameBuffer[trail_y * DISPLAY_WIDTH + trail_x] = colorSchemes[currentColorScheme]->sweep_color; 869 } else { 870 frameBuffer[trail_y * DISPLAY_WIDTH + trail_x] = trail_color; 871 } 872 } 873 } 874 875 current_angle += SWEEP_SPEED; 876 if (current_angle >= 360) { 877 current_angle = 0; 878 879 for (int y = 0; y < DISPLAY_HEIGHT; y++) { 880 for (int x = 0; x < DISPLAY_WIDTH; x++) { 881 int dx = x - CENTER_X; 882 int dy = y - CENTER_Y; 883 if (dx * dx + dy * dy <= RADAR_RADIUS * RADAR_RADIUS) { 884 frameBuffer[y * DISPLAY_WIDTH + x] = colorSchemes[currentColorScheme]->black; 885 } 886 } 887 } 888 889 for (int y = 0; y < DISPLAY_HEIGHT; y++) { 890 for (int x = 0; x < DISPLAY_WIDTH; x++) { 891 int dx = x - CENTER_X; 892 int dy = y - CENTER_Y; 893 if (dx * dx + dy * dy <= RADAR_RADIUS * RADAR_RADIUS) { 894 frameBuffer[y * DISPLAY_WIDTH + x] = staticFrameBuffer[y * DISPLAY_WIDTH + x]; 895 } 896 } 897 } 898 } 899} 900 901void setup() { 902 Serial.begin(115200); 903 Serial.println("\n\nStarting ESP32S3 Radar Clock"); 904 905 // Increase stability by setting WiFi to static mode 906 WiFi.mode(WIFI_STA); 907 WiFi.setAutoReconnect(true); 908 WiFi.persistent(true); 909 910 // Initialize display 911 init_display(); 912 913 // Show title screen 914 startupStartTime = millis(); 915 showTitleScreen(); 916 Serial.println("Showing title screen..."); 917} 918 919void loop() { 920 // Update startup sequence 921 updateStartupSequence(); 922 923 // Only run clock functions when in clock mode 924 if (currentStartupState == SHOW_CLOCK) { 925 // Check for encoder rotation 926 handleEncoder(); 927 928 // Redraw static elements if color scheme changed 929 if (colorSchemeChanged) { 930 redrawStaticElements(); 931 } 932 933 // Update radar sweep 934 draw_radar_sweep(); 935 936 // Update time display 937 update_time_display(); 938 939 // Update display 940 gfx->draw16bitRGBBitmap(0, 0, frameBuffer, DISPLAY_WIDTH, DISPLAY_HEIGHT); 941 } 942 943 delay(50); // ~20 FPS 944}
Documentation
Code
...
Code Final.zip
Comments
Only logged in users can leave comments