/* Timescale Clock - see http://www.technoblogy.com/show?55KG David Johnson-Davies - www.technoblogy.com - 15th April 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/ */ // Globals int ButtonState = 2; // Push button state register8_t *PortAout, *PortDout; // For display auto-polarity uint8_t CommonOut; // Dot-matrix character definitions char CharMap[13][5] = { { 0x00, 0x1F, 0x11, 0x1F, 0x00 }, // 0 { 0x00, 0x00, 0x1F, 0x00, 0x00 }, // 1 { 0x00, 0x1D, 0x15, 0x17, 0x00 }, // 2 { 0x00, 0x15, 0x15, 0x1F, 0x00 }, // 3 { 0x00, 0x07, 0x04, 0x1F, 0x00 }, // 4 { 0x00, 0x17, 0x15, 0x1D, 0x00 }, // 5 { 0x00, 0x1F, 0x15, 0x1D, 0x00 }, // 6 { 0x00, 0x01, 0x01, 0x1F, 0x00 }, // 7 { 0x00, 0x1F, 0x15, 0x1F, 0x00 }, // 8 { 0x00, 0x17, 0x15, 0x1F, 0x00 }, // 9 { 0x1F, 0x00, 0x1F, 0x11, 0x1F }, // 10 { 0x00, 0x1F, 0x00, 0x1F, 0x00 }, // 11 { 0x1F, 0x00, 0x1D, 0x15, 0x17 }, // 12 }; // Display multiplexer ********************************************** 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[2][Ncolumns]; // 8-bit patterns for red and green for each column void AutoPolarity () { pinMode(PIN_PA0, INPUT_PULLUP); pinMode(PIN_PC0, OUTPUT); digitalWrite(PIN_PC0, LOW); if (digitalRead(PIN_PA0) == HIGH) { // Common cathode PortAout = &PORTA.OUTCLR; PortDout = &PORTD.OUTCLR; CommonOut = HIGH; } else { // Common anode PortAout = &PORTA.OUTSET; PortDout = &PORTD.OUTSET; CommonOut = LOW; } pinMode(PIN_PA0, INPUT); pinMode(PIN_PC0, INPUT); } 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 *PortDout = Column[0][8+slice]; PORTD.DIRSET = Column[0][8+slice]; break; case 1: // Red LH display *PortAout = Column[0][slice]; PORTA.DIRSET = Column[0][slice]; break; case 2: // Green LH display *PortAout = Column[1][slice]; PORTA.DIRSET = Column[1][slice]; break; case 3: // Green RH display *PortDout = Column[1][8+slice]; PORTD.DIRSET = Column[1][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 PlotTime24hr (int digits) { for (uint8_t i=0; i<16; i++) Column[0][i] = Column[1][i] = 0; // Clear display for (uint8_t col=1; col<4; col++) { Column[1][col-1] = CharMap[digits/1000][col]; Column[1][col+3] = CharMap[(digits/100)%10][col]; Column[1][col+8] = CharMap[(digits/10)%10][col]; Column[1][col+12] = CharMap[digits%10][col]; } } void PlotTimescale (int t) { int origin = (t + 720 - 40)% 720; // Minutes from left-hand end uint8_t column0, column1; for (uint8_t i=0; i<16; i++) { int d = origin + i*5; // Minutes for column i if ((d/5)%3 == 0) { // Bar along the bottom column0 = 0x40; column1 = 0x40; // Yellow } else { column0 = 0; column1 = 0x40; // Green } if (i == 8) column0 = column0 | 0x80; // Current time pointer uint8_t hour = ((d+10)/60)%12, offset = (d/5)%12; // Hour digits if (hour == 0) hour = 12; for (uint8_t j=0, col=10; j<5; j++, col = (col+1)%12) { if (offset == col) { column0 = column0 | CharMap[hour][j]; } } Column[0][i] = column0; Column[1][i] = column1; } } // 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 } if (ButtonState == 1 || ButtonState == 3) PlotTimescale(hours*60 + minutes); else PlotTime24hr(hours*100 + minutes); } // Setup ********************************************** void setup () { AutoPolarity(); DisplaySetup(); RTCSetup(); ButtonSetup(); } void loop () { }