/*
Forum: https://forum.arduino.cc/t/linienstarke-in-ili9341-library/1417355
Wokwi: https://wokwi.com/projects/449760116365872129
ec2021
2025/12/08
*/
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "PointerBarClass.h"
#undef TFTLCD // For my test application at home ...
#ifdef TFTLCD
#include <Adafruit_TFTLCD.h>
// Color definitions for the tft (RGB565)
constexpr uint16_t ILI9341_BLACK {0x0000};
constexpr uint16_t ILI9341_BLUE {0x001F};
constexpr uint16_t ILI9341_RED {0xF800};
constexpr uint16_t ILI9341_GREEN {0x07E0};
constexpr uint16_t ILI9341_CYAN {0x07FF};
constexpr uint16_t ILI9341_MAGENTA {0xf81F};
constexpr uint16_t ILI9341_YELLOW {0xFFE0};
constexpr uint16_t ILI9341_WHITE {0xFFFF};
constexpr uint16_t LCD_CS {A3};
constexpr uint16_t LCD_CD {A2};
constexpr uint16_t LCD_WR {A1};
constexpr uint16_t LCD_RD {A0};
constexpr uint16_t LCD_RESET {A4};
Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
#else // required for the Wokwi configuration
#include "Adafruit_ILI9341.h"
constexpr byte TFT_DC {2};
constexpr byte TFT_CS {15};
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
#endif
constexpr uint16_t ILI9341_GREY {0x5AEB};
constexpr struct clockData {
uint16_t x{ 120 };
uint16_t y{ 140 };
uint16_t innerR{ 110 };
uint16_t outerR{ 118 };
uint16_t innerMark{ 102 };
uint16_t outerMark{ 114 };
uint16_t secondR{ 100 };
uint16_t secondSize{ 6 };
uint16_t minuteR{ 84 };
uint16_t minuteSize{ 3 };
uint16_t hourR{ 62 };
uint16_t hourSize{ 3 };
uint16_t backColor{ 0x5AEB };
} myClock;
constexpr float ZeroAngle{ -90.0 };
unsigned long lastUpdate = 0;
static uint8_t conv2d(const char* p) {
uint8_t v = 0;
if ('0' <= *p && *p <= '9')
v = *p - '0';
return 10 * v + *++p - '0';
}
// Get H, M, S from compile time
#ifdef TFTLCD
int hours = conv2d(__TIME__), minutes = conv2d(__TIME__ + 3), seconds = conv2d(__TIME__ + 6);
#else
int hours = (conv2d(__TIME__) + 1) % 24, minutes = conv2d(__TIME__ + 3), seconds = conv2d(__TIME__ + 6);
#endif
void DrawAngledLine(int x, int y, int x1, int y1, int size, int color) {
// Source: https://github.com/G6EJD/GFX-Library-Any-Thickness-and-Angle-Line-Drawing
// In the GitHub reference dx and dy where defined the other way round
// however that leads to bars getting smaller and wider depending on the
// angle used
float dx = (size / 2.0) * (y - y1) / sqrt(sq(x - x1) + sq(y - y1));
float dy = (size / 2.0) * (x - x1) / sqrt(sq(x - x1) + sq(y - y1));
tft.fillTriangle(x + dx, y - dy, x - dx, y + dy, x1 + dx, y1 - dy, color);
tft.fillTriangle(x - dx, y + dy, x1 - dx, y1 + dy, x1 + dx, y1 - dy, color);
};
// Function call to draw a line from (x0,y0) to (x1, y1) in color col
void drawLine(int x0, int y0, int x1, int y1, int size, uint16_t col) {
// Insert the library specific function or routines here:
// The example uses tft.drawLine()
if (size == 1) {
tft.drawLine(x0, y0, x1, y1, col);
} else {
DrawAngledLine(x0, y0, x1, y1, size, col);
}
};
PointerClass sekundenZeiger(&drawLine),
minutenZeiger(&drawLine),
stundenZeiger(&drawLine);
// Function call to fill a rectangle from (x,y) with a width w and length l) in color col
void fillRect(int x, int y, int w, int l, uint16_t col) {
tft.fillRect(x, y, w, l, col);
}
BarClass sekundenBar(&fillRect), minutenBar(&fillRect), stundenBar(&fillRect);
void setup() {
Serial.begin(115200);
Serial.println("Graphics Clock Demo");
#ifdef TFTLCD
tft.reset();
uint16_t identifier = tft.readID();
tft.begin(identifier);
#else
tft.begin();
#endif
tft.setRotation(0);
sekundenZeiger.setZeroAngle(ZeroAngle);
minutenZeiger.setZeroAngle(ZeroAngle);
stundenZeiger.setZeroAngle(ZeroAngle);
sekundenZeiger.setXYRM(myClock.x, myClock.y, myClock.secondR, myClock.secondSize);
minutenZeiger.setXYRM(myClock.x, myClock.y, myClock.minuteR, myClock.minuteSize);
stundenZeiger.setXYRM(myClock.x, myClock.y, myClock.hourR, myClock.hourSize);
paintClockFace();
choose();
}
void loop() {
if (millis() - lastUpdate >= 1000) {
lastUpdate = millis();
handleTime();
}
}
void handleTime() {
static int oldMinutes = -1;
seconds = ++seconds % 60;
if (seconds == 0) {
choose();
minutes = ++minutes % 60;
if (minutes == 0) {
hours = ++hours % 24;
}
}
sekundenBar.draw(seconds, ILI9341_RED);
minutenBar.draw(minutes, ILI9341_YELLOW);
stundenBar.draw(hours, ILI9341_WHITE);
float sdeg = seconds * 6;
float mdeg = minutes * 6;
float hdeg = round(hours * 30 + mdeg * 0.0833333); // + sdeg * 0.01666667;
float tolerance = 20.0;
boolean minuteOverlap = doesOverlap(mdeg, sdeg, tolerance);
boolean hourOverlap = doesOverlap(hdeg, sdeg, tolerance);
boolean eraseSecondHand = true;
if (minuteOverlap || hourOverlap) {
sekundenZeiger.erase();
eraseSecondHand = false;
}
stundenZeiger.draw(hdeg, ILI9341_WHITE, minutes != oldMinutes); // Nur dann gelöscht beim Schreiben, wenn eine Minute um ist.
minutenZeiger.draw(mdeg, ILI9341_WHITE, minutes != oldMinutes); // Der Stundenzeiger wird dazu auch nur zur vollen Minute bewegt.
sekundenZeiger.draw(sdeg, ILI9341_RED, eraseSecondHand);
oldMinutes = minutes;
tft.fillCircle(myClock.x, myClock.y, 3, ILI9341_RED);
tft.setCursor(70, 280);
tft.setTextSize(2);
tftTime(hours, ':');
tftTime(minutes, ':');
tftTime(seconds, ' ');
}
boolean doesOverlap(float angleA, float angleB, float tolerance) {
int A = int(angleA) % 360;
int B = int(angleB) % 360;
return (abs(A - B) <= tolerance);
}
void tftTime(int val, char c) {
if (val < 10) tft.print("0");
tft.print(val);
tft.print(c);
}
void paintClockFace() {
PixelCoord dot, ending;
tft.fillScreen(ILI9341_GREY); // GREY);
tft.setTextColor(ILI9341_WHITE, ILI9341_GREY);
tft.fillCircle(myClock.x, myClock.y, myClock.outerR, ILI9341_BLUE);
tft.fillCircle(myClock.x, myClock.y, myClock.innerR, ILI9341_BLACK);
// Zeichne die Stunden-Marken
for (int i = 0; i < 360; i += 30) {
dot = fromAngleRadius(i, 114);
ending = fromAngleRadius(i, 102);
tft.drawLine(dot.x, dot.y, ending.x, ending.y, ILI9341_BLUE);
}
// Zeichne die Sekunden- und die Viertelstunden-Marken
for (int i = 0; i < 360; i += 6) {
dot = fromAngleRadius(i, 102);
tft.drawPixel(dot.x, dot.y, ILI9341_WHITE);
if (i % 90 == 0) tft.fillCircle(dot.x, dot.y, 2, ILI9341_WHITE);
}
}
PixelCoord fromAngleRadius(float Angle, int rad) {
return getPixelCoord(Angle, myClock.x, myClock.y, rad, ZeroAngle);
}
void choose() {
static byte choice = 0;
switch (choice) {
case 0:
{
sekundenBar.setXYWDB(5, 240, 10, Direction::topDown, ILI9341_GREY);
minutenBar.setXYWDB(20, 240, 10, Direction::topDown, ILI9341_GREY);
stundenBar.setXYWDB(35, 240, 10, Direction::topDown, ILI9341_GREY);
}
break;
case 1:
{
sekundenBar.setXYWDB(5, 300, 10, Direction::bottomUp, ILI9341_GREY);
minutenBar.setXYWDB(20, 300, 10, Direction::bottomUp, ILI9341_GREY);
stundenBar.setXYWDB(35, 300, 10, Direction::bottomUp, ILI9341_GREY);
}
break;
case 2:
{
sekundenBar.setXYWDB(5, 270, 10, Direction::leftToRight, ILI9341_GREY);
minutenBar.setXYWDB(5, 285, 10, Direction::leftToRight, ILI9341_GREY);
stundenBar.setXYWDB(5, 300, 10, Direction::leftToRight, ILI9341_GREY);
}
break;
case 3:
{
sekundenBar.setXYWDB(65, 270, 10, Direction::rightToLeft, ILI9341_GREY);
minutenBar.setXYWDB(65, 285, 10, Direction::rightToLeft, ILI9341_GREY);
stundenBar.setXYWDB(65, 300, 10, Direction::rightToLeft, ILI9341_GREY);
}
break;
}
choice = ++choice % 4;
}