Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
| |||||
Hand tools and fabrication machines | ||||||
![]() |
| |||||
![]() |
| |||||
Recently fellow Hackster Mirko Pavleski uploaded a project called "Oscilloscope Clock Simulation on a Round ESP32 Display" based on a Elecrow CrowPanel 2.1 inch module. This upgrade adds a 3D printed case and DC to DC convertor so that the clock can be powered by a standard 12V power brick.
However the main changes are in the software. The clock has the following changes and added features:
- Both line and arc modes are now incorporated in the one clock. Pushing the rotary display will switch the display mode between the analog clock, digital clock with arcs and digital clock with lines.
- The time zone system has been changed to a POSIX timezone string to support more complex daylight saving time switching.
- The analog clock now displays the current day-of-week, date and month. A new 8x14 alphanumeric font has been added to support this feature.
- The rotary encoder no longer changes the size of the analog clock but instead switches between 7 colors each having 16 levels of brightness. Colors are Green, Red, Blue, Cyan, Yellow, Magenta and White.
- The letters W and H have been added to the digital clock with lines so it can display days with these letters correctly
All parts were purchased from Ali-Express including the CrowPanel 2.1inch display module with ESP32-S3.
To allow for programming without opening the case, I also added a USB-C socket to the back panel. It connects to the CrowPanel module using a MX1.25 connector.
3D printingAll printing was done using a 0.2mm layer height. Supports are required when printing "Oscilloscope Clock V2 - Case.stl".
After printing "Oscilloscope Clock V2 - Case.stl", heat insert four M3 4.2mm dia, 5mm high brass insert nuts.
ProgrammingYou need to use Arduino IDE version 1.8.15 or later. Then make sure the latest ESP32 Core is installed.
Select Tools - Board - ESP32 Arduino - ESP32S3 Dev Module
Select Tools - Flash Size - Flash Size 16MB
Select Tools - Partition scheme - Huge App
Select Tools - PSRAM - OPI PSRAM
Code Changes
- Set the Wi-Fi credentials on lines 61 and 62
- Set the NTP Server and POSIX string on line 1420
You also need to install the Arduino_GFX_Library. The code should compile and upload without fatal errors.
AssemblyAdd the DC-022B DC Panel socket. The long leg is negative and the short leg is positive. Connect these wires to the input of the DC-DC buck convertor. Solder the anode of a 3 Amp Diode to the positive output from the DC-DC buck convertor. Connect a red wire to the cathode of the Diode and a black wire to the negative output from the DC-DC buck convertor. Connect a voltmeter or multimeter to the other end of the wires and adjust the output of the regulator so that 5V is present.
I purchased a MX1.25 4 Pin connector with connected wires from Ali-Express. Carefully remove each pin from the shroud and insert them as shown below. Red is VCC, Green is D-, Yellow is D+ and Black is GND. Join the red and black wires with the two wires from the DC-DC buck convertor and solder them onto the USB-C socket. Fix the USB-C socket using M2 screws, nuts and lock washers.
Attach the back panel to the case using four M3 countersunk (Flat head) 6mm screws.
Finally plug in the CrowPanel and screw it to the case from the back using three M3 countersunk (Flat head) 8mm screws.
Thanks Mirko for another great project. I hope the updated software inspires more builders to build what is a interesting talking piece.
/*==============================================================
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.










Comments