FWD Skill Zone
  • home
  • Power
    • Breadboard Power Supply
    • Variable Power Supply
    • DC to DC Boost Converter
  • Robotics
    • Braccio Robotic Arm
    • Voice-activated robotic arm
    • Smart Robot Car
  • Electronics
    • Arduino 101
    • Arduino 102
    • Arduino 103
    • Short Range Radar System
  • 3D print
  • VHDL
    • Intro to VHDL
    • 2 to 4 Binary Decoder
    • 3 to 8 Binary Decoder
    • Universal Shift Register
  • Verilog
    • Intro to Verilog
    • Verilog Construction
    • Verilog Examples
  • Machine Learning
    • Deep Learning
    • Transfer Learning
  • Contact us
  • home
  • Power
    • Breadboard Power Supply
    • Variable Power Supply
    • DC to DC Boost Converter
  • Robotics
    • Braccio Robotic Arm
    • Voice-activated robotic arm
    • Smart Robot Car
  • Electronics
    • Arduino 101
    • Arduino 102
    • Arduino 103
    • Short Range Radar System
  • 3D print
  • VHDL
    • Intro to VHDL
    • 2 to 4 Binary Decoder
    • 3 to 8 Binary Decoder
    • Universal Shift Register
  • Verilog
    • Intro to Verilog
    • Verilog Construction
    • Verilog Examples
  • Machine Learning
    • Deep Learning
    • Transfer Learning
  • Contact us
Picture

Arduino 103 for intermediates 


Arduino Clock with OLED display

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:
- Arduino Nano Board x 1
- DS3231 Real Time Clock Module x 1
- I2C OLED Display Module x1
- 5V Active buzzer x 1
- Tactile Push Button Switch x 3
- Jumper wires (as required)
​- Breadboard x1

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
Picture
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. 

Circuit Connection

Picture
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".) 
Picture
Open the "Adafruit_SSD1306.h" file in Notepad or Notepad++ and find the line with "#define SSD1306_128_64" as shown below
Picture
- Uncomment "#define SSD1306_128_64".
- ​Comment "#define SSD1306_128_32". as shows above.
- Save and close the file.
Arduino Codes:
First set the current time before using the RTC module
- Connect your Arduino to your computer, then open the Arduino IDE, and open the "SetTime" sketch from Arduino example files.  File --> Examples --> DS1307RTC --> SetTime.
- Upload the Arduino "SetTime" code onto your Arduino board.
- Open the Serial Monitor and set  the baud rate to 9600. Observe the time stamp in the Serial Monitor and if everything goes correct, you should see a success message at the Serial Monitor. 
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....

/* 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, 0,  127, 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(4,  10, 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. 

Picture
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.
Picture
Picture

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. 
Picture

Demo Video: ​https://youtu.be/v-EA5hhcpoU
​

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);
​}
Picture
PCB Layout
Picture
Arduino Clock, Temperature, and Alarm with OLED display

If you have enjoyed the Arduino beginner tutorial, then check out the intermediate and advanced Arduino projects under "Robotics" section of this website.
»»» END of  Arduino 103 »»»
  • home
  • Power
    • Breadboard Power Supply
    • Variable Power Supply
    • DC to DC Boost Converter
  • Robotics
    • Braccio Robotic Arm
    • Voice-activated robotic arm
    • Smart Robot Car
  • Electronics
    • Arduino 101
    • Arduino 102
    • Arduino 103
    • Short Range Radar System
  • 3D print
  • VHDL
    • Intro to VHDL
    • 2 to 4 Binary Decoder
    • 3 to 8 Binary Decoder
    • Universal Shift Register
  • Verilog
    • Intro to Verilog
    • Verilog Construction
    • Verilog Examples
  • Machine Learning
    • Deep Learning
    • Transfer Learning
  • Contact us