/* Prime Time v2 - see http://www.technoblogy.com/show?547T David Johnson-Davies - www.technoblogy.com - 8th March 2025 AVR128DA32/ATmega1608 @ 4 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/ */ #define PRIMETIME // Prime clock #define TWELVEHOUR // 12-hour clock; comment out for 24 hour #define SQUARED // Squared displays; comment out for rounded #define COMMONCATHODE // Comment out for common anode #ifdef COMMONCATHODE #define PORTAOUT PORTA.OUTCLR #define PORTDOUT PORTD.OUTCLR #define COMMONOUT HIGH #else #define PORTAOUT PORTA.OUTSET #define PORTDOUT PORTD.OUTSET #define COMMONOUT LOW #endif // Globals volatile int Digits = 0; // Time display as 4-digit number int Lastdigits = 0; int ButtonState = 2; // Push button state // Dot-matrix character definitions #ifdef SQUARED char CharMap[10][3] = { { 0x7F, 0x41, 0x7F }, // 0 { 0x00, 0x02, 0x7F }, // 1 { 0x79, 0x49, 0x4F }, // 2 { 0x49, 0x49, 0x7F }, // 3 { 0x0F, 0x08, 0x7F }, // 4 { 0x4F, 0x49, 0x79 }, // 5 { 0x7F, 0x49, 0x79 }, // 6 { 0x01, 0x01, 0x7F }, // 7 { 0x7F, 0x49, 0x7F }, // 8 { 0x4F, 0x49, 0x7F }, // 9 }; #else // Alternative rounded digits char CharMap[10][3] = { { 0x3E, 0x41, 0x3E }, // 0 { 0x00, 0x02, 0x7F }, // 1 { 0x72, 0x49, 0x46 }, // 2 { 0x2A, 0x49, 0x36 }, // 3 { 0x0C, 0x0A, 0x7F }, // 4 { 0x2F, 0x49, 0x39 }, // 5 { 0x3E, 0x49, 0x32 }, // 6 { 0x71, 0x09, 0x07 }, // 7 { 0x36, 0x49, 0x36 }, // 8 { 0x06, 0x09, 0x7E }, // 9 }; #endif const int Colon = 0x14; // Display multiplexer ********************************************** enum Schemes { BICOLOUR, RED, GREEN }; const int Npins = 24; const int Ncolumns = 16; const int Ndigits = 4; uint8_t Pin[Npins] = { PIN_PA0, PIN_PA1, PIN_PA2, PIN_PA3, PIN_PA4, PIN_PA5, PIN_PA6, PIN_PA7, PIN_PC0, PIN_PC1, PIN_PC2, PIN_PC3, PIN_PF2, PIN_PF3, PIN_PF4, PIN_PF5, PIN_PD0, PIN_PD1, PIN_PD2, PIN_PD3, PIN_PD4, PIN_PD5, PIN_PD6, PIN_PD7 }; uint8_t Column[Ncolumns]; // 8-bit pattern for each column uint8_t Colour[3][Ncolumns] = { // Colour for each column { 1, 1, 1, 1, 1, 1, 1, 3, 2, 2, 2, 2, 2, 2, 2, 2 }, // Hours red, minutes green (with yellow colon) { 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1 }, // All red (with yellow colon) { 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2 }, // All green (with yellow colon) }; #ifdef PRIMETIME int Scheme = RED; #else int Scheme = BICOLOUR; #endif void DisplaySetup () { TCB0.CCMP = 1249; // Divide 4MHz by 1250 = 3.2kHz TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; // Enable timer, divide by 1 TCB0.CTRLB = 0; // Periodic Interrupt Mode CPUINT.LVL1VEC = 12; // Give TCB0 high priority TCB0.INTCTRL = TCB_CAPT_bm; // Enable interrupt } // Timer/Counter TCB interrupt - multiplexes the display ISR(TCB0_INT_vect) { TCB0.INTFLAGS = TCB_CAPT_bm; // Clear the interrupt flag DisplayNextPin(); } void DisplayNextPin() { static uint8_t pin; PORTA.DIRCLR = 0xFF; // All I/O pins as inputs PORTC.DIRCLR = 0x0F; PORTD.DIRCLR = 0xFF; PORTF.DIRCLR = 0x3C; pin = (pin+1) % 32; uint8_t band = pin / 8; uint8_t slice = pin % 8; uint8_t pin2 = pin; switch (band) { // Handle the charlieplexing case 0: // Red RH display if (Colour[Scheme][8+slice] & RED) { PORTDOUT = Column[8+slice]; PORTD.DIRSET = Column[8+slice]; } break; case 1: // Red LH display if (Colour[Scheme][slice] & RED) { PORTAOUT = Column[slice]; PORTA.DIRSET = Column[slice]; } break; case 2: // Green LH display if (Colour[Scheme][slice] & GREEN) { PORTAOUT = Column[slice]; PORTA.DIRSET = Column[slice]; } break; case 3: // Green RH display if (Colour[Scheme][8+slice] & GREEN) { PORTDOUT = Column[8+slice]; PORTD.DIRSET = Column[8+slice]; } pin2 = pin - 16; } pinMode(Pin[pin2], OUTPUT); digitalWrite(Pin[pin2], COMMONOUT); // Take this column high/low } // Set time button ********************************************** void ButtonSetup () { PORTF.PIN6CTRL = PORT_PULLUPEN_bm; // PF6 input pullup } boolean ButtonDown () { return (PORTF.IN & PIN6_bm) == 0; // True if button pressed } // Real-Time Clock ********************************************** void RTCSetup () { uint8_t temp; temp = CLKCTRL.XOSC32KCTRLA & ~CLKCTRL_ENABLE_bm; // Disable oscillator: CPU_CCP = CCP_IOREG_gc; // Write to protected register CLKCTRL.XOSC32KCTRLA = temp; while (CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm); // Wait until XOSC32KS is 0 temp = CLKCTRL.XOSC32KCTRLA & ~CLKCTRL_SEL_bm; // Use External Crystal CPU_CCP = CCP_IOREG_gc; // Write to protected register CLKCTRL.XOSC32KCTRLA = temp; temp = CLKCTRL.XOSC32KCTRLA | CLKCTRL_ENABLE_bm; // Enable oscillator CPU_CCP = CCP_IOREG_gc; // Write to protected register CLKCTRL.XOSC32KCTRLA = temp; while (RTC.STATUS > 0); // Synchronize registers RTC.CLKSEL = RTC_CLKSEL_TOSC32K_gc; // 32.768kHz External Crystal RTC.PITINTCTRL = RTC_PI_bm; // Enable periodic interrupt RTC.PITCTRLA = RTC_PERIOD_CYC16384_gc | RTC_PITEN_bm; } void PlotTime12hr (int digits) { if (digits/1000 == 1) { Column[0] = CharMap[1][1]; Column[1] = CharMap[1][2]; } else { Column[0] = 0; Column[1] = 0; } for (uint8_t col=0; col<3; col++) { Column[col+3] = CharMap[(digits/100)%10][col]; Column[col+9] = CharMap[(digits/10)%10][col]; Column[col+13] = CharMap[digits%10][col]; } } void PlotTime24hr (int digits) { for (uint8_t col=0; col<3; col++) { Column[col] = CharMap[digits/1000][col]; Column[col+4] = CharMap[(digits/100)%10][col]; Column[col+9] = CharMap[(digits/10)%10][col]; Column[col+13] = CharMap[digits%10][col]; } } // Interrupt Service Routine called twice a second ISR(RTC_PIT_vect) { static unsigned long time; // In half seconds uint8_t minutes, hours; RTC.PITINTFLAGS = RTC_PI_bm; // Clear interrupt flag minutes = (time / 120) % 60; hours = (time / 7200) % 24; // Internal time 24-hour if (ButtonDown()) { if (ButtonState == 1 || ButtonState == 3) { ButtonState = (ButtonState + 1) % 4; } if (ButtonState == 0) { // Advance hours hours = (hours + 1) % 24; } else { // Advance minutes minutes = (minutes + 1) % 60; } time = (unsigned long)hours * 7200 + minutes * 120; } else { // Button up if (ButtonState == 0 || ButtonState == 2) { ButtonState = (ButtonState + 1) % 4; } time = (time + 1) % 172800; // Wrap around after 24 hours } #ifdef TWELVEHOUR if (hours > 12) Digits = (hours-12)*100 + minutes; else if (hours < 1) Digits = (hours+12)*100 + minutes; else Digits = hours*100 + minutes; PlotTime12hr(Digits); if (time & 2) Column[7] = Colon; else Column[7] = 0; // Pulsing colon #else Digits = hours*100 + minutes; PlotTime24hr(Digits); #endif } // Prime test ********************************************** bool Prime (int n) { if (n % 2 == 0 || n % 3 == 0) return false; int d = 5, i = 2; while (d * d <= n) { if (n % d == 0) return false; d = d + i; i = 6 - i; } return true; } // Setup ********************************************** void setup () { DisplaySetup(); RTCSetup(); ButtonSetup(); } void loop () { #ifdef PRIMETIME if (Lastdigits != Digits) { // Check once a minute if (Prime(Digits)) Scheme = GREEN; else Scheme = RED; Lastdigits = Digits; } delay(100); #endif }