Let's understand the RTC first,
RTC Module(DS1307)
Datasheet: EW Skill Attachment DS1307-3421066 datasheet
To know more about the DS1307 module, please read the RTC (Real Time Clock ) DS1307 Module
Now we will interface this module with a microcontroller
Note: When interfacing I²C devices operating at different voltages (e.g., 5 V ↔ 3.3 V), always use a voltage level shifter to ensure safe logic levels and reliable communication.
HH:MM:SS
.
So, by connecting and configuring the master and slave devices and the I2C communication, we can implement the task.
Below are the solutions to the given task using different microcontrollers
We’re using an STM32 NUCLEO-F103RB board as a master, which operates at a 3.3V logic level.
Note: RTC DS1307 Breakout Power & Logic Levels
In our DS1307 breakout board, the onboard pull-up resistors connect SDA/SCL to VCC.
Recommended practice: Power the DS1307 at 5 V and use an I²C level shifter for safe, reliable operation.
Circuit Diagram
Project Setup in STM32CubeIDE
SystemClock_Config
).HAL_Init()
→ Initializes HAL and system tick.SystemClock_Config()
→ Configures system clock (HSI + PLL).MX_GPIO_Init()
→ Initializes GPIO ports.MX_USART2_UART_Init()
→ Configures UART2.MX_I2C1_Init()
→ Configures I2C1.I2C Initialization (MX_I2C1_Init)
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000; // 100kHz
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
if (HAL_I2C_Init(&hi2c1) != HAL_OK) {
Error_Handler();
}
Header includes
#include <stdio.h> → For snprintf (string formatting)
#include <string.h> → For strlen (string length operations)
Private defines
#define RTC_ADDR (0x68 << 1) → RTC I2C address (7-bit shifted left for HAL compatibility).
Utility Function
uint8_t bcdToDec(uint8_t val) {
return ((val >> 4) * 10 + (val & 0x0F));
}
uint8_t decToBcd(uint8_t val) {
return ((val / 10) << 4) | (val % 10);
}
bcdToDec(val)
→ Converts BCD (Binary-Coded Decimal) to decimal.decToBcd(val)
→ Converts decimal to BCD.
User-defined RTC Functions
A) Start RTC
// Start RTC by clearing CH (Clock Halt) bit
void rtcStart(void) {
uint8_t sec;
if (HAL_I2C_Mem_Read(&hi2c1, RTC_ADDR, 0x00, I2C_MEMADD_SIZE_8BIT, &sec, 1, 100) == HAL_OK) {
sec &= 0x7F; // Clear CH bit
HAL_I2C_Mem_Write(&hi2c1, RTC_ADDR, 0x00, I2C_MEMADD_SIZE_8BIT, &sec, 1, 100);
HAL_UART_Transmit(&huart2, (uint8_t *)"RTC Start\r\n", 11, HAL_MAX_DELAY);
} else {
HAL_UART_Transmit(&huart2, (uint8_t *)"RTC Comm Error\r\n", 17, HAL_MAX_DELAY);
}
}
B) Read and Print Time
// Read time and convert to 12-hour format
void readAndPrintTime(void) {
uint8_t data[3];
char buffer[64];
if (HAL_I2C_Mem_Read(&hi2c1, RTC_ADDR, 0x00, I2C_MEMADD_SIZE_8BIT, data, 3, 100) == HAL_OK) {
uint8_t second = bcdToDec(data[0] & 0x7F); // Clear CH bit
uint8_t minute = bcdToDec(data[1]);
uint8_t hourRaw = data[2];
uint8_t hour, hourVal;
const char *ampm;
if (hourRaw & 0x40) {
// 12-hour format
hourVal = bcdToDec(hourRaw & 0x1F);
ampm = (hourRaw & 0x20) ? "PM" : "AM";
hour = hourVal;
} else {
// 24-hour format
hourVal = bcdToDec(hourRaw & 0x3F);
if (hourVal == 0) {
hour = 12;
ampm = "AM";
} else if (hourVal < 12) {
hour = hourVal;
ampm = "AM";
} else {
hour = (hourVal == 12) ? 12 : hourVal - 12;
ampm = "PM";
}
}
snprintf(buffer, sizeof(buffer), "Time: %02d:%02d:%02d %s\r\n", hour, minute, second, ampm);
} else {
snprintf(buffer, sizeof(buffer), "RTC Read Error\r\n");
}
HAL_UART_Transmit(&huart2, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY);
}
Main Loop Logic
HAL_Delay(1000);
rtcStart();
while (1)
{
readAndPrintTime();
HAL_Delay(1000);
}
HAL_Delay(1000)
) → Allows hardware stabilization.Expected Output
The UART terminal will display either:
Time: HH:MM:SS AM/PM
"
To set time manually we can use below function
Set Time and Date (User-Defined Function)
void DS1307_SetTimeAndDate(uint8_t hour, uint8_t minute, uint8_t second) {
uint8_t rtc_data[3];
rtc_data[0] = decToBcd(second); // Seconds
rtc_data[1] = decToBcd(minute); // Minutes
rtc_data[2] = decToBcd(hour); // Hours (24-hour format)
HAL_I2C_Mem_Write(&hi2c1, RTC_ADDR, 0x00, I2C_MEMADD_SIZE_8BIT, rtc_data, 3, HAL_MAX_DELAY);
}
This function sets the DS1307 RTC time and date via I²C. It takes hours, minutes, seconds as inputs, converts them from decimal to BCD (as required by DS1307), and writes all 3 bytes (seconds → hours) starting from register 0x00 using HAL_I2C_Mem_Write.
Example:
DS1307_SetTimeAndDate(14, 33, 22); // Sets 14:33:22
The complete STM32CubeIDE project (including .ioc configuration, main.c, and HAL files) is available here:
📥 Download Project
We are using the ESP32 DevKit v4 development board and programming it using the Arduino IDE.
In both ESP32 master and slave codes, the Wire.h the library is used for I²C communication.
Note: RTC DS1307 Breakout Power & Logic Levels
In our DS1307 breakout board, the onboard pull-up resistors connect SDA/SCL to VCC.
Recommended practice: Power the DS1307 at 5 V and use an I²C level shifter for safe, reliable operation.
#include <Wire.h>
// DS1307 I2C address
#define rtc_addr 0x68
uint8_t error_code;
// Default I2C pins for ESP32
#define I2C_SDA 21
#define I2C_SCL 22
void setup() {
Serial.begin(115200);
Wire.begin(I2C_SDA, I2C_SCL); // Initialize I2C with custom pins
uint8_t rtc_response = 1;
Serial.print("Device scanning...");
// Scan for RTC with timeout (max 50 attempts = 5 seconds)
for (int attempts = 0; attempts < 50 && rtc_response != 0; attempts++) {
rtc_response = rtcResponding();
Serial.print(".");
delay(100);
}
if (rtc_response == 0) {
setTime(11, 11, 0); // Set RTC time (hour=0-23, min=0-59, sec=0-59)
if (!error_code) {
Serial.println("\nRTC device found");
Serial.println("Time set successfully");
} else {
Serial.println("\nCommunication error - time not set");
}
} else {
Serial.println("\nRTC not found! Check wiring");
}
}
void loop() {
// Your main code can go here
}
// Convert BCD to decimal
int bcdToDec(byte val) {
return (val >> 4) * 10 + (val & 0x0F);
}
// Convert decimal to BCD
byte decToBcd(int val) {
return ((val / 10) << 4) | (val % 10);
}
// Set time on DS1307
void setTime(int hour, int minute, int second) {
Wire.beginTransmission(rtc_addr);
Wire.write(0x00); // Seconds register
Wire.write(decToBcd(second)); // Set seconds
Wire.write(decToBcd(minute)); // Set minutes
Wire.write(decToBcd(hour)); // Set hours (24-hour format)
error_code = Wire.endTransmission(true); // true = send stop
}
// Check RTC presence
uint8_t rtcResponding() {
Wire.beginTransmission(rtc_addr);
return Wire.endTransmission(true); // true = send stop
}
readTime(int &hour, int &minute, int &second, String &period);
Function used to read the time from the DS1307 RTC module. We first send the register address, and then read the content of each register one by one.void rtcStart(void):
It is used to start the RTC module. The CH (Clock Halt) bit of the 0x00h address register is cleared to enable the RTC oscillator.setTime();
function in void setup()
to initially set the time in the RTC.uint8_t rtcResponding();
Check whether the device is present in the network or not.#include <Wire.h>
// ---------- Types ----------
struct RtcFields {
uint8_t sec, min, hour; // display hour (12 h), minute, second
bool is12h; // true if RTC is in 12 h mode
bool pm; // valid only if is12h==true; else derived from 24 h
uint8_t dayOfWeek, date, month, year2d; // DOW: 1..7, date: 1..31, month: 1..12, year: 00..99
};
// ---------- Prototypes ----------
bool rtcStartClock(); // clears CH bit (starts oscillator) without altering seconds
bool rtcSetTime(uint8_t sec, uint8_t min, uint8_t hour24,
uint8_t dayOfWeek, uint8_t date, uint8_t month, uint8_t year2d);
bool rtcGet(RtcFields &t); // burst-read and parse time/date registers
void printTimeLine(const RtcFields &t);
// ---------- Globals/Consts ----------
static const uint8_t RTC_ADDR = 0x68; // 7-bit I²C address (DS1307)
// --- BCD helpers: DS1307 stores time/date in BCD ---
static inline uint8_t decToBcd(uint8_t v) { return ((v / 10) << 4) | (v % 10); }
static inline uint8_t bcdToDec(uint8_t v) { return ((v >> 4) * 10) + (v & 0x0F); }
// Minimal I²C write with small retry for bus contention/transient NACKs
bool i2cWrite(uint8_t dev, const uint8_t* data, size_t len, uint8_t retries = 2) {
while (true) {
Wire.beginTransmission(dev);
Wire.write(data, len);
uint8_t e = Wire.endTransmission(true); // send STOP
if (e == 0) return true; // success
if (retries-- == 0) return false; // Data is not send successfully
delay(2);
}
}
bool i2cWrite1(uint8_t dev, uint8_t b) {
return i2cWrite(dev, &b, 1);
}
// DS1307 block write
bool rtcWriteRegs(uint8_t startReg, const uint8_t* buf, size_t n) {
uint8_t tmp[1 + 8]; // DS1307 timekeeper burst ≤ 7 bytes; keep small headroom
if (n > 8) return false;
tmp[0] = startReg;
memcpy(&tmp[1], buf, n);
return i2cWrite(RTC_ADDR, tmp, n + 1);
}
// DS1307 block read using register pointer then repeated START
bool rtcReadRegs(uint8_t startReg, uint8_t* buf, size_t n) {
if (!i2cWrite1(RTC_ADDR, startReg)) return false; // set register pointer
uint8_t read = Wire.requestFrom(RTC_ADDR, (uint8_t)n, true); // read N bytes + STOP
if (read != n) return false; // guard against short reads
for (size_t i = 0; i < n; ++i) buf[i] = Wire.read();
return true;
}
//Ensure oscillator is running: clear CH (bit7 of seconds) without changing seconds
bool rtcStartClock() {
uint8_t sec;
if (!rtcReadRegs(0x00, &sec, 1)) return false;
sec &= 0x7F; // CH=0 -> start clock
uint8_t pkt[2] = {0x00, sec};
return i2cWrite(RTC_ADDR, pkt, 2);
}
// Program time/date in 24 h mode (bit6=0). dayOfWeek is user-defined (1..7)
bool rtcSetTime(uint8_t sec, uint8_t min, uint8_t hour24,
uint8_t dayOfWeek, uint8_t date, uint8_t month, uint8_t year2d) {
uint8_t regs[7];
regs[0] = decToBcd(sec & 0x7F); // ensure CH=0
regs[1] = decToBcd(min);
regs[2] = decToBcd(hour24 & 0x3F); // 24 h mode: bit6=0
regs[3] = decToBcd(dayOfWeek);
regs[4] = decToBcd(date);
regs[5] = decToBcd(month);
regs[6] = decToBcd(year2d);
return rtcWriteRegs(0x00, regs, 7);
}
// Read and parse DS1307 time/date; handle both 12 h and 24 h encodings for hours
bool rtcGet(RtcFields &t) {
uint8_t r[7];
if (!rtcReadRegs(0x00, r, 7)) return false; // sec,min,hour,DOW,date,month,year
// Seconds: bit7 is CH (clock halt); masked off
t.sec = bcdToDec(r[0] & 0x7F);
t.min = bcdToDec(r[1] & 0x7F);
// Hours: handle mode (bit6) and AM/PM (bit5 when 12 h)
uint8_t rawHour = r[2];
t.is12h = (rawHour & 0x40) != 0;
if (t.is12h) {
t.pm = (rawHour & 0x20) != 0;
t.hour = bcdToDec(rawHour & 0x1F); // 1..12 as stored
} else {
uint8_t h24 = bcdToDec(rawHour & 0x3F); // 0..23
t.pm = (h24 >= 12);
// Convert to 12 h for display (01..12)
t.hour = (h24 == 0) ? 12 : (h24 > 12 ? (uint8_t)(h24 - 12) : h24);
}
t.dayOfWeek = bcdToDec(r[3]); // DS1307 does not validate DOW; user-defined
t.date = bcdToDec(r[4]);
t.month = bcdToDec(r[5]);
t.year2d = bcdToDec(r[6]);
return true;
}
// Human-readable single-line print: "HH:MM:SS AM DD/MM/YY"
void printTimeLine(const RtcFields &t) {
char buf[40];
snprintf(buf, sizeof(buf), "%02u:%02u:%02u %s %02u/%02u/%02u",
t.hour, t.min, t.sec, t.pm ? "PM" : "AM", t.date, t.month, t.year2d);
Serial.println(buf);
}
void setup() {
Serial.begin(115200);
Wire.begin(21, 22); // ESP32 default I²C pins
Wire.setClock(100000); // Standard mode (DS1307 spec)
if (!rtcStartClock()) Serial.println("RTC start failed");
// OPTIONAL: set ONCE then comment out to avoid resetting the RTC on every boot.
// Example below sets: 14:25:00, DOW=2, 02/09/25 (YY=25)
// rtcSetTime(0, 25, 14, 2, 2, 9, 25);
}
void loop() {
RtcFields t;
if (rtcGet(t)) {
printTimeLine(t);
} else {
Serial.println("RTC read error");
}
delay(1000);
}
rtcStartClock()
→ Reads the seconds register, clears CH (Clock Halt, bit7), and writes it back to ensure the oscillator is running.rtcSetTime(...)
(optional, only once) → Programs initial time/date values into DS1307 registers.rtcGet(t)
→ Reads 7 bytes (sec, min, hour, day-of-week, date, month, year) starting at register 0x00
.printTimeLine(t)
→ Formats and prints: HH:MM:SS AM DD/MM/YY
bcdToDec()
→ Converts a Binary Coded Decimal (e.g., 0x49) into normal decimal (49).decToBcd()
→ Converts decimal to BCD (used when setting the RTC).i2cWrite() / i2cWrite1()
→ Low-level wrappers for sending data over I²C with retries.rtcWriteRegs(startReg, buf, n)
→ Burst write of n registers starting at startReg.rtcReadRegs(startReg, buf, n)
→ Burst read of n registers starting at startReg.rtcStartClock()
→ Clears the CH bit in the seconds register, keeps other bits unchanged, ensuring the RTC ticks.rtcSetTime(ss, mm, hh, dow, dd, mm, yy)
→ Programs all 7 DS1307 timekeeper registers in 24 h mode.rtcGet()
→ Reads and parses all time/date registers into a user-friendly struct (RtcFields).printTimeLine()
→ Prints the parsed struct in a human-readable format.We are using the Arduino UNO development board and programming it using the Arduino IDE.
In this task, Arduino UNO acts as an I2C master, and the RTC module acts as a slave.
First, let's establish the hardware connection.
#include <Wire.h>
/* ===== Types =====
struct RtcFields {
uint8_t sec, min, hour; // hour is 1..12 (we render 12-hour format)
bool is12h; // true if RTC is in 12-hour mode
bool pm; // true if PM (derived or read)
uint8_t dayOfWeek, // 1=Sun .. 7=Sat (per DS1307/DS3231)
date, // 1..31
month, // 1..12
year2d; // 00..99
};
/* ===== Prototypes ===== */
bool rtcStartClock();
bool rtcSetTime(uint8_t sec, uint8_t min, uint8_t hour24,
uint8_t dayOfWeek, uint8_t date, uint8_t month, uint8_t year2d);
bool rtcGet(RtcFields &t);
void printTimeLine(const RtcFields &t);
/* ===== Globals / Consts ===== */
static const uint8_t RTC_ADDR = 0x68; // DS1307/DS3231 I2C address
/* BCD helpers: DS1307/DS3231 store time in Binary-Coded Decimal */
static inline uint8_t decToBcd(uint8_t v) { return ((v / 10) << 4) | (v % 10); }
static inline uint8_t bcdToDec(uint8_t v) { return ((v >> 4) * 10) + (v & 0x0F); }
/* Low-level I2C write with small retry loop.
Returns true on success (endTransmission == 0). */
bool i2cWrite(uint8_t dev, const uint8_t* data, size_t len, uint8_t retries = 2) {
while (true) {
Wire.beginTransmission(dev);
Wire.write(data, len);
uint8_t e = Wire.endTransmission(true);
if (e == 0) return true;
if (retries-- == 0) return false;
delay(2);
}
}
/* Single-byte variant */
bool i2cWrite1(uint8_t dev, uint8_t b) {
return i2cWrite(dev, &b, 1);
}
/* write RTC registers starting at 'startReg' */
bool rtcWriteRegs(uint8_t startReg, const uint8_t* buf, size_t n) {
uint8_t tmp[1 + 8]; // fits 7 time regs plus start addr
if (n > 8) return false;
tmp[0] = startReg;
memcpy(&tmp[1], buf, n);
return i2cWrite(RTC_ADDR, tmp, n + 1);
}
/* read RTC registers starting at 'startReg' */
bool rtcReadRegs(uint8_t startReg, uint8_t* buf, size_t n) {
if (!i2cWrite1(RTC_ADDR, startReg)) return false; // set address pointer
uint8_t read = Wire.requestFrom(RTC_ADDR, (uint8_t)n, (uint8_t)true);
if (read != n) return false;
for (size_t i = 0; i < n; ++i) buf[i] = Wire.read();
return true;
}
/* Clear CH (Clock Halt) bit in seconds register to start the oscillator.
Returns false if I2C fails. */
bool rtcStartClock() {
uint8_t sec;
if (!rtcReadRegs(0x00, &sec, 1)) return false; // reg 0x00 = seconds
sec &= 0x7F; // clear CH (bit7)
uint8_t pkt[2] = {0x00, sec};
return i2cWrite(RTC_ADDR, pkt, 2);
}
/* Set full time/date in 24-hour input form.
dayOfWeek: 1=Sun..7=Sat (device does not validate). */
bool rtcSetTime(uint8_t sec, uint8_t min, uint8_t hour24,
uint8_t dayOfWeek, uint8_t date, uint8_t month, uint8_t year2d) {
uint8_t regs[7];
regs[0] = decToBcd(sec & 0x7F); // keep CH=0
regs[1] = decToBcd(min);
regs[2] = decToBcd(hour24 & 0x3F); // 24h mode (bit6=0), 0..23
regs[3] = decToBcd(dayOfWeek);
regs[4] = decToBcd(date);
regs[5] = decToBcd(month);
regs[6] = decToBcd(year2d);
return rtcWriteRegs(0x00, regs, 7);
}
/* Read RTC and decode into RtcFields.
Handles both 12h (RTC bit6=1) and 24h (bit6=0) hour formats. */
bool rtcGet(RtcFields &t) {
uint8_t r[7];
if (!rtcReadRegs(0x00, r, 7)) return false;
uint8_t rawSec = r[0] & 0x7F; // mask out CH
uint8_t rawMin = r[1] & 0x7F;
uint8_t rawHour = r[2]; // contains 12/24h flags
t.sec = bcdToDec(rawSec);
t.min = bcdToDec(rawMin);
t.is12h = (rawHour & 0x40) != 0; // bit6: 1=12h
if (t.is12h) {
t.pm = (rawHour & 0x20) != 0; // bit5: 1=PM
t.hour = bcdToDec(rawHour & 0x1F); // 1..12
} else {
uint8_t h24 = bcdToDec(rawHour & 0x3F); // 0..23
t.pm = (h24 >= 12);
t.hour = (h24 == 0) ? 12 : (h24 > 12 ? (uint8_t)(h24 - 12) : h24); // to 12h view
}
t.dayOfWeek = bcdToDec(r[3]);
t.date = bcdToDec(r[4]);
t.month = bcdToDec(r[5]);
t.year2d = bcdToDec(r[6]);
return true;
}
/* Print one human-readable line: "hh:mm:ss AM dd/mm/yy" */
void printTimeLine(const RtcFields &t) {
char buf[40];
snprintf(buf, sizeof(buf), "%02u:%02u:%02u %s %02u/%02u/%02u",
t.hour, t.min, t.sec, t.pm ? "PM" : "AM", t.date, t.month, t.year2d);
Serial.println(buf);
}
void setup() {
Serial.begin(115200);
Wire.begin(); // UNO: SDA=A4, SCL=A5 (use external 4.7k pull-ups to VCC)
if (!rtcStartClock()) Serial.println("RTC start failed");
// ONE-TIME SET (uncomment, program once, then comment again)
// rtcSetTime(0, 52, 13, 2, 2, 9, 25); // 13:52:00, Tue, 02/09/25
}
void loop() {
RtcFields t;
if (rtcGet(t)) printTimeLine(t);
else Serial.println("RTC read error");
delay(1000);
}
}
rtcStartClock();
rtcSetTime(sec, min, hour24, dayOfWeek, date, month, year2d);
rtcGet(RtcFields &t);
printTimeLine(const RtcFields &t);
HH:MM: SS AM DD/MM/YY
) to the Serial Monitor.setup();
rtcStartClock()
, and allows a one-time clock set using rtcSetTime().loop();
rtcGet()
once per second and displays the time/date.setTime()
function in void setup()
to initially set the time in the RTC.#include <Wire.h>
// DS1307 I2C address
#define rtc_addr 0x68
uint8_t error_code;
void setup() {
Serial.begin(115200);
Wire.begin();
// Disable internal pull-ups on SDA/SCL
pinMode(A4, INPUT); // SDA
pinMode(A5, INPUT); // SCL
digitalWrite(A4, LOW); // ensure pull-up is off
digitalWrite(A5, LOW); // ensure pull-up is off
uint8_t rtc_response;
Serial.print("Device scanning...");
while(rtc_response = rtcResponding()){
Serial.print(".");
delay(100);
}
setTime(15, 47, 0); // Set RTC time (min= 0-59, sec= 0-59, Hours = 00 to 23)
if (!error_code) {
Serial.println("\nRTC device found");
Serial.println("Time set successfully");
} else {
Serial.println("Time is not Set...");
}
}
void loop() {
}
// Function to convert BCD to decimal
int bcdToDec(byte val) {
return ((val / 16) * 10) + (val % 16);
}
// Function to convert decimal to BCD
byte decToBcd(int val) {
return (val / 10 * 16) + (val % 10);
}
// Function to set the time on the RTC DS1307
void setTime(int hour, int minute, int second) {
Wire.beginTransmission(rtc_addr); // communication start
Wire.write(0x00); // Start at the 0th register (seconds)
Wire.write(decToBcd(second)); // Set seconds
Wire.write(decToBcd(minute)); // Set minutes
Wire.write(decToBcd(hour)); // Set hours
error_code = Wire.endTransmission(); // transfer data and end communication
}
// Check RTC slave device present or not
uint8_t rtcResponding() {
Wire.beginTransmission(rtc_addr);
uint8_t response = Wire.endTransmission();
return response;
}
HARDWARE SETUP
RTC Time Set
RTC Time Read
As we can see in the video, the time read from the RTC and printed on a serial monitor.