John Bradnam
Published © GPL3+

Timescale Clock

A interesting presentation of a clock that shows the current time on a linear time line.

IntermediateFull instructions provided8 hours646
Timescale Clock

Things used in this project

Hardware components

Microchip ATtiny1614
×1
32.768 kHz Crystal
32.768 kHz Crystal
×1
TP4056 LiPo Charging module
×1
Passive components
2 x 22pf 0805 ceramic capacitors, 1 x 0.1uF 0805 ceramic capacitor
×1
Monochrome 0.91”128x32 I2C OLED Display with Chip Pad
DFRobot Monochrome 0.91”128x32 I2C OLED Display with Chip Pad
×1
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
9mm shaft
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

STL files for 3D printing

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle format

Code

TimescaleClockV3.ino

Arduino
/*============================================================================================================
  Timescale Clock
  
  CPU:  ATtiny1614
  Display: 128X32 OLED
 
  ATtiny1614 Code: jbrad2089@gmail.com
  Concept and code: David Johnson-Davies - www.technoblogy.com - 8th November 2014

  2022-06-20 - Modified code for ATtiny1614

  BOARD: ATtiny1614/1604/814/804/414/404/214/204
  Chip: ATtiny1614
  Clock Speed: 20MHz
  Programmer: jtag2updi (megaTinyCore)

  ATTiny1614 Pins mapped to Ardunio Pins

             +--------+
         VCC + 1   14 + GND
 (SS)  0 PA4 + 2   13 + PA3 10 (SCK)
       1 PA5 + 3   12 + PA2 9  (MISO)
 (DAC) 2 PA6 + 4   11 + PA1 8  (MOSI)
       3 PA7 + 5   10 + PA0 11 (UPDI)
 (RXD) 4 PB3 + 6    9 + PB0 7  (SCL)
 (TXD) 5 PB2 + 7    8 + PB1 6  (SDA)
             +--------+

  
==============================================================================================================*/

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <avr/sleep.h>
#include "symbols.h"

#define HOUR_BTN 0  //PA4 D0
#define MIN_BTN 1   //PA5 D1

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

bool updateDisplay;             //Used to force display update
volatile unsigned long Time;    // In minutes
volatile unsigned long Seconds; // In seconds
uint8_t lastMinute;             // Used to test change

//---------------------------------------------------------------------
// Real-Time Clock Setup
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 registers synchronized

  //My external crystal must be bad
  //RTC.CLKSEL = RTC_CLKSEL_TOSC32K_gc;                 // 32.768kHz External Crystal Oscillator  

  //So I used the internal oscillator instead
  RTC.CLKSEL = RTC_CLKSEL_INT32K_gc;                    // 32.768kHz Internal Oscillator  
  
  RTC.PITINTCTRL = RTC_PI_bm;                           //Periodic Interrupt: enabled
  RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | RTC_PITEN_bm; //RTC Clock Cycles 32768, resulting in 32.768kHz/32768 = 1Hz and enable
}

//---------------------------------------------------------------------
//RTC interrupt occurs every second
ISR(RTC_PIT_vect)
{
  if (Seconds < 60)
  {
    Seconds++;
  }
  else
  {
    Seconds = 0;
    Time++;
  }
  RTC.PITINTFLAGS = RTC_PI_bm;          //Clear flag by writing '1'
}

//---------------------------------------------------
// Hardware setup
void setup() 
{
retry:  
 
  //Serial.begin(9600);
  //LEFT button Interrupt will wake up from sleep mode
  pinMode(HOUR_BTN, INPUT_PULLUP);
  pinMode(MIN_BTN, INPUT_PULLUP);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(0, 0x3C)) { // Address 0x3D for 128x64
    //system_sleep();
    goto retry;
  }

  Seconds = 0;
  Time = 0;
  RTCSetup();
  updateDisplay = true;
}

//---------------------------------------------------
// Primary loop
void loop()
{
  int delta = 100;

  // Read set-time buttons
  if (!digitalRead(MIN_BTN)) 
  {
    Time = (Time + 1) % 720;
    updateDisplay = true;
  }
  else if (!digitalRead(HOUR_BTN)) 
  {
    Time = (Time + 60) % 720;
    updateDisplay = true;
    delta = 250;
  }
  
  int Minute = Time % 60;
  if (updateDisplay || Minute != lastMinute)
  {
    lastMinute = Minute;
    int Hour = (Time / 60) % 12;
    
    // Write the time scale
    display.clearDisplay();
    displayPointer();
    displayTimescale(Minute);
    
    // Previous hour
    displayDigit(64 - Minute - 8, Hour);
    
    // Next hour
    displayDigit(124 - Minute - 8, (Hour+1)%12);

    // Update screen
    display.display();
    delay(delta);
  }
}

//---------------------------------------------------
// Displays a byte vertically
void displayVerticalByte(int8_t y,int8_t x,uint8_t b)
{
  uint8_t mask = 0x80;
  for(int i = 0; i < 8; i++)
  {
    display.drawPixel (x, y+i, (b & mask) ? SSD1306_WHITE : SSD1306_BLACK);
    mask = mask >> 1;
  }
}

//---------------------------------------------------
// Displays a fixed pointer in the centre of the bottom page of the display
void displayPointer() 
{
  displayVerticalByte(24,61,0b00100000);
  displayVerticalByte(24,62,0b01100000);
  displayVerticalByte(24,63,0b11111111);
  displayVerticalByte(24,64,0b01100000);
  displayVerticalByte(24,65,0b00100000);
}

//---------------------------------------------------
// Display 16x16 digit starting at column x
void displayDigit (int x, int hour) {
  int offset = (hour<<5);
  // Do top and bottom half
  for (int h=0; h<2; h++) 
  {
    int x1 = x;
    for (int b = h ; b < 32; b+=2)
    {
      if (x1 >= 0 && x1 < 128)
      {
        displayVerticalByte(h*8,x1,pgm_read_byte(&ClockDigits[offset+b]));
      }
      x1++;
    }
    displayVerticalByte(0,x1,0x00);
  }
}

//---------------------------------------------------
// Draw timescale
void displayTimescale(int minute) 
{
  int m = (minute + 120 - 64) % 60;
  for (int x = 0; x < 128; x++) 
  {
    if (m%60 == 0)
    {
      displayVerticalByte(16,x,0b11111111);
    }
    else if (m%30 == 0)
    {
      displayVerticalByte(16,x,0b00111111);
    }
    else if (m%15 == 0)
    {
      displayVerticalByte(16,x,0b00001111);
    }
    else if (m%5 == 0)
    {
      displayVerticalByte(16,x,0b00000011);
    }
    else
    {
      displayVerticalByte(16,x,0b00000001);
    }
    m++;
  }
}

symbols.h

C Header File
#pragma once

#define BLANK 12
const uint8_t ClockDigits[] PROGMEM = {
  // 12
  0x08, 0x02, 0x08, 0x02, 0x1f, 0xfe, 0x1f, 0xfe, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
  0x0c, 0x02, 0x10, 0x06, 0x10, 0x0a, 0x10, 0x32, 0x10, 0x62, 0x1f, 0x82, 0x0e, 0x06, 0x00, 0x08,
  // 1
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x02, 0x08, 0x02, 0x1f, 0xfe,
  0x1f, 0xfe, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  // 2
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x02, 0x10, 0x06, 0x10, 0x0a, 0x10, 0x32,
  0x10, 0x62, 0x1f, 0x82, 0x0e, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  // 3
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x06, 0x10, 0x82, 0x10, 0x82,
  0x11, 0x82, 0x1e, 0xc4, 0x0c, 0xf8, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  // 4
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x70, 0x00, 0x90, 0x03, 0x12, 0x04, 0x12,
  0x0f, 0xfe, 0x1f, 0xfe, 0x00, 0x12, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  // 5
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x07, 0x86, 0x18, 0x82, 0x10, 0x82,
  0x10, 0x82, 0x10, 0xc4, 0x10, 0x78, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  // 6
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x01, 0xfc, 0x07, 0x86, 0x04, 0x82,
  0x08, 0x82, 0x10, 0xc2, 0x10, 0x7c, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  // 7
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x10, 0x02, 0x10, 0x0e, 0x10, 0x38,
  0x10, 0xe0, 0x13, 0x80, 0x1e, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  // 8
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x18, 0x0f, 0x3c, 0x11, 0xc6, 0x10, 0x82,
  0x10, 0xc2, 0x0f, 0x62, 0x06, 0x7c, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  // 9
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x0f, 0x82, 0x10, 0xc2, 0x10, 0x44,
  0x10, 0x4c, 0x18, 0x78, 0x0f, 0xf0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  // 10
  0x08, 0x02, 0x08, 0x02, 0x1f, 0xfe, 0x1f, 0xfe, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
  0x01, 0xf0, 0x0f, 0xfc, 0x18, 0x06, 0x10, 0x02, 0x10, 0x02, 0x18, 0x06, 0x0f, 0xfc, 0x03, 0xe0,
  // 11
  0x08, 0x02, 0x08, 0x02, 0x1f, 0xfe, 0x1f, 0xfe, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x08, 0x02, 0x08, 0x02, 0x1f, 0xfe, 0x1f, 0xfe, 0x00, 0x02, 0x00, 0x02,
  // Blank
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

Credits

John Bradnam

John Bradnam

145 projects • 179 followers

Comments