# [STM32] RTC DS1302
## **RTC (Real-Time Clock) 即時時鐘介紹**
### **1. RTC 概念**
RTC(Real-Time Clock,即時時鐘)是一種特殊的時鐘裝置,用於提供持續的時間計時,即使系統關機或重啟,它仍能保持計時功能。RTC 主要應用於計算機、嵌入式系統、智慧型裝置等,確保系統能夠準確地追蹤時間和日期。
### **2. RTC 的特點**
- **低功耗**:RTC 主要依靠獨立的低功耗電池 (VBAT) 運作,即使主系統電源關閉,RTC 仍能運行。
- **獨立計時**:RTC 內部包含一個 32.768 kHz 的石英晶振,用於保持時間精確度。
- **日期與時間管理**:RTC 通常具備秒、分、時、日、月、星期和年等時間信息,並支援閏年自動調整。
- **支援低功耗模式**:某些 RTC 內建低功耗模式,適用於電池供電的嵌入式系統。
### **3. RTC 的應用**
- **計算機和嵌入式系統**:確保系統開機後能獲取正確時間。
- **智慧家庭**:用於定時設備,如智慧燈光控制。
- **數據記錄器**:確保數據紀錄時間的準確性,例如工業監測設備。
- **消費性電子產品**:如智慧手錶、行動裝置、MP3 播放器等。
---
## **DS1302 RTC 晶片介紹**
DS1302 是一款低功耗、串列介面的 RTC 晶片,適用於各種微控制器應用,如 STM32、Arduino、Raspberry Pi 等。
### **1. DS1302 主要特點**
- **串列通訊介面**:使用三線 SPI (SCLK, I/O, CE) 介面與微控制器通訊,易於連接。
- **時間與日期管理**:支援秒、分、時、日、月、星期與年計時,並具備 12/24 小時格式選擇。
- **低功耗設計**:當主電源關閉時,可使用備用電池 (VBAT) 維持計時功能。
- **內建 RAM**:提供 31 個 8-bit RAM 寄存器,其中 8 個用於 RTC,其他 23 個可作為 SRAM 使用。
- **工作電壓範圍廣**:支援 2V~5.5V 工作電壓,適用於不同應用環境。
- **時鐘精準度校正**:具備時間補償功能,可透過軟體校正計時誤差。
### **2. DS1302 內部架構**
- **主時鐘電路**:由 32.768 kHz 石英晶振提供時鐘訊號,確保時間計時準確。
- **備用電池管理**:當主電源 (VCC) 斷電時,RTC 自動切換至 VBAT 電源,確保時間保持不變。
- **通訊介面**:使用簡單的三線 SPI 介面,可輕鬆與各種微控制器連接。
### **3. DS1302 應用**
- **數據記錄設備**:確保時間戳記的準確性,如溫度監測器、氣象站等。
- **智慧家居**:應用於定時開關、電器控制等場景。
- **電腦與嵌入式系統**:提供系統時間維持功能,如 BIOS 時鐘。
- **低功耗設備**:適用於需要長期運行且低能耗的電子裝置。
---
## **RTC 在 STM32F411 上的應用**
STM32F411 內建 RTC 模組,支援低功耗模式,並可使用 VBAT 保持時間資訊,因此在 STM32F411 上通常不需要外接 DS1302 等外部 RTC 晶片。內建 RTC 具有以下優勢:
- 低功耗運作,適合電池供電應用。
- 內建時鐘補償機制,提高計時準確性。
- 支援警報與定時喚醒功能,可用於節能應用。
## 實作
### Demo
{%youtube RJDkueZB_64 %}
## STM32CubeIDE設定
### PIN 設定

---
### GPIO設定

---
### RCC設定

---
### SYS設定

---
### UART設定

---
### Clock設定

---
### 外設檔案分開+生成

#### 生成程式碼

---
### Nucleo_Pinou

---
### 電路圖

---
## 實作
###
:::spoiler 流程圖
```mermaid
flowchart TD
A[啟動程式] --> B[系統初始化]
B --> C[初始化 DS1302]
C --> D{檢查初始化標記}
D -- 未初始化 --> E[設定時間]
E --> F[寫入初始化標記]
D -- 已初始化 --> G[讀取當前時間]
F --> G
G --> H["顯示時間"]
H --> L
subgraph L[while loop]
J[每秒讀取 DS1302 時間]
J --> K["顯示時間"]
end
```
:::
---
### 程式碼
####
:::spoiler main.c
```c=
/* USER CODE BEGIN Includes */
#include "ds1302.h"
#include "stdio.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 2 */
/* 初始化 DS1302 */
ds1302_init();
/* 讀取 DS1302 內部 RAM 的標記 (判斷是否已經設定過時間) */
uint8_t init_flag = ds1302_read_ram(0);
/* 如果 init_flag 不等於 0xA5,代表 DS1302 還沒初始化過時間 */
if (init_flag != 0xA5) {
printf("初始化 DS1302 的時間...\n");
/* 設定時間 (請手動修改為當前時間) */
Time_s newTime = {MONDAY, 25, 3, 17, 15, 58, 25, DS1302_CLK_SYSTEM_24, DS1302_CLK_AM_PERIOD};
//星期、年、月、日、時、分、秒、12/24小時制、上午/下午(12小時制)
ds1302_set_time(&newTime);
/* 記錄初始化標記,確保下次 STM32 重啟不會再重新設定時間 */
ds1302_write_ram(0, 0xA5);
} else {
printf("DS1302 已經初始化過,直接使用當前時間。\n");
}
/* 讀取 DS1302 當前時間 */
Time_s currentTime;
ds1302_get_time(¤tTime);
printf("當前時間: 20%02d-%02d-%02d %02d:%02d:%02d\n",
currentTime.year, currentTime.month, currentTime.date,
currentTime.hour, currentTime.min, currentTime.sec );
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(1000);
ds1302_get_time(¤tTime);
printf("Time: Date: 20%02d-%02d-%02d %02d:%02d:%02d \n",
currentTime.year, currentTime.month, currentTime.date,
currentTime.hour, currentTime.min, currentTime.sec );
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
```
:::
####
:::spoiler usart.c
```c=
/* USER CODE BEGIN 0 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/* USER CODE END 0 */
```
:::
####
:::spoiler ds1302.h
```c=
/**
******************************************************************************
* @file ds1302.c
* @author Aaron Escoboza
* @brief header file for DS1302 RTC driver
* @link GitGub : https://github.com/aaron-ev
******************************************************************************
*/
#ifndef __DS1302__H
#define __DS1302__H
#include "stm32f4xx.h"
#include "stdint.h"
/************* Configuration section **************/
#define DS1302_GPIO_PORT GPIOA
#define DS1302_PIN_SCLK GPIO_PIN_7
#define DS1302_PIN_SDA GPIO_PIN_6
#define DS1302_PIN_RST GPIO_PIN_5
/************* Configuration section **************/
#define DS1302_RAM_SIZE 31
/**
* Enumeration for days of the week
*/
typedef enum
{
MONDAY = 1, /* Valid values 1 - 7*/
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY,
}Day;
/**
* Enumeration for clock system (12/24)
*/
typedef enum
{
DS1302_CLK_SYSTEM_24,
DS1302_CLK_SYSTEM_12,
}ClockSystem;
/**
* Enumeration for clock period (AM or PM)
*/
typedef enum
{
DS1302_CLK_AM_PERIOD,
DS1302_CLK_PM_PERIOD,
}ClockPeriod;
/**
* Structure for holding time data
*/
typedef struct
{
Day day; /* Range: enum day values*/
uint8_t year; /* Range: 0 - 99 */
uint8_t month; /* Range: 1 - 12 */
uint8_t date; /* Range: 1 - 31 */
uint8_t hour; /* Range: 1-12/0-23*/
uint8_t min; /* Range: 0-59 */
uint8_t sec; /* Range: 0-59 */
ClockSystem clockSystem; /* 12 or 24 clock system */
ClockPeriod clockPeriod; /* AM or PM*/
}Time_s;
void ds1302_init(void);
void ds1302_get_time(Time_s *time);
void ds1302_set_time(const Time_s *time);
void ds1302_clear_ram(void);
uint8_t ds1302_read_ram(const uint8_t addr);
void ds1302_write_ram(const uint8_t addr, uint8_t data);
ClockSystem ds1302_get_clock_system(void);
ClockPeriod ds1302_get_clock_period(void);
void ds1302_write_ram_burst(uint8_t len, uint8_t *buff);
void ds1302_read_ram_burst(uint8_t len, uint8_t *buff);
#endif
```
:::
####
:::spoiler ds1302.c
```c=
/**
******************************************************************************
* @file ds1302.c
* @author Aaron Escoboza
* @brief Source file for DS1302 RTC driver
* @link GitGub : https://github.com/aaron-ev
******************************************************************************
*/
#include "ds1302.h"
#include <string.h>
/* Size definitions */
#define DS1302_DATA_SIZE 8
#define DS1302_CMD_SIZE 8
#define DS1302_RAM_ADDR_START 0xC0
/* Register definition according to the spec */
#define DS1302_REG_SEC 0x80
#define DS1302_REG_MIN 0x82
#define DS1302_REG_HOUR 0x84
#define DS1302_REG_DATE 0x86
#define DS1302_REG_MONTH 0x88
#define DS1302_REG_DAY 0x8A
#define DS1302_REG_YEAR 0x8C
#define DS1302_REG_CONTROL 0x8E
#define D1302_RAM_BURST_MODE 0xFE
#define D1302_CAL_BURST_MODE 0xBE /* Burst mode for calendar */
/* Macros for handling BCD format data stored in the RTC device */
#define BCD_TO_DEC(val) ((val / 16 * 10) + (val % 16))
#define DEC_TO_BCD(val) ((val / 10 * 16) + (val % 10))
/* Masks to get fields in the calendar*/
#define MASK_CLOCK_SYSTEM 0x80
#define MASK_CLOCK_PERIOD 0x20
#define MASK_HOURS_24 0x3F
#define MASK_HOURS_12 0x1F
#define MASK_SECONDS 0x7F
/**
* @brief us delay, note:It implements it using DWT implemented in most
* M3, M4 and M7 devices.
* @param usDelay delay in microseconds
* @return void
*/
void delayUS(uint32_t usDelay)
{
volatile uint32_t cycles = (SystemCoreClock/1000000L) * usDelay;
volatile uint32_t startCYCCNT = DWT->CYCCNT;
do {} while(DWT->CYCCNT - startCYCCNT < cycles);
}
/**
* @brief write a high value on RST line
*
* @param void
* @return void
*/
static void set_rst(void)
{
HAL_GPIO_WritePin(DS1302_GPIO_PORT, DS1302_PIN_RST, GPIO_PIN_SET);
}
/**
* @brief write a high value on CLK line
*
* @param void
* @return void
*/
static void set_clk(void)
{
HAL_GPIO_WritePin(DS1302_GPIO_PORT, DS1302_PIN_SCLK, GPIO_PIN_SET);
}
/**
* @brief write a low value on CLK line
*
* @param void
* @return void
*/
static void reset_clk(void)
{
HAL_GPIO_WritePin(DS1302_GPIO_PORT, DS1302_PIN_SCLK, GPIO_PIN_RESET);
}
/**
* @brief write a low value on RST line
*
* @param void
* @return void
*/
static void reset_rst(void)
{
HAL_GPIO_WritePin(DS1302_GPIO_PORT, DS1302_PIN_RST, GPIO_PIN_RESET);
}
/**
* @brief Clock cycle on CLK line
* @param void
* @return void
*/
static void set_clk_cycle(void)
{
set_clk();
delayUS(1);
reset_clk();
delayUS(1);
}
/**
* @brief Write a high value on DTA line
* @param void
* @return void
*/
static void set_data(void)
{
HAL_GPIO_WritePin(DS1302_GPIO_PORT, DS1302_PIN_SDA, GPIO_PIN_SET);
}
/**
* @brief Write a low value on DTA line
* @param void
* @return void
*/
static void reset_data(void)
{
HAL_GPIO_WritePin(DS1302_GPIO_PORT, DS1302_PIN_SDA, GPIO_PIN_RESET);
}
/**
* @brief Set idle state SDA, CLK and RST low value.
* @param void
* @return void
*/
static void set_idle_state(void)
{
reset_data();
reset_clk();
reset_rst();
}
/**
* @brief Initialize the DS1302 device
*
* @param void
* @return void
*/
void ds1302_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Enable DWT for microseconds delay */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= 1 ;
DWT->CYCCNT = 0;
/* Initialize SCLK, SDA and RST as output, pull push and speed high */
GPIO_InitStructure.Pin = DS1302_PIN_SCLK | DS1302_PIN_SDA | DS1302_PIN_RST;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(DS1302_GPIO_PORT, &GPIO_InitStructure);
set_idle_state();
}
/**
* @brief Read mode: SDA pin is set as input
* @param void
* @return void
*/
static void set_read_mode(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin = DS1302_PIN_SDA;
GPIO_InitStructure.Pull = GPIO_PULLDOWN;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(DS1302_GPIO_PORT, &GPIO_InitStructure);
}
/**
* @brief Write mode: SDA pin is set as ouput
* @param void
* @return void
*/
static void set_write_mode(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin = DS1302_PIN_SDA;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(DS1302_GPIO_PORT, &GPIO_InitStructure);
}
/**
* @brief write a command/address on SDA line
*
* @param bit you being written
* @return void
*/
static void write_cmd(uint8_t cmd)
{
uint8_t i;
/* RST high to start communication and write cmd/address */
set_rst();
for (i = 0; i < DS1302_CMD_SIZE; i++)
{
(cmd & 1) ? set_data() : reset_data();
set_clk_cycle();
cmd >>= 1;
}
}
/**
* @brief write a byte following serial communication specified in the spec
*
* @param addr where you want write to
* @param data that will write to address
* @return void
*/
static void write_data(uint8_t addr, uint8_t data)
{
uint8_t i;
write_cmd(addr);
/* Write data bit by bit */
for (i = 0; i < DS1302_DATA_SIZE; i++)
{
(data & 1) ? set_data() : reset_data();
set_clk_cycle();
data >>= 1;
}
set_idle_state();
}
static GPIO_PinState read_data_pin(void)
{
return HAL_GPIO_ReadPin(DS1302_GPIO_PORT, DS1302_PIN_SDA);
}
/**
* @brief read a byte following serial communication specified in the spec
*
* @param addr address you want to read from
* @return byte read
*/
static uint8_t read_data(uint8_t addr)
{
uint8_t i;
uint8_t data = 0;
/* Make sure LSB bit is high for reading an address */
addr |= 0x1;
write_cmd(addr);
set_read_mode();
for (i = 0; i < DS1302_DATA_SIZE; i++)
{
/* Ones come from the MSB to LSB
* by shifting data to the right
*/
if (read_data_pin() == GPIO_PIN_SET)
{
data |= 0x80;
}
set_clk_cycle();
if (i != (DS1302_DATA_SIZE - 1))
{
data >>= 1;
}
}
set_write_mode();
set_idle_state();
return data;
}
/**
* @brief Get time from calendar registers
*
* @param time pointer to time structure
* @return void
*/
void ds1302_get_time(Time_s *time)
{
uint8_t tmpMaskClockSystem = 0;
if (time == NULL)
{
return;
}
tmpMaskClockSystem = (time->clockSystem == DS1302_CLK_SYSTEM_24) ?
MASK_HOURS_24: MASK_HOURS_12;
time->min = BCD_TO_DEC(read_data(DS1302_REG_MIN));
time->day = BCD_TO_DEC(read_data(DS1302_REG_DAY));
time->year = BCD_TO_DEC(read_data(DS1302_REG_YEAR));
time->date = BCD_TO_DEC(read_data(DS1302_REG_DATE));
time->month = BCD_TO_DEC(read_data(DS1302_REG_MONTH));
time->sec = BCD_TO_DEC((read_data(DS1302_REG_SEC) & MASK_SECONDS));
time->hour = BCD_TO_DEC((read_data(DS1302_REG_HOUR) & tmpMaskClockSystem));
}
/**
* @brief set time into calendar registers
*
* @param time pointer to time structure
* @return void
*/
void ds1302_set_time(const Time_s *time)
{
uint8_t tmpMaskClockSystem = 0;
if (time == NULL)
{
return;
}
/* When 12 clock system is set, bit 7 should be high
* according to the spec, low for 24 clock system.
*/
if (time->clockSystem != DS1302_CLK_SYSTEM_24)
{
/* Set 12 hour clock system */
tmpMaskClockSystem |= MASK_CLOCK_SYSTEM;
if (time->clockPeriod == DS1302_CLK_PM_PERIOD)
{
/* Set PM clock period */
tmpMaskClockSystem |= MASK_CLOCK_PERIOD;
}
}
/* Enable write by driving protected bit to 0*/
write_data(DS1302_REG_CONTROL, 0);
/* Write time data to RTC calendar registers in BCD format */
write_data(DS1302_REG_MIN, DEC_TO_BCD(time->min));
write_data(DS1302_REG_SEC, DEC_TO_BCD(time->sec));
write_data(DS1302_REG_DAY, DEC_TO_BCD(time->day));
write_data(DS1302_REG_YEAR, DEC_TO_BCD(time->year));
write_data(DS1302_REG_DATE, DEC_TO_BCD(time->date));
write_data(DS1302_REG_MONTH, DEC_TO_BCD(time->month));
write_data(DS1302_REG_HOUR, DEC_TO_BCD(time->hour) | tmpMaskClockSystem);
/* Disable write by driving protected bit to 1 */
write_data(DS1302_REG_CONTROL, 0x80);
}
/**
* @brief Write to RAM, valid addresses 0 - 30
* @param addr address you want to write to
* @param data data to write into the address
* @return void
*/
void ds1302_write_ram(const uint8_t addr, uint8_t data)
{
if (addr > (DS1302_RAM_SIZE - 1))
{
return;
}
/* Enable write by driving protected bit to 0*/
write_data(DS1302_REG_CONTROL, 0);
/* Write addresses for RAM are multiple of 2 */
write_data(DS1302_RAM_ADDR_START + (2 * addr), data);
/* Disable write by driving protected bit to 1 */
write_data(DS1302_REG_CONTROL, 0x80);
}
/**
* @brief Read from RAM, valid addresses 0 - 30
* @param addr address you want to read from
* @return data read
*/
uint8_t ds1302_read_ram(const uint8_t addr)
{
if (addr > (DS1302_RAM_SIZE - 1))
{
return 0;
}
return read_data(DS1302_RAM_ADDR_START + (2 * addr));
}
/**
* @brief Clear the entire RAM addresses (0 - 30)
* @param void
* @return void
*/
void ds1302_clear_ram(void)
{
int i;
for (i = 0; i < DS1302_RAM_SIZE; i++)
{
ds1302_write_ram(i, 0);
}
}
/**
* @brief get clock system 12 or 24 format
* @param void
* @return ClockSystem
*/
ClockSystem ds1302_get_clock_system(void)
{
return (read_data(DS1302_REG_HOUR) & MASK_CLOCK_SYSTEM) ?
DS1302_CLK_SYSTEM_12 : DS1302_CLK_SYSTEM_24;
}
/**
* @brief get clock period AM or PM
* @param void
* @return ClockPeriod
*/
ClockPeriod ds1302_get_clock_period(void)
{
return (read_data(DS1302_REG_HOUR) & MASK_CLOCK_PERIOD) ?
DS1302_CLK_PM_PERIOD : DS1302_CLK_AM_PERIOD;
}
/**
* @brief read in burst mode
*
* @param addr address for ram or calendar
* @param len number of bytes to read
* @param buff buffer where data will be stored
* @return void
*/
static void read_burst(uint8_t addr, uint8_t len, uint8_t *buff)
{
uint8_t i, j;
/* Make sure LSB bit is high for reading an address */
addr |= 0x1;
write_cmd(addr);
set_read_mode();
for (i = 0; i < len; i++)
{
buff[i] = 0;
for (j = 0; j < DS1302_DATA_SIZE; j++)
{
/* Ones come from the MSB to LSB
* by shifting data to the right
*/
if (read_data_pin() == GPIO_PIN_SET)
{
buff[i] |= 0x80;
}
set_clk_cycle();
if (j != (DS1302_DATA_SIZE - 1))
{
buff[i] >>= 1;
}
}
}
set_write_mode();
set_idle_state();
}
/**
* @brief write in burst mode
*
* @param addr address for ram or calendar
* @param len number of addresses to write to
* @param buff buffer that will write to RTC
* @return void
*/
static void write_burst(uint8_t addr, uint8_t len, uint8_t *buff)
{
uint8_t i, j;
write_cmd(addr);
/* Write data bit by bit */
for (i = 0; i < len; i++)
{
for (j = 0; j < DS1302_DATA_SIZE; j++)
{
(buff[i] & 1) ? set_data() : reset_data();
set_clk_cycle();
buff[i] >>= 1;
}
}
set_idle_state();
}
/**
* @brief Write to RAM in burst mode
* @param len Number of addresses to write to
* @param buff Buffer that will write to RTC
* @return void
*/
void ds1302_write_ram_burst(uint8_t len, uint8_t *buff)
{
if (len > DS1302_RAM_SIZE)
{
return;
}
/* Enable write by driving protected bit to 0*/
write_data(DS1302_REG_CONTROL, 0);
/* Write addresses for RAM are multiple of 2 */
write_burst(D1302_RAM_BURST_MODE, len, buff);
/* Disable write by driving protected bit to 1 */
write_data(DS1302_REG_CONTROL, 0x80);
}
/**
* @brief Read RAM in burst mode
* @param len Number of addresses to read from RTC
* @param buff Buffer where data will be stored
*/
void ds1302_read_ram_burst(uint8_t len, uint8_t *buff)
{
if (len > DS1302_RAM_SIZE)
{
return;
}
memset(buff, 0, 1);
read_burst(D1302_RAM_BURST_MODE, len, buff);
}
```
:::
---
### 資料來源(驅動)
[GitHub 驅動程式來源](https://github.com/aaron-ev/driver-ds1302-stm32f4)