Oscilloscope Clock Simulation on a Round ESP32 Display
By combining the vintage aesthetics of a CRT oscilloscope with modern ESP32 hardware, this project proves you don't need dangerous high voltage to enjoy the classic look of a vector clock.
Devices & Components
1
Elecrow CrowPanel 2.1inch-HMI ESP32 Rotary Display 480*480
Hardware & Tools
1
Soldering iron kit.
Software & Tools
Arduino IDE
Project description
Code
CodeA
cpp
arcs
1/*============================================================== 2 TWO-MODE NTP CLOCK – CrowPanel 2.1" ESP32-S3 3 + OSCILLOSCOPE GRID OVERLAY (5x5 with faint yellow dots) 4 5 MODE 0: Analog oscilloscope clock (zoom with encoder) 6 MODE 1: Digital clock (Day / HH:MM / Date) with thin CRT lines 7 8 - Rotary encoder CLK/DT: zoom in MODE 0 9 - Encoder button on PCF8574 P5: toggle MODE 0 / MODE 1 10 - Time from NTP 11 12 by mircemk , Feb 2026 13==============================================================*/ 14 15#include <Arduino.h> 16#include <Arduino_GFX_Library.h> 17#include <WiFi.h> 18#include <time.h> 19#include <Wire.h> 20 21// ------------------------------------------------------------- 22// DISPLAY CONSTANTS 23// ------------------------------------------------------------- 24#define DISPLAY_WIDTH 480 25#define DISPLAY_HEIGHT 480 26#define CX 240 27#define CY 240 28 29// -------- PANEL PINS (CrowPanel 2.1") -------- 30#define TYPE_SEL 7 31#define PCLK_NEG 1 32#define BL_PIN 6 33#define PANEL_CS 16 34#define PANEL_SCK 2 35#define PANEL_SDA 1 36 37// Rotary Encoder pins 38#define ENCODER_CLK 4 39#define ENCODER_DT 42 40// Encoder button on PCF8574 P5 41 42// I2C for PCF8574 43#define I2C_SDA 38 44#define I2C_SCL 39 45#define PCF8574_ADDR 0x21 46 47// ------------------------------------------------------------- 48// WIFI / NTP CONFIG 49// ------------------------------------------------------------- 50const char* WIFI_SSID = "*****"; 51const char* WIFI_PASSWORD = "*******"; 52 53const long gmtOffset_sec = 3600; // UTC+1 54const int daylightOffset_sec = 3600; // DST +1h 55const char* ntpServer = "pool.ntp.org"; 56 57// ------------------------------------------------------------- 58// FRAMEBUFFER + DISPLAY OBJECTS 59// ------------------------------------------------------------- 60Arduino_DataBus *panelBus = nullptr; 61Arduino_ESP32RGBPanel *rgbpanel = nullptr; 62Arduino_RGB_Display *gfx = nullptr; 63uint16_t *fb = nullptr; 64 65// ------------- COLORS ------------- 66const uint16_t COL_BG = 0x0000; // black 67const uint16_t COL_GREEN = 0x07FF; //0x07E0; // bright green 68const uint16_t COL_GLOW = 0x03E0; //0x01E0; // faint glow 69const uint16_t COL_GRID = 0x5240; //0x4a20; //0x4200; //0x39c0; // 0x6b22 // faint yellow 70// ---------------------------------- 71 72// ------------------------------------------------------------- 73// ROTARY ENCODER + MODE STATE 74// ------------------------------------------------------------- 75volatile int lastEncoded = 0; 76volatile long encoderValue = 0; 77long lastEncoderValue = 0; 78 79int zoomOffset = 0; // radius offset for analog clock 80int clockMode = 0; // 0 = analog, 1 = digital 81 82bool lastButtonPressed = false; 83unsigned long lastBtnChangeMs = 0; 84 85// ------------------------------------------------------------- 86// TIME STATE 87// ------------------------------------------------------------- 88time_t lastSecond = 0; 89 90// ------------------------------------------------------------- 91// PIXEL HELPERS + GLOW 92// ------------------------------------------------------------- 93static inline void putpix(int x, int y, uint16_t c) { 94 if ((unsigned)x < DISPLAY_WIDTH && (unsigned)y < DISPLAY_HEIGHT) 95 fb[y * DISPLAY_WIDTH + x] = c; 96} 97 98void glow_putpix(int x, int y) 99{ 100 putpix(x, y, COL_GREEN); // main pixel 101 102 putpix(x+1, y, COL_GLOW); 103 putpix(x-1, y, COL_GLOW); 104 putpix(x, y+1, COL_GLOW); 105 putpix(x, y-1, COL_GLOW); 106 107 putpix(x+1, y+1, COL_GLOW); 108 putpix(x-1, y+1, COL_GLOW); 109 putpix(x+1, y-1, COL_GLOW); 110 putpix(x-1, y-1, COL_GLOW); 111} 112 113void draw_line(int x0,int y0,int x1,int y1,uint16_t col){ 114 int dx=abs(x1-x0), sx=x0<x1?1:-1; 115 int dy=-abs(y1-y0), sy=y0<y1?1:-1; 116 int err=dx+dy, e2; 117 118 for(;;){ 119 glow_putpix(x0, y0); 120 if(x0==x1 && y0==y1) break; 121 e2 = 2 * err; 122 if(e2 >= dy){ err += dy; x0 += sx; } 123 if(e2 <= dx){ err += dx; y0 += sy; } 124 } 125} 126 127void clearFB() 128{ 129 memset(fb, 0, DISPLAY_WIDTH * DISPLAY_HEIGHT * 2); 130} 131 132// ------------------------------------------------------------- 133// OSCILLOSCOPE GRID DRAWING 134// ------------------------------------------------------------- 135void draw_oscilloscope_grid() 136{ 137 // 5x5 major divisions (96 pixels each) 138 const int divisions = 5; 139 const int spacing = DISPLAY_WIDTH / divisions; // 96 pixels 140 141 // Draw grid with dots instead of lines 142 for (int i = 0; i <= divisions; i++) { 143 int pos = i * spacing; 144 145 // Vertical grid lines (as dots) 146 for (int y = 0; y < DISPLAY_HEIGHT; y += 4) { 147 putpix(pos, y, COL_GRID); 148 } 149 150 // Horizontal grid lines (as dots) 151 for (int x = 0; x < DISPLAY_WIDTH; x += 4) { 152 putpix(x, pos, COL_GRID); 153 } 154 } 155 156 // Add tick marks on center axes (X and Y) 157 const int tickSize = 6; 158 const int tickSpacing = 12; // Small divisions 159 160 // X-axis tick marks (vertical center line) 161 for (int x = 0; x < DISPLAY_WIDTH; x += tickSpacing) { 162 for (int ty = -tickSize; ty <= tickSize; ty++) { 163 putpix(x, CY + ty, COL_GRID); 164 } 165 } 166 167 // Y-axis tick marks (horizontal center line) 168 for (int y = 0; y < DISPLAY_HEIGHT; y += tickSpacing) { 169 for (int tx = -tickSize; tx <= tickSize; tx++) { 170 putpix(CX + tx, y, COL_GRID); 171 } 172 } 173} 174 175// ------------------------------------------------------------- 176// RETRO DIGIT BITMAP FONT (од твојот 0.6A MODE 0) 177// ------------------------------------------------------------- 178const uint8_t RETRO_DIGIT[10][12] PROGMEM = { 179 {0b00111100,0b01000010,0b10000001,0b10000001,0b10000001,0b10000001,0b10000001,0b10000001,0b10000001,0b01000010,0b00111100,0}, 180 {0b00010000,0b00110000,0b01010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0}, 181 {0b00111100,0b01000010,0b00000010,0b00000100,0b00001000,0b00010000,0b00100000,0b01000000,0b10000000,0b10000000,0b11111110,0}, 182 {0b00111100,0b01000010,0b00000010,0b00000010,0b00111100,0b00000010,0b00000010,0b00000010,0b00000010,0b01000010,0b00111100,0}, 183 {0b00001000,0b00011000,0b00101000,0b01001000,0b10001000,0b11111110,0b00001000,0b00001000,0b00001000,0b00001000,0b00001000,0}, 184 {0b01111110,0b01000000,0b01000000,0b01000000,0b01111100,0b00000010,0b00000010,0b00000010,0b00000010,0b01000100,0b00111000,0}, 185 {0b00111100,0b01000010,0b10000000,0b10000000,0b11111100,0b10000010,0b10000010,0b10000010,0b10000010,0b01000010,0b00111100,0}, 186 {0b11111110,0b00000010,0b00000100,0b00001000,0b00010000,0b00010000,0b00100000,0b00100000,0b01000000,0b01000000,0b01000000,0}, 187 {0b00111100,0b01000010,0b10000001,0b10000001,0b01000010,0b00111100,0b01000010,0b10000001,0b10000001,0b01000010,0b00111100,0}, 188 {0b00111100,0b01000010,0b10000001,0b10000001,0b01000011,0b00111101,0b00000001,0b00000001,0b00000010,0b01000100,0b00111000,0} 189}; 190 191const int GLYPH_W = 8; 192const int GLYPH_H = 12; 193 194// ---- Digit drawing with glow (your original) ---- 195void drawDigitsString(const char *text, int x, int y, int scale) 196{ 197 int len = strlen(text); 198 199 for(int d=0; d<len; d++){ 200 char c = text[d]; 201 202 if(c >= '0' && c <= '9'){ 203 int digit = c - '0'; 204 const uint8_t *glyph = RETRO_DIGIT[digit]; 205 int x0 = x + d*(GLYPH_W*scale + scale); 206 207 for(int row=0; row<GLYPH_H; row++){ 208 uint8_t bits = pgm_read_byte(&glyph[row]); 209 for(int col=0; col<GLYPH_W; col++){ 210 if(bits & (0x80 >> col)){ 211 for(int sx=0; sx<scale; sx++) 212 for(int sy=0; sy<scale; sy++){ 213 glow_putpix(x0 + col*scale + sx, y + row*scale + sy); 214 } 215 } 216 } 217 } 218 } 219 220 if(c=='.'){ 221 int dotSize = 2*scale; 222 int bx = x + d*(GLYPH_W*scale+scale) + GLYPH_W*scale/2 - dotSize/2; 223 int by = y + GLYPH_H*scale - dotSize - scale; 224 225 for(int yy=0; yy<dotSize; yy++) 226 for(int xx=0; xx<dotSize; xx++) 227 glow_putpix(bx+xx, by+yy); 228 } 229 } 230} 231 232// ------------------------------------------------------------- 233// MODE 0 – ANALOG OSCILLOSCOPE CLOCK (од V0.6A, логика) 234// ------------------------------------------------------------- 235void drawDialNumber(int num, float angleDeg, int radius, int scale) 236{ 237 char buf[4]; 238 sprintf(buf,"%d",num); 239 240 float a = angleDeg * PI / 180.0f; 241 int x = CX + (int)(radius * sin(a)); 242 int y = CY - (int)(radius * cos(a)); 243 244 int w = GLYPH_W * scale; 245 drawDigitsString(buf, x - w/2, y - (GLYPH_H*scale)/2, scale); 246} 247 248void draw_ring(int cx,int cy,int rOuter,int thickness,uint16_t col) 249{ 250 int rInner = rOuter - thickness; 251 int ro2 = rOuter*rOuter; 252 int ri2 = rInner*rInner; 253 254 for(int y=-rOuter; y<=rOuter; y++){ 255 for(int x=-rOuter; x<=rOuter; x++){ 256 int rr = x*x + y*y; 257 if(rr<=ro2 && rr>=ri2){ 258 glow_putpix(cx+x, cy+y); 259 } 260 } 261 } 262} 263 264void draw_clock_face_osc(int zoom) 265{ 266 clearFB(); 267 268 // DRAW GRID FIRST (as background layer) 269 draw_oscilloscope_grid(); 270 271 int R_OUT = 225 + zoom; 272 if(R_OUT < 180) R_OUT = 180; 273 if(R_OUT > 240) R_OUT = 240; 274 275 const int SHORT_LEN = 20; 276 const int LONG_LEN = 32; 277 278 for(int i=0;i<60;i++){ 279 float a = i*6.0f * PI/180.0f; 280 281 bool isHour = (i % 5 == 0); 282 int len = isHour ? LONG_LEN : SHORT_LEN; 283 int thickness = isHour ? 4 : 2; 284 int half = thickness/2; 285 286 int x1 = CX + (int)((R_OUT - len)*sin(a)); 287 int y1 = CY - (int)((R_OUT - len)*cos(a)); 288 int x2 = CX + (int)(R_OUT*sin(a)); 289 int y2 = CY - (int)(R_OUT*cos(a)); 290 291 float dx = x2 - x1; 292 float dy = y2 - y1; 293 294 float px = -dy; 295 float py = dx; 296 297 float inv = 1.0f / sqrt(px*px + py*py); 298 px *= inv; 299 py *= inv; 300 301 for(int k=-half; k<=half; k++){ 302 int ox = (int)(px*k); 303 int oy = (int)(py*k); 304 draw_line(x1+ox, y1+oy, x2+ox, y2+oy, COL_GREEN); 305 } 306 } 307 308 int numRadius = R_OUT - 57; 309 for(int n=1;n<=12;n++) 310 drawDialNumber(n, n*30.0f, numRadius, 3); 311 312 // пример дата 313 drawDigitsString("10.12.2008", CX-80, CY+60, 2); 314 315 draw_ring(CX, CY, 10, 2, COL_GREEN); 316} 317 318void draw_clock_hands_osc(int hour,int minute,int second,int zoom) 319{ 320 float secA = second * 6.0f * PI/180.0f; 321 float minA = (minute + second/60.0f)*6.0f * PI/180.0f; 322 float hourA = (hour%12 + minute/60.0f)*30.0f * PI/180.0f; 323 324 const int HUB_R = 10; 325 int R_H = 120 + (zoom/3); 326 int R_M = 175 + (zoom/3); 327 int R_S = 190 + (zoom/3); 328 329 const int W_H = 8; 330 const int W_M = 6; 331 332 { // Hour hand 333 float s = sin(hourA), c = cos(hourA); 334 335 int tipX = CX + (int)(R_H*s); 336 int tipY = CY - (int)(R_H*c); 337 338 int leftX = CX + (int)( HUB_R*s + W_H*c ); 339 int leftY = CY - (int)( HUB_R*c - W_H*s ); 340 341 int rightX = CX + (int)( HUB_R*s - W_H*c ); 342 int rightY = CY - (int)( HUB_R*c + W_H*s ); 343 344 draw_line(leftX, leftY, tipX, tipY, COL_GREEN); 345 draw_line(rightX, rightY, tipX, tipY, COL_GREEN); 346 } 347 348 { // Minute hand 349 float s = sin(minA), c = cos(minA); 350 351 int tipX = CX + (int)(R_M*s); 352 int tipY = CY - (int)(R_M*c); 353 354 int leftX = CX + (int)( HUB_R*s + W_M*c ); 355 int leftY = CY - (int)( HUB_R*c - W_M*s ); 356 357 int rightX = CX + (int)( HUB_R*s - W_M*c ); 358 int rightY = CY - (int)( HUB_R*c + W_M*s ); 359 360 draw_line(leftX, leftY, tipX, tipY, COL_GREEN); 361 draw_line(rightX, rightY, tipX, tipY, COL_GREEN); 362 } 363 364 { // Second hand 365 float s = sin(secA), c = cos(secA); 366 int tipX = CX + (int)(R_S*s); 367 int tipY = CY - (int)(R_S*c); 368 draw_line(CX, CY, tipX, tipY, COL_GREEN); 369 } 370 371 draw_ring(CX, CY, HUB_R, 2, COL_GREEN); 372} 373 374void draw_mode0_osc_clock(const tm &t) 375{ 376 draw_clock_face_osc(zoomOffset); 377 draw_clock_hands_osc(t.tm_hour, t.tm_min, t.tm_sec, zoomOffset); 378 gfx->draw16bitRGBBitmap(0,0,fb,DISPLAY_WIDTH,DISPLAY_HEIGHT); 379} 380 381 382 383 384// ------------------------------------------------------------- 385// 5x7 LETTER FONT (A–Z) FOR DAY/DATE 386// ------------------------------------------------------------- 387const uint8_t FONT5x7[26][7] PROGMEM = { 388 {0b01110,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001}, // A 389 {0b11110,0b10001,0b10001,0b11110,0b10001,0b10001,0b11110}, // B 390 {0b01110,0b10001,0b10000,0b10000,0b10000,0b10001,0b01110}, // C 391 {0b11100,0b10010,0b10001,0b10001,0b10001,0b10010,0b11100}, // D 392 {0b11111,0b10000,0b10000,0b11110,0b10000,0b10000,0b11111}, // E 393 {0b11111,0b10000,0b10000,0b11110,0b10000,0b10000,0b10000}, // F 394 {0b01110,0b10001,0b10000,0b10111,0b10001,0b10001,0b01110}, // G 395 {0b10001,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001}, // H 396 {0b01110,0b00100,0b00100,0b00100,0b00100,0b00100,0b01110}, // I 397 {0b00111,0b00010,0b00010,0b00010,0b10010,0b10010,0b01100}, // J 398 {0b10001,0b10010,0b10100,0b11000,0b10100,0b10010,0b10001}, // K 399 {0b10000,0b10000,0b10000,0b10000,0b10000,0b10000,0b11111}, // L 400 {0b10001,0b11011,0b10101,0b10101,0b10001,0b10001,0b10001}, // M 401 {0b10001,0b10001,0b11001,0b10101,0b10011,0b10001,0b10001}, // N 402 {0b01110,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110}, // O 403 {0b11110,0b10001,0b10001,0b11110,0b10000,0b10000,0b10000}, // P 404 {0b01110,0b10001,0b10001,0b10001,0b10101,0b10010,0b01101}, // Q 405 {0b11110,0b10001,0b10001,0b11110,0b10100,0b10010,0b10001}, // R 406 {0b01111,0b10000,0b10000,0b01110,0b00001,0b00001,0b11110}, // S 407 {0b11111,0b00100,0b00100,0b00100,0b00100,0b00100,0b00100}, // T 408 {0b10001,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110}, // U 409 {0b10001,0b10001,0b10001,0b01010,0b01010,0b00100,0b00100}, // V 410 {0b10001,0b10001,0b10101,0b10101,0b10101,0b11011,0b10001}, // W 411 {0b10001,0b01010,0b00100,0b00100,0b00100,0b01010,0b10001}, // X 412 {0b10001,0b01010,0b00100,0b00100,0b00100,0b00100,0b00100}, // Y 413 {0b11111,0b00001,0b00010,0b00100,0b01000,0b10000,0b11111} // Z 414}; 415 416int textWidth5x7(const char *s, int scale) { 417 int len = strlen(s); 418 if (len == 0) return 0; 419 int charW = 5 * scale; 420 int gap = scale; 421 return len * (charW + gap) - gap; 422} 423 424// ------------------------------------------------------------- 425// MODE 1 – THIN CRT-STYLE DIGITAL CLOCK (Day / HH:MM / Date) 426// ------------------------------------------------------------- 427// ------------------------------------------------------------- 428// THIN VECTOR CRT DIGITS – improved spacing 429// ------------------------------------------------------------- 430 431// ============================================================ 432// FIXED ROUNDED CRT CHARACTERS - Целосно поправена верзија 433// ============================================================ 434 435// HELPER: Draw arc (четвртина круг) - ПРАВИЛНА ОРИЕНТАЦИЈА 436void draw_arc(int cx, int cy, int radius, int startAngle, int endAngle) 437{ 438 // startAngle и endAngle се во степени (0-360) 439 for (int angle = startAngle; angle <= endAngle; angle += 2) { 440 float rad = angle * PI / 180.0f; 441 int x = cx + (int)(radius * cos(rad)); 442 int y = cy + (int)(radius * sin(rad)); 443 glow_putpix(x, y); 444 } 445} 446 447// КРУЖЧИЊА ЗА ДВОТОЧКА 448void draw_colon_rounded(int cx, int cy, int height) { 449 int dotRadius = (int)(height * 0.04f); 450 int offset = (int)(height * 0.18f); 451 452 // Горно кружче 453 for(int angle = 0; angle < 360; angle += 8) { 454 float rad = angle * PI / 180.0f; 455 int x = cx + (int)(dotRadius * cos(rad)); 456 int y = (cy - offset) + (int)(dotRadius * sin(rad)); 457 glow_putpix(x, y); 458 } 459 460 // Долно кружче 461 for(int angle = 0; angle < 360; angle += 8) { 462 float rad = angle * PI / 180.0f; 463 int x = cx + (int)(dotRadius * cos(rad)); 464 int y = (cy + offset) + (int)(dotRadius * sin(rad)); 465 glow_putpix(x, y); 466 } 467} 468 469// ============================================================ 470// ROUNDED DIGIT (0-9) - ЦЕЛОСНО РЕКОНСТРУИРАНИ 471// ============================================================ 472void roundedDigit(int d, int cx, int cy, int h) 473{ 474 int w = h * 0.48; 475 int r = w / 3; 476 477 int top = cy - h/2; 478 int bot = cy + h/2; 479 int mid = cy; 480 481 int left = cx - w/2; 482 int right = cx + w/2; 483 484 switch(d) 485 { 486 case 0: // Заоблен правоаголник 487 // Горни лации 488 draw_arc(left + r, top + r, r, 180, 270); // top-left 489 draw_arc(right - r, top + r, r, 270, 360); // top-right 490 // Долни лации 491 draw_arc(right - r, bot - r, r, 0, 90); // bottom-right 492 draw_arc(left + r, bot - r, r, 90, 180); // bottom-left 493 // Прави линии 494 draw_line(left + r, top, right - r, top, COL_GREEN); 495 draw_line(right, top + r, right, bot - r, COL_GREEN); 496 draw_line(right - r, bot, left + r, bot, COL_GREEN); 497 draw_line(left, bot - r, left, top + r, COL_GREEN); 498 break; 499 500 case 1: // Вертикална линија 501 draw_line(cx, top, cx, bot, COL_GREEN); 502 break; 503 504 case 2: // S-форма 505 draw_arc(left + r, top + r, r, 180, 270); 506 draw_arc(right - r, top + r, r, 270, 360); 507 draw_line(left + r, top, right - r, top, COL_GREEN); 508 draw_line(right, top + r, right, mid, COL_GREEN); 509 draw_line(right, mid, left, mid, COL_GREEN); 510 draw_line(left, mid, left, bot - r, COL_GREEN); 511 draw_arc(left + r, bot - r, r, 90, 180); 512 draw_arc(right - r, bot - r, r, 0, 90); 513 draw_line(left + r, bot, right - r, bot, COL_GREEN); 514 break; 515 516 case 3: // Десно заоблена 517 draw_arc(left + r, top + r, r, 180, 270); 518 draw_arc(right - r, top + r, r, 270, 360); 519 draw_line(left + r, top, right - r, top, COL_GREEN); 520 draw_line(right, top + r, right, mid, COL_GREEN); 521 draw_line(right - w/3, mid, right, mid, COL_GREEN); 522 draw_line(right, mid, right, bot - r, COL_GREEN); 523 draw_arc(right - r, bot - r, r, 0, 90); 524 draw_arc(left + r, bot - r, r, 90, 180); 525 draw_line(right - r, bot, left + r, bot, COL_GREEN); 526 break; 527 528 case 4: // 4 529 draw_line(left, top, left, mid, COL_GREEN); 530 draw_line(left, mid, right, mid, COL_GREEN); 531 draw_line(right, top, right, bot, COL_GREEN); 532 break; 533 534 case 5: // S-обратно 535 draw_arc(right - r, top + r, r, 270, 360); 536 draw_arc(left + r, top + r, r, 180, 270); 537 draw_line(right - r, top, left + r, top, COL_GREEN); 538 draw_line(left, top + r, left, mid, COL_GREEN); 539 draw_line(left, mid, right, mid, COL_GREEN); 540 draw_line(right, mid, right, bot - r, COL_GREEN); 541 draw_arc(right - r, bot - r, r, 0, 90); 542 draw_arc(left + r, bot - r, r, 90, 180); 543 draw_line(right - r, bot, left + r, bot, COL_GREEN); 544 break; 545 546 case 6: // 6 547 draw_arc(right - r, top + r, r, 270, 360); 548 draw_line(right - r, top, left + r, top, COL_GREEN); 549 draw_arc(left + r, top + r, r, 180, 270); 550 draw_line(left, top + r, left, bot - r, COL_GREEN); 551 draw_arc(left + r, bot - r, r, 90, 180); 552 draw_line(left + r, bot, right - r, bot, COL_GREEN); 553 draw_arc(right - r, bot - r, r, 0, 90); 554 draw_line(right, bot - r, right, mid, COL_GREEN); 555 draw_line(right, mid, left, mid, COL_GREEN); 556 break; 557 558 case 7: // 7 559 draw_arc(left + r, top + r, r, 180, 270); 560 draw_line(left + r, top, right - r, top, COL_GREEN); 561 draw_arc(right - r, top + r, r, 270, 360); 562 draw_line(right, top + r, cx, bot, COL_GREEN); 563 break; 564 565 case 8: // 8 - две петли 566 // Горна петла 567 draw_arc(left + r, top + r, r, 180, 270); 568 draw_line(left + r, top, right - r, top, COL_GREEN); 569 draw_arc(right - r, top + r, r, 270, 360); 570 draw_line(right, top + r, right, mid, COL_GREEN); 571 draw_line(right, mid, left, mid, COL_GREEN); 572 draw_line(left, mid, left, top + r, COL_GREEN); 573 // Долна петла 574 draw_line(left, mid, left, bot - r, COL_GREEN); 575 draw_arc(left + r, bot - r, r, 90, 180); 576 draw_line(left + r, bot, right - r, bot, COL_GREEN); 577 draw_arc(right - r, bot - r, r, 0, 90); 578 draw_line(right, bot - r, right, mid, COL_GREEN); 579 break; 580 581 case 9: // 9 582 draw_arc(left + r, top + r, r, 180, 270); 583 draw_line(left + r, top, right - r, top, COL_GREEN); 584 draw_arc(right - r, top + r, r, 270, 360); 585 draw_line(right, top + r, right, bot - r, COL_GREEN); 586 draw_line(right, mid, left, mid, COL_GREEN); 587 draw_line(left, mid, left, top + r, COL_GREEN); 588 draw_arc(left + r, bot - r, r, 90, 180); 589 draw_line(left + r, bot, right - r, bot, COL_GREEN); 590 draw_arc(right - r, bot - r, r, 0, 90); 591 break; 592 } 593} 594 595 596// ============================================================ 597// ROUNDED LETTER (потребни букви) 598// ============================================================ 599void roundedLetter(char c, int cx, int cy, int h) 600{ 601 int w = h * 0.45; 602 int r = w / 3; 603 604 int top = cy - h/2; 605 int bot = cy + h/2; 606 int mid = cy; 607 608 int left = cx - w/2; 609 int right = cx + w/2; 610 611 switch(c) 612 { 613 case 'O': // Како 0 614 draw_arc(left + r, top + r, r, 180, 270); 615 draw_arc(right - r, top + r, r, 270, 360); 616 draw_line(left + r, top, right - r, top, COL_GREEN); 617 draw_line(right, top + r, right, bot - r, COL_GREEN); 618 draw_arc(right - r, bot - r, r, 0, 90); 619 draw_line(right - r, bot, left + r, bot, COL_GREEN); 620 draw_arc(left + r, bot - r, r, 90, 180); 621 draw_line(left, bot - r, left, top + r, COL_GREEN); 622 break; 623 624 case 'A': 625 draw_line(left, bot, cx, top, COL_GREEN); 626 draw_line(cx, top, right, bot, COL_GREEN); 627 draw_line(left + w/4, mid, right - w/4, mid, COL_GREEN); 628 break; 629 630 case 'D': 631 draw_line(left, top, left, bot, COL_GREEN); 632 draw_line(left, top, cx + r, top, COL_GREEN); 633 draw_arc(right - r, top + r, r, 270, 360); 634 draw_line(right, top + r, right, bot - r, COL_GREEN); 635 draw_arc(right - r, bot - r, r, 0, 90); 636 draw_line(cx + r, bot, left, bot, COL_GREEN); 637 break; 638 639 case 'F': 640 draw_line(left, top, left, bot, COL_GREEN); 641 draw_line(left, top, right, top, COL_GREEN); 642 draw_line(left, mid, cx, mid, COL_GREEN); 643 break; 644 645 case 'I': 646 draw_line(cx, top, cx, bot, COL_GREEN); 647 break; 648 649 case 'M': 650 draw_line(left, bot, left, top, COL_GREEN); 651 draw_line(left, top, cx, mid, COL_GREEN); 652 draw_line(cx, mid, right, top, COL_GREEN); 653 draw_line(right, top, right, bot, COL_GREEN); 654 break; 655 656 case 'N': 657 draw_line(left, bot, left, top, COL_GREEN); 658 draw_line(left, top, right, bot, COL_GREEN); 659 draw_line(right, bot, right, top, COL_GREEN); 660 break; 661 662 case 'R': 663 draw_line(left, bot, left, top, COL_GREEN); 664 draw_line(left, top, right - r, top, COL_GREEN); 665 draw_arc(right - r, top + r, r, 270, 360); 666 draw_line(right, top + r, right, mid - r, COL_GREEN); 667 draw_arc(right - r, mid - r, r, 0, 90); 668 draw_line(right - r, mid, left, mid, COL_GREEN); 669 draw_line(cx, mid, right, bot, COL_GREEN); 670 break; 671 672 case 'S': // Како 5 673 draw_arc(right - r, top + r, r, 270, 360); 674 draw_line(right - r, top, left + r, top, COL_GREEN); 675 draw_arc(left + r, top + r, r, 180, 270); 676 draw_line(left, top + r, left, mid, COL_GREEN); 677 draw_line(left, mid, right, mid, COL_GREEN); 678 draw_line(right, mid, right, bot - r, COL_GREEN); 679 draw_arc(right - r, bot - r, r, 0, 90); 680 draw_line(right - r, bot, left + r, bot, COL_GREEN); 681 draw_arc(left + r, bot - r, r, 90, 180); 682 break; 683 684 case 'T': 685 draw_line(left, top, right, top, COL_GREEN); 686 draw_line(cx, top, cx, bot, COL_GREEN); 687 break; 688 689 case 'U': 690 draw_line(left, top, left, bot - r, COL_GREEN); 691 draw_arc(left + r, bot - r, r, 90, 180); 692 draw_line(left + r, bot, right - r, bot, COL_GREEN); 693 draw_arc(right - r, bot - r, r, 0, 90); 694 draw_line(right, bot - r, right, top, COL_GREEN); 695 break; 696 697 case 'E': 698 draw_line(left, top, left, bot, COL_GREEN); 699 draw_line(left, top, right, top, COL_GREEN); 700 draw_line(left, mid, cx, mid, COL_GREEN); 701 draw_line(left, bot, right, bot, COL_GREEN); 702 break; 703 704 case 'Y': 705 draw_line(left, top, cx, mid, COL_GREEN); 706 draw_line(right, top, cx, mid, COL_GREEN); 707 draw_line(cx, mid, cx, bot, COL_GREEN); 708 break; 709 710 case 'W': 711 draw_line(left, top, left, bot, COL_GREEN); 712 draw_line(left, bot, cx, mid, COL_GREEN); 713 draw_line(cx, mid, right, bot, COL_GREEN); 714 draw_line(right, bot, right, top, COL_GREEN); 715 break; 716 717 case 'H': 718 draw_line(left, top, left, bot, COL_GREEN); 719 draw_line(left, mid, right, mid, COL_GREEN); 720 draw_line(right, top, right, bot, COL_GREEN); 721 break; 722 } 723} 724// ------------------------------------------------------------- 725// DRAW DAY OF THE WEEK (Vector Thin) - CENTERED 726// ------------------------------------------------------------- 727void drawWeekday_VectorThin(const char* name, int centerY, int height) { 728 int len = strlen(name); 729 int step = height * 0.6; 730 731 // 1. Calculate the character width 'w' (as defined in thinLetter: w = h * 0.45) 732 int w = (int)(height * 0.45); 733 734 // 2. Calculate the total visual width of the word. 735 // Total Width = (len - 1 gaps * step) + (1 character width) 736 int total_width = (len - 1) * step + w; 737 738 // 3. Calculate the X coordinate of the center of the first letter (startX). 739 // The goal is to set the center of the *entire word* at CX (240). 740 // startX = (Center of screen CX) - (Half of total width) + (Half of first character width) 741 // This correctly positions the center of the first letter. 742 int startX = CX - total_width / 2 + w / 2; 743 744 for (int i = 0; i < len; i++) { 745 roundedLetter(name[i], startX + i * step, centerY, height); 746 } 747} 748 749// ------------------------------------------------------------- 750// Draw HH:MM with improved spacing and adjusted position 751// ------------------------------------------------------------- 752void drawTimeHHMM_VectorThin(int hour, int minute, int centerY, int height) 753{ 754 int w = height * 0.48; 755 int spacing = height * 0.1; 756 const int OFFSET_X = 20; 757 758 int cxH1 = CX - (w * 1.5 + spacing * 1.5) - OFFSET_X; 759 int cxH2 = CX - (w * 0.5 + spacing * 0.5) - OFFSET_X; 760 int cxM1 = CX + (w * 0.5 + spacing * 0.5) + OFFSET_X; 761 int cxM2 = CX + (w * 1.5 + spacing * 1.5) + OFFSET_X; 762 763 roundedDigit(hour/10, cxH1, centerY, height); 764 roundedDigit(hour%10, cxH2, centerY, height); 765 766 // КРУЖЧИЊА НАМЕСТО ТОЧКИ 767 draw_colon_rounded(CX, centerY, height); 768 769 roundedDigit(minute/10, cxM1, centerY, height); 770 roundedDigit(minute%10, cxM2, centerY, height); 771} 772void drawDateDDMM_VectorThin(int day, int month, int centerY, int height) 773{ 774 // digit "visual width" used by thinDigit() 775 int w = (int)(height * 0.48f); 776 int digitStep = w + (int)(height * 0.12f); 777 778 // bigger separation between DD and MM (you asked "a little further apart") 779 int colonGap = (int)(height * 0.65f); 780 781 // positions (FORCED symmetry around CX) 782 int cxD1 = CX - (digitStep + colonGap); 783 int cxD2 = CX - (colonGap); 784 int cxM1 = CX + (colonGap); 785 int cxM2 = CX + (digitStep + colonGap); 786 787 // draw digits 788 roundedDigit((day / 10) % 10, cxD1, centerY, height); 789 roundedDigit(day % 10, cxD2, centerY, height); 790 791 // dots: SAME X as time (CX), and a bit higher as you requested 792 int colonCenterY = centerY - (int)(height * 0.08f); 793 int dotOff = (int)(height * 0.18f); 794 glow_putpix(CX, colonCenterY - dotOff); 795 glow_putpix(CX, colonCenterY + dotOff); 796 797 roundedDigit((month / 10) % 10, cxM1, centerY, height); 798 roundedDigit(month % 10, cxM2, centerY, height); 799} 800 801void draw_mode1_digital_clock(const tm &t) 802{ 803 clearFB(); 804 805 // DRAW GRID FIRST (as background layer) 806 draw_oscilloscope_grid(); 807 808 // ----- Day name (top) ----- 809 static const char* days[] = 810 { 811 "SUNDAY", "MONDAY", "TUESDAY", 812 "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY" 813 }; 814 815 int dayY = CY - 150; // same vertical region you already use 816 int dayH = 50; // thin size 817 818 drawWeekday_VectorThin(days[t.tm_wday], dayY, dayH); 819 820 // ----- Time HH:MM (center) ----- 821 drawTimeHHMM_VectorThin(t.tm_hour, t.tm_min, CY - 10, 150); 822 823 // ----- Date (thin CRT, numeric) ----- 824 drawDateDDMM_VectorThin( 825 t.tm_mday, 826 t.tm_mon + 1, 827 (CY - 10) + 160, // +10 / -10 можеш тука да си играш 828 70 // height на цифри (препорачано 60–80) 829 ); 830 831 gfx->draw16bitRGBBitmap(0, 0, fb, DISPLAY_WIDTH, DISPLAY_HEIGHT); 832} 833 834// ------------------------------------------------------------- 835// DRAW FRAME: PICK MODE 836// ------------------------------------------------------------- 837void drawClockFrame() { 838 struct tm timeinfo; 839 if (!getLocalTime(&timeinfo)) { 840 return; 841 } 842 843 if (clockMode == 0) 844 draw_mode0_osc_clock(timeinfo); 845 else 846 draw_mode1_digital_clock(timeinfo); 847} 848 849// ------------------------------------------------------------- 850// DISPLAY INIT – SAME STYLE AS YOUR WORKING PANEL 851// ------------------------------------------------------------- 852extern const uint8_t st7701_type7_init_operations[]; 853 854void init_display() { 855 pinMode(BL_PIN, OUTPUT); 856 digitalWrite(BL_PIN, HIGH); 857 858 // исто како во твојот 0.6A 859 panelBus = new Arduino_SWSPI( 860 GFX_NOT_DEFINED, PANEL_CS, PANEL_SCK, PANEL_SDA, GFX_NOT_DEFINED 861 ); 862 863 rgbpanel = new Arduino_ESP32RGBPanel( 864 40,7,15,41, 865 46,3,8,18,17, 866 14,13,12,11,10,9, 867 5,45,48,47,21, 868 1,50,10,50, 869 1,30,10,30, 870 PCLK_NEG,8000000UL 871 ); 872 873 // КЛУЧОТ: користи го ST7701 TYPE7 init од библиотеката 874 gfx = new Arduino_RGB_Display( 875 DISPLAY_WIDTH, DISPLAY_HEIGHT, rgbpanel, 0, true, 876 panelBus, GFX_NOT_DEFINED, 877 st7701_type7_init_operations, sizeof(st7701_type7_init_operations) 878 ); 879 880 gfx->begin(16000000); 881 882 fb = (uint16_t*)ps_malloc(DISPLAY_WIDTH * DISPLAY_HEIGHT * 2); 883} 884 885// ------------------------------------------------------------- 886// PCF8574: INIT + READ + BUTTON 887// ------------------------------------------------------------- 888void pcf8574_init() { 889 Wire.begin(I2C_SDA, I2C_SCL); 890 Wire.beginTransmission(PCF8574_ADDR); 891 Wire.write(0xFF); // all high → inputs with weak pull-ups 892 Wire.endTransmission(); 893} 894 895uint8_t pcf8574_read() { 896 Wire.requestFrom(PCF8574_ADDR, (uint8_t)1); 897 if (Wire.available()) 898 return Wire.read(); 899 return 0xFF; 900} 901 902bool isEncoderButtonPressed() { 903 uint8_t state = pcf8574_read(); 904 return !(state & (1 << 5)); // P5 low = pressed 905} 906 907// ------------------------------------------------------------- 908// ROTARY ENCODER ISR + HANDLER 909// ------------------------------------------------------------- 910void IRAM_ATTR updateEncoder() { 911 int MSB = digitalRead(ENCODER_CLK); 912 int LSB = digitalRead(ENCODER_DT); 913 914 int encoded = (MSB << 1) | LSB; 915 int sum = (lastEncoded << 2) | encoded; 916 917 if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) 918 encoderValue++; 919 if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) 920 encoderValue--; 921 922 lastEncoded = encoded; 923} 924 925bool handleEncoderAndButtons() { 926 bool needRedraw = false; 927 928 noInterrupts(); 929 long currentValue = encoderValue; 930 interrupts(); 931 932 if (currentValue != lastEncoderValue && clockMode == 0) { 933 long diff = currentValue - lastEncoderValue; 934 zoomOffset += (int)diff; 935 if (zoomOffset < -40) zoomOffset = -40; 936 if (zoomOffset > 40) zoomOffset = 40; 937 lastEncoderValue = currentValue; 938 needRedraw = true; 939 } 940 941 bool pressed = isEncoderButtonPressed(); 942 unsigned long nowMs = millis(); 943 944 if (pressed && !lastButtonPressed && (nowMs - lastBtnChangeMs) > 200) { 945 clockMode ^= 1; // toggle 0/1 946 lastBtnChangeMs = nowMs; 947 needRedraw = true; 948 } 949 950 lastButtonPressed = pressed; 951 return needRedraw; 952} 953 954// ------------------------------------------------------------- 955// WIFI + NTP INIT 956// ------------------------------------------------------------- 957void connectWiFiAndTime() { 958 WiFi.mode(WIFI_STA); 959 WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 960 Serial.print("Connecting to WiFi"); 961 while (WiFi.status() != WL_CONNECTED) { 962 Serial.print("."); 963 delay(500); 964 } 965 Serial.println("\nWiFi connected"); 966 967 configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); 968 969 struct tm timeinfo; 970 for (int i = 0; i < 30; i++) { 971 if (getLocalTime(&timeinfo)) { 972 Serial.println("Time acquired from NTP"); 973 return; 974 } 975 delay(500); 976 } 977 Serial.println("Failed to get time from NTP (will still try in loop)."); 978} 979 980// ------------------------------------------------------------- 981// SETUP / LOOP 982// ------------------------------------------------------------- 983void setup() { 984 Serial.begin(115200); 985 986 pinMode(ENCODER_CLK, INPUT_PULLUP); 987 pinMode(ENCODER_DT, INPUT_PULLUP); 988 989 attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), updateEncoder, CHANGE); 990 attachInterrupt(digitalPinToInterrupt(ENCODER_DT), updateEncoder, CHANGE); 991 992 init_display(); 993 pcf8574_init(); 994 connectWiFiAndTime(); 995 996 clearFB(); 997 drawClockFrame(); 998} 999 1000void loop() { 1001 bool needRedraw = false; 1002 1003 if (handleEncoderAndButtons()) { 1004 needRedraw = true; 1005 } 1006 1007 time_t nowSec = time(nullptr); 1008 if (nowSec != lastSecond && nowSec > 100000) { 1009 lastSecond = nowSec; 1010 needRedraw = true; 1011 } 1012 1013 if (needRedraw) { 1014 drawClockFrame(); 1015 } 1016 1017 delay(10); 1018}
Code B
cpp
line
1/*============================================================== 2 TWO-MODE NTP CLOCK – CrowPanel 2.1" ESP32-S3 3 + OSCILLOSCOPE GRID OVERLAY (5x5 with faint yellow dots) 4 5 MODE 0: Analog oscilloscope clock (zoom with encoder) 6 MODE 1: Digital clock (Day / HH:MM / Date) with thin CRT lines 7 8 - Rotary encoder CLK/DT: zoom in MODE 0 9 - Encoder button on PCF8574 P5: toggle MODE 0 / MODE 1 10 - Time from NTP 11 12 by mircemk , Feb 2026 13==============================================================*/ 14 15#include <Arduino.h> 16#include <Arduino_GFX_Library.h> 17#include <WiFi.h> 18#include <time.h> 19#include <Wire.h> 20 21// ------------------------------------------------------------- 22// DISPLAY CONSTANTS 23// ------------------------------------------------------------- 24#define DISPLAY_WIDTH 480 25#define DISPLAY_HEIGHT 480 26#define CX 240 27#define CY 240 28 29// -------- PANEL PINS (CrowPanel 2.1") -------- 30#define TYPE_SEL 7 31#define PCLK_NEG 1 32#define BL_PIN 6 33#define PANEL_CS 16 34#define PANEL_SCK 2 35#define PANEL_SDA 1 36 37// Rotary Encoder pins 38#define ENCODER_CLK 4 39#define ENCODER_DT 42 40// Encoder button on PCF8574 P5 41 42// I2C for PCF8574 43#define I2C_SDA 38 44#define I2C_SCL 39 45#define PCF8574_ADDR 0x21 46 47// ------------------------------------------------------------- 48// WIFI / NTP CONFIG 49// ------------------------------------------------------------- 50const char* WIFI_SSID = "Infobiro"; 51const char* WIFI_PASSWORD = "CfmaWmsIb1991!"; 52 53const long gmtOffset_sec = 3600; // UTC+1 54const int daylightOffset_sec = 3600; // DST +1h 55const char* ntpServer = "pool.ntp.org"; 56 57// ------------------------------------------------------------- 58// FRAMEBUFFER + DISPLAY OBJECTS 59// ------------------------------------------------------------- 60Arduino_DataBus *panelBus = nullptr; 61Arduino_ESP32RGBPanel *rgbpanel = nullptr; 62Arduino_RGB_Display *gfx = nullptr; 63uint16_t *fb = nullptr; 64 65// ------------- COLORS ------------- 66const uint16_t COL_BG = 0x0000; // black 67const uint16_t COL_GREEN = 0x07FF; //0x07E0; // bright green 68const uint16_t COL_GLOW = 0x03E0; //0x01E0; // faint glow 69const uint16_t COL_GRID = 0x5240; //0x4a20; //0x4200; //0x39c0; // faint yellow 70// ---------------------------------- 71 72// ------------------------------------------------------------- 73// ROTARY ENCODER + MODE STATE 74// ------------------------------------------------------------- 75volatile int lastEncoded = 0; 76volatile long encoderValue = 0; 77long lastEncoderValue = 0; 78 79int zoomOffset = 0; // radius offset for analog clock 80int clockMode = 0; // 0 = analog, 1 = digital 81 82bool lastButtonPressed = false; 83unsigned long lastBtnChangeMs = 0; 84 85// ------------------------------------------------------------- 86// TIME STATE 87// ------------------------------------------------------------- 88time_t lastSecond = 0; 89 90// ------------------------------------------------------------- 91// PIXEL HELPERS + GLOW 92// ------------------------------------------------------------- 93static inline void putpix(int x, int y, uint16_t c) { 94 if ((unsigned)x < DISPLAY_WIDTH && (unsigned)y < DISPLAY_HEIGHT) 95 fb[y * DISPLAY_WIDTH + x] = c; 96} 97 98void glow_putpix(int x, int y) 99{ 100 putpix(x, y, COL_GREEN); // main pixel 101 102 putpix(x+1, y, COL_GLOW); 103 putpix(x-1, y, COL_GLOW); 104 putpix(x, y+1, COL_GLOW); 105 putpix(x, y-1, COL_GLOW); 106 107 putpix(x+1, y+1, COL_GLOW); 108 putpix(x-1, y+1, COL_GLOW); 109 putpix(x+1, y-1, COL_GLOW); 110 putpix(x-1, y-1, COL_GLOW); 111} 112 113void draw_line(int x0,int y0,int x1,int y1,uint16_t col){ 114 int dx=abs(x1-x0), sx=x0<x1?1:-1; 115 int dy=-abs(y1-y0), sy=y0<y1?1:-1; 116 int err=dx+dy, e2; 117 118 for(;;){ 119 glow_putpix(x0, y0); 120 if(x0==x1 && y0==y1) break; 121 e2 = 2 * err; 122 if(e2 >= dy){ err += dy; x0 += sx; } 123 if(e2 <= dx){ err += dx; y0 += sy; } 124 } 125} 126 127void clearFB() 128{ 129 memset(fb, 0, DISPLAY_WIDTH * DISPLAY_HEIGHT * 2); 130} 131 132// ------------------------------------------------------------- 133// OSCILLOSCOPE GRID DRAWING 134// ------------------------------------------------------------- 135void draw_oscilloscope_grid() 136{ 137 // 5x5 major divisions (96 pixels each) 138 const int divisions = 5; 139 const int spacing = DISPLAY_WIDTH / divisions; // 96 pixels 140 141 // Draw grid with dots instead of lines 142 for (int i = 0; i <= divisions; i++) { 143 int pos = i * spacing; 144 145 // Vertical grid lines (as dots) 146 for (int y = 0; y < DISPLAY_HEIGHT; y += 4) { 147 putpix(pos, y, COL_GRID); 148 } 149 150 // Horizontal grid lines (as dots) 151 for (int x = 0; x < DISPLAY_WIDTH; x += 4) { 152 putpix(x, pos, COL_GRID); 153 } 154 } 155 156 // Add tick marks on center axes (X and Y) 157 const int tickSize = 6; 158 const int tickSpacing = 12; // Small divisions 159 160 // X-axis tick marks (vertical center line) 161 for (int x = 0; x < DISPLAY_WIDTH; x += tickSpacing) { 162 for (int ty = -tickSize; ty <= tickSize; ty++) { 163 putpix(x, CY + ty, COL_GRID); 164 } 165 } 166 167 // Y-axis tick marks (horizontal center line) 168 for (int y = 0; y < DISPLAY_HEIGHT; y += tickSpacing) { 169 for (int tx = -tickSize; tx <= tickSize; tx++) { 170 putpix(CX + tx, y, COL_GRID); 171 } 172 } 173} 174 175// ------------------------------------------------------------- 176// RETRO DIGIT BITMAP FONT (од твојот 0.6A MODE 0) 177// ------------------------------------------------------------- 178const uint8_t RETRO_DIGIT[10][12] PROGMEM = { 179 {0b00111100,0b01000010,0b10000001,0b10000001,0b10000001,0b10000001,0b10000001,0b10000001,0b10000001,0b01000010,0b00111100,0}, 180 {0b00010000,0b00110000,0b01010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0}, 181 {0b00111100,0b01000010,0b00000010,0b00000100,0b00001000,0b00010000,0b00100000,0b01000000,0b10000000,0b10000000,0b11111110,0}, 182 {0b00111100,0b01000010,0b00000010,0b00000010,0b00111100,0b00000010,0b00000010,0b00000010,0b00000010,0b01000010,0b00111100,0}, 183 {0b00001000,0b00011000,0b00101000,0b01001000,0b10001000,0b11111110,0b00001000,0b00001000,0b00001000,0b00001000,0b00001000,0}, 184 {0b01111110,0b01000000,0b01000000,0b01000000,0b01111100,0b00000010,0b00000010,0b00000010,0b00000010,0b01000100,0b00111000,0}, 185 {0b00111100,0b01000010,0b10000000,0b10000000,0b11111100,0b10000010,0b10000010,0b10000010,0b10000010,0b01000010,0b00111100,0}, 186 {0b11111110,0b00000010,0b00000100,0b00001000,0b00010000,0b00010000,0b00100000,0b00100000,0b01000000,0b01000000,0b01000000,0}, 187 {0b00111100,0b01000010,0b10000001,0b10000001,0b01000010,0b00111100,0b01000010,0b10000001,0b10000001,0b01000010,0b00111100,0}, 188 {0b00111100,0b01000010,0b10000001,0b10000001,0b01000011,0b00111101,0b00000001,0b00000001,0b00000010,0b01000100,0b00111000,0} 189}; 190 191const int GLYPH_W = 8; 192const int GLYPH_H = 12; 193 194// ---- Digit drawing with glow (your original) ---- 195void drawDigitsString(const char *text, int x, int y, int scale) 196{ 197 int len = strlen(text); 198 199 for(int d=0; d<len; d++){ 200 char c = text[d]; 201 202 if(c >= '0' && c <= '9'){ 203 int digit = c - '0'; 204 const uint8_t *glyph = RETRO_DIGIT[digit]; 205 int x0 = x + d*(GLYPH_W*scale + scale); 206 207 for(int row=0; row<GLYPH_H; row++){ 208 uint8_t bits = pgm_read_byte(&glyph[row]); 209 for(int col=0; col<GLYPH_W; col++){ 210 if(bits & (0x80 >> col)){ 211 for(int sx=0; sx<scale; sx++) 212 for(int sy=0; sy<scale; sy++){ 213 glow_putpix(x0 + col*scale + sx, y + row*scale + sy); 214 } 215 } 216 } 217 } 218 } 219 220 if(c=='.'){ 221 int dotSize = 2*scale; 222 int bx = x + d*(GLYPH_W*scale+scale) + GLYPH_W*scale/2 - dotSize/2; 223 int by = y + GLYPH_H*scale - dotSize - scale; 224 225 for(int yy=0; yy<dotSize; yy++) 226 for(int xx=0; xx<dotSize; xx++) 227 glow_putpix(bx+xx, by+yy); 228 } 229 } 230} 231 232// ------------------------------------------------------------- 233// MODE 0 – ANALOG OSCILLOSCOPE CLOCK (од V0.6A, логика) 234// ------------------------------------------------------------- 235void drawDialNumber(int num, float angleDeg, int radius, int scale) 236{ 237 char buf[4]; 238 sprintf(buf,"%d",num); 239 240 float a = angleDeg * PI / 180.0f; 241 int x = CX + (int)(radius * sin(a)); 242 int y = CY - (int)(radius * cos(a)); 243 244 int w = GLYPH_W * scale; 245 drawDigitsString(buf, x - w/2, y - (GLYPH_H*scale)/2, scale); 246} 247 248void draw_ring(int cx,int cy,int rOuter,int thickness,uint16_t col) 249{ 250 int rInner = rOuter - thickness; 251 int ro2 = rOuter*rOuter; 252 int ri2 = rInner*rInner; 253 254 for(int y=-rOuter; y<=rOuter; y++){ 255 for(int x=-rOuter; x<=rOuter; x++){ 256 int rr = x*x + y*y; 257 if(rr<=ro2 && rr>=ri2){ 258 glow_putpix(cx+x, cy+y); 259 } 260 } 261 } 262} 263 264void draw_clock_face_osc(int zoom) 265{ 266 clearFB(); 267 268 // DRAW GRID FIRST (as background layer) 269 draw_oscilloscope_grid(); 270 271 int R_OUT = 225 + zoom; 272 if(R_OUT < 180) R_OUT = 180; 273 if(R_OUT > 240) R_OUT = 240; 274 275 const int SHORT_LEN = 20; 276 const int LONG_LEN = 32; 277 278 for(int i=0;i<60;i++){ 279 float a = i*6.0f * PI/180.0f; 280 281 bool isHour = (i % 5 == 0); 282 int len = isHour ? LONG_LEN : SHORT_LEN; 283 int thickness = isHour ? 4 : 2; 284 int half = thickness/2; 285 286 int x1 = CX + (int)((R_OUT - len)*sin(a)); 287 int y1 = CY - (int)((R_OUT - len)*cos(a)); 288 int x2 = CX + (int)(R_OUT*sin(a)); 289 int y2 = CY - (int)(R_OUT*cos(a)); 290 291 float dx = x2 - x1; 292 float dy = y2 - y1; 293 294 float px = -dy; 295 float py = dx; 296 297 float inv = 1.0f / sqrt(px*px + py*py); 298 px *= inv; 299 py *= inv; 300 301 for(int k=-half; k<=half; k++){ 302 int ox = (int)(px*k); 303 int oy = (int)(py*k); 304 draw_line(x1+ox, y1+oy, x2+ox, y2+oy, COL_GREEN); 305 } 306 } 307 308 int numRadius = R_OUT - 57; 309 for(int n=1;n<=12;n++) 310 drawDialNumber(n, n*30.0f, numRadius, 3); 311 312 // пример дата 313 drawDigitsString("10.12.2008", CX-80, CY+60, 2); 314 315 draw_ring(CX, CY, 10, 2, COL_GREEN); 316} 317 318void draw_clock_hands_osc(int hour,int minute,int second,int zoom) 319{ 320 float secA = second * 6.0f * PI/180.0f; 321 float minA = (minute + second/60.0f)*6.0f * PI/180.0f; 322 float hourA = (hour%12 + minute/60.0f)*30.0f * PI/180.0f; 323 324 const int HUB_R = 10; 325 int R_H = 120 + (zoom/3); 326 int R_M = 175 + (zoom/3); 327 int R_S = 190 + (zoom/3); 328 329 const int W_H = 8; 330 const int W_M = 6; 331 332 { // Hour hand 333 float s = sin(hourA), c = cos(hourA); 334 335 int tipX = CX + (int)(R_H*s); 336 int tipY = CY - (int)(R_H*c); 337 338 int leftX = CX + (int)( HUB_R*s + W_H*c ); 339 int leftY = CY - (int)( HUB_R*c - W_H*s ); 340 341 int rightX = CX + (int)( HUB_R*s - W_H*c ); 342 int rightY = CY - (int)( HUB_R*c + W_H*s ); 343 344 draw_line(leftX, leftY, tipX, tipY, COL_GREEN); 345 draw_line(rightX, rightY, tipX, tipY, COL_GREEN); 346 } 347 348 { // Minute hand 349 float s = sin(minA), c = cos(minA); 350 351 int tipX = CX + (int)(R_M*s); 352 int tipY = CY - (int)(R_M*c); 353 354 int leftX = CX + (int)( HUB_R*s + W_M*c ); 355 int leftY = CY - (int)( HUB_R*c - W_M*s ); 356 357 int rightX = CX + (int)( HUB_R*s - W_M*c ); 358 int rightY = CY - (int)( HUB_R*c + W_M*s ); 359 360 draw_line(leftX, leftY, tipX, tipY, COL_GREEN); 361 draw_line(rightX, rightY, tipX, tipY, COL_GREEN); 362 } 363 364 { // Second hand 365 float s = sin(secA), c = cos(secA); 366 int tipX = CX + (int)(R_S*s); 367 int tipY = CY - (int)(R_S*c); 368 draw_line(CX, CY, tipX, tipY, COL_GREEN); 369 } 370 371 draw_ring(CX, CY, HUB_R, 2, COL_GREEN); 372} 373 374void draw_mode0_osc_clock(const tm &t) 375{ 376 draw_clock_face_osc(zoomOffset); 377 draw_clock_hands_osc(t.tm_hour, t.tm_min, t.tm_sec, zoomOffset); 378 gfx->draw16bitRGBBitmap(0,0,fb,DISPLAY_WIDTH,DISPLAY_HEIGHT); 379} 380 381 382 383 384// ------------------------------------------------------------- 385// 5x7 LETTER FONT (A–Z) FOR DAY/DATE 386// ------------------------------------------------------------- 387const uint8_t FONT5x7[26][7] PROGMEM = { 388 {0b01110,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001}, // A 389 {0b11110,0b10001,0b10001,0b11110,0b10001,0b10001,0b11110}, // B 390 {0b01110,0b10001,0b10000,0b10000,0b10000,0b10001,0b01110}, // C 391 {0b11100,0b10010,0b10001,0b10001,0b10001,0b10010,0b11100}, // D 392 {0b11111,0b10000,0b10000,0b11110,0b10000,0b10000,0b11111}, // E 393 {0b11111,0b10000,0b10000,0b11110,0b10000,0b10000,0b10000}, // F 394 {0b01110,0b10001,0b10000,0b10111,0b10001,0b10001,0b01110}, // G 395 {0b10001,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001}, // H 396 {0b01110,0b00100,0b00100,0b00100,0b00100,0b00100,0b01110}, // I 397 {0b00111,0b00010,0b00010,0b00010,0b10010,0b10010,0b01100}, // J 398 {0b10001,0b10010,0b10100,0b11000,0b10100,0b10010,0b10001}, // K 399 {0b10000,0b10000,0b10000,0b10000,0b10000,0b10000,0b11111}, // L 400 {0b10001,0b11011,0b10101,0b10101,0b10001,0b10001,0b10001}, // M 401 {0b10001,0b10001,0b11001,0b10101,0b10011,0b10001,0b10001}, // N 402 {0b01110,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110}, // O 403 {0b11110,0b10001,0b10001,0b11110,0b10000,0b10000,0b10000}, // P 404 {0b01110,0b10001,0b10001,0b10001,0b10101,0b10010,0b01101}, // Q 405 {0b11110,0b10001,0b10001,0b11110,0b10100,0b10010,0b10001}, // R 406 {0b01111,0b10000,0b10000,0b01110,0b00001,0b00001,0b11110}, // S 407 {0b11111,0b00100,0b00100,0b00100,0b00100,0b00100,0b00100}, // T 408 {0b10001,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110}, // U 409 {0b10001,0b10001,0b10001,0b01010,0b01010,0b00100,0b00100}, // V 410 {0b10001,0b10001,0b10101,0b10101,0b10101,0b11011,0b10001}, // W 411 {0b10001,0b01010,0b00100,0b00100,0b00100,0b01010,0b10001}, // X 412 {0b10001,0b01010,0b00100,0b00100,0b00100,0b00100,0b00100}, // Y 413 {0b11111,0b00001,0b00010,0b00100,0b01000,0b10000,0b11111} // Z 414}; 415 416int textWidth5x7(const char *s, int scale) { 417 int len = strlen(s); 418 if (len == 0) return 0; 419 int charW = 5 * scale; 420 int gap = scale; 421 return len * (charW + gap) - gap; 422} 423 424// ------------------------------------------------------------- 425// MODE 1 – THIN CRT-STYLE DIGITAL CLOCK (Day / HH:MM / Date) 426// ------------------------------------------------------------- 427// ------------------------------------------------------------- 428// THIN VECTOR CRT DIGITS – improved spacing 429// ------------------------------------------------------------- 430 431void thinDigit(int d, int cx, int cy, int h) 432{ 433 int w = h * 0.48; // little narrower 434 int top = cy - h/2; 435 int bot = cy + h/2; 436 int mid = cy; 437 438 switch(d) 439 { 440 case 0: 441 draw_line(cx-w/2, top, cx+w/2, top, COL_GREEN); 442 draw_line(cx-w/2, bot, cx+w/2, bot, COL_GREEN); 443 draw_line(cx-w/2, top, cx-w/2, bot, COL_GREEN); 444 draw_line(cx+w/2, top, cx+w/2, bot, COL_GREEN); 445 break; 446 447 case 1: 448 draw_line(cx, top, cx, bot, COL_GREEN); 449 break; 450 451 case 2: 452 draw_line(cx-w/2, top, cx+w/2, top, COL_GREEN); 453 draw_line(cx+w/2, top, cx+w/2, mid, COL_GREEN); 454 draw_line(cx+w/2, mid, cx-w/2, mid, COL_GREEN); 455 draw_line(cx-w/2, mid, cx-w/2, bot, COL_GREEN); 456 draw_line(cx-w/2, bot, cx+w/2, bot, COL_GREEN); 457 break; 458 459 case 3: 460 draw_line(cx-w/2, top, cx+w/2, top, COL_GREEN); 461 draw_line(cx+w/2, top, cx+w/2, mid, COL_GREEN); 462 draw_line(cx-w/2, mid, cx+w/2, mid, COL_GREEN); 463 draw_line(cx+w/2, mid, cx+w/2, bot, COL_GREEN); 464 draw_line(cx-w/2, bot, cx+w/2, bot, COL_GREEN); 465 break; 466 467 case 4: 468 draw_line(cx-w/2, top, cx-w/2, mid, COL_GREEN); 469 draw_line(cx-w/2, mid, cx+w/2, mid, COL_GREEN); 470 draw_line(cx+w/2, top, cx+w/2, bot, COL_GREEN); 471 break; 472 473 case 5: 474 draw_line(cx+w/2, top, cx-w/2, top, COL_GREEN); 475 draw_line(cx-w/2, top, cx-w/2, mid, COL_GREEN); 476 draw_line(cx-w/2, mid, cx+w/2, mid, COL_GREEN); 477 draw_line(cx+w/2, mid, cx+w/2, bot, COL_GREEN); 478 draw_line(cx-w/2, bot, cx+w/2, bot, COL_GREEN); 479 break; 480 481 case 6: 482 draw_line(cx+w/2, top, cx-w/2, top, COL_GREEN); 483 draw_line(cx-w/2, top, cx-w/2, bot, COL_GREEN); 484 draw_line(cx-w/2, mid, cx+w/2, mid, COL_GREEN); 485 draw_line(cx-w/2, bot, cx+w/2, bot, COL_GREEN); 486 // ➕ FIX: missing right-side vertical 487 draw_line(cx+w/2, mid, cx+w/2, bot, COL_GREEN); 488 break; 489 490 case 7: 491 draw_line(cx-w/2, top, cx+w/2, top, COL_GREEN); 492 draw_line(cx+w/2, top, cx+w/2, bot, COL_GREEN); 493 break; 494 495 case 8: 496 thinDigit(0, cx, cy, h); 497 draw_line(cx-w/2, mid, cx+w/2, mid, COL_GREEN); 498 break; 499 500 case 9: 501 thinDigit(3, cx, cy, h); // reuse perfect 3 shape 502 draw_line(cx-w/2, top, cx+w/2, top, COL_GREEN); 503 // ➕ FIX: missing left upper vertical 504 draw_line(cx-w/2, top, cx-w/2, mid, COL_GREEN); 505 break; 506 } 507} 508 509void thinLetter(char c, int cx, int cy, int h) 510{ 511 int w = h * 0.45; 512 int top = cy - h/2; 513 int bot = cy + h/2; 514 int mid = cy; 515 516 switch (c) 517 { 518 case 'O': // Додадено за Monday, Tuesday... 519 case '0': 520 draw_line(cx - w / 2, top, cx + w / 2, top, COL_GREEN); // горе 521 draw_line(cx - w / 2, bot, cx + w / 2, bot, COL_GREEN); // долу 522 draw_line(cx - w / 2, top, cx - w / 2, bot, COL_GREEN); // лево 523 draw_line(cx + w / 2, top, cx + w / 2, bot, COL_GREEN); // десно 524 break; 525 526 case 'A': 527 draw_line(cx-w/2, bot, cx, top, COL_GREEN); 528 draw_line(cx, top, cx+w/2, bot, COL_GREEN); 529 draw_line(cx-w/4, mid, cx+w/4, mid, COL_GREEN); 530 break; 531 532 case 'D': 533 draw_line(cx-w/2, top, cx-w/2, bot, COL_GREEN); 534 draw_line(cx-w/2, top, cx+w/3, mid, COL_GREEN); 535 draw_line(cx+w/3, mid, cx-w/2, bot, COL_GREEN); 536 break; 537 538 case 'F': 539 draw_line(cx-w/2, top, cx-w/2, bot, COL_GREEN); 540 draw_line(cx-w/2, top, cx+w/2, top, COL_GREEN); 541 draw_line(cx-w/2, mid, cx+w/4, mid, COL_GREEN); 542 break; 543 544 case 'I': 545 draw_line(cx, top, cx, bot, COL_GREEN); 546 break; 547 548 case 'M': 549 draw_line(cx-w/2, bot, cx-w/2, top, COL_GREEN); 550 draw_line(cx-w/2, top, cx, mid, COL_GREEN); 551 draw_line(cx, mid, cx+w/2, top, COL_GREEN); 552 draw_line(cx+w/2, top, cx+w/2, bot, COL_GREEN); 553 break; 554 555 case 'N': 556 draw_line(cx-w/2, bot, cx-w/2, top, COL_GREEN); 557 draw_line(cx-w/2, top, cx+w/2, bot, COL_GREEN); 558 draw_line(cx+w/2, top, cx+w/2, bot, COL_GREEN); 559 break; 560 561 case 'R': 562 draw_line(cx-w/2, bot, cx-w/2, top, COL_GREEN); 563 draw_line(cx-w/2, top, cx+w/3, mid, COL_GREEN); 564 draw_line(cx+w/3, mid, cx-w/2, mid, COL_GREEN); 565 draw_line(cx-w/2, mid, cx+w/2, bot, COL_GREEN); 566 break; 567 568 case 'S': 569 draw_line(cx+w/2, top, cx-w/2, top, COL_GREEN); 570 draw_line(cx-w/2, top, cx-w/2, mid, COL_GREEN); 571 draw_line(cx-w/2, mid, cx+w/2, mid, COL_GREEN); 572 draw_line(cx+w/2, mid, cx+w/2, bot, COL_GREEN); 573 draw_line(cx+w/2, bot, cx-w/2, bot, COL_GREEN); 574 break; 575 576 case 'T': 577 draw_line(cx-w/2, top, cx+w/2, top, COL_GREEN); 578 draw_line(cx, top, cx, bot, COL_GREEN); 579 break; 580 581 case 'U': 582 draw_line(cx-w/2, top, cx-w/2, bot, COL_GREEN); 583 draw_line(cx+w/2, top, cx+w/2, bot, COL_GREEN); 584 draw_line(cx-w/2, bot, cx+w/2, bot, COL_GREEN); 585 break; 586 587 case 'E': 588 draw_line(cx-w/2, top, cx-w/2, bot, COL_GREEN); // лева вертикала 589 draw_line(cx-w/2, top, cx+w/2, top, COL_GREEN); // горна хоризонтала 590 draw_line(cx-w/2, mid, cx+w/4, mid, COL_GREEN); // средна хоризонтала 591 draw_line(cx-w/2, bot, cx+w/2, bot, COL_GREEN); // долна хоризонтала 592 break; 593 594 case 'Y': 595 draw_line(cx-w/2, top, cx, mid, COL_GREEN); 596 draw_line(cx+w/2, top, cx, mid, COL_GREEN); 597 draw_line(cx, mid, cx, bot, COL_GREEN); 598 break; 599 } 600} 601 602// ------------------------------------------------------------- 603// DRAW DAY OF THE WEEK (Vector Thin) - CENTERED 604// ------------------------------------------------------------- 605void drawWeekday_VectorThin(const char* name, int centerY, int height) { 606 int len = strlen(name); 607 int step = height * 0.6; 608 609 // 1. Calculate the character width 'w' (as defined in thinLetter: w = h * 0.45) 610 int w = (int)(height * 0.45); 611 612 // 2. Calculate the total visual width of the word. 613 // Total Width = (len - 1 gaps * step) + (1 character width) 614 int total_width = (len - 1) * step + w; 615 616 // 3. Calculate the X coordinate of the center of the first letter (startX). 617 // The goal is to set the center of the *entire word* at CX (240). 618 // startX = (Center of screen CX) - (Half of total width) + (Half of first character width) 619 // This correctly positions the center of the first letter. 620 int startX = CX - total_width / 2 + w / 2; 621 622 for (int i = 0; i < len; i++) { 623 thinLetter(name[i], startX + i * step, centerY, height); 624 } 625} 626 627 628void draw_colon_rounded(int cx, int cy, int height) { 629 int dotRadius = (int)(height * 0.04f); // радиус на кружчињата 630 int offset = (int)(height * 0.18f); // растојание од центарот 631 632 // Горно кружче 633 for(int angle = 0; angle < 360; angle += 8) { 634 float rad = angle * PI / 180.0f; 635 int x = cx + (int)(dotRadius * cos(rad)); 636 int y = (cy - offset) + (int)(dotRadius * sin(rad)); 637 glow_putpix(x, y); 638 } 639 640 // Долно кружче 641 for(int angle = 0; angle < 360; angle += 8) { 642 float rad = angle * PI / 180.0f; 643 int x = cx + (int)(dotRadius * cos(rad)); 644 int y = (cy + offset) + (int)(dotRadius * sin(rad)); 645 glow_putpix(x, y); 646 } 647} 648 649// ------------------------------------------------------------- 650// Draw HH:MM with improved spacing and adjusted position 651// ------------------------------------------------------------- 652void drawTimeHHMM_VectorThin(int hour, int minute, int centerY, int height) 653{ 654 // Constants for digit size and inner spacing 655 int w = height * 0.48; // Digit width (from thinDigit function) 656 int spacing = height * 0.1; // Reduced spacing between digits 657 658 // NEW OFFSET 659 const int OFFSET_X = 20; // Move hours left and minutes right by 20 pixels 660 661 // Digit centers calculation: 662 // 1. Calculate the base center positions (as before) 663 // 2. Apply the OFFSET_X 664 665 // Hour Digit 1 (e.g., '1' in 15:25) 666 // Base: CX - (1.5 * w + 1.5 * spacing) 667 // Adjusted: CX - (1.5 * w + 1.5 * spacing) - OFFSET_X 668 int cxH1 = CX - (w * 1.5 + spacing * 1.5) - OFFSET_X; 669 670 // Hour Digit 2 (e.g., '5' in 15:25) 671 // Base: CX - (0.5 * w + 0.5 * spacing) 672 // Adjusted: CX - (0.5 * w + 0.5 * spacing) - OFFSET_X 673 int cxH2 = CX - (w * 0.5 + spacing * 0.5) - OFFSET_X; 674 675 // Minute Digit 1 (e.g., '2' in 15:25) 676 // Base: CX + (0.5 * w + 0.5 * spacing) 677 // Adjusted: CX + (0.5 * w + 0.5 * spacing) + OFFSET_X 678 int cxM1 = CX + (w * 0.5 + spacing * 0.5) + OFFSET_X; 679 680 // Minute Digit 2 (e.g., '5' in 15:25) 681 // Base: CX + (1.5 * w + 1.5 * spacing) 682 // Adjusted: CX + (1.5 * w + 1.5 * spacing) + OFFSET_X 683 int cxM2 = CX + (w * 1.5 + spacing * 1.5) + OFFSET_X; 684 685 // Draw digits 686 thinDigit(hour/10, cxH1, centerY, height); 687 thinDigit(hour%10, cxH2, centerY, height); 688 689 // Colon (Stays centered at CX) 690 // glow_putpix(CX, centerY - height * 0.18); 691 // glow_putpix(CX, centerY + height * 0.18); 692 693 draw_colon_rounded(CX, centerY, height); 694 695 thinDigit(minute/10, cxM1, centerY, height); 696 thinDigit(minute%10, cxM2, centerY, height); 697} 698 699void drawDateDDMM_VectorThin(int day, int month, int centerY, int height) 700{ 701 // digit "visual width" used by thinDigit() 702 int w = (int)(height * 0.48f); 703 int digitStep = w + (int)(height * 0.12f); 704 705 // bigger separation between DD and MM (you asked "a little further apart") 706 int colonGap = (int)(height * 0.65f); 707 708 // positions (FORCED symmetry around CX) 709 int cxD1 = CX - (digitStep + colonGap); 710 int cxD2 = CX - (colonGap); 711 int cxM1 = CX + (colonGap); 712 int cxM2 = CX + (digitStep + colonGap); 713 714 // draw digits 715 thinDigit((day / 10) % 10, cxD1, centerY, height); 716 thinDigit(day % 10, cxD2, centerY, height); 717 718 // dots: SAME X as time (CX), and a bit higher as you requested 719 int colonCenterY = centerY - (int)(height * 0.08f); 720 int dotOff = (int)(height * 0.18f); 721 722// glow_putpix(CX, colonCenterY - dotOff); 723 // glow_putpix(CX, colonCenterY + dotOff); 724 725 draw_colon_rounded(CX, centerY, height); 726 727 thinDigit((month / 10) % 10, cxM1, centerY, height); 728 thinDigit(month % 10, cxM2, centerY, height); 729} 730 731void draw_mode1_digital_clock(const tm &t) 732{ 733 clearFB(); 734 735 // DRAW GRID FIRST (as background layer) 736 draw_oscilloscope_grid(); 737 738 // ----- Day name (top) ----- 739 static const char* days[] = 740 { 741 "SUNDAY", "MONDAY", "TUESDAY", 742 "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY" 743 }; 744 745 int dayY = CY - 150; // same vertical region you already use 746 int dayH = 50; // thin size 747 748 drawWeekday_VectorThin(days[t.tm_wday], dayY, dayH); 749 750 // ----- Time HH:MM (center) ----- 751 drawTimeHHMM_VectorThin(t.tm_hour, t.tm_min, CY - 10, 150); 752 753 // ----- Date (thin CRT, numeric) ----- 754 drawDateDDMM_VectorThin( 755 t.tm_mday, 756 t.tm_mon + 1, 757 (CY - 10) + 160, // +10 / -10 можеш тука да си играш 758 70 // height на цифри (препорачано 60–80) 759 ); 760 761 gfx->draw16bitRGBBitmap(0, 0, fb, DISPLAY_WIDTH, DISPLAY_HEIGHT); 762} 763 764// ------------------------------------------------------------- 765// DRAW FRAME: PICK MODE 766// ------------------------------------------------------------- 767void drawClockFrame() { 768 struct tm timeinfo; 769 if (!getLocalTime(&timeinfo)) { 770 return; 771 } 772 773 if (clockMode == 0) 774 draw_mode0_osc_clock(timeinfo); 775 else 776 draw_mode1_digital_clock(timeinfo); 777} 778 779// ------------------------------------------------------------- 780// DISPLAY INIT – SAME STYLE AS YOUR WORKING PANEL 781// ------------------------------------------------------------- 782extern const uint8_t st7701_type7_init_operations[]; 783 784void init_display() { 785 pinMode(BL_PIN, OUTPUT); 786 digitalWrite(BL_PIN, HIGH); 787 788 // исто како во твојот 0.6A 789 panelBus = new Arduino_SWSPI( 790 GFX_NOT_DEFINED, PANEL_CS, PANEL_SCK, PANEL_SDA, GFX_NOT_DEFINED 791 ); 792 793 rgbpanel = new Arduino_ESP32RGBPanel( 794 40,7,15,41, 795 46,3,8,18,17, 796 14,13,12,11,10,9, 797 5,45,48,47,21, 798 1,50,10,50, 799 1,30,10,30, 800 PCLK_NEG,8000000UL 801 ); 802 803 // КЛУЧОТ: користи го ST7701 TYPE7 init од библиотеката 804 gfx = new Arduino_RGB_Display( 805 DISPLAY_WIDTH, DISPLAY_HEIGHT, rgbpanel, 0, true, 806 panelBus, GFX_NOT_DEFINED, 807 st7701_type7_init_operations, sizeof(st7701_type7_init_operations) 808 ); 809 810 gfx->begin(16000000); 811 812 fb = (uint16_t*)ps_malloc(DISPLAY_WIDTH * DISPLAY_HEIGHT * 2); 813} 814 815// ------------------------------------------------------------- 816// PCF8574: INIT + READ + BUTTON 817// ------------------------------------------------------------- 818void pcf8574_init() { 819 Wire.begin(I2C_SDA, I2C_SCL); 820 Wire.beginTransmission(PCF8574_ADDR); 821 Wire.write(0xFF); // all high → inputs with weak pull-ups 822 Wire.endTransmission(); 823} 824 825uint8_t pcf8574_read() { 826 Wire.requestFrom(PCF8574_ADDR, (uint8_t)1); 827 if (Wire.available()) 828 return Wire.read(); 829 return 0xFF; 830} 831 832bool isEncoderButtonPressed() { 833 uint8_t state = pcf8574_read(); 834 return !(state & (1 << 5)); // P5 low = pressed 835} 836 837// ------------------------------------------------------------- 838// ROTARY ENCODER ISR + HANDLER 839// ------------------------------------------------------------- 840void IRAM_ATTR updateEncoder() { 841 int MSB = digitalRead(ENCODER_CLK); 842 int LSB = digitalRead(ENCODER_DT); 843 844 int encoded = (MSB << 1) | LSB; 845 int sum = (lastEncoded << 2) | encoded; 846 847 if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) 848 encoderValue++; 849 if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) 850 encoderValue--; 851 852 lastEncoded = encoded; 853} 854 855bool handleEncoderAndButtons() { 856 bool needRedraw = false; 857 858 noInterrupts(); 859 long currentValue = encoderValue; 860 interrupts(); 861 862 if (currentValue != lastEncoderValue && clockMode == 0) { 863 long diff = currentValue - lastEncoderValue; 864 zoomOffset += (int)diff; 865 if (zoomOffset < -40) zoomOffset = -40; 866 if (zoomOffset > 40) zoomOffset = 40; 867 lastEncoderValue = currentValue; 868 needRedraw = true; 869 } 870 871 bool pressed = isEncoderButtonPressed(); 872 unsigned long nowMs = millis(); 873 874 if (pressed && !lastButtonPressed && (nowMs - lastBtnChangeMs) > 200) { 875 clockMode ^= 1; // toggle 0/1 876 lastBtnChangeMs = nowMs; 877 needRedraw = true; 878 } 879 880 lastButtonPressed = pressed; 881 return needRedraw; 882} 883 884// ------------------------------------------------------------- 885// WIFI + NTP INIT 886// ------------------------------------------------------------- 887void connectWiFiAndTime() { 888 WiFi.mode(WIFI_STA); 889 WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 890 Serial.print("Connecting to WiFi"); 891 while (WiFi.status() != WL_CONNECTED) { 892 Serial.print("."); 893 delay(500); 894 } 895 Serial.println("\nWiFi connected"); 896 897 configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); 898 899 struct tm timeinfo; 900 for (int i = 0; i < 30; i++) { 901 if (getLocalTime(&timeinfo)) { 902 Serial.println("Time acquired from NTP"); 903 return; 904 } 905 delay(500); 906 } 907 Serial.println("Failed to get time from NTP (will still try in loop)."); 908} 909 910// ------------------------------------------------------------- 911// SETUP / LOOP 912// ------------------------------------------------------------- 913void setup() { 914 Serial.begin(115200); 915 916 pinMode(ENCODER_CLK, INPUT_PULLUP); 917 pinMode(ENCODER_DT, INPUT_PULLUP); 918 919 attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), updateEncoder, CHANGE); 920 attachInterrupt(digitalPinToInterrupt(ENCODER_DT), updateEncoder, CHANGE); 921 922 init_display(); 923 pcf8574_init(); 924 connectWiFiAndTime(); 925 926 clearFB(); 927 drawClockFrame(); 928} 929 930void loop() { 931 bool needRedraw = false; 932 933 if (handleEncoderAndButtons()) { 934 needRedraw = true; 935 } 936 937 time_t nowSec = time(nullptr); 938 if (nowSec != lastSecond && nowSec > 100000) { 939 lastSecond = nowSec; 940 needRedraw = true; 941 } 942 943 if (needRedraw) { 944 drawClockFrame(); 945 } 946 947 delay(10); 948}
Comments
Only logged in users can leave comments