Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Adrian Smith
Published © GPL3+

Large 6 digit 7 segment LED clock using ATMega328P

A large Arduino based LED clock using 1.8" high digits for the hours and minutes and 0.8" for the seconds. Can be seen from a distance.

IntermediateFull instructions provided2 hours692
Large 6 digit 7 segment LED clock using ATMega328P

Things used in this project

Hardware components

ATmega328PB microcontroller
Microchip ATmega328PB microcontroller
Through hole version, not SMT
×1
Texas Instruments TPIC6B595N
×6
ZS042 DS3231 module
×1
PCBWay Custom PCB
PCBWay Custom PCB
Sometimes I will list one on eBay https://www.adrian-smith31.co.uk/blog/my-ebay-listings/ PCBWay project page https://www.pcbway.com/project/shareproject/Large_1_8_0_8_6_digit_LED_clock_using_ATMega328P_and_TPIC6B595_shift_registe_ad4da5b4.html
×1
SA18_11EWA
×4
SA08_11EWA
×2
Capacitor 100 nF
Capacitor 100 nF
×12
Through Hole Resistor, 390 ohm
Through Hole Resistor, 390 ohm
×28
Through Hole Resistor, 620 ohm
Through Hole Resistor, 620 ohm
×20
Linear Regulator (7805)
Linear Regulator (7805)
×1

Software apps and online services

Arduino IDE
Arduino IDE
KiCad
KiCad

Story

Read more

Schematics

Schematic of the main PCB

This is the schematic for the clock PCB

Bill of materials

Full list of parts required to build the clock PCB (button board not included)

Code

Code for the clock

C/C++
This is the main code for the ATMEGA168P / 328P microcontroller
// 6 Digit clock using TPIC6B595 shift registers
// Leftmost digit is digit 0
// Data will be fed in to the rightmost register first so digit 0 needs shifting out first. 
// The clock will have 3 buttons - hour set, min set and enter / exit set mode.
// To set the time press the set button and the clock will go to the set time function - this is detected in main loop.
// To exit time set function press set button again, this will also zero the seconds and write new values to the rtc.
// A 4th button could be used to display temperature - not sure about this yet.

// 7-segment digits 0-9
// {Q0, Q1, Q2, Q3, Q4, Q5, Q6, Q7} --> {g, f, e, d, c, b, a, DP} 

#include <SPI.h>
#include <SoftPWM.h>
#include <Wire.h>                        
#include <RTClib.h>
#include "Button.h"  // Button library by Alexander Brevig                      

const int reg_clock = 13; // SRCK
const int reg_latch = 10; // RCLK
const int reg_data = 11; // SER_IN
const int reg_OE = 9; // output enable; Active LOW. Can be used to control brightness with PWM.
const int colon_pin = 8; // used to dim the colon LED's when night mode enabled.

Button button1 = Button(2,BUTTON_PULLUP_INTERNAL);       // Setup button A (using button library)
Button button2 = Button(3,BUTTON_PULLUP_INTERNAL);       // Setup button B (using button library)
Button button3 = Button(4,BUTTON_PULLUP_INTERNAL);       // Setup button A (using button library)
Button button4 = Button(5,BUTTON_PULLUP_INTERNAL);       // Setup button B (using button library)

int brightness = 4; // set display brightness. Values 1-5. Set to level 4 as LED's I chose were eye searingly bright at level 5.
int brt = 0; // value for storing reversed PWM percentage
int set_mode = 0; // value for storing timeset mode true or false

#define DS3231_I2C_ADDR 0x68
#define DS3231_TEMPERATURE_ADDR 0x11

RTC_DS3231 rtc;

byte ssddigits[10] = // array without decimal points on
{
  B01111110,  // 0
  B00001100,  // 1
  B10110110,  // 2
  B10011110,  // 3
  B11001100,  // 4
  B11011010,  // 5
  B11111010,  // 6
  B01001110,  // 7
  B11111110,  // 8
  B11011110,  // 9
};

byte ssddigitsDP[10] = // array with decimal points on
{
  B01111111,  // 0
  B00001101,  // 1
  B10110111,  // 2
  B10011111,  // 3
  B11001101,  // 4
  B11011011,  // 5
  B11111011,  // 6
  B01001111,  // 7
  B11111111,  // 8
  B11011111,  // 9
};

byte tempC[2] = // C and degrees symbol
{
  B01110010,
  B11000110,
}; 

void setup() // runs once at powerup
{
  
  pinMode (reg_clock, OUTPUT);
  pinMode (reg_latch, OUTPUT);
  pinMode (reg_data, OUTPUT);
  pinMode (reg_OE,OUTPUT);
  pinMode (colon_pin, OUTPUT);
  digitalWrite(reg_OE,LOW); // enable output of shift register chain; an external pullup resistor on the OE pin prevents garbage being displayed at power on.
  digitalWrite(colon_pin, HIGH); // set colons to bright initially
  setBrt();
  SoftPWMBegin();
  SoftPWMSet(9, 0); // init software PWM on pin 9. This pulses the OE pin on and off to control brightness.
  SoftPWMSetPercent(9, brt);
  Wire.begin(); // start i2c 
  rtc.begin(); //start RTC Clock
  set_mode = 0; // set timeset mode to off
             
  //rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // comment out once clock is initialy set
    
}

void loop() // main program loop
{
   if (set_mode == 1)

   {
     setTime();
   }

   else
   {
     clockDisplay();
   }
   
   if (button1.uniquePress())
   {
    SPI.beginTransaction(SPISettings(8000000, LSBFIRST, SPI_MODE0)); // display "set" for 2 seconds
    digitalWrite(reg_latch,LOW);
    SPI.transfer(B11011010); // S
    SPI.transfer(B11110010); // E
    SPI.transfer(B11110000); // t
    SPI.transfer(B00000000); // blank digit 3
    SPI.transfer(B00000000); // blank seconds display 10s
    SPI.transfer(B00000000); // blank seconds display 1s 
    digitalWrite(reg_latch,HIGH);
    SPI.endTransaction();
    delay(2000);
    set_mode = 1; // enter timeset mode
   }

  setBrt();
  SoftPWMSetPercent(9, brt); 
}

/// functions ///

void clockDisplay() // gets the time from the rtc and sends data to the shift registers.
{

  DateTime now = rtc.now();

  int hours = now.hour();  
	int minutes = now.minute();
  int seconds = now.second();  

  int h1, h2, m1, m2, s1, s2; // split the numbers into seperate digits using mod math
	h2 = hours % 10;
	h1 = ((hours % 100) - h1) / 10;
	m2 = minutes % 10;
	m1 = ((minutes % 100) - m1) / 10;
  s2 = seconds % 10;
  s1 = ((seconds % 100) - s1) / 10;

  // get digit 0
  int dig0 = (h1); // 10h
  // get digit 1
  int dig1 = (h2); // 1h
  // get digit 2
  int dig2 = (m1); // 10m
  // get digit 3
  int dig3 = (m2); // 1m
  // get digit 4
  int dig4 = (s1); // 10s
  // get digit 5
  int dig5 = (s2); // 1s

  SPI.beginTransaction(SPISettings(8000000, LSBFIRST, SPI_MODE0)); // send data to shift registers
  digitalWrite(reg_latch,LOW);
  SPI.transfer(ssddigits[dig0]); 
  SPI.transfer(ssddigits[dig1]);
  SPI.transfer(ssddigits[dig2]);
  SPI.transfer(ssddigits[dig3]);
  SPI.transfer(ssddigits[dig4]);
  SPI.transfer(ssddigits[dig5]);
  digitalWrite(reg_latch,HIGH);
  SPI.endTransaction();

 if (now.hour() == 23 && now.minute() == 0 && now.second() == 0) // dim display at 23:00
    {
       brightness = 1;
       digitalWrite(colon_pin, LOW); // set colon to dim
    }

    if (now.hour() == 7 && now.minute() == 0 && now.second() == 0) // resume normal brightness at 7:00
    {
       brightness = 4;
       digitalWrite(colon_pin, HIGH); // set colon to bright.
    }

}

void setTime() // this will run if hour or minute set button is pressed

{
    
  DateTime now = rtc.now(); // this gets the current time from the RTC so it updates after button presses.
  int set_min = now.minute();
  int set_hour  = now.hour();
 
  int h1, h2, m1, m2; // split the numbers into seperate digits using mod math
	h2 = set_hour % 10;
	h1 = ((set_hour % 100) - h1) / 10;
	m2 = set_min % 10;
	m1 = ((set_min % 100) - m1) / 10;

  // get digit 0
  int dig0 = (h1); // 10h
  // get digit 1
  int dig1 = (h2); // 1h
  // get digit 2
  int dig2 = (m1); // 10m
  // get digit 3
  int dig3 = (m2); // 1m

  SPI.beginTransaction(SPISettings(8000000, LSBFIRST, SPI_MODE0)); // send data to shift registers
  digitalWrite(reg_latch,LOW);
  SPI.transfer(ssddigits[dig0]); 
  SPI.transfer(ssddigits[dig1]);
  SPI.transfer(ssddigits[dig2]);
  SPI.transfer(ssddigits[dig3]);
  SPI.transfer(B00000000); // blank seconds display 10s
  SPI.transfer(B00000001); // blank seconds display 1s & light last decimal point to indicate set mode
  digitalWrite(reg_latch,HIGH);
  SPI.endTransaction();
     
  // code here will increment values above if hour and minute buttons are pressed also sets button_pressed flag to true

  while (button2.uniquePress())
    {
      set_hour++;
      if (set_hour == 24)
      {
        set_hour = 0;
      }
      delay(10); // 10 millisecond delay to avoid spamming the RTC
      rtc.adjust(DateTime(2023, 10, 23, set_hour, set_min)); // write the time to the rtc, dont zero seconds
    }

  while (button3.uniquePress())
    {
      set_min++;

      if (set_min == 60)
      {
        set_min = 0;
      }
      delay(10); // 10 millisecond delay to avoid spamming the RTC
      rtc.adjust(DateTime(2023, 10, 23, set_hour, set_min)); // write the time to the rtc, don't zero seconds
    }  
   
   if (button1.uniquePress())
    {
      DateTime now = rtc.now(); // this gets the current time from the RTC so it updates after button presses.
      int set_min = now.minute();
      int set_hour  = now.hour();
      rtc.adjust(DateTime(2023, 10, 23, set_hour, set_min,0)); // zero seconds when set button is pressed again
      set_mode = 0; // this happens if timeset button is pressed after adjusting the time
      clockDisplay(); // exit function and return to clock display
    }

}

void setBrt() // this changes the brightness logarithmic reversed scale to a more linear one

{
  if (brightness == 1)
  brt = 90;

  else if (brightness == 2)
  brt = 75;

  else if (brightness == 3)
  brt = 50;

  else if (brightness == 4)
  brt = 30;

  else if (brightness == 5)
  brt = 0;
}

Credits

Adrian Smith

Adrian Smith

6 projects • 3 followers
Electronics engineer for around 10 years, repair electronic LED signs and other old electronics from the 1980 / 1990's.
Thanks to Alexander Brevig.

Comments