Skip to content

Chapter 15

Hongjayeong-02 edited this page Oct 5, 2026 · 1 revision

15. Real-Time Clock (RTC)

Goal

STM32F439ZI의 RTC peripheral을 LSI clock으로 동작시키고, 현재 시간과 날짜를 UART로 출력한다.

이번 실습:

LSI
 ↓
RTC Clock
 ↓
Prescaler
 ↓
1 Hz Calendar Clock
 ↓
RTC Time / Date
 ↓
UART
 ↓
PC Terminal

실제 board에서 다음까지 확인했다.

Time count     : PASS
1-second tick  : PASS
Date rollover  : PASS

09-19 ↓ 09-20


1. RTC vs General-Purpose Timer

Ch09 TIM2:

System / Peripheral Clock
↓
Timer Counter
↓
Periodic Event

RTC:

Low-Speed Clock
↓
Backup Domain
↓
Calendar Hardware
↓
Second / Minute / Hour
Day / Month / Year

RTC는 단순 counter가 아니라 시간과 날짜를 관리하는 calendar peripheral이다.


2. Backup Domain

RTC는 일반 peripheral과 달리 Backup Domain에 존재한다.

따라서 바로 register를 수정할 수 없다.

먼저:

PWR Clock Enable
↓
DBP = 1
↓
Backup Domain Write Access

순서가 필요하다.

핵심:

RCC->APB1ENR |= PWREN;
PWR->CR |= CR_DBP;

3. RTC Clock Source

RTC는 여러 clock source를 사용할 수 있다.

LSE
→ External 32.768 kHz crystal

LSI → Internal low-speed RC oscillator

HSE → External high-speed clock division

이번 실습에서는:

LSI

를 사용한다.

장점:

External crystal 불필요
배선 불필요
간단한 실습에 적합

단점:

LSE보다 정확도가 낮음

즉 실제 정밀 RTC application에서는 보통 LSE가 더 적합하다.


4. RTC Clock Flow

이번 실습의 설정:

LSI ≈ 32 kHz
↓
Asynchronous Prescaler
↓
Synchronous Prescaler
↓
≈ 1 Hz
↓
Calendar

사용값:

RTC_ASYNCH_PREDIV = 0x7F
= 127
→ divide by 128

RTC_SYNCH_PREDIV = 0x00F9 = 249 → divide by 250

따라서:

128 × 250

32000

LSI가 약 32kHz라는 가정에 맞춘 설정이다.


5. Important Registers

Register 역할
RTC->TR Hour / Minute / Second
RTC->DR Year / Month / Day / Weekday
RTC->CR RTC control / format
RTC->ISR Initialization / synchronization status
RTC->PRER Async / Sync prescaler
RTC->WPR Write protection
RCC->CSR LSI enable / ready
RCC->BDCR RTC clock select / RTC enable
PWR->CR Backup domain write access

6. BCD

RTC의 시간과 날짜는 일반 binary integer가 아니라 BCD 형태로 저장된다.

예:

Decimal 59

일반 binary:

0x3B

BCD:

0x59

BCD에서는 각 decimal digit을 4bit씩 저장한다.

5 → 0101
9 → 1001

59 → 0101 1001 → 0x59

따라서 RTC register에:

0x59

가 보이면 실제 값:

59

로 읽을 수 있다.


7. BCD to Decimal

원본 실습 코드:

uint8_t rtc_convert_bcd2dec(uint8_t value)
{
return (uint8_t)(((value >> 4U) * 10U)
+ (value & 0x0FU));
}

예:

value = 0x59

상위 nibble:

0x5 × 10

50

하위 nibble:

0x9

9

결과:

50 + 9

59


8. RTC Write Protection

RTC 주요 register는 실수로 변경되는 것을 방지하기 위해 write protection이 걸려 있다.

Unlock sequence:

RTC->WPR = 0xCA;
RTC->WPR = 0x53;

순서가 중요하다.

0xCA
↓
0x53
↓
RTC Write Protection Disabled

설정이 끝난 후:

RTC->WPR = 0xFF;

로 write protection을 다시 활성화한다.


9. Practice Code Status

현재 Notion에 전체가 명확히 남아 있는 핵심:

rtc_init()
rtc_convert_bcd2dec()
main()

다음 helper는 호출 구조와 역할은 남아 있지만 전체 함수 본문은 현재 기록에 보존되어 있지 않다.

rtc_init_seq()
exit_init_seq()

rtc_date_config() rtc_time_config()

rtc_set_asynch_prescaler() rtc_set_synch_prescaler()

display_rtc_calendar()

따라서 없는 원본 코드를 임의로 만들어 넣지는 않는다.


10. rtc.h

#ifndef RTC_H__
#define RTC_H__

#include <stdint.h> #include "stm32f4xx.h"

/*

  • Initialize RTC using the LSI clock. */ void rtc_init(void);

/*

  • Convert one BCD encoded value
  • into normal decimal integer form.
  • Example:
  • 0x59 → 59
    

*/ uint8_t rtc_convert_bcd2dec(uint8_t value);

/*

  • Read RTC time/date registers and
  • print current calendar data through UART.
  • Full archived implementation body
  • is not currently available. */ void display_rtc_calendar(void);

#endif


11. RTC Definitions

#include "rtc.h"

/*

  • ============================================================
  • RCC / PWR Control Bits
  • ============================================================ */

/*

  • APB1 PWR peripheral clock enable. */ #define PWREN (1U << 28)

/*

  • PWR CR DBP:
  • Enable write access to Backup Domain. */ #define CR_DBP (1U << 8)

/*

  • RCC CSR LSION:
  • Enable internal low-speed oscillator. */ #define CSR_LSION (1U << 0)

/*

  • RCC CSR LSIRDY:
  • Indicates LSI oscillator is stable. */ #define CSR_LSIRDY (1U << 1)

/*

  • Backup Domain Reset. */ #define BDCR_BDRST (1U << 16)

/*

  • RTC Enable. */ #define BDCR_RTCEN (1U << 15)

/*

  • ============================================================
  • RTC Write Protection
  • ============================================================ */

#define RTC_WRITE_PROTECTION_KEY_1 0xCAU #define RTC_WRITE_PROTECTION_KEY_2 0x53U

/*

  • ============================================================
  • Prescaler
  • ============================================================ */

#define RTC_ASYNCH_PREDIV 0x7FU #define RTC_SYNCH_PREDIV 0x00F9U


12. rtc_init()

void rtc_init(void)
{
/*
* --------------------------------------------------------
* Step 1. Enable PWR Peripheral Clock
* --------------------------------------------------------
*
* RTC belongs to the Backup Domain.
*
* Backup-domain write permission is controlled
* through the PWR peripheral.
*/
RCC->APB1ENR |= PWREN;
/*
 * --------------------------------------------------------
 * Step 2. Enable Backup Domain Write Access
 * --------------------------------------------------------
 *
 * DBP = Disable Backup-domain Protection.
 *
 * DBP = 1
 *
 * allows RTC / backup domain registers
 * to be modified.
 */
PWR-&gt;CR |= CR_DBP;


/*
 * --------------------------------------------------------
 * Step 3. Enable LSI
 * --------------------------------------------------------
 *
 * LSI:
 *
 *     Low-Speed Internal RC oscillator
 *
 * Used as the RTC source in this project.
 */
RCC-&gt;CSR |= CSR_LSION;


/*
 * --------------------------------------------------------
 * Step 4. Wait Until LSI Is Stable
 * --------------------------------------------------------
 *
 * LSIRDY = 1
 *
 * means the LSI oscillator is ready.
 *
 * RTC clock should not be configured until
 * the selected source clock is stable.
 */
while ((RCC-&gt;CSR &amp; CSR_LSIRDY) != CSR_LSIRDY)
{
}


/*
 * --------------------------------------------------------
 * Step 5. Reset Backup Domain
 * --------------------------------------------------------
 *
 * BDRST = 1
 *
 * resets RTC / backup-domain configuration.
 */
RCC-&gt;BDCR |= BDCR_BDRST;


/*
 * Release reset.
 */
RCC-&gt;BDCR &amp;= ~BDCR_BDRST;


/*
 * --------------------------------------------------------
 * Step 6. Select LSI as RTC Clock
 * --------------------------------------------------------
 *
 * RTCSEL field is configured to:
 *
 *     10b
 *
 * for LSI.
 *
 * Existing code performs this by:
 *
 * bit8 = 0
 * bit9 = 1
 */
RCC-&gt;BDCR &amp;= ~(1U &lt;&lt; 8);
RCC-&gt;BDCR |=  (1U &lt;&lt; 9);


/*
 * --------------------------------------------------------
 * Step 7. Enable RTC
 * --------------------------------------------------------
 */
RCC-&gt;BDCR |= BDCR_RTCEN;


/*
 * --------------------------------------------------------
 * Step 8. Disable RTC Write Protection
 * --------------------------------------------------------
 *
 * Hardware-defined unlock sequence:
 *
 *     0xCA
 *       ↓
 *     0x53
 */
RTC-&gt;WPR = RTC_WRITE_PROTECTION_KEY_1;
RTC-&gt;WPR = RTC_WRITE_PROTECTION_KEY_2;


/*
 * --------------------------------------------------------
 * Step 9. Enter RTC Initialization Mode
 * --------------------------------------------------------
 *
 * RTC calendar registers must be configured
 * while the RTC is in initialization mode.
 *
 * Full helper body is not preserved
 * in the current archive.
 */
rtc_init_seq();


/*
 * --------------------------------------------------------
 * Step 10. Configure Date
 * --------------------------------------------------------
 *
 * Existing practice value:
 *
 *     Weekday : Friday
 *     Date    : 19
 *     Month   : 09
 *     Year    : 26
 *
 * Values are BCD encoded.
 */
rtc_date_config(
    WEEKDAY_FRIDAY,
    0x19,
    0x09,
    0x26
);


/*
 * --------------------------------------------------------
 * Step 11. Configure Time
 * --------------------------------------------------------
 *
 * Existing practice:
 *
 *     11:59:55 PM
 *
 * Starting close to midnight allows
 * date rollover to be tested quickly.
 */
rtc_time_config(
    TIME_FORMAT_PM,
    0x11,
    0x59,
    0x55
);


/*
 * --------------------------------------------------------
 * Step 12. RTC Time Format
 * --------------------------------------------------------
 *
 * IMPORTANT:
 *
 * The archived Notion source contains:
 *
 *     RTC-&gt;CR |= CR_FMT;
 *
 * together with the comment "24-hour format".
 *
 * However the same source calls:
 *
 *     TIME_FORMAT_PM
 *
 * so the archived notes are internally inconsistent
 * regarding 12-hour / 24-hour format.
 *
 * Keep this as a re-test item and verify CR.FMT
 * against the STM32F439 RTC Reference Manual
 * before finalizing the driver.
 */
RTC-&gt;CR |= CR_FMT;


/*
 * --------------------------------------------------------
 * Step 13. Configure Asynchronous Prescaler
 * --------------------------------------------------------
 *
 * 0x7F = 127
 *
 * Actual division:
 *
 *     127 + 1
 *     = 128
 */
rtc_set_asynch_prescaler(
    RTC_ASYNCH_PREDIV
);


/*
 * --------------------------------------------------------
 * Step 14. Configure Synchronous Prescaler
 * --------------------------------------------------------
 *
 * 0xF9 = 249
 *
 * Actual division:
 *
 *     249 + 1
 *     = 250
 */
rtc_set_synch_prescaler(
    RTC_SYNCH_PREDIV
);


/*
 * --------------------------------------------------------
 * Step 15. Exit Initialization Mode
 * --------------------------------------------------------
 *
 * Calendar counting starts/resumes.
 *
 * Helper also waits for register synchronization
 * in the original experiment flow.
 */
exit_init_seq();


/*
 * --------------------------------------------------------
 * Step 16. Re-enable RTC Write Protection
 * --------------------------------------------------------
 */
RTC-&gt;WPR = 0xFF;

}


13. Why Start at 11:59:55 PM?

이 값은 임의로 고른 것이 아니라 날짜 rollover를 빠르게 검증하기 위해 유리한 test condition이다.

11:59:55 PM
↓
11:59:56
↓
11:59:57
↓
11:59:58
↓
11:59:59
↓
12:00:00

동시에 날짜:

09-19
↓
09-20

이 되는지 확인할 수 있다.

즉 단순히 RTC가 증가하는 것뿐 아니라:

Second rollover
Minute rollover
Hour rollover
Date rollover

를 한 번에 검증할 수 있다.


14. Initialization Mode

RTC는 계속 시간을 count하고 있기 때문에 calendar register를 동작 중에 아무 때나 수정하면 안 된다.

설정 과정:

Normal RTC Operation
↓
Enter Init Mode
↓
Calendar Counter Paused
↓
Configure
TR / DR / PRER / CR
↓
Exit Init Mode
↓
RTC Counting Starts

이 과정을 helper:

rtc_init_seq()
exit_init_seq()

가 담당한다.


15. Why Wait for Flags?

Bare-metal peripheral initialization에서는:

Write configuration

만 하고 바로 다음 단계로 넘어가면 안 되는 경우가 많다.

예:

RCC->CSR |= CSR_LSION;

while ((RCC->CSR & CSR_LSIRDY) != CSR_LSIRDY) { }

이는:

LSI Enable
↓
Oscillator starting...
↓
Not Ready
↓
Not Ready
↓
LSIRDY = 1
↓
Continue

라는 hardware synchronization 과정이다.

이 패턴은 이후에도 반복된다.

Enable
↓
Wait Ready Flag
↓
Configure Next Stage

16. rtc_convert_bcd2dec()

uint8_t rtc_convert_bcd2dec(uint8_t value)
{
/*
* Upper nibble contains the tens digit.
*
* Example:
*
*     0x59 >> 4
*     =
*     0x05
*
*     5 × 10
*     =
*     50
*/
uint8_t tens =
(uint8_t)((value >> 4U) * 10U);
/*
 * Lower nibble contains the ones digit.
 *
 *     0x59 &amp; 0x0F
 *     =
 *     9
 */
uint8_t ones =
    (uint8_t)(value &amp; 0x0FU);


/*
 * 50 + 9 = 59
 */
return (uint8_t)(tens + ones);

}

원본의 한 줄 형태:

return (uint8_t)(((value >> 4U) * 10U)
+ (value & 0x0FU));

과 동일한 동작이다.


17. main.c

#include "rtc.h"
#include "uart.h"
#include "systick.h"

int main(void) { /* * -------------------------------------------------------- * Step 1. Initialize UART * -------------------------------------------------------- * * Current RTC date/time is printed * through USART3. */ uart_init();

/*
 * --------------------------------------------------------
 * Step 2. Initialize RTC
 * --------------------------------------------------------
 *
 * RTC setup:
 *
 *     Clock Source → LSI
 *     Prescaler
 *     Date
 *     Time
 */
rtc_init();


while (1)
{
    /*
     * ----------------------------------------------------
     * Step 3. Read and Display RTC Calendar
     * ----------------------------------------------------
     *
     * Expected information:
     *
     *     Hour
     *     Minute
     *     Second
     *     Month
     *     Day
     *     Year
     *
     * The complete archived implementation of
     * display_rtc_calendar() is not currently
     * preserved in the source notes.
     */
    display_rtc_calendar();


    /*
     * ----------------------------------------------------
     * Step 4. Print Approximately Once Per Second
     * ----------------------------------------------------
     *
     * SysTick driver from Ch08 is reused.
     */
    systick_msec_delay(1000);
}

}


18. Complete RTC Initialization Flow

rtc_init()
│
├── PWR Clock Enable
│
├── DBP = 1
│
├── LSI Enable
│
├── Wait LSIRDY
│
├── Backup Domain Reset
│
├── RTCSEL = LSI
│
├── RTC Enable
│
├── WPR Unlock
│
├── Enter Init Mode
│
├── Date Configure
│
├── Time Configure
│
├── Prescaler Configure
│
├── Exit Init Mode
│
└── WPR Lock

19. Runtime Flow

LSI
↓
RTC Prescaler
↓
1-second Calendar Tick
↓
TR / DR Update
↓
display_rtc_calendar()
↓
BCD → Decimal
↓
UART
↓
PC Terminal

20. TR — Time Register

RTC time register stores:

Hours
Minutes
Seconds

개념적으로:

TR
┌────────┬─────────┬─────────┐
│ Hour   │ Minute  │ Second  │
└────────┴─────────┴─────────┘

각 field는 BCD encoded value다.

예:

11:59:55

이면 register field들을 사람이 hex로 봤을 때:

11
59
55

처럼 읽기 쉽다.


21. DR — Date Register

RTC date register에는:

Year
Month
Date
Weekday

정보가 저장된다.

이번 초기값:

Year    = 0x26
Month   = 0x09
Date    = 0x19
Weekday = Friday

자정이 지나면 hardware calendar가 자동으로:

09-19
↓
09-20

으로 증가시킨다.


22. Shadow Register Note

RTC register read는 내부 shadow register와 관련된 synchronization이 존재한다.

기존 실습에서 reset 직후 순간적으로:

01-01-2000

처럼 초기값이 표시될 수 있다는 기록이 있었다.

그 후 RTC register synchronization이 끝나면 실제 설정한 calendar 값이 표시됐다.

따라서 reset 직후 한 번의 잘못된 화면만 보고:

RTC initialization failed

라고 바로 판단하면 안 된다.


23. Important Re-Test Item — CR.FMT

현재 보존된 실습 코드에는:

rtc_time_config(
TIME_FORMAT_PM,
0x11,
0x59,
0x55
);

RTC->CR |= CR_FMT;

가 있고, 기존 주석은:

24시간제

라고 되어 있다.

하지만:

TIME_FORMAT_PM

이라는 설정과 같은 코드 안에 존재하므로 12/24-hour format에 대한 기록이 내부적으로 일관되지 않는다.

따라서 재실습 시 반드시:

STM32F439 RTC CR.FMT definition

을 Reference Manual에서 다시 확인하고 최종 코드의:

FMT
AM/PM
Hour encoding

을 일치시킨다.

이 부분은 현재 Wiki에서 “검증 필요” 항목으로 남긴다.


24. Build Result

기존 실습:

Build    : PASS
Download : PASS
Run      : PASS

Program size:

text = 2260 bytes
data = 0 bytes
bss  = 2560 bytes

경고 없이 build 완료.


25. Board Result

실제 terminal에서:

Second
↓
Second
↓
Second

가 약 1초씩 증가하는 것을 확인했다.

또한:

09-19
11:59:59 PM

이후:

09-20
12:00:00

으로 날짜가 넘어가는 것을 확인했다.

즉:

Second increment PASS
Minute rollover PASS
Hour rollover PASS
Date rollover PASS

로 볼 수 있다.


26. Verification

1. Build

make clean
make

Expected:

text = 2260
data = 0
bss  = 2560

2. LSI

Debugger:

RCC->CSR

확인:

LSION  = 1
LSIRDY = 1

3. Backup Domain

확인:

PWR->CR

Expected:

DBP = 1

initialization 중 backup domain access가 허용되었는지 확인한다.


4. RTC Clock Source

확인:

RCC->BDCR

확인할 항목:

RTCSEL
RTCEN

Expected:

RTC Clock Source = LSI
RTC Enable       = 1

5. Prescaler

확인:

RTC->PRER

Expected:

Asynchronous = 0x7F
Synchronous  = 0x00F9

6. Time Register

Watch:

RTC->TR

초가 증가하는지 확인한다.

55
56
57
58
59
00

7. Date Register

자정 전:

RTC->DR
→ 09-19

자정 후:

RTC->DR
→ 09-20

인지 확인한다.


27. Screenshots to Add

images/ch15_build.png
images/ch15_lsi.png
images/ch15_rtc_registers.png
images/ch15_time_before_midnight.png
images/ch15_time_after_midnight.png
images/ch15_rollover_terminal.png

가장 중요한 evidence:

11:59:59 / 09-19
↓
12:00:00 / 09-20

terminal 화면이다.

이 한 장이:

RTC counting
+
Time rollover
+
Calendar rollover

를 동시에 보여준다.


28. Key Points

LSI
↓
Backup Domain
↓
RTC
↓
Prescaler
↓
Calendar
↓
BCD Time / Date
↓
UART
  • RTC는 일반 timer와 달리 calendar hardware다.

  • RTC는 Backup Domain에 존재한다.

  • RTC register를 수정하려면 DBP로 backup domain write access를 허용해야 한다.

  • 이번 실습은 LSI를 RTC clock source로 사용한다.

  • LSI를 enable한 후 LSIRDY를 기다린다.

  • RTC register write 전 0xCA → 0x53 key sequence로 write protection을 해제한다.

  • RTC 설정은 initialization mode에서 수행한다.

  • TR은 time, DR은 date를 저장한다.

  • RTC 값은 BCD 형식으로 저장된다.

  • 0x59 BCD는 decimal 59를 의미한다.

  • Prescaler는 0x7F / 0x00F9 설정을 사용했다.

  • 실제 board에서 1초 증가와 09-19 → 09-20 날짜 rollover를 검증 완료했다.

  • 기존 기록의 CR.FMT와 TIME_FORMAT_PM 조합은 재실습 시 다시 확인해야 한다.

Clone this wiki locally