Dot-Matrix Timescale Clock
15th April 2025
This Timescale Clock is an analogue clock with a difference – it displays the time on a horizontal scale. The scale scrolls slowly to the left, so you can see the time to within five minutes by the position of a fixed pointer in the centre of the scale:
The Timescale clock displays the time as a scrolling scale
on two bi-colour dot matrix displays; it's 9:35.
The clock is an alternative program for my Prime Time clock circuit, described recently, which displayed the time as four digits on two 8x8 bi-colour LED dot matrix displays. This alternative firmware for the circuit shows the time in a completely different way, for people who don't need to know the time to the nearest minute, for a more leisurely approach to life!
Introduction
This Timescale Clock is based on my earlier implementation of the same concept: Timescale Clock. The idea was inspired by two observations:
- You don't need the minute hand to be able to tell the time on a traditional circular analogue clock.
- If there's no minute hand, you only need to see the part of the dial close to the hour hand.
The display shows just the relevant section of the clock face as a linear scale, a bit like the scale on some mechanical weighing machines. A red pixel on the bottom row is the pointer to the current time. The scale above it marks every five minutes, with a yellow dot every quarter of an hour. Above that the centre of each hour number indicates the hour. There is always one hour number visible, and sometimes two as in the above photograph.
Setting the time
As with the earlier Prime Time clock you can set the time using the button on the back of the clock. When you press the button the display changes to a 24-hour digital clock. The first time you hold it down the hours advance twice a second. Releasing it and pressing it again advances the minutes.
Construction
The Timescale Clock uses the same circuit and PCB as the Prime Time clock, so refer to that article for details, and instructions for uploading the firmware: Prime Time.
The program
The program is substantially the same as for the Prime Time clock, apart from the following details:
Character set
The Timescale Clock uses a 3x5 pixel character set, rather then the 3x7 pixel digits used on the Prime Time clock, to leave space for the scale along the bottom of the displays. The digits are actually defined as a 5x5 block, and the definitions include the two-digit hours 10, 11, and 12:
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 };
This simplified the part of the program that displays the hour, and avoids the need for several if statements.
Display auto-polarity detection
The original Prime Time clock lets you specify whether your dot-matrix displays are common cathode or common anode with a #define at the start of the program. However, when I was testing the prototype Timescale Clock with different dot-matrix displays I got annoyed with having to upload a different version of the firmware for different displays, and wondered why the program couldn't auto-detect the type of display. It turned out to be quite simple:
At startup the program configures one LED as follows:
- A common pin such as PA0 is configured as input pullup.
- The other side of the chosen LED, PC0 in this example, is configured as an output and taken low.
Detecting whether a display is common cathode or common anode by testing
whether an LED conducts with a particular polarity.
If the display is common anode the LED will conduct and PA0 will be pulled low. If it's common cathode the LED won't conduct and PA0 stays pulled high by the pullup. Reading PA0 tells us the polarity of the display.
The configuration uses three global variables: PortAout, PortDout, and CommonOut:
register8_t *PortAout, *PortDout; // For display auto-polarity uint8_t CommonOut;
These are set by the AutoPolarity() test routine:
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); }
Display routine
The display routine then references the display ports via the variables PortAout and PortDout, and the CommonOut value:
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 }
I plan to go back and add this enhancement to the Prime Time program too.
Generating the timescale display
Finally, here's the routine to generate the timescale display:
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; } }
For each of the 16 display columns, corresponding to 5-minute steps, it draws the following components of the display:
- The timescale bar at the bottom of the display, consisting of a yellow pixel at the 15-minute marks and a green pixel otherwise.
- The current time pointer as a red pixel.
- The five columns of the hour, if it is visible in the current column.
Because the current column is being drawn in stages, the variables column0 and column1 are used to calculate the red and green components of the column, and these are written to the display once, when the changes are complete; otherwise the display flickers due to the intermediate states. This wasn't necessary with the Prime Time clock because each column was only written once.
Resources
Here's the Timescale Clock program: Timescale Clock Program.
It uses the same PCB as Prime Time; get the files here: https://github.com/technoblogy/prime-time.
Or order boards from OSH Park here: Prime Time.
blog comments powered by Disqus