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:
- Time and Date Display: How to interface the DS3231 RTC module with Arduino to retrieve and display the current time and date on the OLED screen.
- Alarm Clock: Implementing alarm functionality, allowing users to set and manage alarms with audio notifications.
- Ambient Temperature: Using the DS3231's built-in temperature sensor to display the current ambient temperature.
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.
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:
Arduino Nano
RTC Module (DS3231)
OLED Display (SSD1306)
3 Push Buttons
Buzzer
Breadboard
Jumper Wires
Connections:
RTC Module to Arduino Nano:
SDA (RTC) to A4 (Arduino)
SCL (RTC) to A5 (Arduino)
VCC (RTC) to 5V (Arduino)
GND (RTC) to GND (Arduino)
OLED Display to Arduino Nano:
SDA (OLED) to A4 (Arduino)
SCL (OLED) to A5 (Arduino)
VCC (OLED) to 5V (Arduino)
GND (OLED) to GND (Arduino)
Buttons to Arduino Nano:
Button 1 (S1 Select):
One leg to D9 (Arduino)
The other leg to GND (through breadboard)
Button 2 (S2 Change):
One leg to D8 (Arduino)
The other leg to GND (through breadboard)
Button 3 (S3 Temp):
One leg to D7 (Arduino)
The other leg to GND (through breadboard)
Buzzer to Arduino Nano:
Positive leg of Buzzer to D10 (Arduino)
Negative leg of Buzzer to GND (Arduino)
Summary of Connections:
RTC Module:
SDA -> A4
SCL -> A5
VCC -> 5V
GND -> GND
OLED Display:
SDA -> A4
SCL -> A5
VCC -> 5V
GND -> GND
Buttons:
S1 (Select) -> D9
S2 (Change) -> D8
S3 (Temp) -> D7
Buzzer:
Positive -> D10
Negative -> GND
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
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....
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, 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.
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
Initial Setup:
The video begins by showing the setup of the components on a breadboard. The components include an Arduino Nano, an RTC module (likely DS3231), an OLED display, three push buttons, and a buzzer. The connections among these components are based on the schematic provided.
Displaying Time:
Once powered on, the OLED display shows the current time. The time is fetched from the RTC module, which keeps accurate time and is independent of the Arduino's power state.
Button Interaction:
Button S1 (Select): This button might be used to switch between different modes or settings. For example, it could switch between displaying time and date.
Button S2 (Change): This button could be used to change settings or increment values, such as setting the time or alarm.
Button S3 (Temperature): Holding down this button displays the ambient temperature on the OLED screen, which is read from the RTC module's built-in temperature sensor.
Displaying Temperature:
When the user presses and holds Button S3, the OLED display switches to show the ambient temperature in degrees Celsius. This demonstrates the temperature reading functionality of the RTC module.
Buzzer Feedback:
The buzzer provides audible feedback when the buttons are pressed, confirming the interaction. This can be particularly useful in noisy environments or for users who need an additional form of feedback.
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:
Arduino Nano: The central microcontroller for the project.
DS3231 RTC Module: For precise timekeeping and temperature measurement.
OLED Display: To visually present the time, date, and temperature.
Buzzer: For alarm notifications.
Push Buttons: Two buttons for user interaction, such as setting the time and alarms.
This comprehensive project will cover advanced topics such as:
PCB Design and Fabrication: Using PCB design software to create a custom board that neatly arranges all components, ensuring compactness and reliability.
Advanced Coding Techniques: Writing efficient and robust code to handle multiple functionalities, user inputs, and display updates.
Integration and Testing: Assembling the custom PCB, integrating all components, and thoroughly testing the system to ensure all features work seamlessly.
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 »»»