Interfacing DS1307 RTC with STM32F407
- Encode AND Decode
- Oct 7, 2024
- 10 min read
DS1307 Serial Real-Time Clock (RTC) Overview
The DS1307 is a low-power, serial real-time clock (RTC) that keeps track of the time and date, providing information in binary-coded decimal (BCD) format. It features a full clock/calendar system along with 56 bytes of non-volatile (NV) SRAM for general-purpose storage.
The communication between the DS1307 and a microcontroller is carried out over an I2C (Inter-Integrated Circuit) bus, which is bidirectional.
The clock/calendar function provides precise timekeeping with the following parameters:
Seconds
Minutes
Hours
Day of the Week
Date of the Month
Month
Year
Steps to interface
Master Write:
Start Condition
The master initiates communication by generating a START condition on the I2C bus.
Slave Address Transmission
The first byte transmitted by the master is the slave address byte, which contains the 7-bit DS1307 address. For the DS1307, this is fixed at 1101000.
The last bit of the byte is the R/W bit, where:
0 indicates a write operation.
1 indicates a read operation.
Acknowledge from DS1307
After receiving the slave address byte, the DS1307 decodes it and, if correct, sends an acknowledge (ACK) by pulling the SDA line low.
Word Address Transmission
Once the DS1307 acknowledges the slave address, the master sends a word address (0x00 for accessing the seconds register). This sets the register pointer inside the DS1307, allowing you to specify which register to read from or write to.
The DS1307 acknowledges this byte as well.
Data Transmission (for Write Operation)
After the word address, the master can send zero or more bytes of data. Each byte is acknowledged by the DS1307.
For example, this is where you would write data to set the time or date registers.
Auto-Increment
The DS1307 has an automatic increment feature for the register pointer, which means it will move to the next register after each data byte is written, allowing continuous writes without needing to manually specify each address.
Stop Condition
When the master finishes sending data, it generates a STOP condition to terminate the communication. This signals the DS1307 that the operation is complete.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define DS1307_ADDRESS (0x68 << 1) // Define DS1307 I2C address
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1; // I2C handle for communication
/* USER CODE BEGIN PV */
uint8_t write_time_date[7]; // Buffer to store time data for writing
uint8_t read_time_date[7]; // Buffer to store time data read from DS1307
uint8_t bcd_time_date[7]; // Buffer to store time data in BCD format
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); // Function to configure system clock
static void MX_GPIO_Init(void); // Function to initialize GPIO
static void MX_I2C1_Init(void); // Function to initialize I2C1
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// Convert decimal to BCD (Binary-Coded Decimal)
uint8_t DS1307_DEC2BCD(uint8_t dec) {
return ((dec / 10) << 4) | (dec % 10);
}
// Convert BCD to decimal
uint8_t DS1307_BCD2DEC(uint8_t bcd) {
return ((bcd >> 4) * 10) + (bcd & 0x0F);
}
// Set the time on the DS1307 RTC
void DS1307_SetTime(uint8_t year, uint8_t month, uint8_t date,uint8_t day,uint8_t hour, uint8_t minute, uint8_t second)
{
write_time_date[0] = DS1307_DEC2BCD(second); // Convert seconds to BCD
write_time_date[1] = DS1307_DEC2BCD(minute); // Convert minutes to BCD
write_time_date[2] = DS1307_DEC2BCD(hour); // Convert hours to BCD
write_time_date[3] = DS1307_DEC2BCD(day); // Convert hours to BCD
write_time_date[4] = DS1307_DEC2BCD(date); // Convert hours to BCD
write_time_date[5] = DS1307_DEC2BCD(month); // Convert hours to BCD
write_time_date[6] = DS1307_DEC2BCD(year); // Convert hours to BCD
HAL_I2C_Mem_Write(&hi2c1, DS1307_ADDRESS, 0x00, I2C_MEMADD_SIZE_8BIT, write_time_date, 7, HAL_MAX_DELAY); // Write time data to DS1307
}
// Get the time from the DS1307 RTC
void DS1307_GetTime() {
HAL_I2C_Mem_Read(&hi2c1, DS1307_ADDRESS, 0x00, I2C_MEMADD_SIZE_8BIT, bcd_time_date, 7, HAL_MAX_DELAY); // Read time data from DS1307
for(int i = 0; i < 7; i++)
{
read_time_date[i] = DS1307_BCD2DEC(bcd_time_date[i]); // Convert BCD data to decimal
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init(); // Initialize the HAL Library
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config(); // Configure system clock
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init(); // Initialize GPIO
MX_I2C1_Init(); // Initialize I2C1
/* USER CODE BEGIN 2 */
DS1307_SetTime(24, 12, 31, 3, 23, 59, 40);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
DS1307_GetTime(); // Get time
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE(); // Enable Power Control Clock
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); // Set voltage scaling
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; // Use High-Speed Internal oscillator
RCC_OscInitStruct.HSIState = RCC_HSI_ON; // Turn on HSI
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; // Default calibration value
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // Turn on PLL
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; // Use HSI as PLL source
RCC_OscInitStruct.PLL.PLLM = 8; // PLL M divider
RCC_OscInitStruct.PLL.PLLN = 168; // PLL N multiplier
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // PLL P divider
RCC_OscInitStruct.PLL.PLLQ = 7; // PLL Q divider
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) // Initialize oscillators
{
Error_Handler(); // Call error handler if initialization fails
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; // Configure clock types
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // Set PLL as SYSCLK source
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB clock divider
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; // APB1 clock divider
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // APB2 clock divider
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) // Configure CPU, AHB and APB clocks
{
Error_Handler(); // Call error handler if configuration fails
}
}
/**
* @brief I2C1 Initialization Function
* @param None
* @retval None
*/
static void MX_I2C1_Init(void)
{
/* USER CODE BEGIN I2C1_Init 0 */
/* USER CODE END I2C1_Init 0 */
/* USER CODE BEGIN I2C1_Init 1 */
/* USER CODE END I2C1_Init 1 */
hi2c1.Instance = I2C1; // Initialize I2C1
hi2c1.Init.ClockSpeed = 100000; // Set clock speed
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // Set duty cycle
hi2c1.Init.OwnAddress1 = 0; // Set own address
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // Set addressing mode
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; // Disable dual address mode
hi2c1.Init.OwnAddress2 = 0; // Set second own address
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; // Disable general call mode
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; // Disable clock stretching
if (HAL_I2C_Init(&hi2c1) != HAL_OK) // Initialize I2C1
{
Error_Handler(); // Call error handler if initialization fails
}
/* USER CODE BEGIN I2C1_Init 2 */
/* USER CODE END I2C1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOB_CLK_ENABLE(); // Enable GPIOB clock
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq(); // Disable interrupts
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
Algorithm/Flow of the Code
Initialization:
Initialize the Hardware Abstraction Layer (HAL) library.
Configure the system clock to achieve the desired frequency.
Initialize GPIO and I2C peripherals.
Set Time:
Convert provided time (hours, minutes, seconds, day, date, month, year) from decimal to BCD format.
Write the BCD time data to the DS1307 RTC via I2C.
Get Time:
Read the time data from the DS1307 RTC using I2C.
Convert the received BCD data back to decimal format.
Main Loop:
Continuously retrieve the current time from the DS1307 RTC.
Code Explanation
uint8_t write_time_date[7]; // Buffer to store time data for writing
uint8_t read_time_date[7]; // Buffer to store time data read from DS1307
uint8_t bcd_time_date[7]; // Buffer to store time data in BCD format
The write_time_date buffer holds the BCD values to be sent to the RTC, while the read_time_date and bcd_time_date buffers store the received time data.
Decimal to BCD Conversion
uint8_t DS1307_DEC2BCD(uint8_t dec)
{
return ((dec / 10) << 4) | (dec % 10);
}
This function converts a decimal number to its BCD equivalent.
Calculating the Tens Place:
(dec / 10)
This part divides the decimal number by 10 to extract the tens place digit. For example:
If dec = 25, then 25 / 10 gives 2 (the tens digit).
If dec = 7, then 7 / 10 gives 0 (since integer division truncates the decimal).
Shifting Left:
((dec / 10) << 4)
The result from the previous step (the tens digit) is then shifted left by 4 bits (i.e., multiplied by 16) to place it in the higher nibble (the upper 4 bits) of the resulting BCD value.
For example:
If the tens digit is 2, shifting it left gives 2 << 4 = 32, which is 0010 0000 in binary.
Calculating the Ones Place:
(dec % 10)
This part calculates the units place digit using the modulus operator. For example:
If dec = 25, then 25 % 10 gives 5 (the units digit).
If dec = 7, then 7 % 10 gives 7.
Combining Tens and Ones:
return ((dec / 10) << 4) | (dec % 10);
The | operator (bitwise OR) combines the shifted tens place value and the ones place value into a single 8-bit value.
The result will have the tens digit in the upper nibble and the ones digit in the lower nibble.
Example:
Let's take a specific example to illustrate this function's operation.
Example: Convert 25 to BCD
Input:
dec = 25
Calculating the Tens Place:
dec / 10 = 25 / 10 = 2
Shifting Left:
(dec / 10) << 4 = 2 << 4 = 32
In binary: 32 is represented as 0010 0000.
Calculating the Ones Place:
dec % 10 = 25 % 10 = 5
Combining Tens and Ones:
return (32 | 5)
In binary:
Tens: 0010 0000
Ones: 0000 0101
Bitwise OR:
0010 0000 | 0000 0101 = 0010 0101
Final Result:
The BCD representation of 25 is 0x25 (or 0010 0101 in binary).
BCD to Decimal Conversion
uint8_t DS1307_BCD2DEC(uint8_t bcd)
{ return ((bcd >> 4) * 10) + (bcd & 0x0F);
}
This function converts BCD data back to decimal.
Example Input:
Let's consider the input BCD value 0x23, which is the BCD representation of the decimal number 23.
Input:
bcd = 0x23 (in binary: 0010 0011)
Extracting the Tens Place:
bcd >> 4
Right-shifting bcd by 4 bits gives:
0010 0011 >> 4 = 0000 0010
This results in 2 (which represents the tens place).
Multiplying by 10:
(bcd >> 4) * 10
Multiply 2 by 10:
2 * 10 = 20
Extracting the Ones Place:
bcd & 0x0F
Using the bitwise AND operator with 0x0F (in binary: 0000 1111), we isolate the lower 4 bits (ones place):
0010 0011 & 0000 1111 = 0000 0011
This results in `3` (which represents the ones place).
Combining Tens and Ones:
return (20 + 3);
Add the values from the tens and ones places:
20 + 3 = 23
Setting the Time
void DS1307_SetTime(uint8_t year, uint8_t month, uint8_t date, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second)
{
write_time_date[0] = DS1307_DEC2BCD(second); //Convert seconds to BCD
write_time_date[1] = DS1307_DEC2BCD(minute); //Convert minutes to BCD
write_time_date[2] = DS1307_DEC2BCD(hour); // Convert hours to BCD
write_time_date[3] = DS1307_DEC2BCD(day); // Convert day to BCD
write_time_date[4] = DS1307_DEC2BCD(date); // Convert date to BCD
write_time_date[5] = DS1307_DEC2BCD(month); // Convert month to BCD
write_time_date[6] = DS1307_DEC2BCD(year); // Convert year to BCD
HAL_I2C_Mem_Write(&hi2c1, DS1307_ADDRESS, 0x00, I2C_MEMADD_SIZE_8BIT, write_time_date, 7, HAL_MAX_DELAY); // Write time data to DS1307
}
This function takes time parameters in decimal format, converts them to BCD, and writes them to the DS1307 over I2C.
Parameters:
year: Year (0-99 for the years 2000-2099).
month: Month (1-12).
date: Date (1-31).
day: Day of the week (1-7, where 1 = Sunday).
hour: Hour (0-23).
minute: Minute (0-59).
second: Second (0-59).
I2C Communication:
The HAL_I2C_Mem_Write function sends the data to the DS1307 starting at register 0x00, which is where the time registers begin.
Getting the Time
void DS1307_GetTime() {
HAL_I2C_Mem_Read(&hi2c1, DS1307_ADDRESS, 0x00, I2C_MEMADD_SIZE_8BIT, bcd_time_date, 7, HAL_MAX_DELAY); // Read time data from DS1307
for(int i = 0; i < 7; i++) {
read_time_date[i] = DS1307_BCD2DEC(bcd_time_date[i]); // Convert BCD data to decimal
}
}
This function reads the time data from the DS1307 and converts it from BCD to decimal.
I2C Communication:
The HAL_I2C_Mem_Read function retrieves the 7 bytes starting from the 0x00 register.
Conversion Loop:
A loop is used to convert each BCD byte into its decimal equivalent, storing it in the read_time_date buffer.
int main(void)
{
HAL_Init(); // Initialize the HAL Library
SystemClock_Config(); // Configure system clock
MX_GPIO_Init(); // Initialize GPIO
MX_I2C1_Init(); // Initialize I2C1
DS1307_SetTime(24, 12, 31, 3, 23, 59, 40); // Set the time to 23:59:40 on December 31, 2024
while (1)
{
DS1307_GetTime(); // Continuously get time from DS1307
// Optionally, display the time on an LCD or send it via UART
}
}
The main function performs the following tasks:
Initializes the HAL library and peripherals.
Sets the current time for the DS1307.
Enters an infinite loop where it continuously retrieves the current time from the RTC.
How BCD Works
In BCD, each digit of a decimal number is separately converted to binary:
Decimal 0: 0000
Decimal 1: 0001
Decimal 2: 0010
Decimal 3: 0011
Decimal 4: 0100
Decimal 5: 0101
Decimal 6: 0110
Decimal 7: 0111
Decimal 8: 1000
Decimal 9: 1001
Example
Let's take the decimal number 45 as an example.
Separate the Digits: The number 45 has two digits: 4 and 5.
Convert Each Digit to BCD:
4 in decimal is 0100 in BCD.
5 in decimal is 0101 in BCD.
Combine the BCD Values:
The BCD representation of 45 is 0100 0101.
Difference Between Binary and BCD
Binary: Represents numbers as a single continuous binary value. For example, the decimal number 45 is represented as 101101 in binary.
BCD: Represents each decimal digit separately in binary. The same number (45) is represented as 0100 0101 in BCD.
Comments