# [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 設定 ![PinoutClear](https://imgur.com/VJNPOvV.png) --- ### GPIO設定 ![image](https://hackmd.io/_uploads/B1ZKsRP3yg.png) --- ### RCC設定 ![RccSet](https://imgur.com/7kPTJ9A.png) --- ### SYS設定 ![SysSet](https://imgur.com/rNjjVDv.png) --- ### UART設定 ![image](https://hackmd.io/_uploads/r1-Cs0vnJe.png) --- ### Clock設定 ![ClockSet](https://imgur.com/SlT0y70.png) --- ### 外設檔案分開+生成 ![GeneratorSet](https://imgur.com/Lo1x0Fc.png) #### 生成程式碼 ![CodeGenerator](https://imgur.com/LDLzPZb.png) --- ### Nucleo_Pinou ![nucleo_pinout](https://imgur.com/pW4VU4f.png) --- ### 電路圖 ![DS1302_circult](https://hackmd.io/_uploads/Hk1u4yO3yl.png) --- ## 實作 ### :::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(&currentTime); 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(&currentTime); 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)