Introduction: DIY Digital Clock Using ATmega328p, RTC DS3231 and Seven Segment Displays
Introduction: This is an user friendly tiny digital table clock where user can access all basic functions of clocks including setting alarms, viewing temperature, viewing date, setting date and time, etc. It has a buzzer for alarm and four push buttons (Menu, Left, Right, Cancel) for user interaction. It can be powered by a standard micro USB cable.
Its user interface is very simple. User can enter menu and then change the clock parameters (time, date, month, year, alarm, etc). The alarm parameters are stored in EEPROM of ATmega 328p so it dosen't forget alarm even if the power cuts. Each and every part of clock is detachable as shown in figure. It also comes with serial UART interface headers for any future firmware upgrade.
ATmega328p provides 20 GPIOs (14 digital and 6 analog) but as we are using seven segment displays we will be requiring 28 (7*4) pins only for seven segment display alone. To avoid this issue we will be taking advantage of persistence of vision of human eye which I will discuss in later section.
Supplies
You will be requiring these components and instruments if you want to duplicate this instructable.
Components:
- DS3231 RTC Module
- ATmega 328p IC with preloaded bootloaded
- 4 seven segment displays
- 5V mini buzzer
- 4 push button SPST
- Resistors: 10K ohm - 11units, 220 ohm - 4units
- 22pf capacitors - 2units
- 16Mhz XTAL oscillator
- Male berg strips - 2units, female berg strips - 2units
- Two 4*2 inch dot matrix PCBs
- 28 pin DIP IC base
- Micro-b port
Instruments:
- Soldering gun, soldering wire, soldering paste.
- Digital multimeter.
Step 1: Understand the Logic
I am using the DS3231 library to extract data from RTC module through I2C. The only problem I have faced is the number of GPIO's. If we use seven segment display directly we will be requiring 36 GPIOs but we have only 20 GPIOs. To avoid this we will be taking advantage of persistence of vision of human eye. If any image is displayed too rapidly, our brain averages the image and makes it a complete one. So, to achieve this we need to short all corresponding anodes of seven segments (i.e. short all a to a, b to b and so on till g to g) and connect them to a gpio pin of microcontroller as shown in schematics. Use cathode of each segment as a chip select signal. Now if we want to display a number 1234, select segment first by providing logic low to its cathode and providing logic high to other remaining cathodes. Provide corresponding signals to anodes for displaying the particular number for some milliseconds. Repeat this process for other segment and put this in an iteration, thus continuous four digit number will be displayed if seen from bare eyes.Thus, by doing this we have reduced the number of GPIOs to 19 from 36.
Here, I have provided video which is shot in slow motion (960 fps) where we could able to understand its working in slow motion.
Step 2: Study the Circuit
I have used 16MHz crystal oscillator for providing clock signals to microcontroller ATmega328p. The reset pin is pulled high through a 10K resistor to avoid false reset. Four push button switches (SPST) are connected to corresponding GPIOs through the pull up resistors of 10k ohm each. Seven segment displays are interfaced as discussed earlier, also shown in schematics diagram. Each anode of seven segment display is connected in series with a 220 ohm resistor to limit current less than 20mA. The cathodes of each seven segment display are connected to separate GPIO of microcontroller. RTC module is interfaced through I2C, the corresponding SDA and SCL pins are connected to SDA and SCL of microcontroller. RTC module is provided with the same 5V supply through the micro USB cable. The buzzer is connected directly to a GPIO of microcontroller as it does not requires that much current (< 45mA).
Download the schematic in PDF format provided below.
Step 3: Get the Hardware Ready!
The components are soldered on dot matrix PCB as shown. I have used two 2*4 inch dot matrix PCBs. On one I have mounted seven segments, push buttons(SPST) and buzzer and on other the microcontroller's IC base, capacitors, XTAL oscillator, resistors and RTC module from its back side is mounted as shown in images. The tow PCBs are stacked to each other through berg strips headers. Such arrangement makes the clock more compact. A micro USB port is attached to back PCB for powering it through a micro USB cable . Headers for serial UART interface is also added for uploading sketches and future firmware upgrades.
Step 4: Upload the Code:
You will be requiring Arduino IDE to upload the sketch. Get the code from here or download the ino file which I provided below. The DS3231 module is interfaced through I2C. I have used this library to get the time, date and temperature values from DS3231 buffer. Download this library zip file and install it in your Arduino IDE.
The digits are displayed alternately and at a fast rate as I discussed it earlier. Due to this, long software delay in the code should be avoided. This makes the code little bulkier and complex. I have added comments in the code wherever needed.
// RTC Programmable Clock
// sw1 Menu and Select // sw2 and sw3 Change Values // SW4 Home, back , Alarm Off // - Shivam Kurzekar #include #include "RTClib.h" RTC_DS3231 rtc; int dot_status = 0; int my_hour = 0, my_min = 0, my_date = 0, my_month = 0, my_second = 0, my_year; int alarm_hour = 0, alarm_min = 0; const int a = 10, b = 11, c = 4, d = 3, e = 2, f = A3, g = A2; const int dot = 9; const int s1 = A1, s2 = A0, s3 = 13, s4 = 12; boolean sw1_status = 0, sw2_status = 0, sw3_status = 0, sw4_status = 0; int switch_status = 0; const int sw1 = 5, sw2 = 6, sw3 = 8, sw4 = 7; int delay_time = 3 ; char my_display[3] = " "; int buzzer_pin = 1; int buzzer_status = 0; int beep_delay = 20; int default_alarm_hour = 19, default_alarm_min = 00; boolean alarm_status = 1;int hour_address = 0, min_address = 1;
void setup() { // put your setup code here, to run once: #ifndef ESP8266 while (!Serial); #endif
delay(100);
if (! rtc.begin()) { Serial.println("Couldn't find RTC"); while (1); } DateTime now = rtc.now(); my_year = now.year();
alarm_hour = EEPROM.read(hour_address); alarm_min = EEPROM.read(min_address); pinMode(dot, OUTPUT);
pinMode(a, OUTPUT); pinMode(b, OUTPUT); pinMode(c, OUTPUT); pinMode(d, OUTPUT); pinMode(e, OUTPUT); pinMode(f, OUTPUT); pinMode(g, OUTPUT);
pinMode(s1, OUTPUT); pinMode(s2, OUTPUT); pinMode(s3, OUTPUT); pinMode(s4, OUTPUT);
pinMode(sw1, INPUT); pinMode(sw2, INPUT); pinMode(sw3, INPUT); pinMode(sw4, INPUT);
pinMode(buzzer_pin, OUTPUT); }
void loop() {
DateTime now = rtc.now(); // get_time_date(); check_switch(); // check if any switch is pressed my_hour = now.hour(); my_min = now.minute(); my_date = now.day(); my_month = now.month();
int_to_string(my_hour, my_min); // convert the data into a string digitalWrite(dot, (now.second()) % 2); // blink the dot for 1 sec send_to_display(); // display the data to seven segments
if ((my_min == alarm_min ) && alarm_status ) // Check for alarm { if (my_hour == alarm_hour ) { if (digitalRead(sw4)) { delay(100); buzzer_status = 0; } Serial.println("Alarm!!"); digitalWrite(buzzer_pin, ((now.second() % 2)) && buzzer_status); } } else { buzzer_status = 1; digitalWrite(buzzer_pin, 0); } }
void get_time_date() { DateTime now = rtc.now(); my_hour = now.hour(); my_min = now.minute(); my_date = now.day(); my_month = now.month(); my_second = now.second(); }
void int_to_string(int i1, int i2 ) { if (i1 < 10 && (i2 < 10)) { sprintf(my_display, "0%d0%d", i1, i2); } else if (i1 < 10 && (i2 >= 10)) { sprintf(my_display, "0%d%d", i1, i2); } else if (i1 >= 10 && (i2 < 10)) { sprintf(my_display, "%d0%d", i1, i2); } else if (i1 >= 10 && (i2 >= 10)) { sprintf(my_display, "%d%d", i1, i2); } }
void send_to_display() { segment_1(); display_(0); // to display 1st digit delay(delay_time); low();
segment_2(); display_(1); // to display 2nd digit delay(delay_time); low();
segment_3(); display_(2); // to display 3rd digit delay(delay_time); low();
segment_4(); display_(3); // to display 4th digit delay(delay_time); low(); }
void display_(int i) { switch (my_display[i]) { case '0' : zero(); break;
case '1' : one(); break;
case '2' : two(); break;
case '3': three(); break;
case '4': four(); break;
case '5': five(); break;
case '6': six(); break;
case '7': seven(); break;
case '8': eight(); break;
case '9': nine(); break;
case 'p': pattern(); break;
case 'q': pattern_2(); break;
case 'A': capital_A(); break;
case 'L': capital_L(); break;
case 'F': capital_F(); break;
case 'n': small_n(); break; } }
void segment_1() { digitalWrite(s1, 0); digitalWrite(s2, HIGH); digitalWrite(s3, HIGH); digitalWrite(s4, HIGH); }
void segment_2() { digitalWrite(s1, HIGH); digitalWrite(s2, 0); digitalWrite(s3, HIGH); digitalWrite(s4, HIGH); }
void segment_3() { digitalWrite(s1, HIGH); digitalWrite(s2, HIGH); digitalWrite(s3, 0); digitalWrite(s4, HIGH); }
void segment_4() { digitalWrite(s1, HIGH); digitalWrite(s2, HIGH); digitalWrite(s3, HIGH); digitalWrite(s4, 0); }
void all_segment_off() { digitalWrite(s1, HIGH); digitalWrite(s2, HIGH); digitalWrite(s3, HIGH); digitalWrite(s4, HIGH); }
void all_segment_on() { digitalWrite(s1, 0); digitalWrite(s2, 0); digitalWrite(s3, 0); digitalWrite(s4, 0); }
void low() { digitalWrite(a, 0); digitalWrite(b, 0); digitalWrite(c, 0); digitalWrite(d, 0); digitalWrite(e, 0); digitalWrite(f, 0); digitalWrite(g, 0); }
void one() { digitalWrite(b, HIGH); digitalWrite(c, HIGH); digitalWrite(a, LOW); digitalWrite(f, LOW); digitalWrite(g, LOW); digitalWrite(e, LOW); digitalWrite(d, LOW);
}
void two() { digitalWrite(b, 1); digitalWrite(c, 0); digitalWrite(a, 1); digitalWrite(f, 0); digitalWrite(g, 1); digitalWrite(e, 1); digitalWrite(d, 1);
}
void three() { digitalWrite(a, 1); digitalWrite(b, 1); digitalWrite(c, 1); digitalWrite(d, 1); digitalWrite(e, 0); digitalWrite(f, 0); digitalWrite(g, 1);
}
void four() { digitalWrite(a, 0); digitalWrite(b, 1); digitalWrite(c, 1); digitalWrite(d, 0); digitalWrite(e, 0); digitalWrite(f, 1); digitalWrite(g, 1);
}
void five() { digitalWrite(a, 1); digitalWrite(b, 0); digitalWrite(c, 1); digitalWrite(d, 1); digitalWrite(e, 0); digitalWrite(f, 1); digitalWrite(g, 1);
}
void six() { digitalWrite(a, 1); digitalWrite(b, 0); digitalWrite(c, 1); digitalWrite(d, 1); digitalWrite(e, 1); digitalWrite(f, 1); digitalWrite(g, 1); }
void seven() { digitalWrite(a, 1); digitalWrite(b, 1); digitalWrite(c, 1); digitalWrite(d, 0); digitalWrite(e, 0); digitalWrite(f, 0); digitalWrite(g, 0);
}
void eight() { digitalWrite(a, 1); digitalWrite(b, 1); digitalWrite(c, 1); digitalWrite(d, 1); digitalWrite(e, 1); digitalWrite(f, 1); digitalWrite(g, 1);
}
void nine() { digitalWrite(a, 1); digitalWrite(b, 1); digitalWrite(c, 1); digitalWrite(d, 1); digitalWrite(e, 0); digitalWrite(f, 1); digitalWrite(g, 1);
}
void zero() { digitalWrite(a, 1); digitalWrite(b, 1); digitalWrite(c, 1); digitalWrite(d, 1); digitalWrite(e, 1); digitalWrite(f, 1); digitalWrite(g, 0); }
void pattern() { digitalWrite(a, 1); digitalWrite(b, 1); digitalWrite(c, 1); digitalWrite(d, 0); digitalWrite(e, 0); digitalWrite(f, 0); digitalWrite(g, 0); }
void pattern_2() { digitalWrite(a, 0); digitalWrite(b, 0); digitalWrite(c, 0); digitalWrite(d, 1); digitalWrite(e, 1); digitalWrite(f, 1); digitalWrite(g, 0); }
void capital_A() { digitalWrite(b, HIGH); digitalWrite(c, LOW); digitalWrite(a, HIGH); digitalWrite(f, HIGH); digitalWrite(g, HIGH); digitalWrite(e, HIGH); digitalWrite(d, HIGH); }
void capital_L() { digitalWrite(b, LOW); digitalWrite(c, LOW); digitalWrite(a, LOW); digitalWrite(f, HIGH); digitalWrite(g, LOW); digitalWrite(e, HIGH); digitalWrite(d, HIGH); }
void capital_F() { digitalWrite(b, LOW); digitalWrite(c, LOW); digitalWrite(a, HIGH); digitalWrite(f, HIGH); digitalWrite(g, HIGH); digitalWrite(e, HIGH); digitalWrite(d, LOW); }
void small_n() { digitalWrite(b, LOW); digitalWrite(c, HIGH); digitalWrite(a, LOW); digitalWrite(f, LOW); digitalWrite(g, HIGH); digitalWrite(e, HIGH); digitalWrite(d, LOW); }
void check_switch() { switch_status = read_switch_status(); if (switch_status) { if (sw4_status) { display_alarm(); }
if (sw1_status) { beep(); delay(300); menu(); // go to menu if switch one is pressed delay(200); }
else if (sw2_status) { display_date(); // display date if switch 2 is pressed }
else if (sw3_status) { display_temperature(); // display tempreature if switch 3 is pressed } } }
int display_on() { long t1 = 0; t1 = millis(); my_display[0]='A'; my_display[1]='L'; my_display[2]='0'; my_display[3]='n'; while (millis() - t1 >= 800) { send_to_display(); } }
int display_off() { long t1 = 0; t1 = millis(); my_display[0]='A'; my_display[1]='0'; my_display[2]='F'; my_display[3]='F'; while (millis() - t1 >= 800) { send_to_display(); } } boolean read_switch_status() { sw1_status = digitalRead(sw1); sw2_status = digitalRead(sw2); sw3_status = digitalRead(sw3); sw4_status = digitalRead(sw4); return (sw1_status || sw2_status || sw3_status || sw4_status); }
void display_temperature() { Serial.println("Display Temperature"); float temperature = 0; int x1 = 0, x2 = 0; long t = 0; temperature = rtc.getTemperature(); x1 = int(temperature); x2 = (temperature - x1) * 100; Serial.println(temperature); int_to_string(x1, x2); t = millis(); while ((millis() - t) <= 2000) // display temperature for 2 seconds { send_to_display(); digitalWrite(dot, HIGH); } }
void display_alarm() { Serial.println("Display Alarm"); long t = 0; int_to_string(alarm_hour, alarm_min); t = millis(); while ((millis() - t) <= 2000) // display Alarm time for 2 seconds { send_to_display(); digitalWrite(dot, HIGH); } }
void display_date() { long t = 0; int_to_string(my_date, my_month); t = millis(); while ((millis() - t) <= 1500) // display date for 1.5 seconds { send_to_display(); digitalWrite(dot, HIGH); } int_to_string(int(my_year / 100), (my_year - 2000)); // divide 4 digits into two seperate two digits t = millis(); while ((millis() - t) <= 1000) // display year for 0.5 seconds { send_to_display(); digitalWrite(dot, LOW); } }
void menu() { Serial.println("entered menu"); delay(100); while (!read_switch_status()) { digitalWrite(dot, 0); DateTime now = rtc.now(); if (now.second() % 2) { display_pattern_menu_2(); // display an animation when entered into menu } else { display_pattern_menu(); } } if (sw1_status) { // Set the clock parameters delay(100); if (set_time()) { return 1; } else if (set_date()) { return 1; } else if (set_year()) { return 1; } else if (set_alarm()) { return 1; } } else { beep(); } }
int set_time() {
int time_chandged_status = 0; beep(); delay(100); read_switch_status();
delay(100); while (!sw1_status) { read_switch_status(); if (sw2_status) { time_chandged_status = 1; delay(150); my_hour++; if (my_hour > 23) { my_hour = 0; } } else if (sw3_status) { time_chandged_status = 1; delay(100); my_min++; if (my_min > 59) { my_min = 0; } } else if (sw4_status) { beep(); return 1; } int_to_string(my_hour, my_min); send_to_display(); digitalWrite(dot, HIGH); }
if (time_chandged_status) { DateTime now = rtc.now(); my_date = now.day(); // get the current date my_month = now.month(); //now set the current date as it is and set updated time rtc.adjust(DateTime(my_year, my_month, my_date, my_hour, my_min, 0));; beep(); delay(50); } get_time_date(); delay(200); return 0; }
int set_date() { int date_chandged_status = 0; read_switch_status(); beep(); delay(100); while (!sw1_status) { read_switch_status(); if (sw2_status) { date_chandged_status = 1; delay(150); my_date++; if (my_date > 31) { my_date = 0; } } else if (sw3_status) { date_chandged_status = 1; delay(150); my_month++; if (my_month > 12) { my_month = 0; } } else if (sw4_status) { beep(); return 1; } int_to_string(my_date, my_month); send_to_display(); digitalWrite(dot, HIGH); }
if (date_chandged_status) { DateTime now = rtc.now(); my_hour = now.hour(); // get the current time my_min = now.minute(); rtc.adjust(DateTime(my_year, my_month, my_date, my_hour, my_min, 0)); beep(); delay(50); } get_time_date(); delay(200); return 0; }
int set_year() { int year_chandged_status = 0; read_switch_status(); beep(); while (!sw1_status) { read_switch_status(); if (sw2_status) { year_chandged_status = 1; delay(150); my_year--; if (my_year < 2000 ) { my_year = 2000; // Constrain the year lower limit to 2000 } } else if (sw3_status) { year_chandged_status = 1; delay(150); my_year++; if (my_year > 2070) // Constrain the year upper limit to 2070 { my_year = 2070; } } else if (sw4_status) { beep(); return 1; } int_to_string(int(my_year / 100), (my_year - 2000)); send_to_display(); digitalWrite(dot, LOW); }
if (year_chandged_status) { DateTime now = rtc.now(); my_hour = now.hour(); // get the current time my_min = now.minute(); my_date = now.day(); // get the current date my_month = now.month(); //now set the current time and date as it is and set updated year rtc.adjust(DateTime(my_year, my_month, my_date, my_hour, my_min, 0)); beep(); delay(50); } get_time_date(); delay(200); return 0; }
int set_alarm() { int alarm_chandged_status = 0; read_switch_status(); beep(); while (!sw1_status) { read_switch_status(); if (sw2_status) { alarm_chandged_status = 1; delay(150); alarm_hour++; if (alarm_hour > 23 ) { alarm_hour = 0; } } else if (sw3_status) { alarm_chandged_status = 1; delay(150); alarm_min++; if (alarm_min > 59) { alarm_min = 0; } } else if (sw4_status) { beep(); return 1; } int_to_string(alarm_hour, alarm_min); send_to_display(); digitalWrite(dot, HIGH); } EEPROM.write(hour_address, alarm_hour); // Set the Alarm parameters to EEPROM of ATmega328p EEPROM.write(min_address, alarm_min); get_time_date(); beep(); delay(200); return 0; }
void display_pattern_menu() { my_display[0] = 'q'; my_display[1] = 'p'; my_display[2] = 'q'; my_display[3] = 'p'; send_to_display(); }
void display_pattern_menu_2() { my_display[0] = 'p'; my_display[1] = 'q'; my_display[2] = 'p'; my_display[3] = 'q'; send_to_display(); }
void beep() { digitalWrite(buzzer_pin, HIGH); delay(beep_delay); digitalWrite(buzzer_pin, LOW); }
I have used serial communication for uploading the sketch. You can also upload it using a FTDI chip USB to TTL converter. To upload sketch through Arduino connect the Tx and Rx of arduino board to the PD1 and PD0 respectively of ATmega328p microcontroller. Common both the grounds and connect the reset pin of Arduino board to reset pin of microcontroller. Make sure that no microcontroller IC is inserted in the development board. Once done, select the appropriate board and port and hit upload. After uploading for first time random time will be displayed. User can set date, time as discussed in next step.
Attachments
Step 5: Success!
The project is almost done.You need to set date and time for first the time after upload. Switch 1 is defined for menu, switch 2 and 3 for changing parameters and switch 4 for going back or for canceling alarm. By default current time is displayed. Temperature, date and Alarm can be accessed by pressing switch 3, 2 and 4 respectively. Video explains to set clock parameters. Just set date and time and your clock is ready!!