edge lab LIVE! starts Tuesday in Munich. Grab a workshop seat and take home the board you build on. Code FRIEND-HACKSTER saves 20%. () Learn more!
John Bradnam
Published © GPL3+

Oscilloscope Clock Simulation V2

A 3D printed case and new software for the "Oscilloscope Clock Simulation on a Round ESP32 Display" project.

BeginnerFull instructions provided4 hours51
Oscilloscope Clock Simulation V2

Things used in this project

Hardware components

Elecrow CrowPanel 2.1inch-HMI ESP32 Rotary Display 480*480
×1
Mini360 DC-DC HM Buck Converter
Mini360 DC-DC HM Buck Converter Step Down Power Supply Module 4.75-23V to 1-17V 340KHz Mini-360 Ultra-small from Ali-Express
×1
DC Panel socket
×1
3A Diode
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

Files for 3D printing

Schematics

Parts required

Code

OscilloscopeClockArcsV2.ino

Arduino
/*==============================================================
   TWO-MODE NTP CLOCK  CrowPanel 2.1" ESP32-S3
   + OSCILLOSCOPE GRID OVERLAY (5x5 with faint yellow dots)

   MODE 0: Analog oscilloscope clock (zoom with encoder)
   MODE 1: Digital clock (Day / HH:MM / Date) with thin CRT lines

   - Rotary encoder CLK/DT: zoom in MODE 0
   - Encoder button on PCF8574 P5: toggle MODE 0 / MODE 1
   - Time from NTP

     by mircemk , Feb 2026 

   260825 jlb
     - Incorporated both line and arc modes
     - Changed configTime to configTzTime for POSIX timezone support
     - Fixed draw_clock_face_osc to display current dd.mm.yyyy
     - Allowed rotary control to set 16 levels of brightness (compiler define ROTARY_BRIGHTNESS)
   260827 jlb
     - Added support for 8x14 alphanumeric font to allow display of day of week, day and textual month
     - Enchanced rotary control to cycle through 6 colors with 16 levels of brightness each
     - Added support for letters W and H in thinLetter function
==============================================================*/

#include <Arduino.h>
#include <Arduino_GFX_Library.h>
#include <WiFi.h>
#include <time.h>
#include <Wire.h>

// -------------------------------------------------------------
// DISPLAY CONSTANTS
// -------------------------------------------------------------
#define DISPLAY_WIDTH  480
#define DISPLAY_HEIGHT 480
#define CX 240
#define CY 240

// -------- PANEL PINS (CrowPanel 2.1") --------
#define TYPE_SEL   7
#define PCLK_NEG   1
#define BL_PIN     6
#define PANEL_CS   16
#define PANEL_SCK  2
#define PANEL_SDA  1

// Rotary Encoder pins
#define ENCODER_CLK 4
#define ENCODER_DT  42
// Encoder button on PCF8574 P5

// I2C for PCF8574
#define I2C_SDA 38
#define I2C_SCL 39
#define PCF8574_ADDR 0x21

// -------------------------------------------------------------
// WIFI / NTP CONFIG
// -------------------------------------------------------------
const char* WIFI_SSID     = "ssid";
const char* WIFI_PASSWORD = "password";

const long gmtOffset_sec      = 36000;   // UTC+10
const int  daylightOffset_sec = 3600;   // DST +1h
const char* ntpServer         = "au.pool.ntp.org";

// -------------------------------------------------------------
// FRAMEBUFFER + DISPLAY OBJECTS
// -------------------------------------------------------------
Arduino_DataBus       *panelBus = nullptr;
Arduino_ESP32RGBPanel *rgbpanel = nullptr;
Arduino_RGB_Display   *gfx      = nullptr;
uint16_t              *fb       = nullptr;

// ------------- COLORS -------------
// Comment next line to have rotary encoder change zoom factor instead
#define ROTARY_BRIGHTNESS

#define BRIGHTNESS_MAX 111
uint16_t brightness = 12;
//Colors set in setBrightness function
volatile uint16_t primaryColor;
volatile uint16_t glowColor;
volatile uint16_t gridColor;

// ----------------------------------

// -------------------------------------------------------------
// ROTARY ENCODER + MODE STATE
// -------------------------------------------------------------
volatile int  lastEncoded      = 0;
volatile long encoderValue     = 0;
long          lastEncoderValue = 0;

int  zoomOffset        = 0;   // radius offset for analog clock
enum ClockEnum { CLOCK_ANALOG, CLOCK_ARC, CLOCK_LINE };
ClockEnum clockMode = CLOCK_ANALOG;

bool lastButtonPressed = false;
unsigned long lastBtnChangeMs  = 0;

// -------------------------------------------------------------
// TIME STATE
// -------------------------------------------------------------
time_t lastSecond = 0;

// ----- Day name (top) -----
static const char* days[] =
{
  "SUNDAY", "MONDAY", "TUESDAY",
  "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY"
};

// ----- Day name (top) -----
static const char* months[] =
{
  "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
  "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
};


// -------------------------------------------------------------
// PIXEL HELPERS + GLOW
// -------------------------------------------------------------
static inline void putpix(int x, int y, uint16_t c) {
  if ((unsigned)x < DISPLAY_WIDTH && (unsigned)y < DISPLAY_HEIGHT)
    fb[y * DISPLAY_WIDTH + x] = c;
}

void glow_putpix(int x, int y)
{
  putpix(x, y, primaryColor);      // main pixel

  putpix(x+1, y,   glowColor);
  putpix(x-1, y,   glowColor);
  putpix(x,   y+1, glowColor);
  putpix(x,   y-1, glowColor);

  putpix(x+1, y+1, glowColor);
  putpix(x-1, y+1, glowColor);
  putpix(x+1, y-1, glowColor);
  putpix(x-1, y-1, glowColor);
}

void draw_line(int x0,int y0,int x1,int y1,uint16_t col){
  int dx=abs(x1-x0), sx=x0<x1?1:-1;
  int dy=-abs(y1-y0), sy=y0<y1?1:-1;
  int err=dx+dy, e2;

  for(;;){
    glow_putpix(x0, y0);
    if(x0==x1 && y0==y1) break;
    e2 = 2 * err;
    if(e2 >= dy){ err += dy; x0 += sx; }
    if(e2 <= dx){ err += dx; y0 += sy; }
  }
}

void clearFB()
{
  memset(fb, 0, DISPLAY_WIDTH * DISPLAY_HEIGHT * 2);
}

// -------------------------------------------------------------
// OSCILLOSCOPE GRID DRAWING
// -------------------------------------------------------------
void draw_oscilloscope_grid()
{
  // 5x5 major divisions (96 pixels each)
  const int divisions = 5;
  const int spacing = DISPLAY_WIDTH / divisions; // 96 pixels
  
  // Draw grid with dots instead of lines
  for (int i = 0; i <= divisions; i++) {
    int pos = i * spacing;
    
    // Vertical grid lines (as dots)
    for (int y = 0; y < DISPLAY_HEIGHT; y += 4) {
      putpix(pos, y, gridColor);
    }
    
    // Horizontal grid lines (as dots)
    for (int x = 0; x < DISPLAY_WIDTH; x += 4) {
      putpix(x, pos, gridColor);
    }
  }
  
  // Add tick marks on center axes (X and Y)
  const int tickSize = 6;
  const int tickSpacing = 12; // Small divisions
  
  // X-axis tick marks (vertical center line)
  for (int x = 0; x < DISPLAY_WIDTH; x += tickSpacing) {
    for (int ty = -tickSize; ty <= tickSize; ty++) {
      putpix(x, CY + ty, gridColor);
    }
  }
  
  // Y-axis tick marks (horizontal center line)
  for (int y = 0; y < DISPLAY_HEIGHT; y += tickSpacing) {
    for (int tx = -tickSize; tx <= tickSize; tx++) {
      putpix(CX + tx, y, gridColor);
    }
  }
}

// -------------------------------------------------------------
// RETRO DIGIT BITMAP FONT (  0.6A MODE 0)
// -------------------------------------------------------------

//8x12 numeric font
const uint8_t RETRO_DIGIT[10][12] PROGMEM = {
  {0b00111100,0b01000010,0b10000001,0b10000001,0b10000001,0b10000001,0b10000001,0b10000001,0b10000001,0b01000010,0b00111100,0},
  {0b00010000,0b00110000,0b01010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0b00010000,0},
  {0b00111100,0b01000010,0b00000010,0b00000100,0b00001000,0b00010000,0b00100000,0b01000000,0b10000000,0b10000000,0b11111110,0},
  {0b00111100,0b01000010,0b00000010,0b00000010,0b00111100,0b00000010,0b00000010,0b00000010,0b00000010,0b01000010,0b00111100,0},
  {0b00001000,0b00011000,0b00101000,0b01001000,0b10001000,0b11111110,0b00001000,0b00001000,0b00001000,0b00001000,0b00001000,0},
  {0b01111110,0b01000000,0b01000000,0b01000000,0b01111100,0b00000010,0b00000010,0b00000010,0b00000010,0b01000100,0b00111000,0},
  {0b00111100,0b01000010,0b10000000,0b10000000,0b11111100,0b10000010,0b10000010,0b10000010,0b10000010,0b01000010,0b00111100,0},
  {0b11111110,0b00000010,0b00000100,0b00001000,0b00010000,0b00010000,0b00100000,0b00100000,0b01000000,0b01000000,0b01000000,0},
  {0b00111100,0b01000010,0b10000001,0b10000001,0b01000010,0b00111100,0b01000010,0b10000001,0b10000001,0b01000010,0b00111100,0},
  {0b00111100,0b01000010,0b10000001,0b10000001,0b01000011,0b00111101,0b00000001,0b00000001,0b00000010,0b01000100,0b00111000,0}
};

//8x14 numeric font
const uint8_t RETRO_NUMERIC[10][14] PROGMEM = {
  {0x3C,0x3C,0x42,0x42,0x46,0x46,0x5A,0x5A,0x62,0x62,0x42,0x42,0x3C,0x3C},  // 0
  {0x08,0x08,0x18,0x18,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x1C,0x1C},  // 1
  {0x3C,0x3C,0x42,0x42,0x02,0x02,0x1C,0x1C,0x20,0x20,0x40,0x40,0x7E,0x7E},  // 2
  {0x7E,0x7E,0x02,0x02,0x04,0x04,0x1C,0x1C,0x02,0x02,0x42,0x42,0x3C,0x3C},  // 3
  {0x04,0x04,0x0C,0x0C,0x14,0x14,0x24,0x24,0x7E,0x7E,0x04,0x04,0x04,0x04},  // 4
  {0x7E,0x7E,0x40,0x40,0x7C,0x7C,0x02,0x02,0x02,0x02,0x42,0x42,0x3C,0x3C},  // 5
  {0x1E,0x1E,0x20,0x20,0x40,0x40,0x7C,0x7C,0x42,0x42,0x42,0x42,0x3C,0x3C},  // 6
  {0x7E,0x7E,0x02,0x02,0x04,0x04,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10},  // 7
  {0x3C,0x3C,0x42,0x42,0x42,0x42,0x3C,0x3C,0x42,0x42,0x42,0x42,0x3C,0x3C},  // 8
  {0x3C,0x3C,0x42,0x42,0x42,0x42,0x3E,0x3E,0x02,0x02,0x04,0x04,0x78,0x78},  // 9
};

//8x14 alphabetic font
const uint8_t RETRO_ALPHA[26][14] PROGMEM = {
  {0x18,0x18,0x24,0x24,0x42,0x42,0x42,0x42,0x7E,0x7E,0x42,0x42,0x42,0x42},  // A
  {0x7C,0x7C,0x42,0x42,0x42,0x42,0x7C,0x7C,0x42,0x42,0x42,0x42,0x7C,0x7C},  // B
  {0x3C,0x3C,0x42,0x42,0x40,0x40,0x40,0x40,0x40,0x40,0x42,0x42,0x3C,0x3C},  // C
  {0x7C,0x7C,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x7C,0x7C},  // D
  {0x7E,0x7E,0x40,0x40,0x40,0x40,0x7C,0x7C,0x40,0x40,0x40,0x40,0x7E,0x7E},  // E
  {0x7E,0x7E,0x40,0x40,0x40,0x40,0x7C,0x7C,0x40,0x40,0x40,0x40,0x40,0x40},  // F
  {0x3E,0x3E,0x40,0x40,0x40,0x40,0x40,0x4E,0x4E,0x42,0x42,0x42,0x3E,0x3E},  // G
  {0x42,0x42,0x42,0x42,0x42,0x42,0x7E,0x7E,0x42,0x42,0x42,0x42,0x42,0x42},  // H
  {0x38,0x38,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x38,0x38},  // I
  {0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x42,0x42,0x3C,0x3C},  // J
  {0x42,0x42,0x44,0x44,0x48,0x48,0x70,0x70,0x48,0x48,0x44,0x44,0x42,0x42},  // K
  {0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7E,0x7E},  // L
  {0x42,0x42,0x66,0x66,0x5A,0x5A,0x5A,0x5A,0x42,0x42,0x42,0x42,0x42,0x42},  // M
  {0x42,0x42,0x62,0x62,0x52,0x52,0x5A,0x5A,0x4A,0x4A,0x46,0x46,0x42,0x42},  // N
  {0x3C,0x3C,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x3C,0x3C},  // O
  {0x7C,0x7C,0x42,0x42,0x42,0x42,0x7C,0x7C,0x40,0x40,0x40,0x40,0x40,0x40},  // P
  {0x3C,0x3C,0x42,0x42,0x42,0x42,0x42,0x42,0x4A,0x4A,0x44,0x44,0x3A,0x3A},  // Q
  {0x7C,0x7C,0x42,0x42,0x42,0x42,0x7C,0x7C,0x48,0x48,0x44,0x44,0x42,0x42},  // R
  {0x3C,0x3C,0x42,0x42,0x40,0x40,0x3C,0x3C,0x02,0x02,0x42,0x42,0x3C,0x3C},  // S
  {0x7C,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10},  // T
  {0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x3C,0x3C},  // U
  {0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x24,0x18,0x18},  // V
  {0x42,0x42,0x42,0x42,0x42,0x42,0x5A,0x5A,0x5A,0x5A,0x66,0x66,0x42,0x42},  // W
  {0x42,0x42,0x42,0x42,0x24,0x24,0x18,0x18,0x24,0x24,0x42,0x42,0x42,0x42},  // X
  {0x44,0x44,0x44,0x44,0x28,0x28,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10},  // Y
  {0x7E,0x7E,0x02,0x02,0x04,0x04,0x18,0x18,0x20,0x20,0x40,0x40,0x7E,0x7E}   // Z
};

const int GLYPH_W = 8;
const int GLYPH_H = 12;
const int GLYPH_H_BIG = 14;

// ---- Digit drawing with glow (your original) ----
void drawDigitsString(const char *text, int x, int y, int scale, int glyph_h)
{
  int len = strlen(text);
  int digit;
  const uint8_t *glyph;
  
  for(int d=0; d<len; d++){
    char c = text[d];

    if((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')){
      if (c >= '0' && c <= '9') {
        digit = c - '0';
        glyph = (glyph_h == GLYPH_H) ? RETRO_DIGIT[digit] : RETRO_NUMERIC[digit];;
      } else {
        digit = c - 'A';
        glyph = RETRO_ALPHA[digit];
      }
      int x0 = x + d*(GLYPH_W*scale + scale);

      for(int row=0; row<glyph_h; row++){
        uint8_t bits = pgm_read_byte(&glyph[row]);
        for(int col=0; col<GLYPH_W; col++){
          if(bits & (0x80 >> col)){
            for(int sx=0; sx<scale; sx++)
              for(int sy=0; sy<scale; sy++){
                glow_putpix(x0 + col*scale + sx, y + row*scale + sy);
              }
          }
        }
      }
    }

    if(c=='.'){
      int dotSize = 2*scale;
      int bx = x + d*(GLYPH_W*scale+scale) + GLYPH_W*scale/2 - dotSize/2;
      int by = y + GLYPH_H*scale - dotSize - scale;

      for(int yy=0; yy<dotSize; yy++)
        for(int xx=0; xx<dotSize; xx++)
          glow_putpix(bx+xx, by+yy);
    }
  }
}

// -------------------------------------------------------------
// MODE 0  ANALOG OSCILLOSCOPE CLOCK ( V0.6A, )
// -------------------------------------------------------------
void drawDialNumber(int num, float angleDeg, int radius, int scale)
{
  char buf[4];
  sprintf(buf,"%d",num);

  float a = angleDeg * PI / 180.0f;
  int x = CX + (int)(radius * sin(a));
  int y = CY - (int)(radius * cos(a));

  int w = GLYPH_W * scale;
  drawDigitsString(buf, x - w/2, y - (GLYPH_H*scale)/2, scale, GLYPH_H);
}

void draw_ring(int cx,int cy,int rOuter,int thickness,uint16_t col)
{
  int rInner = rOuter - thickness;
  int ro2 = rOuter*rOuter;
  int ri2 = rInner*rInner;

  for(int y=-rOuter; y<=rOuter; y++){
    for(int x=-rOuter; x<=rOuter; x++){
      int rr = x*x + y*y;
      if(rr<=ro2 && rr>=ri2){
        glow_putpix(cx+x, cy+y);
      }
    }
  }
}

void draw_clock_face_osc(int wd, int dd,int mm,int yyyy,int zoom)
{
  char buf[16];
  char dow[4];
  //sprintf(buf,"%02d.%02d.%04d",dd,mm,yyyy);
  strncpy(dow,days[wd],3);
  dow[3] = '\0';
  sprintf(buf,"%s %02d %s",dow,dd,months[mm-1]);
  
  clearFB();
  
  // DRAW GRID FIRST (as background layer)
  draw_oscilloscope_grid();

  int R_OUT = 225 + zoom;
  if(R_OUT < 180) R_OUT = 180;
  if(R_OUT > 240) R_OUT = 240;

  const int SHORT_LEN = 20;
  const int LONG_LEN  = 32;

  for(int i=0;i<60;i++){
    float a = i*6.0f * PI/180.0f;

    bool isHour = (i % 5 == 0);
    int len = isHour ? LONG_LEN : SHORT_LEN;
    int thickness = isHour ? 4 : 2;
    int half = thickness/2;

    int x1 = CX + (int)((R_OUT - len)*sin(a));
    int y1 = CY - (int)((R_OUT - len)*cos(a));
    int x2 = CX + (int)(R_OUT*sin(a));
    int y2 = CY - (int)(R_OUT*cos(a));

    float dx = x2 - x1;
    float dy = y2 - y1;

    float px = -dy;
    float py = dx;

    float inv = 1.0f / sqrt(px*px + py*py);
    px *= inv;
    py *= inv;

    for(int k=-half; k<=half; k++){
      int ox = (int)(px*k);
      int oy = (int)(py*k);
      draw_line(x1+ox, y1+oy, x2+ox, y2+oy, primaryColor);
    }
  }

  int numRadius = R_OUT - 57;
  for(int n=1;n<=12;n++)
    drawDialNumber(n, n*30.0f, numRadius, 3);

  //  
  drawDigitsString(buf, CX-80, CY+60, 2, GLYPH_H_BIG);

  draw_ring(CX, CY, 10, 2, primaryColor);
}

void draw_clock_hands_osc(int hour,int minute,int second,int zoom)
{
  float secA  = second * 6.0f * PI/180.0f;
  float minA  = (minute + second/60.0f)*6.0f * PI/180.0f;
  float hourA = (hour%12 + minute/60.0f)*30.0f * PI/180.0f;

  const int HUB_R = 10;
  int R_H = 120 + (zoom/3);
  int R_M = 175 + (zoom/3);
  int R_S = 190 + (zoom/3);

  const int W_H = 8;
  const int W_M = 6;

  {   // Hour hand
    float s = sin(hourA), c = cos(hourA);

    int tipX = CX + (int)(R_H*s);
    int tipY = CY - (int)(R_H*c);

    int leftX  = CX + (int)( HUB_R*s + W_H*c );
    int leftY  = CY - (int)( HUB_R*c - W_H*s );

    int rightX = CX + (int)( HUB_R*s - W_H*c );
    int rightY = CY - (int)( HUB_R*c + W_H*s );

    draw_line(leftX, leftY, tipX, tipY, primaryColor);
    draw_line(rightX, rightY, tipX, tipY, primaryColor);
  }

  {   // Minute hand
    float s = sin(minA), c = cos(minA);

    int tipX = CX + (int)(R_M*s);
    int tipY = CY - (int)(R_M*c);

    int leftX  = CX + (int)( HUB_R*s + W_M*c );
    int leftY  = CY - (int)( HUB_R*c - W_M*s );

    int rightX = CX + (int)( HUB_R*s - W_M*c );
    int rightY = CY - (int)( HUB_R*c + W_M*s );

    draw_line(leftX, leftY, tipX, tipY, primaryColor);
    draw_line(rightX, rightY, tipX, tipY, primaryColor);
  }

  {   // Second hand
    float s = sin(secA), c = cos(secA);
    int tipX = CX + (int)(R_S*s);
    int tipY = CY - (int)(R_S*c);
    draw_line(CX, CY, tipX, tipY, primaryColor);
  }

  draw_ring(CX, CY, HUB_R, 2, primaryColor);
}

void draw_mode0_osc_clock(const tm &t)
{
  draw_clock_face_osc(t.tm_wday, t.tm_mday, t.tm_mon+1, t.tm_year+1900, zoomOffset);
  draw_clock_hands_osc(t.tm_hour, t.tm_min, t.tm_sec, zoomOffset);
  gfx->draw16bitRGBBitmap(0,0,fb,DISPLAY_WIDTH,DISPLAY_HEIGHT);
}

// -------------------------------------------------------------
// MODE 1  THIN CRT-STYLE DIGITAL CLOCK (Day / HH:MM / Date)
// -------------------------------------------------------------
// -------------------------------------------------------------
// THIN VECTOR CRT DIGITS  improved spacing
// -------------------------------------------------------------

// ============================================================
// FIXED ROUNDED CRT CHARACTERS -   
// ============================================================

// HELPER: Draw arc ( ) -  
void draw_arc(int cx, int cy, int radius, int startAngle, int endAngle)
{
  // startAngle  endAngle    (0-360)
  for (int angle = startAngle; angle <= endAngle; angle += 2) {
    float rad = angle * PI / 180.0f;
    int x = cx + (int)(radius * cos(rad));
    int y = cy + (int)(radius * sin(rad));
    glow_putpix(x, y);
  }
}

//   
void draw_colon_rounded(int cx, int cy, int height) {
  int dotRadius = (int)(height * 0.04f);
  int offset = (int)(height * 0.18f);
  
  //  
  for(int angle = 0; angle < 360; angle += 8) {
    float rad = angle * PI / 180.0f;
    int x = cx + (int)(dotRadius * cos(rad));
    int y = (cy - offset) + (int)(dotRadius * sin(rad));
    glow_putpix(x, y);
  }
  
  //  
  for(int angle = 0; angle < 360; angle += 8) {
    float rad = angle * PI / 180.0f;
    int x = cx + (int)(dotRadius * cos(rad));
    int y = (cy + offset) + (int)(dotRadius * sin(rad));
    glow_putpix(x, y);
  }
}

// ============================================================
// ROUNDED DIGIT (0-9) -  
// ============================================================
void roundedDigit(int d, int cx, int cy, int h)
{
  int w = h * 0.48;
  int r = w / 3;
  
  int top = cy - h/2;
  int bot = cy + h/2;
  int mid = cy;
  
  int left = cx - w/2;
  int right = cx + w/2;

  switch(d)
  {
    case 0: //  
      //  
      draw_arc(left + r, top + r, r, 180, 270);   // top-left
      draw_arc(right - r, top + r, r, 270, 360);  // top-right
      //  
      draw_arc(right - r, bot - r, r, 0, 90);     // bottom-right
      draw_arc(left + r, bot - r, r, 90, 180);    // bottom-left
      //  
      draw_line(left + r, top, right - r, top, primaryColor);
      draw_line(right, top + r, right, bot - r, primaryColor);
      draw_line(right - r, bot, left + r, bot, primaryColor);
      draw_line(left, bot - r, left, top + r, primaryColor);
      break;

    case 1: //  
      draw_line(cx, top, cx, bot, primaryColor);
      break;

    case 2: // S-
      draw_arc(left + r, top + r, r, 180, 270);
      draw_arc(right - r, top + r, r, 270, 360);
      draw_line(left + r, top, right - r, top, primaryColor);
      draw_line(right, top + r, right, mid, primaryColor);
      draw_line(right, mid, left, mid, primaryColor);
      draw_line(left, mid, left, bot - r, primaryColor);
      draw_arc(left + r, bot - r, r, 90, 180);
      draw_arc(right - r, bot - r, r, 0, 90);
      draw_line(left + r, bot, right - r, bot, primaryColor);
      break;

    case 3: //  
      draw_arc(left + r, top + r, r, 180, 270);
      draw_arc(right - r, top + r, r, 270, 360);
      draw_line(left + r, top, right - r, top, primaryColor);
      draw_line(right, top + r, right, mid, primaryColor);
      draw_line(right - w/3, mid, right, mid, primaryColor);
      draw_line(right, mid, right, bot - r, primaryColor);
      draw_arc(right - r, bot - r, r, 0, 90);
      draw_arc(left + r, bot - r, r, 90, 180);
      draw_line(right - r, bot, left + r, bot, primaryColor);
      break;

    case 4: // 4
      draw_line(left, top, left, mid, primaryColor);
      draw_line(left, mid, right, mid, primaryColor);
      draw_line(right, top, right, bot, primaryColor);
      break;

    case 5: // S-
      draw_arc(right - r, top + r, r, 270, 360);
      draw_arc(left + r, top + r, r, 180, 270);
      draw_line(right - r, top, left + r, top, primaryColor);
      draw_line(left, top + r, left, mid, primaryColor);
      draw_line(left, mid, right, mid, primaryColor);
      draw_line(right, mid, right, bot - r, primaryColor);
      draw_arc(right - r, bot - r, r, 0, 90);
      draw_arc(left + r, bot - r, r, 90, 180);
      draw_line(right - r, bot, left + r, bot, primaryColor);
      break;

    case 6: // 6
      draw_arc(right - r, top + r, r, 270, 360);
      draw_line(right - r, top, left + r, top, primaryColor);
      draw_arc(left + r, top + r, r, 180, 270);
      draw_line(left, top + r, left, bot - r, primaryColor);
      draw_arc(left + r, bot - r, r, 90, 180);
      draw_line(left + r, bot, right - r, bot, primaryColor);
      draw_arc(right - r, bot - r, r, 0, 90);
      draw_line(right, bot - r, right, mid, primaryColor);
      draw_line(right, mid, left, mid, primaryColor);
      break;

    case 7: // 7
      draw_arc(left + r, top + r, r, 180, 270);
      draw_line(left + r, top, right - r, top, primaryColor);
      draw_arc(right - r, top + r, r, 270, 360);
      draw_line(right, top + r, cx, bot, primaryColor);
      break;

    case 8: // 8 -  
      //  
      draw_arc(left + r, top + r, r, 180, 270);
      draw_line(left + r, top, right - r, top, primaryColor);
      draw_arc(right - r, top + r, r, 270, 360);
      draw_line(right, top + r, right, mid, primaryColor);
      draw_line(right, mid, left, mid, primaryColor);
      draw_line(left, mid, left, top + r, primaryColor);
      //  
      draw_line(left, mid, left, bot - r, primaryColor);
      draw_arc(left + r, bot - r, r, 90, 180);
      draw_line(left + r, bot, right - r, bot, primaryColor);
      draw_arc(right - r, bot - r, r, 0, 90);
      draw_line(right, bot - r, right, mid, primaryColor);
      break;

    case 9: // 9
      draw_arc(left + r, top + r, r, 180, 270);
      draw_line(left + r, top, right - r, top, primaryColor);
      draw_arc(right - r, top + r, r, 270, 360);
      draw_line(right, top + r, right, bot - r, primaryColor);
      draw_line(right, mid, left, mid, primaryColor);
      draw_line(left, mid, left, top + r, primaryColor);
      draw_arc(left + r, bot - r, r, 90, 180);
      draw_line(left + r, bot, right - r, bot, primaryColor);
      draw_arc(right - r, bot - r, r, 0, 90);
      break;
  }
}


// ============================================================
// ROUNDED LETTER ( )
// ============================================================
void roundedLetter(char c, int cx, int cy, int h)
{
  int w = h * 0.45;
  int r = w / 3;
  
  int top = cy - h/2;
  int bot = cy + h/2;
  int mid = cy;
  
  int left = cx - w/2;
  int right = cx + w/2;

  switch(c)
  {
    case 'O': //  0
      draw_arc(left + r, top + r, r, 180, 270);
      draw_arc(right - r, top + r, r, 270, 360);
      draw_line(left + r, top, right - r, top, primaryColor);
      draw_line(right, top + r, right, bot - r, primaryColor);
      draw_arc(right - r, bot - r, r, 0, 90);
      draw_line(right - r, bot, left + r, bot, primaryColor);
      draw_arc(left + r, bot - r, r, 90, 180);
      draw_line(left, bot - r, left, top + r, primaryColor);
      break;

    case 'A':
      draw_line(left, bot, cx, top, primaryColor);
      draw_line(cx, top, right, bot, primaryColor);
      draw_line(left + w/4, mid, right - w/4, mid, primaryColor);
      break;

    case 'D':
      draw_line(left, top, left, bot, primaryColor);
      draw_line(left, top, cx + r, top, primaryColor);
      draw_arc(right - r, top + r, r, 270, 360);
      draw_line(right, top + r, right, bot - r, primaryColor);
      draw_arc(right - r, bot - r, r, 0, 90);
      draw_line(cx + r, bot, left, bot, primaryColor);
      break;

    case 'F':
      draw_line(left, top, left, bot, primaryColor);
      draw_line(left, top, right, top, primaryColor);
      draw_line(left, mid, cx, mid, primaryColor);
      break;

    case 'I':
      draw_line(cx, top, cx, bot, primaryColor);
      break;

    case 'M':
      draw_line(left, bot, left, top, primaryColor);
      draw_line(left, top, cx, mid, primaryColor);
      draw_line(cx, mid, right, top, primaryColor);
      draw_line(right, top, right, bot, primaryColor);
      break;

    case 'N':
      draw_line(left, bot, left, top, primaryColor);
      draw_line(left, top, right, bot, primaryColor);
      draw_line(right, bot, right, top, primaryColor);
      break;

    case 'R':
      draw_line(left, bot, left, top, primaryColor);
      draw_line(left, top, right - r, top, primaryColor);
      draw_arc(right - r, top + r, r, 270, 360);
      draw_line(right, top + r, right, mid - r, primaryColor);
      draw_arc(right - r, mid - r, r, 0, 90);
      draw_line(right - r, mid, left, mid, primaryColor);
      draw_line(cx, mid, right, bot, primaryColor);
      break;

    case 'S': //  5
      draw_arc(right - r, top + r, r, 270, 360);
      draw_line(right - r, top, left + r, top, primaryColor);
      draw_arc(left + r, top + r, r, 180, 270);
      draw_line(left, top + r, left, mid, primaryColor);
      draw_line(left, mid, right, mid, primaryColor);
      draw_line(right, mid, right, bot - r, primaryColor);
      draw_arc(right - r, bot - r, r, 0, 90);
      draw_line(right - r, bot, left + r, bot, primaryColor);
      draw_arc(left + r, bot - r, r, 90, 180);
      break;

    case 'T':
      draw_line(left, top, right, top, primaryColor);
      draw_line(cx, top, cx, bot, primaryColor);
      break;

    case 'U':
      draw_line(left, top, left, bot - r, primaryColor);
      draw_arc(left + r, bot - r, r, 90, 180);
      draw_line(left + r, bot, right - r, bot, primaryColor);
      draw_arc(right - r, bot - r, r, 0, 90);
      draw_line(right, bot - r, right, top, primaryColor);
      break;

    case 'E':
      draw_line(left, top, left, bot, primaryColor);
      draw_line(left, top, right, top, primaryColor);
      draw_line(left, mid, cx, mid, primaryColor);
      draw_line(left, bot, right, bot, primaryColor);
      break;

    case 'Y':
      draw_line(left, top, cx, mid, primaryColor);
      draw_line(right, top, cx, mid, primaryColor);
      draw_line(cx, mid, cx, bot, primaryColor);
      break;

    case 'W':
      draw_line(left, top, left, bot, primaryColor);
      draw_line(left, bot, cx, mid, primaryColor);
      draw_line(cx, mid, right, bot, primaryColor);
      draw_line(right, bot, right, top, primaryColor);
      break;

    case 'H':
      draw_line(left, top, left, bot, primaryColor);
      draw_line(left, mid, right, mid, primaryColor);
      draw_line(right, top, right, bot, primaryColor);
      break;
  }
}
// -------------------------------------------------------------
// DRAW DAY OF THE WEEK (Vector Thin) - CENTERED
// -------------------------------------------------------------
void drawWeekday_VectorThinArc(const char* name, int centerY, int height) {
  int len = strlen(name);
  int step = height * 0.6; 
  
  // 1. Calculate the character width 'w' (as defined in thinLetter: w = h * 0.45) 
  int w = (int)(height * 0.45);

  // 2. Calculate the total visual width of the word.
  // Total Width = (len - 1 gaps * step) + (1 character width)
  int total_width = (len - 1) * step + w;
  
  // 3. Calculate the X coordinate of the center of the first letter (startX).
  // The goal is to set the center of the *entire word* at CX (240).
  // startX = (Center of screen CX) - (Half of total width) + (Half of first character width)
  // This correctly positions the center of the first letter.
  int startX = CX - total_width / 2 + w / 2; 

  for (int i = 0; i < len; i++) {
   roundedLetter(name[i], startX + i * step, centerY, height);
  }
}

// -------------------------------------------------------------
// Draw HH:MM with improved spacing and adjusted position
// -------------------------------------------------------------
void drawTimeHHMM_VectorThinArc(int hour, int minute, int centerY, int height)
{
  int w = height * 0.48;
  int spacing = height * 0.1;
  const int OFFSET_X = 20;

  int cxH1 = CX - (w * 1.5 + spacing * 1.5) - OFFSET_X;
  int cxH2 = CX - (w * 0.5 + spacing * 0.5) - OFFSET_X;
  int cxM1 = CX + (w * 0.5 + spacing * 0.5) + OFFSET_X;
  int cxM2 = CX + (w * 1.5 + spacing * 1.5) + OFFSET_X;

  roundedDigit(hour/10, cxH1, centerY, height);
  roundedDigit(hour%10, cxH2, centerY, height);

  //   
  draw_colon_rounded(CX, centerY, height);

  roundedDigit(minute/10, cxM1, centerY, height);
  roundedDigit(minute%10, cxM2, centerY, height);
}
void drawDateDDMM_VectorThinArc(int day, int month, int centerY, int height)
{
  // digit "visual width" used by thinDigit()
  int w = (int)(height * 0.48f);
  int digitStep = w + (int)(height * 0.12f);

  // bigger separation between DD and MM (you asked "a little further apart")
  int colonGap = (int)(height * 0.65f);

  // positions (FORCED symmetry around CX)
  int cxD1 = CX - (digitStep + colonGap);
  int cxD2 = CX - (colonGap);
  int cxM1 = CX + (colonGap);
  int cxM2 = CX + (digitStep + colonGap);

  // draw digits
   roundedDigit((day / 10) % 10, cxD1, centerY, height);
   roundedDigit(day % 10, cxD2, centerY, height);

  // dots: SAME X as time (CX), and a bit higher as you requested
  int colonCenterY = centerY - (int)(height * 0.08f);
  int dotOff = (int)(height * 0.18f);
  glow_putpix(CX, colonCenterY - dotOff);
  glow_putpix(CX, colonCenterY + dotOff);

   roundedDigit((month / 10) % 10, cxM1, centerY, height);
   roundedDigit(month % 10, cxM2, centerY, height);
}

void draw_mode1_digital_clock_arc(const tm &t)
{
  clearFB();

  // DRAW GRID FIRST (as background layer)
  draw_oscilloscope_grid();

  int dayY = CY - 150;     // same vertical region you already use
  int dayH = 50;           // thin size

  drawWeekday_VectorThinArc(days[t.tm_wday], dayY, dayH);
   
  // ----- Time HH:MM (center) -----
  drawTimeHHMM_VectorThinArc(t.tm_hour, t.tm_min, CY - 10, 150);

  // ----- Date (thin CRT, numeric) -----
  drawDateDDMM_VectorThinArc(
    t.tm_mday,
    t.tm_mon + 1,
    (CY - 10) + 160,   // +10 / -10     
    70                // height   ( 6080)
  );

  gfx->draw16bitRGBBitmap(0, 0, fb, DISPLAY_WIDTH, DISPLAY_HEIGHT);
}

// -------------------------------------------------------------
// MODE 2  THIN CRT-STYLE DIGITAL CLOCK (Day / HH:MM / Date)
// -------------------------------------------------------------
// -------------------------------------------------------------
// THIN VECTOR CRT DIGITS  improved spacing
// -------------------------------------------------------------

void thinDigit(int d, int cx, int cy, int h)
{
  int w = h * 0.48;   // little narrower
  int top = cy - h/2;
  int bot = cy + h/2;
  int mid = cy;

  switch(d)
  {
    case 0:
      draw_line(cx-w/2, top, cx+w/2, top, primaryColor);
      draw_line(cx-w/2, bot, cx+w/2, bot, primaryColor);
      draw_line(cx-w/2, top, cx-w/2, bot, primaryColor);
      draw_line(cx+w/2, top, cx+w/2, bot, primaryColor);
      break;

    case 1:
      draw_line(cx, top, cx, bot, primaryColor);
      break;

    case 2:
      draw_line(cx-w/2, top, cx+w/2, top, primaryColor);
      draw_line(cx+w/2, top, cx+w/2, mid, primaryColor);
      draw_line(cx+w/2, mid, cx-w/2, mid, primaryColor);
      draw_line(cx-w/2, mid, cx-w/2, bot, primaryColor);
      draw_line(cx-w/2, bot, cx+w/2, bot, primaryColor);
      break;

    case 3:
      draw_line(cx-w/2, top, cx+w/2, top, primaryColor);
      draw_line(cx+w/2, top, cx+w/2, mid, primaryColor);
      draw_line(cx-w/2, mid, cx+w/2, mid, primaryColor);
      draw_line(cx+w/2, mid, cx+w/2, bot, primaryColor);
      draw_line(cx-w/2, bot, cx+w/2, bot, primaryColor);
      break;

    case 4:
      draw_line(cx-w/2, top, cx-w/2, mid, primaryColor);
      draw_line(cx-w/2, mid, cx+w/2, mid, primaryColor);
      draw_line(cx+w/2, top, cx+w/2, bot, primaryColor);
      break;

    case 5:
      draw_line(cx+w/2, top, cx-w/2, top, primaryColor);
      draw_line(cx-w/2, top, cx-w/2, mid, primaryColor);
      draw_line(cx-w/2, mid, cx+w/2, mid, primaryColor);
      draw_line(cx+w/2, mid, cx+w/2, bot, primaryColor);
      draw_line(cx-w/2, bot, cx+w/2, bot, primaryColor);
      break;

    case 6:
      draw_line(cx+w/2, top, cx-w/2, top, primaryColor);
      draw_line(cx-w/2, top, cx-w/2, bot, primaryColor);
      draw_line(cx-w/2, mid, cx+w/2, mid, primaryColor);
      draw_line(cx-w/2, bot, cx+w/2, bot, primaryColor);
      //  FIX: missing right-side vertical
      draw_line(cx+w/2, mid, cx+w/2, bot, primaryColor);
      break;

    case 7:
      draw_line(cx-w/2, top, cx+w/2, top, primaryColor);
      draw_line(cx+w/2, top, cx+w/2, bot, primaryColor);
      break;

    case 8:
      thinDigit(0, cx, cy, h);
      draw_line(cx-w/2, mid, cx+w/2, mid, primaryColor);
      break;

    case 9:
      thinDigit(3, cx, cy, h);  // reuse perfect 3 shape
      draw_line(cx-w/2, top, cx+w/2, top, primaryColor);
      //  FIX: missing left upper vertical
      draw_line(cx-w/2, top, cx-w/2, mid, primaryColor);
      break;
  }
}

void thinLetter(char c, int cx, int cy, int h)
{
  int w = h * 0.45;
  int top = cy - h/2;
  int bot = cy + h/2;
  int mid = cy;

  switch (c)
  {
    case 'O': //   Monday, Tuesday...
    case '0':
      draw_line(cx - w / 2, top, cx + w / 2, top, primaryColor); // 
      draw_line(cx - w / 2, bot, cx + w / 2, bot, primaryColor); // 
      draw_line(cx - w / 2, top, cx - w / 2, bot, primaryColor); // 
      draw_line(cx + w / 2, top, cx + w / 2, bot, primaryColor); // 
      break;
      
    case 'A':
      draw_line(cx-w/2, bot, cx, top, primaryColor);
      draw_line(cx, top, cx+w/2, bot, primaryColor);
      draw_line(cx-w/4, mid, cx+w/4, mid, primaryColor);
      break;

    case 'D':
      draw_line(cx-w/2, top, cx-w/2, bot, primaryColor);
      draw_line(cx-w/2, top, cx+w/3, mid, primaryColor);
      draw_line(cx+w/3, mid, cx-w/2, bot, primaryColor);
      break;

    case 'F':
      draw_line(cx-w/2, top, cx-w/2, bot, primaryColor);
      draw_line(cx-w/2, top, cx+w/2, top, primaryColor);
      draw_line(cx-w/2, mid, cx+w/4, mid, primaryColor);
      break;

    case 'I':
      draw_line(cx, top, cx, bot, primaryColor);
      break;

...

This file has been truncated, please download it to see its full contents.

Credits

John Bradnam
177 projects • 227 followers
Thanks to Mirko Pavleski.

Comments