Alex Stepanov
Published © GPL3+

ATMEGA328P 7-Segment Clock With Custom PCB V1

Two lines of 4 digits 7-segment display using ATMEGA328P chip (Arduino Uno) can be used as clock with external RTC or as general display.

IntermediateShowcase (no instructions)4 hours2,460
ATMEGA328P 7-Segment Clock With Custom PCB V1

Things used in this project

Hardware components

ATmega328P
×1
Shift Register- Serial to Parallel
Texas Instruments Shift Register- Serial to Parallel
×1
Resistor 220 ohm
Resistor 220 ohm
×10
Resistor 330 ohm
Resistor 330 ohm
×1
4.7k resistor
×2
Male Header 40 Position 1 Row (0.1")
Male Header 40 Position 1 Row (0.1")
×1
Capacitor 100 nF
Capacitor 100 nF
×5
22p capacitor
×2
16 MHz Crystal
16 MHz Crystal
×1
barell connector
×1
Tactil switches
×4
LDS-3492AS 4 digit 7 segment display
×2

Story

Read more

Schematics

Eagle PCB

Eagle schematic

Gerber files for Seedstudio Fusion PCB service

All Eagle files in ZIP

Code

Main sketch

Arduino
//Global libraries
#include <TimeLib.h>  
#include <Wire.h>
#include <RTClib.h>
#include <PinChangeInterrupt.h>
//Local librarues
#include "sevenseg_display_drv.h"

//Define section
#define BUTTON_PLUS 12
#define BUTTON_MINUS 11
#define BUTTON_MODE 10
#define BUTTON_SET 13
#define MAX_MODE 2
#define SECS_PER_HOUR 3600


//Top variables
sevenseg_display_drv display7seg;
double light;
volatile uint8_t mode,stopwatch_running,mode_button_pressed;
volatile unsigned long last_time_button_falling,last_time_button_rising,elapsed_millisec,stopwatch_start_millisec;
//unsigned long 

RTC_DS3231 rtc;

void loop(){
	switch (mode) {
		case 0:
			mode=1;
			display7seg.clear_all();
			display7seg.set_dp_bottom(2);
			break;
		case 1://Clock with date
			show_clock_up();
			show_date_down();
			set_brightness();
			break;
		case 10://Clock with year
			display7seg.clear_all();
			mode=11;
			break;
		case 11://Clock with year
			show_clock_up();
			show_year_down();
			set_brightness();
			break;
		case 20://Stop watch
			mode=21;
			display7seg.clear_all();
			display7seg.set_colon_top();
			display7seg.set_dp_bottom(2);
			break;
		case 21://Stop watch
			stopwatch();
			break;
		default:
			mode=0;
			break;
	}//switch
}

void stopwatch(){
	//Stopwatch function	
	if(stopwatch_running==1) elapsed_millisec=millis()-stopwatch_start_millisec;
	
	uint16_t total_sec=elapsed_millisec/1000;
	uint16_t delata_millisec=elapsed_millisec%1000;
	uint8_t delta_sec=total_sec%60;
	uint8_t delta_min=total_sec/60%60;
	uint8_t delta_hour=total_sec/60/60;
	
	uint8_t digit;
	digit=elapsed_millisec/10%10;
	display7seg.set_digit_bottom(0, digit);
	digit=elapsed_millisec/100%10;
	display7seg.set_digit_bottom(1, digit);
	digit=delta_sec%10;
	display7seg.set_digit_bottom(2, digit);
	digit=delta_sec/10;
	display7seg.set_digit_bottom(3, digit);
	
	digit=delta_min%10;
	display7seg.set_digit_top(0, digit);
	digit=delta_min/10;
	display7seg.set_digit_top(1, digit);
	digit=delta_hour%10;
	display7seg.set_digit_top(2, digit);
	digit=delta_hour/10;
	display7seg.set_digit_top(3, digit);
}

void set_brightness(){
	light = 0;
	for (int i = 0; i < 2000; i++) {
		light += analogRead(A3);
	}

	light = int((light/ 2000) / 1024 * 15);
	display7seg.set_brightness(light);
}

void show_clock_up(){
	display7seg.set_digit_top(0, minute()%10);
	display7seg.set_digit_top(1, minute()/10);
	display7seg.set_digit_top(2, hour()%10);
	display7seg.set_digit_top(3, hour()/10);
	
	if (millis()%1000 > 500) display7seg.set_colon_top();
	else display7seg.reset_colon_top();
}

void show_date_down(){
	display7seg.set_digit_bottom(0, month()%10);
	display7seg.set_digit_bottom(1, month()/10);
	display7seg.set_digit_bottom(2, day()%10);
	display7seg.set_digit_bottom(3, day()/10);
}

void show_year_down(){
	uint8_t digit;
	
	digit=year()%10;
	display7seg.set_digit_bottom(0, digit);
	digit=year()/10%10;
	display7seg.set_digit_bottom(1, digit);
	digit=year()/100%10;
	display7seg.set_digit_bottom(2, digit);
	digit=year()/1000%10;
	display7seg.set_digit_bottom(3, digit);
}

void print_float_num_bottom(double floatnum) {
	char tempString[5];
	int fourdigits;

	display7seg.reset_dp_bottom(0);
	display7seg.reset_dp_bottom(1);
	display7seg.reset_dp_bottom(2);
	display7seg.reset_dp_bottom(3);
	
	int dig_num = log10(floatnum) + 1;
	if (dig_num >= 0) {
		fourdigits = round(floatnum*pow(10, 4 - dig_num));
		if (fourdigits==0) sprintf(tempString, "%4s", "0000");
		else sprintf(tempString, "%4d", fourdigits);
		display7seg.set_digit_bottom(0, (uint8_t)(tempString[3] - '0'));
		display7seg.set_digit_bottom(1, (uint8_t)(tempString[2] - '0'));
		display7seg.set_digit_bottom(2, (uint8_t)(tempString[1] - '0'));
		display7seg.set_digit_bottom(3, (uint8_t)(tempString[0] - '0'));
		display7seg.set_dp_bottom(4 - dig_num);
	}
	else {
		fourdigits = round(floatnum*10000);
		sprintf(tempString, "%4d", fourdigits);
		display7seg.set_digit_bottom(0, fourdigits % 10 );
		display7seg.set_digit_bottom(1, fourdigits /10 %10);
		display7seg.set_digit_bottom(2, (fourdigits /100 % 10));
		display7seg.set_digit_bottom(3, 0);
		display7seg.set_dp_bottom(3);
	}
}

//http://forum.arduino.cc/index.php?topic=44262.0
char *ftoa(char *a, double f, int precision)
{
	long p[] = { 0,10,100,1000,10000,100000,1000000,10000000,100000000 };

	char *ret = a;
	long heiltal = (long)f;
	itoa(heiltal, a, 10);
	while (*a != '\0') a++;
	*a++ = '.';
	long desimal = abs((long)((f - heiltal) * p[precision]));
	itoa(desimal, a, 10);
	return ret;
}

Buttons related functions

Arduino
void plus_button(){

}

void minus_button(){

}

void set_button(){
	if((millis()-last_time_button_falling)<200) return;
	last_time_button_falling=millis();
	
	switch (mode) {
		case 1://Clock with date
			mode=10;
			break;
		case 11://Clock with year
			mode=0;
			break;
		case 21://Stop watch
			switch (stopwatch_running) {
				case 0://Stopwatch not running state
					//Run it
					stopwatch_running=1;
					stopwatch_start_millisec=millis();
					break;
				case 1://Stopwatch running state
					//Stop it
					stopwatch_running=2;
					break;
				case 2://Display results state
					//Reset it
					stopwatch_running=0;
					elapsed_millisec=0;
					break;
				default:
					break;
			}//switch stopwatch_running
		default:
			break;
	}//switch mode
}

void mode_button_down(){
	if((millis()-last_time_button_falling)<200) return;
	last_time_button_falling=millis();
	mode_button_pressed=1;
	switch (mode) {
		case 1://Clock with date
			mode=20;
			break;
		case 11://Clock with year
			mode=20;
			break;
		case 21://Stop watch
			mode=0;
			break;
		default:
			mode=0;
			break;
	}//switch mode
}

void mode_button_up(){
	if((millis()-last_time_button_rising)<200) return;
	last_time_button_rising=millis();
	mode_button_pressed=0;
	/*
	switch (mode) {
		case 1://Clock with date
			break;
		case 11://Clock with year
			break;
		case 21://Stop watch
			break;
		default:
			break;
	}//switch mode
	*/
}

Setup functions

Arduino
void setup()
{
	//Serial.begin(9600);
	display7seg.init();//Initiate displays
	display7seg.begin_drv();//Run interupt
	display7seg.set_brightness(15);
	//setTime(10, 10, 0, 1, 1, 2017);
	if (! rtc.begin()) {
		Serial.println("Couldn't find RTC");
		while (1);
	}

	if (rtc.lostPower()) {
		Serial.println("RTC lost power, lets set the time!");
		// following line sets the RTC to the date & time this sketch was compiled
		rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
		// This line sets the RTC with an explicit date & time, for example to set
		// January 21, 2014 at 3am you would call:
		// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
	}
	
	setSyncProvider(syncProvider);   // the function to get the time from the RTC
	if(timeStatus()!= timeSet) 
		Serial.println("Unable to sync with the RTC");
	else
		Serial.println("RTC has set the system time"); 
	
	pinMode(A3, INPUT);//Light sensor
	pinMode(BUTTON_PLUS, INPUT);
	pinMode(BUTTON_MINUS, INPUT);
	pinMode(BUTTON_MODE, INPUT);
	pinMode(BUTTON_SET, INPUT);
	
	attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(BUTTON_PLUS), plus_button, FALLING);
	attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(BUTTON_MINUS), minus_button, FALLING);
	attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(BUTTON_MODE), mode_button_down, FALLING);
	attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(BUTTON_SET), set_button, FALLING);
	
	last_time_button_falling=0;
	last_time_button_rising=0;
	mode = 0;
	stopwatch_running=0;
	mode_button_pressed=0;
}

static time_t syncProvider(){
	return rtc.now().unixtime();
}

Driver class

C/C++
#include "sevenseg_display_drv.h"

static sevenseg_display_drv *active7segment = NULL;

sevenseg_display_drv::sevenseg_display_drv() { }

void sevenseg_display_drv::begin_drv()
{
	//Start refreshing Nothing will be displayed before this function called
	//From http://www.instructables.com/id/Arduino-Timer-Interrupts/
	//Set timer1 - 16 bit timer
	
	cli();// disable global interrupts
	TCCR1A = 0;// set entire TCCR1A register to 0
	TCCR1B = 0;// same for TCCR1B
	TCNT1 = 0;//initialize counter value to 0

	//Prescaller (timer speed(Hz)) = (Arduino clock speed(16MHz)) / prescaler
	TCCR1B |= (1 << CS10); //No prescaller 63ns per count for 16MHz clock
	
	//Also notice how the setups between the three timers differ slightly in the line which turns on CTC mode :
	//TCCR0A |= (1 << WGM01);//for timer0
	//TCCR1B |= (1 << WGM12);//for timer1
	//TCCR2A |= (1 << WGM21);//for timer2
	TCCR1B |= (1 << WGM12);//CTC mode

	// OCR#A (the compare match value)
	//remember that when you use timers 0 and 2 this number must be less than 256, and less than 65536 for timer1
	OCR1A = 999;//intrupt will happen 63ns*[this value] 63usec for 999
	//OCR1A = 496;//31usec
	TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
	sei();//allow interrupts
}

ISR(TIMER1_COMPA_vect) {//change the 1 to 0 for timer0 and 2 for timer2
	active7segment->updateDisplay();//Matrix refrshing function
}

uint8_t sevenseg_display_drv::init()
{
	//Initialisation
	DDRB |= B00000011;//Set pins 0,1 of port B as output
	DDRD |= B11111111;//Sets pins 0 to 7 of port D as outputs, segments control
	DDRC |= B00000111;//Set bits 0,1,2 of port C as output, connection to 74HC595N


	PORTB &= B11111100;//Turn off colon and apostrof
	PORTD =  B00000000;//Turn off all segments
	PORTC &= B11111000;//Shift register controls low
	
	//Clear all bits of shift register
	for (uint8_t n = 0; n < NUMOFDIGITS; n++) {
		PORTC |= B00000010;//Serial clock high
		PORTC &= B11111101;//Serial clock low
	}
	PORTC |= B00000100;//Latch shift register data

	active7segment = this; // For interrupt hander

	//Clear memory
	clear_all();

	//For pwm_count_max=15 each digit can be turn on up to 1ms out of 8 ms
	//So reftesh rate something like 125Hz
	//pwm_count_max = 15;//Number of brightness levels
	brightness = 15;//Default value of brightness

	//Initiate variables
	pwm_count = 0;
	digit_num = 0;
	return 0;
}

void sevenseg_display_drv::clear_all(){
	//Clear memory
	for (uint8_t n = 0; n < NUMOFDIGITS; n++) {
		dp_buffer[n] =   B00000000;//Reset decimal point buffer
		displaybuff[n] = B00000000;//Set all digits to off
		colon_and_ap[n]= B00000000;//Clean colon and apos
	}
}

void sevenseg_display_drv::clear_top(){
	//Clear memory
	for (uint8_t n = 4; n < NUMOFDIGITS; n++) {
		dp_buffer[n] =   B00000000;//Reset decimal point buffer
		displaybuff[n] = B00000000;//Set all digits to off
		colon_and_ap[n]= B00000000;//Clean colon and apos
	}
}

void sevenseg_display_drv::clear_bottom(){
	//Clear memory
	for (uint8_t n = 0; n < 4; n++) {
		dp_buffer[n] =   B00000000;//Reset decimal point buffer
		displaybuff[n] = B00000000;//Set all digits to off
		colon_and_ap[n]= B00000000;//Clean colon and apos
	}
}

void sevenseg_display_drv::updateDisplay()
{
	/*
	This function refreshing display. Called only from interupt
	*/
	
	//Debug
	//PORTC |= B00000001;//Turn off all digits

	if (pwm_count < PWMCOUNTMAX) {
		//For brightness=0 display will be on for 1 cycle
		if (pwm_count > brightness) {
			PORTB &= B11111100;//Turn off colon and apostrof
			PORTD =  B00000000;//Turn off all segments
		}
		pwm_count++;//PWM counter
	}
	else {//End of current pwm cycle
		//Here we need to update information of digit
		pwm_count = 0;//restart pwm counter
		//Turn off all
		PORTB &= B11111100;//Turn off colon and apostrof
		PORTD  = B00000000;//Turn off all segments

		if (digit_num < NUMOFDIGITS) {// Digits 1-7
			PORTC &= B11111000;//Shift register latch data and clock signal low
			PORTC |= B00000010;//Serial clock high
			PORTC |= B00000101;//Latch data, serial data to high
		}
		else {//Digit 0
			digit_num = 0;//First digit
			//Turn on first digit
			PORTC &= B11111001;//Shift register latch and clock signal low. Data already high vrom previous cycle
			PORTC |= B00000010;//Serial clock high
			PORTC |= B00000100;//Latch data
		}
		//Ser digit data
		PORTD = displaybuff[digit_num];//Apply data for segments of current digit
		PORTB |= colon_and_ap[digit_num];//Colon and apostrophe 
		digit_num++;//Next digit
	}//end else
	
	//*** Debug ***
	//long elapsed = micros() - time_start;
	//Serial.println(elapsed);
	//PORTC &= B11111110;//Turn off all digits
	//********************
}

//Set value of digit
void sevenseg_display_drv::set_digit( uint8_t dig_num, uint8_t value){
	if(dig_num<0 || dig_num>(NUMOFDIGITS-1)) return;
	displaybuff[dig_num]=pgm_read_byte_near(&bcd[value])|dp_buffer[dig_num];
}

void sevenseg_display_drv::set_digit_top( uint8_t dig_num, uint8_t value){
	set_digit( dig_num+4, value);
}

void sevenseg_display_drv::set_digit_bottom( uint8_t dig_num, uint8_t value){
	set_digit( 3-dig_num, value);
}

//Set brightness of all digits
void sevenseg_display_drv::set_brightness(uint8_t value) {
	if (value<= PWMCOUNTMAX) brightness = value;
	else brightness = PWMCOUNTMAX;
}


void sevenseg_display_drv::set_dp_top(uint8_t dig_num) {
	if(dig_num<0 || dig_num>(NUMOFDIGITS-1)) return;
	dp_buffer[dig_num+4] = B10000000;
	displaybuff[dig_num+4] |= dp_buffer[dig_num+4];
}

void sevenseg_display_drv::set_dp_bottom(uint8_t dig_num) {
	if(dig_num<0 || dig_num>(NUMOFDIGITS-1)) return;
	dp_buffer[3-dig_num] = B10000000;
	displaybuff[3-dig_num] |= dp_buffer[3-dig_num];
}

void sevenseg_display_drv::reset_dp_top(uint8_t dig_num) {
	if(dig_num<0 || dig_num>(NUMOFDIGITS-1)) return;
	dp_buffer[dig_num] = B00000000;
	displaybuff[dig_num] &= B01111111;
}

void sevenseg_display_drv::reset_dp_bottom(uint8_t dig_num) {
	if(dig_num<0 || dig_num>(NUMOFDIGITS-1)) return;
	dp_buffer[dig_num] = B00000000;
	displaybuff[dig_num] &= B01111111;
}

void sevenseg_display_drv::set_colon_top() {
	for (uint8_t n = 4; n < 8; n++) {
		colon_and_ap[n] |=B00000010;
	}
}

void sevenseg_display_drv::set_colon_bottom() {
	for (uint8_t n = 0; n < 4; n++) {
		colon_and_ap[n] |=B00000010;
	}
}

void sevenseg_display_drv::reset_colon_top() {
	for (uint8_t n = 4; n < 8; n++) {
		colon_and_ap[n] &=B11111101;
	}
}

void sevenseg_display_drv::reset_colon_bottom() {
	for (uint8_t n = 0; n < 4; n++) {
		colon_and_ap[n] &=B11111101;
	}
}

void sevenseg_display_drv::set_apostrophe_top() {
	for (uint8_t n = 4; n < 8; n++) {
		
	}
}

void sevenseg_display_drv::set_apostrophe_bottom() {
	for (uint8_t n = 0; n < 4; n++) {
		
	}
}

void sevenseg_display_drv::reset_apostrophe_top() {
	for (uint8_t n = 4; n < 8; n++) {
		
	}
}

void sevenseg_display_drv::reset_apostrophe_bottom() {
	for (uint8_t n = 0; n < 4; n++) {
		
	}
}

Driver class header

C/C++
//Used information:
//http://www.allaboutcircuits.com/projects/interface-a-seven-segment-display-to-an-arduino/
//http://skpang.co.uk/blog/archives/323



#ifndef _SEVENSEG_DISPLAY_DRV_h
#define _SEVENSEG_DISPLAY_DRV_h

#if defined(ARDUINO) && ARDUINO >= 100
	#include "arduino.h"
#else
	#include "WProgram.h"
	#include "pins_arduino.h"
#endif

#define NUMOFDIGITS 8
#define PWMCOUNTMAX 15 //Number of brightness levels

//Connections:
//Port D:a-0, b-1,c-2,d-3,e-4,f-5,g-6,7-dp
//Port B 1 - col,0 - apos
//Port C 0 - SER,1 - CLK,2 - RCLK connection to 74HC595N
/*
const unsigned char PROGMEM bcd[10] = {
	0xC0, //0
	0xF9, //1
	0xA4, //2
	0xB0, //3
	0x99, //4
	0x92, //5
	0x82, //6
	0xF8, //7
	0x80, //8
	0x90 //9
};
*/

const unsigned char PROGMEM bcd[10] = {
	~0xC0, //0
	~0xF9, //1
	~0xA4, //2
	~0xB0, //3
	~0x99, //4
	~0x92, //5
	~0x82, //6
	~0xF8, //7
	~0x80, //8
	~0x90 //9
};

class sevenseg_display_drv
{
public:
	sevenseg_display_drv();
	void begin_drv();
	void updateDisplay();
	uint8_t init();
	void set_digit( uint8_t dig_num, uint8_t value);
	void set_digit_top( uint8_t dig_num, uint8_t value);
	void set_digit_bottom( uint8_t dig_num, uint8_t value);
	void set_brightness(uint8_t brightness);
	void set_dp_top(uint8_t dig_num );
	void set_dp_bottom(uint8_t dig_num );
	void reset_dp_top(uint8_t dig_num);
	void reset_dp_bottom(uint8_t dig_num);
	void set_colon_top();
	void reset_colon_top();
	void set_apostrophe_top();
	void reset_apostrophe_top();
	void set_colon_bottom();
	void reset_colon_bottom();
	void set_apostrophe_bottom();
	void reset_apostrophe_bottom();
	void clear_all();
	void clear_top();
	void clear_bottom();
private:
	uint8_t         displaybuff[8],dp_buffer[8],colon_and_ap[8];//Supports up to 2 4 digit 7 segment modules
	volatile uint8_t pwm_count,brightness, digit_num,display_num, pwm_count_max;
};

#endif

Credits

Alex Stepanov

Alex Stepanov

4 projects • 22 followers
Analog engineer

Comments