Arduino 103 for intermediates 

Arduino Clock with OLED display

As an advanced Arduino user, you are ready to tackle more intricate and challenging projects that push the boundaries of your skills in both hardware and software. This guide will focus on creating a an Arduino Clock with multiple functionalities, leveraging an OLED display and a DS3231 Real-Time Clock (RTC) module. The project will encompass displaying the date and time, setting an alarm clock, measuring ambient temperature, and designing a customized PCB integrating all necessary components.

In this project, you will create a multifunctional clock using an Arduino Nano. The clock will utilize a DS3231 RTC module for accurate timekeeping and an OLED display to show the date and time. This section will cover:

This project demonstrates how to build an Arduino based clock, temperature, and alarm display using Arduino NANO + SSD1306 OLED(128x64 ) + Real-Time Clock (RTC) + three Pushbuttons. The DS3231RTC is used as a real time clock chip which keeps the time running even if the main power supply is off (with the help of a battery), time, date and temperature of Nano board are displayed on the SSD1306 OLED.

Parts Required:

RTC Module

The DS3231 RTC module is a battery-backed real time clock (RTC) with an integrated temperature compensated crystal oscillator (TCXO) and crystal that allows your microcontroller-based project to keep track of time if power is lost or when the microcontroller is reprogrammed. A coin cell is required to use the battery-backup capabilities. 

The RTC module is perfect for datalogging, clock-building, time stamping, timers and alarms, and many more. The DS3231 datasheet explains that this RTC is an "Extremely Accurate I²C-Integrated RTC/TCXO/Crystal". The RTC is the most precise you can get in a small IC and at a low power package. The RTC maintains seconds, minutes, hours, day, date, month, and year information. 

Pin Description:

- 32K: 32kHz Output, when enabled, the output operates on either power supply. It may be left open if not used.

- SQW: Active-Low Interrupt or Square-Wave Output.  This multifunction pin is determined by the state of the INTCN bit in the Control Register (0Eh). When INTCN is set to logic 0, this pin outputs a square wave and its frequency is determined by RS2 and RS1 bits. When INTCN is set to logic 1, then a match between the timekeeping registers and either of the alarm registers activates the INT/SQW pin (if the alarm is enabled). Because the INTCN bit is set to logic 1 when power is first applied, the pin defaults to an interrupt output with alarms disabled. If not used, this pin can be left unconnected. 

- SCL: Serial Clock Input. This pin is the clock input for the I2C serial interface and is used to synchronize data movement on the serial interface. Up to 5.5V can be used for this pin, regardless of the voltage on VCC. 

- SDA: Serial Data Input/Output. This pin is the data input/output for the I2C serial interface. This open-drain pin requires an external pullup resistor. The pullup voltage can be up to 5.5V, regardless of the voltage on VCC.

- VCC: DC Power Pin for Primary Power Supply. This pin should be decoupled using a 0.1µF to 1.0µF capacitor. If not used, connect to ground.

- GND: Ground


Note: some RTC breakout modules have V_BAT interface for a Backup Power-Supply Input. When using the device with the VBAT input as the primary power source, this pin should be decoupled using a 0.1µF to 1.0µF low-leakage capacitor. When using the device with the VBAT input as the backup power source, the capacitor is not required. If VBAT is not used, connect to ground. 

Breadboard Circuit Connections

The provided schematic shows a project involving an Arduino Nano, an RTC (Real-Time Clock) module, an OLED display, buttons, and a buzzer. Below are the detailed connections based on the figure:

Components:

Connections:

RTC Module to Arduino Nano:

OLED Display to Arduino Nano:

Buttons to Arduino Nano:

Button 1 (S1 Select):

Button 2 (S2 Change):

Button 3 (S3 Temp):

Buzzer to Arduino Nano:

Summary of Connections:

RTC Module:

OLED Display:

Buttons:

Buzzer:

These connections align with the schematic, ensuring each component is properly wired to the Arduino Nano. This setup can be used for creating a real-time clock display with selectable and changeable settings via buttons, with audible feedback from the buzzer.

Once the circuit connection is complete, install the "​Adafruit_GFX.h" and "Adafruit_SSD1306" libraries into your computer. 

Open the Arduino IDE, select "Sketch"-> "Include Library"-> "Manage Libraries " and type in the library name to be included. After installing the above libraries, find the "Adafruit_SSD1306.h" file in the "Adafruit_SSD1306" library folder that you just downloaded. (On widows 10, click the search icon on the task bar and type "Adafruit_SSD1306.h".) 

Open the "Adafruit_SSD1306.h" file in Notepad or Notepad++ and find the line with "#define SSD1306_128_64" as shown below

- Uncomment "#define SSD1306_128_64".

- ​Comment "#define SSD1306_128_32" as shown above.

- Save and close the file.

Arduino Codes:

First set the current time before using the RTC module

All set! You can now use RTC module from now on. 

Open Arduino IDE and select "File"-> "New" and create a new project file. Copy the following Arduino code into the newly created project, debug it and upload it to your Arduino board.

​Have fun....

Arduino Code 

/* Written by FK Debebe, sept 19/2020

   Based on the adafruit tutorial and Arduino DS1307 example codes

   Arduino real time clock, alarm, and temperature monitor with DS3231 RTC module and SSD1306 OLED

   Arduino NANO + 128*64 OLED(1.3) + RTC + Pushbuttons + Buzzer

   == Connections ===

   NANO A4 --> RTC SDA --> OLED SDA

   NANO A5 --> RTC SCL --> OLED SCL

   NANO D9 --> S1 button (select)

   NANO D8 --> S2 button (change and alarm reset)

   NANO D7 --> S3 Temperature display

   NANO D3 --> Buzzer GND (black wire)

   NANO 5V --> Buzzer VCC (red wire)

   NANO 3V3 --> OLED VDD --> RTC VCC

   === end connection ===

   Thanks to Adafruit for providing open source libraries and example codes

*/

#include <Wire.h>                        // For I2C devices

#include <Adafruit_GFX.h>                // Include Adafruit graphics library

#include <Adafruit_SSD1306.h>            // Include Adafruit SSD1306 OLED driver

#define OLED_RESET 4

Adafruit_SSD1306 display(OLED_RESET);

#define button1    9                       // Button1 is connected to Arduino pin 9

#define button2    8                       // Button2 is connected to Arduino pin 8

#define button3    7                       // Button3 is connected to Arduino pin 7

#define buzzer     3                       // Buzzer is connected to Arduino pin 3


bool Stop;                                  //Bool to stop Alarm

int noteDuration = 1000;

//                NOTE_D4, NOTE_G4, NOTE_FS4, NOTE_A4, NOTE_G4, NOTE_C5, NOTE_AS4, NOTE_A4,NOTE_FS4, NOTE_G4, NOTE_A4, NOTE_FS4, NOTE_DS4, NOTE_D4,NOTE_C4, NOTE_D4,0,

int MyTones[] = {  294,      392,      370,      440,   392,     523,      466,      440,    370,      392,    440,      370,      311,      294,   262,    294,   0};

int duration [] = {8, 4, 8, 4, 4, 4, 4, 12, 4, 4, 4, 4, 4, 4, 4, 16, 4};


void setup(void) {

  Serial.begin(9600);

  pinMode(button1, INPUT_PULLUP);         // input pullUpSerial

  pinMode(button2, INPUT_PULLUP);

  pinMode(button3, INPUT_PULLUP);

  pinMode(buzzer, OUTPUT);

  digitalWrite(buzzer, HIGH);

  Stop = false;                           //alarm not yet

  delay(1000);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3D (for the 128x64)

  // Clear the display buffer.

  display.clearDisplay();

  display.display();

  // Border line around intro

  display.drawRect(0, 0127, 63, WHITE);

  display.setTextColor(WHITE, BLACK);

  draw_text(16, 25, "Made By: ", 2);

  //draw_text(16, 32, "FEKADU D.", 2);

  delay (3000);

  display.clearDisplay();

  display.display();

  display.drawCircle(63, 31.5, 31.5, WHITE);

  draw_text(40, 17, "FK", 4);

  draw_text(25, 56, "(Sept 26/2020)", 1);

  display.display();

  delay (3000);

  display.clearDisplay();

  display.display();

  draw_text(20, 0, "Switch usage:", 1);

  draw_text(0, 10, "S1= select", 1);

  draw_text(0, 30, "S2= adj & alarm", 1);

  draw_text(0, 50, "S3= Temperature ", 1);

  delay (3000);

  // Clear the display buffer.

  display.clearDisplay();

  display.display();

}

char Time[]     = "  :  :  ";

char alarmTime[]     = "  :  ";

char Calendar[] = "  /  /20  ";           // enough for 80 years

char temperature[] = " 00.00";

char temperature_msb;

byte i, second, minute, hour, day, date, month, year, temperature_lsb;


void display_day() {

  switch (day) {

    case 1:  draw_text(40, 0, " SUNDAY  ", 1); break; // draw_text(x_pos, y_pos, "  ", fontHeigh)

    case 2:  draw_text(40, 0, " MONDAY  ", 1); break;

    case 3:  draw_text(40, 0, " TUESDAY ", 1); break;

    case 4:  draw_text(40, 0, "WEDNESDAY", 1); break;

    case 5:  draw_text(40, 0, "THURSDAY ", 1); break;

    case 6:  draw_text(40, 0, " FRIDAY  ", 1); break;

    default: draw_text(40, 0, "SATURDAY ", 1);

  }

}

void DS3231_display() {

  // Convert BCD to decimal

  second = (second >> 4) * 10 + (second & 0x0F);

  minute = (minute >> 4) * 10 + (minute & 0x0F);

  hour   = (hour >> 4)   * 10 + (hour & 0x0F);

  date   = (date >> 4)   * 10 + (date & 0x0F);

  month  = (month >> 4)  * 10 + (month & 0x0F);

  year   = (year >> 4)   * 10 + (year & 0x0F);

  // End conversion

  Time[7]     = second % 10 + 48;

  Time[6]     = second / 10 + 48;

  Time[4]     = minute % 10 + 48;

  Time[3]     = minute / 10 + 48;

  Time[1]     = hour   % 10 + 48;

  Time[0]     = hour   / 10 + 48;


  alarmTime[4]     = minute % 10 + 48;

  alarmTime[3]     = minute / 10 + 48;

  alarmTime[1]     = hour   % 10 + 48;

  alarmTime[0]     = hour   / 10 + 48;


  Calendar[9] = year   % 10 + 48;

  Calendar[8] = year   / 10 + 48;

  Calendar[4] = month  % 10 + 48;

  Calendar[3] = month  / 10 + 48;

  Calendar[1] = date   % 10 + 48;

  Calendar[0] = date   / 10 + 48;


  // Border line around time stamp

  // can also use: display.drawRect(8, 30,  110, 62, WHITE);

  display.drawFastHLine(8, 38, 110, WHITE);

  display.drawFastHLine(8, 62, 110, WHITE);

  display.drawFastVLine(8, 38, 24, WHITE);

  display.drawFastVLine(118, 38, 24, WHITE);

  draw_text(410, Calendar, 2);                     // Display the date (format: dd/mm/yyyy)

  draw_text(16, 45, Time, 2);                         // Display the time

}

/*--------- alarm clock --------*/

void alarmDisp() {

  // Set the alarm time here

  if (Time[0] == '1' && Time[1] == '1' && Time[3] == '2' && Time[4] == '5')

  {

    while (Stop == false)

    {

      // Clear the display buffer.

      display.clearDisplay();

      display.display();

      draw_text(40, 10, "Alarm", 2);

      draw_text(25, 35, alarmTime, 3);         // Display the alarm time

      display.display();

      for (int i = 0; i <= 17; i++)

      {

        //Clear the display buffer.

        tone(buzzer, MyTones[i]);

        delay (noteDuration / duration [i]);

        if (!digitalRead(button2)) {          // If button B1 is pressed

          while (!digitalRead(button2));      // wait for button release

          //if (digitalRead(button2) == 1)

          Stop = true;                        // Alarm off

          noTone(buzzer);

          delay (50);

          // Clear the display buffer.

          display.clearDisplay();

          display.display();

        }

        //if it lastad for more than 1 minutes

        if (Time[0] == '1' && Time[1] == '1' && Time[3] == '2' && Time[4] == (Time[4] + 1))

        {

          Stop = true;                        // Alarm off

          noTone(buzzer);

          delay (50);

          // Clear the display buffer.

          display.clearDisplay();

          display.display();

        }

      }

    }

    // delay (80000);

    // Stop = false;

  }

  else

  {

    digitalWrite(buzzer, HIGH);

  }

}

/*-----end of alarm clock --------*/

void dispTemp () {

  if (temperature_msb < 0) {

    temperature_msb = abs(temperature_msb);

    temperature[0] = '-';

  }

  else

    temperature[0] = ' ';

  temperature_lsb >>= 6;

  temperature[2] = temperature_msb % 10  + 48;

  temperature[1] = temperature_msb / 10  + 48;

  if (temperature_lsb == 0 || temperature_lsb == 2) {

    temperature[5] = '0';

    if (temperature_lsb == 0) temperature[4] = '0';

    else                     temperature[4] = '5';

  }

  if (temperature_lsb == 1 || temperature_lsb == 3) {

    temperature[5] = '5';

    if (temperature_lsb == 1) temperature[4] = '2';

    else                     temperature[4] = '7';

  }

  delay (200);

  // Clear the display buffer.

  display.clearDisplay();

  display.display();

  /*-------------*/

  draw_text(40, 15, "TEMP:", 2);

  draw_text(10, 35, temperature, 2);          // Display the temperature

  display.drawRect(90, 35, 5, 5, WHITE);      // Put degree symbol ( ° )

  draw_text(100, 35, "C", 2);

  draw_text(10, 56, "(Los Angeles, CA)", 1);

  delay (2000);

  // Clear the display buffer.

  display.clearDisplay();

  display.display();

}

void blink_parameter() {

  byte j = 0;

  while (j < 10 && digitalRead(button1) && digitalRead(button2)) {

    j++;

    delay(25);

  }

}

byte edit(byte x_pos, byte y_pos, byte parameter) {

  char text[3];

  sprintf(text, "%02u", parameter);

  while (!digitalRead(button1));                     // Wait until button B1 released

  while (true) {

    while (!digitalRead(button2)) {                  // If button B2 is pressed

      parameter++;

      if (i == 0 && parameter > 31)                  // If date > 31 ==> date = 1

        parameter = 1;

      if (i == 1 && parameter > 12)                  // If month > 12 ==> month = 1

        parameter = 1;

      if (i == 2 && parameter > 99)                  // If year > 99 ==> year = 0

        parameter = 0;

      if (i == 3 && parameter > 23)                  // If hours > 23 ==> hours = 0

        parameter = 0;

      if (i == 4 && parameter > 59)                  // If minutes > 59 ==> minutes = 0

        parameter = 0;

      sprintf(text, "%02u", parameter);

      draw_text(x_pos, y_pos, text, 2);

      delay(200);                                    // Wait 200ms

    }

    draw_text(x_pos, y_pos, "  ", 2);

    blink_parameter();

    draw_text(x_pos, y_pos, text, 2);

    blink_parameter();

    if (!digitalRead(button1)) {                     // If button B1 is pressed

      i++;                                           // Increament 'i' for the next parameter

      return parameter;                              // Return parameter value and exit

    }

  }

}

void draw_text(byte x_pos, byte y_pos, char *text, byte text_size) {

  display.setCursor(x_pos, y_pos);

  display.setTextSize(text_size);

  display.print(text);

  display.display();

}

void loop() {

  if (!digitalRead(button1)) {                       // If button B1 is pressed

    i = 0;

    while (!digitalRead(button1));                   // Wait for button B1 release

    while (true) {

      while (!digitalRead(button2)) {                // While button B2 pressed

        day++;                                       // Increment day

        if (day > 7) day = 1;

        display_day();                               // Call display_day function

        delay(200);                                  // Wait 200 ms

      }

      draw_text(40, 0, "         ", 1);

      blink_parameter();                             // Call blink_parameter function

      display_day();                                 // Call display_day function

      blink_parameter();                             // Call blink_parameter function

      if (!digitalRead(button1))                     // If button B1 is pressed

        break;

    }

    date   = edit(4, 10, date);                      // Edit date

    month  = edit(38, 10, month);                    // Edit month

    year   = edit(100, 10, year);                    // Edit year

    hour   = edit(16, 45, hour);                     // Edit hours

    minute = edit(52, 45, minute);                   // Edit minutes

    // Convert decimal to BCD

    minute = ((minute / 10) << 4) + (minute % 10);

    hour = ((hour / 10) << 4) + (hour % 10);

    date = ((date / 10) << 4) + (date % 10);

    month = ((month / 10) << 4) + (month % 10);

    year = ((year / 10) << 4) + (year % 10);

    // End conversion

    /*----- Write data to DS3231 RTC --------*/

    Wire.beginTransmission(0x68);               // Start I2C protocol with DS3231 address

    Wire.write(0);                              // Send register address

    Wire.write(0);                              // Reset sesonds and start oscillator

    Wire.write(minute);                         // Write minute

    Wire.write(hour);                           // Write hour

    Wire.write(day);                            // Write day

    Wire.write(date);                           // Write date

    Wire.write(month);                          // Write month

    Wire.write(year);                           // Write year

    Wire.endTransmission();                     // Stop transmission and release the I2C bus

    delay(200);                                 // Wait 200ms

  }

  if (!digitalRead(button3)) {                  // If button3 is pressed

    while (!digitalRead(button3));              // Wait for button3 release

    while (true) {

      dispTemp ();

      delay (50);

      break;

    }

  }

  Wire.beginTransmission(0x68);                 // Start I2C protocol with DS3231 address

  Wire.write(0);                                // Send register address

  Wire.endTransmission(false);                  // I2C restart

  Wire.requestFrom(0x68, 7);                    // Request 7 bytes from DS3231 and release I2C bus at end of reading

  second = Wire.read();                         // Read seconds from register 0

  minute = Wire.read();                         // Read minuts from register 1

  hour   = Wire.read();                         // Read hour from register 2

  day    = Wire.read();                         // Read day from register 3

  date   = Wire.read();                         // Read date from register 4

  month  = Wire.read();                         // Read month from register 5

  year   = Wire.read();                         // Read year from register 6

  Wire.beginTransmission(0x68);                 // Start I2C protocol with DS3231 address

  Wire.write(0x11);                             // Send register address

  Wire.endTransmission(false);                  // I2C restart

  Wire.requestFrom(0x68, 2);                    // Request 2 bytes from DS3231 and release I2C bus at end of reading

  temperature_msb = Wire.read();                // Read temperature MSB

  temperature_lsb = Wire.read();                // Read temperature LSB



  display_day();

  DS3231_display();                             // Diaplay time & calendar

  alarmDisp();

  delay(50);                                    // Wait 50ms

}

// End of code.



Experiments

​After the code is uploaded onto the board, you can start playing with the switches. 

Setting Date and Time

The first button switch, S1, connected to the Arduino pin 9, is used to select a time functions to be adjusted. Press and hold S1 for few seconds to enter time setting function. You will see the cursor blinking next to the first function. Single press S2, connected to Arduino pin 8, to adjust the data. In similar fashion, adjust the calendar date and time to the current time and date. 

Alarm Clock

The alarm clock of this tutorial is software controlled by the Arduino code. (If you want more challenge yourself, try converting the Alarm to a hardware-controlled function by adding more buttons or by using the existing buttons.)

For this tutorial, the alarm clock is set at "11:25". If you need to change it, go to the "void alarmDisp" function at line 126 and modify the code to the desired alarm time as shown in the code snippet below.

Temperature display

​The RTC module can read the temperature and uses this reading for temperature compensation to calibrate the adjustable capacitors in order to maintain time and date with accuracy. 

- Press and hold S3 to display the ambient temperature in degree Celsius. 


The demo video at this link showcases a project using an Arduino Nano, an RTC (Real-Time Clock) module, an OLED display, buttons, and a buzzer.

Overview of the Demo


Once the circuit functionality is verified on a breadboard, the design can be implemented onto a circuit board as shown below.

Note: 

On the PCB, I used only two tactile switches instead of three for space optimization purpose. This requires us to modify the above code so that we can use the second button (select button) to display temperature with a long press (pressing and holding "select" button for more than two seconds will call the temperature display function).  

// If buttonPin2 is long press​

if (SWfunction == -1{    dispTemp ();

button2.clicks = 0;

SWfunction = 0;

delay (50);

​} 

Customized PCB Design

To streamline the project and enhance its durability, you'll design a custom PCB that integrates all the necessary components, including:

This comprehensive project will cover advanced topics such as:


PCB Layout 

Arduino Clock, Temperature, and Alarm with OLED display 

By the end of this guide, you'll have built a fully functional, advanced Arduino clock with multiple features, and you'll have gained valuable experience in PCB design, complex programming, and system integration. This project not only demonstrates your proficiency with Arduino but also prepares you for even more ambitious and sophisticated electronic projects.

If you have enjoyed the Arduino beginner tutorial, then check out the intermediate and advanced Arduino projects under "RoboTronics" section of this website.

»»» END of  Arduino 103 »»»