► Games

► Sound & Music

► Watches & Clocks


► Power Supplies

► Computers

► Graphics

► Thermometers

► Wearables

► Test Equipment

► Tutorials

► Libraries

► PCB-Based Projects

By processor

AVR ATtiny

► ATtiny10

► ATtiny2313

► ATtiny84

► ATtiny841

► ATtiny85

► ATtiny861

► ATtiny88

AVR ATmega

► ATmega328

► ATmega1284

AVR 0-series and 1-series

► ATmega4809

► ATtiny1604

► ATtiny1614

► ATtiny3216

► ATtiny3227

► ATtiny402

► ATtiny404

► ATtiny414

► ATtiny814

AVR DA/DB-series

► AVR128DA28

► AVR128DA32

► AVR128DA48

► AVR128DB28



► RP2040

► RA4M1

About me

  • About me
  • Twitter
  • Mastodon


RSS feed

Dot Matrix Clock

15th November 2015

This article describes a simple 12-hour clock using two dot-matrix displays to display the time, controlled by an ATtiny2313 processor:


Dot matrix clock based on an ATtiny2313.


I wanted to design myself a clock that was a bit more individual than one using a standard 7-segment display, so I decided to try basing one on a pair of 8x8 dot matrix LED displays. These are available fairly cheaply from a number of sources. I chose a pair of 0.8" 8x8 LED matrix displays from Adafruit [1]. They are available in a range of different colours, but I chose white because they tend to be the brightest, and this would enable me to minimise the current consumption.

Adafruit recommend driving the displays using a 74HC595 and TPIC6B595, or a single MAX7219. However, I think it's usually cheaper to drive displays directly from a processor, and it also generally makes the wiring simpler. I therefore set about finding the smallest AVR processor that would drive the displays with the information I needed to show.

In general two 8x8 displays would need 24 I/O lines, but for a clock we can make several economies. To display the time on a 12-hour clock, with a colon separator, we can fit the digits and a colon in just seven rows and 12 columns, the remaining row and columns always being blank:


In addition, we can save some I/O lines by using the same I/O line for a row and a column if we never need to light the LED at their intersection, as shown by the light grey squares above. Using all these savings allows us to drive the clock displays with just 14 I/O lines, labelled B0 to B7 and D1 to D6 in the above diagram. The ATtiny2313 provides 17 I/O lines, so that conveniently leaves the two we need for the crystal, and one for a push button to set the time.

The Port B pins PB0 to PB6 are used to drive the rows, so the definition for a complete column can use a single write to the PORTB register. The columns are driven by a combination of Port D pins and Port B pins, as shown in the above diagram. The sequence of pins corresponding to each column is specified by the array Columns:

char Columns[12] = { 15, 9, 1, 4, 13, 16, 5, 11, 6, 8, 10, 7 };

Note that Columns[0] is used for the colon, and Columns[1] to Columns[11] for the 11 digit columns.

Display update

The dot matrix displays are updated in the background, from the values in Buffer[], using interrupts generated by Timer/Counter1. For example:

Buffer[0] = 1;
Buffer[1] = 2;
Buffer[2] = 5;
Buffer[3] = 9;

will display "12:59". The timer is set up for CTC mode, counting up to OCR1A, and with a second interrupt generated by a compare match with OCR1B. Here's the code to set up the timer/counter:

  TCCR1A = 0<<WGM10;
  TCCR1B = 1<<WGM12 | 2<<CS10;           // Divide by 8
  OCR1A = 499;                           // Compare match at 2000Hz
  OCR1B = 480;                           // Brightness; reduce for brighter
  TIMSK = TIMSK | 1<<OCIE1A | 1<<OCIE1B; // Compare match interrupt enable

Timer/Counter1 interrupt B turns on the display, by taking the appropriate column low:

  pinMode(Columns[Column], OUTPUT);      // Turn column on
  digitalWrite(Columns[Column], LOW);

Timer/Counter1 interrupt A turns off the current column, and then calls DisplayNextColumn() which sets up the next column, and SetTime(), which updates the time in Buffer[]:

  pinMode(Columns[Column], INPUT);       // Turn column off

The value in compare register OCR1B determines how long the digit is on for, and can be used to adjust the brightness of the display. I set it to 480, which gives the dimmest possible display, to save battery power. At this brightness the current consumption of the clock is about 10mA. You could also use the OCR1B value to adjust the brightness of the display, such as to dim the whole display at night.

Here's the DisplayNextColumn() routine:

void DisplayNextColumn() {
  char segs;
  Column = (Column+1) % 12;
  int DigitColumn = Column % 3;
  int Digit = Column / 3;
  char Char = Buffer[Digit];
  // Blank leftmost digit
  if (Digit == 0 && Char == 0) Char = Space;
  // Treat column 0 as colon
  if (Column == 0) segs = ColonState; else segs = CharMap[Char][DigitColumn];
  DDRB = DDRB & 0x80;                   // B6-B0 all inputs
  PORTB = PORTB | segs;                 // 1 = high
  DDRB = DDRB | segs;                   // 1 = output

It reads the dot definitions for the next column, from the appropriate digit and digit column. If the leftmost digit is a zero, it replaces it with a space to blank the leading zero. It then writes the dot definitions to PORTB, and configures the lit dots as outputs.

Calculating and setting the time

The clock keeps time using an 8MHz crystal, divided down to give the 2000Hz interrupt. Here's the SetTime() routine:

void SetTime() {
  int Speed = 2000;
  if ((PIND & 1)==0) Speed = 4;         // Check set time button
  if (Counter == 0) {                   // Runs at 1Hz
    Counter = Speed;
    ColonState = ColonState ^ Colon;    // Flash colon
    int Carry = 1;
    for (int p=4; p>=0; p--) {
      char temp = Buffer[p] + Carry;
      Buffer[p] = temp % Size[p];
      Carry = temp / Size[p];
      if (Buffer[0]==1 && Buffer[1]==3) {
        Buffer[0] = 0;
        Buffer[1] = 1;

It counts down from 2000 to give 1Hz from the interrupt. It then increments the values in Buffer[], using the values in the corresponding elements of Size[] to specify when to carry to the next digit. The variable ColonState is used to flash the colon at 1Hz.

The circuit

Here's the circuit for the dot matrix clock:


Circuit for the ATtiny2313-based dot matrix clock.

The seven rows of the dot matrix displays are connected to the corresponding pins of port B via 220Ω resistors, to limit the current. At full brightness, with OCR1B set to 0, the total current consumption is about 25mA, well within the ATtiny2313's maximum rating of 60mA. With the display dimmed by setting OCR1B to 480 the current is reduced to 10mA, but the display is still quite visible. I powered the clock from a 3.7V rechargable LIPO battery.

The wires interconnecting the rows between the two displays are under the displays, to save space:


Interconnections under the dot matrix displays.

The circuit fits neatly on a small solderless breadboard I bought from Bright Components on eBay [2].

Uploading the program

I developed the program using the Arduino IDE and used the Tiny AVR Programmer [3] to upload it to the ATtiny2313 on the breadboard; see ATtiny-Based Beginner's Kit. Plug the Tiny AVR Programmer into the computer's USB port, and then connect it to the ATtiny2313 using six jumper wires:


Connecting the Tiny AVR Programmer to the ATtiny2313.

Compile the program using Spence Konde's ATTiny Core [4]. Choose the ATtiny2313/4313 option under the ATTinyCore heading on the Board menu. Then check that the subsequent options are set as follows (ignore any other options):

Chip: "ATtiny2313"
Clock: "8 MHz (external)"
B.O.D: "B.O.D. Disabled"

Choose Burn Bootloader to set the fuses appropriately. Then choose Upload to upload the program to the ATtiny2313.

Here's the whole program for the Dot Matrix Clock: Dot Matrix Clock Program.


The nice thing about using dot matrix displays is that, subject to the limitations of the wiring, you can customise the display digits to suit your preferences. For example, here are some alternative character definitions for rounded digits:

char CharMap[11][3] = {
{ 0x3E, 0x41, 0x3E }, // 0
{ 0x00, 0x20, 0x7F }, // 1
{ 0x27, 0x49, 0x31 }, // 2
{ 0x2A, 0x49, 0x36 }, // 3
{ 0x18, 0x28, 0x7F }, // 4
{ 0x7A, 0x49, 0x4E }, // 5
{ 0x3E, 0x49, 0x26 }, // 6
{ 0x47, 0x48, 0x70 }, // 7
{ 0x36, 0x49, 0x36 }, // 8
{ 0x32, 0x49, 0x3E }, // 9
{ 0x00, 0x00, 0x00 }, // space

Or, design your own font!


5th October 2020: I now recommend using Spence Konde's ATTinyCore to compile and upload the program, and I've rewritten the description accordingly. I've also updated the program to work with this core.

  1. ^ Miniature Ultra-Bright 8x8 White LED Matrix on Adafruit.
  2. ^ Solderless Prototype Breadboard (360 Points) from Bright Components on eBay.
  3. ^ Tiny AVR Programmer on SparkFun.
  4. ^ ATTinyCore on GitHub.

blog comments powered by Disqus