top of page

Interfacing DS1307 RTC with STM32F407

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:

  1. Start Condition

    • The master initiates communication by generating a START condition on the I2C bus.

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

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

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

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

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

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


  1. Initialization:

    • Initialize the Hardware Abstraction Layer (HAL) library.

    • Configure the system clock to achieve the desired frequency.

    • Initialize GPIO and I2C peripherals.

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

  3. Get Time:

    • Read the time data from the DS1307 RTC using I2C.

    • Convert the received BCD data back to decimal format.

  4. 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:

  1. (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).

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

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

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

  1. Input:

    • dec = 25

  2. Calculating the Tens Place:

    dec / 10 = 25 / 10 = 2

  3. Shifting Left:

    (dec / 10) << 4 = 2 << 4 = 32

    • In binary: 32 is represented as 0010 0000.

  4. Calculating the Ones Place:

    dec % 10 = 25 % 10 = 5

  5. Combining Tens and Ones:

    return (32 | 5)

    • In binary:

      • Tens: 0010 0000

      • Ones: 0000 0101

    • Bitwise OR:

      0010 0000 | 0000 0101 = 0010 0101

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

  1. Input:

    • bcd = 0x23 (in binary: 0010 0011)

  2. 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).

  3. Multiplying by 10:

    (bcd >> 4) * 10

    • Multiply 2 by 10:

      2 * 10 = 20

  4. 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).

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

  1. Separate the Digits: The number 45 has two digits: 4 and 5.

  2. Convert Each Digit to BCD:

    • 4 in decimal is 0100 in BCD.

    • 5 in decimal is 0101 in BCD.

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


bottom of page