/* TFT Analogue Clock - see http://www.technoblogy.com/show?3ZWB David Johnson-Davies - www.technoblogy.com - 11th October 2022 ATtiny814 @ 20 MHz (internal oscillator; BOD disabled) CC BY 4.0 Licensed under a Creative Commons Attribution 4.0 International license: http://creativecommons.org/licenses/by/4.0/ */ // ATtiny814 PORTA positions. int const dc = 7; int const mosi = 1; int const sck = 3; int const cs = 4; // ATtiny 0-, 1-, and 2-series port manipulations - assumes all pins in same port #define PORT_TOGGLE(x) PORTA.OUTTGL = (x) #define PORT_LOW(x) PORTA.OUTCLR = (x) #define PORT_HIGH(x) PORTA.OUTSET = (x) #define PORT_OUTPUT(x) PORTA.DIRSET = (x) #define PORT_INPUT(x) PORTA.DIRCLR = (x) #define PORT_IN PORTA.IN #define swap(a, b) { a = a ^ b; b = b ^ a; a = a ^ b; } // Display parameters - uncomment the line for the one you want to use // AliExpress 1.54" 240x240 display - edge connector on left // int const xsize = 240, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 0, bgr = 0; // AliExpress 1.54" 240x240 display - edge connector at top int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; // AliExpress 2.4" 320x240 display // int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 2, bgr = 1; // Character set for digits - stored in program memory const uint8_t CharMap[32][6] PROGMEM = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 }, { 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 }, { 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 }, { 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 }, { 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 }, { 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 }, { 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 }, { 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 }, { 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 }, { 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 }, { 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 }, { 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 }, { 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, { 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, { 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 }, { 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 }, { 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 }, { 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 }, { 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 }, { 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 }, { 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 }, { 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 }, { 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 }, { 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 }, { 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 }, { 0x00, 0x40, 0x34, 0x00, 0x00, 0x00 }, { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, { 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 }, { 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 }, { 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 }, }; // TFT colour display ********************************************** int const CASET = 0x2A; // Define column address int const RASET = 0x2B; // Define row address int const RAMWR = 0x2C; // Write to display RAM int const RAMRD = 0x2E; // Read from display RAM int const White = 0xFFFF; int const Black = 0; // Current plot position and colours int xpos, ypos; int fore = White; int back = Black; int scale = 1; // Text scale int exor = false; // Plotting mode // Send a byte to the display void Data (uint8_t d) { for (uint8_t bit = 0x80; bit; bit >>= 1) { PORT_TOGGLE(1<>8); Data(d1); Data(d2>>8); Data(d2); } void InitDisplay () { PORT_OUTPUT(1<>3; } // Move current plot position to x,y void MoveTo (int x, int y) { xpos = x; ypos = y; } // Plot point at x,y void PlotPoint (int x, int y) { uint16_t pixel = 0; if (exor) pixel = GetPoint(x, y); PORT_TOGGLE(1<>8); Data((fore^pixel) & 0xff); PORT_TOGGLE(1< -dy) { err = err - dy; xpos = xpos + sx; } if (e2 < dx) { err = err + dx; ypos = ypos + sy; } } } void FillRect (int w, int h) { PORT_TOGGLE(1<>8; uint8_t lo = fore & 0xff; for (int i=0; i= y) { MoveTo(x1-x, y1+y); FillRect(x<<1, 1); MoveTo(x1-y, y1+x); FillRect(y<<1, 1); MoveTo(x1-y, y1-x); FillRect(y<<1, 1); MoveTo(x1-x, y1-y); FillRect(x<<1, 1); if (err > 0) { x = x - 1; dx = dx + 2; err = err - (radius<<1) + dx; } else { y = y + 1; err = err + dy; dy = dy + 2; } } xpos = x1; ypos = y1; } void DrawCircle (int radius) { int x1 = xpos, y1 = ypos, dx = 1, dy = 1; int x = radius - 1, y = 0; int err = dx - (radius<<1); while (x >= y) { PlotPoint(x1-x, y1+y); PlotPoint(x1+x, y1+y); PlotPoint(x1-y, y1+x); PlotPoint(x1+y, y1+x); PlotPoint(x1-y, y1-x); PlotPoint(x1+y, y1-x); PlotPoint(x1-x, y1-y); PlotPoint(x1+x, y1-y); if (err > 0) { x = x - 1; dx = dx + 2; err = err - (radius<<1) + dx; } else { y = y + 1; err = err + dy; dy = dy + 2; } } } // Plot an ASCII character with bottom left corner at x,y void PlotChar (char c) { int colour; PORT_TOGGLE(1<>(7-yy) & 1) colour = fore; else colour = back; for (int yr=0; yr>8); Data(colour & 0xFF); } } } } PORT_TOGGLE(1<0; d = d/10) { char j = (n/d) % 10; if (j!=0 || lead || d==1) { PlotChar(j + '0'); lead = true; } } } void TestChart () { DrawRect(xsize, ysize); scale = 8; fore = Colour(255, 0, 0); MoveTo((xsize-40)/2, (ysize-64)/2); PlotChar('F'); scale = 1; } // Filled quadrilateral void FillQuad(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, int16_t x3, int16_t y3) { // Sort coordinates by y order (y3 >= y2 >= y1 >= y0) if (y0 > y1) { swap(y0, y1); swap(x0, x1); } if (y2 > y3) { swap(y2, y3); swap(x2, x3); } if (y1 > y3) { swap(y1, y3); swap(x1, x3); } if (y0 > y2) { swap(y0, y2); swap(x0, x2); } if (y1 > y2) { swap(y1, y2); swap(x1, x2); } int16_t a, b, y; int16_t x4 = x0 + (x2 - x0) * (y1 - y0) / (y2 - y0); int16_t x5 = x1 + (x3 - x1) * (y2 - y1) / (y3 - y1); // Fill bottom section for (y = y0; y <= y1; y++) { a = x0 + (x4 - x0) * (y - y0) / (y1 - y0); b = x0 + (x1 - x0) * (y - y0) / (y1 - y0); if (a > b) swap(a, b); MoveTo(a, y); DrawTo(b, y); } // Fill middle section for (; y <= y2; y++) { a = x4 + (x2 - x4) * (y - y1) / (y2 - y1); b = x1 + (x5 - x1) * (y - y1) / (y2 - y1); if (a > b) swap(a, b); MoveTo(a, y); DrawTo(b, y); } // Fill top section for (; y <= y3; y++) { a = x2 + (x3 - x2) * (y - y2) / (y3 - y2); b = x5 + (x3 - x5) * (y - y2) / (y3 - y2); if (a > b) swap(a, b); MoveTo(a, y); DrawTo(b, y); } } // Read from display ********************************************** uint16_t GetPoint (uint16_t x, uint16_t y) { uint32_t pixel = 0; PORT_TOGGLE(1<>mosi & 1); if (xsize > 162) PORT_TOGGLE(1<>5) & 0xf800) | ((pixel>>2) & 0x7e0) | (pixel & 0x1f); } // Clock ********************************************** // Colours #define PINK Colour(255, 128, 0) #define RED Colour(255, 0, 0) #define GREEN Colour(0, 255, 0) #define YELLOW Colour(255, 255, 0) #define BLUE Colour(0, 0, 255) #define DARKBLUE Colour(0, 0, 128) // Draw a diamond-shaped hand from x0,y0 by x,y void DrawHand(int x0, int y0, int x, int y) { int v = x/2, u = y/2, w = v/5, t = u/5; FillQuad(x0, y0, x0+v-t, x0+u+w, x0+x, x0+y, x0+v+t, x0+u-w); } const int top = 10; const int bot = 191; const int sca = 4; void ClockFace () { int x0 = 120, y0 = 120, radius = 120; MoveTo(x0, y0); fore = BLUE; DrawCircle(radius); radius = radius - 2; fore = DARKBLUE; FillCircle(radius); int x = 0, y = 118<>sca), y0+(y>>sca)); DrawTo(x0 + ((x*15)>>(sca+4)), y0 + ((y*15)>>(sca+4))); scale = 2; MoveTo(x0 + ((x>>sca)*13/16) - 3*(1+(i==0))*2, y0 + ((y>>sca)*13/16) - 8); fore = GREEN; back = DARKBLUE; if (i==0) PlotInt(12); else PlotInt(i/5); scale = 1; } for (int i=2;i--;) { x = x + (y*top)/bot; y = y - (x*top)/bot; } } } // Real-Time Clock ********************************************** enum state { NONE, UNDRAW, DRAW, BOTH } ; void RTCSetup () { uint8_t temp; // Initialize 32.768kHz Oscillator: // Disable oscillator: temp = CLKCTRL.XOSC32KCTRLA & ~CLKCTRL_ENABLE_bm; // Enable writing to protected register CPU_CCP = CCP_IOREG_gc; CLKCTRL.XOSC32KCTRLA = temp; while (CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm); // Wait until XOSC32KS is 0 temp = CLKCTRL.XOSC32KCTRLA & ~CLKCTRL_SEL_bm; // Use External Crystal // Enable writing to protected register CPU_CCP = CCP_IOREG_gc; CLKCTRL.XOSC32KCTRLA = temp; temp = CLKCTRL.XOSC32KCTRLA | CLKCTRL_ENABLE_bm; // Enable oscillator // Enable writing to protected register CPU_CCP = CCP_IOREG_gc; CLKCTRL.XOSC32KCTRLA = temp; // Initialize RTC while (RTC.STATUS > 0); // Wait until synchronized // 32.768kHz External Crystal Oscillator (XOSC32K) RTC.CLKSEL = RTC_CLKSEL_TOSC32K_gc; // RTC Clock Cycles 32768, enabled ie 1Hz interrupt RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc; RTC.PITINTCTRL = RTC_PI_bm; // Periodic Interrupt: enabled } // Interrupt Service Routine called every second ISR(RTC_PIT_vect) { RTC.PITINTFLAGS = RTC_PI_bm; // Clear interrupt flag NextSecond(BOTH); } void NextSecond (int draw) { int x0 = 120, y0 = 120; // Positions of hands static int secx = 0, secy = 118<>sca), y0+(secy>>sca)); } for (int i=2;i--;) { secx = secx + (secy*top)/bot; secy = secy - (secx*top)/bot; } if (secs == 59) { secx = 0, secy = 118<>sca), y0+(secy>>sca)); } // Advance hour hand every 12 mins if (secs == 59 && mins%12 == 0) { fore = RED; DrawHand(x0, y0, hrx>>sca, hry>>sca); for (int i=2;i--;) { hrx = hrx + (hry*top)/bot; hry = hry - (hrx*top)/bot; } } else if (secs == 0 && mins%12 == 0) { fore = RED; DrawHand(x0, y0, hrx>>sca, hry>>sca); } // Advance minute hand every 60 secs if (secs == 59) { fore = PINK; if (draw & UNDRAW) DrawHand(x0, y0, minx>>sca, miny>>sca); for (int i=2;i--;) {minx = minx + (miny*top)/bot; miny = miny - (minx*top)/bot; } } else if (secs == 0) { fore = PINK; if (mins == 0) minx = 0, miny = 118<>sca, miny>>sca); mins = (mins + 1)%60; } secs = (secs + 1)%60; } void SetTime (int hour, int minute) { uint32_t secs = (uint32_t)(hour * 60 + minute) * 60; for (uint32_t i=0; i