28. Thực nghiệm RTC (Real-Time Clock) 
Quy trình cấu hình RTC 
- Mở khóa bảo vệ ghi của miền dự phòng (Backup Domain): 
 Các thanh ghi lõi của RTC nằm trong miền dự phòng. Để thao tác, cần giải phóng bảo vệ ghi. Vì vậy, sau khi bật nguồn và reset, bước đầu tiên là mở khóa bảo vệ ghi của miền dự phòng.
- Cấu hình nguồn xung nhịp: 
 Trong ví dụ này, RTC sử dụng thạch anh ngoài tần số thấp 32.768 kHz làm nguồn clock. Do đó, khi cấu hình RTC, ta cần bật bộ dao động ngoài tốc độ thấp và chọn nó làm nguồn clock cho RTC.
- Kích hoạt ngoại vi RTC: 
 Bật xung nhịp RTC, chờ RTC hoàn tất quá trình đồng bộ và sẵn sàng hoạt động.
- Cấu hình thời gian – lịch: 
 Bao gồm năm, tháng, ngày, thứ, giờ, phút, giây.
Mở khóa bảo vệ ghi 
Các thanh ghi lõi của RTC nằm trong miền dự phòng (Backup Domain), miền này thuộc sự quản lý của PMU (Power Management Unit).
- Trước hết cần bật clock cho PMU.
- Sau đó cho phép quyền ghi vào các thanh ghi của miền dự phòng để có thể cấu hình RTC.

// Bật clock cho khối quản lý nguồn (Power Management Unit - PMU)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// Cho phép quyền ghi/đọc các thanh ghi trong miền dự phòng (Backup Domain)
PWR_BackupAccessCmd(ENABLE);Cấu hình nguồn xung nhịp (Clock Source) 
RTC có thể sử dụng 3 loại nguồn xung nhịp chính:
- IRC32K – dao động nội 32 kHz (Low-Speed Internal Clock): - Nếu dùng clock nội bộ này, khi hệ thống VDD bị mất nguồn, RTC cũng sẽ dừng, dẫn đến RTC không thể tiếp tục chạy.
 
- HSE – dao động ngoài tốc độ cao (2 ~ 31 MHz): - Nếu kết nối pin dự phòng vào chân VBAT, RTC vẫn có thể duy trì hoạt động ngay cả khi VDD mất nguồn.
- Tuy nhiên, việc dùng clock ngoài tốc độ cao sẽ gây tăng tiêu thụ công suất.
 
- LSE – dao động ngoài tốc độ thấp 32.768 kHz: - Hỗ trợ chạy RTC khi mất nguồn chính VDD nhờ VBAT.
- Tiêu thụ công suất thấp, rất phù hợp cho RTC.
 
👉 Trong ví dụ này, ta chọn LSE – thạch anh ngoài 32.768 kHz làm nguồn clock cho RTC.
 Trên bo mạch phát triển LCKFB·Liangshanpai SkyStar, đã tích hợp sẵn thạch anh ngoài 32.768 kHz (hoặc có thể tự hàn thêm).
// Bật bộ dao động ngoài 32.768 KHz
// Lưu ý: Phiên bản "Thanh xuân" (bản rút gọn) KHÔNG có hàn sẵn thạch anh thấp tần,
// nếu chọn LSE trong trường hợp này thì hệ thống sẽ treo ngay.
// Do đó với bản "Thanh xuân" cần dùng LSI làm nguồn clock.
// Sử dụng cho phiên bản rút gọn (Thanh xuân)
RCC_LSICmd(ENABLE);  
// Sử dụng cho phiên bản đầy đủ (có thạch anh ngoài 32.768 kHz)
// RCC_LSEConfig(RCC_LSE_ON);  
// Cấu hình nguồn clock cho RTC
// Phiên bản rút gọn dùng LSI
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);  
// Phiên bản đầy đủ dùng LSE
// RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);Kích hoạt ngoại vi RTC 
Hàm RCC_RTCCLKCmd() được dùng để bật xung nhịp cho mô-đun RTC.
 RTC là một ngoại vi độc lập, cung cấp chức năng đếm thời gian và đồng hồ với độ chính xác cao.
 Khi bật clock RTC, ta sẽ kích hoạt mô-đun RTC để có thể sử dụng các chức năng của nó.
Sau đó, hàm RTC_WaitForSynchro() được dùng để chờ quá trình đồng bộ hóa (synchronization) của các thanh ghi RTC.
 Việc này rất quan trọng bởi vì:
- Các thiết lập RTC chỉ có hiệu lực sau khi đồng bộ hoàn tất.
- Đảm bảo các thao tác trước đó trên RTC đã thực sự được áp dụng.
RCC_RTCCLKCmd(ENABLE);   // Bật clock cho RTC
RTC_WaitForSynchro();    // Chờ đồng bộ thanh ghi RTCCấu hình thời gian – lịch (Calendar Time) 
Trong thư viện chuẩn của STM32, ta có thể sử dụng trực tiếp hai cấu trúc dữ liệu:
- RTC_TimeTypeDef– dùng để cấu hình thời gian (giờ, phút, giây).
- RTC_DateTypeDef– dùng để cấu hình ngày tháng (năm, tháng, ngày, thứ).


Sau khi cấu hình xong, ta cần gọi các hàm RTC_SetTime và RTC_SetDate để ghi giá trị đã cài đặt vào các thanh ghi của ngoại vi RTC.
⚠️ Lưu ý: Các tham số truyền vào phải được nhập ở dạng mã BCD (Binary-Coded Decimal).
Ví dụ: muốn đặt thời gian thành 18:10:01, Thứ Hai, ngày 11/03/2024, thì tham số truyền vào sẽ như sau:
RtcTimeConfig(24, 3, 11, 1, 18, 10, 1, RTC_H12_AM);/***********************************
 * Hàm      : RtcTimeConfig
 * Chức năng: Cấu hình ngày giờ cho RTC
 * 
 * @defgroup RTC_AM_PM_Definitions
 *
 *  #define RTC_H12_AM   ((uint8_t)0x00)   // Định nghĩa cho AM
 *  #define RTC_H12_PM   ((uint8_t)0x40)   // Định nghĩa cho PM
 ***********************************/
void RtcTimeConfig(uint8_t year, uint8_t month, uint8_t date, uint8_t week, \
                   uint8_t hour, uint8_t minute, uint8_t second, uint8_t RTC_H12)
{
    RTC_WriteProtectionCmd(DISABLE); // Tắt bảo vệ ghi (cho phép ghi vào thanh ghi RTC)
    RTC_InitTypeDef RTC_InitStructure;
    RTC_EnterInitMode();                          // Vào chế độ khởi tạo RTC
    RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24;  // Cấu hình định dạng 24 giờ
    RTC_InitStructure.RTC_AsynchPrediv = 0x7F;   // Bộ chia tần số bất đồng bộ
    RTC_InitStructure.RTC_SynchPrediv  = 0xFF;   // Bộ chia tần số đồng bộ
    RTC_Init(&RTC_InitStructure);
    // --------- Cấu hình Thời gian ----------
    RTC_TimeTypeDef RTC_TimeStructure;
    RTC_TimeStructure.RTC_Seconds = second;  // Giây
    RTC_TimeStructure.RTC_Minutes = minute;  // Phút
    RTC_TimeStructure.RTC_Hours   = hour;    // Giờ
    RTC_TimeStructure.RTC_H12     = RTC_H12; // AM/PM hoặc 24h
    RTC_SetTime(RTC_Format_BIN, &RTC_TimeStructure);
    // --------- Cấu hình Ngày tháng ----------
    RTC_DateTypeDef RTC_DateStructure;
    RTC_DateStructure.RTC_Date   = date;   // Ngày
    RTC_DateStructure.RTC_Month  = month;  // Tháng
    RTC_DateStructure.RTC_WeekDay= week;   // Thứ (1 = Thứ 2, 7 = Chủ Nhật)
    RTC_DateStructure.RTC_Year   = year;   // Năm (dạng 2 chữ số, ví dụ 24 = 2024)
    RTC_SetDate(RTC_Format_BIN, &RTC_DateStructure);
    RTC_ExitInitMode();                               // Thoát chế độ khởi tạo RTC
    RTC_WriteBackupRegister(RTC_BKP_DR0, 0x2002);     // Ghi cờ báo "RTC đã khởi tạo xong"
    RTC_WriteProtectionCmd(ENABLE);                   // Bật lại bảo vệ ghi
}Đọc thời gian từ RTC 
Ta có thể sử dụng các hàm RTC_GetTime và RTC_GetDate để lấy thông tin thời gian – ngày tháng được lưu trong các thanh ghi của RTC.
/***********************************
 * Hàm      : RtcShowTime
 * Chức năng: Đọc thời gian & ngày tháng từ RTC,
 *            sau đó in ra UART ở dạng nhị phân (BIN).
 ***********************************/
void RtcShowTime(void)
{
    // Cấu trúc lưu trữ Thời gian
    RTC_TimeTypeDef RTC_TimeStructure;
    // Cấu trúc lưu trữ Ngày tháng
    RTC_DateTypeDef RTC_DateStructure;
    // Lấy thông tin thời gian hiện tại từ RTC
    RTC_GetTime(RTC_Format_BIN, &RTC_TimeStructure);
    // Lấy thông tin ngày tháng hiện tại từ RTC
    RTC_GetDate(RTC_Format_BIN, &RTC_DateStructure);
    // In ra UART theo định dạng: Giờ:Phút:Giây  Năm-Tháng-Ngày
    printf("Current time: %02d:%02d:%02d  ",
           RTC_TimeStructure.RTC_Hours,
           RTC_TimeStructure.RTC_Minutes,
           RTC_TimeStructure.RTC_Seconds);
    printf("20%02d-%02d-%02d\n\r",
           RTC_DateStructure.RTC_Year,
           RTC_DateStructure.RTC_Month,
           RTC_DateStructure.RTC_Date);
}RTC Mã nguồn đầy đủ 
rtc.c
/*
 * Tài liệu phần cứng & phần mềm của bo mạch phát triển LCKFB
 * Trang chính thức: www.lckfb.com
 * Diễn đàn kỹ thuật: https://oshwhub.com/forum
 * Bilibili: 【立创开发板】
 * Mục tiêu: không kiếm tiền từ việc bán bo mạch, mà đào tạo kỹ sư Trung Quốc
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-03-11     LCKFB-LP     first version
 */
#include "rtc.h"
#include "bsp_uart.h"
#include "stdio.h"
void bsp_rtc_init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    PWR_BackupAccessCmd(ENABLE); // Cho phép truy cập thanh ghi dự phòng
    RCC_LSICmd(ENABLE); // Bật dao động nội LSI (32 kHz)
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
    RCC_RTCCLKCmd(ENABLE); // Bật clock RTC
    RTC_WaitForSynchro();  // Chờ đồng bộ RTC
    // Kiểm tra xem RTC đã được cấu hình chưa
    if (RTC_ReadBackupRegister(RTC_BKP_DR0) != 0x2002)
    {
        printf("Set Time and Date.....\r\n");
        RtcTimeConfig(24, 3, 11, 1, 18, 10, 1, RTC_H12_AM);
        printf("Set End!!\r\n");
    }
    PWR_BackupAccessCmd(DISABLE); // Đóng quyền truy cập thanh ghi dự phòng
}
/***********************************
 * Hàm      : RtcTimeConfig
 * Chức năng: Thiết lập thời gian và ngày tháng
 * @defgroup RTC_AM_PM_Definitions
 *
 *  #define RTC_H12_AM   ((uint8_t)0x00)
 *  #define RTC_H12_PM   ((uint8_t)0x40)
 ***********************************/
void RtcTimeConfig(uint8_t year, uint8_t month, uint8_t date, uint8_t week, \
                   uint8_t hour, uint8_t minute, uint8_t second, uint8_t RTC_H12)
{
    RTC_WriteProtectionCmd(DISABLE); // Tắt bảo vệ ghi
    RTC_InitTypeDef RTC_InitStructure;
    RTC_EnterInitMode(); // Vào chế độ khởi tạo RTC
    RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24; // Định dạng 24h
    RTC_InitStructure.RTC_AsynchPrediv = 0x7F;
    RTC_InitStructure.RTC_SynchPrediv  = 0xFF;
    RTC_Init(&RTC_InitStructure);
    // ---- Thiết lập thời gian ----
    RTC_TimeTypeDef RTC_TimeStructure;
    RTC_TimeStructure.RTC_Seconds = second;
    RTC_TimeStructure.RTC_Minutes = minute;
    RTC_TimeStructure.RTC_Hours   = hour;
    RTC_TimeStructure.RTC_H12     = RTC_H12;
    RTC_SetTime(RTC_Format_BIN, &RTC_TimeStructure);
    // ---- Thiết lập ngày tháng ----
    RTC_DateTypeDef RTC_DateStructure;
    RTC_DateStructure.RTC_Date    = date;
    RTC_DateStructure.RTC_Month   = month;
    RTC_DateStructure.RTC_WeekDay = week;
    RTC_DateStructure.RTC_Year    = year;
    RTC_SetDate(RTC_Format_BIN, &RTC_DateStructure);
    RTC_ExitInitMode();                                // Thoát chế độ khởi tạo
    RTC_WriteBackupRegister(RTC_BKP_DR0, 0x2002);      // Ghi cờ "RTC đã khởi tạo"
    RTC_WriteProtectionCmd(ENABLE);                    // Bật lại bảo vệ ghi
}
/**********************************************************
 * Hàm      : BcdToDecimal
 * Chức năng: Chuyển đổi BCD sang thập phân
 * Tham số  : bcd = giá trị ở dạng BCD
 * Trả về   : giá trị thập phân sau khi chuyển đổi
 * Tác giả  : LCKFB
 * Ghi chú  : Không có
 **********************************************************/
int BcdToDecimal(int bcd)
{
    int decimal = 0;
    int temp = 1;
    int number = 0;
    if (bcd >= 0x0A) // Nếu lớn hơn hoặc bằng 10
    {
        while (bcd > 0)
        {
            number = bcd % 16;
            decimal += number * temp;
            temp *= 10;
            bcd /= 16;
        }
        return decimal;
    }
    return bcd;
}
/**
 * Hàm      : RtcShowTime
 * Chức năng: Đọc thời gian từ RTC và in ra UART
 */
void RtcShowTime(void)
{
    // Cấu trúc lưu thời gian
    RTC_TimeTypeDef RTC_TimeStructure;
    // Cấu trúc lưu ngày tháng
    RTC_DateTypeDef RTC_DateStructure;
    // Lấy thời gian hiện tại từ RTC
    RTC_GetTime(RTC_Format_BIN, &RTC_TimeStructure);
    // Lấy ngày tháng hiện tại từ RTC
    RTC_GetDate(RTC_Format_BIN, &RTC_DateStructure);
    // In thời gian qua UART
    printf("Current time: %d:%d:%d  ",
           RTC_TimeStructure.RTC_Hours,
           RTC_TimeStructure.RTC_Minutes,
           RTC_TimeStructure.RTC_Seconds);
    printf("%d-%d-%d\n\r",
           RTC_DateStructure.RTC_Year,
           RTC_DateStructure.RTC_Month,
           RTC_DateStructure.RTC_Date);
}rtc.h
/*
 Change Logs:
 * Date           Author       Notes
 * 2024-03-11     LCKFB-LP    first version
 */
#ifndef __RTC_H__
#define __RTC_H__
#include "stm32f4xx.h"
void bsp_rtc_init(void);
void RtcShowTime(void);
void RtcTimeConfig(uint8_t year, uint8_t month, uint8_t date, uint8_t week, \
uint8_t hour, uint8_t minute, uint8_t second, uint8_t RTC_H12);
#endifThực nghiệm kiểm chứng 
Trong file main.c, viết như sau:
/*
 * Tài liệu phần cứng & phần mềm của bo mạch phát triển LCKFB
 * Trang chính thức: www.lckfb.com
 * Diễn đàn kỹ thuật: https://oshwhub.com/forum
 * Bilibili: 【立创开发板】
 * Mục tiêu: không kiếm tiền từ việc bán bo mạch, mà đào tạo kỹ sư Trung Quốc
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-03-11     LCKFB-LP     first version
 */
#include "board.h"
#include "bsp_uart.h"
#include <stdio.h>
#include "rtc.h"
int main(void)
{
    board_init();
    uart1_init(115200U);
    printf("Bắt đầu khởi tạo RTC.....\r\n");
    bsp_rtc_init(); // Khởi tạo RTC
    printf("Khởi tạo RTC thành công!!\r\n");
    while(1)
    {
        // Lấy thời gian RTC và in ra qua UART
        RtcShowTime();
        delay_ms(1000);
    }
}Hiện tượng thí nghiệm 
Thực hiện thí nghiệm mất điện trong 9 giây, kết quả quan sát được:
 sau khi rút cáp USB, RTC vẫn tiếp tục đếm giờ nhờ pin dự phòng.
🔔 Lưu ý: Khi tiến hành thí nghiệm mất điện, cần hàn thêm đế pin nút (coin cell holder) ở mặt sau bo mạch, sau đó lắp một viên pin nút phù hợp.

Vị trí chứa mã nguồn của chương này (RTC 实时时钟实验):
Trong đường dẫn tài liệu của bo mạch phát triển, mã nguồn đầy đủ cho phần "Thí nghiệm RTC (có thể thử nghiệm mất điện)" nằm trong thư mục sau:
Lịch sử phát triển của bo mạch LCKFB·Liangshanpai·SkyStar STM32F407VET6
     └── Chương 03 – Phần mềm
          └── Mẫu mã nguồn
               └── 015 RTC thực thời gian (có thể làm thí nghiệm mất điện)Và được lưu trữ trong Baidu Netdisk cùng hướng dẫn từ LCKFB.