267 lines
7.8 KiB
C++
267 lines
7.8 KiB
C++
// Sketch to draw an analogue clock on the screen
|
|
// This uses anti-aliased drawing functions that are built into TFT_eSPI
|
|
|
|
// Anti-aliased lines can be drawn with sub-pixel resolution and permit lines to be
|
|
// drawn with less jaggedness.
|
|
// Requires MBed OS 2040 RAspberry pi pico board
|
|
|
|
// Based on a sketch by DavyLandman:
|
|
// https://github.com/Bodmer/TFT_eSPI/issues/905
|
|
|
|
#include "MBED_RPi_Pico_TimerInterrupt.h"
|
|
|
|
#define WIFI_SSID "Your_SSID"
|
|
#define WIFI_PASSWORD "Your_Password"
|
|
|
|
#include <Arduino.h>
|
|
#include <TFT_eSPI.h> // Master copy here: https://github.com/Bodmer/TFT_eSPI
|
|
#include <SPI.h>
|
|
|
|
#include "NotoSansBold15.h"
|
|
|
|
TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h
|
|
TFT_eSprite face = TFT_eSprite(&tft);
|
|
|
|
const int BtnFwd = 6;
|
|
const int BtnMode = 8;
|
|
const int BtnBack = 7;
|
|
|
|
|
|
#define CLOCK_FG TFT_BLACK
|
|
#define CLOCK_BG TFT_WHITE
|
|
#define HILITE_FG TFT_RED
|
|
#define LABEL_FG TFT_NAVY
|
|
|
|
#define CLOCK_R 240.0f / 2.0f // Clock face radius (float type)
|
|
#define CLOCK_XY CLOCK_R
|
|
#define H_HAND_LENGTH CLOCK_R/2.0f
|
|
#define M_HAND_LENGTH CLOCK_R/1.4f
|
|
#define S_HAND_LENGTH CLOCK_R/1.3f
|
|
|
|
#define FACE_W CLOCK_R * 2 + 1
|
|
#define FACE_H CLOCK_R * 2 + 1
|
|
|
|
// Calculate 1 second increment angles. Hours and minute hand angles
|
|
// change every second so we see smooth sub-pixel movement
|
|
#define SECOND_ANGLE 360.0 / 60.0
|
|
#define MINUTE_ANGLE SECOND_ANGLE / 60.0
|
|
#define HOUR_ANGLE MINUTE_ANGLE / 12.0
|
|
|
|
// Sprite width and height
|
|
#define FACE_W CLOCK_R * 2 + 1
|
|
#define FACE_H CLOCK_R * 2 + 1
|
|
|
|
const int ModeNormal = 0;
|
|
const int ModeMinutes = 1;
|
|
const int ModeHours = 2;
|
|
|
|
// Time h:m:s
|
|
uint8_t h = 0, m = 0, s = 0;
|
|
uint8_t mode = ModeNormal;
|
|
float time_secs = h * 3600 + m * 60 + s;
|
|
|
|
// Init RPI_PICO_Timer, can use any from 0-15 pseudo-hardware timers
|
|
MBED_RPI_PICO_Timer ITimer0(0);
|
|
|
|
void TimerHandler(uint alarm_num)
|
|
{
|
|
///////////////////////////////////////////////////////////
|
|
// Always call this for MBED RP2040 before processing ISR
|
|
///////////////////////////////////////////////////////////
|
|
TIMER_ISR_START(alarm_num);
|
|
// Increment time by 1000 milliseconds
|
|
time_secs += 1;
|
|
|
|
// Midnight roll-over
|
|
if (time_secs >= (60 * 60 * 24)) time_secs = 0;
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// Always call this for MBED RP2040 after processing ISR
|
|
TIMER_ISR_END(alarm_num);
|
|
////////////////////////////////////////////////////////////
|
|
}
|
|
|
|
// =========================================================================
|
|
// Setup
|
|
// =========================================================================
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
pinMode(BtnFwd,INPUT_PULLUP);
|
|
pinMode(BtnBack,INPUT_PULLUP);
|
|
pinMode(BtnMode,INPUT_PULLUP);
|
|
|
|
delay(500);
|
|
Serial.println("Booting...");
|
|
|
|
// Interval in microsecs
|
|
if (ITimer0.attachInterruptInterval(1000*1000, TimerHandler)) {
|
|
Serial.print(F("Starting ITimer0 OK, millis() = ")); Serial.println(millis());
|
|
}
|
|
else {
|
|
Serial.println(F("Can't set ITimer0. Select another freq. or timer"));
|
|
}
|
|
|
|
// Initialise the screen
|
|
tft.init();
|
|
|
|
// Ideally set orientation for good viewing angle range because
|
|
// the anti-aliasing effectiveness varies with screen viewing angle
|
|
// Usually this is when screen ribbon connector is at the bottom
|
|
tft.setRotation(0);
|
|
tft.fillScreen(TFT_RED);
|
|
|
|
// Create the clock face sprite
|
|
//face.setColorDepth(8); // 8 bit will work, but reduces effectiveness of anti-aliasing
|
|
face.createSprite(FACE_W, FACE_H);
|
|
|
|
// Only 1 font used in the sprite, so can remain loaded
|
|
face.loadFont(NotoSansBold15);
|
|
|
|
// Draw the whole clock - NTP time not available yet
|
|
renderFace(time_secs);
|
|
|
|
}
|
|
|
|
// =========================================================================
|
|
// Loop
|
|
// =========================================================================
|
|
void loop() {
|
|
char time[20];
|
|
static unsigned long targetTime = millis() + 1000;
|
|
static uint8_t lastMode = PinStatus::HIGH;
|
|
uint8_t val = PinStatus::HIGH;
|
|
|
|
// Update time periodically
|
|
if (targetTime < millis()) {
|
|
|
|
// Update next tick time in 100 milliseconds for smooth movement
|
|
targetTime = millis() + 1000;
|
|
|
|
// All graphics are drawn in sprite to stop flicker
|
|
renderFace(time_secs);
|
|
|
|
}
|
|
|
|
if ( mode > ModeNormal) {
|
|
if (!digitalRead(BtnFwd)) {
|
|
|
|
if ( mode == ModeHours ) {
|
|
time_secs += 60 * 60; // Add one minute
|
|
} else {
|
|
time_secs += 60; // Add one minute
|
|
}
|
|
|
|
// Midnight roll-over
|
|
if (time_secs >= (60 * 60 * 24)) { time_secs -= (60 * 60 * 24); }
|
|
renderFace(time_secs);
|
|
} else if (!digitalRead(BtnBack)) {
|
|
|
|
if ( mode == ModeHours ) {
|
|
time_secs -= 60 * 60; // Add one minute
|
|
} else {
|
|
time_secs -= 60; // Add one minute
|
|
}
|
|
|
|
// Midnight roll-over
|
|
if (time_secs < 0 ) { time_secs += (60 * 60 * 24); }
|
|
renderFace(time_secs);
|
|
}
|
|
}
|
|
|
|
val = digitalRead(BtnMode);
|
|
if (!val && val != lastMode) {
|
|
|
|
mode += 1;
|
|
if ( mode > 3) { mode = 0; }
|
|
delay(100);
|
|
}
|
|
lastMode = val;
|
|
|
|
}
|
|
|
|
// =========================================================================
|
|
// Draw the clock face in the sprite
|
|
// =========================================================================
|
|
static void renderFace(float t) {
|
|
char time[20];
|
|
float h_angle = t * HOUR_ANGLE;
|
|
float m_angle = t * MINUTE_ANGLE;
|
|
//float s_angle = t * SECOND_ANGLE;
|
|
long r = (unsigned long) time_secs;
|
|
int h = (int)(r/3600);
|
|
r = r%3600;
|
|
int m = (int)(r/60);
|
|
int s = (int)(r % 60);
|
|
int HandColor = CLOCK_FG;
|
|
|
|
// The face is completely redrawn - this can be done quickly
|
|
face.fillSprite(TFT_BLACK);
|
|
|
|
// Draw the face circle
|
|
face.fillSmoothCircle( CLOCK_XY, CLOCK_XY, CLOCK_R, CLOCK_BG );
|
|
|
|
// Set text datum to middle centre and the colour
|
|
face.setTextDatum(MC_DATUM);
|
|
|
|
// The background colour will be read during the character rendering
|
|
face.setTextColor(CLOCK_FG, CLOCK_BG);
|
|
|
|
// Text offset adjustment
|
|
constexpr uint32_t dialOffset = CLOCK_R - 10;
|
|
|
|
float xp = 0.0, yp = 0.0; // Use float pixel position for smooth AA motion
|
|
|
|
// Draw digits around clock perimeter
|
|
for (uint32_t h = 1; h <= 12; h++) {
|
|
getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, dialOffset, h * 360.0 / 12);
|
|
face.drawNumber(h, xp, 2 + yp);
|
|
}
|
|
|
|
/*
|
|
// Add text (could be digital time...)
|
|
face.setTextColor(LABEL_FG, CLOCK_BG);
|
|
sprintf(time,"%02d:%02d:%02d",h,m,s);
|
|
face.drawString(time, CLOCK_XY, CLOCK_XY * 0.75);
|
|
*/
|
|
if (mode == ModeMinutes) {
|
|
HandColor = HILITE_FG;
|
|
} else {
|
|
HandColor = CLOCK_FG;
|
|
}
|
|
// Draw minute hand
|
|
getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, M_HAND_LENGTH, m_angle);
|
|
face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 6.0f, HandColor);
|
|
// face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 2.0f, CLOCK_BG);
|
|
|
|
if (mode == ModeHours) {
|
|
HandColor = HILITE_FG;
|
|
} else {
|
|
HandColor = CLOCK_FG;
|
|
}
|
|
// Draw hour hand
|
|
getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, H_HAND_LENGTH, h_angle);
|
|
face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 6.0f, HandColor);
|
|
//face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 2.0f, CLOCK_BG);
|
|
|
|
// Draw the central pivot circle
|
|
face.fillSmoothCircle( CLOCK_XY, CLOCK_XY, 4, CLOCK_FG);
|
|
|
|
// Draw second hand
|
|
//getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, S_HAND_LENGTH, s_angle);
|
|
//face.drawWedgeLine( CLOCK_XY, CLOCK_XY, xp, yp, 2.5, 1.0, SECCOND_FG);
|
|
face.pushSprite(0,0, TFT_TRANSPARENT);
|
|
}
|
|
|
|
// =========================================================================
|
|
// Get coordinates of end of a line, pivot at x,y, length r, angle a
|
|
// =========================================================================
|
|
// Coordinates are returned to caller via the xp and yp pointers
|
|
#define DEG2RAD 0.0174532925
|
|
void getCoord(int16_t x, int16_t y, float *xp, float *yp, int16_t r, float a)
|
|
{
|
|
float sx1 = cos( (a - 90) * DEG2RAD);
|
|
float sy1 = sin( (a - 90) * DEG2RAD);
|
|
*xp = sx1 * r + x;
|
|
*yp = sy1 * r + y;
|
|
}
|